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

Serve on TCP over stdin #196

Closed
saskenuba opened this issue Jul 27, 2020 · 6 comments · Fixed by #198
Closed

Serve on TCP over stdin #196

saskenuba opened this issue Jul 27, 2020 · 6 comments · Fixed by #198
Assignees
Labels
question Further information is requested

Comments

@saskenuba
Copy link

Is it possible to listen on TCP over stdin? I believe this would make the development a bit easier to follow.

Since Server takes an AsyncRead + Unpin I thought it would be simple, but it only answers the first message and the client hangs. I am still noobish with async rust, sorry.

Minimal example:

#[tokio::main]
async fn main() -> Result<()> {
    env_logger::init();

    let mut listener = tokio::net::TcpListener::bind("127.0.0.1:9001").await?;
    let (stream, _) = listener.accept().await?;

    let stdout = tokio::io::stdout();
    info!("Starting generic LSP Server..");

    let (service, messages) = LspService::new(Backend::default());

    Server::new(stream, stdout)
        .interleave(messages)
        .serve(service)
        .await;

    Ok(())
}
@ebkalderon
Copy link
Owner

ebkalderon commented Jul 28, 2020

Hey there, @saskenuba! Glad you're experimenting and learning with Rust. I should first point out that tower-lsp has never been tested and verified to work in TCP mode, only in the line-based stdio mode. There's no guarantee this will work as expected, as it's not yet supported.

With that said, I can see that your code is receiving requests from a TcpStream and routing responses to stdout, which I highly doubt is what you actually want to do. In fact, what you would want to do is to take the TcpStream and use tokio::io::split() to split it into a read half and a write half, and pass them into the Server like this:

#[tokio::main]
async fn main() -> Result<()> {
    env_logger::init();

    let mut listener = tokio::net::TcpListener::bind("127.0.0.1:9001").await?;
    let (stream, _) = listener.accept().await?;
    let (read, write) = tokio::io::split(stream);

    info!("Starting generic LSP Server..");

    let (service, messages) = LspService::new(Backend::default());

    Server::new(read, write)
        .interleave(messages)
        .serve(service)
        .await;

    Ok(())
}

This should allow requests and responses to actually be routed properly over TCP. But as stated previously, the existing message flushing code was not built with TCP in mind, so I highly doubt this will work. I imagine a true TCP language server would instead have separate connections for client-to-server and server-to-client, rather than multiplexing messages over a single stream, like stdio does. It's just not supported by tower-lsp at this time.

You're more than welcome to submit pull requests, of course!

@ebkalderon ebkalderon added the question Further information is requested label Jul 28, 2020
@saskenuba
Copy link
Author

Hey, I can't believe I was passing stdout, no wonder the client couldn't read those messages, feeling stupid right now, haha.

After adjusting my code, even if not intended, it worked flawlessly over TCP! Thanks!

@ebkalderon
Copy link
Owner

ebkalderon commented Jul 29, 2020

That's fantastic news, @saskenuba! I'm glad to hear that tower-lsp works on TCP. Have you tried it in an editor that supports LSP over TCP to verify it behaves as expected?

Maybe we should update the included example projects to document that this works? We could rename server.rs to stdio.rs, and duplicate stdio.rs as tcp.rs and modify it to initialize with TcpListener as above.

@saskenuba
Copy link
Author

Yes! I've used emacs with the eglot package. It offers an ad-hoc way of connecting to LSP servers, that supports both TCP and stdio with almost no configuration, except for host and port.

We really should do it, developing through TCP made it a whole lot easier than snooping file descriptors or logging into separate files.

@ebkalderon
Copy link
Owner

Sounds great! I was just able to verify that server-to-client notifications work as expected with coc.nvim and am about to verify server-to-client requests. On the examples side, I think we could rename examples/server.rs to stdio.rs, and then duplicate the file and name it tcp.rs, modifying it accordingly to work with tokio::net::TcpListener. Would you like to open a PR for that, @saskenuba? I'd be more than happy to review!

@ebkalderon
Copy link
Owner

Okay, I've created #198 to update and document how to use tower-lsp with a TCP transport. Thanks a ton for testing and validating tower-lsp on your end! I'm planning on releasing a new version to Crates.io soon with the updated documentation and other new features that have been quietly baking on master for a while now.

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

Successfully merging a pull request may close this issue.

2 participants