develop #6

Merged
Fabel merged 35 commits from develop into main 2024-10-09 18:50:26 +00:00
2 changed files with 266 additions and 214 deletions
Showing only changes of commit 12f411fbac - Show all commits

View File

@ -1,135 +1,157 @@
{ {
"name": "fabelous-autocoder", "name": "fabelous-autocoder",
"version": "0.2.0", "version": "0.2.0",
"displayName": "Fabelous Autocoder", "displayName": "Fabelous Autocoder",
"description": "A simple to use Ollama autocompletion Plugin", "description": "A simple to use Ollama autocompletion Plugin",
"icon": "icon.png", "icon": "icon.png",
"publisher": "fabel", "publisher": "Falko Habel",
"license": "CC BY-ND 4.0", "license": "CC BY-ND 4.0",
"bugs": { "bugs": {
"url": "https://gitea.fabelous.app/fabel/Fabelous-Autocoder/issues" "url": "https://gitea.fabelous.app/fabel/Fabelous-Autocoder/issues"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://gitea.fabelous.app/fabel/Fabelous-Autocoder.git" "url": "https://gitea.fabelous.app/fabel/Fabelous-Autocoder.git"
}, },
"engines": { "engines": {
"vscode": "^1.89.0" "vscode": "^1.89.0"
}, },
"categories": [ "categories": [
"Machine Learning", "Machine Learning",
"Snippets", "Snippets",
"Programming Languages" "Programming Languages"
], ],
"keywords": [ "keywords": [
"ollama", "ollama",
"coding", "coding",
"autocomplete", "autocomplete",
"open source", "open source",
"assistant", "assistant",
"ai", "ai",
"llm" "llm"
], ],
"galleryBanner": { "galleryBanner": {
"color": "#133773" "color": "#133773"
}, },
"activationEvents": [ "activationEvents": [
"onStartupFinished" "onStartupFinished"
], ],
"main": "./out/extension.js", "main": "./out/extension.js",
"contributes": { "contributes": {
"configuration": { "configuration": {
"title": "Fabelous Autocoder", "title": "Fabelous Autocoder",
"properties": { "properties": {
"fabelous-autocoder.endpoint": { "fabelous-autocoder.endpoint": {
"type": "string", "type": "string",
"default": "http://localhost:11434/api/generate", "default": "http://localhost:11434/api/generate",
"description": "The endpoint of the ollama REST API" "description": "The endpoint of the ollama REST API"
}, },
"fabelous-autocoder.authentication": { "fabelous-autocoder.authentication": {
"type": "string", "type": "string",
"default": "", "default": "",
"description": "Authorization Token for Ollama" "description": "Authorization Token for Ollama"
}, },
"fabelous-autocoder.model": { "fabelous-autocoder.model": {
"type": "string", "type": "string",
"default": "", "default": "",
"description": "The model to use for generating completions" "description": "The model to use for generating completions"
}, },
"fabelous-autocoder.max tokens predicted": { "fabelous-autocoder.max tokens predicted": {
"type": "integer", "type": "integer",
"default": 1000, "default": 1000,
"description": "The maximum number of tokens generated by the model." "description": "The maximum number of tokens generated by the model."
}, },
"fabelous-autocoder.prompt window size": { "fabelous-autocoder.prompt window size": {
"type": "integer", "type": "integer",
"default": 2000, "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)." "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": { "fabelous-autocoder.completion keys": {
"type": "string", "type": "string",
"default": " ", "default": " ",
"description": "Character that the autocompletion item provider appear on. Multiple characters will be treated as different entries. REQUIRES RELOAD" "description": "Character that the autocompletion item provider appear on. Multiple characters will be treated as different entries. REQUIRES RELOAD"
}, },
"fabelous-autocoder.response preview": { "fabelous-autocoder.response preview": {
"type": "boolean", "type": "boolean",
"default": true, "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." "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": { "fabelous-autocoder.preview max tokens": {
"type": "integer", "type": "integer",
"default": 50, "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." "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": { "fabelous-autocoder.preview delay": {
"type": "number", "type": "number",
"default": 1, "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." "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": { "fabelous-autocoder.continue inline": {
"type": "boolean", "type": "boolean",
"default": true, "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." "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": {
"fabelous-autocoder.temperature": { "type": "number",
"type": "number", "default": 0.5,
"default": 0.5, "description": "Temperature of the model. It is recommended to set it lower than you would for dialogue."
"description": "Temperature of the model. It is recommended to set it lower than you would for dialogue." },
}, "fabelous-autocoder.keep alive": {
"fabelous-autocoder.keep alive": { "type": "number",
"type": "number", "default": 10,
"default": 10, "description": "Time in minutes before Ollama unloads the model."
"description": "Time in minutes before Ollama unloads the model." },
}, "fabelous-autocoder.top p": {
"fabelous-autocoder.top p": { "type": "number",
"type": "number", "default": 1,
"description": "Top p sampling for the model." "description": "Top p sampling for the model."
} },
} "fabelous-autocoder.enableLineByLineAcceptance": {
}, "type": "boolean",
"commands": [ "default": false,
{ "description": "Enable line-by-line acceptance of the generated code."
"command": "fabelous-autocoder.autocomplete", }
"title": "Fabelous Autocompletion" }
}] },
}, "keybindings": [
"scripts": { {
"vscode:prepublish": "npm run compile", "command": "fabelous-autocoder.handleTab",
"compile": "tsc --skipLibCheck -p ./", "key": "tab",
"package": "npm run compile && vsce package", "when": "editorTextFocus && !editorTabMovesFocus"
"lint": "eslint \"src/**/*.ts\"", },
"watch": "tsc --skipLibCheck -watch -p ./" {
}, "command": "fabelous-autocoder.handleBackspace",
"devDependencies": { "key": "backspace",
"@types/node": "^20.12.8", "when": "editorTextFocus"
"@types/vscode": "^1.89.0", }
"@typescript-eslint/eslint-plugin": "^7.8.0", ],
"@typescript-eslint/parser": "^7.8.0", "commands": [
"eslint": "^8.57.0", {
"typescript": "^5.4.5" "command": "fabelous-autocoder.autocomplete",
}, "title": "Fabelous Autocompletion"
"dependencies": { },
"axios": "^1.6.8" {
} "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"
}
}

View File

@ -91,6 +91,7 @@ class CompletionManager {
private document: vscode.TextDocument; private document: vscode.TextDocument;
private startPosition: vscode.Position; private startPosition: vscode.Position;
private completionText: string; private completionText: string;
private insertedLineCount: number = 0; // Track the number of inserted lines
constructor(textEditor: vscode.TextEditor, startPosition: vscode.Position, completionText: string) { constructor(textEditor: vscode.TextEditor, startPosition: vscode.Position, completionText: string) {
this.textEditor = textEditor; this.textEditor = textEditor;
@ -100,53 +101,129 @@ class CompletionManager {
} }
public async showPreview() { public async showPreview() {
this.completionText = '\n' + this.completionText; if (!previewDecorationType) {
const completionLines = this.completionText.split('\n'); createPreviewDecorationType();
}
const emptyLine = ''; // Empty line for spacing
const previewLines = [emptyLine, ...completionLines, emptyLine]; const completionLines = this.completionText.split('\n').length;
const previewRanges: vscode.DecorationOptions[] = previewLines.map((line, index) => { // Adjust the start position to line after the original start position
const actualLineNumber = this.startPosition.line + index; const adjustedStartPosition = this.startPosition.translate(0, 0);
const totalLines = this.textEditor.document.lineCount;
const lineNumber = Math.min(totalLines - 1, actualLineNumber); // Step 1: Insert blank lines to make space for the preview
const edit = new vscode.WorkspaceEdit();
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 lineNumber = adjustedStartPosition.line + index + 1; // Start preview one line later
return { return {
range: new vscode.Range( range: new vscode.Range(
new vscode.Position(lineNumber, 0), new vscode.Position(lineNumber, 0),
new vscode.Position(lineNumber, Number.MAX_VALUE) new vscode.Position(lineNumber, 0)
), ),
renderOptions: { renderOptions: {
after: { after: {
contentText: line.length > 0 ? ` ${line}` : '', contentText: line,
color: '#888888',
fontStyle: 'italic',
}, },
}, },
}; };
}); });
this.textEditor.setDecorations(previewDecorationType, previewRanges); this.textEditor.setDecorations(previewDecorationType, previewRanges);
} }
public async acceptCompletion() { public async acceptCompletion() {
const edit = new vscode.WorkspaceEdit(); const edit = new vscode.WorkspaceEdit();
const startLine = Math.max(0, this.startPosition.line - 1); const completionLines = this.completionText.split('\n');
const range = new vscode.Range( const numberOfLines = completionLines.length;
new vscode.Position(startLine, 0),
this.startPosition.translate(0, Number.MAX_VALUE) // 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)
); );
edit.replace(this.document.uri, range, 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); await vscode.workspace.applyEdit(edit);
// Clear the preview decorations
this.clearPreview(); this.clearPreview();
}
// Set activeCompletionManager to null
public clearPreview() { activeCompletionManager = null;
this.textEditor.setDecorations(previewDecorationType, []);
// 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
this.textEditor.selection = new vscode.Selection(newPosition, newPosition);
} }
public declineCompletion() {
this.clearPreview();
public clearPreview() {
this.textEditor.setDecorations(previewDecorationType, []); // Remove all preview decorations
}
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`);
activeCompletionManager = null;
} 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[]) { async function autocompleteCommand(textEditor: vscode.TextEditor, edit: vscode.TextEditorEdit, ...args: any[]) {
const cancellationTokenSource = new vscode.CancellationTokenSource(); const cancellationTokenSource = new vscode.CancellationTokenSource();
const cancellationToken = cancellationTokenSource.token; const cancellationToken = cancellationTokenSource.token;
@ -173,49 +250,6 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, edit: vscode.T
await completionManager.showPreview(); await completionManager.showPreview();
activeCompletionManager = completionManager; 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) { } catch (err: any) {
console.error('Error in autocompleteCommand:', err); console.error('Error in autocompleteCommand:', err);
@ -225,24 +259,19 @@ async function autocompleteCommand(textEditor: vscode.TextEditor, edit: vscode.T
} }
} }
async function acceptCompletion() { async function handleTab() {
if (activeCompletionManager) { if (activeCompletionManager) {
await activeCompletionManager.acceptCompletion(); await activeCompletionManager.acceptCompletion();
const editor = vscode.window.activeTextEditor; } else {
if (editor) { await vscode.commands.executeCommand('tab');
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() { async function handleBackspace() {
if (activeCompletionManager) { if (activeCompletionManager) {
await acceptCompletion(); await activeCompletionManager.declineCompletion();
} else { } else {
await vscode.commands.executeCommand('tab'); await vscode.commands.executeCommand('deleteLeft');
} }
} }
@ -289,10 +318,11 @@ export function activate(context: vscode.ExtensionContext) {
vscode.workspace.onDidChangeConfiguration(updateConfig), vscode.workspace.onDidChangeConfiguration(updateConfig),
vscode.languages.registerCompletionItemProvider('*', { provideCompletionItems }, ...config.completionKeys), 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),
vscode.commands.registerCommand('fabelous-autocoder.handleTab', handleTab) vscode.commands.registerCommand('fabelous-autocoder.handleBackspace', handleBackspace) // Add this line
); );
} }
export function deactivate() {} export function deactivate() {}