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

custom methods for notification without params not called #409

Open
Kryx-Ikyr opened this issue Jan 20, 2024 · 6 comments
Open

custom methods for notification without params not called #409

Kryx-Ikyr opened this issue Jan 20, 2024 · 6 comments

Comments

@Kryx-Ikyr
Copy link

Kryx-Ikyr commented Jan 20, 2024

Hello,

I'm working on a language server that is using custom notifications.
I can't manage to make them work, and I think it could be coming from tower-lsp.

Here is my code:

use backend::Backend;
use tower_lsp::{LspService, Server};

#[derive(Debug)]
pub struct Backend {
    pub client: Client,
}

#[tower_lsp::async_trait]
impl LanguageServer for Backend {
    async fn initialize(&self, _: InitializeParams) -> Result<InitializeResult> {
        Ok(InitializeResult::default())
    }

    async fn initialized(&self, _: InitializedParams) {
        let options = DidChangeWatchedFilesRegistrationOptions {
            watchers: vec![
                FileSystemWatcher {
                    glob_pattern: GlobPattern::String("**".to_string()),
                    kind: Some(WatchKind::Change),
                },
            ],
        };
        match self.client.register_capability(vec![
            Registration {
                id: "workspace/didChangeWatchedFiles".to_string(),
                method: "workspace/didChangeWatchedFiles".to_string(),
                register_options: Some(to_value(options).unwrap()),
            },
            Registration {
                id: "workspace/didChangeConfiguration".to_string(),
                method: "workspace/didChangeConfiguration".to_string(),
                register_options: None,
            },
        ]).await {
            Ok(_) => (),
            Err(e) => self.client.log_message(MessageType::ERROR, format!("Error registering capabilities: {:?}", e)).await,
        }
        self.client.log_message(MessageType::INFO, "server initialized!").await;
    }

    async fn shutdown(&self) -> Result<()> {
        Ok(())
    }
}

impl Backend {
    pub async fn client_config_changed(&self) {
        
    }

    pub async fn client_ready(&self) {   // <------- Implementation of my custom method
        self.client.log_message(MessageType::INFO, format!("Client ready !")).await
    }
}

#[tokio::main]
async fn main() {
    println!("starting server");
    let debug = true;
    if debug {
        let listener = tokio::net::TcpListener::bind("127.0.0.1:2087").await.unwrap();

        loop {
            let (stream, _) = listener.accept().await.unwrap();
            let (reader, writer) = tokio::io::split(stream);
            let (service, messages) = LspService::build(|client| Backend { client })
                .custom_method("custom/configurationChanged", Backend::client_config_changed)
                .custom_method("custom/clientReady", Backend::client_ready)  //<-------- Here I register my custom method
                .finish();
            let server = Server::new(reader, writer, messages);
            tokio::spawn(async move {
                server.serve(service).await;
            });
        }
    } else {
        let stdin = tokio::io::stdin();
        let stdout = tokio::io::stdout();

        let (service, socket) = LspService::new(|client| Backend { client, odoo:None });
        Server::new(stdin, stdout, socket).serve(service).await;
    }
}

In this example, client_ready is never called, but if I put breakpoints in tower-lsp, it seems that the event is catched, the related method is registered the Router and should be called, but failed to do it. I'm quite new to Rust and I can't understand why my function isn't called.
Is it something from my code?

From what I see, the call is stopped here:

C:\Users\XX.cargo\registry\src\index.crates.io-6f17d22bba15001f\tower-lsp-0.20.0\src\jsonrpc\router.rs

impl<P, R, E> Service<Request> for MethodHandler<P, R, E>
where
    P: FromParams,
    R: IntoResponse,
    E: Send + 'static,
{
    type Response = Option<Response>;
    type Error = E;
    type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;

    fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
        Poll::Ready(Ok(()))
    }

    fn call(&mut self, req: Request) -> Self::Future {
        let (_, id, params) = req.into_parts();

        match id {
            Some(_) if R::is_notification() => return future::ok(().into_response(id)).boxed(),
            None if !R::is_notification() => return future::ok(None).boxed(),
            _ => {}
        }

        let params = match P::from_params(params) {
            Ok(params) => params,
            Err(err) => return future::ok(id.map(|id| Response::from_error(id, err))).boxed(),
        };

        (self.f)(params)
            .map(move |r| Ok(r.into_response(id)))
            .boxed()
    }
}

Thank you for your help !

EDIT: To give more information, the notification is sent from vscode, from a custom extension that is calling

client.sendNotification(
    "custom/clientReady",
);

in typescript. This extension is used with another language server in Python that is working. Moreover I can see tower-lsp catching the message, so I don't think that the issue could come from the client.

@ratmice
Copy link

ratmice commented Jan 20, 2024

It has been long enough since I implemented custom methods in my lsp, that I forget most of the implementation details.
However I can at the very least say that it did work at that time (I appear to be a few releases behind and still on 0.18 though)
https://github.com/ratmice/nimbleparse_lsp/blob/main/server/src/main.rs#L703-L706

Sorry if I can't be more helpful at the moment.

@Kryx-Ikyr
Copy link
Author

It's weird, I don't see what is different in my code. However I'm wondering if it could be related to the params. In my case it is a notification, without params or return value. And from what I see tower-lsp is stopping the propagation on the build of the parameters, so it is maybe related. I will try later (I'm not on my computer) to add a fake parameter to test it.

@Kryx-Ikyr
Copy link
Author

Unfortunately, I tested everything, but can't manage to make it work.
It seems that the json sent from vscode for notification contains an array of 1-length as params with a null value as first param.
From what I see, tower-lsp struggle to deserialize it, but I can totally be wrong, I'm new to Rust :)
Some help would be really appreciated ;)

@Kryx-Ikyr
Copy link
Author

Kryx-Ikyr commented Jan 22, 2024

If I change my method in Rust to:

#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct ReadyParams {
    value1: u32,
}

pub async fn client_ready(&self, params: ReadyParams) {
    self.client.log_message(MessageType::INFO, format!("Client ready !")).await
}

To add a custom parameter, and edit the typescript to send this parameter:

client.sendNotification(
     "Odoo/clientReady",
      {
       "value1": 5
      }
);

my custom method is called. I think tower-lsp doesn't accept notification (and maybe requests) without parameters.
From what I see, it happens here:

if let Some(p) = params {

Where let Some(p) = params is valid because params is not empty but contains a null value, so it is raising an error

@Kryx-Ikyr Kryx-Ikyr changed the title custom methods not called custom methods for notification without params not called Jan 22, 2024
@blmarket
Copy link

custom_method is to define custom method, not custom notification handler. I don't think tower-lsp currently handles custom notifications...

Simply speaking, how about just work with custom methods for now, and ignore the response? it will just do the work for now.

@Kryx-Ikyr
Copy link
Author

As a notification is a method without parameter, custom_method should handle them too, and the documentation of tower-lsp states it clearly: "Defines a custom JSON-RPC request or notification with the given method name and handler."
"Handlers may optionally include a single params argument."

But I managed to solve my issue, and I think it is coming from vscode, and not really tower-lsp.
When you use the LSP API of vscode and call the "send_notification("custom_name"); " method, their are building the parameters to send with the selected method. As there is no parameter here, javascript is setting it to the default value: null.

It results in a call of the method "custom_name" with the parameter 'null' in tower-lsp, which doesn't exist.
For me it's the vscode API that doesn't follow their own documentation...
I solved my issue by adding a dummy parameter to my notifications and they are working like a charm now :)

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

3 participants