You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
200 lines
8.7 KiB
200 lines
8.7 KiB
3 months ago
|
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;
|
||
|
}
|
||
|
}
|