From 58c9af02562d145106103539e6e6f0b88439f42a Mon Sep 17 00:00:00 2001 From: Falko Habel Date: Tue, 10 Sep 2024 19:51:03 +0200 Subject: [PATCH 01/32] preview added but badly --- src/extension.ts | 72 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 55 insertions(+), 17 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 12078e7..a738fc5 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -53,13 +53,19 @@ function createFIMPrompt(prefix: string, language: string): string { return `${prefix}${language}\n`; } +const previewDecorationType = vscode.window.createTextEditorDecorationType({ + after: { + color: '#888888', // Grayed-out preview text + fontStyle: 'italic', + }, + isWholeLine: true, // Support multiline properly +}); 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 +108,58 @@ 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) - ); + // Handle multiline text by splitting it into lines + const lines = completionText.split('\n'); + const previewRanges = lines.map((line: string, idx: number) => { + const linePos = new vscode.Position(position.line + idx, 0); + const range = new vscode.Range(linePos, linePos); + return { + range, + renderOptions: { + after: { + contentText: line, // Show each line + } + } + }; + }); - // Apply the edit - const edit = new vscode.WorkspaceEdit(); - edit.replace(document.uri, rangeToReplace, completionText); - await vscode.workspace.applyEdit(edit); + textEditor.setDecorations(previewDecorationType, previewRanges); - // 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); + const disposable = vscode.workspace.onDidChangeTextDocument(async (event) => { + if (event.document.uri.toString() === document.uri.toString()) { + const change = event.contentChanges[0]; - progress.report({ message: "Fabelous completion finished." }); + // Handle Backspace to decline the preview + if (change && change.text === '' && change.rangeLength === 1) { + textEditor.setDecorations(previewDecorationType, []); + 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) { + const edit = new vscode.WorkspaceEdit(); + const insertPosition = new vscode.Position(position.line, 0); + 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); + + textEditor.setDecorations(previewDecorationType, []); + disposable.dispose(); + } + } + }); } catch (err: any) { vscode.window.showErrorMessage( @@ -135,6 +172,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:}'); From 916ea8ca4a1560c37cbddb9c96623ea41011bcaf Mon Sep 17 00:00:00 2001 From: Falko Habel Date: Tue, 10 Sep 2024 20:34:10 +0200 Subject: [PATCH 02/32] new code with partically work preview --- src/extension.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index a738fc5..02fd31d 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -53,12 +53,13 @@ function createFIMPrompt(prefix: string, language: string): string { return `${prefix}${language}\n`; } + const previewDecorationType = vscode.window.createTextEditorDecorationType({ after: { color: '#888888', // Grayed-out preview text fontStyle: 'italic', }, - isWholeLine: true, // Support multiline properly + isWholeLine: true, // Ensure it handles multiline properly }); async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationToken?: vscode.CancellationToken) { @@ -119,12 +120,13 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo range, renderOptions: { after: { - contentText: line, // Show each line + contentText: line, // Show each line properly } } }; }); + // Set decorations for each line textEditor.setDecorations(previewDecorationType, previewRanges); const disposable = vscode.workspace.onDidChangeTextDocument(async (event) => { @@ -150,7 +152,11 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo const edit = new vscode.WorkspaceEdit(); const insertPosition = new vscode.Position(position.line, 0); edit.insert(document.uri, insertPosition, '\n' + completionText); - await vscode.workspace.applyEdit(edit); + + // Ensure response is added only once + if (!document.getText().includes(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); @@ -173,6 +179,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:}'); From 0450a222e23b91adb6debd9d5d5f572d3881491b Mon Sep 17 00:00:00 2001 From: Falko Habel Date: Wed, 11 Sep 2024 09:41:59 +0200 Subject: [PATCH 03/32] added semi working preview --- src/extension.ts | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 02fd31d..1106a6c 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -53,13 +53,10 @@ function createFIMPrompt(prefix: string, language: string): string { return `${prefix}${language}\n`; } - const previewDecorationType = vscode.window.createTextEditorDecorationType({ - after: { - color: '#888888', // Grayed-out preview text - fontStyle: 'italic', - }, - isWholeLine: true, // Ensure it handles multiline properly + 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) { @@ -111,22 +108,26 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo let completionText = response.data.response; completionText = completionText.replace(/||/g, '').trim(); - // Handle multiline text by splitting it into lines + // Split the completion text by new lines const lines = completionText.split('\n'); + + // 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); + const range = new vscode.Range(linePos, linePos); // Set range at the start of each new line return { range, renderOptions: { - after: { - contentText: line, // Show each line properly + before: { + contentText: line, + color: '#888888', + fontStyle: 'italic', } } }; }); - // Set decorations for each line + // Apply the decorations for multiline preview textEditor.setDecorations(previewDecorationType, previewRanges); const disposable = vscode.workspace.onDidChangeTextDocument(async (event) => { @@ -135,7 +136,7 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo // Handle Backspace to decline the preview if (change && change.text === '' && change.rangeLength === 1) { - textEditor.setDecorations(previewDecorationType, []); + textEditor.setDecorations(previewDecorationType, []); // Remove preview decorations disposable.dispose(); } @@ -149,20 +150,22 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo ); 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); - edit.insert(document.uri, insertPosition, '\n' + completionText); - // Ensure response is added only once + // 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); - textEditor.setDecorations(previewDecorationType, []); - disposable.dispose(); + disposable.dispose(); // Clean up the listener after accepting the completion } } }); @@ -179,7 +182,6 @@ 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:}'); From 8ac3879ee0d399f4bcaf89e0fdf44d1bc717a1e8 Mon Sep 17 00:00:00 2001 From: Falko Habel Date: Wed, 11 Sep 2024 09:49:17 +0200 Subject: [PATCH 04/32] WORKING only one insertion --- src/extension.ts | 43 +++++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 1106a6c..d2b801b 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -75,7 +75,7 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo async (progress, progressCancellationToken) => { try { progress.report({ message: "Starting model..." }); - + let axiosCancelPost: () => void; const axiosCancelToken = new axios.CancelToken((c) => { axiosCancelPost = () => { @@ -85,7 +85,7 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo progressCancellationToken.onCancellationRequested(axiosCancelPost); vscode.workspace.onDidCloseTextDocument(axiosCancelPost); }); - + const response = await axios.post(apiEndpoint, { model: apiModel, prompt: fimPrompt, @@ -102,15 +102,15 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo 'Authorization': apiAuthentication } }); - + progress.report({ message: "Generating..." }); - + let completionText = response.data.response; completionText = completionText.replace(/||/g, '').trim(); - + // Split the completion text by new lines const lines = completionText.split('\n'); - + // 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); @@ -126,20 +126,22 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo } }; }); - + // Apply the decorations for multiline preview textEditor.setDecorations(previewDecorationType, previewRanges); - + + let completionInserted = false; // Flag to track insertion + 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) => { @@ -148,28 +150,31 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo return isCtrlOrCmd; } ); - - if (isCtrlOrCmdPressed) { + + if (isCtrlOrCmdPressed && !completionInserted) { + // Ensure that we insert the completion text only once + completionInserted = true; + // 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 + + // Avoid duplicating the completion text 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( "Fabelous Autocoder encountered an error: " + err.message @@ -178,6 +183,8 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo } } ); + + } From 77e0dbc04898ef5b952bd3e65ac6ed1f21182cba Mon Sep 17 00:00:00 2001 From: Falko Habel Date: Wed, 11 Sep 2024 09:54:28 +0200 Subject: [PATCH 05/32] not working multi line support --- src/extension.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index d2b801b..15fd0fe 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -110,11 +110,16 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo // Split the completion text by new lines const lines = completionText.split('\n'); - + // 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 + // Determine the start and end positions for each line + const startPos = new vscode.Position(position.line + idx, 0); + const endPos = new vscode.Position(position.line + idx, line.length); + + // Create a range covering the whole line + const range = new vscode.Range(startPos, endPos); + return { range, renderOptions: { @@ -126,7 +131,7 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo } }; }); - + // Apply the decorations for multiline preview textEditor.setDecorations(previewDecorationType, previewRanges); From fb6e9a5d1f97a06dabbf1a1c3dd637c00d25a46b Mon Sep 17 00:00:00 2001 From: Falko Habel Date: Wed, 11 Sep 2024 09:58:26 +0200 Subject: [PATCH 06/32] new approach --- src/extension.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 15fd0fe..4b30205 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -108,23 +108,23 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo let completionText = response.data.response; completionText = completionText.replace(/||/g, '').trim(); + // Split the completion text by new lines // Split the completion text by new lines const lines = completionText.split('\n'); // Create a decoration for each line of the response const previewRanges = lines.map((line: string, idx: number) => { - // Determine the start and end positions for each line + // Determine the start position for each line const startPos = new vscode.Position(position.line + idx, 0); - const endPos = new vscode.Position(position.line + idx, line.length); - - // Create a range covering the whole line - const range = new vscode.Range(startPos, endPos); + + // Create a range that decorates the line, spanning from start to start (empty range) + const range = new vscode.Range(startPos, startPos); return { range, renderOptions: { - before: { - contentText: line, + after: { + contentText: line, // Display the current line color: '#888888', fontStyle: 'italic', } @@ -134,6 +134,7 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo // Apply the decorations for multiline preview textEditor.setDecorations(previewDecorationType, previewRanges); + let completionInserted = false; // Flag to track insertion From ac7afb4b4ec7206784a4073c8a342716d735bff8 Mon Sep 17 00:00:00 2001 From: Falko Habel Date: Wed, 11 Sep 2024 11:28:55 +0200 Subject: [PATCH 07/32] step back --- src/extension.ts | 108 ++++++++++++++++++++++------------------------- 1 file changed, 51 insertions(+), 57 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 4b30205..319ee83 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -59,10 +59,13 @@ const previewDecorationType = vscode.window.createTextEditorDecorationType({ 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; + // Get the context and create the FIM prompt const context = getContextLines(document, position); const fimPrompt = createFIMPrompt(context, document.languageId); @@ -85,7 +88,8 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo progressCancellationToken.onCancellationRequested(axiosCancelPost); vscode.workspace.onDidCloseTextDocument(axiosCancelPost); }); - + + // Make the API request const response = await axios.post(apiEndpoint, { model: apiModel, prompt: fimPrompt, @@ -107,80 +111,73 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo let completionText = response.data.response; completionText = completionText.replace(/||/g, '').trim(); - - // Split the completion text by new lines + // Split the completion text by new lines const lines = completionText.split('\n'); + + // Remove context lines and insert the preview + const startLine = Math.max(0, position.line - 1); // Start 1 line before the cursor + const endLine = position.line + 1; // End 1 line after the cursor + const rangeToReplace = new vscode.Range( + new vscode.Position(startLine, 0), + new vscode.Position(endLine, 0) + ); - // Create a decoration for each line of the response - const previewRanges = lines.map((line: string, idx: number) => { - // Determine the start position for each line - const startPos = new vscode.Position(position.line + idx, 0); - - // Create a range that decorates the line, spanning from start to start (empty range) - const range = new vscode.Range(startPos, startPos); - + // Apply grayed-out italic styling to the preview + const previewRanges = lines.map((line: string, index: number) => { + const linePos = new vscode.Position(startLine + index, 0); return { - range, + range: new vscode.Range(linePos, linePos), renderOptions: { - after: { - contentText: line, // Display the current line - color: '#888888', - fontStyle: 'italic', + before: { + contentText: line, + color: '#888888', // Grayed-out text + fontStyle: 'italic', // Italic text } } }; }); - // Apply the decorations for multiline preview + const previewDecorationType = vscode.window.createTextEditorDecorationType({ + color: '#888888', // Grayed-out color + fontStyle: 'italic', // Italic style + }); + + // Apply the preview as decoration textEditor.setDecorations(previewDecorationType, previewRanges); - - let completionInserted = false; // Flag to track insertion - + // Flag to ensure we only accept or dismiss once + let previewInserted = true; + + // Event handler to accept or dismiss the preview 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 + + // Handle Backspace to dismiss 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 && !completionInserted) { - // Ensure that we insert the completion text only once - completionInserted = true; - - // Remove the preview decoration before applying the final completion + // Remove the decoration preview textEditor.setDecorations(previewDecorationType, []); - + disposable.dispose(); // Clean up event listener + previewInserted = false; + } + + // Handle Enter to accept the preview + if (change && change.text === '\n' && previewInserted) { + // Remove the decoration preview and insert actual completion text + textEditor.setDecorations(previewDecorationType, []); // Remove decorations + const edit = new vscode.WorkspaceEdit(); - const insertPosition = new vscode.Position(position.line, 0); - - // Avoid duplicating the completion text - 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 + const acceptedText = completionText; + edit.replace(document.uri, rangeToReplace, acceptedText); // Insert actual completion + await vscode.workspace.applyEdit(edit); + + disposable.dispose(); // Clean up event listener + previewInserted = false; } } }); - + } catch (err: any) { vscode.window.showErrorMessage( "Fabelous Autocoder encountered an error: " + err.message @@ -189,12 +186,9 @@ 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:}'); From 66c88a053b40c7c688e8f3cd97c486c42f57fae7 Mon Sep 17 00:00:00 2001 From: Falko Habel Date: Mon, 23 Sep 2024 09:13:03 +0200 Subject: [PATCH 08/32] having now switech to use tab to complete completion, but the preview itself is still a mess --- package.json | 2 +- src/extension.ts | 54 +++++++++++++++++++++--------------------------- 2 files changed, 24 insertions(+), 32 deletions(-) diff --git a/package.json b/package.json index 0f2c205..6b6d545 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fabelous-autocoder", - "version": "0.1.7", + "version": "0.2.49", "displayName": "Fabelous Autocoder", "description": "A simple to use Ollama autocompletion Plugin", "icon": "icon.png", diff --git a/src/extension.ts b/src/extension.ts index 319ee83..0d06f9c 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -48,7 +48,6 @@ function getContextLines(document: vscode.TextDocument, position: vscode.Positio return lines.join("\n"); } - function createFIMPrompt(prefix: string, language: string): string { return `${prefix}${language}\n`; } @@ -59,8 +58,6 @@ const previewDecorationType = vscode.window.createTextEditorDecorationType({ 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; @@ -78,7 +75,7 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo async (progress, progressCancellationToken) => { try { progress.report({ message: "Starting model..." }); - + let axiosCancelPost: () => void; const axiosCancelToken = new axios.CancelToken((c) => { axiosCancelPost = () => { @@ -106,24 +103,25 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo 'Authorization': apiAuthentication } }); - + progress.report({ message: "Generating..." }); - + let completionText = response.data.response; completionText = completionText.replace(/||/g, '').trim(); // Split the completion text by new lines const lines = completionText.split('\n'); - - // Remove context lines and insert the preview - const startLine = Math.max(0, position.line - 1); // Start 1 line before the cursor - const endLine = position.line + 1; // End 1 line after the cursor + + // Define the preview start and end lines + const startLine = Math.max(0, position.line); // Use cursor position directly + const endLine = position.line + lines.length; + const rangeToReplace = new vscode.Range( new vscode.Position(startLine, 0), new vscode.Position(endLine, 0) ); - // Apply grayed-out italic styling to the preview + // Apply the preview with multi-line support const previewRanges = lines.map((line: string, index: number) => { const linePos = new vscode.Position(startLine + index, 0); return { @@ -138,39 +136,33 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo }; }); - const previewDecorationType = vscode.window.createTextEditorDecorationType({ - color: '#888888', // Grayed-out color - fontStyle: 'italic', // Italic style - }); - - // Apply the preview as decoration textEditor.setDecorations(previewDecorationType, previewRanges); - // Flag to ensure we only accept or dismiss once let previewInserted = true; - // Event handler to accept or dismiss the preview + // Handle preview acceptance or dismissal with Tab and Backspace const disposable = vscode.workspace.onDidChangeTextDocument(async (event) => { if (event.document.uri.toString() === document.uri.toString()) { const change = event.contentChanges[0]; - // Handle Backspace to dismiss the preview + // Dismiss preview with Backspace if (change && change.text === '' && change.rangeLength === 1) { - // Remove the decoration preview - textEditor.setDecorations(previewDecorationType, []); + textEditor.setDecorations(previewDecorationType, []); // Clear preview disposable.dispose(); // Clean up event listener previewInserted = false; } - // Handle Enter to accept the preview - if (change && change.text === '\n' && previewInserted) { - // Remove the decoration preview and insert actual completion text - textEditor.setDecorations(previewDecorationType, []); // Remove decorations - + // Accept preview with Tab key + if (change && change.text === '\t' && previewInserted) { + textEditor.setDecorations(previewDecorationType, []); // Remove preview decorations const edit = new vscode.WorkspaceEdit(); - const acceptedText = completionText; - edit.replace(document.uri, rangeToReplace, acceptedText); // Insert actual completion + + // Replace the context with the actual completion + const acceptedText = completionText; + edit.replace(document.uri, rangeToReplace, acceptedText); + await vscode.workspace.applyEdit(edit); + await document.save(); // Optionally save after replacing disposable.dispose(); // Clean up event listener previewInserted = false; @@ -192,11 +184,11 @@ 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:}'); - + if (responsePreview) { await new Promise(resolve => setTimeout(resolve, responsePreviewDelay * 1000)); if (cancellationToken.isCancellationRequested) { - return [ item ]; + return [item]; } const context = getContextLines(document, position); From 27571fcad8e60708fbbe453fcb7c7588d3fa9dd8 Mon Sep 17 00:00:00 2001 From: Falko Habel Date: Mon, 23 Sep 2024 13:25:12 +0200 Subject: [PATCH 09/32] still only single line --- src/extension.ts | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 0d06f9c..7088f95 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -112,31 +112,27 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo // Split the completion text by new lines const lines = completionText.split('\n'); - // Define the preview start and end lines - const startLine = Math.max(0, position.line); // Use cursor position directly - const endLine = position.line + lines.length; + // Define the preview start line + const startLine = position.line; - const rangeToReplace = new vscode.Range( - new vscode.Position(startLine, 0), - new vscode.Position(endLine, 0) - ); - - // Apply the preview with multi-line support + // Apply the preview line by line const previewRanges = lines.map((line: string, index: number) => { - const linePos = new vscode.Position(startLine + index, 0); + const linePos = new vscode.Position(startLine + index, 0); // Position each line on a new line + const range = new vscode.Range(linePos, linePos); // Create range for the line + return { - range: new vscode.Range(linePos, linePos), + range: range, // Range for the line renderOptions: { - before: { + after: { contentText: line, color: '#888888', // Grayed-out text - fontStyle: 'italic', // Italic text + fontStyle: 'italic', // Italic text for preview } } }; }); - textEditor.setDecorations(previewDecorationType, previewRanges); + textEditor.setDecorations(previewDecorationType, previewRanges); // Apply the decorations for the preview let previewInserted = true; @@ -159,6 +155,13 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo // Replace the context with the actual completion const acceptedText = completionText; + const endLine = startLine + lines.length; + + const rangeToReplace = new vscode.Range( + new vscode.Position(startLine, 0), + new vscode.Position(endLine, 0) + ); + edit.replace(document.uri, rangeToReplace, acceptedText); await vscode.workspace.applyEdit(edit); From 2a46e061d336a342ec60bff90dc5a716b0575fc1 Mon Sep 17 00:00:00 2001 From: Falko Habel Date: Mon, 23 Sep 2024 14:17:23 +0200 Subject: [PATCH 10/32] Updated to show now also the used contet in preview (small step progress) --- package.json | 2 +- src/extension.ts | 36 ++++++++++++++++++++++++------------ 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 6b6d545..635a727 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fabelous-autocoder", - "version": "0.2.49", + "version": "0.2.58", "displayName": "Fabelous Autocoder", "description": "A simple to use Ollama autocompletion Plugin", "icon": "icon.png", diff --git a/src/extension.ts b/src/extension.ts index 7088f95..81d65e7 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -112,14 +112,27 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo // Split the completion text by new lines const lines = completionText.split('\n'); - // Define the preview start line - const startLine = position.line; + // Define the start line and end line based on context (adjusting the preview to the same lines) + let startLine = position.line - (context.split('\n').length - 1); + if (startLine < 0) startLine = 0; // Ensure startLine is not negative + + const endLine = Math.min(startLine + lines.length, document.lineCount); // Ensure endLine doesn't exceed document line count + + // Apply the preview line by line over the context range + const previewRanges: vscode.DecorationOptions[] = lines.map((line: string, index: number): vscode.DecorationOptions | null => { + const currentLine = startLine + index; + + // Ensure the current line is within the bounds of the document + if (currentLine >= document.lineCount) return null; + + const _ = new vscode.Position(currentLine, 0); // Position for each preview line + const endOfLine = document.lineAt(currentLine).text.length; // Full range for the current line + + const range = new vscode.Range( + new vscode.Position(currentLine, 0), + new vscode.Position(currentLine, endOfLine) + ); // Create range for each line - // Apply the preview line by line - const previewRanges = lines.map((line: string, index: number) => { - const linePos = new vscode.Position(startLine + index, 0); // Position each line on a new line - const range = new vscode.Range(linePos, linePos); // Create range for the line - return { range: range, // Range for the line renderOptions: { @@ -130,9 +143,8 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo } } }; - }); - - textEditor.setDecorations(previewDecorationType, previewRanges); // Apply the decorations for the preview + }).filter((range: vscode.DecorationOptions | null): range is vscode.DecorationOptions => range !== null); // Filter out any invalid ranges + textEditor.setDecorations(previewDecorationType, previewRanges as any); // Apply the decorations for the preview let previewInserted = true; @@ -155,11 +167,10 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo // Replace the context with the actual completion const acceptedText = completionText; - const endLine = startLine + lines.length; const rangeToReplace = new vscode.Range( new vscode.Position(startLine, 0), - new vscode.Position(endLine, 0) + new vscode.Position(endLine, document.lineAt(endLine - 1).text.length) ); edit.replace(document.uri, rangeToReplace, acceptedText); @@ -184,6 +195,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:}'); From 8e72d08d53f079fc21bc5e73618450bf4074c641 Mon Sep 17 00:00:00 2001 From: Falko Habel Date: Mon, 23 Sep 2024 15:17:23 +0200 Subject: [PATCH 11/32] the preview is now working in one row, without the context --- package.json | 2 +- src/extension.ts | 69 ++++++++++++++++++++---------------------------- 2 files changed, 30 insertions(+), 41 deletions(-) diff --git a/package.json b/package.json index 635a727..23cdc39 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fabelous-autocoder", - "version": "0.2.58", + "version": "0.2.6", "displayName": "Fabelous Autocoder", "description": "A simple to use Ollama autocompletion Plugin", "icon": "icon.png", diff --git a/src/extension.ts b/src/extension.ts index 81d65e7..8b6816a 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -53,11 +53,14 @@ function createFIMPrompt(prefix: string, language: string): string { } const previewDecorationType = vscode.window.createTextEditorDecorationType({ - color: '#888888', // Grayed-out preview text - fontStyle: 'italic', - rangeBehavior: vscode.DecorationRangeBehavior.ClosedOpen, // Ensure proper handling of multiline decorations + after: { + color: '#888888', // Grayed-out preview text + fontStyle: 'italic', + }, + textDecoration: 'none; display: none;', // Hide the original text }); + async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationToken?: vscode.CancellationToken) { const document = textEditor.document; const position = textEditor.selection.active; @@ -112,73 +115,59 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo // Split the completion text by new lines const lines = completionText.split('\n'); - // Define the start line and end line based on context (adjusting the preview to the same lines) - let startLine = position.line - (context.split('\n').length - 1); - if (startLine < 0) startLine = 0; // Ensure startLine is not negative + // Define the start line based on the current position + const startLine = position.line; + const endLine = Math.min(startLine + lines.length, document.lineCount); - const endLine = Math.min(startLine + lines.length, document.lineCount); // Ensure endLine doesn't exceed document line count + // Apply the preview, replacing existing content + const previewRanges: vscode.DecorationOptions[] = []; + for (let i = 0; i < lines.length; i++) { + const currentLine = startLine + i; + if (currentLine >= document.lineCount) break; - // Apply the preview line by line over the context range - const previewRanges: vscode.DecorationOptions[] = lines.map((line: string, index: number): vscode.DecorationOptions | null => { - const currentLine = startLine + index; - - // Ensure the current line is within the bounds of the document - if (currentLine >= document.lineCount) return null; - - const _ = new vscode.Position(currentLine, 0); // Position for each preview line - const endOfLine = document.lineAt(currentLine).text.length; // Full range for the current line - - const range = new vscode.Range( - new vscode.Position(currentLine, 0), - new vscode.Position(currentLine, endOfLine) - ); // Create range for each line - - return { - range: range, // Range for the line + const range = document.lineAt(currentLine).range; + previewRanges.push({ + range, renderOptions: { after: { - contentText: line, - color: '#888888', // Grayed-out text - fontStyle: 'italic', // Italic text for preview + contentText: lines[i], } } - }; - }).filter((range: vscode.DecorationOptions | null): range is vscode.DecorationOptions => range !== null); // Filter out any invalid ranges - textEditor.setDecorations(previewDecorationType, previewRanges as any); // Apply the decorations for the preview + }); + } + + textEditor.setDecorations(previewDecorationType, previewRanges); let previewInserted = true; - // Handle preview acceptance or dismissal with Tab and Backspace + // Handle preview acceptance or dismissal const disposable = vscode.workspace.onDidChangeTextDocument(async (event) => { if (event.document.uri.toString() === document.uri.toString()) { const change = event.contentChanges[0]; // Dismiss preview with Backspace if (change && change.text === '' && change.rangeLength === 1) { - textEditor.setDecorations(previewDecorationType, []); // Clear preview - disposable.dispose(); // Clean up event listener + textEditor.setDecorations(previewDecorationType, []); + disposable.dispose(); previewInserted = false; } // Accept preview with Tab key if (change && change.text === '\t' && previewInserted) { - textEditor.setDecorations(previewDecorationType, []); // Remove preview decorations + textEditor.setDecorations(previewDecorationType, []); const edit = new vscode.WorkspaceEdit(); - // Replace the context with the actual completion - const acceptedText = completionText; - const rangeToReplace = new vscode.Range( new vscode.Position(startLine, 0), - new vscode.Position(endLine, document.lineAt(endLine - 1).text.length) + new vscode.Position(endLine, document.lineAt(endLine - 1).range.end.character) ); - edit.replace(document.uri, rangeToReplace, acceptedText); + edit.replace(document.uri, rangeToReplace, completionText); await vscode.workspace.applyEdit(edit); await document.save(); // Optionally save after replacing - disposable.dispose(); // Clean up event listener + disposable.dispose(); previewInserted = false; } } From 9109c199e656f0c09201d364d0bd814866e3e2e5 Mon Sep 17 00:00:00 2001 From: Falko Habel Date: Wed, 2 Oct 2024 15:40:11 +0300 Subject: [PATCH 12/32] so preview and complition is technially working, but it overwrites in preview mode the current code --- package.json | 2 +- src/extension.ts | 61 ++++++++++++++++++++++++++++++++---------------- 2 files changed, 42 insertions(+), 21 deletions(-) diff --git a/package.json b/package.json index 23cdc39..3744b0b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fabelous-autocoder", - "version": "0.2.6", + "version": "0.2.61", "displayName": "Fabelous Autocoder", "description": "A simple to use Ollama autocompletion Plugin", "icon": "icon.png", diff --git a/src/extension.ts b/src/extension.ts index 8b6816a..f37b3df 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -48,6 +48,7 @@ function getContextLines(document: vscode.TextDocument, position: vscode.Positio return lines.join("\n"); } + function createFIMPrompt(prefix: string, language: string): string { return `${prefix}${language}\n`; } @@ -59,14 +60,26 @@ const previewDecorationType = vscode.window.createTextEditorDecorationType({ }, textDecoration: 'none; display: none;', // Hide the original text }); +// Generate extra lines for the preview +function generateExtraPreviewLines(document: vscode.TextDocument, position: vscode.Position, numLines: number): string[] { + const extraLines = []; + const startLine = position.line + 1; + const endLine = Math.min(document.lineCount - 1, startLine + numLines - 1); + for (let i = startLine; i <= endLine; i++) { + extraLines.push(document.lineAt(i).text); + } + + return extraLines; +} async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationToken?: vscode.CancellationToken) { const document = textEditor.document; const position = textEditor.selection.active; - - // Get the context and create the FIM prompt + const contextLines = 2; + const startLine = Math.max(0, position.line - contextLines); const context = getContextLines(document, position); + const fimPrompt = createFIMPrompt(context, document.languageId); vscode.window.withProgress( @@ -89,6 +102,9 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo vscode.workspace.onDidCloseTextDocument(axiosCancelPost); }); + // Increase the number of tokens to predict + const extendedNumPredict = numPredict * 2; // Adjust this multiplier as needed + // Make the API request const response = await axios.post(apiEndpoint, { model: apiModel, @@ -96,7 +112,7 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo stream: false, raw: true, options: { - num_predict: numPredict, + num_predict: extendedNumPredict, temperature: apiTemperature, stop: ["", "```"] } @@ -113,24 +129,25 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo completionText = completionText.replace(/||/g, '').trim(); // Split the completion text by new lines - const lines = completionText.split('\n'); + const newLines = completionText.split('\n'); - // Define the start line based on the current position - const startLine = position.line; - const endLine = Math.min(startLine + lines.length, document.lineCount); + // Calculate the number of new lines in the completion + const completionLineCount = newLines.length; - // Apply the preview, replacing existing content + // Ensure we have at least as many new lines as the completion, plus some extra + const extraLines = 1; // You can adjust this number + const totalNewLines = Math.max(completionLineCount + extraLines, position.line - startLine + 1); + + // Create preview decorations const previewRanges: vscode.DecorationOptions[] = []; - for (let i = 0; i < lines.length; i++) { - const currentLine = startLine + i; - if (currentLine >= document.lineCount) break; - - const range = document.lineAt(currentLine).range; + for (let i = 0; i < totalNewLines; i++) { + const lineContent = i < newLines.length ? newLines[i] : ''; + const range = new vscode.Range(startLine + i, 0, startLine + i + 1, 0); previewRanges.push({ range, renderOptions: { after: { - contentText: lines[i], + contentText: lineContent, } } }); @@ -157,15 +174,19 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo textEditor.setDecorations(previewDecorationType, []); const edit = new vscode.WorkspaceEdit(); - const rangeToReplace = new vscode.Range( - new vscode.Position(startLine, 0), - new vscode.Position(endLine, document.lineAt(endLine - 1).range.end.character) - ); + // Create the text to insert: completion text plus extra newlines + const insertText = completionText + '\n'.repeat(Math.max(0, totalNewLines - completionLineCount)); - edit.replace(document.uri, rangeToReplace, completionText); + // Replace the context with the new text + const replaceRange = new vscode.Range(startLine, 0, position.line + 1, 0); + edit.replace(document.uri, replaceRange, insertText); await vscode.workspace.applyEdit(edit); - await document.save(); // Optionally save after replacing + await document.save(); // Optionally save after inserting + + // Move the cursor to the end of the inserted completion + const newPosition = new vscode.Position(startLine + totalNewLines, 0); + textEditor.selection = new vscode.Selection(newPosition, newPosition); disposable.dispose(); previewInserted = false; From 0416896254bdb947b35f8e4e75a01f7903fb56a6 Mon Sep 17 00:00:00 2001 From: Falko Habel Date: Wed, 2 Oct 2024 15:48:08 +0300 Subject: [PATCH 13/32] fixed bug for showing header --- package.json | 2 +- src/extension.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 3744b0b..10be8e5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fabelous-autocoder", - "version": "0.2.61", + "version": "0.2.74", "displayName": "Fabelous Autocoder", "description": "A simple to use Ollama autocompletion Plugin", "icon": "icon.png", diff --git a/src/extension.ts b/src/extension.ts index f37b3df..ac30149 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -178,7 +178,7 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo const insertText = completionText + '\n'.repeat(Math.max(0, totalNewLines - completionLineCount)); // Replace the context with the new text - const replaceRange = new vscode.Range(startLine, 0, position.line + 1, 0); + const replaceRange = new vscode.Range(startLine, 0, position.line, 0); edit.replace(document.uri, replaceRange, insertText); await vscode.workspace.applyEdit(edit); From ffd22f7cc6fb642b3bc6346f06db074471f33ba8 Mon Sep 17 00:00:00 2001 From: Falko Habel Date: Thu, 3 Oct 2024 11:43:47 +0300 Subject: [PATCH 14/32] fixed --- src/extension.ts | 107 +++++++++++++++++++---------------------------- 1 file changed, 44 insertions(+), 63 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index ac30149..d44e9e3 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -60,18 +60,6 @@ const previewDecorationType = vscode.window.createTextEditorDecorationType({ }, textDecoration: 'none; display: none;', // Hide the original text }); -// Generate extra lines for the preview -function generateExtraPreviewLines(document: vscode.TextDocument, position: vscode.Position, numLines: number): string[] { - const extraLines = []; - const startLine = position.line + 1; - const endLine = Math.min(document.lineCount - 1, startLine + numLines - 1); - - for (let i = startLine; i <= endLine; i++) { - extraLines.push(document.lineAt(i).text); - } - - return extraLines; -} async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationToken?: vscode.CancellationToken) { const document = textEditor.document; @@ -102,9 +90,6 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo vscode.workspace.onDidCloseTextDocument(axiosCancelPost); }); - // Increase the number of tokens to predict - const extendedNumPredict = numPredict * 2; // Adjust this multiplier as needed - // Make the API request const response = await axios.post(apiEndpoint, { model: apiModel, @@ -112,7 +97,7 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo stream: false, raw: true, options: { - num_predict: extendedNumPredict, + num_predict: numPredict, temperature: apiTemperature, stop: ["", "```"] } @@ -131,12 +116,8 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo // Split the completion text by new lines const newLines = completionText.split('\n'); - // Calculate the number of new lines in the completion - const completionLineCount = newLines.length; - - // Ensure we have at least as many new lines as the completion, plus some extra - const extraLines = 1; // You can adjust this number - const totalNewLines = Math.max(completionLineCount + extraLines, position.line - startLine + 1); + // Calculate the number of new lines in the completion, plus 2 extra lines + const totalNewLines = newLines.length + 2; // Create preview decorations const previewRanges: vscode.DecorationOptions[] = []; @@ -158,54 +139,54 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo let previewInserted = true; // Handle preview acceptance or dismissal - const disposable = vscode.workspace.onDidChangeTextDocument(async (event) => { - if (event.document.uri.toString() === document.uri.toString()) { - const change = event.contentChanges[0]; + // Handle preview acceptance or dismissal + const disposable = vscode.workspace.onDidChangeTextDocument(async (event) => { + if (event.document.uri.toString() === document.uri.toString()) { + const change = event.contentChanges[0]; - // Dismiss preview with Backspace - if (change && change.text === '' && change.rangeLength === 1) { - textEditor.setDecorations(previewDecorationType, []); - disposable.dispose(); - previewInserted = false; - } - - // Accept preview with Tab key - if (change && change.text === '\t' && previewInserted) { - textEditor.setDecorations(previewDecorationType, []); - const edit = new vscode.WorkspaceEdit(); - - // Create the text to insert: completion text plus extra newlines - const insertText = completionText + '\n'.repeat(Math.max(0, totalNewLines - completionLineCount)); - - // Replace the context with the new text - const replaceRange = new vscode.Range(startLine, 0, position.line, 0); - edit.replace(document.uri, replaceRange, insertText); - - await vscode.workspace.applyEdit(edit); - await document.save(); // Optionally save after inserting - - // Move the cursor to the end of the inserted completion - const newPosition = new vscode.Position(startLine + totalNewLines, 0); - textEditor.selection = new vscode.Selection(newPosition, newPosition); - - disposable.dispose(); - previewInserted = false; - } + // Dismiss preview with Backspace + if (change && change.text === '' && change.rangeLength === 1) { + textEditor.setDecorations(previewDecorationType, []); + disposable.dispose(); + previewInserted = false; } - }); - } catch (err: any) { - vscode.window.showErrorMessage( - "Fabelous Autocoder encountered an error: " + err.message - ); - console.log(err); - } + // Accept preview with Tab key + if (change && change.text === '\t' && previewInserted) { + textEditor.setDecorations(previewDecorationType, []); + const edit = new vscode.WorkspaceEdit(); + + // Insert the completion text + const insertText = '\n'.repeat(1) + completionText + '\n'.repeat(1); // Add 2 extra newlines + + // Replace the entire range from the start of the context to the current position + const replaceRange = new vscode.Range(startLine, 0, position.line, position.character); + edit.replace(document.uri, replaceRange, insertText); + + await vscode.workspace.applyEdit(edit); + await document.save(); // Optionally save after inserting + + // Move the cursor to the end of the inserted completion + const newPosition = new vscode.Position(startLine + totalNewLines - 2, 0); + textEditor.selection = new vscode.Selection(newPosition, newPosition); + + disposable.dispose(); + previewInserted = false; + } + } + }); + + } 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:}'); From 6a953b7a126770f1229a87e4f6c78d62a37a3de1 Mon Sep 17 00:00:00 2001 From: Falko Habel Date: Thu, 3 Oct 2024 12:03:05 +0300 Subject: [PATCH 15/32] preview extra lines --- src/extension.ts | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index d44e9e3..d233ffa 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -119,11 +119,21 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo // Calculate the number of new lines in the completion, plus 2 extra lines const totalNewLines = newLines.length + 2; - // Create preview decorations + // Create preview decorations and insert new lines const previewRanges: vscode.DecorationOptions[] = []; + const edit = new vscode.WorkspaceEdit(); + const document = textEditor.document; + for (let i = 0; i < totalNewLines; i++) { const lineContent = i < newLines.length ? newLines[i] : ''; - const range = new vscode.Range(startLine + i, 0, startLine + i + 1, 0); + const position = new vscode.Position(startLine + i, 0); + + // Insert a new line + edit.insert(document.uri, position, '\n'); + + // Create a range for the newly inserted line + const range = new vscode.Range(position, position.translate(1, 0)); + previewRanges.push({ range, renderOptions: { @@ -134,11 +144,14 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo }); } + // Apply the edit to insert new lines + await vscode.workspace.applyEdit(edit); + + // Set decorations on the newly inserted lines textEditor.setDecorations(previewDecorationType, previewRanges); let previewInserted = true; - // Handle preview acceptance or dismissal // Handle preview acceptance or dismissal const disposable = vscode.workspace.onDidChangeTextDocument(async (event) => { if (event.document.uri.toString() === document.uri.toString()) { From 77e328fceef0b9f056502067576293ab6fc75efb Mon Sep 17 00:00:00 2001 From: Falko Habel Date: Thu, 3 Oct 2024 12:20:38 +0300 Subject: [PATCH 16/32] near final --- src/extension.ts | 170 +++++++++++++++++++++++++---------------------- 1 file changed, 91 insertions(+), 79 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index d233ffa..0e417f5 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -61,6 +61,7 @@ const previewDecorationType = vscode.window.createTextEditorDecorationType({ textDecoration: 'none; display: none;', // Hide the original text }); + async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationToken?: vscode.CancellationToken) { const document = textEditor.document; const position = textEditor.selection.active; @@ -113,90 +114,101 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo let completionText = response.data.response; completionText = completionText.replace(/||/g, '').trim(); - // Split the completion text by new lines - const newLines = completionText.split('\n'); + let previewInserted = false; + let originalContent: string; + let previewStartLine: number; - // Calculate the number of new lines in the completion, plus 2 extra lines - const totalNewLines = newLines.length + 2; + // Store the original content and insert preview +const storeAndInsertPreview = async () => { + previewStartLine = startLine; + const endLine = document.lineCount - 1; + const endCharacter = document.lineAt(endLine).text.length; + const fullRange = new vscode.Range(startLine, 0, endLine, endCharacter); + originalContent = document.getText(fullRange); - // Create preview decorations and insert new lines - const previewRanges: vscode.DecorationOptions[] = []; - const edit = new vscode.WorkspaceEdit(); - const document = textEditor.document; + const previewContent = completionText + '\n'.repeat(1); + const edit = new vscode.WorkspaceEdit(); + // Overwrite the context by replacing instead of inserting + edit.replace(document.uri, fullRange, previewContent); + await vscode.workspace.applyEdit(edit); - for (let i = 0; i < totalNewLines; i++) { - const lineContent = i < newLines.length ? newLines[i] : ''; - const position = new vscode.Position(startLine + i, 0); - - // Insert a new line - edit.insert(document.uri, position, '\n'); - - // Create a range for the newly inserted line - const range = new vscode.Range(position, position.translate(1, 0)); - - previewRanges.push({ - range, - renderOptions: { - after: { - contentText: lineContent, - } - } - }); + // Set decorations on the newly inserted lines + const previewRanges: vscode.DecorationOptions[] = []; + const previewLines = previewContent.split('\n'); + for (let i = 0; i < previewLines.length; i++) { + const range = new vscode.Range(startLine + i, 0, startLine + i + 1, 0); + previewRanges.push({ + range, + renderOptions: { + after: { + contentText: previewLines[i], } - - // Apply the edit to insert new lines - await vscode.workspace.applyEdit(edit); - - // Set decorations on the newly inserted lines - textEditor.setDecorations(previewDecorationType, previewRanges); - - let previewInserted = true; - - // Handle preview acceptance or dismissal - const disposable = vscode.workspace.onDidChangeTextDocument(async (event) => { - if (event.document.uri.toString() === document.uri.toString()) { - const change = event.contentChanges[0]; - - // Dismiss preview with Backspace - if (change && change.text === '' && change.rangeLength === 1) { - textEditor.setDecorations(previewDecorationType, []); - disposable.dispose(); - previewInserted = false; - } - - // Accept preview with Tab key - if (change && change.text === '\t' && previewInserted) { - textEditor.setDecorations(previewDecorationType, []); - const edit = new vscode.WorkspaceEdit(); - - // Insert the completion text - const insertText = '\n'.repeat(1) + completionText + '\n'.repeat(1); // Add 2 extra newlines - - // Replace the entire range from the start of the context to the current position - const replaceRange = new vscode.Range(startLine, 0, position.line, position.character); - edit.replace(document.uri, replaceRange, insertText); - - await vscode.workspace.applyEdit(edit); - await document.save(); // Optionally save after inserting - - // Move the cursor to the end of the inserted completion - const newPosition = new vscode.Position(startLine + totalNewLines - 2, 0); - textEditor.selection = new vscode.Selection(newPosition, newPosition); - - disposable.dispose(); - previewInserted = false; - } - } - }); - - } catch (err: any) { - vscode.window.showErrorMessage( - "Fabelous Autocoder encountered an error: " + err.message - ); - console.log(err); - } + } + }); } -); + textEditor.setDecorations(previewDecorationType, previewRanges); + previewInserted = true; +}; + + + // Handle preview acceptance or dismissal + const disposable = vscode.workspace.onDidChangeTextDocument(async (event) => { + if (event.document.uri.toString() === document.uri.toString() && previewInserted) { + const change = event.contentChanges[0]; + + // Dismiss preview with Backspace + if (change && change.text === '' && change.rangeLength === 1) { + await restoreOriginalContent(); + } + + // Accept preview with Tab key + if (change && change.text === '\t') { + await acceptPreview(); + } + } + }); + + const restoreOriginalContent = async () => { + const edit = new vscode.WorkspaceEdit(); + const endLine = document.lineCount - 1; + const endCharacter = document.lineAt(endLine).text.length; + const fullRange = new vscode.Range(previewStartLine, 0, endLine, endCharacter); + edit.replace(document.uri, fullRange, originalContent); + await vscode.workspace.applyEdit(edit); + textEditor.setDecorations(previewDecorationType, []); + previewInserted = false; + disposable.dispose(); + }; + + const acceptPreview = async () => { + textEditor.setDecorations(previewDecorationType, []); + const edit = new vscode.WorkspaceEdit(); + const endLine = document.lineCount - 1; + const endCharacter = document.lineAt(endLine).text.length; + const fullRange = new vscode.Range(previewStartLine, 0, endLine, endCharacter); + edit.replace(document.uri, fullRange, completionText); + await vscode.workspace.applyEdit(edit); + await document.save(); + + // Move the cursor to the end of the inserted completion + const newPosition = new vscode.Position(previewStartLine + completionText.split('\n').length - 1, 0); + textEditor.selection = new vscode.Selection(newPosition, newPosition); + + previewInserted = false; + disposable.dispose(); + }; + + // Call this function to initiate the preview + await storeAndInsertPreview(); + + } catch (err: any) { + vscode.window.showErrorMessage( + "Fabelous Autocoder encountered an error: " + err.message + ); + console.log(err); + } + } + ); } From ccd35283253f0e20a298f03f3a8c62a09331264b Mon Sep 17 00:00:00 2001 From: Falko Habel Date: Thu, 3 Oct 2024 12:41:27 +0300 Subject: [PATCH 17/32] minor incosnitentcy with removing --- src/extension.ts | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 0e417f5..1ec7535 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -119,6 +119,7 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo let previewStartLine: number; // Store the original content and insert preview +// Store the original content and insert preview const storeAndInsertPreview = async () => { previewStartLine = startLine; const endLine = document.lineCount - 1; @@ -128,15 +129,18 @@ const storeAndInsertPreview = async () => { const previewContent = completionText + '\n'.repeat(1); const edit = new vscode.WorkspaceEdit(); - // Overwrite the context by replacing instead of inserting - edit.replace(document.uri, fullRange, previewContent); + edit.replace(document.uri, fullRange, previewContent); // Overwrite the context await vscode.workspace.applyEdit(edit); + // Move the cursor to the start of the inserted content for easy dismissal + const newPosition = new vscode.Position(previewStartLine, 0); + textEditor.selection = new vscode.Selection(newPosition, newPosition); + // Set decorations on the newly inserted lines const previewRanges: vscode.DecorationOptions[] = []; const previewLines = previewContent.split('\n'); for (let i = 0; i < previewLines.length; i++) { - const range = new vscode.Range(startLine + i, 0, startLine + i + 1, 0); + const range = new vscode.Range(previewStartLine + i, 0, previewStartLine + i + 1, 0); previewRanges.push({ range, renderOptions: { @@ -160,7 +164,6 @@ const storeAndInsertPreview = async () => { if (change && change.text === '' && change.rangeLength === 1) { await restoreOriginalContent(); } - // Accept preview with Tab key if (change && change.text === '\t') { await acceptPreview(); @@ -178,7 +181,15 @@ const storeAndInsertPreview = async () => { textEditor.setDecorations(previewDecorationType, []); previewInserted = false; disposable.dispose(); + + // Move the cursor to the end of the original content + const originalLines = originalContent.split('\n'); + const lastLine = previewStartLine + originalLines.length - 1; + const lastLineLength = originalLines[originalLines.length - 1].length; + const newPosition = new vscode.Position(lastLine, lastLineLength); + textEditor.selection = new vscode.Selection(newPosition, newPosition); }; + const acceptPreview = async () => { textEditor.setDecorations(previewDecorationType, []); @@ -191,13 +202,17 @@ const storeAndInsertPreview = async () => { await document.save(); // Move the cursor to the end of the inserted completion - const newPosition = new vscode.Position(previewStartLine + completionText.split('\n').length - 1, 0); + const completionLines = completionText.split('\n'); + const lastLine = previewStartLine + completionLines.length - 1; + const lastLineLength = completionLines[completionLines.length - 1].length; + const newPosition = new vscode.Position(lastLine, lastLineLength); textEditor.selection = new vscode.Selection(newPosition, newPosition); previewInserted = false; disposable.dispose(); }; + // Call this function to initiate the preview await storeAndInsertPreview(); From b6241855fc02d2e8b7161658193fb5bbaee56fb4 Mon Sep 17 00:00:00 2001 From: Falko Habel Date: Fri, 4 Oct 2024 13:00:14 +0300 Subject: [PATCH 18/32] bug in approving --- src/extension.ts | 93 +++++++++++++++++++++++++++--------------------- 1 file changed, 53 insertions(+), 40 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 1ec7535..cfa1354 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -118,8 +118,7 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo let originalContent: string; let previewStartLine: number; - // Store the original content and insert preview -// Store the original content and insert preview + // Store the original content and insert preview const storeAndInsertPreview = async () => { previewStartLine = startLine; const endLine = document.lineCount - 1; @@ -132,13 +131,15 @@ const storeAndInsertPreview = async () => { edit.replace(document.uri, fullRange, previewContent); // Overwrite the context await vscode.workspace.applyEdit(edit); - // Move the cursor to the start of the inserted content for easy dismissal - const newPosition = new vscode.Position(previewStartLine, 0); + // Move the cursor to the end of the inserted content + const previewLines = previewContent.split('\n'); + const lastPreviewLineIndex = previewStartLine + previewLines.length - 1; + const lastPreviewLine = previewLines[previewLines.length - 1]; + const newPosition = new vscode.Position(lastPreviewLineIndex, lastPreviewLine.length); textEditor.selection = new vscode.Selection(newPosition, newPosition); // Set decorations on the newly inserted lines const previewRanges: vscode.DecorationOptions[] = []; - const previewLines = previewContent.split('\n'); for (let i = 0; i < previewLines.length; i++) { const range = new vscode.Range(previewStartLine + i, 0, previewStartLine + i + 1, 0); previewRanges.push({ @@ -154,62 +155,74 @@ const storeAndInsertPreview = async () => { previewInserted = true; }; + const disposable = vscode.workspace.onDidChangeTextDocument(async (event) => { + if (event.document.uri.toString() === document.uri.toString() && previewInserted) { + console.log("URI matches and preview inserted"); + if (event.contentChanges.length > 0) { + const change = event.contentChanges[0]; + console.log("Change:", change); - // Handle preview acceptance or dismissal - const disposable = vscode.workspace.onDidChangeTextDocument(async (event) => { - if (event.document.uri.toString() === document.uri.toString() && previewInserted) { - const change = event.contentChanges[0]; - - // Dismiss preview with Backspace - if (change && change.text === '' && change.rangeLength === 1) { - await restoreOriginalContent(); - } - // Accept preview with Tab key - if (change && change.text === '\t') { - await acceptPreview(); - } - } - }); + // Accept preview with Tab key + if (change && change.text === '\t' && previewInserted) { + console.log("Tab key pressed, accepting preview"); + await acceptPreview(textEditor, document, startLine, position, completionText); + } + // Keep the existing backspace functionality + if (change.text === '' && change.range.start.line >= previewStartLine) { + console.log("Backspace detected"); + await restoreOriginalContent(); + } + } + } + }); const restoreOriginalContent = async () => { - const edit = new vscode.WorkspaceEdit(); + if (!previewInserted) return; + const endLine = document.lineCount - 1; - const endCharacter = document.lineAt(endLine).text.length; - const fullRange = new vscode.Range(previewStartLine, 0, endLine, endCharacter); + const fullRange = new vscode.Range(previewStartLine, 0, endLine, document.lineAt(endLine).text.length); + const edit = new vscode.WorkspaceEdit(); + edit.replace(document.uri, fullRange, originalContent); await vscode.workspace.applyEdit(edit); + textEditor.setDecorations(previewDecorationType, []); previewInserted = false; disposable.dispose(); - - // Move the cursor to the end of the original content - const originalLines = originalContent.split('\n'); - const lastLine = previewStartLine + originalLines.length - 1; - const lastLineLength = originalLines[originalLines.length - 1].length; - const newPosition = new vscode.Position(lastLine, lastLineLength); + + // Move cursor back to the end of the original content + const lastOriginalLineNumber = previewStartLine + originalContent.split('\n').length - 1; + const lastLineLength = originalContent.split('\n').pop()?.length || 0; + const newPosition = new vscode.Position(lastOriginalLineNumber, lastLineLength); textEditor.selection = new vscode.Selection(newPosition, newPosition); }; + - const acceptPreview = async () => { + // Modify the acceptPreview function to match the working version + const acceptPreview = async (textEditor: vscode.TextEditor, document: vscode.TextDocument, startLine: number, position: vscode.Position, completionText: string) => { + console.log("Accepting preview"); textEditor.setDecorations(previewDecorationType, []); const edit = new vscode.WorkspaceEdit(); - const endLine = document.lineCount - 1; - const endCharacter = document.lineAt(endLine).text.length; - const fullRange = new vscode.Range(previewStartLine, 0, endLine, endCharacter); - edit.replace(document.uri, fullRange, completionText); + + // Insert the completion text + const insertText = '\n'.repeat(1) + completionText + '\n'.repeat(1); // Add 2 extra newlines + + // Replace the entire range from the start of the context to the current position + const replaceRange = new vscode.Range(startLine, 0, position.line, position.character); + edit.replace(document.uri, replaceRange, insertText); + await vscode.workspace.applyEdit(edit); - await document.save(); + await document.save(); // Optionally save after inserting // Move the cursor to the end of the inserted completion - const completionLines = completionText.split('\n'); - const lastLine = previewStartLine + completionLines.length - 1; - const lastLineLength = completionLines[completionLines.length - 1].length; - const newPosition = new vscode.Position(lastLine, lastLineLength); + const totalNewLines = insertText.split('\n').length; + const newPosition = new vscode.Position(startLine + totalNewLines - 2, 0); textEditor.selection = new vscode.Selection(newPosition, newPosition); - previewInserted = false; disposable.dispose(); + previewInserted = false; + console.log("Preview accepted"); }; From d8e97c2b4efb9944e89134f3365f1531c19fbb93 Mon Sep 17 00:00:00 2001 From: Falko Habel Date: Fri, 4 Oct 2024 18:26:01 +0300 Subject: [PATCH 19/32] stable version 0.2.0, now providing preview pressing double tab to accept preview --- package.json | 9 ++- src/extension.ts | 140 +++++++++++++++++++------------------ src/test.py | 175 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 252 insertions(+), 72 deletions(-) create mode 100644 src/test.py diff --git a/package.json b/package.json index 10be8e5..53d6a68 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fabelous-autocoder", - "version": "0.2.74", + "version": "0.2.0", "displayName": "Fabelous Autocoder", "description": "A simple to use Ollama autocompletion Plugin", "icon": "icon.png", @@ -110,10 +110,9 @@ }, "commands": [ { - "command": "fabelous-autocoder.autocomplete", - "title": "Fabelous autocompletion" - } - ] + "command": "fabelous-autocoder.autocomplete", + "title": "Fabelous Autocompletion" + }] }, "scripts": { "vscode:prepublish": "npm run compile", diff --git a/src/extension.ts b/src/extension.ts index cfa1354..acaa133 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -61,7 +61,6 @@ const previewDecorationType = vscode.window.createTextEditorDecorationType({ textDecoration: 'none; display: none;', // Hide the original text }); - async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationToken?: vscode.CancellationToken) { const document = textEditor.document; const position = textEditor.selection.active; @@ -117,65 +116,74 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo let previewInserted = false; let originalContent: string; let previewStartLine: number; + const storeAndInsertPreview = async () => { + previewStartLine = startLine; + const endLine = document.lineCount - 1; + const endCharacter = document.lineAt(endLine).text.length; + const fullRange = new vscode.Range(startLine, 0, endLine, endCharacter); + originalContent = document.getText(fullRange); + + const previewContent = completionText + '\n'.repeat(1); + const edit = new vscode.WorkspaceEdit(); + edit.replace(document.uri, fullRange, previewContent); // Overwrite the content + await vscode.workspace.applyEdit(edit); + + // Split the preview content into lines + const previewLines = previewContent.split('\n'); + + // Calculate the exact position of the last character of the preview content + const lastPreviewLineIndex = previewStartLine + previewLines.length - 1; + const lastPreviewLine = previewLines[previewLines.length - 2]; // Second to last line (last line is newline) + const newPosition = new vscode.Position(lastPreviewLineIndex - 1, lastPreviewLine.length); // Place cursor at end of last line + + // Set the new cursor position after inserting preview + textEditor.selection = new vscode.Selection(newPosition, newPosition); + + // Explicitly scroll to reveal the cursor position at the end of the preview + textEditor.revealRange(new vscode.Range(newPosition, newPosition), vscode.TextEditorRevealType.InCenter); + + // Set decorations on the newly inserted lines + const previewRanges: vscode.DecorationOptions[] = []; + for (let i = 0; i < previewLines.length; i++) { + const range = new vscode.Range(previewStartLine + i, 0, previewStartLine + i, previewLines[i].length); + previewRanges.push({ + range, + renderOptions: { + after: { + contentText: previewLines[i], + } + } + }); + } + textEditor.setDecorations(previewDecorationType, previewRanges); + previewInserted = true; + }; + + const disposable = vscode.workspace.onDidChangeTextDocument(async (event) => { + const textEditor = vscode.window.activeTextEditor; + if (!textEditor || event.document.uri.toString() !== textEditor.document.uri.toString() || !previewInserted) { + return; + } + + // Determine tab size setting + const tabSize = textEditor.options.tabSize as number || 4; + + for (const change of event.contentChanges) { + const changeStartLine = change.range.start.line; + + // Detect Backspace by a single-character deletion + if (change.text === '' && change.rangeLength === 1 && changeStartLine >= previewStartLine) { + await restoreOriginalContent(); + } + + // Detect Tab by checking if the change is a multiple of spaces equal to the tab size + if (/^[ ]+$/.test(change.text) && change.text.length === tabSize && changeStartLine >= previewStartLine) { + await acceptPreview(textEditor, document, startLine, position, completionText); + } + } + }); - // Store the original content and insert preview -const storeAndInsertPreview = async () => { - previewStartLine = startLine; - const endLine = document.lineCount - 1; - const endCharacter = document.lineAt(endLine).text.length; - const fullRange = new vscode.Range(startLine, 0, endLine, endCharacter); - originalContent = document.getText(fullRange); - - const previewContent = completionText + '\n'.repeat(1); - const edit = new vscode.WorkspaceEdit(); - edit.replace(document.uri, fullRange, previewContent); // Overwrite the context - await vscode.workspace.applyEdit(edit); - - // Move the cursor to the end of the inserted content - const previewLines = previewContent.split('\n'); - const lastPreviewLineIndex = previewStartLine + previewLines.length - 1; - const lastPreviewLine = previewLines[previewLines.length - 1]; - const newPosition = new vscode.Position(lastPreviewLineIndex, lastPreviewLine.length); - textEditor.selection = new vscode.Selection(newPosition, newPosition); - - // Set decorations on the newly inserted lines - const previewRanges: vscode.DecorationOptions[] = []; - for (let i = 0; i < previewLines.length; i++) { - const range = new vscode.Range(previewStartLine + i, 0, previewStartLine + i + 1, 0); - previewRanges.push({ - range, - renderOptions: { - after: { - contentText: previewLines[i], - } - } - }); - } - textEditor.setDecorations(previewDecorationType, previewRanges); - previewInserted = true; -}; - - const disposable = vscode.workspace.onDidChangeTextDocument(async (event) => { - if (event.document.uri.toString() === document.uri.toString() && previewInserted) { - console.log("URI matches and preview inserted"); - if (event.contentChanges.length > 0) { - const change = event.contentChanges[0]; - console.log("Change:", change); - - // Accept preview with Tab key - if (change && change.text === '\t' && previewInserted) { - console.log("Tab key pressed, accepting preview"); - await acceptPreview(textEditor, document, startLine, position, completionText); - } - - // Keep the existing backspace functionality - if (change.text === '' && change.range.start.line >= previewStartLine) { - console.log("Backspace detected"); - await restoreOriginalContent(); - } - } - } - }); + const restoreOriginalContent = async () => { if (!previewInserted) return; @@ -196,36 +204,33 @@ const storeAndInsertPreview = async () => { const newPosition = new vscode.Position(lastOriginalLineNumber, lastLineLength); textEditor.selection = new vscode.Selection(newPosition, newPosition); }; - - // Modify the acceptPreview function to match the working version const acceptPreview = async (textEditor: vscode.TextEditor, document: vscode.TextDocument, startLine: number, position: vscode.Position, completionText: string) => { console.log("Accepting preview"); textEditor.setDecorations(previewDecorationType, []); const edit = new vscode.WorkspaceEdit(); - + // Insert the completion text const insertText = '\n'.repeat(1) + completionText + '\n'.repeat(1); // Add 2 extra newlines - + // Replace the entire range from the start of the context to the current position const replaceRange = new vscode.Range(startLine, 0, position.line, position.character); edit.replace(document.uri, replaceRange, insertText); - + await vscode.workspace.applyEdit(edit); await document.save(); // Optionally save after inserting - + // Move the cursor to the end of the inserted completion const totalNewLines = insertText.split('\n').length; const newPosition = new vscode.Position(startLine + totalNewLines - 2, 0); textEditor.selection = new vscode.Selection(newPosition, newPosition); - + disposable.dispose(); previewInserted = false; console.log("Preview accepted"); }; - // Call this function to initiate the preview await storeAndInsertPreview(); @@ -240,6 +245,7 @@ const storeAndInsertPreview = async () => { } + 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:}'); diff --git a/src/test.py b/src/test.py new file mode 100644 index 0000000..8dfb3f9 --- /dev/null +++ b/src/test.py @@ -0,0 +1,175 @@ + + + + + +def quicksort(arr): + if len(arr) <= 1: + return arr + else: + pivot = arr[len(arr)//2] # choose the middle element as the pivot + left_partition = [i for i in arr if i < pivot] + right_partition = [i for i in arr if i > pivot] + + return quicksort(left_partition) + [pivot] + quicksort(right_partition) + + +def quicksort(arr): + if len(arr) <= 1: + return arr + mid = int(len(arr)/2) + left = [item for i, item in enumerate(arr) if i=mid] + + return quicksort(left)+[arr[mid]]+quicksort(right) +def is_fibunccai(num): + num = str(num) + size = len(num) + + # Create 1s array for DP table to store last 10 digits of Fibonacci sequence + fib = [1] * 10 + + # Initialize last two numbers in Fibonacci sequence as 1 and 2 +def binary_search(arr, num) : + left = 0; right = len(arr) - 1 + + while (left <= right) : + mid = int((right + left) / 2) + + if arr[mid] == num : + return mid + + elif arr[mid] > num : + right = mid - 1 + + else: + left = mid + 1 + + return -1 + +arr = [2, 4, 5, 7, 8, 9, 10, 11, 12] +num = 9 +index = binary_search(arr, num) + +if index!= -1: + print("Element is present at ", index) +else: + print("Element is not present in array") + +def quicksort(arr): + if len(arr) <= 1: + return arr + + pivot = arr[len(arr)//2] + left_partition, right_partition = [], [] + + for i in range(len(arr)): + if arr[i] < pivot: + left_partition.append(arr[i]) + else: + right_partition.append(arr[i]) + + return quicksort(left_partition) + [pivot] + quicksort(right_partition) + +def merge_sort(arr): + if len(arr) > 1: + mid = len(arr) // 2 + left_half = arr[:mid] + right_half = arr[mid:] + + # Recursive call for sorting the two halves + merge_sort(left_half) + merge_sort(right_half) + + i = j = k = 0 + + while i < len(left_half) and j < len(right_half): + if left_half[i] < right_half[j]: + arr[k] = left_half[i] + i += 1 + else: + arr[k] = right_half[j] + j += 1 + k += 1 + + # Checking if any element was left + while i < len(left_half): + arr[k] = left_half[i] + i += 1 + k += 1 + + while j < len(right_half): + arr[k] = right_half[j] + j += 1 + k += 1 +# Driver code to test above +arr = [24, 32, 10, 67, 55, 38, 19] +print(arr) +merge_sort(arr) + +print("Sorted array is:") +print(arr) +r) // 2 + left_half = arr[:mid] + right_half = arr[mid:] + + # Recursive call for sorting the two halves + merge_sort(left_half) + merge_sort(right_half) + + i = j = k = 0 + + while i < len(left_half) and j < len(right_half): + if left_half[i] < right_half[j]: + arr[k] = left_half[i] + i += 1 + else: + arr[k] = right_half[j] + j += 1 + k += 1 + + # Checking if any element was left + while i < len(left_half): + arr[k] = left_half[i] + i += 1 + k += 1 + + while j < len(right_half): + arr[k] = right_half[j] + j += 1 + k += 1 +# Driver code to test above +arr = [24, 32, 10, 67, 55, 38, 19] +print(arr) +merge_sort(arr) + +print("Sorted array is:") +print(arr) + +def is_fibuncacci(num): + if (num < 2): + return False + + # Base case + if num == 1: + return True + + for i in range(2, num + 1): + if is_fibonacci(i) and is_fibonacci(num - i): + return True + + # If we reach here, then n + + + # Base case + if num == 1: + return True + + for i in range(2, num + 1): + if is_fibonacci(i) and is_fibonacci(num - i): + return True + + # If we reach here, then n + + +def quicksort(arr) \ No newline at end of file From 4c5eb334dfd9955e1c937c15b45e39563f8c7bfc Mon Sep 17 00:00:00 2001 From: Falko Habel Date: Fri, 4 Oct 2024 18:26:16 +0300 Subject: [PATCH 20/32] remove test file --- src/test.py | 175 ---------------------------------------------------- 1 file changed, 175 deletions(-) delete mode 100644 src/test.py diff --git a/src/test.py b/src/test.py deleted file mode 100644 index 8dfb3f9..0000000 --- a/src/test.py +++ /dev/null @@ -1,175 +0,0 @@ - - - - - -def quicksort(arr): - if len(arr) <= 1: - return arr - else: - pivot = arr[len(arr)//2] # choose the middle element as the pivot - left_partition = [i for i in arr if i < pivot] - right_partition = [i for i in arr if i > pivot] - - return quicksort(left_partition) + [pivot] + quicksort(right_partition) - - -def quicksort(arr): - if len(arr) <= 1: - return arr - mid = int(len(arr)/2) - left = [item for i, item in enumerate(arr) if i=mid] - - return quicksort(left)+[arr[mid]]+quicksort(right) -def is_fibunccai(num): - num = str(num) - size = len(num) - - # Create 1s array for DP table to store last 10 digits of Fibonacci sequence - fib = [1] * 10 - - # Initialize last two numbers in Fibonacci sequence as 1 and 2 -def binary_search(arr, num) : - left = 0; right = len(arr) - 1 - - while (left <= right) : - mid = int((right + left) / 2) - - if arr[mid] == num : - return mid - - elif arr[mid] > num : - right = mid - 1 - - else: - left = mid + 1 - - return -1 - -arr = [2, 4, 5, 7, 8, 9, 10, 11, 12] -num = 9 -index = binary_search(arr, num) - -if index!= -1: - print("Element is present at ", index) -else: - print("Element is not present in array") - -def quicksort(arr): - if len(arr) <= 1: - return arr - - pivot = arr[len(arr)//2] - left_partition, right_partition = [], [] - - for i in range(len(arr)): - if arr[i] < pivot: - left_partition.append(arr[i]) - else: - right_partition.append(arr[i]) - - return quicksort(left_partition) + [pivot] + quicksort(right_partition) - -def merge_sort(arr): - if len(arr) > 1: - mid = len(arr) // 2 - left_half = arr[:mid] - right_half = arr[mid:] - - # Recursive call for sorting the two halves - merge_sort(left_half) - merge_sort(right_half) - - i = j = k = 0 - - while i < len(left_half) and j < len(right_half): - if left_half[i] < right_half[j]: - arr[k] = left_half[i] - i += 1 - else: - arr[k] = right_half[j] - j += 1 - k += 1 - - # Checking if any element was left - while i < len(left_half): - arr[k] = left_half[i] - i += 1 - k += 1 - - while j < len(right_half): - arr[k] = right_half[j] - j += 1 - k += 1 -# Driver code to test above -arr = [24, 32, 10, 67, 55, 38, 19] -print(arr) -merge_sort(arr) - -print("Sorted array is:") -print(arr) -r) // 2 - left_half = arr[:mid] - right_half = arr[mid:] - - # Recursive call for sorting the two halves - merge_sort(left_half) - merge_sort(right_half) - - i = j = k = 0 - - while i < len(left_half) and j < len(right_half): - if left_half[i] < right_half[j]: - arr[k] = left_half[i] - i += 1 - else: - arr[k] = right_half[j] - j += 1 - k += 1 - - # Checking if any element was left - while i < len(left_half): - arr[k] = left_half[i] - i += 1 - k += 1 - - while j < len(right_half): - arr[k] = right_half[j] - j += 1 - k += 1 -# Driver code to test above -arr = [24, 32, 10, 67, 55, 38, 19] -print(arr) -merge_sort(arr) - -print("Sorted array is:") -print(arr) - -def is_fibuncacci(num): - if (num < 2): - return False - - # Base case - if num == 1: - return True - - for i in range(2, num + 1): - if is_fibonacci(i) and is_fibonacci(num - i): - return True - - # If we reach here, then n - - - # Base case - if num == 1: - return True - - for i in range(2, num + 1): - if is_fibonacci(i) and is_fibonacci(num - i): - return True - - # If we reach here, then n - - -def quicksort(arr) \ No newline at end of file From 804a3113e45d3a70a28e80ed8c37322f68c1496f Mon Sep 17 00:00:00 2001 From: Falko Habel Date: Fri, 4 Oct 2024 19:00:47 +0300 Subject: [PATCH 21/32] [wip] preview is semi working, i need to jump to the end off the inserted content and extra lines needs to be generated --- src/extension.ts | 74 +++++++++++++++++++----------------------------- 1 file changed, 29 insertions(+), 45 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index acaa133..cdf30b8 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -67,7 +67,7 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo const contextLines = 2; const startLine = Math.max(0, position.line - contextLines); const context = getContextLines(document, position); - + let isHandlingChange = false; const fimPrompt = createFIMPrompt(context, document.languageId); vscode.window.withProgress( @@ -131,16 +131,6 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo // Split the preview content into lines const previewLines = previewContent.split('\n'); - // Calculate the exact position of the last character of the preview content - const lastPreviewLineIndex = previewStartLine + previewLines.length - 1; - const lastPreviewLine = previewLines[previewLines.length - 2]; // Second to last line (last line is newline) - const newPosition = new vscode.Position(lastPreviewLineIndex - 1, lastPreviewLine.length); // Place cursor at end of last line - - // Set the new cursor position after inserting preview - textEditor.selection = new vscode.Selection(newPosition, newPosition); - - // Explicitly scroll to reveal the cursor position at the end of the preview - textEditor.revealRange(new vscode.Range(newPosition, newPosition), vscode.TextEditorRevealType.InCenter); // Set decorations on the newly inserted lines const previewRanges: vscode.DecorationOptions[] = []; @@ -161,29 +151,33 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo const disposable = vscode.workspace.onDidChangeTextDocument(async (event) => { const textEditor = vscode.window.activeTextEditor; - if (!textEditor || event.document.uri.toString() !== textEditor.document.uri.toString() || !previewInserted) { + if (!textEditor || event.document.uri.toString() !== textEditor.document.uri.toString() || !previewInserted || isHandlingChange) { return; } - // Determine tab size setting - const tabSize = textEditor.options.tabSize as number || 4; + isHandlingChange = true; - for (const change of event.contentChanges) { - const changeStartLine = change.range.start.line; + try { + for (const change of event.contentChanges) { + const changeStartLine = change.range.start.line; - // Detect Backspace by a single-character deletion - if (change.text === '' && change.rangeLength === 1 && changeStartLine >= previewStartLine) { - await restoreOriginalContent(); - } - - // Detect Tab by checking if the change is a multiple of spaces equal to the tab size - if (/^[ ]+$/.test(change.text) && change.text.length === tabSize && changeStartLine >= previewStartLine) { - await acceptPreview(textEditor, document, startLine, position, completionText); + // Detect Backspace by a single-character deletion + if (change.text === '' && change.rangeLength === 1 && changeStartLine >= previewStartLine) { + await restoreOriginalContent(); + } + + // Detect Tab key press + if (change.text === '\t' && changeStartLine >= previewStartLine) { + await acceptPreview(textEditor, event.document, startLine, textEditor.selection.active, completionText); + } } + } finally { + isHandlingChange = false; } }); - + + const restoreOriginalContent = async () => { if (!previewInserted) return; @@ -197,35 +191,25 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo textEditor.setDecorations(previewDecorationType, []); previewInserted = false; disposable.dispose(); - - // Move cursor back to the end of the original content - const lastOriginalLineNumber = previewStartLine + originalContent.split('\n').length - 1; - const lastLineLength = originalContent.split('\n').pop()?.length || 0; - const newPosition = new vscode.Position(lastOriginalLineNumber, lastLineLength); - textEditor.selection = new vscode.Selection(newPosition, newPosition); }; // Modify the acceptPreview function to match the working version + const acceptPreview = async (textEditor: vscode.TextEditor, document: vscode.TextDocument, startLine: number, position: vscode.Position, completionText: string) => { - console.log("Accepting preview"); + console.log("Accepting preview with completion text:", completionText); textEditor.setDecorations(previewDecorationType, []); const edit = new vscode.WorkspaceEdit(); - - // Insert the completion text - const insertText = '\n'.repeat(1) + completionText + '\n'.repeat(1); // Add 2 extra newlines - - // Replace the entire range from the start of the context to the current position + + // Adjust the insertion logic to avoid duplicate newlines + const insertText = completionText; + + // Replace the range from the start of the context to the current position const replaceRange = new vscode.Range(startLine, 0, position.line, position.character); edit.replace(document.uri, replaceRange, insertText); - + await vscode.workspace.applyEdit(edit); - await document.save(); // Optionally save after inserting - - // Move the cursor to the end of the inserted completion - const totalNewLines = insertText.split('\n').length; - const newPosition = new vscode.Position(startLine + totalNewLines - 2, 0); - textEditor.selection = new vscode.Selection(newPosition, newPosition); - + await document.save(); + disposable.dispose(); previewInserted = false; console.log("Preview accepted"); From f122d99ba1de2fa162a6e2ca47fd493a35a02223 Mon Sep 17 00:00:00 2001 From: Falko Habel Date: Sat, 5 Oct 2024 09:18:22 +0300 Subject: [PATCH 22/32] code now works with everything except tab --- src/extension.ts | 62 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 45 insertions(+), 17 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index cdf30b8..ff82814 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -116,6 +116,8 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo let previewInserted = false; let originalContent: string; let previewStartLine: number; + + const storeAndInsertPreview = async () => { previewStartLine = startLine; const endLine = document.lineCount - 1; @@ -148,8 +150,33 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo textEditor.setDecorations(previewDecorationType, previewRanges); previewInserted = true; }; - - const disposable = vscode.workspace.onDidChangeTextDocument(async (event) => { + const disposable = vscode.window.onDidChangeTextEditorSelection(async (event) => { + const textEditor = vscode.window.activeTextEditor; + if (!textEditor || !previewInserted || isHandlingChange) { + return; + } + + isHandlingChange = true; + + try { + const activeSelection = textEditor.selection; + const changeStartLine = activeSelection.active.line; + + // Detect Tab key press by checking the active selection + if (event.kind === vscode.TextEditorSelectionChangeKind.Keyboard && changeStartLine >= previewStartLine) { + const changeText = textEditor.document.getText(activeSelection); + + if (changeText === '') { + // Tab key (empty selection) -> Accept the preview + await acceptPreview(textEditor, textEditor.document, startLine, activeSelection.active, completionText); + } + } + } finally { + isHandlingChange = false; + } + }); + + vscode.workspace.onDidChangeTextDocument(async (event) => { const textEditor = vscode.window.activeTextEditor; if (!textEditor || event.document.uri.toString() !== textEditor.document.uri.toString() || !previewInserted || isHandlingChange) { return; @@ -166,7 +193,7 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo await restoreOriginalContent(); } - // Detect Tab key press + // Detect Tab key press by checking if the inserted text is a tab character if (change.text === '\t' && changeStartLine >= previewStartLine) { await acceptPreview(textEditor, event.document, startLine, textEditor.selection.active, completionText); } @@ -175,26 +202,24 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo isHandlingChange = false; } }); - - - + + // Restore original content if Backspace is pressed (decline the preview) const restoreOriginalContent = async () => { if (!previewInserted) return; - + const endLine = document.lineCount - 1; const fullRange = new vscode.Range(previewStartLine, 0, endLine, document.lineAt(endLine).text.length); const edit = new vscode.WorkspaceEdit(); - + edit.replace(document.uri, fullRange, originalContent); await vscode.workspace.applyEdit(edit); - + textEditor.setDecorations(previewDecorationType, []); previewInserted = false; - disposable.dispose(); + disposable.dispose(); // Cancel listener when preview is discarded }; - - // Modify the acceptPreview function to match the working version - + + // Accept the preview when Tab is pressed const acceptPreview = async (textEditor: vscode.TextEditor, document: vscode.TextDocument, startLine: number, position: vscode.Position, completionText: string) => { console.log("Accepting preview with completion text:", completionText); textEditor.setDecorations(previewDecorationType, []); @@ -202,21 +227,24 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo // Adjust the insertion logic to avoid duplicate newlines const insertText = completionText; - + // Replace the range from the start of the context to the current position const replaceRange = new vscode.Range(startLine, 0, position.line, position.character); edit.replace(document.uri, replaceRange, insertText); - + await vscode.workspace.applyEdit(edit); await document.save(); - disposable.dispose(); + disposable.dispose(); // Cancel listener when preview is accepted previewInserted = false; console.log("Preview accepted"); }; - + // Call this function to initiate the preview await storeAndInsertPreview(); + + + } catch (err: any) { vscode.window.showErrorMessage( From f6ea8494a72e4884c955b2d9ab35df2e4f44c8d3 Mon Sep 17 00:00:00 2001 From: Falko Habel Date: Mon, 7 Oct 2024 09:22:02 +0200 Subject: [PATCH 23/32] accept and decline is working --- src/extension.ts | 52 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index ff82814..53614c6 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -176,32 +176,62 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo } }); + // Handles Enter key separately vscode.workspace.onDidChangeTextDocument(async (event) => { const textEditor = vscode.window.activeTextEditor; if (!textEditor || event.document.uri.toString() !== textEditor.document.uri.toString() || !previewInserted || isHandlingChange) { return; } - + isHandlingChange = true; - + try { for (const change of event.contentChanges) { const changeStartLine = change.range.start.line; - - // Detect Backspace by a single-character deletion - if (change.text === '' && change.rangeLength === 1 && changeStartLine >= previewStartLine) { - await restoreOriginalContent(); + + if (change.text.includes('\n') && changeStartLine >= previewStartLine) { + // Accept the preview and move to the next line + await acceptPreview(textEditor, textEditor.document, startLine, textEditor.selection.active, completionText); + await vscode.commands.executeCommand('default:type', { text: '\n' }); + break; } - - // Detect Tab key press by checking if the inserted text is a tab character - if (change.text === '\t' && changeStartLine >= previewStartLine) { - await acceptPreview(textEditor, event.document, startLine, textEditor.selection.active, completionText); + + // Handle Backspace + if (change.text === '' && change.rangeLength === 1 && changeStartLine >= previewStartLine) { + // Discard the preview if Backspace is pressed + await restoreOriginalContent(); + break; } } } finally { isHandlingChange = false; } }); + + vscode.window.onDidChangeTextEditorSelection(async (event) => { + const textEditor = vscode.window.activeTextEditor; + if (!textEditor || !previewInserted || isHandlingChange) { + return; + } + + isHandlingChange = true; + + try { + // Handle arrow keys or any other navigation keys + const currentSelection = event.selections[0]; + const { document } = textEditor; + + // Detect unwanted acceptance from simple navigation + if (currentSelection.start.line < previewStartLine) { + await restoreOriginalContent(); + } + } finally { + isHandlingChange = false; + } + }); + + + // Restore original content if Backspace is pressed (decline the preview) const restoreOriginalContent = async () => { @@ -221,7 +251,6 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo // Accept the preview when Tab is pressed const acceptPreview = async (textEditor: vscode.TextEditor, document: vscode.TextDocument, startLine: number, position: vscode.Position, completionText: string) => { - console.log("Accepting preview with completion text:", completionText); textEditor.setDecorations(previewDecorationType, []); const edit = new vscode.WorkspaceEdit(); @@ -237,7 +266,6 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo disposable.dispose(); // Cancel listener when preview is accepted previewInserted = false; - console.log("Preview accepted"); }; // Call this function to initiate the preview From dcb5a3bbdf8c4a85c40a24e3a6407488d875ec33 Mon Sep 17 00:00:00 2001 From: Falko Habel Date: Tue, 8 Oct 2024 11:17:17 +0200 Subject: [PATCH 24/32] not enough lines between preview and final --- src/extension.ts | 128 ++++++++++++++++++----------------------------- 1 file changed, 50 insertions(+), 78 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 53614c6..0b3c6b4 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -61,6 +61,7 @@ const previewDecorationType = vscode.window.createTextEditorDecorationType({ textDecoration: 'none; display: none;', // Hide the original text }); + async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationToken?: vscode.CancellationToken) { const document = textEditor.document; const position = textEditor.selection.active; @@ -116,40 +117,42 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo let previewInserted = false; let originalContent: string; let previewStartLine: number; - + let previewEndLine: number; const storeAndInsertPreview = async () => { - previewStartLine = startLine; - const endLine = document.lineCount - 1; - const endCharacter = document.lineAt(endLine).text.length; - const fullRange = new vscode.Range(startLine, 0, endLine, endCharacter); + const currentLine = position.line; + previewStartLine = currentLine; + const previewLines = completionText.split('\n'); + previewEndLine = previewStartLine + previewLines.length; + + // Ensure the previewEndLine doesn't exceed the document's line count + const documentEndLine = document.lineCount - 1; + previewEndLine = Math.min(previewEndLine, documentEndLine + 1); + + // Store original content + const fullRange = new vscode.Range(previewStartLine, 0, previewEndLine, document.lineAt(previewEndLine - 1).text.length); originalContent = document.getText(fullRange); - - const previewContent = completionText + '\n'.repeat(1); + + // Prepare the new content + const newContent = previewLines.join('\n'); + const edit = new vscode.WorkspaceEdit(); - edit.replace(document.uri, fullRange, previewContent); // Overwrite the content + edit.replace(document.uri, fullRange, newContent); await vscode.workspace.applyEdit(edit); - - // Split the preview content into lines - const previewLines = previewContent.split('\n'); - - - // Set decorations on the newly inserted lines - const previewRanges: vscode.DecorationOptions[] = []; - for (let i = 0; i < previewLines.length; i++) { - const range = new vscode.Range(previewStartLine + i, 0, previewStartLine + i, previewLines[i].length); - previewRanges.push({ - range, - renderOptions: { - after: { - contentText: previewLines[i], - } + + // Highlight only the new lines + const previewRanges: vscode.DecorationOptions[] = previewLines.map((line: string, index: number) => ({ + range: new vscode.Range(previewStartLine + index, 0, previewStartLine + index, line.length), + renderOptions: { + after: { + contentText: line, } - }); - } + } + })); textEditor.setDecorations(previewDecorationType, previewRanges); previewInserted = true; }; + const disposable = vscode.window.onDidChangeTextEditorSelection(async (event) => { const textEditor = vscode.window.activeTextEditor; if (!textEditor || !previewInserted || isHandlingChange) { @@ -162,7 +165,6 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo const activeSelection = textEditor.selection; const changeStartLine = activeSelection.active.line; - // Detect Tab key press by checking the active selection if (event.kind === vscode.TextEditorSelectionChangeKind.Keyboard && changeStartLine >= previewStartLine) { const changeText = textEditor.document.getText(activeSelection); @@ -176,7 +178,6 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo } }); - // Handles Enter key separately vscode.workspace.onDidChangeTextDocument(async (event) => { const textEditor = vscode.window.activeTextEditor; if (!textEditor || event.document.uri.toString() !== textEditor.document.uri.toString() || !previewInserted || isHandlingChange) { @@ -190,15 +191,12 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo const changeStartLine = change.range.start.line; if (change.text.includes('\n') && changeStartLine >= previewStartLine) { - // Accept the preview and move to the next line await acceptPreview(textEditor, textEditor.document, startLine, textEditor.selection.active, completionText); await vscode.commands.executeCommand('default:type', { text: '\n' }); break; } - // Handle Backspace if (change.text === '' && change.rangeLength === 1 && changeStartLine >= previewStartLine) { - // Discard the preview if Backspace is pressed await restoreOriginalContent(); break; } @@ -208,72 +206,46 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationTo } }); - vscode.window.onDidChangeTextEditorSelection(async (event) => { - const textEditor = vscode.window.activeTextEditor; - if (!textEditor || !previewInserted || isHandlingChange) { - return; - } - - isHandlingChange = true; - - try { - // Handle arrow keys or any other navigation keys - const currentSelection = event.selections[0]; - const { document } = textEditor; - - // Detect unwanted acceptance from simple navigation - if (currentSelection.start.line < previewStartLine) { - await restoreOriginalContent(); - } - } finally { - isHandlingChange = false; - } - }); - - - - - // Restore original content if Backspace is pressed (decline the preview) const restoreOriginalContent = async () => { if (!previewInserted) return; - - const endLine = document.lineCount - 1; - const fullRange = new vscode.Range(previewStartLine, 0, endLine, document.lineAt(endLine).text.length); + + const fullRange = new vscode.Range(previewStartLine, 0, previewEndLine, 0); const edit = new vscode.WorkspaceEdit(); - + edit.replace(document.uri, fullRange, originalContent); await vscode.workspace.applyEdit(edit); - + textEditor.setDecorations(previewDecorationType, []); previewInserted = false; disposable.dispose(); // Cancel listener when preview is discarded }; - - // Accept the preview when Tab is pressed + const acceptPreview = async (textEditor: vscode.TextEditor, document: vscode.TextDocument, startLine: number, position: vscode.Position, completionText: string) => { textEditor.setDecorations(previewDecorationType, []); const edit = new vscode.WorkspaceEdit(); - - // Adjust the insertion logic to avoid duplicate newlines - const insertText = completionText; - - // Replace the range from the start of the context to the current position - const replaceRange = new vscode.Range(startLine, 0, position.line, position.character); - edit.replace(document.uri, replaceRange, insertText); - + + const previewLines = completionText.split('\n'); + const endLine = startLine + previewLines.length; + + // Ensure that endLine does not exceed document bounds + const documentEndLine = document.lineCount - 1; + const finalEndLine = Math.min(endLine, documentEndLine + 1); + + // Prepare the new content + const newContent = previewLines.join('\n'); + + // Replace the range with the new content + const replaceRange = new vscode.Range(startLine, 0, finalEndLine, document.lineAt(finalEndLine - 1).text.length); + edit.replace(document.uri, replaceRange, newContent.trim()); + await vscode.workspace.applyEdit(edit); await document.save(); - + disposable.dispose(); // Cancel listener when preview is accepted previewInserted = false; }; - - // Call this function to initiate the preview + await storeAndInsertPreview(); - - - - } catch (err: any) { vscode.window.showErrorMessage( "Fabelous Autocoder encountered an error: " + err.message From 424cc124b2d1cf5b2da59ce922742eab70812907 Mon Sep 17 00:00:00 2001 From: Falko Habel Date: Tue, 8 Oct 2024 19:07:50 +0200 Subject: [PATCH 25/32] omg this is working --- src/extension.ts | 451 ++++++++++++++++++----------------------------- 1 file changed, 168 insertions(+), 283 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 0b3c6b4..1291669 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,269 +1,179 @@ -import * as vscode from "vscode"; -import axios from "axios"; +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 config: { + apiEndpoint: string; + apiAuthentication: string; + apiModel: string; + apiTemperature: number; + numPredict: number; + promptWindowSize: number; + completionKeys: string[]; + responsePreview: boolean; + responsePreviewMaxTokens: number; + responsePreviewDelay: number; + continueInline: boolean; + keepAlive: number; + topP: number; +}; -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") || " "; - 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; +let previewDecorationType: vscode.TextEditorDecorationType; + +function updateConfig() { + const vsConfig = vscode.workspace.getConfiguration('fabelous-autocoder'); + config = { + apiEndpoint: vsConfig.get('endpoint') || 'http://localhost:11434/api/generate', + apiAuthentication: vsConfig.get('authentication') || '', + apiModel: vsConfig.get('model') || 'fabelous-coder:latest', + apiTemperature: vsConfig.get('temperature') || 0.7, + numPredict: vsConfig.get('max tokens predicted') || 1000, + promptWindowSize: vsConfig.get('prompt window size') || 2000, + completionKeys: (vsConfig.get('completion keys') as string || ' ').split(''), + responsePreview: vsConfig.get('response preview') || false, + 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(); -vscode.workspace.onDidChangeConfiguration(updateVSConfig); +function createPreviewDecorationType() { + previewDecorationType = vscode.window.createTextEditorDecorationType({ + after: { + color: '#888888', + fontStyle: 'italic', + }, + textDecoration: 'none; display: none;', + }); +} 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"); + return document.getText(new vscode.Range(startLine, 0, endLine, position.character)); } - function createFIMPrompt(prefix: string, language: string): string { return `${prefix}${language}\n`; } -const previewDecorationType = vscode.window.createTextEditorDecorationType({ - after: { - color: '#888888', // Grayed-out preview text - fontStyle: 'italic', - }, - textDecoration: 'none; display: none;', // Hide the original text -}); +async function generateCompletion(prompt: string, cancellationToken: vscode.CancellationToken): Promise { + const axiosCancelToken = new axios.CancelToken((c) => { + cancellationToken.onCancellationRequested(() => c('Request cancelled')); + }); - -async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationToken?: vscode.CancellationToken) { - const document = textEditor.document; - const position = textEditor.selection.active; - const contextLines = 2; - const startLine = Math.max(0, position.line - contextLines); - const context = getContextLines(document, position); - let isHandlingChange = false; - 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); - }); - - // Make the API request - 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; - completionText = completionText.replace(/||/g, '').trim(); - - let previewInserted = false; - let originalContent: string; - let previewStartLine: number; - let previewEndLine: number; - - const storeAndInsertPreview = async () => { - const currentLine = position.line; - previewStartLine = currentLine; - const previewLines = completionText.split('\n'); - previewEndLine = previewStartLine + previewLines.length; - - // Ensure the previewEndLine doesn't exceed the document's line count - const documentEndLine = document.lineCount - 1; - previewEndLine = Math.min(previewEndLine, documentEndLine + 1); - - // Store original content - const fullRange = new vscode.Range(previewStartLine, 0, previewEndLine, document.lineAt(previewEndLine - 1).text.length); - originalContent = document.getText(fullRange); - - // Prepare the new content - const newContent = previewLines.join('\n'); - - const edit = new vscode.WorkspaceEdit(); - edit.replace(document.uri, fullRange, newContent); - await vscode.workspace.applyEdit(edit); - - // Highlight only the new lines - const previewRanges: vscode.DecorationOptions[] = previewLines.map((line: string, index: number) => ({ - range: new vscode.Range(previewStartLine + index, 0, previewStartLine + index, line.length), - renderOptions: { - after: { - contentText: line, - } - } - })); - textEditor.setDecorations(previewDecorationType, previewRanges); - previewInserted = true; - }; - - const disposable = vscode.window.onDidChangeTextEditorSelection(async (event) => { - const textEditor = vscode.window.activeTextEditor; - if (!textEditor || !previewInserted || isHandlingChange) { - return; - } - - isHandlingChange = true; - - try { - const activeSelection = textEditor.selection; - const changeStartLine = activeSelection.active.line; - - if (event.kind === vscode.TextEditorSelectionChangeKind.Keyboard && changeStartLine >= previewStartLine) { - const changeText = textEditor.document.getText(activeSelection); - - if (changeText === '') { - // Tab key (empty selection) -> Accept the preview - await acceptPreview(textEditor, textEditor.document, startLine, activeSelection.active, completionText); - } - } - } finally { - isHandlingChange = false; - } - }); - - vscode.workspace.onDidChangeTextDocument(async (event) => { - const textEditor = vscode.window.activeTextEditor; - if (!textEditor || event.document.uri.toString() !== textEditor.document.uri.toString() || !previewInserted || isHandlingChange) { - return; - } - - isHandlingChange = true; - - try { - for (const change of event.contentChanges) { - const changeStartLine = change.range.start.line; - - if (change.text.includes('\n') && changeStartLine >= previewStartLine) { - await acceptPreview(textEditor, textEditor.document, startLine, textEditor.selection.active, completionText); - await vscode.commands.executeCommand('default:type', { text: '\n' }); - break; - } - - if (change.text === '' && change.rangeLength === 1 && changeStartLine >= previewStartLine) { - await restoreOriginalContent(); - break; - } - } - } finally { - isHandlingChange = false; - } - }); - - const restoreOriginalContent = async () => { - if (!previewInserted) return; - - const fullRange = new vscode.Range(previewStartLine, 0, previewEndLine, 0); - const edit = new vscode.WorkspaceEdit(); - - edit.replace(document.uri, fullRange, originalContent); - await vscode.workspace.applyEdit(edit); - - textEditor.setDecorations(previewDecorationType, []); - previewInserted = false; - disposable.dispose(); // Cancel listener when preview is discarded - }; - - const acceptPreview = async (textEditor: vscode.TextEditor, document: vscode.TextDocument, startLine: number, position: vscode.Position, completionText: string) => { - textEditor.setDecorations(previewDecorationType, []); - const edit = new vscode.WorkspaceEdit(); - - const previewLines = completionText.split('\n'); - const endLine = startLine + previewLines.length; - - // Ensure that endLine does not exceed document bounds - const documentEndLine = document.lineCount - 1; - const finalEndLine = Math.min(endLine, documentEndLine + 1); - - // Prepare the new content - const newContent = previewLines.join('\n'); - - // Replace the range with the new content - const replaceRange = new vscode.Range(startLine, 0, finalEndLine, document.lineAt(finalEndLine - 1).text.length); - edit.replace(document.uri, replaceRange, newContent.trim()); - - await vscode.workspace.applyEdit(edit); - await document.save(); - - disposable.dispose(); // Cancel listener when preview is accepted - previewInserted = false; - }; - - await storeAndInsertPreview(); - } catch (err: any) { - vscode.window.showErrorMessage( - "Fabelous Autocoder encountered an error: " + err.message - ); - console.log(err); - } + const response = await axios.post(config.apiEndpoint, { + model: config.apiModel, + prompt: prompt, + stream: false, + raw: true, + options: { + num_predict: config.numPredict + 100, // Generate extra lines for preview + temperature: config.apiTemperature, + stop: ['', '```'], + keep_alive: config.keepAlive, + top_p: config.topP, } - ); + }, { + cancelToken: axiosCancelToken, + headers: { + 'Authorization': config.apiAuthentication + } + }); + + 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'); } +class CompletionManager { + private textEditor: vscode.TextEditor; + 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, + } + } + })); + this.textEditor.setDecorations(previewDecorationType, previewRanges); + } + + public async acceptCompletion() { + const edit = new vscode.WorkspaceEdit(); + edit.insert(this.document.uri, this.startPosition, this.completionText); + await vscode.workspace.applyEdit(edit); + this.clearPreview(); + } + + public clearPreview() { + this.textEditor.setDecorations(previewDecorationType, []); + } +} + +async function autocompleteCommand(textEditor: vscode.TextEditor, edit: vscode.TextEditorEdit, ...args: any[]) { + const cancellationTokenSource = new vscode.CancellationTokenSource(); + const cancellationToken = cancellationTokenSource.token; + + try { + 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); + }); + + const completionManager = new CompletionManager(textEditor, position, completionText); + await completionManager.showPreview(); + + const disposable = vscode.window.onDidChangeTextEditorSelection(async (event) => { + if (event.textEditor !== textEditor) return; + + if (event.kind === vscode.TextEditorSelectionChangeKind.Keyboard) { + await completionManager.acceptCompletion(); + disposable.dispose(); + } + }); + } catch (err: any) { + vscode.window.showErrorMessage(`Fabelous Autocoder encountered an error: ${err.message}`); + console.error(err); + } finally { + cancellationTokenSource.dispose(); + } +} async function provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, cancellationToken: vscode.CancellationToken) { - const item = new vscode.CompletionItem("Fabelous autocompletion"); + 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 (responsePreview) { - await new Promise(resolve => setTimeout(resolve, responsePreviewDelay * 1000)); + if (config.responsePreview) { + await new Promise(resolve => setTimeout(resolve, config.responsePreviewDelay * 1000)); if (cancellationToken.isCancellationRequested) { return [item]; } @@ -272,58 +182,33 @@ async function provideCompletionItems(document: vscode.TextDocument, position: v 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 }), - } - }, { - cancelToken: new axios.CancelToken((c) => { - cancellationToken.onCancellationRequested(() => c("Autocompletion request terminated by completion cancel")); - }) - }); + const previewText = await generateCompletion(fimPrompt, cancellationToken); + item.detail = previewText.split('\n')[0]; // Show first line as preview } 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 (continueInline || !responsePreview) { + if (config.continueInline || !config.responsePreview) { item.command = { command: 'fabelous-autocoder.autocomplete', title: 'Fabelous Autocomplete', - arguments: [cancellationToken] + arguments: [] }; } + return [item]; } -function activate(context: vscode.ExtensionContext) { - const completionProvider = vscode.languages.registerCompletionItemProvider("*", { - provideCompletionItems - }, - ...completionKeys.split("") +export function activate(context: vscode.ExtensionContext) { + updateConfig(); + createPreviewDecorationType(); + + context.subscriptions.push( + vscode.workspace.onDidChangeConfiguration(updateConfig), + vscode.languages.registerCompletionItemProvider('*', { provideCompletionItems }, ...config.completionKeys), + vscode.commands.registerTextEditorCommand('fabelous-autocoder.autocomplete', autocompleteCommand) ); - 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, - deactivate, -}; +export function deactivate() {} From 96a3971b714811fe12cfbd2aeb77380c02202917 Mon Sep 17 00:00:00 2001 From: Falko Habel Date: Wed, 9 Oct 2024 08:10:02 +0200 Subject: [PATCH 26/32] preview added --- src/extension.ts | 105 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 80 insertions(+), 25 deletions(-) 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); } From a56d47747d2d55f25efe82aa8b7b1fa1967c57bc Mon Sep 17 00:00:00 2001 From: Falko Habel Date: Wed, 9 Oct 2024 14:18:33 +0200 Subject: [PATCH 27/32] only rows are not working yet --- src/extension.ts | 58 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 42 insertions(+), 16 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 86841ec..facecec 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -18,6 +18,7 @@ let config: { }; let previewDecorationType: vscode.TextEditorDecorationType; +let activeCompletionManager: CompletionManager | null = null; function updateConfig() { const vsConfig = vscode.workspace.getConfiguration('fabelous-autocoder'); @@ -100,14 +101,13 @@ class CompletionManager { public async showPreview() { const completionLines = this.completionText.split('\n'); - const previewLines = [ - '', // Empty line before - ...completionLines, - '' // Empty line after - ]; - + const emptyLine = ''; // Empty line for spacing + const previewLines = [emptyLine, ...completionLines, emptyLine]; + const previewRanges: vscode.DecorationOptions[] = previewLines.map((line, index) => { - const lineNumber = Math.max(0, this.startPosition.line + index - 1); + const actualLineNumber = this.startPosition.line + index; + const totalLines = this.textEditor.document.lineCount; + const lineNumber = Math.min(totalLines - 1, actualLineNumber); return { range: new vscode.Range( new vscode.Position(lineNumber, 0), @@ -115,14 +115,14 @@ class CompletionManager { ), renderOptions: { after: { - contentText: line, - } - } + contentText: line.length > 0 ? ` ${line}` : '', + }, + }, }; }); + this.textEditor.setDecorations(previewDecorationType, previewRanges); } - public async acceptCompletion() { const edit = new vscode.WorkspaceEdit(); @@ -139,6 +139,7 @@ class CompletionManager { public clearPreview() { this.textEditor.setDecorations(previewDecorationType, []); } + public declineCompletion() { this.clearPreview(); } @@ -167,6 +168,7 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, edit: vscode.T const completionManager = new CompletionManager(textEditor, position, completionText); await completionManager.showPreview(); + activeCompletionManager = completionManager; let isDisposed = false; @@ -174,7 +176,8 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, edit: vscode.T if (!isDisposed) { console.log('Disposing listeners'); disposable.dispose(); - declineDisposable.dispose(); + typeDisposable.dispose(); + activeCompletionManager = null; isDisposed = true; } }; @@ -203,7 +206,7 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, edit: vscode.T }) ); - const declineDisposable = vscode.commands.registerCommand('type', async (args) => { + const typeDisposable = vscode.commands.registerCommand('type', async (args) => { if (args.text === '\b') { // Backspace key console.log('Declining completion'); completionManager.declineCompletion(); @@ -219,6 +222,27 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, edit: vscode.T } } +async function acceptCompletion() { + 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; + } +} + +async function handleTab() { + if (activeCompletionManager) { + await acceptCompletion(); + } else { + await vscode.commands.executeCommand('tab'); + } +} + 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:}'); @@ -237,7 +261,7 @@ async function provideCompletionItems(document: vscode.TextDocument, position: v const result = await generateCompletion(fimPrompt, cancellationToken); const preview = (result as any).preview; if (preview) { - item.detail = preview.split('\n')[0]; + item.detail = preview.split('\n')[0]; } } catch (error) { console.error('Error fetching preview:', error); @@ -254,7 +278,6 @@ async function provideCompletionItems(document: vscode.TextDocument, position: v return [item]; } - export function activate(context: vscode.ExtensionContext) { updateConfig(); createPreviewDecorationType(); @@ -262,8 +285,11 @@ export function activate(context: vscode.ExtensionContext) { context.subscriptions.push( vscode.workspace.onDidChangeConfiguration(updateConfig), vscode.languages.registerCompletionItemProvider('*', { provideCompletionItems }, ...config.completionKeys), - vscode.commands.registerTextEditorCommand('fabelous-autocoder.autocomplete', autocompleteCommand) + vscode.commands.registerTextEditorCommand('fabelous-autocoder.autocomplete', autocompleteCommand), + vscode.commands.registerCommand('fabelous-autocoder.acceptCompletion', acceptCompletion), + vscode.commands.registerCommand('fabelous-autocoder.handleTab', handleTab) ); } + export function deactivate() {} From 439a538c7e2ffec71f1652f9e065979bfad9c830 Mon Sep 17 00:00:00 2001 From: Falko Habel Date: Wed, 9 Oct 2024 14:24:53 +0200 Subject: [PATCH 28/32] bug fix for lines add extra lines before inserting --- src/extension.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/extension.ts b/src/extension.ts index facecec..3790c1b 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -100,7 +100,9 @@ class CompletionManager { } public async showPreview() { + this.completionText = '\n' + this.completionText; const completionLines = this.completionText.split('\n'); + const emptyLine = ''; // Empty line for spacing const previewLines = [emptyLine, ...completionLines, emptyLine]; @@ -166,6 +168,7 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, edit: vscode.T console.log('Completion generated:', completionText); + const completionManager = new CompletionManager(textEditor, position, completionText); await completionManager.showPreview(); activeCompletionManager = completionManager; From 621db41722e3a2960d1b81069dfe1ed5337ce8a6 Mon Sep 17 00:00:00 2001 From: Falko Habel Date: Wed, 9 Oct 2024 15:22:51 +0200 Subject: [PATCH 29/32] updated to version 2.0 --- package.json | 285 +++++++++++++++++++++++++++------------------------ 1 file changed, 151 insertions(+), 134 deletions(-) diff --git a/package.json b/package.json index 53d6a68..bf85831 100644 --- a/package.json +++ b/package.json @@ -1,135 +1,152 @@ { - "name": "fabelous-autocoder", - "version": "0.2.0", - "displayName": "Fabelous Autocoder", - "description": "A simple to use Ollama autocompletion Plugin", - "icon": "icon.png", - "publisher": "fabel", - "license": "CC BY-ND 4.0", - "bugs": { - "url": "https://gitea.fabelous.app/fabel/Fabelous-Autocoder/issues" - }, - "repository": { - "type": "git", - "url": "https://gitea.fabelous.app/fabel/Fabelous-Autocoder.git" - }, - "engines": { - "vscode": "^1.89.0" - }, - "categories": [ - "Machine Learning", - "Snippets", - "Programming Languages" - ], - "keywords": [ - "ollama", - "coding", - "autocomplete", - "open source", - "assistant", - "ai", - "llm" - ], - "galleryBanner": { - "color": "#133773" - }, - "activationEvents": [ - "onStartupFinished" - ], - "main": "./out/extension.js", - "contributes": { - "configuration": { - "title": "Fabelous Autocoder", - "properties": { - "fabelous-autocoder.endpoint": { - "type": "string", - "default": "http://localhost:11434/api/generate", - "description": "The endpoint of the ollama REST API" - }, - "fabelous-autocoder.authentication": { - "type": "string", - "default": "", - "description": "Authorization Token for Ollama" - }, - "fabelous-autocoder.model": { - "type": "string", - "default": "", - "description": "The model to use for generating completions" - }, - "fabelous-autocoder.max tokens predicted": { - "type": "integer", - "default": 1000, - "description": "The maximum number of tokens generated by the model." - }, - "fabelous-autocoder.prompt window size": { - "type": "integer", - "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)." - }, - "fabelous-autocoder.completion keys": { - "type": "string", - "default": " ", - "description": "Character that the autocompletion item provider appear on. Multiple characters will be treated as different entries. REQUIRES RELOAD" - }, - "fabelous-autocoder.response preview": { - "type": "boolean", - "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." - }, - "fabelous-autocoder.preview max tokens": { - "type": "integer", - "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." - }, - "fabelous-autocoder.preview delay": { - "type": "number", - "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." - }, - "fabelous-autocoder.continue inline": { - "type": "boolean", - "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." - - }, - "fabelous-autocoder.temperature": { - "type": "number", - "default": 0.5, - "description": "Temperature of the model. It is recommended to set it lower than you would for dialogue." - }, - "fabelous-autocoder.keep alive": { - "type": "number", - "default": 10, - "description": "Time in minutes before Ollama unloads the model." - }, - "fabelous-autocoder.top p": { - "type": "number", - "description": "Top p sampling for the model." - } - } - }, - "commands": [ - { - "command": "fabelous-autocoder.autocomplete", - "title": "Fabelous Autocompletion" - }] - }, - "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" - } -} \ No newline at end of file + "name": "fabelous-autocoder", + "version": "0.2.0", + "displayName": "Fabelous Autocoder", + "description": "A simple to use Ollama autocompletion Plugin", + "icon": "icon.png", + "publisher": "fabel", + "license": "CC BY-ND 4.0", + "bugs": { + "url": "https://gitea.fabelous.app/fabel/Fabelous-Autocoder/issues" + }, + "repository": { + "type": "git", + "url": "https://gitea.fabelous.app/fabel/Fabelous-Autocoder.git" + }, + "engines": { + "vscode": "^1.89.0" + }, + "categories": [ + "Machine Learning", + "Snippets", + "Programming Languages" + ], + "keywords": [ + "ollama", + "coding", + "autocomplete", + "open source", + "assistant", + "ai", + "llm" + ], + "galleryBanner": { + "color": "#133773" + }, + "activationEvents": [ + "onStartupFinished" + ], + "main": "./out/extension.js", + "contributes": { + "configuration": { + "title": "Fabelous Autocoder", + "properties": { + "fabelous-autocoder.endpoint": { + "type": "string", + "default": "http://localhost:11434/api/generate", + "description": "The endpoint of the ollama REST API" + }, + "fabelous-autocoder.authentication": { + "type": "string", + "default": "", + "description": "Authorization Token for Ollama" + }, + "fabelous-autocoder.model": { + "type": "string", + "default": "", + "description": "The model to use for generating completions" + }, + "fabelous-autocoder.max tokens predicted": { + "type": "integer", + "default": 1000, + "description": "The maximum number of tokens generated by the model." + }, + "fabelous-autocoder.prompt window size": { + "type": "integer", + "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)." + }, + "fabelous-autocoder.completion keys": { + "type": "string", + "default": " ", + "description": "Character that the autocompletion item provider appear on. Multiple characters will be treated as different entries. REQUIRES RELOAD" + }, + "fabelous-autocoder.response preview": { + "type": "boolean", + "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." + }, + "fabelous-autocoder.preview max tokens": { + "type": "integer", + "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." + }, + "fabelous-autocoder.preview delay": { + "type": "number", + "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." + }, + "fabelous-autocoder.continue inline": { + "type": "boolean", + "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." + }, + "fabelous-autocoder.temperature": { + "type": "number", + "default": 0.5, + "description": "Temperature of the model. It is recommended to set it lower than you would for dialogue." + }, + "fabelous-autocoder.keep alive": { + "type": "number", + "default": 10, + "description": "Time in minutes before Ollama unloads the model." + }, + "fabelous-autocoder.top p": { + "type": "number", + "default": 1, + "description": "Top p sampling for the model." + }, + "fabelous-autocoder.enableLineByLineAcceptance": { + "type": "boolean", + "default": false, + "description": "Enable line-by-line acceptance of the generated code." + } + } + }, + "keybindings": [ + { + "command": "fabelous-autocoder.handleTab", + "key": "tab", + "when": "editorTextFocus && !editorTabMovesFocus" + } + ], + "commands": [ + { + "command": "fabelous-autocoder.autocomplete", + "title": "Fabelous Autocompletion" + }, + { + "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" + } +} From e02296c5a718a7d5dff4af0c602232cc69641c87 Mon Sep 17 00:00:00 2001 From: Falko Habel Date: Wed, 9 Oct 2024 16:05:40 +0200 Subject: [PATCH 30/32] only ehader is to much --- src/extension.ts | 46 ++++++++++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 3790c1b..49926c6 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -85,7 +85,6 @@ 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; @@ -100,24 +99,27 @@ class CompletionManager { } public async showPreview() { - this.completionText = '\n' + this.completionText; - const completionLines = this.completionText.split('\n'); + // Calculate lines in the completionText + const completionLines = this.completionText.split('\n').length; - const emptyLine = ''; // Empty line for spacing - const previewLines = [emptyLine, ...completionLines, emptyLine]; + // Insert empty lines to make space for completion 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); + await vscode.workspace.applyEdit(edit); - const previewRanges: vscode.DecorationOptions[] = previewLines.map((line, index) => { + const previewRanges: vscode.DecorationOptions[] = this.completionText.split('\n').map((line, index) => { const actualLineNumber = this.startPosition.line + index; - const totalLines = this.textEditor.document.lineCount; - const lineNumber = Math.min(totalLines - 1, actualLineNumber); return { range: new vscode.Range( - new vscode.Position(lineNumber, 0), - new vscode.Position(lineNumber, Number.MAX_VALUE) + new vscode.Position(actualLineNumber, 0), + new vscode.Position(actualLineNumber, 0) // Positions should be zero-length for insertions ), renderOptions: { after: { - contentText: line.length > 0 ? ` ${line}` : '', + contentText: ` ${line}`.trim(), }, }, }; @@ -128,18 +130,25 @@ class CompletionManager { public async acceptCompletion() { const edit = new vscode.WorkspaceEdit(); - 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) + + // Prepare to insert completion text + const endLine = this.startPosition.line + this.completionText.split('\n').length; + + // Replace the preview lines with actual completion text + const rangeToReplace = new vscode.Range( + this.startPosition, + new vscode.Position(endLine, 0) // Position for line end should just reach intended insert ); - edit.replace(this.document.uri, range, this.completionText); + + edit.replace(this.document.uri, rangeToReplace, this.completionText); + await vscode.workspace.applyEdit(edit); + this.clearPreview(); } - + public clearPreview() { - this.textEditor.setDecorations(previewDecorationType, []); + this.textEditor.setDecorations(previewDecorationType, []); // Clear only the decorations } public declineCompletion() { @@ -147,6 +156,7 @@ class CompletionManager { } } + async function autocompleteCommand(textEditor: vscode.TextEditor, edit: vscode.TextEditorEdit, ...args: any[]) { const cancellationTokenSource = new vscode.CancellationTokenSource(); const cancellationToken = cancellationTokenSource.token; From c1d0a53720c95ee940ed5393932277fccc88dcb7 Mon Sep 17 00:00:00 2001 From: Falko Habel Date: Wed, 9 Oct 2024 19:51:12 +0200 Subject: [PATCH 31/32] oonly remaining bug: backspace is sometimes still triggerable --- src/extension.ts | 181 +++++++++++++++++++++++++---------------------- 1 file changed, 96 insertions(+), 85 deletions(-) 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() {} From a3bf2f93bbe9941f930af28c087554397be37b17 Mon Sep 17 00:00:00 2001 From: Falko Habel Date: Wed, 9 Oct 2024 19:58:21 +0200 Subject: [PATCH 32/32] =?UTF-8?q?Final=20Version.=20Everything=20is=20work?= =?UTF-8?q?ing;=20atleast=20for=20Python=F0=9F=A5=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 17 +++++++++++------ src/extension.ts | 29 +++++++++++++++++++---------- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index bf85831..a3c4aef 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "displayName": "Fabelous Autocoder", "description": "A simple to use Ollama autocompletion Plugin", "icon": "icon.png", - "publisher": "fabel", + "publisher": "Falko Habel", "license": "CC BY-ND 4.0", "bugs": { "url": "https://gitea.fabelous.app/fabel/Fabelous-Autocoder/issues" @@ -114,11 +114,16 @@ } }, "keybindings": [ - { - "command": "fabelous-autocoder.handleTab", - "key": "tab", - "when": "editorTextFocus && !editorTabMovesFocus" - } + { + "command": "fabelous-autocoder.handleTab", + "key": "tab", + "when": "editorTextFocus && !editorTabMovesFocus" + }, + { + "command": "fabelous-autocoder.handleBackspace", + "key": "backspace", + "when": "editorTextFocus" + } ], "commands": [ { diff --git a/src/extension.ts b/src/extension.ts index 1026b12..0bbacc8 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -144,31 +144,39 @@ class CompletionManager { 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); - this.clearPreview(); // Clear the preview decorations - + + // 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 without any additional move + 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() { @@ -204,6 +212,7 @@ class CompletionManager { this.textEditor.selection = new vscode.Selection(this.startPosition, this.startPosition); console.log(`Lines ${startLine + 1} to ${endLine + 1} removed successfully`); + activeCompletionManager = null; } else { console.log('No lines to remove'); }