First commit
This commit is contained in:
commit
ca5bbb33e4
25
.eslintrc.json
Normal file
25
.eslintrc.json
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"env": {
|
||||||
|
"browser": false,
|
||||||
|
"commonjs": true,
|
||||||
|
"es6": true,
|
||||||
|
"node": true,
|
||||||
|
"mocha": true
|
||||||
|
},
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": 2018,
|
||||||
|
"ecmaFeatures": {
|
||||||
|
"jsx": true
|
||||||
|
},
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
node_modules
|
||||||
|
.vscode-test/
|
||||||
|
*.vsix
|
5
.vscode-test.mjs
Normal file
5
.vscode-test.mjs
Normal 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
8
.vscode/extensions.json
vendored
Normal 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
17
.vscode/launch.json
vendored
Normal 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
10
.vscodeignore
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
.vscode/**
|
||||||
|
.vscode-test/**
|
||||||
|
test/**
|
||||||
|
.gitignore
|
||||||
|
.yarnrc
|
||||||
|
vsc-extension-quickstart.md
|
||||||
|
**/jsconfig.json
|
||||||
|
**/*.map
|
||||||
|
**/.eslintrc.json
|
||||||
|
**/.vscode-test.*
|
9
CHANGELOG.md
Normal file
9
CHANGELOG.md
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
# Change Log
|
||||||
|
|
||||||
|
All notable changes to the "flinstones-llm" 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
|
65
README.md
Normal file
65
README.md
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
# flinstones-llm README
|
||||||
|
|
||||||
|
This is the README for your extension "flinstones-llm". 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!**
|
36
extension.js
Normal file
36
extension.js
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
// The module 'vscode' contains the VS Code extensibility API
|
||||||
|
// Import the module and reference it with the alias vscode in your code below
|
||||||
|
const vscode = require('vscode');
|
||||||
|
|
||||||
|
// This method is called when your extension is activated
|
||||||
|
// Your extension is activated the very first time the command is executed
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {vscode.ExtensionContext} context
|
||||||
|
*/
|
||||||
|
function activate(context) {
|
||||||
|
|
||||||
|
// Use the console to output diagnostic information (console.log) and errors (console.error)
|
||||||
|
// This line of code will only be executed once when your extension is activated
|
||||||
|
console.log('Congratulations, your extension "flinstones-llm" is now active!');
|
||||||
|
|
||||||
|
// The command has been defined in the package.json file
|
||||||
|
// Now provide the implementation of the command with registerCommand
|
||||||
|
// The commandId parameter must match the command field in package.json
|
||||||
|
const disposable = vscode.commands.registerCommand('flinstones-llm.helloWorld', function () {
|
||||||
|
// The code you place here will be executed every time your command is executed
|
||||||
|
|
||||||
|
// Display a message box to the user
|
||||||
|
vscode.window.showInformationMessage('Hello World from flinstones-llm!');
|
||||||
|
});
|
||||||
|
|
||||||
|
context.subscriptions.push(disposable);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This method is called when your extension is deactivated
|
||||||
|
function deactivate() {}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
activate,
|
||||||
|
deactivate
|
||||||
|
}
|
13
jsconfig.json
Normal file
13
jsconfig.json
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "Node16",
|
||||||
|
"target": "ES2022",
|
||||||
|
"checkJs": false, /* Typecheck .js files. */
|
||||||
|
"lib": [
|
||||||
|
"ES2022"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
|
}
|
3598
package-lock.json
generated
Normal file
3598
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
97
package.json
Normal file
97
package.json
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
{
|
||||||
|
"name": "flinstones-llm",
|
||||||
|
"displayName": "Flinstones LLM",
|
||||||
|
"description": "A VS Code extension that uses AWS Bedrock to suggest code changes",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"engines": {
|
||||||
|
"vscode": "^1.60.0"
|
||||||
|
},
|
||||||
|
"categories": [
|
||||||
|
"Other"
|
||||||
|
],
|
||||||
|
"activationEvents": [
|
||||||
|
"onCommand:flinstones-llm.processCodeChanges"
|
||||||
|
],
|
||||||
|
"main": "./src/extension.js",
|
||||||
|
"contributes": {
|
||||||
|
"menus": {
|
||||||
|
"explorer/context": [
|
||||||
|
{
|
||||||
|
"when": "resourceLangId",
|
||||||
|
"command": "flinstones-llm.addFileToContext",
|
||||||
|
"group": "flinstonesLlm@1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"when": "explorerResourceIsFolder",
|
||||||
|
"command": "flinstones-llm.addDirectoryToContext",
|
||||||
|
"group": "flinstonesLlm@2"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"commands": [
|
||||||
|
{
|
||||||
|
"command": "flinstones-llm.processCodeChanges",
|
||||||
|
"title": "Flinstones LLM: Process Code Changes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "flinstones-llm.openChat",
|
||||||
|
"title": "Flinstones LLM: Open Chat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "flinstones-llm.addFileToContext",
|
||||||
|
"title": "Add to Flinstones LLM Context"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "flinstones-llm.addDirectoryToContext",
|
||||||
|
"title": "Add Directory to Flinstones LLM Context"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "flinstones-llm.refreshFileContent",
|
||||||
|
"title": "Flinstones LLM: Refresh File Content"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "flinstones-llm.clearContext",
|
||||||
|
"title": "Flinstones LLM: Clear Context"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"viewsContainers": {
|
||||||
|
"activitybar": [
|
||||||
|
{
|
||||||
|
"id": "flinstones-llm-chat",
|
||||||
|
"title": "Flinstones LLM",
|
||||||
|
"icon": "resources/chat-icon.svg"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"views": {
|
||||||
|
"flinstones-llm-chat": [
|
||||||
|
{
|
||||||
|
"type": "webview",
|
||||||
|
"id": "flinstones-llm-chat-view",
|
||||||
|
"name": "Chat"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"lint": "eslint .",
|
||||||
|
"pretest": "npm run lint",
|
||||||
|
"test": "node ./test/runTest.js"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/glob": "^7.1.3",
|
||||||
|
"@types/mocha": "^8.2.2",
|
||||||
|
"@types/node": "14.x",
|
||||||
|
"@types/vscode": "^1.60.0",
|
||||||
|
"eslint": "^7.27.0",
|
||||||
|
"glob": "^7.1.7",
|
||||||
|
"mocha": "^8.4.0",
|
||||||
|
"typescript": "^4.3.2",
|
||||||
|
"vscode-test": "^1.5.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@aws-sdk/client-bedrock-runtime": "^3.x",
|
||||||
|
"diff": "^5.2.0",
|
||||||
|
"ignore": "^5.2.0"
|
||||||
|
}
|
||||||
|
}
|
3
resources/chat-icon.svg
Normal file
3
resources/chat-icon.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||||
|
<path fill="currentColor" d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 212 B |
105
src/bedrock-service.js
Normal file
105
src/bedrock-service.js
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
const { BedrockRuntimeClient, InvokeModelCommand } = require("@aws-sdk/client-bedrock-runtime");
|
||||||
|
const fs = require('fs').promises;
|
||||||
|
|
||||||
|
class BedrockService {
|
||||||
|
constructor() {
|
||||||
|
this.client = new BedrockRuntimeClient({ region: "us-east-1" }); // Set your region
|
||||||
|
}
|
||||||
|
|
||||||
|
async invokeModel(prompt, imagePath) {
|
||||||
|
if (!this.client) {
|
||||||
|
throw new Error('Bedrock client not initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("DEBUG: Generating response for prompt:", prompt);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const requestBody = {
|
||||||
|
anthropic_version: "bedrock-2023-05-31",
|
||||||
|
max_tokens: 20000,
|
||||||
|
temperature: 0.7,
|
||||||
|
top_p: 0.9,
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: "user",
|
||||||
|
content: []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
if (imagePath) {
|
||||||
|
const imageBuffer = await fs.readFile(imagePath);
|
||||||
|
const base64Image = imageBuffer.toString('base64');
|
||||||
|
|
||||||
|
requestBody.messages[0].content.push({
|
||||||
|
type: "image",
|
||||||
|
source: {
|
||||||
|
type: "base64",
|
||||||
|
media_type: "image/jpeg",
|
||||||
|
data: base64Image
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const improvedPrompt = `
|
||||||
|
${prompt}
|
||||||
|
|
||||||
|
When suggesting changes to files, please provide a unified diff format that includes line numbers. Use the following format for each file change:
|
||||||
|
|
||||||
|
File: /path/to/file.js
|
||||||
|
\`\`\`diff
|
||||||
|
@@ -<old_start>,<old_count> +<new_start>,<new_count> @@
|
||||||
|
context line
|
||||||
|
-removed line
|
||||||
|
+added line
|
||||||
|
context line
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
Ensure that:
|
||||||
|
1. The @@ line includes accurate line numbers for where the changes should be applied.
|
||||||
|
2. The <old_start> represents the line number in the original file where changes begin.
|
||||||
|
3. The <old_count> is the number of lines from the original file in this chunk.
|
||||||
|
4. The <new_start> represents the line number in the new file where changes begin.
|
||||||
|
5. The <new_count> is the number of lines in the new file in this chunk.
|
||||||
|
6. Provide at least 3 lines of context before and after the changes.
|
||||||
|
7. For multiple changes in the same file, provide separate @@ blocks for each change.
|
||||||
|
`;
|
||||||
|
|
||||||
|
requestBody.messages[0].content.push({
|
||||||
|
type: "text",
|
||||||
|
text: improvedPrompt
|
||||||
|
});
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
modelId: "anthropic.claude-3-sonnet-20240229-v1:0",
|
||||||
|
contentType: "application/json",
|
||||||
|
accept: "application/json",
|
||||||
|
body: JSON.stringify(requestBody),
|
||||||
|
};
|
||||||
|
|
||||||
|
const command = new InvokeModelCommand(params);
|
||||||
|
const response = await this.client.send(command);
|
||||||
|
const responseBody = JSON.parse(new TextDecoder().decode(response.body));
|
||||||
|
console.log("DEBUG: Raw response from Bedrock:", JSON.stringify(responseBody));
|
||||||
|
|
||||||
|
if (responseBody.content && Array.isArray(responseBody.content) && responseBody.content.length > 0) {
|
||||||
|
const generatedText = responseBody.content[0].text;
|
||||||
|
if (generatedText) {
|
||||||
|
console.log("DEBUG: Generated text:", generatedText);
|
||||||
|
return generatedText;
|
||||||
|
} else {
|
||||||
|
console.log("WARNING: Generated text is empty. Full response:", JSON.stringify(responseBody));
|
||||||
|
return "No response generated";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log("WARNING: Unexpected response format. Full response:", JSON.stringify(responseBody));
|
||||||
|
return "Unexpected response format";
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("ERROR: Failed to generate text with Bedrock:", error);
|
||||||
|
return "Error: " + error.message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = BedrockService;
|
323
src/chat-view.js
Normal file
323
src/chat-view.js
Normal file
|
@ -0,0 +1,323 @@
|
||||||
|
const vscode = require('vscode');
|
||||||
|
const BedrockService = require('./bedrock-service');
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs').promises;
|
||||||
|
const diff = require('diff');
|
||||||
|
|
||||||
|
class ChatViewProvider {
|
||||||
|
constructor(context) {
|
||||||
|
this.context = context;
|
||||||
|
this.bedrockService = new BedrockService();
|
||||||
|
this._view = null;
|
||||||
|
this.contextContent = '';
|
||||||
|
this.fileContents = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
resolveWebviewView(webviewView) {
|
||||||
|
this._view = webviewView;
|
||||||
|
|
||||||
|
webviewView.webview.options = {
|
||||||
|
enableScripts: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
webviewView.webview.html = this.getWebviewContent();
|
||||||
|
|
||||||
|
webviewView.webview.onDidReceiveMessage(async (data) => {
|
||||||
|
if (data.type === 'userInput') {
|
||||||
|
const fullPrompt = `${this.contextContent}\n\nUser: ${data.value}`;
|
||||||
|
const response = await this.bedrockService.invokeModel(fullPrompt);
|
||||||
|
this._view.webview.postMessage({ type: 'aiResponse', value: response });
|
||||||
|
|
||||||
|
const fileChanges = this.extractFileChanges(response);
|
||||||
|
if (fileChanges.length > 0) {
|
||||||
|
await this.handleFileChanges(fileChanges);
|
||||||
|
}
|
||||||
|
} else if (data.type === 'refreshContent') {
|
||||||
|
await this.refreshFileContent();
|
||||||
|
} else if (data.type === 'clearContext') {
|
||||||
|
this.clearContext();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addToContext(content) {
|
||||||
|
this.contextContent += content;
|
||||||
|
if (this._view) {
|
||||||
|
this._view.webview.postMessage({ type: 'contextUpdate', value: this.contextContent });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extractFileChanges(response) {
|
||||||
|
const fileChangeRegex = /File: (.*?)\n```diff\n([\s\S]*?)\n```/g;
|
||||||
|
const fileChanges = [];
|
||||||
|
let match;
|
||||||
|
|
||||||
|
while ((match = fileChangeRegex.exec(response)) !== null) {
|
||||||
|
fileChanges.push({
|
||||||
|
filename: match[1],
|
||||||
|
diff: match[2].trim() // Trim to remove any leading/trailing whitespace
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return fileChanges;
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleFileChanges(fileChanges) {
|
||||||
|
for (const change of fileChanges) {
|
||||||
|
console.log(`Processing changes for file: ${change.filename}`);
|
||||||
|
console.log(`Diff content:\n${change.diff}`);
|
||||||
|
|
||||||
|
// Try to find the file in the workspace
|
||||||
|
const files = await vscode.workspace.findFiles(`**/${path.basename(change.filename)}`);
|
||||||
|
|
||||||
|
let uri;
|
||||||
|
if (files.length > 0) {
|
||||||
|
uri = files[0];
|
||||||
|
} else {
|
||||||
|
// If file not found, ask the user if they want to create it
|
||||||
|
const create = await vscode.window.showQuickPick(['Yes', 'No'], {
|
||||||
|
placeHolder: `File ${change.filename} not found. Create it?`
|
||||||
|
});
|
||||||
|
|
||||||
|
if (create === 'Yes') {
|
||||||
|
const workspaceFolders = vscode.workspace.workspaceFolders;
|
||||||
|
if (workspaceFolders) {
|
||||||
|
uri = vscode.Uri.joinPath(workspaceFolders[0].uri, change.filename);
|
||||||
|
await vscode.workspace.fs.writeFile(uri, new Uint8Array());
|
||||||
|
} else {
|
||||||
|
vscode.window.showErrorMessage('No workspace folder is open');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
vscode.window.showInformationMessage(`Skipped changes for ${change.filename}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let document;
|
||||||
|
try {
|
||||||
|
document = await vscode.workspace.openTextDocument(uri);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to open file: ${change.filename}. Error: ${error.message}`);
|
||||||
|
vscode.window.showErrorMessage(`Failed to open file: ${change.filename}. Error: ${error.message}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const originalContent = document.getText();
|
||||||
|
console.log(`Original content:\n${originalContent}`);
|
||||||
|
|
||||||
|
const updatedContent = this.applyDiff(originalContent, change.diff);
|
||||||
|
console.log(`Updated content:\n${updatedContent}`);
|
||||||
|
|
||||||
|
// Show diff and ask for approval
|
||||||
|
const diffHtml = await this.createDiffHtml(originalContent, updatedContent);
|
||||||
|
const approved = await this.showDiffAndConfirm(diffHtml, change.filename);
|
||||||
|
|
||||||
|
if (approved) {
|
||||||
|
const edit = new vscode.WorkspaceEdit();
|
||||||
|
edit.replace(uri, new vscode.Range(0, 0, document.lineCount, 0), updatedContent);
|
||||||
|
await vscode.workspace.applyEdit(edit);
|
||||||
|
vscode.window.showInformationMessage(`Changes applied to ${path.basename(change.filename)}`);
|
||||||
|
} else {
|
||||||
|
vscode.window.showInformationMessage(`Changes to ${path.basename(change.filename)} were not applied`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
applyDiff(originalContent, diffContent) {
|
||||||
|
const lines = originalContent.split('\n');
|
||||||
|
const diffLines = diffContent.split('\n');
|
||||||
|
let lineIndex = 0;
|
||||||
|
|
||||||
|
for (const diffLine of diffLines) {
|
||||||
|
if (diffLine.startsWith('@@')) {
|
||||||
|
const match = diffLine.match(/@@ -(\d+),\d+ \+(\d+),\d+ @@/);
|
||||||
|
if (match) {
|
||||||
|
lineIndex = parseInt(match[1]) - 1; // -1 because array indices start at 0
|
||||||
|
}
|
||||||
|
} else if (diffLine.startsWith('+')) {
|
||||||
|
lines.splice(lineIndex, 0, diffLine.slice(1));
|
||||||
|
lineIndex++;
|
||||||
|
} else if (diffLine.startsWith('-')) {
|
||||||
|
lines.splice(lineIndex, 1);
|
||||||
|
} else if (!diffLine.startsWith(' ')) {
|
||||||
|
lineIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
async createDiffHtml(oldContent, newContent) {
|
||||||
|
const differences = diff.createTwoFilesPatch('old', 'new', oldContent, newContent);
|
||||||
|
return `
|
||||||
|
<style>
|
||||||
|
.diff { white-space: pre; font-family: monospace; }
|
||||||
|
.diff-add { background-color: #e6ffed; }
|
||||||
|
.diff-remove { background-color: #ffeef0; }
|
||||||
|
</style>
|
||||||
|
<div class="diff">
|
||||||
|
${differences.split('\n').map(line => {
|
||||||
|
if (line.startsWith('+')) return `<div class="diff-add">${this.escapeHtml(line)}</div>`;
|
||||||
|
if (line.startsWith('-')) return `<div class="diff-remove">${this.escapeHtml(line)}</div>`;
|
||||||
|
return `<div>${this.escapeHtml(line)}</div>`;
|
||||||
|
}).join('')}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
escapeHtml(unsafe) {
|
||||||
|
return unsafe
|
||||||
|
.replace(/&/g, "&")
|
||||||
|
.replace(/</g, "<")
|
||||||
|
.replace(/>/g, ">")
|
||||||
|
.replace(/"/g, """)
|
||||||
|
.replace(/'/g, "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
async showDiffAndConfirm(diffHtml, filename) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const panel = vscode.window.createWebviewPanel(
|
||||||
|
'diffView',
|
||||||
|
`Proposed Changes for ${filename}`,
|
||||||
|
vscode.ViewColumn.One,
|
||||||
|
{ enableScripts: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
panel.webview.html = `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Proposed Changes</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h2>Proposed Changes for ${this.escapeHtml(filename)}</h2>
|
||||||
|
${diffHtml}
|
||||||
|
<button id="approve">Approve Changes</button>
|
||||||
|
<button id="reject">Reject Changes</button>
|
||||||
|
<script>
|
||||||
|
const vscode = acquireVsCodeApi();
|
||||||
|
document.getElementById('approve').addEventListener('click', () => {
|
||||||
|
vscode.postMessage({ type: 'approve' });
|
||||||
|
});
|
||||||
|
document.getElementById('reject').addEventListener('click', () => {
|
||||||
|
vscode.postMessage({ type: 'reject' });
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`;
|
||||||
|
|
||||||
|
panel.webview.onDidReceiveMessage(
|
||||||
|
message => {
|
||||||
|
if (message.type === 'approve') {
|
||||||
|
resolve(true);
|
||||||
|
} else if (message.type === 'reject') {
|
||||||
|
resolve(false);
|
||||||
|
}
|
||||||
|
panel.dispose();
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
this.context.subscriptions
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async refreshFileContent() {
|
||||||
|
this.fileContents.clear();
|
||||||
|
for (const [filePath, _] of this.fileContents) {
|
||||||
|
await this.addFileToContext(vscode.Uri.file(filePath));
|
||||||
|
}
|
||||||
|
this.updateContextView();
|
||||||
|
}
|
||||||
|
|
||||||
|
clearContext() {
|
||||||
|
this.contextContent = '';
|
||||||
|
this.fileContents.clear();
|
||||||
|
this.updateContextView();
|
||||||
|
}
|
||||||
|
|
||||||
|
async addFileToContext(uri) {
|
||||||
|
const content = await fs.readFile(uri.fsPath, 'utf8');
|
||||||
|
this.fileContents.set(uri.fsPath, content);
|
||||||
|
this.updateContextView();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateContextView() {
|
||||||
|
let contextContent = '';
|
||||||
|
for (const [filePath, content] of this.fileContents) {
|
||||||
|
contextContent += `File: ${filePath}\n\n${content}\n\n`;
|
||||||
|
}
|
||||||
|
this.contextContent = contextContent;
|
||||||
|
if (this._view) {
|
||||||
|
this._view.webview.postMessage({ type: 'contextUpdate', value: this.contextContent });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
getWebviewContent() {
|
||||||
|
return `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Flinstones LLM Chat</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: Arial, sans-serif; padding: 10px; }
|
||||||
|
#chat-container { height: 300px; overflow-y: auto; border: 1px solid #ccc; padding: 10px; margin-bottom: 10px; }
|
||||||
|
#context-container { height: 100px; overflow-y: auto; border: 1px solid #ccc; padding: 10px; margin-bottom: 10px; }
|
||||||
|
#user-input { width: 100%; padding: 5px; }
|
||||||
|
.button { margin-right: 10px; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="context-container"></div>
|
||||||
|
<div id="chat-container"></div>
|
||||||
|
<input type="text" id="user-input" placeholder="Type your message...">
|
||||||
|
<button id="refresh-content" class="button">Refresh File Content</button>
|
||||||
|
<button id="clear-context" class="button">Clear Context</button>
|
||||||
|
<script>
|
||||||
|
const vscode = acquireVsCodeApi();
|
||||||
|
const contextContainer = document.getElementById('context-container');
|
||||||
|
const chatContainer = document.getElementById('chat-container');
|
||||||
|
const userInput = document.getElementById('user-input');
|
||||||
|
const refreshButton = document.getElementById('refresh-content');
|
||||||
|
const clearButton = document.getElementById('clear-context');
|
||||||
|
|
||||||
|
userInput.addEventListener('keypress', (e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
const message = userInput.value;
|
||||||
|
chatContainer.innerHTML += '<p><strong>You:</strong> ' + message + '</p>';
|
||||||
|
vscode.postMessage({ type: 'userInput', value: message });
|
||||||
|
userInput.value = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
refreshButton.addEventListener('click', () => {
|
||||||
|
vscode.postMessage({ type: 'refreshContent' });
|
||||||
|
});
|
||||||
|
|
||||||
|
clearButton.addEventListener('click', () => {
|
||||||
|
vscode.postMessage({ type: 'clearContext' });
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('message', (event) => {
|
||||||
|
const message = event.data;
|
||||||
|
if (message.type === 'aiResponse') {
|
||||||
|
chatContainer.innerHTML += '<p><strong>AI:</strong> ' + message.value + '</p>';
|
||||||
|
chatContainer.scrollTop = chatContainer.scrollHeight;
|
||||||
|
} else if (message.type === 'contextUpdate') {
|
||||||
|
contextContainer.textContent = message.value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = ChatViewProvider;
|
219
src/extension.js
Normal file
219
src/extension.js
Normal file
|
@ -0,0 +1,219 @@
|
||||||
|
const vscode = require("vscode");
|
||||||
|
const fs = require('fs').promises;
|
||||||
|
const path = require('path');
|
||||||
|
const ignore = require('ignore');
|
||||||
|
const BedrockService = require("./bedrock-service");
|
||||||
|
const { getDiff, applyPatch } = require("./git-utils");
|
||||||
|
const ChatViewProvider = require("./chat-view");
|
||||||
|
|
||||||
|
let chatViewProvider;
|
||||||
|
let ig;
|
||||||
|
|
||||||
|
async function processCodeChanges() {
|
||||||
|
const bedrockService = new BedrockService();
|
||||||
|
|
||||||
|
// Get the current workspace folder
|
||||||
|
const workspaceFolder = vscode.workspace.workspaceFolders[0];
|
||||||
|
if (!workspaceFolder) {
|
||||||
|
vscode.window.showErrorMessage("No workspace folder is open");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the file tree
|
||||||
|
const fileTree = await getFileTree(workspaceFolder.uri.fsPath);
|
||||||
|
|
||||||
|
// Get the contents of all files
|
||||||
|
const fileContents = await getAllFileContents(workspaceFolder.uri.fsPath);
|
||||||
|
|
||||||
|
const prompt = `Given the following file tree and file contents, suggest code improvements:
|
||||||
|
|
||||||
|
File Tree:
|
||||||
|
${JSON.stringify(fileTree, null, 2)}
|
||||||
|
|
||||||
|
File Contents:
|
||||||
|
${JSON.stringify(fileContents, null, 2)}
|
||||||
|
|
||||||
|
Provide your suggestions in the form of a git patch for each file that needs changes.`;
|
||||||
|
|
||||||
|
const response = await bedrockService.invokeModel(prompt);
|
||||||
|
|
||||||
|
// Extract the patches from the response
|
||||||
|
const patches = extractPatchesFromResponse(response);
|
||||||
|
|
||||||
|
if (patches.length > 0) {
|
||||||
|
for (const patch of patches) {
|
||||||
|
const shouldApply = await vscode.window.showQuickPick(["Yes", "No"], {
|
||||||
|
placeHolder: `Apply the suggested changes to ${patch.filename}?`,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (shouldApply === "Yes") {
|
||||||
|
await applyPatch(patch.content, patch.filename);
|
||||||
|
vscode.window.showInformationMessage(`Changes applied successfully to ${patch.filename}!`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
vscode.window.showWarningMessage("No valid patches found in the response.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getFileTree(dir) {
|
||||||
|
const files = await fs.readdir(dir);
|
||||||
|
const fileTree = {};
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
const filePath = path.join(dir, file);
|
||||||
|
const stats = await fs.stat(filePath);
|
||||||
|
|
||||||
|
if (stats.isDirectory()) {
|
||||||
|
fileTree[file] = await getFileTree(filePath);
|
||||||
|
} else {
|
||||||
|
fileTree[file] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fileTree;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getAllFileContents(dir) {
|
||||||
|
const files = await vscode.workspace.findFiles('**/*', '**/node_modules/**');
|
||||||
|
const fileContents = {};
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
const relativePath = path.relative(dir, file.fsPath);
|
||||||
|
const content = await fs.readFile(file.fsPath, 'utf8');
|
||||||
|
fileContents[relativePath] = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fileContents;
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractPatchesFromResponse(response) {
|
||||||
|
const patchRegex = /File: (.*?)\n```diff\n([\s\S]*?)\n```/g;
|
||||||
|
const patches = [];
|
||||||
|
let match;
|
||||||
|
|
||||||
|
while ((match = patchRegex.exec(response)) !== null) {
|
||||||
|
patches.push({
|
||||||
|
filename: match[1],
|
||||||
|
content: match[2]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return patches;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function initializeIgnore(workspaceRoot) {
|
||||||
|
ig = ignore();
|
||||||
|
try {
|
||||||
|
const gitignorePath = path.join(workspaceRoot, '.gitignore');
|
||||||
|
const gitignoreContent = await fs.readFile(gitignorePath, 'utf8');
|
||||||
|
ig.add(gitignoreContent);
|
||||||
|
} catch (error) {
|
||||||
|
// .gitignore file doesn't exist or couldn't be read
|
||||||
|
console.log("No .gitignore file found or couldn't be read");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addFileToContext(uri) {
|
||||||
|
if (!uri) {
|
||||||
|
uri = await vscode.window.showOpenDialog({
|
||||||
|
canSelectFiles: true,
|
||||||
|
canSelectFolders: false,
|
||||||
|
canSelectMany: false,
|
||||||
|
filters: {
|
||||||
|
'Text files': ['cjs', 'svelte', 'txt', 'md', 'js', 'ts', 'py', 'java', 'c', 'cpp', 'cs', 'html', 'css', 'json']
|
||||||
|
}
|
||||||
|
}).then(fileUri => fileUri[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uri) {
|
||||||
|
const content = await fs.readFile(uri.fsPath, 'utf8');
|
||||||
|
const lines = content.split('\n');
|
||||||
|
const numberedContent = lines.map((line, index) => `${index + 1}: ${line}`).join('\n');
|
||||||
|
chatViewProvider.addToContext(`File: ${uri.fsPath}\n\n${numberedContent}\n\n`);
|
||||||
|
vscode.window.showInformationMessage(`Added ${path.basename(uri.fsPath)} to context with line numbers`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addDirectoryToContext(uri) {
|
||||||
|
if (!uri) {
|
||||||
|
uri = await vscode.window.showOpenDialog({
|
||||||
|
canSelectFiles: false,
|
||||||
|
canSelectFolders: true,
|
||||||
|
canSelectMany: false
|
||||||
|
}).then(folderUri => folderUri[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uri) {
|
||||||
|
await initializeIgnore(uri.fsPath);
|
||||||
|
await addFilesRecursively(uri);
|
||||||
|
vscode.window.showInformationMessage(`Added files from ${uri.fsPath} and its subdirectories to context (respecting .gitignore)`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addFilesRecursively(uri) {
|
||||||
|
const stats = await fs.stat(uri.fsPath);
|
||||||
|
|
||||||
|
if (stats.isDirectory()) {
|
||||||
|
const entries = await fs.readdir(uri.fsPath, { withFileTypes: true });
|
||||||
|
for (const entry of entries) {
|
||||||
|
const entryUri = vscode.Uri.file(path.join(uri.fsPath, entry.name));
|
||||||
|
const relativePath = path.relative(vscode.workspace.workspaceFolders[0].uri.fsPath, entryUri.fsPath);
|
||||||
|
|
||||||
|
if (!ig.ignores(relativePath)) {
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
await addFilesRecursively(entryUri);
|
||||||
|
} else {
|
||||||
|
await addFileIfSupported(entryUri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await addFileIfSupported(uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addFileIfSupported(uri) {
|
||||||
|
const supportedExtensions = ['.cjs', '.svelte', '.txt', '.md', '.js', '.ts', '.py', '.java', '.c', '.cpp', '.cs', '.html', '.css', '.json'];
|
||||||
|
const fileExtension = path.extname(uri.fsPath).toLowerCase();
|
||||||
|
|
||||||
|
if (supportedExtensions.includes(fileExtension)) {
|
||||||
|
const relativePath = path.relative(vscode.workspace.workspaceFolders[0].uri.fsPath, uri.fsPath);
|
||||||
|
if (!ig.ignores(relativePath)) {
|
||||||
|
await addFileToContext(uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function activate(context) {
|
||||||
|
chatViewProvider = new ChatViewProvider(context);
|
||||||
|
|
||||||
|
let processCodeChangesDisposable = vscode.commands.registerCommand('flinstones-llm.processCodeChanges', processCodeChanges);
|
||||||
|
let chatViewDisposable = vscode.window.registerWebviewViewProvider('flinstones-llm-chat-view', chatViewProvider);
|
||||||
|
let openChatDisposable = vscode.commands.registerCommand('flinstones-llm.openChat', () => {
|
||||||
|
vscode.commands.executeCommand('workbench.view.extension.flinstones-llm-chat');
|
||||||
|
});
|
||||||
|
let addFileDisposable = vscode.commands.registerCommand('flinstones-llm.addFileToContext', addFileToContext);
|
||||||
|
let addDirectoryDisposable = vscode.commands.registerCommand('flinstones-llm.addDirectoryToContext', addDirectoryToContext);
|
||||||
|
|
||||||
|
// New commands
|
||||||
|
let refreshFileContentDisposable = vscode.commands.registerCommand('flinstones-llm.refreshFileContent', () => chatViewProvider.refreshFileContent());
|
||||||
|
let clearContextDisposable = vscode.commands.registerCommand('flinstones-llm.clearContext', () => chatViewProvider.clearContext());
|
||||||
|
|
||||||
|
context.subscriptions.push(
|
||||||
|
processCodeChangesDisposable,
|
||||||
|
chatViewDisposable,
|
||||||
|
openChatDisposable,
|
||||||
|
addFileDisposable,
|
||||||
|
addDirectoryDisposable,
|
||||||
|
refreshFileContentDisposable,
|
||||||
|
clearContextDisposable
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function deactivate() {}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
activate,
|
||||||
|
deactivate
|
||||||
|
};
|
48
src/git-utils.js
Normal file
48
src/git-utils.js
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
const { exec } = require('child_process');
|
||||||
|
const { promisify } = require('util');
|
||||||
|
const vscode = require('vscode');
|
||||||
|
|
||||||
|
const execAsync = promisify(exec);
|
||||||
|
|
||||||
|
async function getDiff() {
|
||||||
|
const { stdout } = await execAsync('git diff');
|
||||||
|
return stdout;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function applyPatch(patch, filePath) {
|
||||||
|
const uri = vscode.Uri.file(filePath);
|
||||||
|
const document = await vscode.workspace.openTextDocument(uri);
|
||||||
|
const edit = new vscode.WorkspaceEdit();
|
||||||
|
|
||||||
|
const originalContent = document.getText();
|
||||||
|
const updatedContent = applyDiffToContent(originalContent, patch);
|
||||||
|
|
||||||
|
edit.replace(uri, new vscode.Range(0, 0, document.lineCount, 0), updatedContent);
|
||||||
|
await vscode.workspace.applyEdit(edit);
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyDiffToContent(originalContent, patch) {
|
||||||
|
const lines = originalContent.split('\n');
|
||||||
|
const diffLines = patch.split('\n');
|
||||||
|
let lineIndex = 0;
|
||||||
|
|
||||||
|
for (const diffLine of diffLines) {
|
||||||
|
if (diffLine.startsWith('@@')) {
|
||||||
|
const match = diffLine.match(/@@ -(\d+),\d+ \+(\d+),\d+ @@/);
|
||||||
|
if (match) {
|
||||||
|
lineIndex = parseInt(match[1]) - 1;
|
||||||
|
}
|
||||||
|
} else if (diffLine.startsWith('+')) {
|
||||||
|
lines.splice(lineIndex, 0, diffLine.slice(1));
|
||||||
|
lineIndex++;
|
||||||
|
} else if (diffLine.startsWith('-')) {
|
||||||
|
lines.splice(lineIndex, 1);
|
||||||
|
} else if (!diffLine.startsWith(' ')) {
|
||||||
|
lineIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { getDiff, applyPatch };
|
15
test/extension.test.js
Normal file
15
test/extension.test.js
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
const assert = require('assert');
|
||||||
|
|
||||||
|
// You can import and use all API from the 'vscode' module
|
||||||
|
// as well as import your extension to test it
|
||||||
|
const vscode = require('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));
|
||||||
|
});
|
||||||
|
});
|
41
vsc-extension-quickstart.md
Normal file
41
vsc-extension-quickstart.md
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
# 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 doesn’t 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).
|
Loading…
Reference in New Issue
Block a user