diff --git a/src/extension.ts b/src/extension.ts index 1291669..86841ec 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -69,7 +69,7 @@ async function generateCompletion(prompt: string, cancellationToken: vscode.Canc stream: false, raw: true, options: { - num_predict: config.numPredict + 100, // Generate extra lines for preview + num_predict: config.numPredict, temperature: config.apiTemperature, stop: ['', '```'], keep_alive: config.keepAlive, @@ -82,12 +82,7 @@ async function generateCompletion(prompt: string, cancellationToken: vscode.Canc } }); - const fullCompletion = response.data.response.replace(/||/g, '').trim(); - const completionLines = fullCompletion.split('\n'); - const usedContextLines = prompt.split('\n').length; - - // Remove used context and take extra lines for preview - return completionLines.slice(usedContextLines, usedContextLines + config.numPredict).join('\n'); + return response.data.response.replace(/||/g, '').trim(); } class CompletionManager { @@ -95,38 +90,58 @@ class CompletionManager { private document: vscode.TextDocument; private startPosition: vscode.Position; private completionText: string; - private previewLines: string[]; constructor(textEditor: vscode.TextEditor, startPosition: vscode.Position, completionText: string) { this.textEditor = textEditor; this.document = textEditor.document; this.startPosition = startPosition; this.completionText = completionText; - this.previewLines = completionText.split('\n'); } public async showPreview() { - const previewRanges: vscode.DecorationOptions[] = this.previewLines.map((line, index) => ({ - range: new vscode.Range(this.startPosition.translate(index, 0), this.startPosition.translate(index, 0)), - renderOptions: { - after: { - contentText: line, + const completionLines = this.completionText.split('\n'); + const previewLines = [ + '', // Empty line before + ...completionLines, + '' // Empty line after + ]; + + const previewRanges: vscode.DecorationOptions[] = previewLines.map((line, index) => { + const lineNumber = Math.max(0, this.startPosition.line + index - 1); + return { + range: new vscode.Range( + new vscode.Position(lineNumber, 0), + new vscode.Position(lineNumber, Number.MAX_VALUE) + ), + renderOptions: { + after: { + contentText: line, + } } - } - })); + }; + }); this.textEditor.setDecorations(previewDecorationType, previewRanges); } + public async acceptCompletion() { const edit = new vscode.WorkspaceEdit(); - edit.insert(this.document.uri, this.startPosition, this.completionText); + const startLine = Math.max(0, this.startPosition.line - 1); + const range = new vscode.Range( + new vscode.Position(startLine, 0), + this.startPosition.translate(0, Number.MAX_VALUE) + ); + edit.replace(this.document.uri, range, this.completionText); await vscode.workspace.applyEdit(edit); this.clearPreview(); } - + public clearPreview() { this.textEditor.setDecorations(previewDecorationType, []); } + public declineCompletion() { + this.clearPreview(); + } } async function autocompleteCommand(textEditor: vscode.TextEditor, edit: vscode.TextEditorEdit, ...args: any[]) { @@ -148,20 +163,57 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, edit: vscode.T return await generateCompletion(fimPrompt, progressCancellationToken); }); + console.log('Completion generated:', completionText); + const completionManager = new CompletionManager(textEditor, position, completionText); await completionManager.showPreview(); - const disposable = vscode.window.onDidChangeTextEditorSelection(async (event) => { - if (event.textEditor !== textEditor) return; + let isDisposed = false; - if (event.kind === vscode.TextEditorSelectionChangeKind.Keyboard) { - await completionManager.acceptCompletion(); + const dispose = () => { + if (!isDisposed) { + console.log('Disposing listeners'); disposable.dispose(); + declineDisposable.dispose(); + isDisposed = true; + } + }; + + const disposable = vscode.Disposable.from( + vscode.window.onDidChangeTextEditorSelection(async (event) => { + if (event.textEditor !== textEditor) return; + + if (event.kind === vscode.TextEditorSelectionChangeKind.Keyboard) { + console.log('Accepting completion'); + await completionManager.acceptCompletion(); + dispose(); + } + }), + vscode.window.onDidChangeActiveTextEditor(() => { + console.log('Active editor changed, clearing preview'); + completionManager.clearPreview(); + dispose(); + }), + vscode.workspace.onDidChangeTextDocument((event) => { + if (event.document === document) { + console.log('Document changed, clearing preview'); + completionManager.clearPreview(); + dispose(); + } + }) + ); + + const declineDisposable = vscode.commands.registerCommand('type', async (args) => { + if (args.text === '\b') { // Backspace key + console.log('Declining completion'); + completionManager.declineCompletion(); + dispose(); } }); + } catch (err: any) { + console.error('Error in autocompleteCommand:', err); vscode.window.showErrorMessage(`Fabelous Autocoder encountered an error: ${err.message}`); - console.error(err); } finally { cancellationTokenSource.dispose(); } @@ -182,8 +234,11 @@ async function provideCompletionItems(document: vscode.TextDocument, position: v const fimPrompt = createFIMPrompt(context, document.languageId); try { - const previewText = await generateCompletion(fimPrompt, cancellationToken); - item.detail = previewText.split('\n')[0]; // Show first line as preview + const result = await generateCompletion(fimPrompt, cancellationToken); + const preview = (result as any).preview; + if (preview) { + item.detail = preview.split('\n')[0]; + } } catch (error) { console.error('Error fetching preview:', error); }