home ~ projects ~ socials

Make A Basic LSP Server In Rust

"test"

These are basic scratch notes for what I did to get hello world setup and making progress for a LSP for neopolitan

Notes

  • use this to attach manually:

    https://neovim.io/doc/user/lsp.html#vim.lsp.buf_attach_client%28%29

  • Looks like from the tower-lsp-boilerplate you need
capabilities: ServerCapabilities {
    text_document_sync: Some(TextDocumentSyncCapability::Kind(
        TextDocumentSyncKind::FULL,
    )),
    semantic_tokens_provider: Some(...stuff...),
    ..ServerCapabilities::default()
}
Stuff:

:lua vim.lsp.set_log_level('debug')
:lua =vim.lsp.get_active_clients()
:lua vim.lsp.buf_attach_client(0, #ID)

Cargo.toml

[package]
name = "neopolitan-lsp"
version = "0.1.0"
edition = "2021"

[dependencies]
tokio = { version = "1.32", features=['full'] }
tower-lsp = "0.20.0"

src/main.rs

use tower_lsp::jsonrpc::Result;
use tower_lsp::lsp_types::*;
use tower_lsp::{Client, LanguageServer, LspService, Server};

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

#[tower_lsp::async_trait]
impl LanguageServer for Backend {
    async fn initialize(&self, _: InitializeParams) -> Result<InitializeResult> {
        Ok(InitializeResult {
            capabilities: ServerCapabilities {
                hover_provider: Some(HoverProviderCapability::Simple(true)),
                completion_provider: Some(CompletionOptions::default()),
                ..Default::default()
            },
            ..Default::default()
        })
    }

    async fn initialized(&self, _: InitializedParams) {
        self.client
            .log_message(MessageType::INFO, "server initialized!")
            .await;
    }

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

    async fn completion(&self, _: CompletionParams) -> Result<Option<CompletionResponse>> {
        Ok(Some(CompletionResponse::Array(vec![
            CompletionItem::new_simple("Hello".to_string(), "Some detail".to_string()),
            CompletionItem::new_simple("Bye".to_string(), "More detail".to_string()),
        ])))
    }

    async fn hover(&self, _: HoverParams) -> Result<Option<Hover>> {
        Ok(Some(Hover {
            contents: HoverContents::Scalar(MarkedString::String("You're hovering!".to_string())),
            range: None,
        }))
    }
}

#[tokio::main]
async fn main() {
    let stdin = tokio::io::stdin();
    let stdout = tokio::io::stdout();

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

nvim.lua

vim.lsp.start({
  name = 'neopolitan',
  cmd = {'neopolitan-lsp'},
  root_dir = vim.fs.dirname(vim.fs.find({'setup.py', 'pyproject.toml'}, { upward = true })[1]),
})

Notes

  • I installed the binary with cargo install --path . to get it on my path.
  • The root_dir is from the example is isn't what I need, but it doesn't fight with anything
-- end of line --

References