diff --git a/bundled/tool/lsp_server.py b/bundled/tool/lsp_server.py index 7090137..4543f11 100644 --- a/bundled/tool/lsp_server.py +++ b/bundled/tool/lsp_server.py @@ -67,6 +67,7 @@ # todo: we should either investigate why logging interact with lsp or find a better way. # Need to stop kedro.framework.project.LOGGING from changing logging settings, otherwise pygls fails with unknown reason. import os + os.environ["KEDRO_LOGGING_CONFIG"] = str(Path(__file__).parent / "dummy_logging.yml") from typing import List @@ -83,7 +84,6 @@ from pygls.server import LanguageServer - class KedroLanguageServer(LanguageServer): """Store Kedro-specific information in the language server.""" @@ -262,51 +262,71 @@ def _get_param_location( @LSP_SERVER.feature(TEXT_DOCUMENT_DEFINITION) def definition( - server: KedroLanguageServer, params: TextDocumentPositionParams + server: KedroLanguageServer, params: TextDocumentPositionParams, word=None ) -> Optional[List[Location]]: """Support Goto Definition for a dataset or parameter.""" _check_project() if not server.is_kedro_project(): return None - document: TextDocument = server.workspace.get_text_document( - params.text_document.uri - ) - word = document.word_at_position(params.position, RE_START_WORD, RE_END_WORD) - - log_for_lsp_debug(f"Query keyword for params: {word}") - - if word.startswith("params:"): - param_location = _get_param_location(server.project_metadata, word) - if param_location: - log_for_lsp_debug(f"{param_location=}") - return [param_location] - - catalog_paths = _get_conf_paths(server, "catalog") + def _query_parameter(document, word=None): + if not word: + word = document.word_at_position( + params.position, RE_START_WORD, RE_END_WORD + ) - catalog_word = document.word_at_position(params.position) - log_for_lsp_debug(f"Attempt to search `{catalog_word}` from catalog") - log_for_lsp_debug(f"{catalog_paths=}") - for catalog_path in catalog_paths: - log_for_lsp_debug(f" {catalog_path=}") - catalog_conf = yaml.load(catalog_path.read_text(), Loader=SafeLineLoader) - if not catalog_conf: - continue - if word in catalog_conf: - line = catalog_conf[word]["__line__"] - location = Location( - uri=f"file://{catalog_path}", - range=Range( - start=Position(line=line - 1, character=0), - end=Position( - line=line, - character=0, + log_for_lsp_debug(f"Query keyword for params: {word}") + + if word.startswith("params:"): + param_location = _get_param_location(server.project_metadata, word) + if param_location: + log_for_lsp_debug(f"{param_location=}") + return [param_location] + + def _query_catalog(document, word=None): + if not word: + word = document.word_at_position(params.position) + catalog_paths = _get_conf_paths(server, "catalog") + log_for_lsp_debug(f"Attempt to search `{word}` from catalog") + log_for_lsp_debug(f"{catalog_paths=}") + + for catalog_path in catalog_paths: + log_for_lsp_debug(f" {catalog_path=}") + catalog_conf = yaml.load(catalog_path.read_text(), Loader=SafeLineLoader) + if not catalog_conf: + continue + if word in catalog_conf: + line = catalog_conf[word]["__line__"] + location = Location( + uri=f"file://{catalog_path}", + range=Range( + start=Position(line=line - 1, character=0), + end=Position( + line=line, + character=0, + ), ), - ), - ) - log_for_lsp_debug(f"{location=}") - return [location] + ) + log_for_lsp_debug(f"{location=}") + return [location] + + if params: + document: TextDocument = server.workspace.get_text_document( + params.text_document.uri + ) + else: + document = None + result = _query_parameter(document, word) + if result: + return result + result = _query_catalog(document, word) + if result: + return result + # If no result, return current location + # This is a VSCode specific logic called Alternative Definition Command + # By default, it triggers Go to Reference so it supports using mouse click for both directions + # from pipline to config and config to pipeline uri = params.text_document.uri pos = params.position curr_pos = Position(line=pos.line, character=pos.character) @@ -522,6 +542,17 @@ def _is_pipeline(uri): return False +###### Commands +@LSP_SERVER.command("kedro.goToDefinitionFromFlowchart") +def definition_from_flowchart(ls, word): + """Starts counting down and showing message synchronously. + It will `block` the main thread, which can be tested by trying to show + completion items. + """ + word = word[0] + result = definition(LSP_SERVER, params=None, word=word) + return result + ### End of kedro-lsp diff --git a/package.json b/package.json index fc25fcf..787754c 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "Kedro", "displayName": "Kedro", "description": "A Kedro VSCode Extension.", - "version": "0.1.0", + "version": "0.2.0-rc0", "preview": false, "serverInfo": { "name": "Kedro", @@ -155,7 +155,13 @@ "command": "pygls.server.executeCommand", "title": "Execute Command", "category": "pygls" + }, + { + "title": "Send Definition Request", + "category": "kedro", + "command": "kedro.sendDefinitionRequest" } + ] }, "dependencies": { diff --git a/src/common/commands.ts b/src/common/commands.ts index ebfd2ce..eb0758d 100644 --- a/src/common/commands.ts +++ b/src/common/commands.ts @@ -1,7 +1,8 @@ import { QuickPickItem, window } from 'vscode'; import * as fs from 'fs'; +import * as vscode from 'vscode'; import { getWorkspaceFolders } from './vscodeapi'; - +import { LanguageClient, LanguageClientOptions, ServerOptions, State, integer } from 'vscode-languageclient/node'; export async function selectEnvironment() { let workspaces = getWorkspaceFolders(); const root_dir = workspaces[0].uri.fsPath; // Only pick the first workspace @@ -20,3 +21,82 @@ export async function selectEnvironment() { return result; } +let logger: vscode.LogOutputChannel; + +/** + * Execute a command provided by the language server. + */ +logger = vscode.window.createOutputChannel('pygls', { log: true }); + +export async function executeServerCommand(lsClient: LanguageClient | undefined) { + if (!lsClient || lsClient.state !== State.Running) { + await vscode.window.showErrorMessage('There is no language server running.'); + return; + } + if (!lsClient.initializeResult) { + await vscode.window.showErrorMessage('The Language Server fail to initialise.'); + return; + } + + const knownCommands = lsClient.initializeResult.capabilities.executeCommandProvider?.commands; + if (!knownCommands || knownCommands.length === 0) { + const info = lsClient.initializeResult.serverInfo; + const name = info?.name || 'Server'; + const version = info?.version || ''; + + await vscode.window.showInformationMessage(`${name} ${version} does not implement any commands.`); + return; + } + + const commandName = await vscode.window.showQuickPick(knownCommands, { canPickMany: false }); + if (!commandName) { + return; + } + logger.info(`executing command: '${commandName}'`); + + const result = await vscode.commands.executeCommand( + commandName /* if your command accepts arguments you can pass them here */, + ); + logger.info(`${commandName} result: ${JSON.stringify(result, undefined, 2)}`); +} + +export async function executeServerDefinitionCommand(lsClient: LanguageClient | undefined) { + if (!lsClient || lsClient.state !== State.Running) { + await vscode.window.showErrorMessage('There is no language server running.'); + return; + } + if (!lsClient.initializeResult) { + await vscode.window.showErrorMessage('The Language Server fail to initialise.'); + return; + } + + const commandName = 'kedro.goToDefinitionFromFlowchart'; + const target = await window.showInputBox({ + placeHolder: 'Type the name of the dataset/parameters, i.e. companies', + }); + logger.info(`executing command: '${commandName}'`); + + const result: any[] | undefined = await vscode.commands.executeCommand( + commandName, + /* if your command accepts arguments you can pass them here */ + target + ); + logger.info(`${commandName} result: ${JSON.stringify(result, undefined, 2)}`); + if (result && result.length > 0) { + const location = result[0]; + const uri: vscode.Uri = vscode.Uri.parse(location.uri); + const range = location.range; + + vscode.window.showTextDocument(uri, + { + selection: range, + viewColumn: vscode.ViewColumn.One, + } + ); + + } + + + + + diff --git a/src/extension.ts b/src/extension.ts index f3344d4..8ea717f 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,6 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { selectEnvironment } from './common/commands'; +import { selectEnvironment, executeServerCommand, executeServerDefinitionCommand } from './common/commands'; import * as vscode from 'vscode'; import { LanguageClient } from 'vscode-languageclient/node'; import { registerLogger, traceError, traceLog, traceVerbose } from './common/log/logging'; @@ -85,9 +85,9 @@ export async function activate(context: vscode.ExtensionContext): Promise traceError( 'Python interpreter missing:\r\n' + - '[Option 1] Select python interpreter using the ms-python.python.\r\n' + - `[Option 2] Set an interpreter using "${serverId}.interpreter" setting.\r\n` + - 'Please use Python 3.8 or greater.', + '[Option 1] Select python interpreter using the ms-python.python.\r\n' + + `[Option 2] Set an interpreter using "${serverId}.interpreter" setting.\r\n` + + 'Please use Python 3.8 or greater.', ); }; @@ -125,6 +125,13 @@ export async function activate(context: vscode.ExtensionContext): Promise if (result) { statusBarItem.text = `$(kedro-logo)` + ' ' + result.label; } + + }), + registerCommand('pygls.server.executeCommand', async () => { + await executeServerCommand(lsClient); + }), + registerCommand('kedro.sendDefinitionRequest', async () => { + await executeServerDefinitionCommand(lsClient); }), );