import * as vscode from "vscode"; import axios from "axios"; let VSConfig: vscode.WorkspaceConfiguration; let apiEndpoint: string; let apiAuthentication: string; let apiModel: string; let apiTemperature: number; let numPredict: number; let promptWindowSize: number; let completionKeys: string; let responsePreview: boolean | undefined; let responsePreviewMaxTokens: number; let responsePreviewDelay: number; let continueInline: boolean | undefined; let keepAlive: number | undefined; let topP: number | undefined; let inlinePreviewDecoration: vscode.TextEditorDecorationType; let currentPreviewText: string | undefined; function createInlinePreviewDecoration() { return vscode.window.createTextEditorDecorationType({ after: { color: '#808080', fontStyle: 'italic' } }); } function updateVSConfig() { VSConfig = vscode.workspace.getConfiguration("fabelous-autocoder"); apiEndpoint = VSConfig.get("endpoint") || "http://localhost:11434/api/generate"; apiAuthentication = VSConfig.get("authentication") || ""; apiModel = VSConfig.get("model") || "fabelous-coder:latest"; numPredict = VSConfig.get("max tokens predicted") || 1000; promptWindowSize = VSConfig.get("prompt window size") || 2000; completionKeys = VSConfig.get("completion keys") || " "; // Here's where the response preview setting is read responsePreview = VSConfig.get("response preview"); responsePreviewMaxTokens = VSConfig.get("preview max tokens") || 50; responsePreviewDelay = VSConfig.get("preview delay") || 0; continueInline = VSConfig.get("continue inline"); apiTemperature = VSConfig.get("temperature") || 0.7; keepAlive = VSConfig.get("keep alive") || 30; topP = VSConfig.get("top p") || 1; } updateVSConfig(); vscode.workspace.onDidChangeConfiguration(updateVSConfig); function getContextLines(document: vscode.TextDocument, position: vscode.Position): string { const lines = []; const startLine = Math.max(0, position.line - 1); const endLine = position.line; for (let i = startLine; i <= endLine; i++) { lines.push(document.lineAt(i).text); } return lines.join("\n"); } function createFIMPrompt(prefix: string, language: string): string { return `${prefix}${language}\n`; } async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationToken?: vscode.CancellationToken) { const document = textEditor.document; const position = textEditor.selection.active; const context = getContextLines(document, position); const fimPrompt = createFIMPrompt(context, document.languageId); vscode.window.withProgress( { location: vscode.ProgressLocation.Notification, title: "Fabelous Autocoder", cancellable: true, }, async (progress, progressCancellationToken) => { try { progress.report({ message: "Starting model..." }); let axiosCancelPost: () => void; const axiosCancelToken = new axios.CancelToken((c) => { axiosCancelPost = () => { 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, { model: apiModel, prompt: fimPrompt, stream: false, raw: true, options: { num_predict: numPredict, temperature: apiTemperature, stop: ["", "```"] } }, { cancelToken: axiosCancelToken, headers: { 'Authorization': apiAuthentication } }); progress.report({ message: "Generating..." }); let completionText = response.data.response; // Remove any FIM tags and leading/trailing whitespace completionText = completionText.replace(/||/g, '').trim(); // Remove the context lines const startLine = Math.max(0, position.line - 1); const endLine = position.line; const rangeToReplace = new vscode.Range( new vscode.Position(startLine, 0), new vscode.Position(endLine, document.lineAt(endLine).text.length) ); // Apply the edit const edit = new vscode.WorkspaceEdit(); edit.replace(document.uri, rangeToReplace, completionText); await vscode.workspace.applyEdit(edit); // 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); textEditor.selection = new vscode.Selection(newPosition, newPosition); progress.report({ message: "Fabelous completion finished." }); } catch (err: any) { vscode.window.showErrorMessage( "Fabelous Autocoder encountered an error: " + err.message ); console.log(err); } } ); } 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:}'); // This is where the response preview functionality is implemented if (responsePreview) { await new Promise(resolve => setTimeout(resolve, responsePreviewDelay * 1000)); if (cancellationToken.isCancellationRequested) { return [ item ]; } const context = getContextLines(document, position); const fimPrompt = createFIMPrompt(context, document.languageId); try { const response_preview = await axios.post(apiEndpoint, { model: apiModel, prompt: fimPrompt, stream: false, raw: true, options: { num_predict: responsePreviewMaxTokens, temperature: apiTemperature, stop: ['', '\n', '```'], ...(keepAlive && { keep_alive: keepAlive }), ...(topP && { top_p: topP }), } }); currentPreviewText = response_preview.data.response.replace(/||/g, '').trim(); updateInlinePreview(document, position); if (currentPreviewText) { item.label = currentPreviewText.split('\n')[0]; } } catch (error) { console.error("Error fetching preview:", error); } } item.documentation = new vscode.MarkdownString('Press `Enter` to accept the autocompletion, or `Escape` to dismiss'); item.command = { command: 'fabelous-autocoder.acceptCompletion', title: 'Accept Fabelous Autocompletion', }; return [item]; } function removeInlinePreview() { const editor = vscode.window.activeTextEditor; if (editor) { editor.setDecorations(inlinePreviewDecoration, []); } currentPreviewText = undefined; } function acceptCompletion() { const editor = vscode.window.activeTextEditor; if (editor && currentPreviewText) { editor.edit(editBuilder => { const position = editor.selection.active; editBuilder.insert(position, currentPreviewText as string); }); removeInlinePreview(); } } function activate(context: vscode.ExtensionContext) { inlinePreviewDecoration = createInlinePreviewDecoration(); const completionProvider = vscode.languages.registerCompletionItemProvider("*", { provideCompletionItems }, ...completionKeys.split("") ); const acceptCompletionCommand = vscode.commands.registerCommand( "fabelous-autocoder.acceptCompletion", acceptCompletion ); context.subscriptions.push(completionProvider); context.subscriptions.push(acceptCompletionCommand); // Handle Escape key to remove preview context.subscriptions.push(vscode.commands.registerCommand('type', args => { if (args.text === 'Escape') { removeInlinePreview(); } return vscode.commands.executeCommand('default:type', args); })); // Remove preview when cursor moves context.subscriptions.push(vscode.window.onDidChangeTextEditorSelection(() => { removeInlinePreview(); })); } function updateInlinePreview(document: vscode.TextDocument, position: vscode.Position) { if (currentPreviewText) { const editor = vscode.window.activeTextEditor; if (editor && editor.document === document) { const decorations = [{ range: new vscode.Range(position, position), renderOptions: { after: { contentText: currentPreviewText } } }]; editor.setDecorations(inlinePreviewDecoration, decorations); } } } function deactivate() { } export { activate, deactivate, };