From 272660939fcd624e0850dd214b7ff55605d382de Mon Sep 17 00:00:00 2001 From: Luke Lin Date: Tue, 30 Jan 2024 03:20:16 +0800 Subject: [PATCH] [VSCode Extension] UI enhancements (#786) * Features added 1. Generate code with user selected text only 2. Add right-click menus, "Generate Inline Code Completion" and Generate Inline Code Completion In New Tab" * Update modules/openvino_code/README.md --- modules/openvino_code/README.md | 2 + modules/openvino_code/package.json | 28 ++++++++ modules/openvino_code/src/constants.ts | 1 + .../inline-completion/completion.service.ts | 43 +++++++++++++ .../inline-completion-component.ts | 64 +++++++++++-------- .../streaming-inline-completion-component.ts | 19 +++++- .../src/inline-completion/tab.ts | 9 +++ 7 files changed, 137 insertions(+), 29 deletions(-) create mode 100644 modules/openvino_code/src/inline-completion/tab.ts diff --git a/modules/openvino_code/README.md b/modules/openvino_code/README.md index 6555e4369..cd5f5dc57 100644 --- a/modules/openvino_code/README.md +++ b/modules/openvino_code/README.md @@ -28,6 +28,8 @@ To check the connection manually, use the `Check Connection` button located on t 1. Create a new Python file or open an existing one. 1. Type `def main():` or place the cursor where you'd like code suggestions to be generated. 1. Press the keyboard shortcut `Ctrl+Alt+Space` (`Cmd+Alt+Space` for macOS) or click the `Generate Code Completion` button located in the side panel. +1. You can select the text then generate the related code. +1. You may also right-click on "Generate Inline Code Completion In New Tab" to generate code in a new tab. 1. Use the `Tab` key to accept the entire suggestion or `Ctrl`+`Right Arrow` to accept it word by word. To decline the suggestion, press `Esc`. You can customize the length of the generated code by adjusting `Max New Tokens` and `Min New Tokens` parameters in the extension settings. diff --git a/modules/openvino_code/package.json b/modules/openvino_code/package.json index 73dfd5901..ee16aa4b0 100644 --- a/modules/openvino_code/package.json +++ b/modules/openvino_code/package.json @@ -124,6 +124,11 @@ "category": "OpenVINO Code", "title": "Generate Inline Code Completion" }, + { + "command": "openvinoCode.generateInlineCompletionTab", + "category": "OpenVINO Code", + "title": "Generate Inline Code Completion In New Tab" + }, { "command": "openvinoCode.generateDocstring", "category": "OpenVINO Code", @@ -159,6 +164,23 @@ "when": "view == openvino-code-side-panel", "group": "navigation" } + ], + "editor/context": [ + { + "command": "openvino-code-completion.toggle", + "group": "openvino-code-completion", + "when": "editorTextFocus && config.openvino-code-completion.showCommandsInContextMenu" + }, + { + "command": "openvinoCode.generateInlineCompletion", + "when": "editorFocus", + "group": "openvino-code-completion@1" + }, + { + "command": "openvinoCode.generateInlineCompletionTab", + "when": "editorFocus", + "group": "openvino-code-completion@2" + } ] }, "configuration": [ @@ -264,6 +286,12 @@ "mac": "ctrl+alt+space", "when": "editorTextFocus" }, + { + "command": "openvinoCode.generateInlineCompletionTab", + "key": "ctrl+alt+shift+1", + "mac": "ctrl+alt+shift+1", + "when": "editorTextFocus" + }, { "command": "openvinoCode.stopGeneration", "key": "escape", diff --git a/modules/openvino_code/src/constants.ts b/modules/openvino_code/src/constants.ts index d389d5a9b..230c1250d 100644 --- a/modules/openvino_code/src/constants.ts +++ b/modules/openvino_code/src/constants.ts @@ -18,6 +18,7 @@ export const COMMANDS = { FOCUS_SIDE_PANEL: `${SIDE_PANEL_VIEW_ID}.focus`, OPEN_SETTINGS: 'openvinoCode.openSettings', GENERATE_INLINE_COPMLETION: 'openvinoCode.generateInlineCompletion', + GENERATE_INLINE_COPMLETION_TAB: 'openvinoCode.generateInlineCompletionTab', ACCEPT_INLINE_COMPLETION: 'openvinoCode.acceptInlineCompletion', GENERATE_DOC_STRING: 'openvinoCode.generateDocstring', CHECK_CONNECTION: 'openvinoCode.checkConnection', diff --git a/modules/openvino_code/src/inline-completion/completion.service.ts b/modules/openvino_code/src/inline-completion/completion.service.ts index 4c8be03d0..4f17827a6 100644 --- a/modules/openvino_code/src/inline-completion/completion.service.ts +++ b/modules/openvino_code/src/inline-completion/completion.service.ts @@ -2,6 +2,8 @@ import { InlineCompletionItem, Position, Range, TextDocument, window } from 'vsc import { EXTENSION_DISPLAY_NAME } from '../constants'; import { IGenerateRequest, backendService } from '../services/backend.service'; import { extensionState } from '../state'; +import * as vscode from 'vscode'; +import { getIsGeneralTabActive } from './tab'; const outputChannel = window.createOutputChannel(EXTENSION_DISPLAY_NAME, { log: true }); const logCompletionInput = (input: string): void => outputChannel.append(`Completion input:\n${input}\n\n`); @@ -31,6 +33,47 @@ class CompletionService { if (fillInTheMiddleMode && textAfterCursor.trim()) { return `${startToken}${textBeforeCursor}${middleToken}${textAfterCursor}${endToken}`; } + + const editor = window.activeTextEditor; + if (!editor) { + return ``; // No open text editor + } + + if (getIsGeneralTabActive() === true){ + const text = editor.document.getText(); + const currentPosition = editor.selection.active; + const selectedText = editor.document.getText(editor.selection); + //const logContent = `Cursor Position: Line ${currentPosition.line + 1}, Character ${currentPosition.character + 1}\nSelected Text: ${selectedText}`; + + vscode.workspace.openTextDocument({ content: text }).then(doc => { + vscode.window.showTextDocument(doc, { viewColumn: vscode.ViewColumn.Beside }).then(TabTextEditor => { + const newPosition = new vscode.Position((currentPosition.line + 1), (currentPosition.character + 1)); + const newSelection = new vscode.Selection(newPosition, newPosition); + TabTextEditor.selection = newSelection; + }, + error => { + // Failed to open the document + console.error('Error:', error); + } + ); + }, + error => { + // Failed to open the document + console.error('Error:', error); + } + ); + + if (selectedText !== ``){ + return selectedText; + } else { + return textBeforeCursor; + } + } + + if (!editor.selection.isEmpty) { + const selectedText = editor.document.getText(editor.selection) + return selectedText; + } return textBeforeCursor; } diff --git a/modules/openvino_code/src/inline-completion/inline-completion-component.ts b/modules/openvino_code/src/inline-completion/inline-completion-component.ts index 968c5508d..140f771d7 100644 --- a/modules/openvino_code/src/inline-completion/inline-completion-component.ts +++ b/modules/openvino_code/src/inline-completion/inline-completion-component.ts @@ -4,6 +4,7 @@ import { IExtensionComponent } from '../extension-component.interface'; import { notificationService } from '../services/notification.service'; import { extensionState } from '../state'; import { CommandInlineCompletionItemProvider } from './command-inline-completion-provider'; +import { setIsGeneralTabActive } from './tab'; class InlineCompletion implements IExtensionComponent { private _disposables: Disposable[] = []; @@ -14,39 +15,50 @@ class InlineCompletion implements IExtensionComponent { let commandInlineCompletionDisposable: Disposable; - const generateCommandDisposable = commands.registerCommand(COMMANDS.GENERATE_INLINE_COPMLETION, () => { - if (!extensionState.get('isServerAvailable')) { - notificationService.showServerNotAvailableMessage(extensionState.state); - return; - } - if (extensionState.get('isLoading') && window.activeTextEditor) { - void window.showTextDocument(window.activeTextEditor.document); - return; - } - - extensionState.set('isLoading', true); - - if (commandInlineCompletionDisposable) { - commandInlineCompletionDisposable.dispose(); - } - - commandInlineCompletionDisposable = languages.registerInlineCompletionItemProvider( - { pattern: '**' }, - commandInlineCompletionProvider - ); - - void commandInlineCompletionProvider.triggerCompletion(() => { - commandInlineCompletionDisposable.dispose(); - extensionState.set('isLoading', false); - }); - }); + function generateFunction(): void { + if (!extensionState.get('isServerAvailable')) { + notificationService.showServerNotAvailableMessage(extensionState.state); + return; + } + if (extensionState.get('isLoading') && window.activeTextEditor) { + void window.showTextDocument(window.activeTextEditor.document); + return; + } + + extensionState.set('isLoading', true); + + if (commandInlineCompletionDisposable) { + commandInlineCompletionDisposable.dispose(); + } + + commandInlineCompletionDisposable = languages.registerInlineCompletionItemProvider( + { pattern: '**' }, + commandInlineCompletionProvider + ); + + void commandInlineCompletionProvider.triggerCompletion(() => { + commandInlineCompletionDisposable.dispose(); + extensionState.set('isLoading', false); + }); + } const acceptCommandDisposable = commands.registerCommand(COMMANDS.ACCEPT_INLINE_COMPLETION, () => { void commands.executeCommand('editor.action.inlineSuggest.commit'); }); + const generateCommandDisposable = commands.registerCommand(COMMANDS.GENERATE_INLINE_COPMLETION, () => { + setIsGeneralTabActive(false); + generateFunction(); + }); context.subscriptions.push(generateCommandDisposable, acceptCommandDisposable); this._disposables.push(generateCommandDisposable, acceptCommandDisposable); + + const generateTabCommandDisposable = commands.registerCommand(COMMANDS.GENERATE_INLINE_COPMLETION_TAB, () => { + setIsGeneralTabActive(true); + generateFunction(); + }); + context.subscriptions.push(generateTabCommandDisposable, acceptCommandDisposable); + this._disposables.push(generateTabCommandDisposable, acceptCommandDisposable); } deactivate(): void { diff --git a/modules/openvino_code/src/inline-completion/streaming-inline-completion-component.ts b/modules/openvino_code/src/inline-completion/streaming-inline-completion-component.ts index e6263fe0c..84277a454 100644 --- a/modules/openvino_code/src/inline-completion/streaming-inline-completion-component.ts +++ b/modules/openvino_code/src/inline-completion/streaming-inline-completion-component.ts @@ -12,6 +12,7 @@ import { IExtensionComponent } from '../extension-component.interface'; import { extensionState } from '../state'; import { StreamingCommandInlineCompletionItemProvider } from './streaming-command-inline-completion-provider'; import { notificationService } from '../services/notification.service'; +import { setIsGeneralTabActive } from './tab'; class StreamingInlineCompletion implements IExtensionComponent { private _disposables: Disposable[] = []; @@ -29,7 +30,7 @@ class StreamingInlineCompletion implements IExtensionComponent { generationDisposables = []; } - const generateCommandDisposable = commands.registerCommand(COMMANDS.GENERATE_INLINE_COPMLETION, () => { + function generateFunction(): void { if (!extensionState.get('isServerAvailable')) { notificationService.showServerNotAvailableMessage(extensionState.state); return; @@ -69,14 +70,26 @@ class StreamingInlineCompletion implements IExtensionComponent { // TODO: handle unsetting context on error thrown from triggerCompletion void commands.executeCommand('setContext', EXTENSION_CONTEXT_STATE.GENERATING, false); }); - }); + } const acceptCommandDisposable = commands.registerCommand(COMMANDS.ACCEPT_INLINE_COMPLETION, () => { void commands.executeCommand('editor.action.inlineSuggest.commit'); }); - + + const generateCommandDisposable = commands.registerCommand(COMMANDS.GENERATE_INLINE_COPMLETION, () => { + // Update the boolean variable + setIsGeneralTabActive(false); + generateFunction(); + }); context.subscriptions.push(generateCommandDisposable, acceptCommandDisposable); this._disposables.push(generateCommandDisposable, acceptCommandDisposable); + + const generateTabCommandDisposable = commands.registerCommand(COMMANDS.GENERATE_INLINE_COPMLETION_TAB, () => { + setIsGeneralTabActive(true); + generateFunction(); + }); + context.subscriptions.push(generateTabCommandDisposable, acceptCommandDisposable); + this._disposables.push(generateTabCommandDisposable, acceptCommandDisposable); } deactivate(): void { diff --git a/modules/openvino_code/src/inline-completion/tab.ts b/modules/openvino_code/src/inline-completion/tab.ts new file mode 100644 index 000000000..33bfbd2cd --- /dev/null +++ b/modules/openvino_code/src/inline-completion/tab.ts @@ -0,0 +1,9 @@ +let isGeneralTabActiveInternal: boolean = false; + +export function setIsGeneralTabActive(value: boolean): void { + isGeneralTabActiveInternal = value; +} + +export function getIsGeneralTabActive(): boolean { + return isGeneralTabActiveInternal; +} \ No newline at end of file