Compare commits

...

41 Commits
0.1.7 ... main

Author SHA1 Message Date
Falko Victor Habel d1ccb89544 updated link 2024-10-09 20:56:46 +02:00
Falko Victor Habel c0af28ee1e Merge pull request 'updateded docs' (#7) from docs into main
Reviewed-on: #7
2024-10-09 18:50:55 +00:00
Falko Victor Habel 89b179bbe3 Merge pull request 'develop' (#6) from develop into main
Reviewed-on: #6
2024-10-09 18:50:25 +00:00
Falko Victor Habel a7292cdcea Final Version 0.2.0 2024-10-09 20:50:11 +02:00
Falko Victor Habel 2233f4ec63 updateded docs 2024-10-09 20:47:51 +02:00
Falko Victor Habel 12f411fbac Merge pull request 'preview_lines' (#4) from preview_lines into develop
Reviewed-on: #4
2024-10-09 18:00:32 +00:00
Falko Victor Habel a3bf2f93bb Final Version. Everything is working; atleast for Python🥳 2024-10-09 19:58:21 +02:00
Falko Victor Habel c1d0a53720 oonly remaining bug: backspace is sometimes still triggerable 2024-10-09 19:51:12 +02:00
Falko Victor Habel e02296c5a7 only ehader is to much 2024-10-09 16:05:40 +02:00
Falko Victor Habel 621db41722 updated to version 2.0 2024-10-09 15:22:51 +02:00
Falko Victor Habel e234969638 Merge pull request 'feat/preview' (#3) from feat/preview into develop
Reviewed-on: #3
2024-10-09 12:27:58 +00:00
Falko Victor Habel 439a538c7e bug fix for lines add extra lines before inserting 2024-10-09 14:24:53 +02:00
Falko Victor Habel a56d47747d only rows are not working yet 2024-10-09 14:18:33 +02:00
Falko Victor Habel 96a3971b71 preview added 2024-10-09 08:10:02 +02:00
Falko Victor Habel 424cc124b2 omg this is working 2024-10-08 19:07:50 +02:00
Falko Victor Habel dcb5a3bbdf not enough lines between preview and final 2024-10-08 11:17:17 +02:00
Falko Victor Habel f6ea8494a7 accept and decline is working 2024-10-07 09:22:02 +02:00
Falko Victor Habel f122d99ba1 code now works with everything except tab 2024-10-05 09:18:22 +03:00
Falko Victor Habel 804a3113e4 [wip] preview is semi working, i need to jump to the end off the inserted content and extra lines needs to be generated 2024-10-04 19:00:47 +03:00
Falko Victor Habel 4c5eb334df remove test file 2024-10-04 18:26:16 +03:00
Falko Victor Habel d8e97c2b4e stable version 0.2.0, now providing preview pressing double tab to accept preview 2024-10-04 18:26:01 +03:00
Falko Victor Habel b6241855fc bug in approving 2024-10-04 13:00:14 +03:00
Falko Victor Habel ccd3528325 minor incosnitentcy with removing 2024-10-03 12:41:27 +03:00
Falko Victor Habel 77e328fcee near final 2024-10-03 12:20:38 +03:00
Falko Victor Habel 6a953b7a12 preview extra lines 2024-10-03 12:03:05 +03:00
Falko Victor Habel ffd22f7cc6 fixed 2024-10-03 11:43:47 +03:00
Falko Victor Habel 0416896254 fixed bug for showing header 2024-10-02 15:48:08 +03:00
Falko Victor Habel 9109c199e6 so preview and complition is technially working, but it overwrites in preview mode the current code 2024-10-02 15:40:11 +03:00
Falko Victor Habel 8e72d08d53 the preview is now working in one row, without the context 2024-09-23 15:17:23 +02:00
Falko Victor Habel 2a46e061d3 Updated to show now also the used contet in preview (small step progress) 2024-09-23 14:17:23 +02:00
Falko Victor Habel 27571fcad8 still only single line 2024-09-23 13:25:12 +02:00
Falko Victor Habel 66c88a053b having now switech to use tab to complete completion, but the preview itself is still a mess 2024-09-23 09:13:03 +02:00
Falko Victor Habel ac7afb4b4e step back 2024-09-11 11:28:55 +02:00
Falko Victor Habel fb6e9a5d1f new approach 2024-09-11 09:58:26 +02:00
Falko Victor Habel 678c4823b3 Merge pull request 'preview_develop' (#2) from preview_develop into develop
Reviewed-on: fabel/Fabelous-Autocoder#2
2024-09-11 07:55:18 +00:00
Falko Victor Habel 77e0dbc048 not working multi line support 2024-09-11 09:54:28 +02:00
Falko Victor Habel 8ac3879ee0 WORKING only one insertion 2024-09-11 09:49:17 +02:00
Falko Victor Habel 0450a222e2 added semi working preview 2024-09-11 09:41:59 +02:00
Falko Victor Habel 916ea8ca4a new code with partically work preview 2024-09-10 20:34:10 +02:00
Falko Victor Habel 58c9af0256 preview added but badly 2024-09-10 19:51:03 +02:00
Falko Victor Habel 91d13d36df added preview 2024-09-10 16:39:33 +02:00
5 changed files with 470 additions and 344 deletions

1
2exe.txt Normal file
View File

@ -0,0 +1 @@
vsce package --baseContentUrl https://gitea.fabelous.app/fabel/Fabelous-Autocoder/src/branch/main --baseImagesUrl https://gitea.fabelous.app/fabel/Fabelous-Autocoder/src/branch/main

102
README.md
View File

@ -1,76 +1,58 @@
# Fabelous Autocoder # Fabelous Autocoder
Fabelous Autocoder is a Visual Studio Code extension that provides an easy-to-use interface for Ollama autocompletion. This extension allows developers to use Ollama's powerful language models to generate code completions as they type. It is highly customizable, allowing users to configure various settings to fit their needs. Fabelous Autocoder is a powerful VS Code extension that provides intelligent code completion using advanced language models. It offers seamless integration with your development workflow, allowing you to generate and preview code suggestions with ease.
![Fabelous Autocoder in Action](demo.gif)
## Features ## Features
- Autocompletion using Ollama language models - **Intelligent Code Completion**: Leverages advanced language models to provide context-aware code suggestions.
- Customizable completion keys - **Preview Functionality**: View generated code completions before accepting them.
- Inline preview of generated completions - **Easy Accept/Decline**: Use simple keyboard shortcuts to accept or decline suggestions.
- Configurable maximum tokens predicted - **Customizable**: Configure various parameters like API endpoint, model, and response behavior.
- Configurable prompt window size - **Language Agnostic**: Works with multiple programming languages.
- Configurable response preview delay
- Configurable temperature for the model
## Installation ## How It Works
You can also download the extension from the release tab of the following Git repository:
[Fabelous-Autocoder Git Repository](https://gitea.fabelous.app/fabel/Fabelous-Autocoder.git) 1. Trigger the autocompletion by typing a completion key (configurable, default is space).
2. The extension sends your current code context to the configured API.
To do so, follow these steps: 3. A code completion is generated and displayed as a preview.
4. Accept the completion with `Tab` or decline it with `Backspace`.
1. Visit the repository link.
2. Click on the "Releases" tab.
3. Look for the latest release and click on it.
4. Download the extension file compatible with your operating system.
5. Install the extension manually in Visual Studio Code.
After installation, you'll be able to use the Fabelous Autocoder extension in your Visual Studio Code environment.
## Configuration
Fabelous Autocoder is highly customizable, allowing users to configure various settings to fit their needs. To access the configuration settings, follow these steps:
1. Open Visual Studio Code
2. Click on the Settings icon on the sidebar (or press `Ctrl+,`)
3. Search for "Fabelous Autocoder" in the search bar
4. Configure the desired settings
Here are some of the available configuration options:
- `fabelous-autocoder.endpoint`: The endpoint of the Ollama REST API
- `fabelous-autocoder.authentication`: The authentication token for Ollama
- `fabelous-autocoder.model`: The model to use for generating completions
- `fabelous-autocoder.max tokens predicted`: The maximum number of tokens generated by the model
- `fabelous-autocoder.prompt window size`: The size of the prompt in characters
- `fabelous-autocoder.completion keys`: The characters that trigger the autocompletion item provider
- `fabelous-autocoder.response preview`: Whether to show a preview of the generated completion inline
- `fabelous-autocoder.preview max tokens`: The maximum number of tokens generated for the response preview
- `fabelous-autocoder.preview delay`: The time to wait before starting inline preview generation
- `fabelous-autocoder.continue inline`: Whether to continue autocompletion after the inline preview
- `fabelous-autocoder.temperature`: The temperature of the model
- `fabelous-autocoder.keep alive`: The time in minutes before Ollama unloads the model
Note that changing the `completion keys` setting requires a reload of Visual Studio Code.
## Usage ## Usage
To use Fabelous Autocoder, simply start typing in the editor. When the configured completion keys are pressed, the extension will generate a completion using the configured Ollama model. The completion will be displayed inline with a preview of the generated code. If the `continue inline` setting is enabled, the extension will continue generating completions after the inline preview. ![Fabelous Autocoder Showcase](demo.gif)
To generate a multi-line completion, press `Enter` after the inline preview. This will open a new editor with the generated completion. 1. **Trigger Completion**: Type normally and hit the completion key (space by default).
2. **Preview**: The suggested completion appears in light gray text.
3. **Accept**: Press `Tab` to accept the entire suggestion.
4. **Decline**: Press `Backspace` to remove the preview and decline the suggestion.
5. **Partial Accept**: You can continue typing to partially accept the suggestion.
To customize the behavior of the extension, see the Configuration section above. ## Configuration
## License Customize Fabelous Autocoder through VS Code settings:
Fabelous Autocoder is licensed under the CC BY-ND 4.0 license. See the [LICENSE](https://gitea.fabelous.app/fabel/Fabelous-Autocoder/src/branch/main/LICENSE) file for more information. - `fabelous-autocoder.endpoint`: API endpoint for the language model.
- `fabelous-autocoder.model`: Specify the model to use.
- `fabelous-autocoder.temperature`: Control the randomness of completions.
- `fabelous-autocoder.max tokens predicted`: Set the maximum length of completions.
- `fabelous-autocoder.prompt window size`: Adjust the context window size.
- `fabelous-autocoder.completion keys`: Set custom completion trigger keys.
- `fabelous-autocoder.response preview`: Toggle preview functionality.
- `fabelous-autocoder.preview max tokens`: Limit preview length.
- `fabelous-autocoder.preview delay`: Add delay before showing preview.
- `fabelous-autocoder.continue inline`: Control inline continuation behavior.
## Acknowledgments ## Installation
Fabelous Autocoder was created by [Falko Habel](https://gitea.fabelous.app/fabel). It was inspired by the [Ollama](https://ollama.ai) project.
1. [Click here to download the latest version](https://gitea.fabelous.app/Fabel/Fabelous-Autocoder/releases/download/latest/fabelous-autocoder-0.2.0.vsix)
2. Open Visual Studio Code
3. Go to Extensions (Ctrl+Shift+X)
4. Click on the three dots in the upper-right corner and select "Install from VSIX..."
5. Navigate to the location where you extracted Fabelous Autocoder and select the .vsix file
6. Click "Install" to install the extension
## Requirements
- VS Code version 1.89.0 or higher
- Internet connection for API calls

BIN
demo.gif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 213 KiB

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

@ -1,136 +1,157 @@
{ {
"name": "fabelous-autocoder", "name": "fabelous-autocoder",
"version": "0.1.7", "version": "0.2.0",
"displayName": "Fabelous Autocoder", "displayName": "Fabelous Autocoder",
"description": "A simple to use Ollama autocompletion Plugin", "description": "A simple to use Ollama autocompletion Plugin",
"icon": "icon.png", "icon": "icon.png",
"publisher": "fabel", "publisher": "Falko Habel",
"license": "CC BY-ND 4.0", "license": "CC BY-ND 4.0",
"bugs": { "bugs": {
"url": "https://gitea.fabelous.app/fabel/Fabelous-Autocoder/issues" "url": "https://gitea.fabelous.app/fabel/Fabelous-Autocoder/issues"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://gitea.fabelous.app/fabel/Fabelous-Autocoder.git" "url": "https://gitea.fabelous.app/fabel/Fabelous-Autocoder.git"
}, },
"engines": { "engines": {
"vscode": "^1.89.0" "vscode": "^1.89.0"
}, },
"categories": [ "categories": [
"Machine Learning", "Machine Learning",
"Snippets", "Snippets",
"Programming Languages" "Programming Languages"
], ],
"keywords": [ "keywords": [
"ollama", "ollama",
"coding", "coding",
"autocomplete", "autocomplete",
"open source", "open source",
"assistant", "assistant",
"ai", "ai",
"llm" "llm"
], ],
"galleryBanner": { "galleryBanner": {
"color": "#133773" "color": "#133773"
}, },
"activationEvents": [ "activationEvents": [
"onStartupFinished" "onStartupFinished"
], ],
"main": "./out/extension.js", "main": "./out/extension.js",
"contributes": { "contributes": {
"configuration": { "configuration": {
"title": "Fabelous Autocoder", "title": "Fabelous Autocoder",
"properties": { "properties": {
"fabelous-autocoder.endpoint": { "fabelous-autocoder.endpoint": {
"type": "string", "type": "string",
"default": "http://localhost:11434/api/generate", "default": "http://localhost:11434/api/generate",
"description": "The endpoint of the ollama REST API" "description": "The endpoint of the ollama REST API"
}, },
"fabelous-autocoder.authentication": { "fabelous-autocoder.authentication": {
"type": "string", "type": "string",
"default": "", "default": "",
"description": "Authorization Token for Ollama" "description": "Authorization Token for Ollama"
}, },
"fabelous-autocoder.model": { "fabelous-autocoder.model": {
"type": "string", "type": "string",
"default": "", "default": "",
"description": "The model to use for generating completions" "description": "The model to use for generating completions"
}, },
"fabelous-autocoder.max tokens predicted": { "fabelous-autocoder.max tokens predicted": {
"type": "integer", "type": "integer",
"default": 1000, "default": 1000,
"description": "The maximum number of tokens generated by the model." "description": "The maximum number of tokens generated by the model."
}, },
"fabelous-autocoder.prompt window size": { "fabelous-autocoder.prompt window size": {
"type": "integer", "type": "integer",
"default": 2000, "default": 2000,
"description": "The size of the prompt in characters. NOT tokens, so can be set about 1.5-2x the max tokens of the model (varies)." "description": "The size of the prompt in characters. NOT tokens, so can be set about 1.5-2x the max tokens of the model (varies)."
}, },
"fabelous-autocoder.completion keys": { "fabelous-autocoder.completion keys": {
"type": "string", "type": "string",
"default": " ", "default": " ",
"description": "Character that the autocompletion item provider appear on. Multiple characters will be treated as different entries. REQUIRES RELOAD" "description": "Character that the autocompletion item provider appear on. Multiple characters will be treated as different entries. REQUIRES RELOAD"
}, },
"fabelous-autocoder.response preview": { "fabelous-autocoder.response preview": {
"type": "boolean", "type": "boolean",
"default": true, "default": true,
"description": "Inline completion label will be the first line of response. Max is 10 tokens, but this is unlikely to be reached. If the first line is empty, the default label will be used. Not streamable, disable on slow devices." "description": "Inline completion label will be the first line of response. Max is 10 tokens, but this is unlikely to be reached. If the first line is empty, the default label will be used. Not streamable, disable on slow devices."
}, },
"fabelous-autocoder.preview max tokens": { "fabelous-autocoder.preview max tokens": {
"type": "integer", "type": "integer",
"default": 50, "default": 50,
"description": "The maximum number of tokens generated by the model for the response preview. Typically not reached as the preview stops on newline. Recommended to keep very low due to computational cost." "description": "The maximum number of tokens generated by the model for the response preview. Typically not reached as the preview stops on newline. Recommended to keep very low due to computational cost."
}, },
"fabelous-autocoder.preview delay": { "fabelous-autocoder.preview delay": {
"type": "number", "type": "number",
"default": 1, "default": 1,
"description": "Time to wait in seconds before starting inline preview generation. Prevents Ollama server from running briefly every time the completion key is pressed, which causes unnecessary compute usage. If you are not on a battery powered device, set this to 0 for a more responsive experience." "description": "Time to wait in seconds before starting inline preview generation. Prevents Ollama server from running briefly every time the completion key is pressed, which causes unnecessary compute usage. If you are not on a battery powered device, set this to 0 for a more responsive experience."
}, },
"fabelous-autocoder.continue inline": { "fabelous-autocoder.continue inline": {
"type": "boolean", "type": "boolean",
"default": true, "default": true,
"description": "Ollama continues autocompletion after what is previewed inline. Disabling disables that feature as some may find it irritating. Multiline completion is still accessible through the shortcut even after disabling." "description": "Ollama continues autocompletion after what is previewed inline. Disabling disables that feature as some may find it irritating. Multiline completion is still accessible through the shortcut even after disabling."
},
}, "fabelous-autocoder.temperature": {
"fabelous-autocoder.temperature": { "type": "number",
"type": "number", "default": 0.5,
"default": 0.5, "description": "Temperature of the model. It is recommended to set it lower than you would for dialogue."
"description": "Temperature of the model. It is recommended to set it lower than you would for dialogue." },
}, "fabelous-autocoder.keep alive": {
"fabelous-autocoder.keep alive": { "type": "number",
"type": "number", "default": 10,
"default": 10, "description": "Time in minutes before Ollama unloads the model."
"description": "Time in minutes before Ollama unloads the model." },
}, "fabelous-autocoder.top p": {
"fabelous-autocoder.top p": { "type": "number",
"type": "number", "default": 1,
"description": "Top p sampling for the model." "description": "Top p sampling for the model."
} },
} "fabelous-autocoder.enableLineByLineAcceptance": {
}, "type": "boolean",
"commands": [ "default": false,
{ "description": "Enable line-by-line acceptance of the generated code."
"command": "fabelous-autocoder.autocomplete", }
"title": "Fabelous autocompletion" }
} },
] "keybindings": [
}, {
"scripts": { "command": "fabelous-autocoder.handleTab",
"vscode:prepublish": "npm run compile", "key": "tab",
"compile": "tsc --skipLibCheck -p ./", "when": "editorTextFocus && !editorTabMovesFocus"
"package": "npm run compile && vsce package", },
"lint": "eslint \"src/**/*.ts\"", {
"watch": "tsc --skipLibCheck -watch -p ./" "command": "fabelous-autocoder.handleBackspace",
}, "key": "backspace",
"devDependencies": { "when": "editorTextFocus"
"@types/node": "^20.12.8", }
"@types/vscode": "^1.89.0", ],
"@typescript-eslint/eslint-plugin": "^7.8.0", "commands": [
"@typescript-eslint/parser": "^7.8.0", {
"eslint": "^8.57.0", "command": "fabelous-autocoder.autocomplete",
"typescript": "^5.4.5" "title": "Fabelous Autocompletion"
}, },
"dependencies": { {
"axios": "^1.6.8" "command": "fabelous-autocoder.handleTab",
} "title": "Handle Tab"
} }
]
},
"scripts": {
"vscode:prepublish": "npm run compile",
"compile": "tsc --skipLibCheck -p ./",
"package": "npm run compile && vsce package",
"lint": "eslint \"src/**/*.ts\"",
"watch": "tsc --skipLibCheck -watch -p ./"
},
"devDependencies": {
"@types/node": "^20.12.8",
"@types/vscode": "^1.89.0",
"@typescript-eslint/eslint-plugin": "^7.8.0",
"@typescript-eslint/parser": "^7.8.0",
"eslint": "^8.57.0",
"typescript": "^5.4.5"
},
"dependencies": {
"axios": "^1.6.8"
}
}

View File

@ -1,206 +1,328 @@
import * as vscode from "vscode"; import * as vscode from 'vscode';
import axios from "axios"; import axios from 'axios';
let VSConfig: vscode.WorkspaceConfiguration; let config: {
let apiEndpoint: string; apiEndpoint: string;
let apiAuthentication: string; apiAuthentication: string;
let apiModel: string; apiModel: string;
let apiTemperature: number; apiTemperature: number;
let numPredict: number; numPredict: number;
let promptWindowSize: number; promptWindowSize: number;
let completionKeys: string; completionKeys: string[];
let responsePreview: boolean | undefined; responsePreview: boolean;
let responsePreviewMaxTokens: number; responsePreviewMaxTokens: number;
let responsePreviewDelay: number; responsePreviewDelay: number;
let continueInline: boolean | undefined; continueInline: boolean;
let keepAlive: number | undefined; keepAlive: number;
let topP: number | undefined; topP: number;
};
function updateVSConfig() { let previewDecorationType: vscode.TextEditorDecorationType;
VSConfig = vscode.workspace.getConfiguration("fabelous-autocoder"); let activeCompletionManager: CompletionManager | null = null;
apiEndpoint = VSConfig.get("endpoint") || "http://localhost:11434/api/generate";
apiAuthentication = VSConfig.get("authentication") || ""; function updateConfig() {
apiModel = VSConfig.get("model") || "fabelous-coder:latest"; const vsConfig = vscode.workspace.getConfiguration('fabelous-autocoder');
numPredict = VSConfig.get("max tokens predicted") || 1000; config = {
promptWindowSize = VSConfig.get("prompt window size") || 2000; apiEndpoint: vsConfig.get('endpoint') || 'http://localhost:11434/api/generate',
completionKeys = VSConfig.get("completion keys") || " "; apiAuthentication: vsConfig.get('authentication') || '',
responsePreview = VSConfig.get("response preview"); apiModel: vsConfig.get('model') || 'fabelous-coder:latest',
responsePreviewMaxTokens = VSConfig.get("preview max tokens") || 50; apiTemperature: vsConfig.get('temperature') || 0.7,
responsePreviewDelay = VSConfig.get("preview delay") || 0; numPredict: vsConfig.get('max tokens predicted') || 1000,
continueInline = VSConfig.get("continue inline"); promptWindowSize: vsConfig.get('prompt window size') || 2000,
apiTemperature = VSConfig.get("temperature") || 0.7; completionKeys: (vsConfig.get('completion keys') as string || ' ').split(''),
keepAlive = VSConfig.get("keep alive") || 30; responsePreview: vsConfig.get('response preview') || false,
topP = VSConfig.get("top p") || 1; responsePreviewMaxTokens: vsConfig.get('preview max tokens') || 50,
responsePreviewDelay: vsConfig.get('preview delay') || 0,
continueInline: vsConfig.get('continue inline') || false,
keepAlive: vsConfig.get('keep alive') || 30,
topP: vsConfig.get('top p') || 1,
};
} }
updateVSConfig(); function createPreviewDecorationType() {
vscode.workspace.onDidChangeConfiguration(updateVSConfig); previewDecorationType = vscode.window.createTextEditorDecorationType({
after: {
color: '#888888',
fontStyle: 'italic',
},
textDecoration: 'none; display: none;',
});
}
function getContextLines(document: vscode.TextDocument, position: vscode.Position): string { function getContextLines(document: vscode.TextDocument, position: vscode.Position): string {
const lines = [];
const startLine = Math.max(0, position.line - 1); const startLine = Math.max(0, position.line - 1);
const endLine = position.line; const endLine = position.line;
return document.getText(new vscode.Range(startLine, 0, endLine, position.character));
for (let i = startLine; i <= endLine; i++) {
lines.push(document.lineAt(i).text);
}
return lines.join("\n");
} }
function createFIMPrompt(prefix: string, language: string): string { function createFIMPrompt(prefix: string, language: string): string {
return `<fim_prefix>${prefix}<fim_middle><fim_suffix>${language}\n`; return `<fim_prefix>${prefix}<fim_middle><fim_suffix>${language}\n`;
} }
async function generateCompletion(prompt: string, cancellationToken: vscode.CancellationToken): Promise<string> {
const axiosCancelToken = new axios.CancelToken((c) => {
cancellationToken.onCancellationRequested(() => c('Request cancelled'));
});
async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationToken?: vscode.CancellationToken) { const response = await axios.post(config.apiEndpoint, {
const document = textEditor.document; model: config.apiModel,
const position = textEditor.selection.active; prompt: prompt,
stream: false,
raw: true,
options: {
num_predict: config.numPredict,
temperature: config.apiTemperature,
stop: ['<fim_suffix>', '```'],
keep_alive: config.keepAlive,
top_p: config.topP,
}
}, {
cancelToken: axiosCancelToken,
headers: {
'Authorization': config.apiAuthentication
}
});
const context = getContextLines(document, position); return response.data.response.replace(/<fim_middle>|<fim_suffix>|<fim_prefix>/g, '').trim();
}
const fimPrompt = createFIMPrompt(context, document.languageId); class CompletionManager {
private textEditor: vscode.TextEditor;
private document: vscode.TextDocument;
private startPosition: vscode.Position;
private completionText: string;
private insertedLineCount: number = 0; // Track the number of inserted lines
vscode.window.withProgress( constructor(textEditor: vscode.TextEditor, startPosition: vscode.Position, completionText: string) {
{ this.textEditor = textEditor;
location: vscode.ProgressLocation.Notification, this.document = textEditor.document;
title: "Fabelous Autocoder", this.startPosition = startPosition;
cancellable: true, this.completionText = completionText;
}, }
async (progress, progressCancellationToken) => {
try {
progress.report({ message: "Starting model..." });
let axiosCancelPost: () => void; public async showPreview() {
const axiosCancelToken = new axios.CancelToken((c) => { if (!previewDecorationType) {
axiosCancelPost = () => { createPreviewDecorationType();
c("Autocompletion request terminated by user cancel"); }
};
if (cancellationToken) cancellationToken.onCancellationRequested(axiosCancelPost); const completionLines = this.completionText.split('\n').length;
progressCancellationToken.onCancellationRequested(axiosCancelPost);
vscode.workspace.onDidCloseTextDocument(axiosCancelPost); // Adjust the start position to line after the original start position
}); const adjustedStartPosition = this.startPosition.translate(0, 0);
// Step 1: Insert blank lines to make space for the preview
const edit = new vscode.WorkspaceEdit();
const linePadding = '\n'.repeat(completionLines + 1); // Include extra line break for visual separation
edit.insert(this.document.uri, adjustedStartPosition, linePadding);
await vscode.workspace.applyEdit(edit);
this.insertedLineCount = completionLines + 1;
// Step 2: Apply decorations
const previewRanges: vscode.DecorationOptions[] = this.completionText.split('\n').map((line, index) => {
const lineNumber = adjustedStartPosition.line + index + 1; // Start preview one line later
return {
range: new vscode.Range(
new vscode.Position(lineNumber, 0),
new vscode.Position(lineNumber, 0)
),
renderOptions: {
after: {
contentText: line,
color: '#888888',
fontStyle: 'italic',
},
},
};
});
this.textEditor.setDecorations(previewDecorationType, previewRanges);
}
const response = await axios.post(apiEndpoint, { public async acceptCompletion() {
model: apiModel, const edit = new vscode.WorkspaceEdit();
prompt: fimPrompt, const completionLines = this.completionText.split('\n');
stream: false, const numberOfLines = completionLines.length;
raw: true,
options: { // Ensure the start position is never negative
num_predict: numPredict, const safeStartPosition = new vscode.Position(Math.max(0, this.startPosition.line - 1), 0);
temperature: apiTemperature,
stop: ["<fim_suffix>", "```"]
}
}, {
cancelToken: axiosCancelToken,
headers: {
'Authorization': apiAuthentication
}
});
progress.report({ message: "Generating..." }); // Prepare the range to replace
const rangeToReplace = new vscode.Range(
safeStartPosition,
this.startPosition.translate(numberOfLines, 0)
);
let completionText = response.data.response; // Construct the content to insert
// Remove any FIM tags and leading/trailing whitespace const contentToInsert = (safeStartPosition.line === 0 ? '' : '\n') + this.completionText + '\n';
completionText = completionText.replace(/<fim_middle>|<fim_suffix>|<fim_prefix>/g, '').trim(); edit.replace(this.document.uri, rangeToReplace, contentToInsert);
await vscode.workspace.applyEdit(edit);
// Clear the preview decorations
this.clearPreview();
// Remove the context lines // Set activeCompletionManager to null
const startLine = Math.max(0, position.line - 1); activeCompletionManager = null;
const endLine = position.line;
const rangeToReplace = new vscode.Range( // Calculate the new cursor position from the inserted content
const lastCompletionLine = completionLines[completionLines.length - 1];
const newPosition = new vscode.Position(
this.startPosition.line + numberOfLines - 1,
lastCompletionLine.length
);
// Set the new cursor position
this.textEditor.selection = new vscode.Selection(newPosition, newPosition);
}
public clearPreview() {
this.textEditor.setDecorations(previewDecorationType, []); // Remove all preview decorations
}
public async declineCompletion() {
this.clearPreview(); // Clear the preview decorations
try {
const document = this.textEditor.document;
const currentPosition = this.textEditor.selection.active;
// Calculate the range of lines to remove
const startLine = this.startPosition.line + 1;
const endLine = currentPosition.line;
if (endLine > startLine) {
const workspaceEdit = new vscode.WorkspaceEdit();
// Create a range from start of startLine to end of endLine
const range = new vscode.Range(
new vscode.Position(startLine, 0), new vscode.Position(startLine, 0),
new vscode.Position(endLine, document.lineAt(endLine).text.length) new vscode.Position(endLine, document.lineAt(endLine).text.length)
); );
// Delete the range
workspaceEdit.delete(document.uri, range);
// Apply the edit // Apply the edit
const edit = new vscode.WorkspaceEdit(); await vscode.workspace.applyEdit(workspaceEdit);
edit.replace(document.uri, rangeToReplace, completionText);
await vscode.workspace.applyEdit(edit); // Move the cursor back to the original position
this.textEditor.selection = new vscode.Selection(this.startPosition, this.startPosition);
// Move the cursor to the end of the inserted text
const newPosition = new vscode.Position(startLine + completionText.split('\n').length - 1, completionText.split('\n').pop()!.length); console.log(`Lines ${startLine + 1} to ${endLine + 1} removed successfully`);
textEditor.selection = new vscode.Selection(newPosition, newPosition); activeCompletionManager = null;
} else {
progress.report({ message: "Fabelous completion finished." }); console.log('No lines to remove');
} catch (err: any) {
vscode.window.showErrorMessage(
"Fabelous Autocoder encountered an error: " + err.message
);
console.log(err);
} }
} catch (error) {
console.error('Error declining completion:', error);
vscode.window.showErrorMessage(`Error removing lines: ${error}`);
} }
); }
} }
async function autocompleteCommand(textEditor: vscode.TextEditor, edit: vscode.TextEditorEdit, ...args: any[]) {
const cancellationTokenSource = new vscode.CancellationTokenSource();
const cancellationToken = cancellationTokenSource.token;
try {
const document = textEditor.document;
const position = textEditor.selection.active;
const context = getContextLines(document, position);
const fimPrompt = createFIMPrompt(context, document.languageId);
const completionText = await vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
title: 'Fabelous Autocoder',
cancellable: true,
}, async (progress, progressCancellationToken) => {
progress.report({ message: 'Generating...' });
return await generateCompletion(fimPrompt, progressCancellationToken);
});
console.log('Completion generated:', completionText);
const completionManager = new CompletionManager(textEditor, position, completionText);
await completionManager.showPreview();
activeCompletionManager = completionManager;
} catch (err: any) {
console.error('Error in autocompleteCommand:', err);
vscode.window.showErrorMessage(`Fabelous Autocoder encountered an error: ${err.message}`);
} finally {
cancellationTokenSource.dispose();
}
}
async function handleTab() {
if (activeCompletionManager) {
await activeCompletionManager.acceptCompletion();
} else {
await vscode.commands.executeCommand('tab');
}
}
async function handleBackspace() {
if (activeCompletionManager) {
await activeCompletionManager.declineCompletion();
} else {
await vscode.commands.executeCommand('deleteLeft');
}
}
async function provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, cancellationToken: vscode.CancellationToken) { async function provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, cancellationToken: vscode.CancellationToken) {
const item = new vscode.CompletionItem("Fabelous autocompletion"); const item = new vscode.CompletionItem('Fabelous autocompletion');
item.insertText = new vscode.SnippetString('${1:}'); item.insertText = new vscode.SnippetString('${1:}');
item.documentation = new vscode.MarkdownString('Press `Enter` to get an autocompletion from Fabelous Autocoder');
if (responsePreview) {
await new Promise(resolve => setTimeout(resolve, responsePreviewDelay * 1000)); if (config.responsePreview) {
await new Promise(resolve => setTimeout(resolve, config.responsePreviewDelay * 1000));
if (cancellationToken.isCancellationRequested) { if (cancellationToken.isCancellationRequested) {
return [ item ]; return [item];
} }
const context = getContextLines(document, position); const context = getContextLines(document, position);
const fimPrompt = createFIMPrompt(context, document.languageId); const fimPrompt = createFIMPrompt(context, document.languageId);
try { try {
const response_preview = await axios.post(apiEndpoint, { const result = await generateCompletion(fimPrompt, cancellationToken);
model: apiModel, const preview = (result as any).preview;
prompt: fimPrompt, if (preview) {
stream: false, item.detail = preview.split('\n')[0];
raw: true, }
options: {
num_predict: responsePreviewMaxTokens,
temperature: apiTemperature,
stop: ['<fim_suffix>', '\n', '```'],
...(keepAlive && { keep_alive: keepAlive }),
...(topP && { top_p: topP }),
}
}, {
cancelToken: new axios.CancelToken((c) => {
cancellationToken.onCancellationRequested(() => c("Autocompletion request terminated by completion cancel"));
})
});
} catch (error) { } catch (error) {
console.error("Error fetching preview:", error); console.error('Error fetching preview:', error);
} }
} }
item.documentation = new vscode.MarkdownString('Press `Enter` to get an autocompletion from Fabelous Autocoder'); if (config.continueInline || !config.responsePreview) {
if (continueInline || !responsePreview) {
item.command = { item.command = {
command: 'fabelous-autocoder.autocomplete', command: 'fabelous-autocoder.autocomplete',
title: 'Fabelous Autocomplete', title: 'Fabelous Autocomplete',
arguments: [cancellationToken] arguments: []
}; };
} }
return [item]; return [item];
} }
export function activate(context: vscode.ExtensionContext) {
updateConfig();
createPreviewDecorationType();
function activate(context: vscode.ExtensionContext) { context.subscriptions.push(
const completionProvider = vscode.languages.registerCompletionItemProvider("*", { vscode.workspace.onDidChangeConfiguration(updateConfig),
provideCompletionItems vscode.languages.registerCompletionItemProvider('*', { provideCompletionItems }, ...config.completionKeys),
}, vscode.commands.registerTextEditorCommand('fabelous-autocoder.autocomplete', autocompleteCommand),
...completionKeys.split("") vscode.commands.registerCommand('fabelous-autocoder.handleTab', handleTab),
vscode.commands.registerCommand('fabelous-autocoder.handleBackspace', handleBackspace) // Add this line
); );
const externalAutocompleteCommand = vscode.commands.registerTextEditorCommand(
"fabelous-autocoder.autocomplete",
(textEditor, _, cancellationToken?) => {
autocompleteCommand(textEditor, cancellationToken);
}
);
context.subscriptions.push(completionProvider);
context.subscriptions.push(externalAutocompleteCommand);
} }
function deactivate() { }
module.exports = {
activate, export function deactivate() {}
deactivate,
};