diff --git a/package.json b/package.json index d5ae142..f2b6dba 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "turbo-file-header", "displayName": "Turbo File Header", "description": "%description%", - "version": "0.2.6", + "version": "0.2.7", "icon": "resources/icons/icon.png", "repository": { "type": "git", diff --git a/sampleWorkspace/function-comment-for-rust/function-none-params-with-return.result.rs b/sampleWorkspace/function-comment-for-rust/function-none-params-with-return.result.rs new file mode 100644 index 0000000..f81417d --- /dev/null +++ b/sampleWorkspace/function-comment-for-rust/function-none-params-with-return.result.rs @@ -0,0 +1,7 @@ +/** + * @description + * @return default {String} + */ +fn foo() -> String { + "a" +} diff --git a/sampleWorkspace/function-comment-for-rust/function-none-params-with-return.rs b/sampleWorkspace/function-comment-for-rust/function-none-params-with-return.rs new file mode 100644 index 0000000..beaf7d3 --- /dev/null +++ b/sampleWorkspace/function-comment-for-rust/function-none-params-with-return.rs @@ -0,0 +1,3 @@ +fn foo() -> String { + "a" +} diff --git a/sampleWorkspace/function-comment-for-rust/function-none-params-without-return.result.rs b/sampleWorkspace/function-comment-for-rust/function-none-params-without-return.result.rs new file mode 100644 index 0000000..6c8de12 --- /dev/null +++ b/sampleWorkspace/function-comment-for-rust/function-none-params-without-return.result.rs @@ -0,0 +1,7 @@ +/** + * @description + * @return default {auto} + */ +fn foo(x) { + println!("a"); +} diff --git a/sampleWorkspace/function-comment-for-rust/function-none-params-without-return.rs b/sampleWorkspace/function-comment-for-rust/function-none-params-without-return.rs new file mode 100644 index 0000000..a6ff25b --- /dev/null +++ b/sampleWorkspace/function-comment-for-rust/function-none-params-without-return.rs @@ -0,0 +1,3 @@ +fn foo(x) { + println!("a"); +} diff --git a/sampleWorkspace/function-comment-for-rust/function-with-params-with-return.result.rs b/sampleWorkspace/function-comment-for-rust/function-with-params-with-return.result.rs new file mode 100644 index 0000000..ad190fa --- /dev/null +++ b/sampleWorkspace/function-comment-for-rust/function-with-params-with-return.result.rs @@ -0,0 +1,10 @@ +/** + * @description + * @return default {i32} + * @param bar {Option} + */ +fn foo(bar: Option) -> i32 { + let bar = bar.unwrap_or(42); + println!("{}", bar); + bar +} diff --git a/sampleWorkspace/function-comment-for-rust/function-with-params-with-return.rs b/sampleWorkspace/function-comment-for-rust/function-with-params-with-return.rs new file mode 100644 index 0000000..43402eb --- /dev/null +++ b/sampleWorkspace/function-comment-for-rust/function-with-params-with-return.rs @@ -0,0 +1,5 @@ +fn foo(bar: Option) -> i32 { + let bar = bar.unwrap_or(42); + println!("{}", bar); + bar +} diff --git a/sampleWorkspace/function-comment-for-rust/function-with-params-without-return.result.rs b/sampleWorkspace/function-comment-for-rust/function-with-params-without-return.result.rs new file mode 100644 index 0000000..e4d366e --- /dev/null +++ b/sampleWorkspace/function-comment-for-rust/function-with-params-without-return.result.rs @@ -0,0 +1,9 @@ +/** + * @description + * @return default {auto} + * @param bar {Option} + */ +fn foo(bar: Option) { + let bar = bar.unwrap_or(42); + println!("{}", bar); +} diff --git a/sampleWorkspace/function-comment-for-rust/function-with-params-without-return.rs b/sampleWorkspace/function-comment-for-rust/function-with-params-without-return.rs new file mode 100644 index 0000000..e098d20 --- /dev/null +++ b/sampleWorkspace/function-comment-for-rust/function-with-params-without-return.rs @@ -0,0 +1,4 @@ +fn foo(bar: Option) { + let bar = bar.unwrap_or(42); + println!("{}", bar); +} diff --git a/sampleWorkspace/test.code-workspace b/sampleWorkspace/test.code-workspace index 2b8996a..cb6853f 100644 --- a/sampleWorkspace/test.code-workspace +++ b/sampleWorkspace/test.code-workspace @@ -7,6 +7,7 @@ { "path": "function-comment-for-java" }, { "path": "function-comment-for-python" }, { "path": "function-comment-for-php" }, + { "path": "function-comment-for-rust" }, { "path": "workspace" }, ], "settings": {}, diff --git a/src/function-params-parser/FunctionParserLoader.ts b/src/function-params-parser/FunctionParserLoader.ts index 7ac66a4..4230b90 100644 --- a/src/function-params-parser/FunctionParserLoader.ts +++ b/src/function-params-parser/FunctionParserLoader.ts @@ -8,6 +8,7 @@ import { JavaParser } from './JavaProvider'; import { JavascriptParser } from './JavascriptProvider'; import { PhpParser } from './PhpProvider'; import { PythonParser } from './PythonProvider'; +import { RustParser } from './RustProvider'; import { TypescriptParser } from './TypescriptProvider'; export class FunctionParserLoader { @@ -33,6 +34,7 @@ export class FunctionParserLoader { java: JavaParser, python: PythonParser, php: PhpParser, + rust: RustParser, }; public async loadParser(languageId: string): Promise { diff --git a/src/function-params-parser/RustProvider.ts b/src/function-params-parser/RustProvider.ts new file mode 100644 index 0000000..8640512 --- /dev/null +++ b/src/function-params-parser/RustProvider.ts @@ -0,0 +1,196 @@ +import * as vscode from 'vscode'; + +import { ConfigManager } from '@/configuration/ConfigManager'; +import { logger } from '@/extension'; +import { LanguageFunctionCommentSettings } from '@/typings/types'; +import { escapeRegexString } from '@/utils/str'; + +import { extractFunctionParamsString } from './extractFunctionParamsString'; +import { FunctionParamsParser } from './FunctionParamsParser'; +import { splitParams } from './rust-splitParams'; +import { FunctionParamsInfo, ParamsInfo, ReturnInfo } from './types'; + +function matchNormalFunction( + functionDefinition: string, + languageSettings: LanguageFunctionCommentSettings, +): { + matched: boolean; + returnType: ReturnInfo; + params: ParamsInfo; +} { + const { defaultReturnName = 'default', defaultReturnType = 'auto' } = languageSettings; + const returnType: ReturnInfo = {}; + let matched = false; + let params: ParamsInfo = {}; + + // 提取参数括号里的字符串 + const functionParamsStr = extractFunctionParamsString(functionDefinition); + const functionParamsRegStr = escapeRegexString(functionParamsStr); + const functionPattern = new RegExp( + `fn\\s+([a-zA-Z0-9_]+)\\s*(<.*>)?\\s*\\(${functionParamsRegStr}\\)\\s*(->\\s*(.*))?\\s*{`, + 'm', + ); + + const match = functionPattern.exec(functionDefinition); + if (match) { + matched = true; + const returnTypeStr = match[4] ? match[4].trim() : defaultReturnType; + + returnType[defaultReturnName] = { + type: returnTypeStr, + description: '', + }; + + params = splitParams(functionParamsStr, languageSettings); + } + + return { matched, returnType, params }; +} + +function matchAssociatedFunction( + functionDefinition: string, + languageSettings: LanguageFunctionCommentSettings, +): { + matched: boolean; + returnType: ReturnInfo; + params: ParamsInfo; +} { + const { defaultReturnName = 'default', defaultReturnType = 'auto' } = languageSettings; + const returnType: ReturnInfo = {}; + let matched = false; + let params: ParamsInfo = {}; + + // 提取参数括号里的字符串 + const functionParamsStr = extractFunctionParamsString(functionDefinition); + const functionParamsRegStr = escapeRegexString(functionParamsStr); + const functionPattern = new RegExp( + `impl\\s+([a-zA-Z0-9_]+)\\s*{\\s*fn\\s+([a-zA-Z0-9_]+)\\s*(<.*>)?\\s*\\(${functionParamsRegStr}\\)\\s*(->\\s*(.*))?\\s*{`, + 'm', + ); + + const match = functionPattern.exec(functionDefinition); + + if (match) { + matched = true; + const returnTypeStr = match[5] ? match[5].trim() : defaultReturnType; + + returnType[defaultReturnName] = { + type: returnTypeStr, + description: '', + }; + + params = splitParams(functionParamsStr, languageSettings); + } + + return { matched, returnType, params }; +} + +/** + * @description + * @return default {auto} + */ +function matchFunction( + functionDefinition: string, + languageSettings: LanguageFunctionCommentSettings, +): { matched: boolean; returnType: ReturnInfo; params: ParamsInfo } { + const { defaultReturnName = 'default', defaultReturnType = 'Null' } = languageSettings; + let returnType: ReturnInfo = { + [defaultReturnName]: { type: defaultReturnType, description: '' }, + }; + let matched = false; + let params: ParamsInfo = {}; + + const matchers = [matchNormalFunction, matchAssociatedFunction]; + + for (const matcher of matchers) { + const result = matcher(functionDefinition, languageSettings); + if (result.matched) { + matched = result.matched; + params = result.params; + returnType = result.returnType; + break; + } + } + + return { matched, returnType, params }; +} + +export class RustParser extends FunctionParamsParser { + constructor(configManager: ConfigManager, languageId: string) { + super(configManager, languageId); + } + + private getFunctionString(document: vscode.TextDocument, startLine: number) { + let functionDefinition = ''; + let bracketCount = 0; // 大括号计数 + let parenthesisCount = 0; // 小括号计数 + + for (let i = startLine; i < document.lineCount; i++) { + const line = document.lineAt(i); + functionDefinition += line.text + '\n'; + + for (const char of line.text) { + if (char === '(') { + parenthesisCount++; + } else if (char === ')') { + parenthesisCount--; + } else if (char === '{') { + bracketCount++; + } else if (char === '}') { + bracketCount--; + } + } + + if (bracketCount === 0 && parenthesisCount === 0) { + break; + } + } + + return functionDefinition; + } + + public getFunctionParamsAtCursor( + activeEditor: vscode.TextEditor, + languageSettings: LanguageFunctionCommentSettings = this.languageSettings, + ): FunctionParamsInfo { + let functionParams: ParamsInfo = {}; + let matchedFunction = false; + let returnType: ReturnInfo = {}; + const document = activeEditor.document; + const cursorLine = activeEditor.selection.start.line; + let startLine = cursorLine; + // 如果光标所在行为空行或者注释,则从下一行开始 + const cursorLineText = document.lineAt(cursorLine).text.trim(); + if ( + cursorLineText === '' || + cursorLineText === '//' || + cursorLineText === '#' || + cursorLineText === '*/' + ) { + startLine = cursorLine + 1; + } + + const functionDefinition = this.getFunctionString(document, startLine); + const { + matched, + returnType: returnTypeTmp, + params, + } = matchFunction(functionDefinition, languageSettings); + if (matched) { + matchedFunction = true; + returnType = returnTypeTmp; + functionParams = params; + } + + if (!matchFunction) { + logger.info(vscode.l10n.t('No function found at the cursor')); + } + + return { + matchedFunction, + returnType, + params: functionParams, + insertPosition: new vscode.Position(startLine, 0), + }; + } +} diff --git a/src/function-params-parser/rust-splitParams.ts b/src/function-params-parser/rust-splitParams.ts new file mode 100644 index 0000000..4a6066d --- /dev/null +++ b/src/function-params-parser/rust-splitParams.ts @@ -0,0 +1,45 @@ +import { LanguageFunctionCommentSettings } from '@/typings/types'; + +import { ParamsInfo } from './types'; + +export function splitParams( + paramsStr: string, + languageSettings: LanguageFunctionCommentSettings, +): ParamsInfo { + const { defaultParamType = 'mixed', defaultReturnName = 'default' } = languageSettings; + let bracketCount = 0; + let paramStartIndex = 0; + let defaultCount = 0; + const params: ParamsInfo = {}; + for (let i = 0; i < paramsStr?.length; i++) { + const char = paramsStr[i]; + if (char === '(' || char === '[' || char === '{' || char === '<') { + bracketCount++; + } else if (char === ')' || char === ']' || char === '}' || char === '>') { + bracketCount--; + } + + if ( + (char === ',' && bracketCount === 0) || + (i === paramsStr.length - 1 && bracketCount === 0) + ) { + const paramStr = paramsStr + .slice(paramStartIndex, i === paramsStr.length - 1 ? i + 1 : i) + .trim(); + + const paramPattern = /^(&self|&mut\s+self)?(\w+)\s*:\s*(.*)$/; + const match = paramPattern.exec(paramStr); + if (match) { + const name = + match[2] || + (defaultCount > 0 ? `${defaultReturnName}${defaultCount++}` : defaultReturnName); + const type = match[3]?.trim() || defaultParamType; + + params[name] = { type, description: '' }; + } + paramStartIndex = i + 1; + } + } + + return params; +} diff --git a/src/test/suite/rust-functionComment.test.ts b/src/test/suite/rust-functionComment.test.ts new file mode 100644 index 0000000..cc5e6a8 --- /dev/null +++ b/src/test/suite/rust-functionComment.test.ts @@ -0,0 +1,17 @@ +import { functionCommentTester } from './common/functionCommentTester'; +import { TestInfo } from './types'; + +const testInfo: TestInfo = [ + { + testName: 'rust-function', + workspaceName: 'function-comment-for-rust', + files: [ + { fileName: 'function-none-params-with-return.rs', cursorLine: 0 }, + { fileName: 'function-none-params-without-return.rs', cursorLine: 0 }, + { fileName: 'function-with-params-with-return.rs', cursorLine: 0 }, + { fileName: 'function-with-params-without-return.rs', cursorLine: 0 }, + ], + }, +]; + +functionCommentTester(testInfo);