develop #6
289
package.json
289
package.json
|
@ -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"
|
||||||
|
}
|
||||||
}
|
}
|
416
src/extension.ts
416
src/extension.ts
|
@ -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);
|
|
||||||
progressCancellationToken.onCancellationRequested(axiosCancelPost);
|
|
||||||
vscode.workspace.onDidCloseTextDocument(axiosCancelPost);
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await axios.post(apiEndpoint, {
|
const completionLines = this.completionText.split('\n').length;
|
||||||
model: apiModel,
|
|
||||||
prompt: fimPrompt,
|
|
||||||
stream: false,
|
|
||||||
raw: true,
|
|
||||||
options: {
|
|
||||||
num_predict: numPredict,
|
|
||||||
temperature: apiTemperature,
|
|
||||||
stop: ["<fim_suffix>", "```"]
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
cancelToken: axiosCancelToken,
|
|
||||||
headers: {
|
|
||||||
'Authorization': apiAuthentication
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
progress.report({ message: "Generating..." });
|
// Adjust the start position to line after the original start position
|
||||||
|
const adjustedStartPosition = this.startPosition.translate(0, 0);
|
||||||
|
|
||||||
let completionText = response.data.response;
|
// Step 1: Insert blank lines to make space for the preview
|
||||||
// Remove any FIM tags and leading/trailing whitespace
|
const edit = new vscode.WorkspaceEdit();
|
||||||
completionText = completionText.replace(/<fim_middle>|<fim_suffix>|<fim_prefix>/g, '').trim();
|
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);
|
||||||
|
|
||||||
// Remove the context lines
|
this.insertedLineCount = completionLines + 1;
|
||||||
const startLine = Math.max(0, position.line - 1);
|
|
||||||
const endLine = position.line;
|
// Step 2: Apply decorations
|
||||||
const rangeToReplace = new vscode.Range(
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async acceptCompletion() {
|
||||||
|
const edit = new vscode.WorkspaceEdit();
|
||||||
|
const completionLines = this.completionText.split('\n');
|
||||||
|
const numberOfLines = completionLines.length;
|
||||||
|
|
||||||
|
// Ensure the start position is never negative
|
||||||
|
const safeStartPosition = new vscode.Position(Math.max(0, this.startPosition.line - 1), 0);
|
||||||
|
|
||||||
|
// Prepare the range to replace
|
||||||
|
const rangeToReplace = new vscode.Range(
|
||||||
|
safeStartPosition,
|
||||||
|
this.startPosition.translate(numberOfLines, 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Construct the content to insert
|
||||||
|
const contentToInsert = (safeStartPosition.line === 0 ? '' : '\n') + this.completionText + '\n';
|
||||||
|
edit.replace(this.document.uri, rangeToReplace, contentToInsert);
|
||||||
|
|
||||||
|
await vscode.workspace.applyEdit(edit);
|
||||||
|
|
||||||
|
// Clear the preview decorations
|
||||||
|
this.clearPreview();
|
||||||
|
|
||||||
|
// Set activeCompletionManager to null
|
||||||
|
activeCompletionManager = null;
|
||||||
|
|
||||||
|
// 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 to the end of the inserted text
|
// Move the cursor back to the original position
|
||||||
const newPosition = new vscode.Position(startLine + completionText.split('\n').length - 1, completionText.split('\n').pop()!.length);
|
this.textEditor.selection = new vscode.Selection(this.startPosition, this.startPosition);
|
||||||
textEditor.selection = new vscode.Selection(newPosition, newPosition);
|
|
||||||
|
|
||||||
progress.report({ message: "Fabelous completion finished." });
|
console.log(`Lines ${startLine + 1} to ${endLine + 1} removed successfully`);
|
||||||
|
activeCompletionManager = null;
|
||||||
} catch (err: any) {
|
} else {
|
||||||
vscode.window.showErrorMessage(
|
console.log('No lines to remove');
|
||||||
"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 provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, cancellationToken: vscode.CancellationToken) {
|
async function autocompleteCommand(textEditor: vscode.TextEditor, edit: vscode.TextEditorEdit, ...args: any[]) {
|
||||||
const item = new vscode.CompletionItem("Fabelous autocompletion");
|
const cancellationTokenSource = new vscode.CancellationTokenSource();
|
||||||
item.insertText = new vscode.SnippetString('${1:}');
|
const cancellationToken = cancellationTokenSource.token;
|
||||||
|
|
||||||
if (responsePreview) {
|
try {
|
||||||
await new Promise(resolve => setTimeout(resolve, responsePreviewDelay * 1000));
|
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) {
|
||||||
|
const item = new vscode.CompletionItem('Fabelous autocompletion');
|
||||||
|
item.insertText = new vscode.SnippetString('${1:}');
|
||||||
|
item.documentation = new vscode.MarkdownString('Press `Enter` to get an autocompletion from Fabelous Autocoder');
|
||||||
|
|
||||||
|
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,
|
|
||||||
};
|
|
||||||
|
|
Loading…
Reference in New Issue