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

tower-lsp in the web #187

Closed
segeljakt opened this issue May 4, 2020 · 20 comments
Closed

tower-lsp in the web #187

segeljakt opened this issue May 4, 2020 · 20 comments
Labels
question Further information is requested

Comments

@segeljakt
Copy link

Hi,

I am trying to setup a website which has both an editor (https://github.com/TypeFox/monaco-languageclient) and language server (tower-lsp) running concurrently on the client-side in the browser. If possible it would be nice for portability to run everything inside WebAssembly or Javascript.

An example of how to implement the language server on the javascript side is: https://github.com/TypeFox/monaco-languageclient/blob/98f15c6499f23c853882c557dc18ccf7406ed1cf/example/src/json-server.ts#L18-L23

This looks very similar to how it's done in tower-lsp:

#[tokio::main]
async fn main() {
env_logger::init();
let stdin = tokio::io::stdin();
let stdout = tokio::io::stdout();
let (service, messages) = LspService::new(Backend::default());
Server::new(stdin, stdout)
.interleave(messages)
.serve(service)
.await;
}

From discussions on the Tokio Discord, Tokio cannot be compiled to WebAssembly since it has dependencies to unix. Do you see any other possibilities down the line to run tower-lsp in the browser?

The reason behind writing the server in Rust and compiling it to WASM, instead of writing a server in Javascript that makes calls to handlers in Rust, is that I it should be easier to keep state between compilations.

@ebkalderon
Copy link
Owner

Sounds like an awesome project, @segeljakt! While it is a goal for tower-lsp to decouple completely from tokio, it is unfortunately impossible to achieve at the moment. There are two pain points which currently keep us tied to tokio:

  1. There are a few uses of tokio::spawn in a few internal locations (tracked by Avoid calling tokio::spawn internally #180). Most, if not all, of these can removed with a bit of refactoring. I'm actively looking into this and have seen some success in recent PRs (see Fix broken client request routing and loosen trait bounds #184 for one example). The remaining uses could potentially be removed using FuturesUnordered or similar, but it requires a bit more investigation.
  2. There is no standard AsyncRead and AsyncWrite trait available in the standard library, and tokio and futures diverge in compatibility currently. We require tokio::io::{AsyncRead,AsyncWrite} as trait bounds in Server currently, which locks us into one executor at the moment. We could switch over to futures::io::{AsyncRead,AsyncWrite}, but that would break compatibility with tokio, a runtime with a sizeable community which integrates closely with the jsonrpc-core and tower ecosystems anyway.

The first issue will hopefully be resolved soon, but the second one is a blocker. We can't fully remove the dependency on tokio until the AsyncRead and AsyncWrite traits are standardized, unfortunately. 😞

@ebkalderon ebkalderon added the question Further information is requested label May 4, 2020
@ebkalderon
Copy link
Owner

I found this GitHub Gist which which demonstrates a clever but ugly hack to adapt a type implementing tokio::io::AsyncRead into futures::io::AsyncRead using a wrapper type. Given that the default runtime for most tower users is likely to be tokio, I imagine they wouldn't appreciate being required to use these wrappers. Ideally, we should find a common solution to the AsyncRead and AsyncWrite standardization problem going forward.

@segeljakt
Copy link
Author

segeljakt commented May 4, 2020

Thank you for the detailed answer and also for tower-lsp! I agree, it's probably not best to rush it. For the moment I will try out a synchronous approach to my website. When the support hits, I could potentially try and help with creating a minimal example of how to setup LSP for a browser editor.

@ebkalderon
Copy link
Owner

Sure, that would be most appreciated! Thanks. 😄 In the meantime, I'll be keeping this issue open and update it as new changes come in.

@segeljakt
Copy link
Author

Ok, cool 🤩

@ebkalderon
Copy link
Owner

Combined with the recent release of v0.12.0 and efforts on #180 (comment), all calls to tokio::spawn() except one have been eliminated, thereby making this task very close to being solved! Only the issue of AsyncRead and AsyncWrite remains unanswered today.

@ebkalderon
Copy link
Owner

I wonder if it would be possible to add an opt-in feature flag which replaces tokio::io::{AsyncRead,AsyncWrite} with the futures equivalents to permit usage with alternative executors and/or with WASM targets? If these traits are later stabilized in std, as appears to be the plan, this feature could be safely removed in a new minor release. What do you think about this, @segeljakt?

@ebkalderon
Copy link
Owner

ebkalderon commented Aug 16, 2020

Actually, after a bit of investigation, I'm not so sure that could work. FramedRead and FramedWrite seem to have a hard requirement on the tokio::io::AsyncRead and tokio::io::AsyncWrite and StreamExt traits (source), and they likely cannot be easily switched out.

@ebkalderon
Copy link
Owner

ebkalderon commented Aug 20, 2020

A bit more investigation yielded the futures_codec crate, which seems to do exactly what we need! It provides precisely the same FramedRead and FramedWrite structs and similar, but not identical, Encoder and Decoder traits as tokio and tokio-util. ❤️

I'm currently blocked on matthunz/futures-codec#46 for now. If this can be resolved in a timely manner, it could be possible to add a runtime-independent Cargo flag which would allow us to swap out tokio and tokio-util for plain futures and futures_codec and therefore be compatible with async-std, smol, and/or WASM.

@ebkalderon
Copy link
Owner

WIP code is available in the opt-out-tokio branch, although it does not compile at the time of writing due to the issue described above.

@segeljakt
Copy link
Author

The progress you have made is excellent. Sorry I have not been active. My knowledge in the async Rust ecosystem is very poor. I doubt I can be of too much help besides testing the functionality 😬

@segeljakt
Copy link
Author

segeljakt commented Aug 20, 2020

The option to opt-out of Tokio with a feature flag would be awesome, on my end I might use something like:

// in Cargo.toml

[target.'cfg(target_arch = "wasm32")'.dependencies]
// tower-lsp without tokio

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
// tower-lsp with tokio

@ebkalderon
Copy link
Owner

No problem at all, @segeljakt! Just trying to keep you and everyone else following up to date regarding the current progress.

@ebkalderon
Copy link
Owner

ebkalderon commented Mar 3, 2022

Should be supported with --no-default-features --features runtime-agnostic as of merging PR #309!

@TimJentzsch
Copy link

Hey @segeljakt, I was wondering how you ended up making the language server for your website and if you found success with tower-lsp on that front.
I also tried to compile it to WASM (to distribute it platform-independently together with the client), but had issues combining async Rust with the WASM limitations.

If you have any insights I'd much appreciate it :)

@segeljakt
Copy link
Author

Hey, @TimJentzsch. I don't recall getting it working at the time, but probably Rust's async has matured since then. It should probably be possible to use an async executor like https://github.com/wasm-rs/async-executor.

@TimJentzsch
Copy link

Thanks for your reply!

My main problem has been to get the IO working, i.e. implementing AsyncRead/AsyncWrite for some IO that's available on WASM. Unfortunately, because the target OS is unknown, almost all common IO methods do not work.
The only runtime I got compiled with WASM was async-std, but I have not seen IO in async-executor either. I will make sure to check it out in more detail though, thanks for the link!
I have also tried to wrap the stdin/stdout that node.js provides, but was unable to implement the AsyncRead trait for that.

If you're interested in helping out with using tower-lsp with WASM, feel free to check out my sample project where I'm experimenting with it: https://github.com/TimJentzsch/tower-lsp-wasm
But as said earlier, it's not working yet, unfortunately.

@ebkalderon
Copy link
Owner

@TimJentzsch Have you tried using the WebSockets transport with ws_stream_wasm instead of standard I/O or any of the other transports supported by both vscode-languageserver-node and WASM that are readable/writable?

I see that it might be valuable for us to create an example VSCode extension demonstrating WASM support and commit it to the repository, or else point to your external repo once completed, to help future users.

@TimJentzsch
Copy link

I have not tried ws_stream_wasm yet, I only looked into I/O methods built into async-std (which all didn't work). I'll try that out next!

@segeljakt
Copy link
Author

Hi, sorry for the late response. I'm not currently using Rust for my compiler but I'm very interested to see it working in action.

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

No branches or pull requests

3 participants