Skip to content

billy-and-the-oceans/lsp-builder

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

lsp-builder

Build language servers in 30 lines of Rust.

lsp-builder wraps tower-lsp into a builder pattern that eliminates the protocol boilerplate. You write the language logic; it handles the LSP plumbing.

Before / After

tower-lsp (manual)lsp-builder
struct Backend { client: Client, ... }

#[tower_lsp::async_trait]
impl LanguageServer for Backend {
    async fn initialize(&self, _: InitializeParams)
        -> Result<InitializeResult> {
        Ok(InitializeResult {
            capabilities: ServerCapabilities {
                text_document_sync: Some(...),
                completion_provider: Some(CompletionOptions {
                    trigger_characters: Some(vec![...]),
                    ..Default::default()
                }),
                hover_provider: Some(...),
                ..Default::default()
            },
            ..Default::default()
        })
    }
    async fn completion(&self, params: CompletionParams)
        -> Result<Option<CompletionResponse>> {
        // 30+ lines of conversion code...
    }
    async fn hover(&self, params: HoverParams)
        -> Result<Option<Hover>> {
        // more conversion code...
    }
    // did_open, did_change, did_close, shutdown...
}
use lsp_builder::*;

let config = LspBuilder::new("my-lsp")
    .with_completions(|ctx| {
        vec![Completion::new("hello", "A greeting")]
    })
    .with_hover(|ctx| {
        Some(HoverInfo::new("**hello** — A greeting"))
    })
    .build();

config.run_stdio().await;

Quick Start

Add to your Cargo.toml:

[dependencies]
lsp-builder = { git = "https://github.com/billy-and-the-oceans/lsp-builder" }
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }

Then write your server:

use lsp_builder::{Completion, CompletionKind, HoverInfo, LspBuilder};

#[tokio::main]
async fn main() {
    let keywords = vec![
        ("let", "Declare a variable"),
        ("fn", "Declare a function"),
        ("if", "Conditional branch"),
    ];

    let config = LspBuilder::new("my-lsp")
        .with_completions(move |ctx| {
            let prefix = ctx.word_at_position().unwrap_or("");
            keywords
                .iter()
                .filter(|(kw, _)| kw.starts_with(prefix))
                .map(|(kw, desc)| {
                    Completion::new(*kw, *desc).with_kind(CompletionKind::Keyword)
                })
                .collect()
        })
        .with_hover(|ctx| {
            let word = ctx.word_at_position()?;
            match word {
                "let" => Some(HoverInfo::new("**let** — Bind a value")),
                "fn" => Some(HoverInfo::new("**fn** — Define a function")),
                _ => None,
            }
        })
        .build();

    config.run_stdio().await;
}

Run it: cargo run — then point your editor's LSP client at the process.

Features

  • Builder pattern — configure only what you need, get sensible defaults for the rest
  • Closure API — pass a function for quick prototyping
  • Trait object API — implement CompletionProvider, HoverProvider, etc. for complex logic
  • Tree-sitter integration — optional incremental parsing with automatic document management
  • Thread-safe document store — built-in DashMap-backed store with parse tree caching
  • Custom IOrun_stdio() for standard LSP, or run_with_streams() for testing/custom transport

Supported LSP Features

Feature Closure API Trait API
Completions with_completions() with_completion_provider()
Hover with_hover() with_hover_provider()
Diagnostics with_diagnostics() with_diagnostic_provider()
Go to Definition with_definition_provider()
Find References with_references_provider()
Document Formatting with_formatting_provider()

Tree-sitter Integration

If your language has a tree-sitter grammar, lsp-builder handles incremental parsing automatically:

let config = LspBuilder::new("my-lsp")
    .with_language(tree_sitter_my_lang::language())
    .with_completions(|ctx| {
        // ctx.content has the source text
        // DocumentStore automatically maintains parse trees
        vec![]
    })
    .build();

The DocumentStore detects edit regions between versions and uses tree-sitter's incremental parsing to re-parse only what changed.

Real-world Usage

lsp-builder powers Kotonoha LSP, a language server for a music notation DSL. It provides completions, hover, diagnostics, and go-to-definition for a custom tree-sitter grammar.

License

Licensed under either of

at your option.

About

Build language servers in 30 lines of Rust

Resources

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages