ebkalderon / tower-lsp

Language Server Protocol implementation written in Rust
Apache License 2.0
1.04k stars 60 forks source link

Add an example for handling state #113

Open icsaszar opened 4 years ago

icsaszar commented 4 years ago

I think an example with a recommended method to handle state would be very useful (since it's what a real use case would probably look like).

I think simply printing the open files in the workspace every time a file is opened (did_open) or closed did_close and counting the number of calls to did_change would be enough to get started.

The main reason for this is that only &self is available in the methods, so something straight forward like this

struct SourceFile {
     // ...
}

struct Backend {
     files: Vec<SourceFile>
}

impl LanguageServer for Backend {
    // ...
    fn did_open(&self, printer: &Printer, params: DidOpenTextDocumentParams) {
        let file = SourceFile::new(...);
        self.files.push(file);
    }
    // ...
}

won't compile because Vec::push() requires a mutable reference to &self.

It would be also nice to recommend something that can make use of the fact that the server has async support.

ebkalderon commented 4 years ago

I think this is a great idea! Normally, the solution for this would be to use a Mutex or RwLock to manage synchronization of the files field. We could write an example which showcases this use.

If we follow through with https://github.com/ebkalderon/tower-lsp/issues/13#issuecomment-589995913 and turn the notification handlers into async fn methods which can be spawned onto the runtime with tokio::spawn(), we could then use a tokio::sync::Mutex to avoid blocking the executor.

icsaszar commented 4 years ago

I can help with the implementation of the example once #13 is done.

While on this subject, a "Useful resources/Relevant projects" section in the readme could also be useful.

So far the following resources were very helpful for me:

ebkalderon commented 4 years ago

I wholeheartedly recommend this! I especially recommend codespan and codespan-lsp for handling text spans and code diagnostics because it interoperates directly with lsp-types, a foundational crate used by tower-lsp. I try to be mindful of version incompatibilities between codespan-lsp and lsp-types and I generally avoid upgrading the lsp-types dependency in tower-lsp sometimes specifically to remain compatible with both.

However, I don't think I can reasonably recommend using both codespan and ropey in the same project, though, since codespan::Files expects T: AsRef<str> and ropey::{Ropey,RopeySlice} do not implement this, sadly, so the two crates cannot easily integrate.

icsaszar commented 4 years ago

@ebkalderon I just found your nix-language-server project which could serve as the example we're looking for.

It's clean and understandable, it has state handling, error reporting and even incremental text updates.

ebkalderon commented 4 years ago

Thanks for the kind words! While I think it's a pretty good example of tower-lsp on its own, but I feel it's hardly clean enough to recommend to new users to reference. Error handling is poor and there is no revision checking of incremental text updates at the moment, which means that undoing certain edits and re-applying them can occasionally screw up the integrity of the cached text. Note the giant //! HACK: All of this comment at the top of the backend.rs module (link). 😅

It could serve as a good example if given a bit more attention and polish, though.

ratmice commented 2 years ago

I ran into a case where the above blueprint didn't really work, because of a variable which was !Sync and !Send, which kept that variable from living across an await point (I.e. it could only live on the stack within an async block). If you guys think it would be worth having an example which shows dealing with this situation, I could try and extract a minimal example out of it.

silvanshade commented 2 years ago

@ratmice sure, if you have an example to share that could be interesting. Even just having some notes about some other scenario like that in this issue could be useful for people searching the repo.

ratmice commented 2 years ago

I think it probably requires too much detail about the specific situation, but I threw together a very specific example in pr #340