Inital commit/ Base to the extension

main
Jeremy 2025-02-15 16:10:00 -06:00
parent 5c0dd9a556
commit 02ebd7b198
29 changed files with 6564 additions and 134 deletions

135
.gitignore vendored
View File

@ -1,132 +1,3 @@
# ---> Node
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
node_modules
.vscode-test/
*.vsix

5
.vscode-test.mjs Normal file
View File

@ -0,0 +1,5 @@
import { defineConfig } from '@vscode/test-cli';
export default defineConfig({
files: 'test/**/*.test.js',
});

8
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,8 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the extensions.json format
"recommendations": [
"dbaeumer.vscode-eslint",
"ms-vscode.extension-test-runner"
]
}

17
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,17 @@
// A launch configuration that launches the extension inside a new window
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
{
"version": "0.2.0",
"configurations": [
{
"name": "Run Extension",
"type": "extensionHost",
"request": "launch",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}"
]
}
]
}

10
.vscodeignore Normal file
View File

@ -0,0 +1,10 @@
.vscode/**
.vscode-test/**
test/**
.gitignore
.yarnrc
vsc-extension-quickstart.md
**/jsconfig.json
**/*.map
**/eslint.config.mjs
**/.vscode-test.*

9
CHANGELOG.md Normal file
View File

@ -0,0 +1,9 @@
# Change Log
All notable changes to the "93m1n1gpt" extension will be documented in this file.
Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
## [Unreleased]
- Initial release

View File

@ -1,3 +1,65 @@
# 93m1n1gpt
# 93m1n1gpt README
A local llm pair programming ai assistant.
This is the README for your extension "93m1n1gpt". After writing up a brief description, we recommend including the following sections.
## Features
Describe specific features of your extension including screenshots of your extension in action. Image paths are relative to this README file.
For example if there is an image subfolder under your extension project workspace:
\!\[feature X\]\(images/feature-x.png\)
> Tip: Many popular extensions utilize animations. This is an excellent way to show off your extension! We recommend short, focused animations that are easy to follow.
## Requirements
If you have any requirements or dependencies, add a section describing those and how to install and configure them.
## Extension Settings
Include if your extension adds any VS Code settings through the `contributes.configuration` extension point.
For example:
This extension contributes the following settings:
* `myExtension.enable`: Enable/disable this extension.
* `myExtension.thing`: Set to `blah` to do something.
## Known Issues
Calling out known issues can help limit users opening duplicate issues against your extension.
## Release Notes
Users appreciate release notes as you update your extension.
### 1.0.0
Initial release of ...
### 1.0.1
Fixed issue #.
### 1.1.0
Added features X, Y, and Z.
---
## Working with Markdown
You can author your README using Visual Studio Code. Here are some useful editor keyboard shortcuts:
* Split the editor (`Cmd+\` on macOS or `Ctrl+\` on Windows and Linux)
* Toggle preview (`Shift+Cmd+V` on macOS or `Shift+Ctrl+V` on Windows and Linux)
* Press `Ctrl+Space` (Windows, Linux, macOS) to see a list of Markdown snippets
## For more information
* [Visual Studio Code's Markdown Support](http://code.visualstudio.com/docs/languages/markdown)
* [Markdown Syntax Reference](https://help.github.com/articles/markdown-basics/)
**Enjoy!**

25
eslint.config.mjs Normal file
View File

@ -0,0 +1,25 @@
import globals from "globals";
export default [{
files: ["**/*.js"],
languageOptions: {
globals: {
...globals.commonjs,
...globals.node,
...globals.mocha,
},
ecmaVersion: 2022,
sourceType: "module",
},
rules: {
"no-const-assign": "warn",
"no-this-before-super": "warn",
"no-undef": "warn",
"no-unreachable": "warn",
"no-unused-vars": "warn",
"constructor-super": "warn",
"valid-typeof": "warn",
},
}];

50
extension.js Normal file
View File

@ -0,0 +1,50 @@
const vscode = require("vscode");
const { AISidebarProvider } = require('./src/utils/AISidebarProvider');
const LLMSelectorPanel = require('./src/settings/panels/llmSelector');
const SecureSettingsManager = require('./src/settings/secureSettingsManager');
const { RepoTracker } = require('./src/utils/RepoTracker');
async function activate(context) {
// Initialize secure settings manager first
const settingsManager = new SecureSettingsManager(context);
await settingsManager.initialize();
// Initialize RepoTracker
const repoTracker = new RepoTracker();
await repoTracker.initialize();
// Set up git watcher
const gitWatcher = await repoTracker.watchGitChanges();
context.subscriptions.push(gitWatcher);
// Initialize providers with secure settings manager and repoTracker
const provider = new AISidebarProvider(context.extensionUri, settingsManager, repoTracker);
// Register the sidebar provider
context.subscriptions.push(
vscode.window.registerWebviewViewProvider('C4B3Rstudios.93m1n1gpt', provider)
);
// Register the LLM selector command
let llmSelectorCommand = vscode.commands.registerCommand(
'C4B3Rstudios.93m1n1gpt.showLLMSelector',
() => {
const llmSelector = new LLMSelectorPanel(context.extensionUri, settingsManager);
llmSelector.createOrShow(vscode.ViewColumn.One);
}
);
// Register the main command
let mainCommand = vscode.commands.registerCommand('C4B3Rstudios.93m1n1gpt', () => {
vscode.window.showInformationMessage('93m1n1gpt is now active!');
});
context.subscriptions.push(llmSelectorCommand, mainCommand);
}
function deactivate() { }
module.exports = {
activate,
deactivate
};

14
jsconfig.json Normal file
View File

@ -0,0 +1,14 @@
{
"compilerOptions": {
"module": "Node16",
"target": "ES2022",
"checkJs": true, /* Typecheck .js files. */
"lib": [
"ES2022",
"DOM"
]
},
"exclude": [
"node_modules"
]
}

4692
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

83
package.json Normal file
View File

@ -0,0 +1,83 @@
{
"name": "93m1n1gpt",
"displayName": "93m1n1gpt",
"description": "Agent flow BYOLLM pair programming assistint ",
"publisher": "C4B3Rstudios",
"version": "0.0.1",
"engines": {
"vscode": "^1.97.0"
},
"categories": [
"Other"
],
"activationEvents": [],
"main": "./extension.js",
"contributes": {
"commands": [
{
"command": "C4B3Rstudios.93m1n1gpt",
"title": "Open 93m1n1GPT",
"category": "93m1n1GPT"
},
{
"command": "C4B3Rstudios.93m1n1gpt.showLLMSelector",
"title": "93m1n1GPT: Show LLM Selector"
}
],
"viewsContainers": {
"activitybar": [
{
"id": "93m1n1gpt-sidebar",
"title": "93m1n1GPT",
"icon": "resources/icon.svg"
}
]
},
"views": {
"93m1n1gpt-sidebar": [
{
"type": "webview",
"id": "C4B3Rstudios.93m1n1gpt",
"name": "93m1n1GPT Chat",
"icon": "resources/icon.svg"
}
]
},
"configuration": {
"title": "93m1n1GPT",
"properties": {
"93m1n1gpt.selectedModel": {
"type": "string",
"default": "llama3.2:3b",
"description": "Selected LLM model to use"
},
"93m1n1gpt.apiUrl": {
"type": "string",
"default": "http://localhost:11434",
"description": "Ollama API URL"
}
}
}
},
"scripts": {
"lint": "eslint .",
"pretest": "npm run lint",
"test": "vscode-test"
},
"devDependencies": {
"@types/mocha": "^10.0.10",
"@types/node": "20.x",
"@types/vscode": "^1.97.0",
"@vscode/test-cli": "^0.0.10",
"@vscode/test-electron": "^2.4.1",
"eslint": "^9.19.0"
},
"dependencies": {
"@langchain/community": "^0.3.29",
"@langchain/core": "^0.3.39",
"@langchain/ollama": "^0.2.0",
"highlight.js": "^11.11.1",
"markdown-it": "^14.1.0",
"marked": "^15.0.7"
}
}

5
resources/icon.svg Normal file
View File

@ -0,0 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20 12V22H4V2H20V12ZM18 14H6V20H18V14ZM18 4H6V12H18V4Z" fill="#424242"/>
<path d="M8 6H16V8H8V6Z" fill="#424242"/>
<path d="M8 16H16V18H8V16Z" fill="#424242"/>
</svg>

After

Width:  |  Height:  |  Size: 284 B

View File

@ -0,0 +1,63 @@
// ChatService.js
const vscode = require("vscode")
const { ChatOllama } = require("@langchain/ollama");
const { PromptTemplate } = require("@langchain/core/prompts");
class ChatService {
constructor(settingsManager) {
this._settingsManager = settingsManager;
}
async handleChatMessage(webviewView, userMessage, messageHistory, markdownRenderer, repoTracker) {
messageHistory.push({ role: "user", content: userMessage });
const config = this._getConfig();
const contextInfo = await this._getContextInfo(repoTracker);
const chat = this._createChatInstance(config);
await this._streamResponse(chat, messageHistory, webviewView, markdownRenderer);
}
_getConfig() {
const config = vscode.workspace.getConfiguration('93m1n1gpt');
return {
model: config.get('selectedModel') || "llama2:13b",
apiUrl: config.get('apiUrl') || "http://localhost:11434"
};
}
async _getContextInfo(repoTracker) {
const gitStatus = await repoTracker.getGitStatus();
return gitStatus ?
`\nCurrent git branch: ${gitStatus.branch}\nLast commit: ${gitStatus.lastCommit}` : '';
}
_createChatInstance(config) {
return new ChatOllama({
model: config.model,
baseUrl: config.apiUrl,
temperature: 0.6,
topP: 0.9,
topK: 40,
streaming: true
});
}
async _streamResponse(chat, messageHistory, webviewView, markdownRenderer) {
let currentStreamedText = "";
const stream = await chat.stream([...messageHistory]);
for await (const chunk of stream) {
currentStreamedText += chunk.content;
const formattedMarkdown = markdownRenderer.render(currentStreamedText);
webviewView.webview.postMessage({
command: "updateBotMessage",
html: formattedMarkdown
});
}
messageHistory.push({ role: "assistant", content: currentStreamedText });
}
}
module.exports = { ChatService };

View File

@ -0,0 +1,58 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ollama LLM Selection</title>
<!-- Include any necessary CSS frameworks or stylesheets here -->
</head>
<body>
<h1>Select an LLM</h1>
<p>Choose an LLM from the list below:</p>
<select id="llmSelector">
<!-- Options will be dynamically populated with available LLMs -->
</select>
<button id="selectButton">Select</button>
<script>
// Function to fetch available LLMs from Ollama
async function fetchAvailableLLMs() {
try {
const response = await fetch('https://api.ollama.ai/tags');
if (!response.ok) {
throw new Error('Failed to fetch available LLMs');
}
const data = await response.json();
return data.tags;
} catch (error) {
console.error('Error fetching available LLMs:', error);
return [];
}
}
// Function to populate the LLM selector with options
async function populateLLMSelector() {
const llms = await fetchAvailableLLMs();
const llmSelector = document.getElementById('llmSelector');
llms.forEach(llm => {
const option = document.createElement('option');
option.value = llm;
option.textContent = llm;
llmSelector.appendChild(option);
});
}
// Event listener for the "Select" button
document.getElementById('selectButton').addEventListener('click', () => {
const selectedLLM = document.getElementById('llmSelector').value;
// Here, you can perform actions based on the selected LLM, such as sending it to the extension.
console.log('Selected LLM:', selectedLLM);
// You can also send a message to the extension to set up the selected LLM.
});
// Call the function to populate the LLM selector
populateLLMSelector();
</script>
</body>
</html>

View File

@ -0,0 +1,348 @@
const vscode = require('vscode');
class LLMSelectorPanel {
constructor(extensionUri, settingsManager) {
this.extensionUri = extensionUri;
this.panel = null;
this.settingsManager = settingsManager;
}
async createOrShow(viewColumn) {
if (this.panel) {
this.panel.reveal(viewColumn);
} else {
this.panel = vscode.window.createWebviewPanel(
'C4B3Rstudios.93m1n1gpt.llmSelector',
'93m1n1GPT LLM Selector',
viewColumn,
{
enableScripts: true,
localResourceRoots: [vscode.Uri.joinPath(this.extensionUri, 'media')]
}
);
this.panel.webview.html = await this.getWebviewContent(this.panel.webview);
this.panel.webview.onDidReceiveMessage(
async message => {
switch (message.command) {
case 'selectLLM':
await vscode.workspace.getConfiguration('93m1n1gpt').update(
'selectedModel',
message.model,
vscode.ConfigurationTarget.Global
);
vscode.window.showInformationMessage(`Selected LLM: ${message.model}`);
this.panel.dispose();
break;
case 'updateApiUrl':
await this.settingsManager.setSecure('apiUrl', message.url);
vscode.window.showInformationMessage('API URL updated securely');
break;
}
}
);
this.panel.onDidDispose(() => {
this.panel = null;
});
}
}
async getWebviewContent(webview) {
try {
const apiUrl = await this.settingsManager.getSecure('apiUrl') || 'http://localhost:11434';
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>93m1n1GPT LLM Selection</title>
<style>
body {
padding: 20px;
font-family: var(--vscode-font-family);
color: var(--vscode-foreground);
line-height: 1.6;
}
.container {
max-width: 800px;
margin: 0 auto;
}
.card {
background: var(--vscode-editor-background);
border: 1px solid var(--vscode-panel-border);
border-radius: 6px;
padding: 20px;
margin-bottom: 20px;
}
h1 {
font-size: 24px;
font-weight: 600;
color: var(--vscode-foreground);
margin: 0 0 20px 0;
border-bottom: 1px solid var(--vscode-panel-border);
padding-bottom: 10px;
}
h2 {
font-size: 18px;
font-weight: 600;
color: var(--vscode-foreground);
margin: 20px 0 15px 0;
}
.input-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 8px;
color: var(--vscode-foreground);
font-weight: 500;
}
input[type="text"], select {
width: 100%;
padding: 8px 12px;
background: var(--vscode-input-background);
color: var(--vscode-input-foreground);
border: 1px solid var(--vscode-input-border);
border-radius: 4px;
font-size: 14px;
margin-bottom: 10px;
}
input[type="text"]:focus, select:focus {
outline: none;
border-color: var(--vscode-focusBorder);
}
select {
height: 36px;
}
button {
background: var(--vscode-button-background);
color: var(--vscode-button-foreground);
border: none;
padding: 8px 16px;
border-radius: 4px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: background-color 0.2s;
}
button:hover {
background: var(--vscode-button-hoverBackground);
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.status {
margin-top: 10px;
padding: 10px;
border-radius: 4px;
font-size: 14px;
}
.status.error {
background: var(--vscode-inputValidation-errorBackground);
border: 1px solid var(--vscode-inputValidation-errorBorder);
color: var(--vscode-inputValidation-errorForeground);
}
.status.info {
background: var(--vscode-infoBackground);
border: 1px solid var(--vscode-infoBorder);
color: var(--vscode-infoForeground);
}
.button-container {
display: flex;
gap: 10px;
margin-top: 15px;
}
.model-count {
font-size: 12px;
color: var(--vscode-descriptionForeground);
margin-top: 5px;
}
.spinner {
border: 2px solid var(--vscode-input-background);
border-top: 2px solid var(--vscode-focusBorder);
border-radius: 50%;
width: 16px;
height: 16px;
animation: spin 1s linear infinite;
display: inline-block;
vertical-align: middle;
margin-right: 8px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
</head>
<body>
<div class="container">
<div class="card">
<h1>93m1n1GPT Configuration</h1>
<div class="input-group">
<label for="apiUrl">Ollama API URL</label>
<input type="text"
id="apiUrl"
value="${apiUrl}"
placeholder="Enter Ollama API URL"
/>
<button id="saveApiUrl">Save API URL</button>
</div>
<h2>LLM Model Selection</h2>
<div class="input-group">
<label for="llmSelector">Available Models</label>
<select id="llmSelector">
<option value="">Loading available models...</option>
</select>
<div class="model-count" id="modelCount"></div>
</div>
<div id="status" class="status" style="display: none;"></div>
<div class="button-container">
<button id="selectButton" disabled>Select Model</button>
<button id="refreshModels">
<span id="refreshSpinner" class="spinner" style="display: none;"></span>
Refresh Models
</button>
</div>
</div>
</div>
<script>
const vscode = acquireVsCodeApi();
const llmSelector = document.getElementById('llmSelector');
const selectButton = document.getElementById('selectButton');
const statusDiv = document.getElementById('status');
const apiUrlInput = document.getElementById('apiUrl');
const saveApiUrlButton = document.getElementById('saveApiUrl');
const refreshButton = document.getElementById('refreshModels');
const refreshSpinner = document.getElementById('refreshSpinner');
const modelCountDiv = document.getElementById('modelCount');
async function fetchAvailableLLMs() {
try {
refreshSpinner.style.display = 'inline-block';
const apiUrl = apiUrlInput.value;
const response = await fetch(apiUrl + '/api/tags');
if (!response.ok) {
throw new Error('Failed to fetch available LLMs');
}
const data = await response.json();
return data.models || [];
} catch (error) {
console.error('Error fetching available LLMs:', error);
showError('Failed to fetch models. Please ensure Ollama is running.');
return [];
} finally {
refreshSpinner.style.display = 'none';
}
}
function showError(message) {
statusDiv.style.display = 'block';
statusDiv.className = 'status error';
statusDiv.textContent = message;
}
function showStatus(message) {
statusDiv.style.display = 'block';
statusDiv.className = 'status info';
statusDiv.textContent = message;
}
async function populateLLMSelector() {
showStatus('Fetching available models...');
const llms = await fetchAvailableLLMs();
llmSelector.innerHTML = '';
if (llms.length === 0) {
const option = document.createElement('option');
option.value = '';
option.textContent = 'No models found';
llmSelector.appendChild(option);
selectButton.disabled = true;
modelCountDiv.textContent = 'No models available';
return;
}
const defaultOption = document.createElement('option');
defaultOption.value = '';
defaultOption.textContent = 'Select a model';
llmSelector.appendChild(defaultOption);
llms.forEach(llm => {
const option = document.createElement('option');
option.value = llm.name;
option.textContent = llm.name;
llmSelector.appendChild(option);
});
modelCountDiv.textContent = \`\${llms.length} model\${llms.length === 1 ? '' : 's'} available\`;
selectButton.disabled = true;
statusDiv.style.display = 'none';
}
llmSelector.addEventListener('change', () => {
selectButton.disabled = !llmSelector.value;
});
selectButton.addEventListener('click', () => {
const selectedLLM = llmSelector.value;
if (selectedLLM) {
vscode.postMessage({
command: 'selectLLM',
model: selectedLLM
});
}
});
saveApiUrlButton.addEventListener('click', async () => {
showStatus('Updating API URL...');
await vscode.postMessage({
command: 'updateApiUrl',
url: apiUrlInput.value
});
populateLLMSelector();
});
refreshButton.addEventListener('click', () => {
populateLLMSelector();
});
// Initialize the selector
populateLLMSelector();
</script>
</body>
</html>`;
} catch (error) {
console.error('Error generating webview content:', error);
return `<html><body>Error loading content: ${error.message}</body></html>`;
}
}
}
module.exports = LLMSelectorPanel;

View File

@ -0,0 +1,74 @@
const vscode = require('vscode');
class SecureSettingsManager {
constructor(context) {
this.secretStorage = context.secrets;
this.CONFIG_PREFIX = '93m1n1gpt.';
this.SECURE_KEYS = ['apiUrl']; // List of keys that should be stored securely
}
async initialize() {
// Migrate any existing settings to secure storage
const config = vscode.workspace.getConfiguration('93m1n1gpt');
for (const key of this.SECURE_KEYS) {
const existingValue = config.get(key);
if (existingValue) {
await this.setSecure(key, existingValue);
// Clear the value from regular configuration
await config.update(key, undefined, vscode.ConfigurationTarget.Global);
}
}
}
async getSecure(key) {
try {
const value = await this.secretStorage.get(this.CONFIG_PREFIX + key);
return value || null;
} catch (error) {
console.error(`Error retrieving secure setting ${key}:`, error);
return null;
}
}
async setSecure(key, value) {
try {
await this.secretStorage.store(this.CONFIG_PREFIX + key, value);
return true;
} catch (error) {
console.error(`Error storing secure setting ${key}:`, error);
return false;
}
}
async deleteSecure(key) {
try {
await this.secretStorage.delete(this.CONFIG_PREFIX + key);
return true;
} catch (error) {
console.error(`Error deleting secure setting ${key}:`, error);
return false;
}
}
// Helper method to get all settings (both secure and non-secure)
async getAllSettings() {
const config = vscode.workspace.getConfiguration('93m1n1gpt');
const settings = {};
// Get non-secure settings
for (const key of Object.keys(config)) {
if (!this.SECURE_KEYS.includes(key)) {
settings[key] = config.get(key);
}
}
// Get secure settings
for (const key of this.SECURE_KEYS) {
settings[key] = await this.getSecure(key);
}
return settings;
}
}
module.exports = SecureSettingsManager;

View File

@ -0,0 +1,60 @@
// AISidebarProvider.js
const { MarkdownRenderer } = require("../utils/MarkdownRender");
const { ChatService } = require("../handlers/ChatService");
const { FileSystemManager } = require("./FileSystemManager");
const { GitManager } = require("./GitManager");
const { UIManager } = require("./UIManager");
class AISidebarProvider {
constructor(extensionUri, settingsManager, repoTracker) {
this._extensionUri = extensionUri;
this._settingsManager = settingsManager;
this.repoTracker = repoTracker;
this.messageHistory = [];
this._disposables = [];
// Initialize services
this.markdownRenderer = new MarkdownRenderer();
this.chatService = new ChatService(settingsManager);
this.fileManager = new FileSystemManager(repoTracker);
this.gitManager = new GitManager(repoTracker);
this.uiManager = new UIManager(this._extensionUri);
}
async resolveWebviewView(webviewView, context, _token) {
this._view = webviewView;
this.uiManager.initializeWebview(webviewView, this._extensionUri);
await this._setupServices(webviewView);
this._setupMessageHandlers(webviewView);
}
async _setupServices(webviewView) {
await this.fileManager.setupFileWatcher(webviewView, this._disposables);
await this.gitManager.setupGitWatcher(webviewView, this._disposables);
}
_setupMessageHandlers(webviewView) {
webviewView.webview.onDidReceiveMessage(async (message) => {
const handlers = {
chat: () => this.chatService.handleChatMessage(webviewView, message.text, this.messageHistory, this.markdownRenderer, this.repoTracker),
copyCode: () => this.uiManager.handleCopyCode(message.text),
insertCode: () => this.uiManager.handleInsertCode(message.text),
openFile: () => this.fileManager.openFileInEditor(message.path, message.line),
getFileReferences: () => this.fileManager.handleFileReferenceRequest(webviewView, message.prefix),
getGitStatus: () => this.gitManager.sendGitStatus(webviewView)
};
const handler = handlers[message.command];
if (handler) {
await handler();
}
});
}
dispose() {
this._disposables.forEach(d => d.dispose());
}
}
module.exports = { AISidebarProvider };

View File

@ -0,0 +1,69 @@
// FileSystemManager.js
const vscode = require("vscode");
class FileSystemManager {
constructor(repoTracker) {
this.repoTracker = repoTracker;
}
async setupFileWatcher(webviewView, disposables) {
await this._sendFileTree(webviewView);
const watcher = vscode.workspace.createFileSystemWatcher('**/*');
['onDidChange', 'onDidCreate', 'onDidDelete'].forEach(event => {
watcher[event](() => this._sendFileTree(webviewView));
});
disposables.push(watcher);
}
async _sendFileTree(webviewView) {
const fileTree = await this.repoTracker.buildFileTree();
webviewView.webview.postMessage({
command: 'updateFileTree',
fileTree: fileTree
});
}
async handleFileReferenceRequest(webviewView, prefix) {
const fileTree = this.repoTracker.fileTree;
const files = this._flattenFileTree(fileTree)
.filter(file => file.path.toLowerCase().includes(prefix.toLowerCase()))
.slice(0, 10);
webviewView.webview.postMessage({
command: 'fileReferenceSuggestions',
suggestions: files
});
}
_flattenFileTree(tree, path = '') {
return tree.reduce((acc, item) => {
if (item.type === 'file') {
acc.push({ type: 'file', path: path + item.name, name: item.name });
} else if (item.type === 'directory' && item.children) {
acc.push(...this._flattenFileTree(item.children, path + item.name + '/'));
}
return acc;
}, []);
}
async openFileInEditor(filePath, line) {
try {
const document = await vscode.workspace.openTextDocument(filePath);
const editor = await vscode.window.showTextDocument(document);
if (line) {
const lineNumber = parseInt(line) - 1;
const range = editor.document.lineAt(lineNumber).range;
editor.selection = new vscode.Selection(range.start, range.end);
editor.revealRange(range, vscode.TextEditorRevealType.InCenter);
}
} catch (error) {
console.error('Error opening file:', error);
vscode.window.showErrorMessage(`Failed to open file: ${filePath}`);
}
}
}
module.exports = { FileSystemManager };

25
src/utils/GitManager.js Normal file
View File

@ -0,0 +1,25 @@
// GitManager.js
class GitManager {
constructor(repoTracker) {
this.repoTracker = repoTracker;
}
async setupGitWatcher(webviewView, disposables) {
await this.sendGitStatus(webviewView);
const gitWatcher = await this.repoTracker.watchGitChanges();
gitWatcher.onDidChange(() => this.sendGitStatus(webviewView));
disposables.push(gitWatcher);
}
async sendGitStatus(webviewView) {
const gitStatus = await this.repoTracker.getGitStatus();
webviewView.webview.postMessage({
command: 'updateGitStatus',
gitStatus: gitStatus
});
}
}
module.exports = { GitManager };

View File

@ -0,0 +1,48 @@
// MarkdownRenderer.js
const MarkdownIt = require("markdown-it");
const hljs = require("highlight.js");
class MarkdownRenderer {
constructor() {
this.md = new MarkdownIt({
highlight: this._highlightCode.bind(this),
html: true,
linkify: true
});
}
_highlightCode(str, lang) {
let highlighted;
try {
highlighted = lang && hljs.getLanguage(lang)
? hljs.highlight(str, { language: lang }).value
: this.md.utils.escapeHtml(str);
} catch (err) {
console.error("Syntax highlighting error:", err);
highlighted = this.md.utils.escapeHtml(str);
}
const escapedCode = this.md.utils.escapeHtml(str);
return this._wrapCodeBlock(escapedCode, highlighted, lang);
}
_wrapCodeBlock(escapedCode, highlighted, lang) {
return `
<div class="code-block-wrapper">
<div class="code-block-header">
<span class="code-language">${lang || 'text'}</span>
<div class="code-block-buttons">
<button class="code-button copy-button" data-code="${escapedCode}">Copy</button>
<button class="code-button insert-button" data-code="${escapedCode}">Insert</button>
</div>
</div>
<pre class="hljs"><code class="language-${lang || 'text'}">${highlighted}</code></pre>
</div>`;
}
render(text) {
return this.md.render(text);
}
}
module.exports = { MarkdownRenderer };

232
src/utils/RepoTracker.js Normal file
View File

@ -0,0 +1,232 @@
const vscode = require("vscode");
const { exec } = require('child_process');
const path = require('path')
const fs = require('fs')
const util = require('util');
const execPromise = util.promisify(exec);
class RepoTracker {
constructor() {
this.workspaceRoot = "";
this.fileTree = [];
this.gitInfo = null;
this.ignoredPaths = [
'node_modules',
'.git',
'dist',
'build',
'.next',
'.cache'
];
this._initializeCompletionProvider();
}
async initialize() {
const workspaceFolders = vscode.workspace.workspaceFolders;
if (!workspaceFolders) return;
this.workspaceRoot = workspaceFolders[0].uri.fsPath;
await this.buildFileTree();
await this.initializeGitTracking();
}
async initializeGitTracking() {
try {
const { stdout: gitRoot } = await execPromise('git rev-parse --show-toplevel', {
cwd: this.workspaceRoot
});
this.gitInfo = {
root: gitRoot.trim(),
branch: '',
lastCommit: '',
status: []
};
await this.updateGitStatus();
} catch (error) {
console.log('Not a git repository or git not installed: ', error);
this.gitInfo = null;
}
}
async updateGitStatus() {
if (!this.gitInfo) return;
try {
// Get current branch
const { stdout: branch } = await execPromise('git branch --show-current', {
cwd: this.workspaceRoot
});
this.gitInfo.branch = branch.trim();
// Get last commit
const { stdout: lastCommit } = await execPromise('git log -1 --oneline', {
cwd: this.workspaceRoot
});
this.gitInfo.lastCommit = lastCommit.trim();
// Get status
const { stdout: status } = await execPromise('git status --porcelain', {
cwd: this.workspaceRoot
});
this.gitInfo.status = status.split('\n')
.filter(line => line.trim())
.map(line => ({
status: line.substring(0, 2).trim(),
file: line.substring(3).trim()
}));
} catch (error) {
console.error('Error updating git status:', error);
}
}
_initializeCompletionProvider() {
// Register the @ completion provider
vscode.languages.registerCompletionItemProvider(
{ pattern: '**' },
{
provideCompletionItems: (document, position) => {
const linePrefix = document.lineAt(position).text.substring(0, position.character);
// Check if we're typing after an @
if (!linePrefix.endsWith('@')) {
return undefined;
}
// Create completion items for all files in the workspace
return this.fileTree
.flat(Infinity)
.filter(item => item.type === 'file')
.map(file => {
const completionItem = new vscode.CompletionItem(
file.path,
vscode.CompletionItemKind.File
);
// When selected, it will insert the file path and prepare for line number
completionItem.insertText = file.path + ':';
completionItem.detail = `File: ${file.path}`;
completionItem.command = {
command: 'editor.action.triggerSuggest',
title: 'Re-trigger completions'
};
return completionItem;
});
}
},
'@' // Trigger character
);
// Register the line number completion provider
vscode.languages.registerCompletionItemProvider(
{ pattern: '**' },
{
provideCompletionItems: async (document, position) => {
const linePrefix = document.lineAt(position).text.substring(0, position.character);
// Check if we're typing after a file path and colon
const match = linePrefix.match(/@([^:]+):$/);
if (!match) {
return undefined;
}
const filePath = match[1];
const content = await this.getFileContent(filePath);
if (!content) {
return undefined;
}
// Create completion items for each line number with preview
const lines = content.split('\n');
return lines.map((line, index) => {
const lineNum = index + 1;
const completionItem = new vscode.CompletionItem(
`${lineNum}`,
vscode.CompletionItemKind.Reference
);
completionItem.detail = line.trim();
completionItem.documentation = new vscode.MarkdownString(
`Preview of line ${lineNum}:\n\`\`\`\n${line.trim()}\n\`\`\``
);
return completionItem;
});
}
},
':' // Trigger character
);
}
async buildFileTree() {
this.fileTree = await this.scanDirectory(this.workspaceRoot);
}
async scanDirectory(dirPath) {
const entries = await fs.promises.readdir(dirPath, { withFileTypes: true });
const files = [];
for (const entry of entries) {
const fullPath = path.join(dirPath, entry.name);
const relativePath = path.relative(this.workspaceRoot, fullPath);
if (this.ignoredPaths.some(ignored => relativePath.includes(ignored))) {
continue;
}
if (entry.isDirectory()) {
const subFiles = await this.scanDirectory(fullPath);
files.push({
type: 'directory',
name: entry.name,
path: relativePath,
children: subFiles
});
} else {
files.push({
type: 'file',
name: entry.name,
path: relativePath,
extension: path.extname(entry.name)
});
}
}
return files;
}
async getFileContent(filePath) {
const fullPath = path.join(this.workspaceRoot, filePath);
try {
const content = await fs.promises.readFile(fullPath, 'utf-8');
return content;
} catch (error) {
console.error(`Error reading file ${filePath}:`, error);
return null;
}
}
getFileTreeSummary() {
return JSON.stringify(this.fileTree, null, 2);
}
getGitStatus() {
return this.gitInfo;
}
async watchGitChanges() {
// Set up a file system watcher for git changes
const gitWatcher = vscode.workspace.createFileSystemWatcher(
new vscode.RelativePattern(this.workspaceRoot, '.git/**')
);
gitWatcher.onDidChange(() => this.updateGitStatus());
gitWatcher.onDidCreate(() => this.updateGitStatus());
gitWatcher.onDidDelete(() => this.updateGitStatus());
return gitWatcher;
}
}
module.exports = { RepoTracker };

62
src/utils/UIManager.js Normal file
View File

@ -0,0 +1,62 @@
// UIManager.js
const vscode = require("vscode");
const { styles } = require("../webview/sidebar/styles");
const { scripts } = require("../webview/sidebar/scripts");
class UIManager {
constructor(extensionUri) {
this._extensionUri = extensionUri;
}
initializeWebview(webviewView, extensionUri) {
webviewView.webview.options = {
enableScripts: true,
localResourceRoots: [extensionUri]
};
webviewView.webview.html = this._getWebviewContent();
}
handleCopyCode(text) {
vscode.env.clipboard.writeText(text);
vscode.window.showInformationMessage('Code copied to clipboard!');
}
handleInsertCode(code) {
const editor = vscode.window.activeTextEditor;
if (editor) {
editor.edit(editBuilder => {
editBuilder.insert(editor.selection.active, code);
});
}
}
_getWebviewContent() {
return `
<!DOCTYPE html>
<html>
<head>
<style>${styles}</style>
</head>
<body>
<div id="gitStatus"></div>
<div id="chatContainer" class="chat-container">
<div id="messageContainer" class="message-container"></div>
<div class="input-container">
<input
type="text"
id="promptInput"
placeholder="Type a message..."
class="prompt-input"
/>
<button id="sendButton" class="send-button">Send</button>
</div>
</div>
<script>${scripts}</script>
</body>
</html>
`;
}
}
module.exports = { UIManager };

231
src/utils/aisidebarnew.js Normal file
View File

@ -0,0 +1,231 @@
const vscode = require("vscode");
const { exec } = require('child_process');
const util = require('util');
const execPromise = util.promisify(exec);
class RepoTracker {
constructor() {
this.workspaceRoot = "";
this.fileTree = [];
this.gitInfo = null;
this.ignoredPaths = [
'node_modules',
'.git',
'dist',
'build',
'.next',
'.cache'
];
this._initializeCompletionProvider();
}
async initialize() {
const workspaceFolders = vscode.workspace.workspaceFolders;
if (!workspaceFolders) return;
this.workspaceRoot = workspaceFolders[0].uri.fsPath;
await this.buildFileTree();
await this.initializeGitTracking();
}
async initializeGitTracking() {
try {
const { stdout: gitRoot } = await execPromise('git rev-parse --show-toplevel', {
cwd: this.workspaceRoot
});
this.gitInfo = {
root: gitRoot.trim(),
branch: '',
lastCommit: '',
status: []
};
await this.updateGitStatus();
} catch (error) {
console.log('Not a git repository or git not installed');
this.gitInfo = null;
}
}
async updateGitStatus() {
if (!this.gitInfo) return;
try {
// Get current branch
const { stdout: branch } = await execPromise('git branch --show-current', {
cwd: this.workspaceRoot
});
this.gitInfo.branch = branch.trim();
// Get last commit
const { stdout: lastCommit } = await execPromise('git log -1 --oneline', {
cwd: this.workspaceRoot
});
this.gitInfo.lastCommit = lastCommit.trim();
// Get status
const { stdout: status } = await execPromise('git status --porcelain', {
cwd: this.workspaceRoot
});
this.gitInfo.status = status.split('\n')
.filter(line => line.trim())
.map(line => ({
status: line.substring(0, 2).trim(),
file: line.substring(3).trim()
}));
} catch (error) {
console.error('Error updating git status:', error);
}
}
_initializeCompletionProvider() {
// Register the @ completion provider
vscode.languages.registerCompletionItemProvider(
{ pattern: '**' },
{
provideCompletionItems: (document, position) => {
const linePrefix = document.lineAt(position).text.substring(0, position.character);
// Check if we're typing after an @
if (!linePrefix.endsWith('@')) {
return undefined;
}
// Create completion items for all files in the workspace
return this.fileTree
.flat(Infinity)
.filter(item => item.type === 'file')
.map(file => {
const completionItem = new vscode.CompletionItem(
file.path,
vscode.CompletionItemKind.File
);
// When selected, it will insert the file path and prepare for line number
completionItem.insertText = file.path + ':';
completionItem.detail = `File: ${file.path}`;
completionItem.command = {
command: 'editor.action.triggerSuggest',
title: 'Re-trigger completions'
};
return completionItem;
});
}
},
'@' // Trigger character
);
// Register the line number completion provider
vscode.languages.registerCompletionItemProvider(
{ pattern: '**' },
{
provideCompletionItems: async (document, position) => {
const linePrefix = document.lineAt(position).text.substring(0, position.character);
// Check if we're typing after a file path and colon
const match = linePrefix.match(/@([^:]+):$/);
if (!match) {
return undefined;
}
const filePath = match[1];
const content = await this.getFileContent(filePath);
if (!content) {
return undefined;
}
// Create completion items for each line number with preview
const lines = content.split('\n');
return lines.map((line, index) => {
const lineNum = index + 1;
const completionItem = new vscode.CompletionItem(
`${lineNum}`,
vscode.CompletionItemKind.Reference
);
completionItem.detail = line.trim();
completionItem.documentation = new vscode.MarkdownString(
`Preview of line ${lineNum}:\n\`\`\`\n${line.trim()}\n\`\`\``
);
return completionItem;
});
}
},
':' // Trigger character
);
}
// ... (keep existing methods: buildFileTree, scanDirectory, getFileContent, getFileTreeSummary)
async buildFileTree() {
this.fileTree = await this.scanDirectory(this.workspaceRoot);
}
async scanDirectory(dirPath) {
const entries = await fs.promises.readdir(dirPath, { withFileTypes: true });
const files = [];
for (const entry of entries) {
const fullPath = path.join(dirPath, entry.name);
const relativePath = path.relative(this.workspaceRoot, fullPath);
if (this.ignoredPaths.some(ignored => relativePath.includes(ignored))) {
continue;
}
if (entry.isDirectory()) {
const subFiles = await this.scanDirectory(fullPath);
files.push({
type: 'directory',
name: entry.name,
path: relativePath,
children: subFiles
});
} else {
files.push({
type: 'file',
name: entry.name,
path: relativePath,
extension: path.extname(entry.name)
});
}
}
return files;
}
async getFileContent(filePath) {
const fullPath = path.join(this.workspaceRoot, filePath);
try {
const content = await fs.promises.readFile(fullPath, 'utf-8');
return content;
} catch (error) {
console.error(`Error reading file ${filePath}:`, error);
return null;
}
}
getFileTreeSummary() {
return JSON.stringify(this.fileTree, null, 2);
}
getGitStatus() {
return this.gitInfo;
}
async watchGitChanges() {
// Set up a file system watcher for git changes
const gitWatcher = vscode.workspace.createFileSystemWatcher(
new vscode.RelativePattern(this.workspaceRoot, '.git/**')
);
gitWatcher.onDidChange(() => this.updateGitStatus());
gitWatcher.onDidCreate(() => this.updateGitStatus());
gitWatcher.onDidDelete(() => this.updateGitStatus());
return gitWatcher;
}
}
module.exports = { RepoTracker };

View File

@ -0,0 +1,124 @@
// scripts.js
exports.scripts = /*javascript*/`
let fileTree = [];
let gitStatus = null;
const vscode = acquireVsCodeApi();
// Handle file reference suggestions
promptInput.addEventListener('input', (e) => {
const text = e.target.value;
const lastAtSign = text.lastIndexOf('@');
if (lastAtSign !== -1) {
const prefix = text.substring(lastAtSign + 1);
vscode.postMessage({
command: 'getFileReferences',
prefix: prefix
});
}
});
// Handle git status updates
window.addEventListener('message', event => {
const message = event.data;
switch (message.command) {
case 'updateGitStatus':
gitStatus = message.gitStatus;
updateGitStatusDisplay();
break;
case 'updateFileTree':
fileTree = message.fileTree;
break;
case 'fileReferenceSuggestions':
showFileReferenceSuggestions(message.suggestions);
break;
case 'updateBotMessage':
updateChatDisplay(message.html);
break;
}
});
function updateGitStatusDisplay() {
if (!gitStatus) return;
const statusEl = document.getElementById('gitStatus');
if (statusEl) {
statusEl.innerHTML = \`
<div class="git-info">
<span>Branch: \${gitStatus.branch}</span>
<span>Last commit: \${gitStatus.lastCommit}</span>
</div>
\`;
}
}
function showFileReferenceSuggestions(suggestions) {
let suggestionsDiv = document.getElementById('suggestions');
if (!suggestionsDiv) {
suggestionsDiv = document.createElement('div');
suggestionsDiv.id = 'suggestions';
suggestionsDiv.className = 'suggestions';
document.body.appendChild(suggestionsDiv);
}
if (suggestions.length === 0) {
suggestionsDiv.style.display = 'none';
return;
}
const rect = promptInput.getBoundingClientRect();
suggestionsDiv.style.position = 'absolute';
suggestionsDiv.style.left = rect.left + 'px';
suggestionsDiv.style.top = (rect.bottom + 5) + 'px';
suggestionsDiv.style.display = 'block';
suggestionsDiv.innerHTML = suggestions.map(file => \`
<div class="suggestion" data-path="\${file.path}">
\${file.path}
</div>
\`).join('');
suggestionsDiv.querySelectorAll('.suggestion').forEach(el => {
el.addEventListener('click', () => {
const path = el.dataset.path;
const cursorPos = promptInput.selectionStart;
const textBeforeCursor = promptInput.value.substring(0, cursorPos);
const lastAtSign = textBeforeCursor.lastIndexOf('@');
const textAfterCursor = promptInput.value.substring(cursorPos);
promptInput.value = textBeforeCursor.substring(0, lastAtSign) +
'@' + path + ':' + textAfterCursor;
suggestionsDiv.style.display = 'none';
promptInput.focus();
});
});
}
function updateChatDisplay(html) {
const chatContainer = document.getElementById('chatContainer');
if (chatContainer) {
chatContainer.innerHTML = html;
chatContainer.scrollTop = chatContainer.scrollHeight;
}
}
// Handle code actions
document.addEventListener('click', (e) => {
if (e.target.classList.contains('copy-button')) {
vscode.postMessage({
command: 'copyCode',
text: e.target.dataset.code
});
} else if (e.target.classList.contains('insert-button')) {
vscode.postMessage({
command: 'insertCode',
text: e.target.dataset.code
});
}
// Hide suggestions when clicking outside
const suggestionsDiv = document.getElementById('suggestions');
if (suggestionsDiv && !suggestionsDiv.contains(e.target) && e.target !== promptInput) {
suggestionsDiv.style.display = 'none';
}
});
`;

View File

@ -0,0 +1,99 @@
// styles.js
exports.styles = /*css*/ `
.git-info {
padding: 8px;
background: var(--vscode-editor-inactiveSelectionBackground);
border-radius: var(--border-radius);
margin-bottom: 8px;
font-size: 0.9em;
}
.git-info span {
margin-right: 16px;
color: var(--vscode-descriptionForeground);
}
.suggestions {
position: absolute;
background: var(--vscode-editor-background);
border: 1px solid var(--vscode-panel-border);
border-radius: var(--border-radius);
max-height: 200px;
overflow-y: auto;
z-index: 1000;
display: none;
}
.suggestion {
padding: 8px 12px;
cursor: pointer;
}
.suggestion:hover {
background: var(--vscode-list-hoverBackground);
}
.chat-container {
display: flex;
flex-direction: column;
height: 100%;
padding: 16px;
}
.message-container {
flex: 1;
overflow-y: auto;
margin-bottom: 16px;
}
.input-container {
display: flex;
gap: 8px;
}
.code-block-wrapper {
margin: 16px 0;
border-radius: var(--border-radius);
border: 1px solid var(--vscode-panel-border);
}
.code-block-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px;
background: var(--vscode-editor-inactiveSelectionBackground);
border-bottom: 1px solid var(--vscode-panel-border);
}
.code-language {
font-family: var(--vscode-editor-font-family);
font-size: 0.9em;
color: var(--vscode-descriptionForeground);
}
.code-block-buttons {
display: flex;
gap: 8px;
}
.code-button {
padding: 4px 8px;
background: var(--vscode-button-background);
color: var(--vscode-button-foreground);
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 0.9em;
}
.code-button:hover {
background: var(--vscode-button-hoverBackground);
}
pre.hljs {
margin: 0;
padding: 16px;
overflow-x: auto;
}
`;

View File

@ -0,0 +1,29 @@
const getBaseStyles = require('./styles');
const getBaseScripts = require('./scripts');
function getWebviewContent() {
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/vs2015.min.css">
<style>
${getBaseStyles()}
</style>
</head>
<body>
<div id="gitStatus"></div>
<div id="chatContainer"></div>
<div class="input-container">
<textarea id="prompt" placeholder="Type your message... Use @ to reference files"></textarea>
<button id="send">Send</button>
</div>
<script>
${getBaseScripts()}
</script>
</body>
</html>`;
}
module.exports = getWebviewContent;

15
test/extension.test.js Normal file
View File

@ -0,0 +1,15 @@
import * as assert from 'assert';
// You can import and use all API from the 'vscode' module
// as well as import your extension to test it
import * as vscode from 'vscode';
// const myExtension = require('../extension');
suite('Extension Test Suite', () => {
vscode.window.showInformationMessage('Start all tests.');
test('Sample test', () => {
assert.strictEqual(-1, [1, 2, 3].indexOf(5));
assert.strictEqual(-1, [1, 2, 3].indexOf(0));
});
});

View File

@ -0,0 +1,42 @@
# Welcome to your VS Code Extension
## What's in the folder
* This folder contains all of the files necessary for your extension.
* `package.json` - this is the manifest file in which you declare your extension and command.
* The sample plugin registers a command and defines its title and command name. With this information VS Code can show the command in the command palette. It doesnt yet need to load the plugin.
* `extension.js` - this is the main file where you will provide the implementation of your command.
* The file exports one function, `activate`, which is called the very first time your extension is activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`.
* We pass the function containing the implementation of the command as the second parameter to `registerCommand`.
## Get up and running straight away
* Press `F5` to open a new window with your extension loaded.
* Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World`.
* Set breakpoints in your code inside `extension.js` to debug your extension.
* Find output from your extension in the debug console.
## Make changes
* You can relaunch the extension from the debug toolbar after changing code in `extension.js`.
* You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes.
## Explore the API
* You can open the full set of our API when you open the file `node_modules/@types/vscode/index.d.ts`.
## Run tests
* Install the [Extension Test Runner](https://marketplace.visualstudio.com/items?itemName=ms-vscode.extension-test-runner)
* Open the Testing view from the activity bar and click the Run Test" button, or use the hotkey `Ctrl/Cmd + ; A`
* See the output of the test result in the Test Results view.
* Make changes to `test/extension.test.js` or create new test files inside the `test` folder.
* The provided test runner will only consider files matching the name pattern `**.test.js`.
* You can create folders inside the `test` folder to structure your tests any way you want.
## Go further
* [Follow UX guidelines](https://code.visualstudio.com/api/ux-guidelines/overview) to create extensions that seamlessly integrate with VS Code's native interface and patterns.
* [Publish your extension](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) on the VS Code extension marketplace.
* Automate builds by setting up [Continuous Integration](https://code.visualstudio.com/api/working-with-extensions/continuous-integration).
* Integrate to the [report issue](https://code.visualstudio.com/api/get-started/wrapping-up#issue-reporting) flow to get issue and feature requests reported by users.