commit
99b037eb28
20 changed files with 6078 additions and 0 deletions
@ -0,0 +1,5 @@ |
|||
out |
|||
dist |
|||
node_modules |
|||
.vscode-test/ |
|||
*.vsix |
@ -0,0 +1,5 @@ |
|||
import { defineConfig } from '@vscode/test-cli'; |
|||
|
|||
export default defineConfig({ |
|||
files: 'out/test/**/*.test.js', |
|||
}); |
@ -0,0 +1,5 @@ |
|||
{ |
|||
// See http://go.microsoft.com/fwlink/?LinkId=827846 |
|||
// for the documentation about the extensions.json format |
|||
"recommendations": ["dbaeumer.vscode-eslint", "amodio.tsl-problem-matcher", "ms-vscode.extension-test-runner"] |
|||
} |
@ -0,0 +1,21 @@ |
|||
// A launch configuration that compiles the extension and then opens it 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}" |
|||
], |
|||
"outFiles": [ |
|||
"${workspaceFolder}/dist/**/*.js" |
|||
], |
|||
"preLaunchTask": "${defaultBuildTask}" |
|||
} |
|||
] |
|||
} |
@ -0,0 +1,13 @@ |
|||
// Place your settings in this file to overwrite default and user settings. |
|||
{ |
|||
"files.exclude": { |
|||
"out": false, // set this to true to hide the "out" folder with the compiled JS files |
|||
"dist": false // set this to true to hide the "dist" folder with the compiled JS files |
|||
}, |
|||
"search.exclude": { |
|||
"out": true, // set this to false to include "out" folder in search results |
|||
"dist": true // set this to false to include "dist" folder in search results |
|||
}, |
|||
// Turn off tsc task auto detection since we have the necessary tasks as npm scripts |
|||
"typescript.tsc.autoDetect": "off" |
|||
} |
@ -0,0 +1,40 @@ |
|||
// See https://go.microsoft.com/fwlink/?LinkId=733558 |
|||
// for the documentation about the tasks.json format |
|||
{ |
|||
"version": "2.0.0", |
|||
"tasks": [ |
|||
{ |
|||
"type": "npm", |
|||
"script": "watch", |
|||
"problemMatcher": "$ts-webpack-watch", |
|||
"isBackground": true, |
|||
"presentation": { |
|||
"reveal": "never", |
|||
"group": "watchers" |
|||
}, |
|||
"group": { |
|||
"kind": "build", |
|||
"isDefault": true |
|||
} |
|||
}, |
|||
{ |
|||
"type": "npm", |
|||
"script": "watch-tests", |
|||
"problemMatcher": "$tsc-watch", |
|||
"isBackground": true, |
|||
"presentation": { |
|||
"reveal": "never", |
|||
"group": "watchers" |
|||
}, |
|||
"group": "build" |
|||
}, |
|||
{ |
|||
"label": "tasks: watch-tests", |
|||
"dependsOn": [ |
|||
"npm: watch", |
|||
"npm: watch-tests" |
|||
], |
|||
"problemMatcher": [] |
|||
} |
|||
] |
|||
} |
@ -0,0 +1,14 @@ |
|||
.vscode/** |
|||
.vscode-test/** |
|||
out/** |
|||
node_modules/** |
|||
src/** |
|||
.gitignore |
|||
.yarnrc |
|||
webpack.config.js |
|||
vsc-extension-quickstart.md |
|||
**/tsconfig.json |
|||
**/eslint.config.mjs |
|||
**/*.map |
|||
**/*.ts |
|||
**/.vscode-test.* |
@ -0,0 +1,9 @@ |
|||
# Change Log |
|||
|
|||
All notable changes to the "vscode-plugin-demo" 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 |
@ -0,0 +1,18 @@ |
|||
# Code Helper Extension |
|||
|
|||
## 介绍 |
|||
Code Helper Extension は、VS Code 用の拡張機能で、コードレビュー、コードのチューニングなどの機能を提供します。 |
|||
|
|||
## 機能 |
|||
- コードの解釈 |
|||
- 関数の注釈 |
|||
- コードの最適化 |
|||
|
|||
## インストール |
|||
1. .vsix ファイルをダウンロード |
|||
2. VS Code で Extensions(拡張機能) ページを開く |
|||
3. Install from VSIX... を選択してインストール |
|||
|
|||
## 使用方法 |
|||
1. VS Code で任意のコードファイルを開く |
|||
2. Ctrl + Shift + P を押し、Code Helper を入力して機能を選択 |
After Width: | Height: | Size: 1.9 KiB |
@ -0,0 +1,28 @@ |
|||
import typescriptEslint from "@typescript-eslint/eslint-plugin"; |
|||
import tsParser from "@typescript-eslint/parser"; |
|||
|
|||
export default [{ |
|||
files: ["**/*.ts"], |
|||
}, { |
|||
plugins: { |
|||
"@typescript-eslint": typescriptEslint, |
|||
}, |
|||
|
|||
languageOptions: { |
|||
parser: tsParser, |
|||
ecmaVersion: 2022, |
|||
sourceType: "module", |
|||
}, |
|||
|
|||
rules: { |
|||
"@typescript-eslint/naming-convention": ["warn", { |
|||
selector: "import", |
|||
format: ["camelCase", "PascalCase"], |
|||
}], |
|||
|
|||
curly: "warn", |
|||
eqeqeq: "warn", |
|||
"no-throw-literal": "warn", |
|||
semi: "warn", |
|||
}, |
|||
}]; |
File diff suppressed because it is too large
@ -0,0 +1,80 @@ |
|||
{ |
|||
"name": "cloudscale-code-helper-extension", |
|||
"displayName": "Mini Code Helper", |
|||
"publisher": "Mini-Solution", |
|||
"description": "コード上でコードの解釈、関数の注釈、最適化提案を提供", |
|||
"version": "1.0.0", |
|||
"engines": { |
|||
"vscode": "^1.97.0" |
|||
}, |
|||
"categories": [ |
|||
"Programming Languages" |
|||
], |
|||
"activationEvents": [ |
|||
"*" |
|||
], |
|||
"main": "./dist/extension.js", |
|||
"contributes": { |
|||
"viewsContainers": { |
|||
"activitybar": [ |
|||
{ |
|||
"id": "code-helper-sidebar", |
|||
"title": "Code Helper", |
|||
"icon": "./assets/icon3.svg" |
|||
} |
|||
] |
|||
}, |
|||
"views": { |
|||
"code-helper-sidebar": [ |
|||
{ |
|||
"id": "cloudscale-codeHelperView", |
|||
"name": "Code Helper", |
|||
"type": "webview" |
|||
} |
|||
] |
|||
}, |
|||
"commands": [ |
|||
{ |
|||
"command": "extension.showExplanation", |
|||
"title": "Cloudscale Explanation" |
|||
}, |
|||
{ |
|||
"command": "extension.showComments", |
|||
"title": "Cloudscale Comments" |
|||
}, |
|||
{ |
|||
"command": "extension.showOptimization", |
|||
"title": "Cloudscale Optimization" |
|||
} |
|||
] |
|||
}, |
|||
"scripts": { |
|||
"vscode:prepublish": "npm run package", |
|||
"compile": "webpack", |
|||
"watch": "webpack --watch", |
|||
"package": "webpack --mode production --devtool hidden-source-map", |
|||
"compile-tests": "tsc -p . --outDir out", |
|||
"watch-tests": "tsc -p . -w --outDir out", |
|||
"pretest": "npm run compile-tests && npm run compile && npm run lint", |
|||
"lint": "eslint src", |
|||
"test": "vscode-test" |
|||
}, |
|||
"devDependencies": { |
|||
"@types/eventsource": "^3.0.0", |
|||
"@types/mocha": "^10.0.10", |
|||
"@types/node": "20.x", |
|||
"@types/vscode": "^1.97.0", |
|||
"@typescript-eslint/eslint-plugin": "^8.22.0", |
|||
"@typescript-eslint/parser": "^8.22.0", |
|||
"@vscode/test-cli": "^0.0.10", |
|||
"@vscode/test-electron": "^2.4.1", |
|||
"eslint": "^9.19.0", |
|||
"ts-loader": "^9.5.2", |
|||
"typescript": "^5.7.3", |
|||
"webpack": "^5.97.1", |
|||
"webpack-cli": "^6.0.1" |
|||
}, |
|||
"dependencies": { |
|||
"marked": "^15.0.7" |
|||
} |
|||
} |
@ -0,0 +1,199 @@ |
|||
import * as vscode from 'vscode'; |
|||
import * as ts from 'typescript'; |
|||
|
|||
import { CONSTANTS } from './constants'; |
|||
|
|||
export class CodeLensProvider implements vscode.CodeLensProvider { |
|||
|
|||
public async provideCodeLenses(document: vscode.TextDocument, _token: vscode.CancellationToken): Promise<vscode.CodeLens[]> { |
|||
|
|||
let functionRanges: { range: vscode.Range, code: string }[] = []; |
|||
|
|||
// 1. 使用 VS Code 内置的 DocumentSymbolProvider 获取符号
|
|||
const symbols = await vscode.commands.executeCommand<vscode.DocumentSymbol[]>( |
|||
'vscode.executeDocumentSymbolProvider', document.uri |
|||
); |
|||
|
|||
if (symbols && symbols.length > 0) { |
|||
const flattenedSymbols = this.flattenSymbols(symbols); |
|||
const providerFunctions = flattenedSymbols.filter(symbol => |
|||
symbol.kind === vscode.SymbolKind.Function || symbol.kind === vscode.SymbolKind.Method |
|||
); |
|||
|
|||
providerFunctions.forEach(symbol => { |
|||
const code = document.getText(symbol.range); |
|||
functionRanges.push({ range: symbol.range, code }); |
|||
}); |
|||
} |
|||
|
|||
// 2. 针对 JavaScript/TypeScript,额外尝试 AST 解析
|
|||
if (document.languageId === 'javascript' || document.languageId === 'typescript') { |
|||
const astFunctions = this.extractFunctionsFromAST(document); |
|||
astFunctions.forEach(astFunc => { |
|||
if (!this.isOverlapped(astFunc.range, functionRanges)) { |
|||
functionRanges.push(astFunc); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
// 3. 如果上述方法未获取到函数(或某些语言符号解析失败),使用正则表达式进行兜底匹配
|
|||
if (functionRanges.length === 0) { |
|||
for (let i = 0; i < document.lineCount; i++) { |
|||
const line = document.lineAt(i); |
|||
const lineText = line.text.trim(); |
|||
if (this.isFunctionDefinition(lineText)) { |
|||
const range = new vscode.Range(i, 0, i, line.text.length); |
|||
const functionCode = this.extractFunctionCode(document, i); |
|||
functionRanges.push({ range, code: functionCode }); |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 根据每个函数范围创建 CodeLens(每个函数显示三个操作)
|
|||
const codeLenses: vscode.CodeLens[] = []; |
|||
functionRanges.forEach(item => { |
|||
codeLenses.push(new vscode.CodeLens(item.range, { |
|||
title: CONSTANTS.CODE_INTERPRETATION, |
|||
command: "extension.showExplanation", |
|||
arguments: [item.code] |
|||
})); |
|||
codeLenses.push(new vscode.CodeLens(item.range, { |
|||
title: CONSTANTS.FUNCTION_ANNOTATION, |
|||
command: "extension.showComments", |
|||
arguments: [item.code] |
|||
})); |
|||
codeLenses.push(new vscode.CodeLens(item.range, { |
|||
title: CONSTANTS.TUNING_RECOMMENDATION, |
|||
command: "extension.showOptimization", |
|||
arguments: [item.code] |
|||
})); |
|||
}); |
|||
|
|||
return codeLenses; |
|||
} |
|||
|
|||
// 递归扁平化 DocumentSymbol 树
|
|||
private flattenSymbols(symbols: vscode.DocumentSymbol[]): vscode.DocumentSymbol[] { |
|||
let result: vscode.DocumentSymbol[] = []; |
|||
for (const symbol of symbols) { |
|||
result.push(symbol); |
|||
if (symbol.children && symbol.children.length > 0) { |
|||
result = result.concat(this.flattenSymbols(symbol.children)); |
|||
} |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
// 判断一个范围是否与已有的范围有重叠(简单根据起始和结束行判断)
|
|||
private isOverlapped(range: vscode.Range, ranges: { range: vscode.Range, code: string }[]): boolean { |
|||
return ranges.some(item => |
|||
item.range.start.line === range.start.line && item.range.end.line === range.end.line |
|||
); |
|||
} |
|||
|
|||
// 针对 JavaScript/TypeScript,使用 TypeScript AST 解析函数、方法、箭头函数等
|
|||
private extractFunctionsFromAST(document: vscode.TextDocument): { range: vscode.Range, code: string }[] { |
|||
const results: { range: vscode.Range, code: string }[] = []; |
|||
const sourceCode = document.getText(); |
|||
const sourceFile = ts.createSourceFile(document.fileName, sourceCode, ts.ScriptTarget.Latest, true); |
|||
|
|||
const visit = (node: ts.Node) => { |
|||
// 检查函数声明、方法声明、箭头函数等
|
|||
if (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node) || |
|||
ts.isArrowFunction(node) || ts.isFunctionExpression(node)) { |
|||
|
|||
const start = document.positionAt(node.getStart()); |
|||
const end = document.positionAt(node.getEnd()); |
|||
const range = new vscode.Range(start, end); |
|||
const code = document.getText(range); |
|||
|
|||
// 确保我们抓取的是完整的函数定义,而不仅仅是函数体
|
|||
results.push({ range, code }); |
|||
} |
|||
ts.forEachChild(node, visit); |
|||
}; |
|||
visit(sourceFile); |
|||
return results; |
|||
} |
|||
|
|||
// 兜底正则匹配函数定义,支持多种语言(包括 Java 的泛型、数组等)
|
|||
private isFunctionDefinition(lineText: string): boolean { |
|||
// Python
|
|||
if (lineText.startsWith("def ")) return true; |
|||
|
|||
// JavaScript/TypeScript的函数定义(包括箭头函数和方法)
|
|||
if (lineText.match(/^\s*(async\s+)?function\s+\w+\s*\(.*\)\s*\{?/)) return true; |
|||
if (lineText.match(/^\s*(const|let|var)\s+\w+\s*=\s*\(?\s*\w*\s*\)?\s*=>\s*\{?/)) return true; |
|||
if (lineText.match(/^\s*\w+\s*\(.*\)\s*\{?$/)) return true; |
|||
|
|||
// Java:支持 public/private/protected/static, 泛型、数组等
|
|||
if (lineText.match(/^\s*(public|private|protected|static|final|synchronized|abstract|native|strictfp)(\s+\S+)*\s+(\w+)\s*\(/m)) return true; |
|||
|
|||
// C#、C++:可能带返回类型
|
|||
if (lineText.match(/^\s*(public|private|protected|static|virtual|override|\w+)\s+\w+\s*\(.*\)\s*\{?$/)) return true; |
|||
|
|||
// PHP
|
|||
if (lineText.match(/^\s*function\s+\w+\s*\(.*\)\s*\{?$/)) return true; |
|||
|
|||
// Swift
|
|||
if (lineText.match(/^\s*func\s+\w+\s*\(.*\)\s*\{?$/)) return true; |
|||
|
|||
// Go
|
|||
if (lineText.match(/^\s*func\s+\w+\s*\(.*\)\s*\{?$/)) return true; |
|||
|
|||
return false; |
|||
} |
|||
|
|||
// 简单基于缩进提取函数体(正则兜底方案)
|
|||
// 针对所有语言的提取函数,根据语言类型选择合适的提取策略
|
|||
private extractFunctionCode(document: vscode.TextDocument, startLine: number): string { |
|||
if (document.languageId === 'python') { |
|||
return this.extractPythonFunctionCode(document, startLine); |
|||
} else { |
|||
// 针对大括号语言(如 Java、JavaScript 等)的提取逻辑
|
|||
let code = ''; |
|||
let openBraces = 0; |
|||
let started = false; |
|||
for (let i = startLine; i < document.lineCount; i++) { |
|||
const line = document.lineAt(i).text; |
|||
// 如果还没开始并且当前行存在大括号,则标记开始
|
|||
if (!started && line.indexOf('{') !== -1) { |
|||
started = true; |
|||
} |
|||
// 累加当前行代码
|
|||
code += line + "\n"; |
|||
if (started) { |
|||
// 统计当前行中 { 和 } 的数量
|
|||
const opens = (line.match(/{/g) || []).length; |
|||
const closes = (line.match(/}/g) || []).length; |
|||
openBraces += opens - closes; |
|||
// 当大括号全部匹配,则认为函数体结束
|
|||
if (openBraces <= 0) { |
|||
break; |
|||
} |
|||
} else { |
|||
// 如果函数体还未开始(即没有 {),则继续读取下一行
|
|||
continue; |
|||
} |
|||
} |
|||
return code; |
|||
} |
|||
} |
|||
|
|||
// 专门针对 Python 的函数体提取(基于缩进判断)
|
|||
private extractPythonFunctionCode(document: vscode.TextDocument, startLine: number): string { |
|||
let code = ''; |
|||
// 获取定义行的缩进(空格数)
|
|||
const defIndent = document.lineAt(startLine).firstNonWhitespaceCharacterIndex; |
|||
for (let i = startLine; i < document.lineCount; i++) { |
|||
const line = document.lineAt(i).text; |
|||
// 如果不是空行,且缩进小于等于定义行缩进,则认为函数体结束
|
|||
if (i > startLine && line.trim() !== '') { |
|||
const currentIndent = document.lineAt(i).firstNonWhitespaceCharacterIndex; |
|||
if (currentIndent <= defIndent) break; |
|||
} |
|||
code += line + "\n"; |
|||
} |
|||
return code; |
|||
} |
|||
} |
@ -0,0 +1,21 @@ |
|||
// constants.ts
|
|||
export const MESSAGES = { |
|||
SIDEBAR_OPEN_FAILED: "サイドバーの開放に失敗しました",//"侧边栏打开失败",
|
|||
PLEASE_OPEN_SIDEBAR: "内容を確認するには、まずサイドバーを開いてください",//"请先打开侧边栏查看内容",
|
|||
REQUEST_FAILED: "リクエストに失敗しました",//"请求失败",
|
|||
PARSE_DATA_ERROR: "データの解析中にエラーが発生しました:",//"解析数据时发生错误:",
|
|||
FAILED_OBTAIN_LOCAL_MODEL: "ローカルモデルの取得に失敗しました",//"获取本地模型失败",
|
|||
NO_AVAILABLE_MODELS: "利用可能なモデルがありません",//"没有可用的模型"
|
|||
ANSWER_IN_JAPANESE: "【返信】返信は必ず日本語で行ってください。"//"请用日语回答"
|
|||
}; |
|||
|
|||
export const CONSTANTS = { |
|||
CODE_INTERPRETATION: "コードレビュー", |
|||
FUNCTION_ANNOTATION: "関数注釈", |
|||
TUNING_RECOMMENDATION: "チューニング推奨" |
|||
}; |
|||
|
|||
export const MODELLIST = { |
|||
DIFY: "dify", |
|||
OLLAMA: "ollama" |
|||
}; |
@ -0,0 +1,664 @@ |
|||
import * as vscode from 'vscode'; |
|||
import * as os from 'os'; |
|||
import * as fs from 'fs'; |
|||
import * as path from 'path'; |
|||
|
|||
import { CodeLensProvider } from './codeLensProvider'; |
|||
import { CONSTANTS, MESSAGES, MODELLIST} from './constants'; // 导入
|
|||
|
|||
const modelType = 'ollama'; |
|||
|
|||
|
|||
|
|||
// 新增:侧边栏内容管理器
|
|||
class SidebarContentProvider implements vscode.WebviewViewProvider { |
|||
public static readonly viewType = 'cloudscale-codeHelperView'; |
|||
public _view?: vscode.WebviewView; |
|||
private _currentContent: { [key: string]: string } = {}; |
|||
private _contentBackup: Map<string, string> = new Map(); // 用于保存内容
|
|||
private _isInitialized = false; |
|||
public _selectedModel = ""; |
|||
|
|||
// 添加视图准备状态跟踪
|
|||
private _viewReadyResolver!: (value: vscode.WebviewView) => void; |
|||
private readonly _viewReady = new Promise<vscode.WebviewView>(resolve => { |
|||
this._viewReadyResolver = resolve; |
|||
}); |
|||
|
|||
constructor(private readonly _extensionUri: vscode.Uri) {} |
|||
|
|||
// 获取本地模型列表
|
|||
private async fetchModels(): Promise<{ name: string }[]> { |
|||
try { |
|||
const response = await fetch('http://localhost:11434/api/tags'); |
|||
if (!response.ok) { |
|||
throw new Error(`HTTP Error! Code: ${response.status}`); |
|||
} |
|||
const dataResp = await response.json() as { models?: { name: string }[] }; |
|||
return dataResp.models || []; |
|||
} catch (error) { |
|||
console.error(MESSAGES.FAILED_OBTAIN_LOCAL_MODEL, error); |
|||
vscode.window.showInformationMessage(MESSAGES.FAILED_OBTAIN_LOCAL_MODEL); |
|||
return []; |
|||
} |
|||
} |
|||
|
|||
// 发送模型列表到 Webview
|
|||
public async sendModelsToWebview(): Promise<void> { |
|||
if (!this._view) return; |
|||
|
|||
try { |
|||
const models = await this.fetchModels(); |
|||
//这里初始化_selectedModel为默认值,因为_getHtml那里的下拉选择器来不及传默认值给_selectedModel
|
|||
if (this.strIsEmpty(this._selectedModel)) { |
|||
if(models.length){ |
|||
this._selectedModel = models[0].name; |
|||
} |
|||
} |
|||
this._view.webview.postMessage({ |
|||
type: 'updateModels', |
|||
models: models, |
|||
selectedModel: this._selectedModel |
|||
}); |
|||
} catch (error) { |
|||
console.error(MESSAGES.FAILED_OBTAIN_LOCAL_MODEL + ':', error); |
|||
vscode.window.showInformationMessage(MESSAGES.FAILED_OBTAIN_LOCAL_MODEL); |
|||
this._view.webview.postMessage({ |
|||
type: 'updateModels', |
|||
models: [] |
|||
}); |
|||
} |
|||
} |
|||
|
|||
// 修改后的 resolveWebviewView 方法
|
|||
public resolveWebviewView(webviewView: vscode.WebviewView) { |
|||
this._view = webviewView; |
|||
this._isInitialized = true; |
|||
this._viewReadyResolver(webviewView); |
|||
|
|||
webviewView.webview.options = { |
|||
enableScripts: true, |
|||
localResourceRoots: [this._extensionUri] |
|||
}; |
|||
|
|||
webviewView.webview.html = this._getHtml(); |
|||
|
|||
// 监听 Webview
|
|||
webviewView.onDidChangeVisibility(() => { |
|||
if (webviewView.visible) { |
|||
this._restoreContent(); |
|||
this.sendModelsToWebview(); |
|||
if (!this._isInitialized) { |
|||
console.log('View became visible and initialized'); |
|||
this._isInitialized = true; |
|||
} |
|||
} |
|||
|
|||
}); |
|||
|
|||
// 监听 Webview 消息
|
|||
webviewView.webview.onDidReceiveMessage((message) => { |
|||
if (message.type === 'resize') { |
|||
this._view?.webview.postMessage({ |
|||
type: 'resize', |
|||
width: message.width |
|||
}); |
|||
} else if (message.type === 'modelChange') { |
|||
// 更新 model
|
|||
this.updateModel(message.model); |
|||
} |
|||
}); |
|||
|
|||
// 这里要刷新下拉列表,以防没有点击指令,那这里就要初始化
|
|||
if (modelType === MODELLIST.OLLAMA && this.strIsEmpty(this._selectedModel)){ |
|||
this.sendModelsToWebview(); |
|||
} |
|||
|
|||
} |
|||
|
|||
private updateModel(model: string) { |
|||
// 更新 model
|
|||
this._selectedModel = model; |
|||
} |
|||
|
|||
private _restoreContent() { |
|||
// 恢复之前保存的内容
|
|||
// 将 Map 转换为数组并按照时间进行排序
|
|||
const sortedEntries = Array.from(this._contentBackup.entries()).sort((a, b) => { |
|||
// 提取时间部分(假设时间部分在标题的最后)
|
|||
const timeRegex = /(\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2})$/; |
|||
const timeA = a[0].match(timeRegex)?.[1]; |
|||
const timeB = b[0].match(timeRegex)?.[1]; |
|||
|
|||
// 如果找不到时间部分,使用默认顺序
|
|||
if (!timeA || !timeB) return 0; |
|||
|
|||
const dateA = new Date(timeA.replace(/\//g, "-")); |
|||
const dateB = new Date(timeB.replace(/\//g, "-")); |
|||
|
|||
// 如果日期解析失败,使用默认顺序
|
|||
if (isNaN(dateA.getTime()) || isNaN(dateB.getTime())) return 0; |
|||
|
|||
// 按时间升序排序,若时间相同则按标题字母顺序排序
|
|||
return dateA.getTime() - dateB.getTime() || a[0].localeCompare(b[0]); |
|||
}); |
|||
|
|||
// 按照排序后的顺序恢复内容
|
|||
sortedEntries.forEach(([title, content]) => { |
|||
this._view?.webview.postMessage({ |
|||
type: 'update', |
|||
title: title, |
|||
content: content, |
|||
isAppend: true |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
// 新增:确保视图可见方法
|
|||
public async ensureVisible(): Promise<void> { |
|||
try { |
|||
|
|||
// 触发视图显示
|
|||
await vscode.commands.executeCommand(`${SidebarContentProvider.viewType}.focus`); |
|||
// 等待视图准备就绪
|
|||
await this._viewReady; |
|||
} catch (error) { |
|||
vscode.window.showErrorMessage(MESSAGES.SIDEBAR_OPEN_FAILED + ': ' + error); |
|||
throw error; // 抛出错误,供外部处理
|
|||
} |
|||
} |
|||
|
|||
// 更新内容的方法(保留原有SSE效果)
|
|||
public updateContent(title: string, content: string, isAppend: boolean = true) { |
|||
if(MODELLIST.DIFY === modelType){ |
|||
this.updateContentForDify(title, content, isAppend); |
|||
} else if(MODELLIST.OLLAMA === modelType){ |
|||
this.updateContentForOllama(title, content, isAppend); |
|||
} |
|||
} |
|||
|
|||
public updateContentForOllama(title: string, content: string, isAppend: boolean = true) { |
|||
if (!this._view) { |
|||
vscode.window.showInformationMessage(MESSAGES.PLEASE_OPEN_SIDEBAR); |
|||
return; |
|||
} |
|||
if (!isAppend) { |
|||
this._currentContent[title] = ''; |
|||
} |
|||
|
|||
const parsedObject = JSON.parse(content); // 解析 JSON
|
|||
|
|||
if ('message' in parsedObject) { |
|||
const message = parsedObject.message; |
|||
if ('content' in message) { |
|||
this._currentContent[title] = message.content; |
|||
// 将当前内容备份
|
|||
if (!this._contentBackup.has(title)) { |
|||
this._contentBackup.set(title, '');// 使用 set() 添加键值对
|
|||
} |
|||
this._contentBackup.set(title, (this._contentBackup.get(title) ?? '') + this._currentContent[title]); // 使用 get() 获取值,更新并重新设置
|
|||
|
|||
this._view.webview.postMessage({ |
|||
type: 'update', |
|||
title: title, |
|||
content: this._currentContent[title], |
|||
isAppend: isAppend |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public updateContentForDify(title: string, content: string, isAppend: boolean = true) { |
|||
if (!this._view) { |
|||
vscode.window.showInformationMessage(MESSAGES.PLEASE_OPEN_SIDEBAR); |
|||
return; |
|||
} |
|||
if (!isAppend) { |
|||
this._currentContent[title] = ''; |
|||
} |
|||
|
|||
//这里可能会出现多条data: 开头数据,所以用正则匹配出每一条,然后循环遍历
|
|||
const dataRegex = /data:\s*(\{.*\})/g; //确保匹配完整的JSON对象
|
|||
let match; |
|||
while ((match = dataRegex.exec(content)) !== null) { |
|||
try { |
|||
const jsonString = match[1]; // 提取数据部分
|
|||
const parsedObject = JSON.parse(jsonString); // 解析 JSON
|
|||
|
|||
if ((parsedObject.event === 'message' || parsedObject.event === 'message_end') && 'answer' in parsedObject) { |
|||
this._currentContent[title] = parsedObject.answer; |
|||
|
|||
// Promise.resolve(marked(parsedObject.answer)).then((result) => {
|
|||
// console.log(result);
|
|||
// });
|
|||
|
|||
|
|||
// 将当前内容备份
|
|||
if (!this._contentBackup.has(title)) { |
|||
this._contentBackup.set(title, '');// 使用 set() 添加键值对
|
|||
} |
|||
this._contentBackup.set(title, (this._contentBackup.get(title) ?? '') + this._currentContent[title]); // 使用 get() 获取值,更新并重新设置
|
|||
|
|||
this._view.webview.postMessage({ |
|||
type: 'update', |
|||
title: title, |
|||
content: this._currentContent[title], |
|||
isAppend: isAppend |
|||
}); |
|||
} else { |
|||
// 处理不是以 'data:' 开头的情况
|
|||
console.info('Content does not start with "data:"', content); |
|||
} |
|||
} catch (e) { |
|||
console.error(MESSAGES.PARSE_DATA_ERROR, e); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public async fetchSSE(type: string, code: string, onData: (data: string) => void) { |
|||
let params = { apiUrl: "", requestBody: {}, headers: {} }; // 先定义默认值
|
|||
switch (modelType) { |
|||
case MODELLIST.DIFY: |
|||
params = this.createParamsForDify(type, code); |
|||
break; |
|||
case MODELLIST.OLLAMA: |
|||
params = this.createParamsForOllama(type, code); |
|||
break; |
|||
default: |
|||
// 不做任何修改,保持默认值
|
|||
break; |
|||
} |
|||
|
|||
try { |
|||
const response = await fetch(params.apiUrl, { |
|||
method: 'POST', |
|||
headers: params.headers, |
|||
body: JSON.stringify(params.requestBody) |
|||
}); |
|||
|
|||
if (!response.body) { |
|||
throw new Error('Response body is empty'); |
|||
} |
|||
|
|||
const reader = response.body.getReader(); |
|||
const decoder = new TextDecoder(); |
|||
|
|||
while (true) { |
|||
const { done, value } = await reader.read(); |
|||
|
|||
if (done) { |
|||
// SSE结束,通知停止更新
|
|||
this.stopAppendingContent(type); |
|||
break; |
|||
} |
|||
|
|||
const text = decoder.decode(value, { stream: true }); |
|||
onData(text); |
|||
} |
|||
} catch (error) { |
|||
console.error('Fetch Error:', error); |
|||
throw error; // 抛出错误,供外部处理
|
|||
} |
|||
} |
|||
|
|||
public strIsEmpty(str: string | null | undefined): boolean { |
|||
return !str || str.trim() === ""; |
|||
} |
|||
|
|||
public createParamsForOllama(type: string, code: string) { |
|||
if(this.strIsEmpty(this._selectedModel)){ |
|||
vscode.window.showInformationMessage(MESSAGES.NO_AVAILABLE_MODELS); |
|||
} |
|||
|
|||
const apiUrl = 'http://localhost:11434/api/chat'; |
|||
|
|||
const requestBody = { |
|||
"model": this._selectedModel, |
|||
"messages": [ |
|||
{ "role": "user", "content": MESSAGES.ANSWER_IN_JAPANESE + "\n" + `${type}: ${code}`} |
|||
] |
|||
}; |
|||
|
|||
const headers = { |
|||
'Content-Type': 'application/json' |
|||
} |
|||
|
|||
return { apiUrl: apiUrl, requestBody: requestBody, headers: headers}; |
|||
|
|||
} |
|||
|
|||
public createParamsForDify(type: string, code: string) { |
|||
const apiUrl = 'https://dify.cloudscale.jp/v1/chat-messages'; |
|||
const apiKey = 'app-hMuLiedinKFqvntOPQOwtZJg'; //API Key
|
|||
|
|||
const requestBody = { |
|||
query: MESSAGES.ANSWER_IN_JAPANESE + "\n" + `${type}: ${code}`, |
|||
response_mode: "streaming", |
|||
conversation_id: "", |
|||
user: "vscode-test-1", |
|||
inputs: {} |
|||
}; |
|||
|
|||
const headers = { |
|||
'Authorization': `Bearer ${apiKey}`, |
|||
'Content-Type': 'application/json' |
|||
} |
|||
|
|||
return { apiUrl: apiUrl, requestBody: requestBody, headers: headers}; |
|||
} |
|||
|
|||
// 停止追加内容
|
|||
public stopAppendingContent(title: string) { |
|||
if (!this._view) { |
|||
vscode.window.showInformationMessage(MESSAGES.PLEASE_OPEN_SIDEBAR); |
|||
return; |
|||
} |
|||
this._view.webview.postMessage({ |
|||
type: 'stop', |
|||
title: title |
|||
}); |
|||
} |
|||
|
|||
private _getHtml(): string { |
|||
|
|||
const showModelSelector = modelType === MODELLIST.OLLAMA; |
|||
|
|||
return ` |
|||
<!DOCTYPE html> |
|||
<html> |
|||
<head> |
|||
<meta charset="UTF-8"> |
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script> |
|||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-javascript.min.js"></script> |
|||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-python.min.js"></script> |
|||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> |
|||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css"> |
|||
<style> |
|||
body { |
|||
margin: 0; |
|||
padding: 4px; /* 减少内边距 */ |
|||
color: var(--vscode-editor-foreground); |
|||
background: var(--vscode-editor-background); |
|||
overflow-x: hidden; |
|||
width: 100%; |
|||
} |
|||
#contentContainer { |
|||
width: 100%; |
|||
max-width: 100vw; /* 新增 */ |
|||
} |
|||
.content-title { |
|||
font-size: 14px; |
|||
margin: 12px 0 6px 4px; |
|||
color: #F4A460; |
|||
} |
|||
.content-section { |
|||
margin-bottom: 2px; /* 减小段落之间的间距 */ |
|||
width: 100%; |
|||
box-sizing: border-box; /* 新增 */ |
|||
} |
|||
pre { |
|||
width: 100%; |
|||
max-width: 100%; |
|||
overflow-x: auto; /* 避免内容溢出 */ |
|||
word-wrap: break-word; |
|||
white-space: pre-wrap; /* 确保代码换行 */ |
|||
background: var(--vscode-editorWidget-background); |
|||
padding: 10px; |
|||
border-radius: 4px; |
|||
border: 1px solid var(--vscode-editorWidget-border); |
|||
margin: 0; |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
code { |
|||
font-family: Consolas, Monaco, 'Courier New', monospace; |
|||
display: inline !important; /* 保持代码块行内显示 ,如果用block会导致代码单独占一行*/ |
|||
width: 100%; |
|||
max-width: 100%; |
|||
white-space: pre-wrap !important; /* 强制换行 */ |
|||
word-wrap: break-word; |
|||
overflow-wrap: break-word; |
|||
color: inherit; |
|||
} |
|||
|
|||
p { |
|||
margin: 0; /* 如果有段落标签,取消默认的上下间距 */ |
|||
} |
|||
|
|||
ul, ol { |
|||
margin: -10px 0 -16px 0 !important; /* 强制缩小列表的上下间距 */ |
|||
} |
|||
|
|||
li { |
|||
margin: -6px 0 !important; /* 强制减少 li 之间的间距 */ |
|||
} |
|||
|
|||
|
|||
|
|||
/* Prism.js 代码块样式修正 */ |
|||
pre[class*="language-"] { |
|||
white-space: pre-wrap !important; |
|||
word-break: break-word; |
|||
} |
|||
|
|||
/* 下拉选择器样式 */ |
|||
.model-selector-container { |
|||
display: ${showModelSelector ? 'flex' : 'none'}; /* 根据 modelType 决定是否显示 */ |
|||
align-items: center; |
|||
margin-bottom: 12px; |
|||
padding: 8px; |
|||
background: var(--vscode-editorWidget-background); |
|||
border-radius: 4px; |
|||
border: 1px solid var(--vscode-editorWidget-border); |
|||
} |
|||
|
|||
.model-selector-label { |
|||
font-size: 14px; |
|||
color: var(--vscode-editor-foreground); |
|||
margin-right: 8px; |
|||
} |
|||
|
|||
#modelSelector { |
|||
flex: 1; |
|||
padding: 6px; |
|||
font-size: 14px; |
|||
color: var(--vscode-editor-foreground); |
|||
background: var(--vscode-editor-background); |
|||
border: 1px solid var(--vscode-editorWidget-border); |
|||
border-radius: 4px; |
|||
outline: none; |
|||
cursor: pointer; |
|||
} |
|||
|
|||
#modelSelector:hover { |
|||
border-color: var(--vscode-focusBorder); |
|||
} |
|||
|
|||
#modelSelector:focus { |
|||
border-color: var(--vscode-focusBorder); |
|||
box-shadow: 0 0 0 2px var(--vscode-focusBorder); |
|||
} |
|||
</style> |
|||
|
|||
</head> |
|||
<body> |
|||
${showModelSelector ? ` |
|||
<div class="model-selector-container"> |
|||
<span class="model-selector-label">Model: </span> |
|||
<select id="modelSelector"> |
|||
</select> |
|||
</div> |
|||
` : ''}
|
|||
<div id="contentContainer"></div> |
|||
<script> |
|||
const vscode = acquireVsCodeApi(); |
|||
const container = document.getElementById('contentContainer'); |
|||
const modelSelector = document.getElementById('modelSelector'); |
|||
|
|||
// 监听下拉选择列表的变化
|
|||
if (modelSelector) { |
|||
modelSelector.addEventListener('change', (event) => { |
|||
const selectedModel = event.target.value; |
|||
vscode.postMessage({ |
|||
type: 'modelChange', |
|||
model: selectedModel |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
window.addEventListener('message', event => { |
|||
const { type, title, content, isAppend, models, selectedModel} = event.data; |
|||
if (type === 'update') { |
|||
let section = document.getElementById(title); |
|||
if (!section) { |
|||
section = document.createElement('div'); |
|||
section.className = 'content-section'; |
|||
section.id = title; |
|||
section.innerHTML = \` |
|||
<div class="content-title">\${title}</div> |
|||
<pre><code id="\${title}-code" class="language-javascript"></code></pre> |
|||
<div style="display: none;"><rawCode id="\${title}-raw-code"></rawCode></div> |
|||
\`;
|
|||
container.appendChild(section); |
|||
} |
|||
|
|||
const rawCodeElement = section.querySelector('rawCode'); |
|||
let rawContent = ''; |
|||
if (isAppend) { |
|||
rawContent = rawCodeElement.textContent + content; |
|||
} else { |
|||
rawContent = content; |
|||
} |
|||
|
|||
//特殊符号需要加个\转义
|
|||
// rawContent = rawContent.replace(/\\n+/g, '\\n');
|
|||
|
|||
rawCodeElement.textContent = rawContent; |
|||
|
|||
// 配置高亮
|
|||
marked.setOptions({ |
|||
highlight: function (code, lang) { |
|||
const language = Prism.languages[lang] || Prism.languages.javascript; // 默认 JavaScript
|
|||
return Prism.highlight(code, language, lang || 'javascript'); |
|||
}, |
|||
gfm: true, |
|||
breaks: true |
|||
}); |
|||
|
|||
let htmlContent = marked.parse(rawContent); |
|||
const finalContent = htmlContent |
|||
|
|||
const showCodeElement = section.querySelector('code'); |
|||
showCodeElement.innerHTML = finalContent; |
|||
|
|||
// 重新高亮代码
|
|||
// Prism.highlightAll();
|
|||
} else if (type === 'updateModels') { |
|||
if (modelSelector) { |
|||
modelSelector.innerHTML = ''; // 清空加载中的提示
|
|||
|
|||
if (models.length > 0) { |
|||
models.forEach(model => { |
|||
const option = document.createElement('option'); |
|||
option.value = model.name; |
|||
option.textContent = model.name; |
|||
modelSelector.appendChild(option); |
|||
}); |
|||
|
|||
// 默认选中第一个模型
|
|||
modelSelector.value = selectedModel; |
|||
|
|||
// 发送默认选中的模型给扩展
|
|||
vscode.postMessage({ |
|||
type: 'modelChange', |
|||
model: selectedModel |
|||
}); |
|||
} else { |
|||
// 如果没有模型,显示提示
|
|||
const option = document.createElement('option'); |
|||
option.value = ''; |
|||
option.textContent = ''; |
|||
modelSelector.appendChild(option); |
|||
} |
|||
} |
|||
} |
|||
}); |
|||
</script> |
|||
</body> |
|||
</html>`;
|
|||
} |
|||
} |
|||
|
|||
// 修改后的 activate 函数
|
|||
export function activate(context: vscode.ExtensionContext) { |
|||
// 注册侧边栏
|
|||
const sidebarProvider = new SidebarContentProvider(context.extensionUri); |
|||
context.subscriptions.push( |
|||
vscode.window.registerWebviewViewProvider( |
|||
SidebarContentProvider.viewType, |
|||
sidebarProvider |
|||
) |
|||
); |
|||
|
|||
|
|||
// 保留原有 CodeLens 注册
|
|||
context.subscriptions.push( |
|||
vscode.languages.registerCodeLensProvider( |
|||
{ scheme: 'file' }, |
|||
new CodeLensProvider() |
|||
) |
|||
); |
|||
|
|||
// 通用命令注册函数
|
|||
const registerCommand = (command: string, title: string) => { |
|||
context.subscriptions.push( |
|||
vscode.commands.registerCommand(command, async (code) => { |
|||
try { |
|||
// 自动显示侧边栏,并等待侧边栏完全初始化
|
|||
await sidebarProvider.ensureVisible(); |
|||
// 这里要刷新下拉列表,以防没有提前打开工具栏
|
|||
if (modelType === MODELLIST.OLLAMA && sidebarProvider.strIsEmpty(sidebarProvider._selectedModel)){ |
|||
await sidebarProvider.sendModelsToWebview(); |
|||
} |
|||
|
|||
// 加入时间,这样 _getHtml 那里 section,每次都可以初始化新的 div
|
|||
const now = new Date(); |
|||
const year = now.getFullYear(); // 获取年份
|
|||
const month = String(now.getMonth() + 1).padStart(2, '0'); // 获取月份(注意月份从0开始,所以加1)
|
|||
const day = String(now.getDate()).padStart(2, '0'); // 获取日期
|
|||
const hours = String(now.getHours()).padStart(2, '0'); // 获取小时
|
|||
const minutes = String(now.getMinutes()).padStart(2, '0'); // 获取分钟
|
|||
const seconds = String(now.getSeconds()).padStart(2, '0'); // 获取秒钟
|
|||
|
|||
// 格式化输出
|
|||
const formattedTime = `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`; |
|||
const titleWithTime = title + " " + formattedTime; |
|||
|
|||
// 执行 SSE 请求
|
|||
await sidebarProvider.fetchSSE(title, code, (data) => { |
|||
sidebarProvider.updateContent(titleWithTime, data); |
|||
}); |
|||
} catch (error) { |
|||
let errorMessage = MESSAGES.REQUEST_FAILED; |
|||
if (error instanceof Error) { |
|||
errorMessage += `: ${error.message}`; |
|||
} else if (typeof error === 'string') { |
|||
errorMessage += `: ${error}`; |
|||
} else { |
|||
errorMessage += ': Unknown error'; |
|||
} |
|||
vscode.window.showErrorMessage(errorMessage); |
|||
} |
|||
}) |
|||
); |
|||
}; |
|||
|
|||
// 注册原有命令(保留所有功能)
|
|||
registerCommand('extension.showExplanation', CONSTANTS.CODE_INTERPRETATION); |
|||
registerCommand('extension.showComments', CONSTANTS.FUNCTION_ANNOTATION); |
|||
registerCommand('extension.showOptimization', CONSTANTS.TUNING_RECOMMENDATION); |
|||
} |
|||
|
|||
// 保留 deactivate 函数
|
|||
export function deactivate() {} |
@ -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'; |
|||
// import * as myExtension from '../../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)); |
|||
}); |
|||
}); |
@ -0,0 +1,12 @@ |
|||
{ |
|||
"compilerOptions": { |
|||
"target": "es6", |
|||
"lib": ["es6"], |
|||
"module": "commonjs", |
|||
"outDir": "./dist", |
|||
"rootDir": "./src", |
|||
"strict": true |
|||
}, |
|||
"exclude": ["node_modules", ".vscode"] |
|||
} |
|||
|
@ -0,0 +1,48 @@ |
|||
# 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. |
|||
* `src/extension.ts` - 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`. |
|||
|
|||
## Setup |
|||
|
|||
* install the recommended extensions (amodio.tsl-problem-matcher, ms-vscode.extension-test-runner, and dbaeumer.vscode-eslint) |
|||
|
|||
|
|||
## 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 `src/extension.ts` 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 `src/extension.ts`. |
|||
* 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) |
|||
* Run the "watch" task via the **Tasks: Run Task** command. Make sure this is running, or tests might not be discovered. |
|||
* 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 `src/test/extension.test.ts` or create new test files inside the `test` folder. |
|||
* The provided test runner will only consider files matching the name pattern `**.test.ts`. |
|||
* You can create folders inside the `test` folder to structure your tests any way you want. |
|||
|
|||
## Go further |
|||
|
|||
* Reduce the extension size and improve the startup time by [bundling your extension](https://code.visualstudio.com/api/working-with-extensions/bundling-extension). |
|||
* [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). |
@ -0,0 +1,48 @@ |
|||
//@ts-check
|
|||
|
|||
'use strict'; |
|||
|
|||
const path = require('path'); |
|||
|
|||
//@ts-check
|
|||
/** @typedef {import('webpack').Configuration} WebpackConfig **/ |
|||
|
|||
/** @type WebpackConfig */ |
|||
const extensionConfig = { |
|||
target: 'node', // VS Code extensions run in a Node.js-context 📖 -> https://webpack.js.org/configuration/node/
|
|||
mode: 'none', // this leaves the source code as close as possible to the original (when packaging we set this to 'production')
|
|||
|
|||
entry: './src/extension.ts', // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/
|
|||
output: { |
|||
// the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/
|
|||
path: path.resolve(__dirname, 'dist'), |
|||
filename: 'extension.js', |
|||
libraryTarget: 'commonjs2' |
|||
}, |
|||
externals: { |
|||
vscode: 'commonjs vscode' // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/
|
|||
// modules added here also need to be added in the .vscodeignore file
|
|||
}, |
|||
resolve: { |
|||
// support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader
|
|||
extensions: ['.ts', '.js'] |
|||
}, |
|||
module: { |
|||
rules: [ |
|||
{ |
|||
test: /\.ts$/, |
|||
exclude: /node_modules/, |
|||
use: [ |
|||
{ |
|||
loader: 'ts-loader' |
|||
} |
|||
] |
|||
} |
|||
] |
|||
}, |
|||
devtool: 'nosources-source-map', |
|||
infrastructureLogging: { |
|||
level: "log", // enables logging required for problem matchers
|
|||
}, |
|||
}; |
|||
module.exports = [ extensionConfig ]; |
Loading…
Reference in new issue