Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to register a command and run it in one step? #476

Open
noklam opened this issue Jun 26, 2024 · 9 comments
Open

How to register a command and run it in one step? #476

noklam opened this issue Jun 26, 2024 · 9 comments

Comments

@noklam
Copy link
Contributor

noklam commented Jun 26, 2024

I found this section in the docs: https://pygls.readthedocs.io/en/v0.10.2/pages/advanced_usage.html#commands
The entrypoint is registered in the playground:

{
"command": "pygls.server.executeCommand",
"title": "Execute Command",
"category": "pygls"

While this works fine, it involves two steps to execute a command:

  1. Execute pygls command
  2. Select the corresponding one

Is there a way to combine these into 1 step? The alternative is using the VSCode API directly https://code.visualstudio.com/api/extension-guides/command, but it has some downside:

  1. I have to write TS, I am much more familiar with Python
  2. It cannot access information on the server side.

It seems like this is possible by modifying

async function executeServerCommand() {
if (!client || client.state !== State.Running) {
await vscode.window.showErrorMessage("There is no language server running.")
return
}
, is there any tradeoff implementing this on the server side instead of client?

@noklam noklam changed the title How to register a command? How to register a command and run it in one step? Jun 26, 2024
@alcarney
Copy link
Collaborator

The 2-step process is just a quirk from the fact the playground tries to make it possible to call a command from any server, without knowing up front what those commands are called.

Assuming that your command does not require any arguments, you should be able to replace the value of command in

{
"command": "pygls.server.executeCommand",
"title": "Execute Command",
"category": "pygls"

with the id of your custom command (the string you pass to @server.command()). It should then be possible to call your command directly from the VSCode command palette.

However, if your command does take arguments or you need to process the return value in any way then I don't think you can avoid writing some TS! 😅

@noklam
Copy link
Contributor Author

noklam commented Aug 2, 2024

Coming back to this after one month! I haven't tried this yet. We are working on a visual component and currently exploring if it's possible to add interaction between a webview and the LSP.

I have been thinking there is two possible ways:

  1. Send request directly through the language client, but I cannot find any API that I can use.
  2. Use VSCode command, basically when I click on a React component, it triggers the VSCode action (argument will be extracted from the react component), and trigger the LSP response.

@alcarney
Copy link
Collaborator

alcarney commented Aug 2, 2024

Send request directly through the language client, but I cannot find any API that I can use.

There are generic sendRequest and sendNotification methods on the VSCode language client.

I use them to send a few custom messages to esbonio, but I imagine they would work fine for standard LSP messages also

add interaction between a webview and the LSP

It's also possible to setup a web socket connection with a pygls powered server

Yes, it's more complicated than routing the message via VSCode's Webview API, but it does open the door to the webview working with editors other than VSCode.

@noklam
Copy link
Contributor Author

noklam commented Aug 13, 2024

There are generic sendRequest and sendNotification methods on the VSCode language client.

Thanks for giving some pointer. I tried to implement this today and using the lsClient in Typescript directly to sendRequest. It seems like the the server is communicating properly but VSCode UI does not respond to it at all.

The request looks like this in the client (it's hard coded now as I just want to test if this mechanism works)

    lsClient?.sendRequest("textDocument/definition",
        {
            "textDocument": {
                "uri": "file:///Users/Nok_Lam_Chan/dev/kedro/tmp/spaceflights/src/spaceflights/pipelines/data_science/pipeline.py"
            },
            "position": {
                "line": 11,
                "character": 29
            }
        }
    );

I register this function in a command so I can invoke it with the UI.

export async function sendDefinitionRequest(lsClient: LanguageClient | undefined) {
    lsClient?.sendRequest("textDocument/definition",
  ...

After that, the server respond with the correct response but the cursor didn't move.

2024-08-13 14:35:54.996 [info] [Trace - 14:35:54] Received response 'textDocument/definition - (47)' in 22ms.
2024-08-13 14:35:54.996 [info] Result: [
{
"uri": "file:///Users/Nok_Lam_Chan/dev/kedro/tmp/spaceflights/conf/base/catalog.yml",
"range": {
"start": {
"line": 43,
"character": 0
},
"end": {
"line": 44,
"character": 0
}
}
}
]

Do I need to handle the response from the client side or is that something VSCode take care of automatically?

@noklam
Copy link
Contributor Author

noklam commented Aug 13, 2024

command-request

The command is mainly for testing, it mimics when I do cmd+click on a string. I manage to reproduce the navigation function, but I wonder is this necessary. Since in the case of using pygls, I only need to declare @LSP_SERVER.feature(TEXT_DOCUMENT_DEFINITION) and the UI know how to respond to the server.

For now I am using the vscode.window api on the client side.

@alcarney
Copy link
Collaborator

Do I need to handle the response from the client side or is that something VSCode take care of automatically?

Yes, you would need to handle the response as using sendRequest is going to bypass the "internal wiring" setup by VSCode's language client. Here for example is how the client usually handles TEXT_DOCUMENT_DEFINITION.

To be honest... I don't know what the "right" way to call TEXT_DOCUMENT_DEFINITION programmatically so that the result is automatically handled. Since the example above is implementing an API that allows VSCode to decide when TEXT_DOCUMENT_DEFINITION should be called.

The command is mainly for testing,

Maybe I'm reading too much into your example, but from the gif it looks like you might want to implement the workspace/symbol request?

@noklam
Copy link
Contributor Author

noklam commented Aug 13, 2024

@alcarney Adding symbol would be interesting but I don't think it's suitable in this case. The idea in mind is to embedded this into webview and triggering the Go to Definition from the graph. So the command is temporary for testing just because it is handy to trigger it manually. It won't be needed once we integrate it properly.

bypass the "internal wiring" setup by VSCode's language client.

This is the logic that I was looking for, but at the end I implemented a simple one just to get started. It roughly looks like this.

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

Maybe I'm reading too much into your example, but from the gif it looks like you might want to implement the #470 request?

Since you mentioned symbol, is there any example that I can look into. Right now the LSP do the analysis on the demand, I think I will need to build these symbols with cache.

@alcarney
Copy link
Collaborator

Since you mentioned symbol, is there any example that I can look into. Right now the LSP do the analysis on the demand, I think I will need to build these symbols with cache.

There is the example symbols.py server which maintains an internal index of all the known symbols.

Other than that you could go through some of the servers in Implementations.md and see if/how they handle it

@noklam
Copy link
Contributor Author

noklam commented Aug 15, 2024

We actually implemented a custom command that that a word as an argument. But seems like we could just use vscode.executeDefinitionProvider, since our own LSP is already registered as a definition provider, it's not necessary to call the LSP directly. Our LSP is build around a Python DSL with YAML.

vscode.commands.executeCommand<vscode.Location[]>(
                'vscode.executeDefinitionProvider',
                document.uri,
                position,
            );

This is what it looks like now.
viz-flow

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants