Skip to content

Commit

Permalink
Merge pull request #63 from kedro-org/noklam/lsp-command
Browse files Browse the repository at this point in the history
Add new command for LSP/Webview integration
  • Loading branch information
noklam authored Aug 14, 2024
2 parents 2bccbd8 + 911879d commit c8517ea
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 43 deletions.
105 changes: 68 additions & 37 deletions bundled/tool/lsp_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -83,7 +84,6 @@
from pygls.server import LanguageServer



class KedroLanguageServer(LanguageServer):
"""Store Kedro-specific information in the language server."""

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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


Expand Down
8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -155,7 +155,13 @@
"command": "pygls.server.executeCommand",
"title": "Execute Command",
"category": "pygls"
},
{
"title": "Send Definition Request",
"category": "kedro",
"command": "kedro.sendDefinitionRequest"
}

]
},
"dependencies": {
Expand Down
82 changes: 81 additions & 1 deletion src/common/commands.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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,
}
);

}





15 changes: 11 additions & 4 deletions src/extension.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -85,9 +85,9 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>

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.',
);
};

Expand Down Expand Up @@ -125,6 +125,13 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
if (result) {
statusBarItem.text = `$(kedro-logo)` + ' ' + result.label;
}

}),
registerCommand('pygls.server.executeCommand', async () => {
await executeServerCommand(lsClient);
}),
registerCommand('kedro.sendDefinitionRequest', async () => {
await executeServerDefinitionCommand(lsClient);
}),
);

Expand Down

0 comments on commit c8517ea

Please sign in to comment.