Skip to content

Implement Language Server Protocol (LSP) for LUMOS #45

@rz1989s

Description

@rz1989s

Goal

Create a Language Server Protocol implementation for LUMOS to enable IDE features (auto-completion, diagnostics, hover info) across multiple editors.

Phase: 5.2 IDE Integration
Foundation Issue: Enables multi-editor support


Problem

Currently:

  • ❌ IDE features only in VSCode extension (limited to one editor)
  • ❌ Duplicated logic needed for each editor plugin
  • ❌ No standardized way to provide language intelligence
  • ❌ Other editors (IntelliJ, Neovim, Emacs) have no LUMOS support

Solution

Implement LSP server that provides language features to any LSP-compatible editor.

What is LSP?

Language Server Protocol standardizes communication between editors and language tooling:

  • One LSP server → Works with all editors
  • Developed by Microsoft, industry standard
  • Supported by VS Code, IntelliJ, Neovim, Emacs, Sublime, etc.

Features to Implement

1. Diagnostics (Errors & Warnings)

  • Syntax errors (red squiggles)
  • Undefined type references
  • Invalid attribute usage
  • Version format errors

2. Auto-Completion

  • Solana types (PublicKey, Signature)
  • Primitive types (u8-u128, i8-i128, bool)
  • Complex types (Vec, Option)
  • Attributes (#[solana], #[account], #[version])

3. Hover Information

  • Type definitions
  • Field documentation
  • Attribute explanations

4. Go to Definition

  • Jump to struct/enum definition
  • Jump to field type definition

5. Code Actions (Quick Fixes)

  • Add missing #[solana] attribute
  • Fix undefined type references
  • Convert between Vec and arrays

6. Document Symbols

  • Outline view (list of structs/enums)
  • Symbol search

7. Formatting

  • Auto-format on save
  • Consistent indentation

Implementation

Technology Stack

Rust crates:

  • tower-lsp - LSP server framework
  • tokio - Async runtime
  • serde_json - JSON-RPC communication

Architecture

Editor (VS Code, Neovim, etc.)
    ↕ JSON-RPC (LSP)
LUMOS Language Server
    ↓
LUMOS Parser (existing)
    ↓
AST Analysis

Project Structure

packages/
  └── lsp/
      ├── Cargo.toml
      ├── src/
      │   ├── main.rs           # Binary entry point
      │   ├── server.rs         # LSP server implementation
      │   ├── handlers/
      │   │   ├── diagnostics.rs
      │   │   ├── completion.rs
      │   │   ├── hover.rs
      │   │   └── formatting.rs
      │   └── utils.rs
      └── tests/

Core Server Implementation

Location: packages/lsp/src/server.rs

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

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

#[tower_lsp::async_trait]
impl LanguageServer for LumosLanguageServer {
    async fn initialize(&self, _: InitializeParams) -> Result<InitializeResult> {
        Ok(InitializeResult {
            capabilities: ServerCapabilities {
                text_document_sync: Some(TextDocumentSyncCapability::Kind(
                    TextDocumentSyncKind::FULL,
                )),
                completion_provider: Some(CompletionOptions::default()),
                hover_provider: Some(HoverProviderCapability::Simple(true)),
                diagnostic_provider: Some(DiagnosticServerCapabilities::Options(
                    DiagnosticOptions::default(),
                )),
                // ... more capabilities
                ..Default::default()
            },
            ..Default::default()
        })
    }

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

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

    async fn did_open(&self, params: DidOpenTextDocumentParams) {
        self.on_change(params.text_document).await;
    }

    async fn did_change(&self, params: DidChangeTextDocumentParams) {
        self.on_change(params.text_document).await;
    }

    async fn completion(&self, params: CompletionParams) -> Result<Option<CompletionResponse>> {
        // Implementation
    }

    async fn hover(&self, params: HoverParams) -> Result<Option<Hover>> {
        // Implementation
    }
}

LSP Features Implementation

1. Diagnostics

async fn on_change(&self, text_document: TextDocumentItem) {
    let uri = text_document.uri;
    let text = text_document.text;
    
    // Parse LUMOS schema
    match parse_schema(&text) {
        Ok(_) => {
            // Clear diagnostics
            self.client.publish_diagnostics(uri, vec\![], None).await;
        }
        Err(errors) => {
            let diagnostics: Vec<Diagnostic> = errors.iter().map(|e| {
                Diagnostic {
                    range: e.span.to_lsp_range(),
                    severity: Some(DiagnosticSeverity::ERROR),
                    message: e.message.clone(),
                    source: Some("lumos".to_string()),
                    ..Default::default()
                }
            }).collect();
            
            self.client.publish_diagnostics(uri, diagnostics, None).await;
        }
    }
}

2. Auto-Completion

async fn completion(&self, params: CompletionParams) -> Result<Option<CompletionResponse>> {
    let items = vec\![
        CompletionItem {
            label: "PublicKey".to_string(),
            kind: Some(CompletionItemKind::STRUCT),
            detail: Some("Solana public key (32 bytes)".to_string()),
            ..Default::default()
        },
        CompletionItem {
            label: "u64".to_string(),
            kind: Some(CompletionItemKind::KEYWORD),
            detail: Some("64-bit unsigned integer".to_string()),
            ..Default::default()
        },
        // ... more items
    ];
    
    Ok(Some(CompletionResponse::Array(items)))
}

3. Hover Information

async fn hover(&self, params: HoverParams) -> Result<Option<Hover>> {
    // Get word at cursor position
    let word = get_word_at_position(params.text_document_position_params);
    
    let hover_text = match word.as_str() {
        "PublicKey" => "Solana public key (32 bytes)\nUsed for account addresses",
        "u64" => "64-bit unsigned integer\nRange: 0 to 18,446,744,073,709,551,615",
        // ... more cases
        _ => return Ok(None),
    };
    
    Ok(Some(Hover {
        contents: HoverContents::Scalar(MarkedString::String(hover_text.to_string())),
        range: None,
    }))
}

Binary Distribution

Install as CLI

cargo install lumos-lsp

Usage

Start server (typically called by editor):

lumos-lsp

With custom options:

lumos-lsp --stdio --log-level debug

Editor Configuration

VS Code

Update existing extension to use LSP:

{
  "contributes": {
    "configuration": {
      "properties": {
        "lumos.lsp.path": {
          "type": "string",
          "default": "lumos-lsp",
          "description": "Path to LUMOS language server"
        }
      }
    }
  }
}

Neovim (nvim-lspconfig)

require'lspconfig'.lumos.setup{
  cmd = {"lumos-lsp"},
  filetypes = {"lumos"},
  root_dir = require'lspconfig'.util.root_pattern(".git"),
}

Emacs (lsp-mode)

(add-to-list 'lsp-language-id-configuration '(lumos-mode . "lumos"))
(lsp-register-client
 (make-lsp-client :new-connection (lsp-stdio-connection "lumos-lsp")
                  :major-modes '(lumos-mode)
                  :server-id 'lumos-lsp))

Testing

Unit Tests

#[tokio::test]
async fn test_diagnostics_on_syntax_error() {
    let server = create_test_server();
    let text = "struct Player { invalid syntax }";
    
    let diagnostics = server.analyze(text).await;
    assert\!(\!diagnostics.is_empty());
}

#[tokio::test]
async fn test_completion_includes_solana_types() {
    let server = create_test_server();
    let completions = server.get_completions(Position::new(0, 0)).await;
    
    assert\!(completions.iter().any(|c| c.label == "PublicKey"));
}

Integration Tests

  • Start LSP server
  • Send LSP requests (via JSON-RPC)
  • Verify responses match LSP spec
  • Test all features (diagnostics, completion, hover)

Success Criteria

  • LSP server binary compiles and runs
  • Implements core LSP features (diagnostics, completion, hover)
  • Works with VS Code
  • Works with Neovim
  • Works with at least one more editor (Emacs or IntelliJ)
  • Real-time diagnostics on file changes
  • Auto-completion for Solana and primitive types
  • Hover information for types and attributes
  • Published as lumos-lsp on crates.io
  • All tests passing
  • Documentation for editor setup

Documentation

Create New Files

  • packages/lsp/README.md - LSP server documentation
  • docs/editors/setup.md - Editor configuration guide
  • docs/editors/vscode.md - VS Code setup
  • docs/editors/neovim.md - Neovim setup
  • docs/editors/emacs.md - Emacs setup

Update Files

  • README.md - Add LSP installation instructions
  • CHANGELOG.md - Document LSP release

Benefits

Universal Support: One server, all editors
Maintainability: Single codebase for language features
Consistency: Same features across editors
Scalability: Easy to add new editors
Standard Protocol: Industry-standard LSP


Related


Priority: High (foundation for IDE support)
Complexity: High
Timeline: 7-10 days
Blocks: All other Phase 5.2 features

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:coreCore compiler (parser, generator, IR, transform)area:ecosystemCross-repo initiatives and ecosystem-wide featureshelp wantedExtra attention is neededhigh-priorityHigh priority tasktype:featureNew feature or functionality

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions