diff --git a/src/extension.ts b/src/extension.ts index 12078e7..1106a6c 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -53,13 +53,17 @@ function createFIMPrompt(prefix: string, language: string): string { return `${prefix}${language}\n`; } +const previewDecorationType = vscode.window.createTextEditorDecorationType({ + color: '#888888', // Grayed-out preview text + fontStyle: 'italic', + rangeBehavior: vscode.DecorationRangeBehavior.ClosedOpen, // Ensure proper handling of multiline decorations +}); 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( @@ -102,27 +106,69 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo 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) - ); + // Split the completion text by new lines + const lines = completionText.split('\n'); - // Apply the edit - const edit = new vscode.WorkspaceEdit(); - edit.replace(document.uri, rangeToReplace, completionText); - await vscode.workspace.applyEdit(edit); + // Create a decoration for each line of the response + const previewRanges = lines.map((line: string, idx: number) => { + const linePos = new vscode.Position(position.line + idx, 0); + const range = new vscode.Range(linePos, linePos); // Set range at the start of each new line + return { + range, + renderOptions: { + before: { + contentText: line, + color: '#888888', + fontStyle: 'italic', + } + } + }; + }); - // 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); + // Apply the decorations for multiline preview + textEditor.setDecorations(previewDecorationType, previewRanges); - progress.report({ message: "Fabelous completion finished." }); + const disposable = vscode.workspace.onDidChangeTextDocument(async (event) => { + if (event.document.uri.toString() === document.uri.toString()) { + const change = event.contentChanges[0]; + + // Handle Backspace to decline the preview + if (change && change.text === '' && change.rangeLength === 1) { + textEditor.setDecorations(previewDecorationType, []); // Remove preview decorations + disposable.dispose(); + } + + // Handle Ctrl + Enter (or Cmd + Enter on macOS) to accept the preview + const isCtrlOrCmdPressed = event.contentChanges.some( + (change) => { + const isMac = process.platform === 'darwin'; + const isCtrlOrCmd = isMac ? change.text.includes('\u0010') : change.text.includes('\n'); + return isCtrlOrCmd; + } + ); + + if (isCtrlOrCmdPressed) { + // Remove the preview decoration before applying the final completion + textEditor.setDecorations(previewDecorationType, []); + + const edit = new vscode.WorkspaceEdit(); + const insertPosition = new vscode.Position(position.line, 0); + + // Insert the completion only once + if (!document.getText().includes(completionText)) { + edit.insert(document.uri, insertPosition, '\n' + completionText); + await vscode.workspace.applyEdit(edit); + } + + const newPosition = new vscode.Position(position.line + lines.length, lines[lines.length - 1].length); + textEditor.selection = new vscode.Selection(newPosition, newPosition); + + disposable.dispose(); // Clean up the listener after accepting the completion + } + } + }); } catch (err: any) { vscode.window.showErrorMessage( @@ -135,6 +181,7 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo } + 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:}');