diff --git a/src/extension.ts b/src/extension.ts index 49926c6..1026b12 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -85,11 +85,13 @@ async function generateCompletion(prompt: string, cancellationToken: vscode.Canc return response.data.response.replace(/||/g, '').trim(); } + 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 constructor(textEditor: vscode.TextEditor, startPosition: vscode.Position, completionText: string) { this.textEditor = textEditor; @@ -99,64 +101,120 @@ class CompletionManager { } public async showPreview() { - // Calculate lines in the completionText + if (!previewDecorationType) { + createPreviewDecorationType(); + } + const completionLines = this.completionText.split('\n').length; - - // Insert empty lines to make space for completion preview + + // 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); // Adjust padding based on lines in completion - const range = new vscode.Range(this.startPosition, this.startPosition); - - edit.insert(this.document.uri, this.startPosition, linePadding); + 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 actualLineNumber = this.startPosition.line + index; + const lineNumber = adjustedStartPosition.line + index + 1; // Start preview one line later return { range: new vscode.Range( - new vscode.Position(actualLineNumber, 0), - new vscode.Position(actualLineNumber, 0) // Positions should be zero-length for insertions + new vscode.Position(lineNumber, 0), + new vscode.Position(lineNumber, 0) ), renderOptions: { after: { - contentText: ` ${line}`.trim(), + contentText: line, + color: '#888888', + fontStyle: 'italic', }, }, }; }); - + this.textEditor.setDecorations(previewDecorationType, previewRanges); } + public async acceptCompletion() { const edit = new vscode.WorkspaceEdit(); - - // Prepare to insert completion text - const endLine = this.startPosition.line + this.completionText.split('\n').length; - - // Replace the preview lines with actual completion text + 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( - this.startPosition, - new vscode.Position(endLine, 0) // Position for line end should just reach intended insert + safeStartPosition, + this.startPosition.translate(numberOfLines, 0) ); - - edit.replace(this.document.uri, rangeToReplace, this.completionText); - + + // 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); - - this.clearPreview(); + this.clearPreview(); // Clear the preview decorations + + // 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 without any additional move + this.textEditor.selection = new vscode.Selection(newPosition, newPosition); } + + public clearPreview() { - this.textEditor.setDecorations(previewDecorationType, []); // Clear only the decorations + this.textEditor.setDecorations(previewDecorationType, []); // Remove all preview decorations } - public declineCompletion() { - this.clearPreview(); + 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(endLine, document.lineAt(endLine).text.length) + ); + + // Delete the range + workspaceEdit.delete(document.uri, range); + + // Apply the edit + await vscode.workspace.applyEdit(workspaceEdit); + + // Move the cursor back to the original position + this.textEditor.selection = new vscode.Selection(this.startPosition, this.startPosition); + + console.log(`Lines ${startLine + 1} to ${endLine + 1} removed successfully`); + } else { + console.log('No lines to remove'); + } + } 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; @@ -183,49 +241,6 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, edit: vscode.T await completionManager.showPreview(); activeCompletionManager = completionManager; - let isDisposed = false; - - const dispose = () => { - if (!isDisposed) { - console.log('Disposing listeners'); - disposable.dispose(); - typeDisposable.dispose(); - activeCompletionManager = null; - 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 typeDisposable = 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); @@ -235,24 +250,19 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, edit: vscode.T } } -async function acceptCompletion() { +async function handleTab() { if (activeCompletionManager) { await activeCompletionManager.acceptCompletion(); - const editor = vscode.window.activeTextEditor; - if (editor) { - const lastLine = editor.document.lineAt(editor.document.lineCount - 1); - const newPosition = new vscode.Position(lastLine.lineNumber, lastLine.text.length); - editor.selection = new vscode.Selection(newPosition, newPosition); - } - activeCompletionManager = null; + } else { + await vscode.commands.executeCommand('tab'); } } -async function handleTab() { +async function handleBackspace() { if (activeCompletionManager) { - await acceptCompletion(); + await activeCompletionManager.declineCompletion(); } else { - await vscode.commands.executeCommand('tab'); + await vscode.commands.executeCommand('deleteLeft'); } } @@ -299,10 +309,11 @@ export function activate(context: vscode.ExtensionContext) { vscode.workspace.onDidChangeConfiguration(updateConfig), vscode.languages.registerCompletionItemProvider('*', { provideCompletionItems }, ...config.completionKeys), vscode.commands.registerTextEditorCommand('fabelous-autocoder.autocomplete', autocompleteCommand), - vscode.commands.registerCommand('fabelous-autocoder.acceptCompletion', acceptCompletion), - vscode.commands.registerCommand('fabelous-autocoder.handleTab', handleTab) + vscode.commands.registerCommand('fabelous-autocoder.handleTab', handleTab), + vscode.commands.registerCommand('fabelous-autocoder.handleBackspace', handleBackspace) // Add this line ); } + export function deactivate() {}