├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .vscode ├── launch.json └── tasks.json ├── .vscodeignore ├── LICENSE ├── README.md ├── code-clippy-demo.gif ├── code_clippy_logo.jpg ├── out ├── config.js ├── extension.js └── utils │ ├── extractGoogleResults.js │ ├── extractStackOverflowResults.js │ ├── fetchCodeCompletion.js │ ├── fetchCodeCompletions.js │ ├── fetchPageContent copy.js │ ├── fetchPageContent.js │ └── search.js ├── package.json ├── pic ├── 安装.png ├── 安装vsix.png ├── 效果.png ├── 未命名.png ├── 配置服务器.png └── 配置服务器2.png ├── scripts └── build.sh ├── server └── docker-compose.yaml ├── src ├── config.ts ├── extension.ts └── utils │ └── fetchCodeCompletions.ts ├── tsconfig.json ├── vscode.d.ts └── vscode.proposed.inlineCompletions.d.ts /.eslintignore: -------------------------------------------------------------------------------- 1 | vscode.d.ts 2 | vscode.proposed.inlineCompletions.d.ts 3 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /**@type {import('eslint').Linter.Config} */ 2 | // eslint-disable-next-line no-undef 3 | module.exports = { 4 | root: true, 5 | parser: '@typescript-eslint/parser', 6 | plugins: [ 7 | '@typescript-eslint', 8 | ], 9 | extends: [ 10 | 'eslint:recommended', 11 | 'plugin:@typescript-eslint/recommended', 12 | ], 13 | rules: { 14 | 'semi': [2, "always"], 15 | '@typescript-eslint/no-unused-vars': 0, 16 | '@typescript-eslint/no-explicit-any': 0, 17 | '@typescript-eslint/explicit-module-boundary-types': 0, 18 | '@typescript-eslint/no-non-null-assertion': 0, 19 | } 20 | }; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | node_modules 3 | .vscode-test/ 4 | *.vsix 5 | 6 | yarn.lock 7 | package-lock.json 8 | .vscode 9 | *.log 10 | test* -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [{ 8 | "name": "Run Extension", 9 | "type": "extensionHost", 10 | "request": "launch", 11 | "runtimeExecutable": "${execPath}", 12 | "args": [ 13 | "--extensionDevelopmentPath=${workspaceFolder}" 14 | ], 15 | "outFiles": [ 16 | "${workspaceFolder}/out/**/*.js" 17 | ], 18 | "preLaunchTask": "npm: watch" 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": "$tsc-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never" 13 | }, 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/test/** 4 | src/** 5 | .gitignore 6 | .yarnrc 7 | **/tsconfig.json 8 | **/.eslintrc.json 9 | **/*.map 10 | *.vsix -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Hieu Nguyen (Jack) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CodeGen plugin for vscode 2 | 3 | ## 直接安装 4 | 5 | 1. 搜索安装插件 6 | 7 | ![](pic/%E5%AE%89%E8%A3%85.png) 8 | 9 | 2. 私有化服务 10 | 11 | + [启动服务目录](./server/docker-compose.yaml) 12 | + 代码生成服务仓库见[fastgpt](https://github.com/LowinLi/fastgpt/tree/main/example/codegen) 13 | 14 | 3. 配置插件的服务地址 15 | 16 | ![](pic/%E9%85%8D%E7%BD%AE%E6%9C%8D%E5%8A%A1%E5%99%A82.png) 17 | 18 | ![](pic/%E9%85%8D%E7%BD%AE%E6%9C%8D%E5%8A%A1%E5%99%A8.png) 19 | 20 | 21 | ## 自行制作 22 | 23 | + 1.安装vsce,一个vscode插件打包工具 24 | ```bash 25 | npm install -g vsce 26 | ``` 27 | + 2.打包vsce插件 28 | ```bash 29 | vsce package 30 | ``` 31 | 32 | + 3.安装[code-insiders](https://code.visualstudio.com/insiders/) 33 | 34 | + 4.打开vscode命令行启动权限 35 | + [参考](https://blog.csdn.net/flitrue/article/details/90906578) 36 | 37 | + 5.启动vscode 38 | ```bash 39 | code-insiders --enable-proposed-api lowinli.codegen-inlineCompletions # 本地未发布插件需要命令行授权 40 | ``` 41 | 42 | + 6.安装插件 43 | 44 | ![](pic/%E5%AE%89%E8%A3%85vsix.png) 45 | 46 | + 7.配置服务器地址 47 | 48 | ![](pic/%E9%85%8D%E7%BD%AE%E6%9C%8D%E5%8A%A1%E5%99%A8.png) 49 | 50 | + 8.在线生成 51 | 52 | ![](pic/效果.png) 53 | 54 | --- 55 | + 可以直接下载制作好的[vsce](https://github.com/LowinLi/code-clippy-vscode/releases/tag/v0.0.1),跳过步骤1、步骤2 -------------------------------------------------------------------------------- /code-clippy-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LowinLi/code-clippy-vscode/e4552315706366c91985487ccfee8c9d537549e9/code-clippy-demo.gif -------------------------------------------------------------------------------- /code_clippy_logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LowinLi/code-clippy-vscode/e4552315706366c91985487ccfee8c9d537549e9/code_clippy_logo.jpg -------------------------------------------------------------------------------- /out/config.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const CSConfig = { 4 | SEARCH_PHARSE_END: ['.', ',', '{', '(', ' ', '-', '_', '+', '-', '*', '=', '/', '?', '<', '>'] 5 | }; 6 | exports.default = CSConfig; 7 | -------------------------------------------------------------------------------- /out/extension.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.activate = void 0; 13 | const vscode = require("vscode"); 14 | const config_1 = require("./config"); 15 | const fetchCodeCompletions_1 = require("./utils/fetchCodeCompletions"); 16 | // QHD: some code refer to 17 | // https://github.com/kirillpanfile/ai-autocomplete/blob/cf2de2f4a32a0aee77d040364507eeef4349838c/src/extension.js 18 | // Make an output channel for debug 19 | const print = vscode.window.createOutputChannel("codegen"); 20 | function activate(context) { 21 | const disposable = vscode.commands.registerCommand('extension.codegen-settings', () => { 22 | vscode.window.showInformationMessage('Show settings'); 23 | }); 24 | context.subscriptions.push(disposable); 25 | function sleep(ms) { 26 | return new Promise(resolve => setTimeout(resolve, ms)); 27 | } 28 | let lastRequest = null; 29 | const provider = { 30 | provideInlineCompletionItems: (document, position, context, token) => __awaiter(this, void 0, void 0, function* () { 31 | // Grab the api key from the extension's config 32 | const configuration = vscode.workspace.getConfiguration('', document.uri); 33 | const API_KEY = configuration.get("conf.resource.codegen", "http://localhost:8000/api/codegen"); 34 | const OUTPUT_MAX_LENGTH = configuration.get("conf.resource.output_max_length", "18"); 35 | // on request last change 36 | let requestId = new Date().getTime(); 37 | lastRequest = requestId; 38 | yield sleep(1000); 39 | if (lastRequest !== requestId) { 40 | return { items: [] }; 41 | } 42 | vscode.comments.createCommentController; 43 | const textBeforeCursor = document.getText(); 44 | if (textBeforeCursor.trim() === "") { 45 | return { items: [] }; 46 | } 47 | const currLineBeforeCursor = document.getText(new vscode.Range(position.with(undefined, 0), position)); 48 | // Check if user's state meets one of the trigger criteria 49 | if (config_1.default.SEARCH_PHARSE_END.includes(textBeforeCursor[textBeforeCursor.length - 1]) || currLineBeforeCursor.trim() === "") { 50 | let rs = null; 51 | try { 52 | // Fetch the code completion based on the text in the user's document 53 | rs = yield (0, fetchCodeCompletions_1.fetchCodeCompletionTexts)(textBeforeCursor, API_KEY, OUTPUT_MAX_LENGTH); 54 | } 55 | catch (err) { 56 | if (err instanceof Error) { 57 | vscode.window.showErrorMessage(err.toString()); 58 | } 59 | return { items: [] }; 60 | } 61 | if (!rs) { 62 | return { items: [] }; 63 | } 64 | // Add the generated code to the inline suggestion list 65 | const items = new Array(); 66 | for (const text of rs.completions) { 67 | const insertText = text.replace(/\\n/g, '\n'); 68 | items.push({ 69 | insertText, 70 | range: new vscode.Range(position.translate(0, text.length), position), 71 | }); 72 | } 73 | print.appendLine(JSON.stringify(items)); 74 | return { items }; 75 | } 76 | return { items: [] }; 77 | }), 78 | }; 79 | vscode.languages.registerInlineCompletionItemProvider({ pattern: "**" }, provider); 80 | } 81 | exports.activate = activate; 82 | -------------------------------------------------------------------------------- /out/utils/extractGoogleResults.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.extractGoogleResults = void 0; 4 | const config_1 = require("../config"); 5 | const fetchPageContent_1 = require("./fetchPageContent"); 6 | // Get search results from google, then return a list of stackoverflow links 7 | function extractGoogleResults(keyword) { 8 | return new Promise((resolve, reject) => { 9 | return fetchPageContent_1.fetchPageTextContent(`${config_1.default.SEARCH_ENDPOINT}${keyword.replace(/\s/, '+')}`) 10 | .then(rs => { 11 | let urls = rs.textContent.match(/(https:\/\/stackoverflow.com\/[a-z0-9-/]+)/g); 12 | urls && (urls = urls.filter((url, i, list) => list.indexOf(url) === i)); 13 | resolve(urls); 14 | }) 15 | .catch(reject); 16 | }); 17 | } 18 | exports.extractGoogleResults = extractGoogleResults; 19 | -------------------------------------------------------------------------------- /out/utils/extractStackOverflowResults.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.extractSnippetResults = void 0; 4 | const jsdom_1 = require("jsdom"); 5 | // Extract and sort stackoverflow answers 6 | function extractSnippetResults(options) { 7 | var doc = new jsdom_1.JSDOM(options.textContent); 8 | let answersWithCodeBlock = Array.from(doc.window.document.querySelectorAll(".answer")) 9 | .filter((item) => item.querySelector("code") != null); 10 | let results = answersWithCodeBlock 11 | .map((item) => ({ 12 | textContent: item.textContent, 13 | votes: parseInt(item.querySelector(".js-vote-count").textContent), 14 | // TODO: Handle answers with more than one code block 15 | // p/s: they often about explaining the something 16 | code: item.querySelector("code").textContent, 17 | sourceURL: item.querySelector(".js-share-link").href, 18 | hasCheckMark: item.querySelector("iconCheckmarkLg") != null 19 | })) 20 | .filter(item => isCodeValid(item.code)); 21 | results.sort(sortSnippetResultFn); 22 | return { url: options.url, results }; 23 | } 24 | exports.extractSnippetResults = extractSnippetResults; 25 | function sortSnippetResultFn(a, b) { 26 | if (a.hasCheckMark != b.hasCheckMark) { 27 | return a.hasCheckMark ? 1 : -1; 28 | } 29 | let result = b.votes - a.votes; 30 | return result === 0 ? b.code.length - a.code.length : result; 31 | } 32 | // Check whether the input should be considered as code input or random text 33 | function isCodeValid(input) { 34 | // This is just a temporary solution, 35 | // it would filter codes that are too short 36 | return input.length > 12; 37 | } 38 | -------------------------------------------------------------------------------- /out/utils/fetchCodeCompletion.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.fetchCodeCompletionText = void 0; 4 | const node_fetch_1 = require("node-fetch"); 5 | const API_URL = "https://api-inference.huggingface.co/models/flax-community/gpt-neo-125M-code-clippy-dedup-2048"; 6 | function fetchCodeCompletionText(prompt, API_KEY) { 7 | // Setup header with API key 8 | // eslint-disable-next-line @typescript-eslint/naming-convention 9 | const headers = { "Authorization": `Bearer ${API_KEY}` }; 10 | return new Promise((resolve, reject) => { 11 | // Send post request to inference API 12 | return node_fetch_1.default(API_URL, { 13 | method: "post", 14 | body: JSON.stringify({ 15 | "inputs": prompt, "parameters": { 16 | "max_new_tokens": 16, "return_full_text": false, 17 | "do_sample": true, "temperature": 0.8, 18 | "max_time": 5.0, "num_return_sequences": 3, 19 | // "use_gpu": true 20 | } 21 | }), 22 | headers: headers 23 | }) 24 | .then(res => res.json()) 25 | .then(json => { 26 | if (Array.isArray(json)) { 27 | const generations = Array(); 28 | for (let i = 0; i < json.length; i++) { 29 | generations.push(json[i].generated_text.trimStart()); 30 | resolve({ generations }); 31 | } 32 | } 33 | else { 34 | console.log(json); 35 | throw new Error(json["error"]); 36 | } 37 | }) 38 | .catch(err => reject(err)); 39 | }); 40 | } 41 | exports.fetchCodeCompletionText = fetchCodeCompletionText; 42 | // def incr_list(l: list): 43 | // """Return list with elements incremented by 1. 44 | // >>> incr_list([1, 2, 3]) 45 | // [2, 3, 4] 46 | // >>> incr_list([5, 3, 5, 2, 3, 3, 9, 0, 123]) 47 | // [6, 4, 6, 3, 4, 4, 10, 1, 124] 48 | // """ 49 | -------------------------------------------------------------------------------- /out/utils/fetchCodeCompletions.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.fetchCodeCompletionTexts = void 0; 13 | const node_fetch_1 = require("node-fetch"); 14 | // const API_URL = 'http://localhost:8000/api/codegen' 15 | const headers = { "Content-Type": "application/json" }; 16 | function fetchCodeCompletionTexts(prompt, API_URL, OUTPUT_MAX_LENGTH) { 17 | return __awaiter(this, void 0, void 0, function* () { 18 | // Send post request to inference API 19 | const res = yield (0, node_fetch_1.default)(API_URL, { 20 | method: "post", 21 | body: JSON.stringify({ 22 | "inputs": prompt, 23 | "parameters": { 24 | "output_max_length": Number(OUTPUT_MAX_LENGTH) 25 | } 26 | }), 27 | headers: headers 28 | }); 29 | const json = yield res.json(); 30 | if (Array.isArray(json)) { 31 | const completions = Array(); 32 | for (let i = 0; i < json.length; i++) { 33 | const completion = json[i].generated_text.trimStart(); 34 | if (completion.trim() === "") 35 | continue; 36 | completions.push(completion); 37 | } 38 | console.log(completions); 39 | return { completions }; 40 | } 41 | else { 42 | throw new Error(json["error"]); 43 | } 44 | }); 45 | } 46 | exports.fetchCodeCompletionTexts = fetchCodeCompletionTexts; 47 | -------------------------------------------------------------------------------- /out/utils/fetchPageContent copy.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.fetchPageTextContent = void 0; 4 | const node_fetch_1 = require("node-fetch"); 5 | function fetchPageTextContent(url) { 6 | return new Promise((resolve, reject) => { 7 | return node_fetch_1.default(url) 8 | .then(rs => rs.text()) 9 | .then(textContent => resolve({ textContent, url })) 10 | .catch(reject); 11 | }); 12 | } 13 | exports.fetchPageTextContent = fetchPageTextContent; 14 | -------------------------------------------------------------------------------- /out/utils/fetchPageContent.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.fetchPageTextContent = void 0; 4 | const node_fetch_1 = require("node-fetch"); 5 | function fetchPageTextContent(url) { 6 | return new Promise((resolve, reject) => { 7 | return node_fetch_1.default(url) 8 | .then(rs => rs.text()) 9 | .then(textContent => resolve({ textContent, url })) 10 | .catch(reject); 11 | }); 12 | } 13 | exports.fetchPageTextContent = fetchPageTextContent; 14 | -------------------------------------------------------------------------------- /out/utils/search.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.search = void 0; 13 | const extractGoogleResults_1 = require("./extractGoogleResults"); 14 | const extractStackOverflowResults_1 = require("./extractStackOverflowResults"); 15 | const fetchPageContent_1 = require("./fetchPageContent"); 16 | // Send search query to google, get answers from stackoverflow 17 | // then extract and return code results 18 | function search(keyword) { 19 | return __awaiter(this, void 0, void 0, function* () { 20 | return new Promise((resolve, reject) => { 21 | extractGoogleResults_1.extractGoogleResults(keyword) 22 | .then((urls) => __awaiter(this, void 0, void 0, function* () { 23 | if (urls === null) { 24 | return Promise.resolve(null); 25 | } 26 | let results = []; 27 | try { 28 | let fetchResult; 29 | for (const i in urls.splice(0, 6)) { 30 | if (urls[i]) { 31 | fetchResult = yield fetchPageContent_1.fetchPageTextContent(urls[i]); 32 | results = results.concat(extractStackOverflowResults_1.extractSnippetResults(fetchResult).results); 33 | } 34 | } 35 | resolve({ results }); 36 | } 37 | catch (err) { 38 | reject(err); 39 | } 40 | })).catch(reject); 41 | }); 42 | }); 43 | } 44 | exports.search = search; 45 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "codegen-inlineCompletions", 3 | "displayName": "Code Gen", 4 | "description": "codegen inline completions", 5 | "version": "0.0.1", 6 | "publisher": "lowinli", 7 | "icon": "code_clippy_logo.jpg", 8 | "repository": "https://github.com/LowinLi/code-clippy-vscode/", 9 | "engines": { 10 | "vscode": "^1.34.0" 11 | }, 12 | "license": "MIT", 13 | "categories": [ 14 | "Other" 15 | ], 16 | "activationEvents": [ 17 | "onLanguage:python" 18 | ], 19 | "main": "./out/extension.js", 20 | "contributes": { 21 | "commands": [ 22 | { 23 | "command": "extension.codegen-settings", 24 | "title": "Code Clippy Settings" 25 | } 26 | ], 27 | "menus": { 28 | "editor/inlineCompletions/actions": [ 29 | { 30 | "command": "extension.codegen-settings" 31 | } 32 | ] 33 | }, 34 | "configuration": { 35 | "title": "Code Clippy Configuration", 36 | "properties": { 37 | "conf.resource.codegen": { 38 | "type": "string", 39 | "default": "http://localhost:8000/generate_mono", 40 | "description": "Model api address.", 41 | "scope": "resource" 42 | }, 43 | "conf.resource.output_max_length": { 44 | "type": "string", 45 | "default": "18", 46 | "description": "generate output max length.", 47 | "scope": "resource" 48 | } 49 | } 50 | } 51 | }, 52 | "scripts": { 53 | "vscode:prepublish": "npm run compile", 54 | "compile": "tsc -p ./", 55 | "lint": "eslint . --ext .ts,.tsx", 56 | "watch": "tsc -watch -p ./", 57 | "download-api": "vscode-dts dev", 58 | "postdownload-api": "vscode-dts main", 59 | "postinstall": "npm run download-api" 60 | }, 61 | "devDependencies": { 62 | "@types/node": "^12.12.0", 63 | "@types/node-fetch": "^2.5.10", 64 | "@typescript-eslint/eslint-plugin": "^4.16.0", 65 | "@typescript-eslint/parser": "^4.16.0", 66 | "eslint": "^7.21.0", 67 | "typescript": "^4.2.2", 68 | "vscode-dts": "^0.3.1" 69 | }, 70 | "dependencies": { 71 | "@types/jsdom": "^16.2.12", 72 | "jsdom": "^16.6.0", 73 | "node-fetch": "^2.6.1" 74 | } 75 | } -------------------------------------------------------------------------------- /pic/安装.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LowinLi/code-clippy-vscode/e4552315706366c91985487ccfee8c9d537549e9/pic/安装.png -------------------------------------------------------------------------------- /pic/安装vsix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LowinLi/code-clippy-vscode/e4552315706366c91985487ccfee8c9d537549e9/pic/安装vsix.png -------------------------------------------------------------------------------- /pic/效果.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LowinLi/code-clippy-vscode/e4552315706366c91985487ccfee8c9d537549e9/pic/效果.png -------------------------------------------------------------------------------- /pic/未命名.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LowinLi/code-clippy-vscode/e4552315706366c91985487ccfee8c9d537549e9/pic/未命名.png -------------------------------------------------------------------------------- /pic/配置服务器.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LowinLi/code-clippy-vscode/e4552315706366c91985487ccfee8c9d537549e9/pic/配置服务器.png -------------------------------------------------------------------------------- /pic/配置服务器2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LowinLi/code-clippy-vscode/e4552315706366c91985487ccfee8c9d537549e9/pic/配置服务器2.png -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | vsce package 4 | 5 | -------------------------------------------------------------------------------- /server/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "2.3" 2 | services: 3 | fastgpt-codegen: 4 | container_name: fastgpt-codegen 5 | image: lowinli98/fastgpt-codegen:v0.0.7 6 | expose: 7 | - 7104 8 | ports: 9 | - "7104:7104" 10 | environment: 11 | - PORT=7104 12 | - GUNICORN_WORKER=1 13 | - GUNICORN_THREADS=1 14 | restart: always -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | const CSConfig = { 2 | SEARCH_PHARSE_END: ['.', ',', '{', '(', ' ', '-', '_', '+', '-', '*', '=', '/', '?', '<', '>'] 3 | } 4 | 5 | export default CSConfig -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | import CSConfig from './config' 3 | import { fetchCodeCompletionTexts } from './utils/fetchCodeCompletions' 4 | 5 | // QHD: some code refer to 6 | // https://github.com/kirillpanfile/ai-autocomplete/blob/cf2de2f4a32a0aee77d040364507eeef4349838c/src/extension.js 7 | 8 | // Make an output channel for debug 9 | const print = vscode.window.createOutputChannel("codegen") 10 | 11 | export function activate(context: vscode.ExtensionContext) { 12 | const disposable = vscode.commands.registerCommand( 13 | 'extension.codegen-settings', 14 | () => { 15 | vscode.window.showInformationMessage('Show settings') 16 | } 17 | ) 18 | 19 | context.subscriptions.push(disposable) 20 | 21 | 22 | function sleep(ms: number) { 23 | return new Promise(resolve => setTimeout(resolve, ms)) 24 | } 25 | 26 | let lastRequest = null 27 | 28 | const provider: vscode.InlineCompletionItemProvider = { 29 | provideInlineCompletionItems: async (document, position, context, token) => { 30 | // Grab the api key from the extension's config 31 | const configuration = vscode.workspace.getConfiguration('', document.uri) 32 | const API_KEY = configuration.get("conf.resource.codegen", "http://localhost:8000/api/codegen") 33 | const OUTPUT_MAX_LENGTH = configuration.get("conf.resource.output_max_length", "18") 34 | 35 | // on request last change 36 | let requestId = new Date().getTime() 37 | lastRequest = requestId 38 | await sleep(1000) 39 | if (lastRequest !== requestId) { 40 | return { items: [] } 41 | } 42 | 43 | vscode.comments.createCommentController 44 | const textBeforeCursor = document.getText() 45 | if (textBeforeCursor.trim() === "") { 46 | return { items: [] } 47 | } 48 | 49 | const currLineBeforeCursor = document.getText( 50 | new vscode.Range(position.with(undefined, 0), position) 51 | ) 52 | 53 | // Check if user's state meets one of the trigger criteria 54 | if (CSConfig.SEARCH_PHARSE_END.includes(textBeforeCursor[textBeforeCursor.length - 1]) || currLineBeforeCursor.trim() === "") { 55 | let rs = null 56 | 57 | try { 58 | // Fetch the code completion based on the text in the user's document 59 | rs = await fetchCodeCompletionTexts(textBeforeCursor, API_KEY, OUTPUT_MAX_LENGTH) 60 | } catch (err) { 61 | if (err instanceof Error) { 62 | vscode.window.showErrorMessage(err.toString()) 63 | } 64 | return { items: [] } 65 | } 66 | 67 | if (!rs) { 68 | return { items: [] } 69 | } 70 | 71 | // Add the generated code to the inline suggestion list 72 | const items = new Array() 73 | for (const text of rs.completions) { 74 | const insertText = text.replace(/\\n/g, '\n'); 75 | items.push({ 76 | insertText, 77 | range: new vscode.Range(position.translate(0, text.length), position), 78 | }) 79 | } 80 | print.appendLine(JSON.stringify(items)) 81 | return { items } 82 | } 83 | return { items: [] } 84 | }, 85 | } 86 | 87 | vscode.languages.registerInlineCompletionItemProvider({ pattern: "**" }, provider) 88 | } 89 | -------------------------------------------------------------------------------- /src/utils/fetchCodeCompletions.ts: -------------------------------------------------------------------------------- 1 | import fetch from "node-fetch"; 2 | 3 | export type FetchCodeCompletions = { 4 | completions: Array 5 | } 6 | 7 | // const API_URL = 'http://localhost:8000/api/codegen' 8 | const headers = { "Content-Type": "application/json" } 9 | 10 | export async function fetchCodeCompletionTexts(prompt: string, API_URL: string, OUTPUT_MAX_LENGTH: string): Promise { 11 | // Send post request to inference API 12 | const res = await fetch(API_URL, { 13 | method: "post", 14 | body: JSON.stringify({ 15 | "inputs": prompt, 16 | "parameters": { 17 | "output_max_length": Number(OUTPUT_MAX_LENGTH) 18 | } 19 | }), 20 | headers: headers 21 | }) 22 | const json = await res.json() 23 | if (Array.isArray(json)) { 24 | const completions = Array() 25 | for (let i=0; i < json.length; i++) { 26 | const completion = json[i].generated_text.trimStart() 27 | if (completion.trim() === "") continue 28 | 29 | completions.push( 30 | completion 31 | ) 32 | } 33 | console.log(completions) 34 | return { completions } 35 | } 36 | else { 37 | throw new Error(json["error"]) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "lib": ["es6"], 6 | "outDir": "out", 7 | "sourceMap": false, 8 | "strict": true, 9 | "rootDir": "src", 10 | "skipDefaultLibCheck": true, 11 | "skipLibCheck": true 12 | }, 13 | "exclude": ["node_modules", ".vscode-test"] 14 | } 15 | -------------------------------------------------------------------------------- /vscode.proposed.inlineCompletions.d.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | declare module 'vscode' { 7 | 8 | // https://github.com/microsoft/vscode/issues/124024 @hediet @alexdima 9 | 10 | export namespace languages { 11 | /** 12 | * Registers an inline completion provider. 13 | * 14 | * @return A {@link Disposable} that unregisters this provider when being disposed. 15 | */ 16 | // TODO@API what are the rules when multiple providers apply 17 | export function registerInlineCompletionItemProvider(selector: DocumentSelector, provider: InlineCompletionItemProvider): Disposable; 18 | } 19 | 20 | export interface InlineCompletionItemProvider { 21 | /** 22 | * Provides inline completion items for the given position and document. 23 | * If inline completions are enabled, this method will be called whenever the user stopped typing. 24 | * It will also be called when the user explicitly triggers inline completions or asks for the next or previous inline completion. 25 | * Use `context.triggerKind` to distinguish between these scenarios. 26 | */ 27 | provideInlineCompletionItems(document: TextDocument, position: Position, context: InlineCompletionContext, token: CancellationToken): ProviderResult | T[]>; 28 | } 29 | 30 | export interface InlineCompletionContext { 31 | /** 32 | * How the completion was triggered. 33 | */ 34 | readonly triggerKind: InlineCompletionTriggerKind; 35 | 36 | /** 37 | * Provides information about the currently selected item in the autocomplete widget if it is visible. 38 | * 39 | * If set, provided inline completions must extend the text of the selected item 40 | * and use the same range, otherwise they are not shown as preview. 41 | * As an example, if the document text is `console.` and the selected item is `.log` replacing the `.` in the document, 42 | * the inline completion must also replace `.` and start with `.log`, for example `.log()`. 43 | * 44 | * Inline completion providers are requested again whenever the selected item changes. 45 | * 46 | * The user must configure `"editor.suggest.preview": true` for this feature. 47 | */ 48 | readonly selectedCompletionInfo: SelectedCompletionInfo | undefined; 49 | } 50 | 51 | // TODO@API remove kind, snippet properties 52 | // TODO@API find a better name, xyzFilter, xyzConstraint 53 | export interface SelectedCompletionInfo { 54 | range: Range; 55 | text: string; 56 | 57 | 58 | completionKind: CompletionItemKind; 59 | isSnippetText: boolean; 60 | } 61 | 62 | /** 63 | * How an {@link InlineCompletionItemProvider inline completion provider} was triggered. 64 | */ 65 | // TODO@API align with CodeActionTriggerKind 66 | // (1) rename Explicit to Invoke 67 | // (2) swap order of Invoke and Automatic 68 | export enum InlineCompletionTriggerKind { 69 | /** 70 | * Completion was triggered automatically while editing. 71 | * It is sufficient to return a single completion item in this case. 72 | */ 73 | Automatic = 0, 74 | 75 | /** 76 | * Completion was triggered explicitly by a user gesture. 77 | * Return multiple completion items to enable cycling through them. 78 | */ 79 | Explicit = 1, 80 | } 81 | 82 | /** 83 | * @deprecated Return an array of Inline Completion items directly. Will be removed eventually. 84 | */ 85 | // TODO@API We could keep this and allow for `vscode.Command` instances that explain 86 | // the result. That would replace the existing proposed menu-identifier and be more LSP friendly 87 | // TODO@API maybe use MarkdownString 88 | export class InlineCompletionList { 89 | items: T[]; 90 | 91 | // command: Command; "Show More..." 92 | 93 | // description: MarkdownString 94 | 95 | /** 96 | * @deprecated Return an array of Inline Completion items directly. Will be removed eventually. 97 | */ 98 | constructor(items: T[]); 99 | } 100 | 101 | export class InlineCompletionItem { 102 | /** 103 | * The text to replace the range with. Must be set. 104 | * Is used both for the preview and the accept operation. 105 | * 106 | * The text the range refers to must be a subword of this value (`AB` and `BEF` are subwords of `ABCDEF`, but `Ab` is not). 107 | * Additionally, if possible, it should be a prefix of this value for a better user-experience. 108 | * 109 | * However, any indentation of the text to replace does not matter for the subword constraint. 110 | * Thus, ` B` can be replaced with ` ABC`, effectively removing a whitespace and inserting `A` and `C`. 111 | */ 112 | insertText?: string | SnippetString; 113 | 114 | /** 115 | * @deprecated Use `insertText` instead. Will be removed eventually. 116 | */ 117 | text?: string; 118 | 119 | /** 120 | * The range to replace. 121 | * Must begin and end on the same line. 122 | * 123 | * Prefer replacements over insertions to avoid cache invalidation: 124 | * Instead of reporting a completion that inserts an extension at the end of a word, 125 | * the whole word (or even the whole line) should be replaced with the extended word (or extended line) to improve the UX. 126 | * That way, when the user presses backspace, the cache can be reused and there is no flickering. 127 | */ 128 | range?: Range; 129 | 130 | /** 131 | * An optional {@link Command} that is executed *after* inserting this completion. 132 | */ 133 | command?: Command; 134 | 135 | constructor(insertText: string, range?: Range, command?: Command); 136 | } 137 | 138 | 139 | // TODO@API move "never" API into new proposal 140 | 141 | export interface InlineCompletionItem { 142 | /** 143 | * If set to `true`, unopened closing brackets are removed and unclosed opening brackets are closed. 144 | * Defaults to `false`. 145 | */ 146 | completeBracketPairs?: boolean; 147 | } 148 | 149 | /** 150 | * Be aware that this API will not ever be finalized. 151 | */ 152 | export namespace window { 153 | // TODO@API move into provider (just like internal API). Only read property if proposal is enabled! 154 | export function getInlineCompletionItemController(provider: InlineCompletionItemProvider): InlineCompletionController; 155 | } 156 | 157 | /** 158 | * Be aware that this API will not ever be finalized. 159 | */ 160 | export interface InlineCompletionController { 161 | /** 162 | * Is fired when an inline completion item is shown to the user. 163 | */ 164 | // eslint-disable-next-line vscode-dts-event-naming 165 | readonly onDidShowCompletionItem: Event>; 166 | } 167 | 168 | /** 169 | * Be aware that this API will not ever be finalized. 170 | */ 171 | export interface InlineCompletionItemDidShowEvent { 172 | completionItem: T; 173 | } 174 | } 175 | --------------------------------------------------------------------------------