diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ea00df25..9cc42eea 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -74,7 +74,7 @@ jobs: # Store already-built plugin as an artifact for downloading - name: Upload artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ steps.artifact.outputs.filename }} path: ${{ steps.artifact.outputs.filename }} diff --git a/CHANGELOG.md b/CHANGELOG.md index c46f347f..2705a963 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,31 @@ +## [0.5.2](https://github.com/unit-mesh/auto-dev-vscode/compare/v0.5.0...v0.5.2) (2024-07-19) + + +### Bug Fixes + +* add simple local test for local version ([ddff9fb](https://github.com/unit-mesh/auto-dev-vscode/commit/ddff9fb87abb7087455980194c9a540c49f36dd8)) +* **commandsService:** swap SemanticSearchKeyword and SemanticSearchCode methods ([f87dbc7](https://github.com/unit-mesh/auto-dev-vscode/commit/f87dbc7d126ae0ccfd3cc2f7400c8587c81da4ff)) +* **extension:** fix hf local model and remote cache paths ([#64](https://github.com/unit-mesh/auto-dev-vscode/issues/64)) ([1d5c96f](https://github.com/unit-mesh/auto-dev-vscode/commit/1d5c96f0ef3e074743854f6e29e90a16600cc7b8)) +* invoke codebase indexing action ([69f35cf](https://github.com/unit-mesh/auto-dev-vscode/commit/69f35cf9fec418b5f67ab66c3751bb1b0c7ca5b7)) +* openai end-of-stream output is undefined ([be24963](https://github.com/unit-mesh/auto-dev-vscode/commit/be249634de4a6677069c3b2b27b99aaa3aed19e1)) +* support for chat.models override base configuration ([#60](https://github.com/unit-mesh/auto-dev-vscode/issues/60)) ([e2070d4](https://github.com/unit-mesh/auto-dev-vscode/commit/e2070d45cf10b231399e61bec7c8bc926bed6e13)) +* version field is not configured ([9ebfe0e](https://github.com/unit-mesh/auto-dev-vscode/commit/9ebfe0e347b0ef6eb72cd137537f42183dd1832e)) + + +### Features + +* **chatView:** auto add selected code to chat panel when shortcut (ctrl + l) or the menu ([c0c97ef](https://github.com/unit-mesh/auto-dev-vscode/commit/c0c97effe7be0a44789ac487b3f886eee1dbceed)) +* **code-search:** implement BM25 similarity algorithm ([6a9175c](https://github.com/unit-mesh/auto-dev-vscode/commit/6a9175c21ff656a6447fb5af356cad9adb04fd4b)) +* **completion:** Add new language model "codegeex-4" for inline completion ([15a6c80](https://github.com/unit-mesh/auto-dev-vscode/commit/15a6c80093c34d66ba1bf9e18d3b4d9d54c3a31f)) +* custom display and sort codelens items ([#57](https://github.com/unit-mesh/auto-dev-vscode/issues/57)) ([e66a4f8](https://github.com/unit-mesh/auto-dev-vscode/commit/e66a4f889be02d4040d5c8962a03c7428c00af4e)) +* **embedding:** implement ILanguageModelProvider in LocalEmbeddingsProvider ([a7a1862](https://github.com/unit-mesh/auto-dev-vscode/commit/a7a18629007605b103372967067f46329bab3d9d)) +* **llm:** add codegeex-4 model support for chats ([3e8e2d2](https://github.com/unit-mesh/auto-dev-vscode/commit/3e8e2d2f45d38e60cf481f63fed391b2b15ff889)) +* **llm:** add glm-4 model support for chats ([c6a92b4](https://github.com/unit-mesh/auto-dev-vscode/commit/c6a92b4285e1f19ff36ec7a80cebc4b281e61176)) +* **search:** add removeDocument method to Tfidf class ([9b63fcd](https://github.com/unit-mesh/auto-dev-vscode/commit/9b63fcd7c1f500d3c56325a74ed30f1d5efcf76d)) +* support fim special tokens configuration ([#62](https://github.com/unit-mesh/auto-dev-vscode/issues/62)) ([ccc47fb](https://github.com/unit-mesh/auto-dev-vscode/commit/ccc47fb27f93fe3b93d2e67b0209e54de3e0641a)) + + + # [0.5.0](https://github.com/unit-mesh/auto-dev-vscode/compare/v0.3.3...v0.5.0) (2024-06-14) diff --git a/README.md b/README.md index 0700954d..049eec22 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,10 @@ JetBrains' IDE Version: [https://github.com/unit-mesh/auto-dev](https://github.c Documentation: [https://vscode.unitmesh.cc/](https://vscode.unitmesh.cc/) +## Quick Start Guide to Contributing + +Contributing Documentation: [https://vscode.unitmesh.cc/development](https://vscode.unitmesh.cc/development) + ## Join the Community wechat qrcode diff --git a/docs/configuration.md b/docs/configuration.md index 88007d9a..77af79e0 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -68,12 +68,6 @@ examples: "provider": "openai", "model": "gpt-4", }, - { - "title": "GPT-3.5 turbo", - "provider": "openai", - "model": "gpt-3.5-turbo", - "temperature": 0.75, - }, { "title": "QWen turbo", "provider": "tongyi", diff --git a/docs/customize/team-prompts.md b/docs/customize/team-prompts.md index b2a95d8f..0367280a 100644 --- a/docs/customize/team-prompts.md +++ b/docs/customize/team-prompts.md @@ -52,7 +52,7 @@ interaction type: ## English Examples -variables list: [https://ide.unitmesh.cc/variables](https://ide.unitmesh.cc/variables) +variables list: [https://vscode.unitmesh.cc/customize/variables](https://vscode.unitmesh.cc/customize/variables) --- interaction: AppendCursorStream @@ -77,7 +77,7 @@ example: [https://github.com/unit-mesh/untitled/tree/english/prompts](https://gi ## 中文示例(Chinese example): -变量列表: [AutoDev 模板变量](/variables)。 +变量列表: [AutoDev 模板变量](/customize/variables)。 --- interaction: AppendCursorStream diff --git a/docs/development/dev-new-language.md b/docs/development/dev-new-language.md index 28b68f0c..c88a26c1 100644 --- a/docs/development/dev-new-language.md +++ b/docs/development/dev-new-language.md @@ -5,34 +5,46 @@ nav_order: 3 parent: Development --- +## Before adding a new language + +1. check if the language is already supported by tree-sitter +2. check the wasm file of tree-sitter grammar exist in https://github.com/unit-mesh/treesitter-artifacts +3. our version treesiter playground: https://unit-mesh.github.io/treesitter-artifacts/ + ## Add a New Language If you want to add a new language support, you can follow the steps below: -1. Create a new file in `src/semantic/${your-newlanguage}` folder, for example, `src/semantic/java`. +1. Create a new file in `src/code-context/${your-newlanguage}` folder, for example, `src/code-context/java`. 2. Implement the following interfaces: - - `LanguageConfig.ts`, core configuration for the language - - `RelatedCodeProvider.ts`, which will provider the related code for the code - - `Structurer.ts`, which will provider the UML for simplifying the code + - `LanguageProfile.ts`, core configuration for the language + - `RelevantCodeProvider.ts`, which will provider the related code for the code + - `BaseStructurerProvider.ts`, which will provider the UML for simplifying the code - `TestGenProvider.ts`, which will provider the test generation for the code -3. Add to ProviderManager, for example: - - RelatedCodeProviderManager - - StructurerProviderManager - - TestGenProviderManager +3. Add to `ProviderContainer.config.ts`, for example: +```typescript +providerContainer.bind(IToolchainContextProvider).to(SpringContextProvider); +providerContainer.bind(IToolchainContextProvider).to(JavaVersionProvider); -## LanguageConfig +providerContainer.bind(IRelevantCodeProvider).to(JavaRelevantCodeProvider); +providerContainer.bind(ITestGenProvider).to(JavaTestGenProvider); +providerContainer.bind(IBuildToolProvider).to(GradleBuildToolProvider); +providerContainer.bind(IStructurerProvider).to(JavaStructurerProvider); +``` + +### LanguageConfig example ```typescript -export interface LanguageConfig { +export interface LanguageProfile { // A list of language names that can be processed by these node queries - // e.g.: ["typescript", "typescriptreact"], ["rust"] + // e.g.: ["Typescript", "TSX"], ["Rust"] languageIds: string[]; // Extensions that can help classify the file: .rs, .rb, .cabal fileExtensions: string[]; // tree-sitter grammar for this language - grammar: (langService: TSLanguageService, langId: SupportedLanguage) => Promise; + grammar: (langService: ILanguageServiceProvider, langId?: LanguageIdentifier) => Promise; // Compiled tree-sitter node query for this language. scopeQuery: MemoizedQuery; @@ -40,20 +52,38 @@ export interface LanguageConfig { // Compiled tree-sitter hoverables query hoverableQuery: MemoizedQuery; + // in java, the canonical name is the package name + packageQuery?: MemoizedQuery; + // class query classQuery: MemoizedQuery; // method query methodQuery: MemoizedQuery; + blockCommentQuery: MemoizedQuery; + // method input and output query methodIOQuery?: MemoizedQuery; + fieldQuery?: MemoizedQuery; + // structurer query structureQuery: MemoizedQuery; // Namespaces defined by this language, // E.g.: type namespace, variable namespace, function namespace namespaces: NameSpaces; + + // should select parent + // for example, in JavaScript/TypeScript, if we select function, we should also select the export keyword. + autoSelectInsideParent: string[]; + + /// for IO analysis + builtInTypes: string[]; + + // should return true if the file is a test file + isTestFile: (filePath: string) => boolean; } + ``` diff --git a/docs/development/development.md b/docs/development/development.md index f361c105..b1238f26 100644 --- a/docs/development/development.md +++ b/docs/development/development.md @@ -6,12 +6,170 @@ has_children: true permalink: /development --- -{: .no_toc } +[!IMPORTANT] +# Study VSCode extension development + + - `VSCode extension development`: [English Document](https://code.visualstudio.com/api),[中文文档](https://github.com/Liiked/VS-Code-Extension-Doc-ZH) + - `debugging`: [debugging Document](https://code.visualstudio.com/docs/editor/debugging) + - `extension samples`: [VSCode extension samples](https://github.com/microsoft/vscode-extension-samples) + +# Quick Start Guide to Contributing + +1. Fork + +Click this URL [https://github.com/unit-mesh/auto-dev-vscode](https://github.com/unit-mesh/auto-dev-vscode), Click the `Fork` button to create your own copy of this repository + +2. Clone + +Use this command in the terminal tool to clone this project to your local machine: `git clone git@github.com:/auto-dev-vscode.git`, Replace "``" with your GitHub account, such as "git clone git@github.com:unit-mesh/auto-dev-vscode.git" + +3. Sync Fork + +Only execute this command once: `cd auto-dev-vscode && git remote add upstream git@github.com:unit-mesh/auto-dev-vscode.git` + +> Scenarios Where You Might Need to `git checkout master && git pull upstream master` First +> +> This ensures that your local repository stays up-to-date with the upstream repository, fetching and merging the latest changes each time. +> +> - **After a Long Period of Inactivity**: After a long period of inactivity, execute `git checkout master && git pull upstream master` before continuing to ensure you have the latest changes. +> - **Before Creating a New Branch**: Before creating a new branch, it's a good practice to `git checkout master && git pull upstream master && git checkout -b feat-xxxx` to ensure the new branch is based on the latest state of the upstream repository. +> - **Before Merging Changes**: Before merging other branches into the current branch, it's recommended to `git checkout master && git pull upstream master && git merge feat-xxxx` to ensure the local branch is up-to-date. +> - **Before Pushing Changes**: Before pushing your local changes to the remote repository, it's recommended to `git checkout master && git pull upstream master && git push` to avoid conflicts or outdated changes. + + + +4. Branch + +Create a new branch for your changes, such as: `git checkout master && git pull upstream master && git checkout -b feat-xxxx master` + +4. Merge + +Merge your branch into the master branch, such as: `git checkout master && git merge feat-xxxx` + +5. Push + +`git checkout master && git pull upstream master && git push` + +5. Pull Request(pr) + +Coding or edit documentation of this project, then commit your changes, and finally submit a pull request on GitHub + # Development +> Uninstall AutoDev extension +> +>If you have previously installed the AutoDev extension in Visual Studio Code, please uninstall it first, then restart Visual Studio Code. +>previously installed extension's configurations can still be automatically applied to cloned projects. + +## Quick Start Guide to Development + +1. Clone: `git clone https://github.com/unit-mesh/auto-dev-vscode.git` + +2. Install Dependencies for project: `cd auto-dev-vscode && npm install` + +3. Install dependencies for gui-sidebar: `cd gui-sidebar && npm install && build` + +4. open `auto-dev-vscode` folder in VSCode + +5. Press `F5` to launch the extension and begin debugging. + + + +## Node.js development environment + +> Before you start developing or debugging, make sure to initialize submodules + + +### Install nvm : + +Install nvm to manage different versions of Node.js. + + +``` +# macOS + +# nvm officially does not recommend installing via brew; a personal VPN is needed for a successful installation in mainland China; +# Run the following command directly in the terminal: +curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash + +# Set the node mirror.People in China can use +# Set the mirror source for installing Node.js: +nvm node_mirror https://npmmirror.com/mirrors/node/ + +# Set the mirror source for the npm that comes with the nvm installation: +nvm npm_mirror https://npmmirror.com/mirrors/npm/ +``` + + +``` +# Windows + +# Install nvm, the Node.js version manager, which is used to manage different versions of Node.js (nvm officially does not recommend installing via brew; a personal VPN is required for a successful installation in mainland China). +# nvm was originally designed for Unix-like systems, but there is a ported version for Windows called nvm-windows. Here are the installation steps: + +# Download nvm-windows (if the speed is slow, you may need to use a personal VPN): +# Open URL: https://github.com/coreybutler/nvm/releases, select the version you wish to download, and in the Assets section, click on "nvm-setup.exe". + +# Install nvm-windows: +# Run the downloaded nvm-setup.exe installer. +# During the installation process, the program will prompt you to select the installation path for nvm as well as the storage path for Node.js. +# By default, nvm will be installed in C:\Users\AppData\Roaming\nvm, and Node.js will be installed in C:\Program Files\nodejs. + +# Configure Environment Variables: The installer will automatically configure the environment variables for you, but if you encounter issues, you may need to set them manually: +# Right-click on “This PC” -> “Properties” -> “Advanced system settings” -> “Environment Variables”. +# In the “System variables” section, find the “Path” variable and click “Edit”. +# Ensure that the installation path for nvm and the path for Node.js are included; if not, create two new entries, one for the nvm installation path and one for the Node.js path. +# Verify Installation: Open Command Prompt or PowerShell and enter the following command to verify that nvm has been installed successfully: +nvm --version +``` + +### Common `nvm` commands. + +``` +nvm install node # Install the latest version of Node.js. +nvm install # Install the specified version of Node.js, nvm install 20.11.0 or nvm install v20.11.0. +nvm use # Switch to the specified version of Node.js. +nvm ls # List the installed versions of Node.js. +nvm current # Display the current version of Node.js being used. +``` + +Tips: After switching Node.js versions with `nvm use `, please delete the `node_modules` folder first, and then run `npm install` to install dependencies. + + +### Install Node.js : + +Note: Right now, with `npm install`, these Node.js versions show: + - v20.11.0 is okay. + - v14.15.4 and v22.7.0 have errors. + +``` +# Install a specific version of Node.js. +# Note: Errors were encountered with other versions; install this recommended version first. +nvm install v20.11.0 + +# Navigate to the root directory of your project, then use this command to switch the Node.js version: +nvm use v20.11.0 + +# For users in China, please set the Chinese mirror source to solve the problem of slow downloads within the country. +npm config set registry https://registry.npmmirror.com +``` + +### Installing Dependencies + +#### In the root Folder + +> Note:The package.json file uses the npx only-allow npm script, which will prevent the use of any package manager other than npm + +``` +# Install dependencies. +cd auto-dev-vscode && npm install +``` + + +#### In the gui-sidebar Folder + +``` +# Install dependencies +cd gui-sidebar && cnpm install && buildy +``` -1. `git clone https://github.com/unit-mesh/auto-dev-for-code` -2. init submodules -3. build gui-sidebar: `cd gui-sidebar && npm install && build` -4. open in VSCode -5. press `F5` to start the extension diff --git a/docs/features/chat.md b/docs/features/chat.md index 9a81fbab..4df78b8a 100644 --- a/docs/features/chat.md +++ b/docs/features/chat.md @@ -32,8 +32,8 @@ For example, use openai: "autodev.chat.models": [ { "provider": "openai", // chat provider - "title": "GPT-3.5 turbo", // Text displayed in selector - "model": "gpt-3.5-turbo" // Used chat model + "title": "GPT-4O Mini", // Text displayed in selector + "model": "gpt-4o-mini" // Used chat model } ] } diff --git a/docs/quick-start.md b/docs/quick-start.md index add672b5..659083bb 100644 --- a/docs/quick-start.md +++ b/docs/quick-start.md @@ -30,6 +30,26 @@ Config OpenAI example: } ``` +### DeekSeek Example + +```json + "autodev.openaiCompatibleConfig": { + "apiType": "openai", + "model": "deepseek-chat", + "apiBase": "https://api.deepseek.com/v1", + "apiKey": "sk-ii" + }, + "autodev.openai.baseURL": "https://api.deepseek.com/v1", + "autodev.openai.apiKey": "sk-ii", + "autodev.chat.models": [ + { + "title": "DeepSeek Chat", + "provider": "openai", + "model": "deepseek-chat" + } + ], +``` + ## Next - [Enable Code-completion](./features/code-completion.md) diff --git a/extension-manual-tests/ComprehensiveCSharpExample.cs b/extension-manual-tests/ComprehensiveCSharpExample.cs new file mode 100644 index 00000000..a328de4c --- /dev/null +++ b/extension-manual-tests/ComprehensiveCSharpExample.cs @@ -0,0 +1,198 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace ComprehensiveCSharpExample +{ + // 1. 枚举 + enum Direction { North, South, East, West } + + // 2. 接口 + interface IDrawable + { + void Draw(); + } + + // 3. 结构体 + struct Point + { + public int X { get; } + public int Y { get; } + + public Point(int x, int y) + { + X = x; + Y = y; + } + } + + // 4. 类与继承、多态 + class Shape + { + public virtual double Area() => 0.0; + } + + class Circle : Shape + { + public double Radius { get; } + + public Circle(double radius) + { + Radius = radius; + } + + public override double Area() => Math.PI * Radius * Radius; + } + + class Square : Shape, IDrawable + { + public double Side { get; } + + public Square(double side) + { + Side = side; + } + + public override double Area() => Side * Side; + + public void Draw() + { + Console.WriteLine($"Drawing a square with side {Side}"); + } + } + + // 5. 泛型类 + class Box + { + public T Value { get; } + + public Box(T value) + { + Value = value; + } + } + + class Program + { + // 6. 委托与事件 + public delegate void Notify(string message); + public static event Notify OnNotify; + + static void Main(string[] args) + { + // 7. 变量与数据类型 + int number = 42; + double pi = 3.14159; + bool isTrue = true; + char letter = 'C'; + string text = "Hello, C#!"; + + Console.WriteLine($"Number: {number}, Pi: {pi}, IsTrue: {isTrue}, Letter: {letter}, Text: {text}"); + + // 8. 控制流 + if (number > 40) + { + Console.WriteLine("Number is greater than 40"); + } + else + { + Console.WriteLine("Number is 40 or less"); + } + + for (int i = 0; i < 5; i++) + { + Console.WriteLine($"i = {i}"); + } + + int counter = 0; + while (counter < 5) + { + Console.WriteLine($"Counter = {counter}"); + counter++; + } + + // 9. 函数调用 + int sum = Add(10, 20); + Console.WriteLine($"Sum: {sum}"); + + // 10. 枚举使用 + Navigate(Direction.East); + + // 11. 类与对象 + Circle circle = new Circle(5.0); + Square square = new Square(4.0); + Console.WriteLine($"Circle Area: {circle.Area()}"); + Console.WriteLine($"Square Area: {square.Area()}"); + square.Draw(); + + // 12. 结构体 + Point point = new Point(10, 20); + Console.WriteLine($"Point coordinates: X = {point.X}, Y = {point.Y}"); + + // 13. 泛型 + Box intBox = new Box(123); + Box stringBox = new Box("Generics in C#"); + Console.WriteLine($"Int Box: {intBox.Value}"); + Console.WriteLine($"String Box: {stringBox.Value}"); + + // 14. 集合与 LINQ + List numbers = new List { 1, 2, 3, 4, 5 }; + var doubled = numbers.Select(n => n * 2); + var filtered = numbers.Where(n => n > 3); + + Console.WriteLine("Doubled numbers: " + string.Join(", ", doubled)); + Console.WriteLine("Filtered numbers: " + string.Join(", ", filtered)); + + // 15. 异常处理 + try + { + int result = Divide(10, 0); + Console.WriteLine($"Result: {result}"); + } + catch (DivideByZeroException e) + { + Console.WriteLine("Error: " + e.Message); + } + + // 16. 事件触发 + OnNotify += MessageHandler; + OnNotify?.Invoke("This is a notification message."); + } + + // 17. 函数 + static int Add(int a, int b) => a + b; + + // 18. 异常处理函数 + static int Divide(int a, int b) + { + if (b == 0) throw new DivideByZeroException("Cannot divide by zero"); + return a / b; + } + + // 19. 枚举与 switch 语句 + static void Navigate(Direction direction) + { + switch (direction) + { + case Direction.North: + Console.WriteLine("Going North"); + break; + case Direction.South: + Console.WriteLine("Going South"); + break; + case Direction.East: + Console.WriteLine("Going East"); + break; + case Direction.West: + Console.WriteLine("Going West"); + break; + } + } + + // 20. 事件处理函数 + static void MessageHandler(string message) + { + Console.WriteLine($"Received message: {message}"); + } + } +} diff --git a/extension-manual-tests/Sample.kt b/extension-manual-tests/Sample.kt new file mode 100644 index 00000000..9265904a --- /dev/null +++ b/extension-manual-tests/Sample.kt @@ -0,0 +1,131 @@ +// 1. 变量与常量 +val pi: Double = 3.14159 // 常量 (不可变) +var x: Int = 5 // 变量 (可变) +x = 6 + +// 2. 基本数据类型与字符串模板 +val integer: Int = 42 +val float: Float = 3.14f +val boolean: Boolean = true +val character: Char = 'K' +val string: String = "Hello, Kotlin!" +println("Integer: $integer, Float: $float, Boolean: $boolean, Character: $character, String: $string") + +// 3. 函数与默认参数 +fun add(a: Int, b: Int = 10): Int { + return a + b +} + +val sum = add(5) +println("Sum: $sum") + +// 4. 控制流 +if (sum > 10) { + println("Sum is greater than 10") +} else { + println("Sum is 10 or less") +} + +for (i in 0..4) { + println("i = $i") +} + +var counter = 0 +while (counter < 5) { + println("Counter = $counter") + counter++ +} + +// 5. 类与对象 +class Rectangle(val width: Int, val height: Int) { + fun area(): Int { + return width * height + } +} + +val rect = Rectangle(30, 50) +println("The area of the rectangle is ${rect.area()} square pixels.") + +// 6. 继承与抽象类 +abstract class Shape { + abstract fun area(): Double +} + +class Circle(val radius: Double) : Shape() { + override fun area(): Double { + return pi * radius * radius + } +} + +val circle = Circle(5.0) +println("The area of the circle is ${circle.area()} square units.") + +// 7. 接口 +interface Drawable { + fun draw() +} + +class Square(val side: Int) : Drawable { + override fun draw() { + println("Drawing a square with side $side") + } +} + +val square = Square(10) +square.draw() + +// 8. 数据类与解构 +data class Point(val x: Int, val y: Int) + +val point = Point(10, 20) +val (xCoord, yCoord) = point +println("Point coordinates: x = $xCoord, y = $yCoord") + +// 9. 枚举 +enum class Direction { + NORTH, SOUTH, EAST, WEST +} + +fun navigate(direction: Direction) { + when (direction) { + Direction.NORTH -> println("Going North") + Direction.SOUTH -> println("Going South") + Direction.EAST -> println("Going East") + Direction.WEST -> println("Going West") + } +} + +navigate(Direction.EAST) + +// 10. 集合与集合操作 +val numbers = listOf(1, 2, 3, 4, 5) +val doubled = numbers.map { it * 2 } +val filtered = numbers.filter { it > 3 } +println("Numbers: $numbers") +println("Doubled: $doubled") +println("Filtered: $filtered") + +// 11. 异常处理 +fun divide(a: Int, b: Int): Int { + return try { + a / b + } catch (e: ArithmeticException) { + println("Error: Division by zero") + 0 + } +} + +val result = divide(10, 0) +println("Result: $result") + +// 12. 泛型 +class Box(val item: T) { + fun getItem(): T { + return item + } +} + +val intBox = Box(123) +val stringBox = Box("Hello, Generics!") +println("Int Box: ${intBox.getItem()}") +println("String Box: ${stringBox.getItem()}") diff --git a/extension-manual-tests/Sample.rs b/extension-manual-tests/Sample.rs new file mode 100644 index 00000000..cfbad578 --- /dev/null +++ b/extension-manual-tests/Sample.rs @@ -0,0 +1,137 @@ +// 1. 常量与变量 +const PI: f64 = 3.14159; +let mut x = 5; // 可变变量 +x = 6; + +// 2. 基本数据类型 +let integer: i32 = 42; +let float: f64 = 3.14; +let boolean: bool = true; +let character: char = 'R'; +let tuple: (i32, f64, char) = (42, 3.14, 'R'); +let array: [i32; 3] = [1, 2, 3]; + +// 3. 函数与返回值 +fn add(a: i32, b: i32) -> i32 { + a + b +} + +let sum = add(5, 10); + +// 4. 控制流 +if sum > 10 { + println!("Sum is greater than 10"); +} else { + println!("Sum is 10 or less"); +} + +for i in 0..5 { + println!("i = {}", i); +} + +let mut counter = 0; +while counter < 5 { + println!("counter = {}", counter); + counter += 1; +} + +// 5. 所有权与借用 +fn take_ownership(s: String) { + println!("Took ownership of: {}", s); +} + +fn borrow_string(s: &String) { + println!("Borrowed string: {}", s); +} + +let s = String::from("Hello, Rust!"); +take_ownership(s); // s 的所有权被转移,s 不再有效 +// borrow_string(&s); // 这一行会报错,因为 s 的所有权已被转移 + +let s2 = String::from("Hello again!"); +borrow_string(&s2); // s2 的所有权未被转移 + +// 6. 结构体 +struct Rectangle { + width: u32, + height: u32, +} + +impl Rectangle { + fn area(&self) -> u32 { + self.width * self.height + } +} + +let rect = Rectangle { width: 30, height: 50 }; +println!("The area of the rectangle is {} square pixels.", rect.area()); + +// 7. 枚举与模式匹配 +enum Message { + Quit, + Move { x: i32, y: i32 }, + Write(String), + ChangeColor(i32, i32, i32), +} + +fn process_message(msg: Message) { + match msg { + Message::Quit => println!("Quit"), + Message::Move { x, y } => println!("Move to x: {}, y: {}", x, y), + Message::Write(text) => println!("Text message: {}", text), + Message::ChangeColor(r, g, b) => println!("Change color to red: {}, green: {}, blue: {}", r, g, b), + } +} + +let msg = Message::Write(String::from("Hello, enums!")); +process_message(msg); + +// 8. 错误处理 +fn divide(a: f64, b: f64) -> Result { + if b == 0.0 { + Err(String::from("Division by zero")) + } else { + Ok(a / b) + } +} + +match divide(10.0, 2.0) { + Ok(result) => println!("Result: {}", result), + Err(e) => println!("Error: {}", e), +} + +// 9. 泛型与 trait +fn largest(list: &[T]) -> &T { + let mut largest = &list[0]; + for item in list.iter() { + if item > largest { + largest = item; + } + } + largest +} + +let numbers = vec![34, 50, 25, 100, 65]; +println!("The largest number is {}", largest(&numbers)); + +trait Summary { + fn summarize(&self) -> String; +} + +struct Article { + title: String, + content: String, +} + +impl Summary for Article { + fn summarize(&self) -> String { + format!("{}: {}", self.title, &self.content[..20]) + } +} + +let article = Article { + title: String::from("Rust Programming"), + content: String::from("Rust is a systems programming language focused on safety, speed, and concurrency."), +}; + +println!("New article available! {}", article.summarize()); diff --git a/gui-sidebar/src/components/mainInput/ContinueButton.tsx b/gui-sidebar/src/components/mainInput/ContinueButton.tsx index b550aec4..cebbcfdb 100644 --- a/gui-sidebar/src/components/mainInput/ContinueButton.tsx +++ b/gui-sidebar/src/components/mainInput/ContinueButton.tsx @@ -83,7 +83,7 @@ function ContinueButton(props: { ) : ( )} - CONTINUE + AutoDev )} diff --git a/gui-sidebar/src/pages/error.tsx b/gui-sidebar/src/pages/error.tsx index 88504398..52980c84 100644 --- a/gui-sidebar/src/pages/error.tsx +++ b/gui-sidebar/src/pages/error.tsx @@ -16,12 +16,12 @@ export default function ErrorPage() { className="text-center" style={{ backgroundColor: vscBackground }} > -

Error in Continue React App

+

Error in AutoDev Debug page

{error.statusText || error.message}


-

Click below to Continue

+

Click below to continue


0 ) +${context.chatContext} +#end +#if($context.forbiddenRules.length > 0) +${context.forbiddenRules} +#end +- Start your documentation with ${context.startSymbol} here, and ends with `${context.endSymbol}`. +Here is User's code: +```${context.language} +${context.code} +``` +#if($context.originalComments.length > 0) +Here is code Origin comment: ${context.originalComments} +Please according to the code to update documentation. +#end +Please write documentation for user's code inside the Markdown code block. diff --git a/prompts/genius/zh-cn/code/auto-doc.vm b/prompts/genius/zh-cn/code/auto-doc.vm index 81d8fde6..c5226fad 100644 --- a/prompts/genius/zh-cn/code/auto-doc.vm +++ b/prompts/genius/zh-cn/code/auto-doc.vm @@ -10,5 +10,4 @@ Here is User's code: ```${context.language} ${context.code} ``` - Please write documentation for this code inside the Markdown code block. diff --git a/prompts/genius/zh-cn/code/auto-method.vm b/prompts/genius/zh-cn/code/auto-method.vm new file mode 100644 index 00000000..81d8fde6 --- /dev/null +++ b/prompts/genius/zh-cn/code/auto-method.vm @@ -0,0 +1,14 @@ +Write documentation for user's given ${context.language} code. +#if($context.chatContext.length > 0 ) +${context.chatContext} +#end +#if($context.forbiddenRules.length > 0) +${context.forbiddenRules} +#end +- Start your documentation with ${context.startSymbol} here, and ends with `${context.endSymbol}`. +Here is User's code: +```${context.language} +${context.code} +``` + +Please write documentation for this code inside the Markdown code block. diff --git a/src/AutoDevExtension.ts b/src/AutoDevExtension.ts index b67dc461..baaa6bdc 100644 --- a/src/AutoDevExtension.ts +++ b/src/AutoDevExtension.ts @@ -10,6 +10,8 @@ import { logger } from 'base/common/log/log'; import { AutoDocActionExecutor } from './action/autodoc/AutoDocActionExecutor'; import { AutoTestActionExecutor } from './action/autotest/AutoTestActionExecutor'; +import { AutoMethodActionExecutor } from './action/autoMethod/AutoMethodActionExecutor'; + import { registerAutoDevProviders, registerCodeLensProvider, @@ -19,7 +21,7 @@ import { } from './action/ProviderRegister'; import { SystemActionService } from './action/setting/SystemActionService'; import { Catalyser } from './agent/catalyser/Catalyser'; -import { LanguageModelsService } from './base/common/language-models/languageModelsService'; +import { LanguageModelsService } from 'base/common/language-models/languageModelsService'; import { ChunkerManager } from './code-search/chunk/ChunkerManager'; import { CodebaseIndexer } from './code-search/indexing/CodebaseIndexer'; import { LanceDbIndex } from './code-search/indexing/LanceDbIndex'; @@ -42,6 +44,7 @@ import { TemplateRender } from './prompt-manage/template/TemplateRender'; import { IProjectService } from './ProviderTypes'; import { ToolchainContextManager } from './toolchain-context/ToolchainContextManager'; + @injectable() export class AutoDevExtension { // Vscode @@ -223,6 +226,9 @@ export class AutoDevExtension { executeAutoDocAction(document: TextDocument, nameElement: NamedElement, edit?: WorkspaceEdit) { return new AutoDocActionExecutor(this, document, nameElement, edit).execute(); } + executeAutoMethodAction(document: TextDocument, nameElement: NamedElement, edit?: WorkspaceEdit) { + return new AutoMethodActionExecutor(this, document, nameElement, edit).execute(); + } executeAutoTestAction(document: TextDocument, nameElement: NamedElement, edit?: WorkspaceEdit) { return new AutoTestActionExecutor(this, document, nameElement, edit).execute(); diff --git a/src/ProviderContainer.config.ts b/src/ProviderContainer.config.ts index b9ba3f91..9044754a 100644 --- a/src/ProviderContainer.config.ts +++ b/src/ProviderContainer.config.ts @@ -34,9 +34,12 @@ import { JavaScriptContextProvider } from './toolchain-context/framework/javascr import { SpringContextProvider } from './toolchain-context/framework/jvm/SpringContextProvider'; import { ToolchainContextProvider } from './toolchain-context/ToolchainContextProvider'; import { JavaVersionProvider } from './toolchain-context/version/JavaVersionProvider'; +import { AutoMethodActionCreator } from './action/autoMethod/AutoMethodActionCreator'; +import { KotlinStructurerProvider } from "./code-context/kotlin/KotlinStructurerProvider"; // Action Register providerContainer.bind(IActionCreator).to(AutoDocActionCreator); +providerContainer.bind(IActionCreator).to(AutoMethodActionCreator); providerContainer.bind(IActionCreator).to(AutoTestActionCreator); providerContainer.bind(IActionCreator).to(GenApiDataActionCreator); @@ -59,6 +62,7 @@ providerContainer.bind(IRelevantCodeProvider).to(JavaRelevantCodeProvider); providerContainer.bind(ITestGenProvider).to(JavaTestGenProvider); providerContainer.bind(IBuildToolProvider).to(GradleBuildToolProvider); providerContainer.bind(IStructurerProvider).to(JavaStructurerProvider); +providerContainer.bind(IStructurerProvider).to(KotlinStructurerProvider); // TypeScript providerContainer.bind(IToolchainContextProvider).to(JavaScriptContextProvider); diff --git a/src/ProviderLanguageProfile.config.ts b/src/ProviderLanguageProfile.config.ts index 1a8c7dad..d33ce8c8 100644 --- a/src/ProviderLanguageProfile.config.ts +++ b/src/ProviderLanguageProfile.config.ts @@ -3,8 +3,11 @@ import { Container } from 'inversify'; import { GolangProfile } from './code-context/go/GolangProfile'; import { JavaProfile } from './code-context/java/JavaProfile'; import { PythonProfile } from './code-context/python/PythonProfile'; +import { RustProfile } from './code-context/rust/RustProfile'; import { TypeScriptProfile } from './code-context/typescript/TypeScriptProfile'; import { ILanguageProfile } from './ProviderTypes'; +import { CsharpProfile } from './code-context/csharp/CsharpProfile'; +import { KotlinProfile } from './code-context/kotlin/KotlinProfile'; const languageContainer = new Container(); @@ -12,5 +15,8 @@ languageContainer.bind(ILanguageProfile).to(JavaProfile); languageContainer.bind(ILanguageProfile).to(TypeScriptProfile); languageContainer.bind(ILanguageProfile).to(GolangProfile); languageContainer.bind(ILanguageProfile).to(PythonProfile); +languageContainer.bind(ILanguageProfile).to(CsharpProfile); +languageContainer.bind(ILanguageProfile).to(RustProfile); +languageContainer.bind(ILanguageProfile).to(KotlinProfile); export { languageContainer }; diff --git a/src/action/autoMethod/AutoMethodActionCreator.ts b/src/action/autoMethod/AutoMethodActionCreator.ts new file mode 100644 index 00000000..f69a4e97 --- /dev/null +++ b/src/action/autoMethod/AutoMethodActionCreator.ts @@ -0,0 +1,40 @@ +import { injectable } from 'inversify'; +import vscode from 'vscode'; + +import { NamedElement } from '../../editor/ast/NamedElement'; +import { ActionCreatorContext } from '../_base/ActionCreatorContext'; +import { CodeActionCreator } from '../_base/CodeActionCreator'; +import { CMD_GEN_CODE_METHOD_COMPLETIONS } from 'base/common/configuration/configuration'; + +@injectable() +export class AutoMethodActionCreator extends CodeActionCreator { + static readonly providedCodeActionKinds = [vscode.CodeActionKind.RefactorRewrite]; + + isApplicable(creatorContext: ActionCreatorContext): boolean { + return true; + } + + buildClassAction(context: ActionCreatorContext, elementBlock: NamedElement) { + const title = `AutoDoc for class \`${elementBlock.identifierRange.text}\` (AutoDev)`; + return this.createMethodAction(title, context.document, elementBlock); + } + + buildMethodAction(context: ActionCreatorContext, elementBlock: NamedElement): vscode.CodeAction { + const title = `AutoDoc for method \`${elementBlock.identifierRange.text}\` (AutoDev)`; + return this.createMethodAction(title, context.document, elementBlock); + } + + private createMethodAction(title: string, document: vscode.TextDocument, block: NamedElement): vscode.CodeAction { + const codeAction = new vscode.CodeAction(title, AutoMethodActionCreator.providedCodeActionKinds[0]); + + codeAction.isPreferred = false; + codeAction.edit = new vscode.WorkspaceEdit(); + codeAction.command = { + command: CMD_GEN_CODE_METHOD_COMPLETIONS, + title: title, + arguments: [document, block, codeAction.edit], + }; + + return codeAction; + } +} diff --git a/src/action/autoMethod/AutoMethodActionExecutor.ts b/src/action/autoMethod/AutoMethodActionExecutor.ts new file mode 100644 index 00000000..a8eaff29 --- /dev/null +++ b/src/action/autoMethod/AutoMethodActionExecutor.ts @@ -0,0 +1,139 @@ +import { AutoDevExtension } from 'src/AutoDevExtension'; +import { Position, TextDocument, WorkspaceEdit } from 'vscode'; + +import { ChatMessageRole, IChatMessage } from 'base/common/language-models/languageModels'; +import { LanguageModelsService } from 'base/common/language-models/languageModelsService'; +import { LANGUAGE_BLOCK_COMMENT_MAP } from 'base/common/languages/docstring'; +import { log } from 'base/common/log/log'; +import { MarkdownTextProcessor } from 'base/common/markdown/MarkdownTextProcessor'; +import { StreamingMarkdownCodeBlock } from 'base/common/markdown/StreamingMarkdownCodeBlock'; + +import { type NamedElement } from '../../editor/ast/NamedElement'; +import { insertCodeByRange, selectCodeInRange } from '../../editor/ast/PositionUtil'; +import { AutoDevStatus, AutoDevStatusManager } from '../../editor/editor-api/AutoDevStatusManager'; +import { ActionType } from '../../prompt-manage/ActionType'; +import { PromptManager } from '../../prompt-manage/PromptManager'; +import { CreateToolchainContext } from '../../toolchain-context/ToolchainContextProvider'; +import { ActionExecutor } from '../_base/ActionExecutor'; +import { AutoMethodTemplateContext } from './AutoMethodTemplateContext'; +import fs from 'fs' +import vscode from 'vscode'; +export class AutoMethodActionExecutor implements ActionExecutor { + type: ActionType = ActionType.AutoDoc; + + private lm: LanguageModelsService; + private promptManager: PromptManager; + private statusBarManager: AutoDevStatusManager; + + private document: TextDocument; + private range: NamedElement; + private edit?: WorkspaceEdit; + private language: string; + + constructor(autodev: AutoDevExtension, document: TextDocument, range: NamedElement, edit?: WorkspaceEdit) { + this.lm = autodev.lm; + this.promptManager = autodev.promptManager; + this.statusBarManager = autodev.statusBarManager; + + this.document = document; + this.range = range; + this.edit = edit; + this.language = document.languageId; + } + + async execute() { + const document = this.document; + const range = this.range; + const language = document.languageId; + + const startSymbol = LANGUAGE_BLOCK_COMMENT_MAP[language]!.start; + const endSymbol = LANGUAGE_BLOCK_COMMENT_MAP[language]!.end; + + const config = vscode.workspace.getConfiguration('autodev.Workspace'); + const customFrameworkCodeFilesPath = config.get('customFrameworkCodeFiles', []); + let customFrameworkCodeFileContext: string = ""; + if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 0) { + const workspaceFolder = vscode.workspace.workspaceFolders[0]; + const workspacePath = workspaceFolder.uri.fsPath; + if (customFrameworkCodeFilesPath.length > 0) { + for (let i = 0; i < customFrameworkCodeFilesPath.length; i++) { + let codeFileNormalPath = workspacePath + '\\' + customFrameworkCodeFilesPath[i]; + // add support for linux + customFrameworkCodeFileContext += fs.readFileSync(codeFileNormalPath).toString(); + customFrameworkCodeFileContext += '\n' + } + } + } + + const templateContext: AutoMethodTemplateContext = { + language: language, + startSymbol: startSymbol, + endSymbol: endSymbol, + code: document.getText(range.blockRange), + forbiddenRules: [], + // 原有代码块 + originalMethodCodes: [], + customFrameworkCodeFileContext + }; + + if (range.commentRange) { + templateContext.originalMethodCodes.push(document.getText(range.commentRange)); + } + + this.statusBarManager.setStatus(AutoDevStatus.InProgress); + + selectCodeInRange(range.blockRange.start, range.blockRange.end); + if (range.commentRange) { + selectCodeInRange(range.commentRange.start, range.commentRange.end); + } + + const creationContext: CreateToolchainContext = { + action: 'AutoMethodAction', + filename: document.fileName, + language: language, + + content: document.getText(), + element: range, + }; + + const contextItems = await this.promptManager.collectToolchain(creationContext); + if (contextItems.length > 0) { + templateContext.chatContext = contextItems.map(item => item.text).join('\n - '); + } + + let content = await this.promptManager.generateInstruction(ActionType.AutoMethod, templateContext); + log(`request: ${content}`); + + let msg: IChatMessage = { + role: ChatMessageRole.User, + content: content, + }; + + try { + const doc = await this.lm.chat([msg], {}); + + this.statusBarManager.setStatus(AutoDevStatus.Done); + const finalText = StreamingMarkdownCodeBlock.parse(doc).text; + + log(`FencedCodeBlock parsed output: ${finalText}`); + + let codestring = MarkdownTextProcessor.buildDocFromSuggestion(doc, startSymbol, endSymbol); + + let startLine = range.blockRange.start.line; + let startChar = range.blockRange.start.character; + + if (startLine === 0) { + startLine = 1; + } + + // todo: add format by indent. + + const textRange: Position = new Position(startLine - 1, startChar); + insertCodeByRange(textRange, codestring); + } catch (e) { + console.error(e); + this.statusBarManager.setStatus(AutoDevStatus.Error); + return; + } + } +} diff --git a/src/action/autoMethod/AutoMethodTemplateContext.ts b/src/action/autoMethod/AutoMethodTemplateContext.ts new file mode 100644 index 00000000..0a4801d5 --- /dev/null +++ b/src/action/autoMethod/AutoMethodTemplateContext.ts @@ -0,0 +1,10 @@ +import { TemplateContext } from '../../prompt-manage/template/TemplateContext'; + +export interface AutoMethodTemplateContext extends TemplateContext { + startSymbol: string; + endSymbol: string; + code: string; + forbiddenRules: string[]; + originalMethodCodes: string[]; + customFrameworkCodeFileContext?:string; +} diff --git a/src/action/providers/AutoDevCodeInlineCompletionProvider.ts b/src/action/providers/AutoDevCodeInlineCompletionProvider.ts index 68d869ff..b9ea4d56 100644 --- a/src/action/providers/AutoDevCodeInlineCompletionProvider.ts +++ b/src/action/providers/AutoDevCodeInlineCompletionProvider.ts @@ -116,7 +116,8 @@ export class AutoDevCodeInlineCompletionProvider implements vscode.InlineComplet } if (result) { - return [new vscode.InlineCompletionItem(result.trimStart())]; + // return [new vscode.InlineCompletionItem(result.trimStart())]; + return [new vscode.InlineCompletionItem(result)]; } } catch (error) { if (!token.isCancellationRequested) { diff --git a/src/action/providers/AutoDevCodeLensProvider.ts b/src/action/providers/AutoDevCodeLensProvider.ts index 949a18fe..5b7d9b61 100644 --- a/src/action/providers/AutoDevCodeLensProvider.ts +++ b/src/action/providers/AutoDevCodeLensProvider.ts @@ -27,6 +27,7 @@ import { CMD_CODELENS_QUICK_CHAT, CMD_CODELENS_SHOW_CUSTOM_ACTION, CMD_SHOW_CODELENS_DETAIL_QUICKPICK, + CMD_CODELENS_SHOW_CODE_METHOD_COMPLETIONS, } from 'base/common/configuration/configuration'; import { ConfigurationService } from 'base/common/configuration/configurationService'; import { isFileTooLarge } from 'base/common/files/files'; @@ -35,8 +36,9 @@ import { ILanguageServiceProvider } from 'base/common/languages/languageService' import { logger } from 'base/common/log/log'; import { type AutoDevExtension } from '../../AutoDevExtension'; +import { CodeElementType } from 'src/editor/codemodel/CodeElementType'; -type CodeLensItemType = 'quickChat' | 'explainCode' | 'optimizeCode' | 'autoComment' | 'autoTest' | 'customAction'; +type CodeLensItemType = 'quickChat' | 'explainCode' | 'optimizeCode' | 'autoComment' | 'autoTest' | 'customAction'|'AutoMethod'; export class AutoDevCodeLensProvider implements CodeLensProvider { private config: ConfigurationService; @@ -123,6 +125,9 @@ export class AutoDevCodeLensProvider implements CodeLensProvider { commands.registerCommand(CMD_CODELENS_SHOW_CUSTOM_ACTION, (document: TextDocument, nameElement: NamedElement) => { autodev.executeCustomAction(document, nameElement); }), + commands.registerCommand(CMD_CODELENS_SHOW_CODE_METHOD_COMPLETIONS, (document: TextDocument, nameElement: NamedElement) => { + autodev.executeAutoMethodAction(document, nameElement); + }), ]; } @@ -153,6 +158,7 @@ export class AutoDevCodeLensProvider implements CodeLensProvider { } const elements = await this.parseToNamedElements(document); +// elements为空导致codelens组没有数据,无法生成codelens if (token.isCancellationRequested || elements.length === 0) { return []; @@ -250,7 +256,6 @@ export class AutoDevCodeLensProvider implements CodeLensProvider { } continue; } - if (type === 'customAction') { if (hasCustomPromps) { codelenses.push( @@ -263,6 +268,19 @@ export class AutoDevCodeLensProvider implements CodeLensProvider { } continue; } + if (type === 'AutoMethod') { + if (element.codeElementType==CodeElementType.Method) { + codelenses.push( + new CodeLens(element.identifierRange, { + title: l10n.t('AutoMethod'), + command: CMD_CODELENS_SHOW_CODE_METHOD_COMPLETIONS, + arguments: [document, element], + }), + ); + } + continue; + } + } result.push(codelenses); diff --git a/src/base/common/configuration/configuration.ts b/src/base/common/configuration/configuration.ts index 8c198d9e..18c6947f 100644 --- a/src/base/common/configuration/configuration.ts +++ b/src/base/common/configuration/configuration.ts @@ -18,6 +18,7 @@ export const CMD_FIX_THIS = 'autodev.fixThis'; export const CMD_EXPLAIN_CODE = 'autodev.explainCode'; export const CMD_OPTIMIZE_CODE = 'autodev.optimizeCode'; export const CMD_GEN_DOCSTRING = 'autodev.autoComment'; +export const CMD_GEN_CODE_METHOD_COMPLETIONS= 'autodev.methodCompletions'; export const CMD_CREATE_UNIT_TEST = 'autodev.autoTest'; // Codelens Commands @@ -27,6 +28,7 @@ export const CMD_CODELENS_OPTIMIZE_CODE = 'autodev.codelens.optimizeCode'; export const CMD_CODELENS_GEN_DOCSTRING = 'autodev.codelens.autoComment'; export const CMD_CODELENS_CREATE_UNIT_TEST = 'autodev.codelens.autoTest'; export const CMD_CODELENS_SHOW_CUSTOM_ACTION = 'autodev.codelens.customAction'; +export const CMD_CODELENS_SHOW_CODE_METHOD_COMPLETIONS= 'autodev.codelens.methodCompletions'; // Chat Commands export const CMD_SHOW_CHAT_PANEL = 'autodev.showChatPanel'; diff --git a/src/base/common/instantiation/instantiation.ts b/src/base/common/instantiation/instantiation.ts index cb72e64c..17ccef2f 100644 --- a/src/base/common/instantiation/instantiation.ts +++ b/src/base/common/instantiation/instantiation.ts @@ -1,4 +1,4 @@ -import { type interfaces, LazyServiceIdentifer } from 'inversify'; +import { type interfaces, LazyServiceIdentifier } from 'inversify'; export type Newable = interfaces.Newable; @@ -6,9 +6,9 @@ export type ServiceIdentifier = interfaces.ServiceIdentifier; export type FactoryFunction = () => interfaces.ServiceIdentifier; -export type ServiceDescriptor = T | LazyServiceIdentifer | FactoryFunction; +export type ServiceDescriptor = T | LazyServiceIdentifier | FactoryFunction; -export { LazyServiceIdentifer }; +export { LazyServiceIdentifier }; export interface ServicesAccessor { get(id: ServiceIdentifier): T; @@ -16,7 +16,7 @@ export interface ServicesAccessor { const _registry: [ServiceIdentifier, ServiceDescriptor][] = []; -export function registerSingleton(id: ServiceIdentifier, useValue: T | LazyServiceIdentifer): void; +export function registerSingleton(id: ServiceIdentifier, useValue: T | LazyServiceIdentifier): void; export function registerSingleton(id: ServiceIdentifier, useFactory: FactoryFunction): void; export function registerSingleton( id: ServiceIdentifier, @@ -25,16 +25,16 @@ export function registerSingleton( ): void; export function registerSingleton( id: ServiceIdentifier, - ctorOrDescriptor: T | LazyServiceIdentifer | Newable | FactoryFunction, + ctorOrDescriptor: T | LazyServiceIdentifier | Newable | FactoryFunction, supportsDelayedInstantiation?: boolean, ): void { - if (ctorOrDescriptor instanceof LazyServiceIdentifer) { + if (ctorOrDescriptor instanceof LazyServiceIdentifier) { _registry.push([id, ctorOrDescriptor]); return; } if (typeof ctorOrDescriptor === 'function' && supportsDelayedInstantiation) { - _registry.push([id, new LazyServiceIdentifer(() => ctorOrDescriptor as Newable)]); + _registry.push([id, new LazyServiceIdentifier(() => ctorOrDescriptor as Newable)]); return; } diff --git a/src/base/common/instantiation/instantiationService.ts b/src/base/common/instantiation/instantiationService.ts index 9c64c66b..a1f77427 100644 --- a/src/base/common/instantiation/instantiationService.ts +++ b/src/base/common/instantiation/instantiationService.ts @@ -1,8 +1,11 @@ -import { Container, type interfaces, LazyServiceIdentifer } from 'inversify'; +import { Container, type interfaces, LazyServiceIdentifier } from 'inversify'; + + import { isDisposable } from '../lifecycle'; import { getSingletonServiceDescriptors, type ServiceIdentifier } from './instantiation'; + export const providerContainer = new Container(); export class InstantiationService { @@ -13,7 +16,7 @@ export class InstantiationService { for (const [identifier, descriptor] of getSingletonServiceDescriptors()) { const binding = providerContainer.bind(identifier); - if (descriptor instanceof LazyServiceIdentifer) { + if (descriptor instanceof LazyServiceIdentifier) { binding.toDynamicValue(() => descriptor.unwrap()).inSingletonScope(); } else { binding.toConstantValue(descriptor); @@ -104,4 +107,4 @@ export class InstantiationService { throw new Error('InstantiationService has been disposed'); } } -} +} \ No newline at end of file diff --git a/src/base/common/language-models/languageModelsService.ts b/src/base/common/language-models/languageModelsService.ts index 2ec3cbc6..c3dbd443 100644 --- a/src/base/common/language-models/languageModelsService.ts +++ b/src/base/common/language-models/languageModelsService.ts @@ -5,7 +5,6 @@ import { EMBEDDING_BATCH_SIZE } from '../configuration/configuration'; import { ConfigurationService } from '../configuration/configurationService'; import { IChatMessage, IChatResponseFragment, ILanguageModelProvider } from './languageModels'; import { AnthropicLanguageModelProvider } from './providers/anthropicProvider'; -import { HuggingFaceTransformersLanguageModelProvider } from './providers/hgTransformersProvider'; import { OllamaLanguageModelProvider } from './providers/ollamaProvider'; import { OpenAILanguageModelProvider } from './providers/openaiProvider'; import { TongyiLanguageModelProvider } from './providers/TongyiProvider'; diff --git a/src/base/common/language-models/providers/hgTransformersProvider.ts b/src/base/common/language-models/providers/hgTransformersProvider.ts index 73ee942f..2db2a6c6 100644 --- a/src/base/common/language-models/providers/hgTransformersProvider.ts +++ b/src/base/common/language-models/providers/hgTransformersProvider.ts @@ -3,9 +3,7 @@ import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { chunkArray } from '@langchain/core/utils/chunk_array'; -import { - type FeatureExtractionPipeline, // @ts-expect-error vite esm => cjs -} from '@xenova/transformers'; +import { type FeatureExtractionPipeline } from '@xenova/transformers'; import { type CancellationToken } from 'vscode'; import { ConfigurationService } from '../../configuration/configurationService'; diff --git a/src/base/common/language-models/providers/openaiProvider.ts b/src/base/common/language-models/providers/openaiProvider.ts index 02795196..3f2e3397 100644 --- a/src/base/common/language-models/providers/openaiProvider.ts +++ b/src/base/common/language-models/providers/openaiProvider.ts @@ -48,10 +48,10 @@ export class OpenAILanguageModelProvider implements ILanguageModelProvider { for await (const chunk of completion) { const [choice] = chunk.choices || []; - part = choice.delta.content; + part = choice.delta.content || ''; // Note: Empty if finish_reason exists. - if (choice.finish_reason || part == null) { + if (choice.finish_reason) { break; } @@ -209,7 +209,7 @@ export class OpenAILanguageModelProvider implements ILanguageModelProvider { return model; } - return this.configService.get('openai.model', 'gpt-3.5-turbo'); + return this.configService.get('openai.model', 'gpt-4o-mini'); } private _resolveComletionModel(model?: string) { diff --git a/src/base/common/language-models/providers/zhipuaiProvider.ts b/src/base/common/language-models/providers/zhipuaiProvider.ts index b07901d9..f4f6b00a 100644 --- a/src/base/common/language-models/providers/zhipuaiProvider.ts +++ b/src/base/common/language-models/providers/zhipuaiProvider.ts @@ -60,10 +60,10 @@ export class ZhipuAILanguageModelProvider implements ILanguageModelProvider { for await (const chunk of completion) { const [choice] = chunk.choices || []; - part = choice.delta.content; + part = choice.delta.content || ''; // Note: Empty if finish_reason exists. - if (choice.finish_reason || part == null) { + if (choice.finish_reason) { break; } diff --git a/src/base/common/languages/languageService.ts b/src/base/common/languages/languageService.ts index 434e4bd5..4da43fad 100644 --- a/src/base/common/languages/languageService.ts +++ b/src/base/common/languages/languageService.ts @@ -45,7 +45,7 @@ export class LanguageServiceProvider implements ILanguageServiceProvider { } async parse(identifier: LanguageIdentifier, input: string) { - if (this.isSupportLanguage(identifier)) { + if (!this.isSupportLanguage(identifier)) { return; } diff --git a/src/base/common/languages/languages.ts b/src/base/common/languages/languages.ts index 67f6e0ab..01d22c6a 100644 --- a/src/base/common/languages/languages.ts +++ b/src/base/common/languages/languages.ts @@ -11,7 +11,7 @@ type SupportedLanguage = | 'python' | 'rust' | 'javascript' - // | 'javascriptreact' + | 'javascriptreact' | 'typescript' | 'typescriptreact'; @@ -27,7 +27,7 @@ export const SUPPORTED_LANGUAGES: LanguageIdentifier[] = [ 'python', 'rust', 'javascript', - // 'javascriptreact', + 'javascriptreact', 'typescript', 'typescriptreact', ]; @@ -55,7 +55,7 @@ const SupportLanguagesList: LanguageItem[] = [ { languageId: 'java', fileExts: ['.java'] }, { languageId: 'kotlin', - fileExts: ['.kt, .kts, .ktm'], + fileExts: ['.kt', '.kts', '.ktm'], }, { languageId: 'python', fileExts: ['.py'] }, { diff --git a/src/base/node/tree-sitter/treeSitterLoader.ts b/src/base/node/tree-sitter/treeSitterLoader.ts index 0e4d441a..fa754f74 100644 --- a/src/base/node/tree-sitter/treeSitterLoader.ts +++ b/src/base/node/tree-sitter/treeSitterLoader.ts @@ -95,6 +95,10 @@ export class TreeSitterLoader { return readFile(pathFactory(languageId)); } + if (languageId === 'csharp') { + languageId = 'c_sharp'; + } + return readFile(formatWasmFileName(pathTemplate || WASM_FILE_PATH_TEMPLATE, languageId)); } diff --git a/src/code-context/csharp/CsharpCodeCorrector.ts b/src/code-context/csharp/CsharpCodeCorrector.ts new file mode 100644 index 00000000..c9b30429 --- /dev/null +++ b/src/code-context/csharp/CsharpCodeCorrector.ts @@ -0,0 +1,87 @@ +import vscode from 'vscode'; + +import { ILanguageServiceProvider } from 'base/common/languages/languageService'; + +import { PositionUtil } from '../../editor/ast/PositionUtil'; +import { CodeCorrector, CorrectorContext } from '../_base/CodeCorrector'; +import { TreeSitterFile } from '../ast/TreeSitterFile'; +import { textToTreeSitterFile } from '../ast/TreeSitterWrapper'; + +export class CsharpCodeCorrector implements CodeCorrector { + constructor( + private context: CorrectorContext, + private lsp: ILanguageServiceProvider, + ) {} + + async correct(): Promise { + let tsfile = await textToTreeSitterFile(this.context.sourcecode, 'csharp', this.lsp); + + if (!tsfile) { + return Promise.reject(`Failed to find tree-sitter file for: ${this.context.document.uri}`); + } + + await this.fixIncorrectClassName(tsfile, this.context.document); + await this.fixIncorrectPackageName(tsfile, this.context.document); + } + + /** + * Fix LLM generated test file lost class name issue + */ + private async fixIncorrectClassName(tsfile: TreeSitterFile, document: vscode.TextDocument) { + let query = tsfile.languageProfile.classQuery.query(tsfile.tsLanguage); + const captures = query!!.captures(tsfile.tree.rootNode); + + const queryCapture = captures.find(c => c.name === 'name.definition.class'); + if (queryCapture) { + // compare targetClassName to queryCapture.text if they are different, replace queryCapture.text with targetClassName + let classNode = queryCapture.node; + if (this.context.targetClassName !== classNode.text) { + let edit = new vscode.WorkspaceEdit(); + + let classNameRange = new vscode.Range( + PositionUtil.fromNode(classNode.startPosition), + PositionUtil.fromNode(classNode.endPosition), + ); + + edit.replace(document.uri, classNameRange, this.context.targetClassName); + // applyEdit + await vscode.workspace.applyEdit(edit); + } + } + } + + /** + * Fix LLM generated test file lost package name issue + */ + private async fixIncorrectPackageName(tsfile: TreeSitterFile, document: vscode.TextDocument) { + let packageQuery = tsfile.languageProfile.packageQuery!!.query(tsfile.tsLanguage); + const packageCapture = packageQuery!!.captures(tsfile.tree.rootNode); + + // if package is not found, add package to the top of the file + if (packageCapture.length === 0) { + let content = 'package ' + this.context.packageName + ';\n\n'; + + let edit = new vscode.WorkspaceEdit(); + edit.insert(document.uri, new vscode.Position(0, 0), content); + + await vscode.workspace.applyEdit(edit); + } + + // fixme: not tested + // if package is found, compare package name to this.packageName if they are different, replace package name + if (packageCapture.length > 0) { + let packageNode = packageCapture[0].node; + if (this.context.packageName !== packageNode.text) { + let edit = new vscode.WorkspaceEdit(); + + let pkgNameRange = new vscode.Range( + PositionUtil.fromNode(packageNode.startPosition), + PositionUtil.fromNode(packageNode.endPosition), + ); + + edit.replace(document.uri, pkgNameRange, this.context.packageName); + await vscode.workspace.applyEdit(edit); + } + } + } +} diff --git a/src/code-context/csharp/CsharpProfile.ts b/src/code-context/csharp/CsharpProfile.ts new file mode 100644 index 00000000..a14713a5 --- /dev/null +++ b/src/code-context/csharp/CsharpProfile.ts @@ -0,0 +1,97 @@ +import { injectable } from 'inversify'; + +import { ILanguageServiceProvider } from 'base/common/languages/languageService'; + +import csharpscm from '../../code-search/schemas/indexes/c_sharp.scm?raw'; +import { LanguageProfile, MemoizedQuery } from '../_base/LanguageProfile'; + +@injectable() +export class CsharpProfile implements LanguageProfile { + languageIds = ['csharp']; + fileExtensions = ['csharp']; + grammar = (langService: ILanguageServiceProvider) => langService.getLanguage('csharp'); + isTestFile = (filePath: string) => filePath.endsWith('Test.cs') && filePath.includes('src/test'); + scopeQuery = new MemoizedQuery(csharpscm); + hoverableQuery = new MemoizedQuery(` + [(identifier) + (type_identifier)] @hoverable + `); + methodQuery = new MemoizedQuery(` + (method_declaration + name: (identifier) @name.definition.method) @definition.method + `); + /** + * (struct_declaration + name: (identifier) @definition.struct) + */ + classQuery = new MemoizedQuery(` + (class_declaration + name: (identifier) @name.definition.class) @definition.class + `); + blockCommentQuery = new MemoizedQuery(` + ((block_comment) @block_comment + (#match? @block_comment "^\\\\/\\\\*\\\\*")) @docComment`); + packageQuery = new MemoizedQuery(` + (package_declaration + (scoped_identifier) @package-name) + `); + structureQuery = new MemoizedQuery(` + (struct_declaration + name: (identifier) @name.definition.struct) @definition.struct + `); + methodIOQuery = new MemoizedQuery(` + (method_declaration + type: (_) @method-returnType + name: (identifier) @method-name + parameters: (formal_parameters + (formal_parameter + (type_identifier) @method-param.type + (identifier) @method-param.value + )? + @method-params) + body: (block) @method-body + )`); + + fieldQuery = new MemoizedQuery(` + (field_declaration + (type_identifier) @field-type + (variable_declarator + (identifier) @field-name + ) + ) @field-declaration + `); + namespaces = [ + [ + // variables + "local", + // types + "class", + "struct", + "enum", + "typedef", + "interface", + "enumerator", + // methods + "method", + // namespaces + "namespace", + ], + ]; + autoSelectInsideParent = []; + builtInTypes = [ + "bool", + "sbyte", + "byte", + "short", + "ushort", + "int", + "uint", + "ulong", + "float", + "double", + "decimal", + "char", + "string", + "object" + ]; +} diff --git a/src/code-context/csharp/CsharpRelevantCodeProvider.ts b/src/code-context/csharp/CsharpRelevantCodeProvider.ts new file mode 100644 index 00000000..3a68180c --- /dev/null +++ b/src/code-context/csharp/CsharpRelevantCodeProvider.ts @@ -0,0 +1,74 @@ +import { injectable } from 'inversify'; +import vscode from 'vscode'; + + +import { TextRange } from '../../code-search/scope-graph/model/TextRange'; +import { ScopeGraph } from '../../code-search/scope-graph/ScopeGraph'; +import { NamedElement } from '../../editor/ast/NamedElement'; +import { CodeFile } from '../../editor/codemodel/CodeElement'; +import { RelevantCodeProvider } from '../_base/RelevantCodeProvider'; +import { TreeSitterFile } from '../ast/TreeSitterFile'; +import { CsharpStructurerProvider } from './CsharpStructurerProvider'; +import { CsharpRelevantLookup } from './utils/CsharpRelevantLookup'; +import { ILanguageServiceProvider } from 'base/common/languages/languageService'; + +@injectable() +export class CsharpRelevantCodeProvider implements RelevantCodeProvider { + name = 'CsharpRelatedProvider'; + language = 'csharp'; + languageService: ILanguageServiceProvider | undefined; + + async setupLanguage(defaultLanguageServiceProvider: ILanguageServiceProvider) { + this.languageService = defaultLanguageServiceProvider; + } + + async getMethodFanInAndFanOut(file: TreeSitterFile, method: NamedElement): Promise { + let graph = await file.scopeGraph(); + return await this.lookupRelevantClass(method, file, graph); + } + + async lookupRelevantClass(element: NamedElement, tsfile: TreeSitterFile, graph: ScopeGraph): Promise { + let structurer = new CsharpStructurerProvider(); + await structurer.init(this.languageService!!); + + const textRange: TextRange = element.blockRange.toTextRange(); + const source = tsfile.sourcecode; + let ios: string[] = + (await structurer.retrieveMethodIOImports(graph, tsfile.tree.rootNode, textRange, source)) ?? []; + + let lookup = new CsharpRelevantLookup(tsfile); + let paths = lookup.relevantImportToFilePath(ios); + + // read file by path and structurer to parse it to uml + async function parseCodeFile(path: string): Promise { + const uri = vscode.Uri.file(path); + if (!(await vscode.workspace.fs.stat(uri))) { + return undefined; + } + + try { + const document = await vscode.workspace.openTextDocument(uri); + return await structurer.parseFile(document.getText(), path); + } catch (e) { + console.info(`Failed to open file ${path}`); + return undefined; + } + } + + let codeFiles: CodeFile[] = []; + for (const path of paths) { + let codeFile: CodeFile | undefined = undefined; + try { + codeFile = await parseCodeFile(path); + } catch (e) { + console.info(`Failed to parse file ${path}`); + } + + if (codeFile !== undefined) { + codeFiles.push(codeFile); + } + } + + return codeFiles; + } +} diff --git a/src/code-context/csharp/CsharpStructurerProvider.ts b/src/code-context/csharp/CsharpStructurerProvider.ts new file mode 100644 index 00000000..ddf644b4 --- /dev/null +++ b/src/code-context/csharp/CsharpStructurerProvider.ts @@ -0,0 +1,281 @@ +import { injectable } from 'inversify'; +import Parser, { SyntaxNode } from 'web-tree-sitter'; + + +import { TextRange } from '../../code-search/scope-graph/model/TextRange'; +import { ScopeGraph } from '../../code-search/scope-graph/ScopeGraph'; +import { CodeFile, CodeFunction, CodeStructure, CodeVariable, StructureType } from '../../editor/codemodel/CodeElement'; +import { LanguageProfile, LanguageProfileUtil } from '../_base/LanguageProfile'; +import { BaseStructurerProvider } from '../_base/StructurerProvider'; +import { LanguageIdentifier } from 'base/common/languages/languages'; + +@injectable() +export class CsharpStructurerProvider extends BaseStructurerProvider { + protected langId: LanguageIdentifier = 'csharp'; + protected config: LanguageProfile = LanguageProfileUtil.from(this.langId)!!; + protected parser: Parser | undefined; + protected language: Parser.Language | undefined; + + constructor() { + super(); + } + + isApplicable(lang: string) { + return lang === this.langId; + } + + /** + * The `parseFile` method is an asynchronous function that parses a given code string and generates a CodeFile object. This object represents the structure of the code. + * + * @param code - A string representing the code to be parsed. + * @param filepath - A string representing the path of the file. + * + * @returns A Promise that resolves to a CodeFile object. This object contains information about the structure of the parsed code, including the name, filepath, language, functions, path, package, imports, and classes. If the parsing fails, the Promise resolves to undefined. + * + * The method uses a parser to parse the code and a query to capture the structure of the code. It then iterates over the captures to extract information about the package, imports, classes, methods, and other elements of the code. This information is used to populate the CodeFile object. + * + * The method also handles nested classes and methods, ensuring that each class and method is correctly associated with its parent class or method. + * + * Note: This method assumes that the code string is written in a language that the parser can parse. If the parser cannot parse the code, the method may fail or return incorrect results. + */ + + /** + * `parseFile`方法是一个异步函数,它解析给定的代码字符串并生成CodeFile对象。此对象表示代码的结构。 + * + * @param code 表示要解析的代码的字符串。 + * @param filepath 表示文件路径的字符串。 + * @returns 解析为CodeFile对象的Promise。此对象包含有关解析代码结构的信息,包括名称、文件路径、语言、函数、路径、包、导入和类。如果解析失败,Promise将解析为undefined。 + * + * 该方法使用解析器来解析代码,并使用查询来捕获代码的结构。然后,它迭代这些捕获,以提取有关包、导入、类、方法和代码其他元素的信息。此信息用于填充CodeFile对象。 + * + * 该方法还处理嵌套的类和方法,确保每个类和方法与其父类或方法正确关联。 + * + * 注意:此方法假定代码字符串是用解析器可以解析的语言编写的。如果解析器无法解析代码,则该方法可能会失败或返回不正确的结果。 + * + */ + async parseFile(code: string, filepath: string): Promise { + const tree = this.parser!!.parse(code); + const query = this.config.structureQuery.query(this.language!!); + const captures = query!!.captures(tree.rootNode); + + let filename = filepath.split('/')[filepath.split('/').length - 1]; + const codeFile: CodeFile = { + name: filename, + filepath: filepath, + language: this.langId, + functions: [], + path: '', + package: '', + imports: [], + classes: [], + }; + let classObj: CodeStructure = { + type: StructureType.Class, + canonicalName: '', + constant: [], + extends: [], + methods: [], + name: '', + package: '', + implements: [], + start: { row: 0, column: 0 }, + end: { row: 0, column: 0 }, + }; + let isLastNode = false; + const methods: CodeFunction[] = []; + let methodReturnType = ''; + let methodName = ''; + + const fields: CodeVariable[] = []; + let lastField: CodeVariable = this.initVariable(); + + for (const element of captures) { + const capture: Parser.QueryCapture = element!!; + const text = capture.node.text; + + switch (capture.name) { + case 'package-name': + codeFile.package = text; + break; + case 'import-name': + codeFile.imports.push(text); + break; + case 'class-name': + if (classObj.name !== '') { + codeFile.classes.push({ ...classObj }); + classObj = { + type: StructureType.Class, + canonicalName: '', + package: codeFile.package, + implements: [], + constant: [], + extends: [], + methods: [], + name: '', + start: { row: 0, column: 0 }, + end: { row: 0, column: 0 }, + }; + } + + classObj.name = text; + classObj.canonicalName = codeFile.package + '.' + classObj.name; + const classNode: Parser.SyntaxNode | null = capture.node?.parent ?? null; + if (classNode !== null) { + this.insertLocation(classNode, classObj); + if (!isLastNode) { + isLastNode = true; + } + } + break; + case 'method-returnType': + methodReturnType = text; + break; + case 'method-name': + methodName = text; + break; + case 'method-body': + if (methodName !== '') { + const methodNode = capture.node; + const methodObj = this.createFunction(capture.node, methodName); + if (methodReturnType !== '') { + methodObj.returnType = methodReturnType; + } + if (methodNode !== null) { + this.insertLocation(methodNode, classObj); + } + + methods.push(methodObj); + } + + methodReturnType = ''; + methodName = ''; + break; + case 'field-type': + lastField.type = text; + break; + case 'field-decl': + lastField.name = text; + fields.push({ ...lastField }); + lastField = this.initVariable(); + break; + case 'impl-name': + classObj.implements.push(text); + break; + default: + break; + } + } + + classObj.fields = fields; + classObj.methods = methods; + + if (isLastNode && classObj.name !== '') { + codeFile.classes.push({ ...classObj }); + } + + return this.combineSimilarClasses(codeFile); + } + + /** + * `extractMethodIOImports` is an asynchronous method that extracts the import statements related to the input and output + * types of a given method from the source code. + * + * @param {ScopeGraph} graph - The node graph of the source code. + * @param {SyntaxNode} node - The syntax node representing the method in the source code. + * @param {TextRange} range - The range of the method in the source code. + * @param {string} src - The source code as a string. + * + * @returns {Promise} A promise that resolves to an array of import statements or undefined if no import statements are found. + * + * The method works by first finding the syntax node that corresponds to the given range in the source code. It then uses a query to capture the return type and parameter types of the method. For each captured element, it fetches the corresponding import statements from the source code and adds them to an array. Finally, it removes any duplicate import statements from the array before returning it. + * + * The method uses the `fetchImportsWithinScope` method to fetch the import statements for a given syntax node from the source code. + * + * Note: The method assumes that the `methodIOQuery` and `language` properties of the `config` object are defined. + */ + +/** +*`extractMethodIOImports`是一个异步方法,用于提取与输入和输出相关的导入语句 +*源代码中给定方法的类型。 +* +* @param {ScopeGraph} graph -源代码的节点图。 +* @param {SyntaxNode} 节点 -表示源代码中方法的语法节点。 +* @param {TextRange} range -源代码中方法的范围。 +* @param {string} src-源代码为字符串。 +* +* @returns {Promise} 一个promise,解析为一个import语句数组,如果找不到import语句,则解析为undefined。 +* +* 该方法的工作原理是首先在源代码中找到与给定范围对应的语法节点。然后,它使用查询来捕获方法的返回类型和参数类型。对于每个捕获的元素,它从源代码中获取相应的导入语句并将其添加到数组中。最后,在返回数组之前,它会从数组中删除任何重复的导入语句。 +* +* 该方法使用“fetchImportsWithinScope”方法从源代码中获取给定语法节点的导入语句。 +* +* 注意:该方法假定定义了`config`对象的`methodIOQuery`和`language`属性。 +*/ + + async retrieveMethodIOImports( + graph: ScopeGraph, + node: SyntaxNode, + range: TextRange, + src: string, + ): Promise { + let syntaxNode = node.namedDescendantForPosition( + { row: range.start.line, column: range.start.column }, + { row: range.end.line, column: range.end.column }, + ); + + const query = this.config.methodIOQuery!!.query(this.language!!); + const captures = query!!.captures(syntaxNode); + + const inputAndOutput: string[] = []; + + for (const element of captures) { + const capture: Parser.QueryCapture = element!!; + + switch (capture.name) { + case 'method-returnType': + let imports = await this.fetchImportsWithinScope(graph, capture.node, src); + inputAndOutput.push(...imports); + break; + case 'method-param.type': + let typeImports = await this.fetchImportsWithinScope(graph, capture.node, src); + inputAndOutput.push(...typeImports); + break; + default: + break; + } + } + + // remove duplicates + return [...new Set(inputAndOutput)]; + } + + async extractFields(node: SyntaxNode) { + const query = this.config.fieldQuery!!.query(this.language!!); + const captures = query!!.captures(node); + + const fields: CodeVariable[] = []; + let fieldObj: CodeVariable = this.initVariable(); + + for (const element of captures) { + const capture: Parser.QueryCapture = element!!; + const text = capture.node.text; + + switch (capture.name) { + case 'field-name': + fieldObj.name = text; + fields.push({ ...fieldObj }); + fieldObj = this.initVariable(); + break; + case 'field-type': + fieldObj.type = text; + break; + case 'field-declaration': + break; + default: + break; + } + } + + return fields; + } +} diff --git a/src/code-context/csharp/CsharpTestGenProvider.ts b/src/code-context/csharp/CsharpTestGenProvider.ts new file mode 100644 index 00000000..7e009437 --- /dev/null +++ b/src/code-context/csharp/CsharpTestGenProvider.ts @@ -0,0 +1,272 @@ +import { inject, injectable } from 'inversify'; +import path from 'path'; +import vscode, { l10n } from 'vscode'; + +import { LanguageIdentifier } from 'base/common/languages/languages'; +import { ILanguageServiceProvider } from 'base/common/languages/languageService'; + +import { ScopeGraph } from '../../code-search/scope-graph/ScopeGraph'; +import { NamedElement } from '../../editor/ast/NamedElement'; +import { TreeSitterFileManager } from '../../editor/cache/TreeSitterFileManager'; +import { CommentedUmlPresenter } from '../../editor/codemodel/presenter/CommentedUmlPresenter'; +import { GradleBuildToolProvider } from '../../toolchain-context/buildtool/GradleBuildToolProvider'; +import { ToolchainContextItem } from '../../toolchain-context/ToolchainContextProvider'; +import { AutoTestTemplateContext } from '../_base/test/AutoTestTemplateContext'; +import { TestGenProvider } from '../_base/test/TestGenProvider'; +import { TestTemplateManager } from '../_lookup/TestTemplateManager'; +import { TreeSitterFile } from '../ast/TreeSitterFile'; +import { CsharpCodeCorrector } from './CsharpCodeCorrector'; +import { CsharpStructurerProvider } from './CsharpStructurerProvider'; + +@injectable() +export class CsharpTestGenProvider implements TestGenProvider { + baseTestPrompt: string = `${l10n.t('lang.java.prompt.basicTestTemplate')}`.trim(); + importRegex = /import\s+([\w.]+);/g; + + private clazzName = this.constructor.name; + + private graph: ScopeGraph | undefined; + private tsfile: TreeSitterFile | undefined; + private context: AutoTestTemplateContext | undefined; + private packageName = ''; + + private lsp!: ILanguageServiceProvider; + private treeSitterFileManager!: TreeSitterFileManager; + + constructor() {} + + isApplicable(lang: LanguageIdentifier): boolean { + return lang === 'csharp'; + } + + async setupLanguage(lsp: ILanguageServiceProvider, context?: AutoTestTemplateContext) { + this.lsp = lsp; + + // TODO hack, after move + this.treeSitterFileManager = new TreeSitterFileManager(lsp); + this.context = context; + } + + /** + * The `setupTestFile` method is an asynchronous function that sets up a test file for a given document and named element. + * + * @param {vscode.TextDocument} document - The document for which the test file is to be set up. + * @param {NamedElement} element - The named element for which the test file is to be set up. + * + * @returns {Promise} - Returns a promise that resolves to an AutoTestTemplateContext object. + * + * The method first converts the document to a TreeSitterFile and generates a scope graph for it. It then constructs a target path for the test file by replacing ".java" with "Test.java" and "src/main/java" with "src/test/java" in the document's file name. + * + * An AutoTestTemplateContext object is then created with the following properties: + * - filename: The file name of the document. + * - language: The language ID of the document. + * - targetPath: The target path for the test file. + * - testClassName: The text of the identifier range of the named element. + * - sourceCode: The text of the block range of the named element. + * - relatedClasses: An empty array. + * - imports: An empty array. + * + * The method then creates a URI for the target path and writes an empty file to it in the workspace's file system. + * + * Finally, the method returns a promise that resolves to the created AutoTestTemplateContext object. + */ + + + + /** + * + * @param document -要为其设置测试文件的文档。 + * @param element -要为其设置测试文件的命名元素。 + * @returns {Promise} -返回一个解析为AutoTestTemplateContext对象的promise。 + * + * 该方法的工作原理是首先在源代码中找到与给定范围对应的语法节点。然后,它使用查询来捕获方法的返回类型和参数类型。对于每个捕获的元素,它从源代码中获取相应的导入语句并将其添加到数组中。最后,在返回数组之前,它会从数组中删除任何重复的导入语句。**该方法使用`fetchImportsWithinScope`方法从源代码中获取给定语法节点的导入声明。**注意:该方法假设定义了`config`对象的`methodIOQuery`和`language`属性。 + * + * 该方法首先将文档转换为TreeSitterFile并为其生成作用域图。然后,通过在文档文件名中将“.java”替换为“test.java”,将“src/main/java”替换为”src/test/java“,为测试文件构建目标路径。 + * + * 然后创建具有以下属性的AutoTestTemplateContext对象: + * filename:文档的文件名。 + * -language:文档的语言ID。 + * -targetPath:测试文件的目标路径。 + * -testClassName:命名元素的标识符范围的文本。 + * -sourceCode:指定元素的块范围的文本。 + * -relatedClasses:一个空数组。 + * -imports:一个空数组。 + * + * 然后,该方法为目标路径创建一个URI,并在工作区的文件系统中向其写入一个空文件。 + * + * 最后,该方法返回一个promise,该promise解析为创建的AutoTestTemplateContext对象。 +*/ + async setupTestFile(document: vscode.TextDocument, element: NamedElement): Promise { + this.tsfile = await this.treeSitterFileManager.create(document); + this.graph = await this.tsfile.scopeGraph(); + + let query = this.tsfile.languageProfile.packageQuery!!.query(this.tsfile.tsLanguage); + let matches = query.captures(this.tsfile.tree.rootNode); + if (matches.length > 0) { + this.packageName = matches[0].node.text; + } + + const targetPath = document.fileName.replace('.java', 'Test.java').replace('src/main/java', 'src/test/java'); + + let filename = path.basename(document.uri.fsPath); + let classname = filename.replace('.java', ''); + + let structurerProvider = new CsharpStructurerProvider(); + await structurerProvider.init(this.lsp); + + let codeFile = await structurerProvider.parseFile(document.getText(), document.fileName); + let currentClass = codeFile?.classes.find(clazz => clazz.name === classname); + + const testContext: AutoTestTemplateContext = { + filename: filename, + language: document.languageId, + targetPath: targetPath, + underTestClassName: classname, + targetTestClassName: classname + 'Test', + sourceCode: element.blockRange.text, + relatedClasses: '', + currentClass: new CommentedUmlPresenter().presentClass(currentClass!!, 'java'), + chatContext: '', + imports: [], + }; + + const targetUri = vscode.Uri.file(targetPath); + await vscode.workspace.fs.writeFile(targetUri, new Uint8Array()); + this.context = testContext; + return Promise.resolve(testContext); + } + + /** + * after test file is created, try to fix the code, like packageName and className, etc. + */ + async postProcessCodeFix(document: vscode.TextDocument, output: string): Promise { + let codeFixer = new CsharpCodeCorrector( + { + document: document, + sourcecode: output, + packageName: this.packageName, + targetClassName: this.context!!.targetTestClassName!!, + }, + this.lsp, + ); + + await codeFixer.correct(); + } + + /** + * addition test context + * @param context + */ + async additionalTestContext(context: AutoTestTemplateContext): Promise { + const fileName = context.filename; + + if (context && context.imports === undefined) { + context.imports = []; + } + + let isSpringRelated = true; + if (context) { + const imports = this.importRegex.exec(context.sourceCode!!); + const importStrings = imports?.map(imp => imp[1]) ?? []; + context!!.imports = imports?.map(imp => imp[1]) ?? []; + if (this.context) { + this.context.imports = context.imports; + } + + isSpringRelated = this.checkIsSpringRelated(importStrings) ?? false; + } + + let prompt = this.baseTestPrompt + (await this.determineJUnitVersion()); + + const testPrompt = new TestTemplateManager(); + let finalPrompt: ToolchainContextItem; + + if (this.isController(fileName) && isSpringRelated) { + let testControllerPrompt = prompt + `\n${l10n.t('lang.java.prompt.testForController')}\n`.trim(); + + const lookup = testPrompt.lookup('ControllerTest.java'); + if (lookup !== null) { + testControllerPrompt += `\nTest code template:\n\`\`\`java\n${lookup}\n\`\`\`\n`; + } + + finalPrompt = { clazz: this.clazzName, text: testControllerPrompt }; + } else if (this.isService(fileName) && isSpringRelated) { + let testServicePrompt = prompt + `\n${l10n.t('lang.java.prompt.testForService')}\n`.trim(); + + const lookup = testPrompt.lookup('ServiceTest.java'); + if (lookup !== null) { + testServicePrompt += `\nTest code template:\n\`\`\`java\n${lookup}\n\`\`\`\n`; + } + + finalPrompt = { clazz: this.clazzName, text: testServicePrompt }; + } else { + const lookup = testPrompt.lookup('Test.java'); + if (lookup !== null) { + prompt += `\nTest code template:\n\`\`\`java\n${lookup}\n\`\`\`\n`; + } + finalPrompt = { clazz: this.clazzName, text: prompt }; + } + + return [finalPrompt]; + } + + protected isService(fileName: string | null): boolean { + return fileName !== null && MvcUtil.isService(fileName, 'java'); + } + + protected isController(fileName: string | null): boolean { + return fileName !== null && MvcUtil.isController(fileName, 'java'); + } + + async determineJUnitVersion(): Promise { + let dependencies = await GradleBuildToolProvider.instance().getDependencies(); + let rule = ''; + let hasJunit5 = false; + let hasJunit4 = false; + + const libraryData = dependencies.dependencies; + if (libraryData) { + for (const lib of libraryData) { + if (lib.group === 'org.junit.jupiter') { + hasJunit5 = true; + break; + } + + if (lib.group === 'junit') { + hasJunit4 = true; + break; + } + } + } + + if (hasJunit5) { + rule = l10n.t('lang.java.prompt.useJunit5'); + } else if (hasJunit4) { + rule = l10n.t('lang.java.prompt.useJunit4'); + } + + return rule; + } + + private checkIsSpringRelated(imports: string[]) { + for (const imp of imports) { + if (imp.startsWith('org.springframework')) { + return true; + } + } + } +} + +export namespace MvcUtil { + export function isController(fileName: string, lang: string): boolean { + return fileName.endsWith(`Controller.${lang.toLowerCase()}`); + } + + export function isService(fileName: string, lang: string): boolean { + return fileName.endsWith(`Service.${lang.toLowerCase()}`) || fileName.endsWith(`ServiceImpl.${lang.toLowerCase()}`); + } + + export function isRepository(fileName: string, lang: string): boolean { + return fileName.endsWith(`Repository.${lang.toLowerCase()}`) || fileName.endsWith(`Repo.${lang.toLowerCase()}`); + } +} diff --git a/src/code-context/csharp/utils/CsharpRelevantLookup.ts b/src/code-context/csharp/utils/CsharpRelevantLookup.ts new file mode 100644 index 00000000..c3ddc9ba --- /dev/null +++ b/src/code-context/csharp/utils/CsharpRelevantLookup.ts @@ -0,0 +1,88 @@ +import { LanguageProfileUtil } from '../../_base/LanguageProfile'; +import { TreeSitterFile } from '../../ast/TreeSitterFile'; + +export class CsharpRelevantLookup { + tsfile: TreeSitterFile; + + constructor(tsfile: TreeSitterFile) { + this.tsfile = tsfile; + } + + /** + *“relevantImportToFilePath”方法用于从给定的导入中筛选出相关类并返回其文件路径。 + * + *@param import 这是一个字符串数组,其中每个字符串代表一个导入语句。 + * + * @return 此方法返回一个字符串数组,其中每个字符串都是相关导入的文件路径。 + * + *该方法首先通过调用“imports”参数上的“refineImportTypes”方法来细化导入类型。“refineImportTypes”方法预计将返回一个相关导入的数组。 + * + *然后,它映射相关导入的数组,并为每个导入调用`pathByPackageName`方法。`pathByPackageName`方法应返回导入的文件路径。 + * + *然后,`relevantImportToFilePath`方法返回生成的文件路径数组。 + * + *注意:此代码段中未定义“refineImportTypes”和“pathByPackageName”方法。它们应在规范的其他地方定义,并应按照要求使用。 + **/ + relevantImportToFilePath(imports: string[]): string[] { + const relevantImports = this.refineImportTypes(imports); + + let packages = relevantImports.map(imp => { + return this.pathByPackageName(imp); + }); + + //return packages.filter(p => !this.isCsharpFrameworks(p)); + return packages; + } + + // isCsharpFrameworks(imp: string) { + // let javaImport = imp.startsWith('java.') || imp.startsWith('javax.'); + // let isSpringImport = imp.startsWith('org.springframework'); + // return javaImport || isSpringImport; + // } + + private refineImportTypes(imports: string[]) { + return imports.map(imp => { + const impArr = imp.split(' '); + return impArr[impArr.length - 1].replace(';', ''); + }); + } + + extractCurrentPackageName() { + let languageProfile = LanguageProfileUtil.from('csharp')!!; + + const query = languageProfile.packageQuery?.query(this.tsfile.tsLanguage)!!; + const matches = query.matches(this.tsfile.tree.rootNode); + + if (matches.length === 0) { + return ''; + } + + const packageNameNode = matches[0].captures[0].node; + return packageNameNode.text; + } + + + + + /** + *给定一个包名,如果与当前的tsfile包相似,请尝试在代码库中查找。 + * + *例如,如果当前文件包名为“cc.unitmesh.unttled.demo.service”,则相关的包名 + *是`cc.unitmesh.unttled.demo.repository`,那么我们可以根据当前的文件路径查找相关的类路径; + **/ + pathByPackageName(packageName: string) { + let currentPath = this.tsfile.filePath; + const currentPackageName = this.extractCurrentPackageName(); + if (currentPackageName === '') { + return ''; + } + + // let projectPath = currentPath.split('src/main/java')[0]; + // we need to support kotlin, scala file path as well, which is src/main/kotlin + let projectPath = currentPath.split('src/main')[0]; + const packagePath = packageName.replace(/\./g, '/'); + const lang = currentPath.split('src/main')?.[1]?.split('/')?.[1] || 'cs'; + + return `${projectPath}src/main/${lang}/${packagePath}.cs`; + } +} diff --git a/src/code-context/kotlin/KotlinProfile.ts b/src/code-context/kotlin/KotlinProfile.ts new file mode 100644 index 00000000..62ae8482 --- /dev/null +++ b/src/code-context/kotlin/KotlinProfile.ts @@ -0,0 +1,196 @@ +import { injectable } from 'inversify'; + +import kotlinscm from '../../code-search/schemas/indexes/kotlin.scm?raw'; +import { LanguageProfile, MemoizedQuery } from '../_base/LanguageProfile'; +import { ILanguageServiceProvider } from 'base/common/languages/languageService'; + +@injectable() +export class KotlinProfile implements LanguageProfile { + languageIds = ['kotlin']; + fileExtensions = ['kt']; + grammar = (langService: ILanguageServiceProvider) => langService.getLanguage('kotlin'); + isTestFile = (filePath: string) => filePath.endsWith('Test.kt') && filePath.includes('src/test'); + scopeQuery = new MemoizedQuery(kotlinscm); + hoverableQuery = new MemoizedQuery(` + [(simple_identifier) + (user_type (type_identifier))] @hoverable + `); + methodQuery = new MemoizedQuery(` + (function_declaration + (simple_identifier) @name.definition.method) @definition.method + `); + classQuery = new MemoizedQuery(` + (class_declaration + (type_identifier) @name.definition.class) @definition.class + `); + blockCommentQuery = new MemoizedQuery(` + ((block_comment) @block_comment + (#match? @block_comment "^\\\\/\\\\*\\\\*")) @docComment`); + packageQuery = new MemoizedQuery(` + (package_header + (identifier) @package-name) + `); + structureQuery = new MemoizedQuery(` + (package_header + (identifier) @package-name)? + + (import_header + (identifier) @import-name)? + + (class_declaration + (type_identifier) @class-name + (delegation_specifier + (user_type + (type_identifier)) @extend-name) + )? + (primary_constructor + (class_parameter + (simple_identifier) @field-name + (user_type (type_identifier)) @field-type + )? + )? + (class_body + (property_declaration + (modifiers + (member_modifier)?)? + (binding_pattern_kind)? + (variable_declaration + (simple_identifier) @field-name + (user_type (type_identifier)) @field-type + ) + )? + (function_declaration + (modifiers + (member_modifier)?)? + (simple_identifier) @method-name + (function_value_parameters)? + (function_body)? @method-body + )? + )? + + (class_declaration + (type_identifier) @class-name + (class_body + (property_declaration + (binding_pattern_kind)? + (variable_declaration + (simple_identifier) @interface-property-name + (user_type (type_identifier)) @interface-property-type + ) + )? + (getter + (function_body)? + )? + (function_declaration + (simple_identifier) @interface-method-name + (function_value_parameters)? + (function_body)? @interface-method-body + )? + ) + )? + `); + methodIOQuery = new MemoizedQuery(` + (function_declaration + type: (_) @method-returnType + (simple_identifier) @method-name + (function_value_parameters + (parameter + (simple_identifier) @method-param.value + (user_type (type_identifier)) @method-param.type + )? + @method-params) + (function_body) @method-body + )`); + + fieldQuery = new MemoizedQuery(` + (property_declaration + (modifiers (member_modifier))? + (binding_pattern_kind)? + (variable_declaration + (simple_identifier) @field-name + (user_type (type_identifier)) @field-type + ) + (integer_literal | string_literal | boolean_literal)? @field-value + ) @field-declaration + `); + + interfaceQuery = new MemoizedQuery(` + (class_declaration + (type_identifier) @interface-name + (class_body + (property_declaration + (binding_pattern_kind)? + (variable_declaration + (simple_identifier) @interface-property-name + (user_type (type_identifier)) @interface-property-type + ) + )? + (function_declaration + (simple_identifier) @interface-method-name + (function_value_parameters)? + (function_body)? @interface-method-body + )? + ) + ) + `); + namespaces = [ + [ + // variables + 'local', + // functions + 'method', + // namespacing, modules + 'package', + 'module', + // types + 'class', + 'enum', + 'enumConstant', + 'interface', + 'typealias', + // devops. + 'label', + ], + ]; + autoSelectInsideParent = []; + builtInTypes = [ + 'Boolean', + 'Byte', + 'Char', + 'Short', + 'Int', + 'Long', + 'Float', + 'Double', + 'Unit', + 'String', + 'Array', + 'List', + 'Map', + 'Set', + 'Collection', + 'Iterable', + 'Iterator', + 'Sequence', + 'Any', + 'Nothing', + 'Unit', + 'Boolean', + 'Byte', + 'Char', + 'Short', + 'Int', + 'Long', + 'Float', + 'Double', + 'String', + 'Array', + 'List', + 'Map', + 'Set', + 'Collection', + 'Iterable', + 'Iterator', + 'Sequence', + ]; +} diff --git a/src/code-context/kotlin/KotlinStructurerProvider.ts b/src/code-context/kotlin/KotlinStructurerProvider.ts new file mode 100644 index 00000000..a2ebaecc --- /dev/null +++ b/src/code-context/kotlin/KotlinStructurerProvider.ts @@ -0,0 +1,193 @@ +import { injectable } from 'inversify'; +import Parser from 'web-tree-sitter'; + +import { CodeFile, CodeFunction, CodeStructure, CodeVariable, StructureType } from '../../editor/codemodel/CodeElement'; +import { LanguageProfile, LanguageProfileUtil } from '../_base/LanguageProfile'; +import { BaseStructurerProvider } from '../_base/StructurerProvider'; +import { LanguageIdentifier } from 'base/common/languages/languages'; + + +@injectable() +export class KotlinStructurerProvider extends BaseStructurerProvider { + protected langId: LanguageIdentifier = 'kotlin'; + protected config: LanguageProfile = LanguageProfileUtil.from(this.langId)!!; + protected parser: Parser | undefined; + protected language: Parser.Language | undefined; + + constructor() { + super(); + } + + isApplicable(lang: string) { + return lang === this.langId; + } + + async parseFile(code: string, filepath: string): Promise { + const tree = this.parser!!.parse(code); + const query = this.config.structureQuery.query(this.language!!); + const captures = query!!.captures(tree.rootNode); + + let filename = filepath.split('/')[filepath.split('/').length - 1]; + const codeFile: CodeFile = { + name: filename, + filepath: filepath, + language: this.langId, + functions: [], + path: '', + package: '', + imports: [], + classes: [], + }; + let classObj: CodeStructure = this.createEmptyStructure(StructureType.Class); + let interfaceObj: CodeStructure = this.createEmptyStructure(StructureType.Interface); + + let isLastNode = false; + let isInInterface = false; + const methods: CodeFunction[] = []; + let methodReturnType = ''; + let methodName = ''; + + const fields: CodeVariable[] = []; + let lastField: CodeVariable = this.initVariable(); + + for (const element of captures) { + const capture: Parser.QueryCapture = element!!; + const text = capture.node.text; + + switch (capture.name) { + case 'package-name': + codeFile.package = text; + break; + case 'import-name': + codeFile.imports.push(text); + break; + case 'class-name': + if (classObj.name !== '') { + classObj.fields = fields.slice(); + classObj.methods = methods.slice(); + codeFile.classes.push({ ...classObj }); + + // 重置字段和方法 + methods.length = 0; + fields.length = 0; + } + + classObj = this.createEmptyStructure(StructureType.Class); + classObj.name = text; + classObj.canonicalName = codeFile.package ? codeFile.package + '.' + classObj.name : classObj.name; + isInInterface = false; + + const classNode: Parser.SyntaxNode | null = capture.node?.parent ?? null; + if (classNode !== null) { + this.insertLocation(classNode, classObj); + if (!isLastNode) { + isLastNode = true; + } + } + break; + case 'interface-name': + if (interfaceObj.name !== '') { + interfaceObj.fields = fields.slice(); + interfaceObj.methods = methods.slice(); + codeFile.classes.push({ ...interfaceObj }); + + methods.length = 0; + fields.length = 0; + } + + interfaceObj = this.createEmptyStructure(StructureType.Interface); + interfaceObj.name = text; + interfaceObj.canonicalName = codeFile.package ? codeFile.package + '.' + interfaceObj.name : interfaceObj.name; + isInInterface = true; + + const interfaceNode: Parser.SyntaxNode | null = capture.node?.parent ?? null; + if (interfaceNode !== null) { + this.insertLocation(interfaceNode, interfaceObj); + if (!isLastNode) { + isLastNode = true; + } + } + break; + case 'extend-name': + if (isInInterface) { + interfaceObj.extends?.push(text); + } else { + classObj.extends?.push(text); + } + break; + case 'implements-name': + classObj.implements.push(text); + break; + case 'method-name': + case 'interface-method-name': + methodName = text; + break; + case 'method-body': + case 'interface-method-body': + if (methodName !== '') { + const methodNode = capture.node; + const methodObj = this.createFunction(capture.node, methodName); + if (methodReturnType !== '') { + methodObj.returnType = methodReturnType; + } + + methods.push(methodObj); + } + + methodReturnType = ''; + methodName = ''; + break; + case 'field-name': + case 'interface-property-name': + lastField = this.initVariable(); + lastField.name = text; + break; + case 'field-type': + case 'interface-property-type': + if (lastField.name) { + lastField.type = text; + fields.push({ ...lastField }); + lastField = this.initVariable(); + } + break; + case 'field-value': + if (lastField.name) { + lastField.type = text; + } + break; + default: + break; + } + } + + // 处理最后一个类或接口 + if (isInInterface && interfaceObj.name !== '') { + interfaceObj.fields = fields; + interfaceObj.methods = methods; + codeFile.classes.push({ ...interfaceObj }); + } else if (!isInInterface && classObj.name !== '') { + classObj.fields = fields; + classObj.methods = methods; + codeFile.classes.push({ ...classObj }); + } + + return this.combineSimilarClasses(codeFile); + } + + // 创建空的结构对象 + private createEmptyStructure(type: StructureType): CodeStructure { + return { + type: type, + canonicalName: '', + constant: [], + extends: [], + methods: [], + name: '', + package: '', + implements: [], + fields: [], + end: { row: 0, column: 0 }, + start: { row: 0, column: 0 } + } + } +} diff --git a/src/code-context/rust/RustProfile.ts b/src/code-context/rust/RustProfile.ts new file mode 100644 index 00000000..374f008b --- /dev/null +++ b/src/code-context/rust/RustProfile.ts @@ -0,0 +1,90 @@ +import { injectable } from 'inversify'; + +import rust from '../../code-search/schemas/indexes/rust.scm?raw'; +import { LanguageProfile, MemoizedQuery } from '../_base/LanguageProfile'; +import { ILanguageServiceProvider } from 'base/common/languages/languageService'; + +@injectable() +export class RustProfile implements LanguageProfile { + languageIds = ['rust']; + fileExtensions = ['rs']; + grammar = (langService: ILanguageServiceProvider) => langService.getLanguage('rust'); + isTestFile = (filePath: string) => filePath.endsWith('test.rs'); + scopeQuery = new MemoizedQuery(rust); + hoverableQuery = new MemoizedQuery(` + [(identifier) + (shorthand_field_identifier) + (field_identifier) + (type_identifier)] @hoverable + `); + classQuery = new MemoizedQuery(` + (struct_item (type_identifier) @type_identifier) @type_declaration + `); + methodQuery = new MemoizedQuery(` + (function_item (identifier) @name.definition.method) @definition.method + `); + blockCommentQuery = new MemoizedQuery(` + (block_comment) @docComment + `); + methodIOQuery = new MemoizedQuery(` + (function_item + name: (identifier) @function.identifier + return_type: (type_identifier)? @method-returnType + ) @function + `); + structureQuery = new MemoizedQuery(``); + namespaces = [[ + // variables + "const", + "function", + "variable", + // types + "struct", + "enum", + "union", + "typedef", + "interface", + // fields + "field", + "enumerator", + // namespacing + "module", + // misc + "label", + "lifetime", + ]]; + autoSelectInsideParent = []; + builtInTypes = [ +// 基本类型 + "bool", // 对应 Java 的 boolean + "i8", // 对应 Java 的 byte + "char", // 对应 Java 的 char + "i16", // 对应 Java 的 short + "i32", // 对应 Java 的 int + "i64", // 对应 Java 的 long + "f32", // 对应 Java 的 float + "f64", // 对应 Java 的 double + "()", // 对应 Java 的 void + + // 包装类对应类型(Rust 没有直接的包装类型,但以下为常用的类型) + "bool", // 对应 Java 的 Boolean + "i8", // 对应 Java 的 Byte + "char", // 对应 Java 的 Character + "i16", // 对应 Java 的 Short + "i32", // 对应 Java 的 Integer + "i64", // 对应 Java 的 Long + "f32", // 对应 Java 的 Float + "f64", // 对应 Java 的 Double + "String", // 对应 Java 的 String + + // 集合类型(Rust 没有直接对应的类型名,但以下为常用的集合类型) + "&[T]", // 对应 Java 的 Array,Rust 中的 slice 引用 + "Vec", // 对应 Java 的 List,Rust 中的动态数组 + "HashMap",// 对应 Java 的 Map,Rust 中的哈希映射 + "HashSet", // 对应 Java 的 Set,Rust 中的哈希集合 + "Vec", // 对应 Java 的 Collection,Rust 中可用 Vec 代表 + "impl Iterator",// 对应 Java 的 Iterable 和 Iterator,Rust 中的 Iterator trait + "impl Iterator",// 对应 Java 的 Stream,Rust 中流式处理可以用 Iterator 实现 + "Option", // 对应 Java 的 Optional,Rust 中的 Option 类型 + ]; +} diff --git a/src/commands/commandsService.ts b/src/commands/commandsService.ts index 4e9c30d6..31ad71f3 100644 --- a/src/commands/commandsService.ts +++ b/src/commands/commandsService.ts @@ -27,6 +27,7 @@ import { CMD_EXPLAIN_CODE, CMD_FEEDBACK, CMD_FIX_THIS, + CMD_GEN_CODE_METHOD_COMPLETIONS, CMD_GEN_DOCSTRING, CMD_GIT_MESSAGE_COMMIT_GENERATE, CMD_NEW_CHAT_SESSION, @@ -90,7 +91,6 @@ export class CommandsService { return; } - const selection = editor.selection; if (selection.isEmpty) { await chat.send('focusAutoDevInput', undefined); @@ -181,6 +181,30 @@ export class CommandsService { chat.input(`${l10n.t('I got the following error, can you please help explain how to fix it?')}: ${input}`); } + async generateMethod() { + const editor = window.activeTextEditor; + if (!editor) { + return; + } + + try { + const document = editor.document; + const edit = new WorkspaceEdit(); + const elementBuilder = await createNamedElement(this.autodev.treeSitterFileManager, document); + const currentLine = editor.selection.active.line; + const ranges = elementBuilder.getElementForAction(currentLine); + + if (ranges.length === 0) { + return; + } + + await this.autodev.executeAutoMethodAction(document, ranges[0], edit); + } catch (error) { + logger.error(`Commands error`, error); + showErrorMessage('Command Call Error'); + } + } + async generateDocstring() { const editor = window.activeTextEditor; if (!editor) { @@ -376,6 +400,7 @@ export class CommandsService { commands.registerCommand(CMD_FIX_THIS, this.fixThis, this), commands.registerCommand(CMD_QUICK_FIX, this.quickFix, this), commands.registerCommand(CMD_GEN_DOCSTRING, this.generateDocstring, this), + commands.registerCommand(CMD_GEN_CODE_METHOD_COMPLETIONS, this.generateMethod, this), commands.registerCommand(CMD_CREATE_UNIT_TEST, this.generateUnitTest, this), // Codebase Commands commands.registerCommand(CMD_CODEBASE_INDEXING, this.startCodebaseIndexing, this), diff --git a/src/editor/views/chat/continue/continueViewProvider.ts b/src/editor/views/chat/continue/continueViewProvider.ts index 6dda5857..e26a06b6 100644 --- a/src/editor/views/chat/continue/continueViewProvider.ts +++ b/src/editor/views/chat/continue/continueViewProvider.ts @@ -353,8 +353,9 @@ export class ContinueViewProvider extends AbstractWebviewViewProvider implements await this.lm.chat( mapToChatMessages(event.data.messages), { - ...resource, + ...completionOptions, + model:resource?.model, }, { report(fragment) { diff --git a/src/prompt-manage/ActionType.ts b/src/prompt-manage/ActionType.ts index 3d97d8df..afeb42a3 100644 --- a/src/prompt-manage/ActionType.ts +++ b/src/prompt-manage/ActionType.ts @@ -6,4 +6,5 @@ export enum ActionType { Rename, GenCommitMessage, LlmReranker, + AutoMethod, } diff --git a/src/prompt-manage/PromptManager.ts b/src/prompt-manage/PromptManager.ts index ed86a75e..e17635e6 100644 --- a/src/prompt-manage/PromptManager.ts +++ b/src/prompt-manage/PromptManager.ts @@ -134,6 +134,9 @@ export class PromptManager { case ActionType.AutoDoc: template = await templateRender.getTemplate(`prompts/genius/${humanLanguage}/code/auto-doc.vm`); break; + case ActionType.AutoMethod: + template = await templateRender.getTemplate(`prompts/genius/${humanLanguage}/code/auto-Method.vm`); + break; case ActionType.AutoTest: template = await templateRender.getTemplate(`prompts/genius/${humanLanguage}/code/test-gen.vm`); break; diff --git a/src/test/language/kotlin/KotlinStructure.test.ts b/src/test/language/kotlin/KotlinStructure.test.ts new file mode 100644 index 00000000..75318999 --- /dev/null +++ b/src/test/language/kotlin/KotlinStructure.test.ts @@ -0,0 +1,29 @@ +import { TestLanguageServiceProvider } from "src/test/TestLanguageService"; +import { KotlinStructurerProvider } from "../../../code-context/kotlin/KotlinStructurerProvider"; + +const Parser = require('web-tree-sitter'); + +describe('KotlinStructure', () => { + it('should convert a simple file to CodeFile', async () => { + const kotlinHelloWorld = `package com.example +interface Shape { + val vertexCount: Int +} + +class Rectangle(override val vertexCount: Int = 4) : Shape // Always has 4 vertices + +class Polygon : Shape { + override var vertexCount: Int = 0 // Can be set to any number later +}`; + + await Parser.init(); + const parser = new Parser(); + const languageService = new TestLanguageServiceProvider(parser); + + const structurer = new KotlinStructurerProvider(); + await structurer.init(languageService); + + const codeFile = await structurer.parseFile(kotlinHelloWorld, ''); + console.log(codeFile); + }); +}); diff --git a/vsc-extension-quickstart.md b/vsc-extension-quickstart.md deleted file mode 100644 index b2eb4a43..00000000 --- a/vsc-extension-quickstart.md +++ /dev/null @@ -1,47 +0,0 @@ -# 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 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 - -* Open the debug viewlet (`Ctrl+Shift+D` or `Cmd+Shift+D` on Mac) and from the launch configuration dropdown pick `Extension Tests`. -* Press `F5` to run the tests in a new window with your extension loaded. -* See the output of the test result in the debug console. -* Make changes to `src/test/suite/extension.test.ts` or create new test files inside the `test/suite` 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).