LSP 서버

Language Server Protocol 1.18 Features

Overview

The Vais Language Server implements the Language Server Protocol (LSP) version 1.18, providing rich IDE features for Vais development.

Implemented Features

Text Document Synchronization

Full Document Sync:

  • Open document notification
  • Change document notification
  • Close document notification
  • Save document notification

Implementation:

pub fn handle_text_document_sync(
    &mut self,
    params: DidChangeTextDocumentParams,
) -> Result<()> {
    let uri = params.text_document.uri;
    let changes = params.content_changes;

    for change in changes {
        self.update_document(&uri, &change.text);
    }

    // Trigger re-analysis
    self.analyze_document(&uri)?;
    self.publish_diagnostics(&uri)?;

    Ok(())
}

Code Completion

Features:

  • Keyword completion (F, S, E, I, L, M, R, etc.)
  • Variable name completion
  • Function name completion
  • Struct field completion
  • Module/import completion
  • Method completion

Trigger Characters: ., :, >

Example:

S Point { x: i64, y: i64 }

F main() -> i64 {
    p := Point { x: 10, y: 20 }
    p.  # <-- Completion shows: x, y
}

Implementation:

pub fn completion(&self, params: CompletionParams) -> Result<CompletionList> {
    let position = params.text_document_position.position;
    let uri = &params.text_document_position.text_document.uri;

    let doc = self.documents.get(uri)?;
    let scope = self.get_scope_at_position(doc, position)?;

    let mut items = Vec::new();

    // Add keywords
    items.extend(self.keyword_completions());

    // Add variables in scope
    items.extend(self.variable_completions(&scope));

    // Add functions
    items.extend(self.function_completions(&scope));

    // Add struct fields if after '.'
    if let Some(fields) = self.struct_field_completions(doc, position)? {
        items.extend(fields);
    }

    Ok(CompletionList {
        is_incomplete: false,
        items,
    })
}

Go to Definition

Supported:

  • Function definitions
  • Variable definitions
  • Struct definitions
  • Enum definitions
  • Type aliases

Example:

F helper() -> i64 { 42 }

F main() -> i64 {
    result := helper()  # Ctrl+Click on 'helper' goes to definition
}

Implementation:

pub fn goto_definition(
    &self,
    params: GotoDefinitionParams,
) -> Result<Option<Location>> {
    let position = params.text_document_position_params.position;
    let uri = &params.text_document_position_params.text_document.uri;

    let doc = self.documents.get(uri)?;
    let word = self.word_at_position(doc, position)?;

    // Look up definition in symbol table
    if let Some(symbol) = self.symbols.get(&word) {
        return Ok(Some(Location {
            uri: symbol.uri.clone(),
            range: symbol.range,
        }));
    }

    Ok(None)
}

Hover Information

Provides:

  • Type information
  • Function signatures
  • Documentation comments
  • Value information

Example:

# Adds two numbers
F add(a: i64, b: i64) -> i64 {
    a + b
}

F main() -> i64 {
    # Hovering over 'add' shows:
    # F add(a: i64, b: i64) -> i64
    # Adds two numbers
    result := add(5, 10)
}

Implementation:

pub fn hover(&self, params: HoverParams) -> Result<Option<Hover>> {
    let position = params.text_document_position_params.position;
    let uri = &params.text_document_position_params.text_document.uri;

    let doc = self.documents.get(uri)?;
    let word = self.word_at_position(doc, position)?;

    if let Some(symbol) = self.symbols.get(&word) {
        let contents = MarkedString::LanguageString(LanguageString {
            language: "vais".to_string(),
            value: symbol.signature.clone(),
        });

        return Ok(Some(Hover {
            contents: HoverContents::Scalar(contents),
            range: Some(symbol.range),
        }));
    }

    Ok(None)
}

Diagnostics

Real-time Error Reporting:

  • Syntax errors
  • Type errors
  • Undefined variable errors
  • Unused variable warnings
  • Dead code warnings

Severity Levels:

  • Error (red squiggly)
  • Warning (yellow squiggly)
  • Information (blue squiggly)
  • Hint (gray dots)

Example:

F main() -> i64 {
    x := "hello"
    result := x + 5  # Error: cannot add string and integer
    unused := 42     # Warning: unused variable
}

Implementation:

pub fn publish_diagnostics(&self, uri: &Url) -> Result<()> {
    let doc = self.documents.get(uri)?;
    let mut diagnostics = Vec::new();

    // Parse and type check
    match self.analyze(doc) {
        Ok(_) => {}
        Err(errors) => {
            for error in errors {
                diagnostics.push(Diagnostic {
                    range: error.range,
                    severity: Some(DiagnosticSeverity::ERROR),
                    message: error.message,
                    ..Default::default()
                });
            }
        }
    }

    // Check for warnings
    diagnostics.extend(self.check_warnings(doc)?);

    self.client.publish_diagnostics(uri.clone(), diagnostics, None);
    Ok(())
}

Document Symbols

Outline View:

  • Functions
  • Structs
  • Enums
  • Traits
  • Implementations

Example Outline:

main.vais
├── S Point
├── S Circle
├── F distance(Point, Point) -> f64
├── F main() -> i64
└── impl Circle
    ├── F new(f64) -> Circle
    └── F area(*Circle) -> f64

Implementation:

pub fn document_symbols(
    &self,
    params: DocumentSymbolParams,
) -> Result<Vec<DocumentSymbol>> {
    let uri = &params.text_document.uri;
    let doc = self.documents.get(uri)?;

    let ast = self.parse(doc)?;
    let mut symbols = Vec::new();

    for item in &ast.items {
        match item {
            Item::Function(func) => {
                symbols.push(DocumentSymbol {
                    name: func.name.clone(),
                    kind: SymbolKind::FUNCTION,
                    range: func.range,
                    selection_range: func.name_range,
                    children: None,
                    ..Default::default()
                });
            }
            Item::Struct(s) => {
                symbols.push(DocumentSymbol {
                    name: s.name.clone(),
                    kind: SymbolKind::STRUCT,
                    range: s.range,
                    selection_range: s.name_range,
                    children: Some(self.struct_field_symbols(s)),
                    ..Default::default()
                });
            }
            // ... more item types
        }
    }

    Ok(symbols)
}

Workspace Symbols

Project-wide Symbol Search:

Ctrl+T / Cmd+T: Search for symbols across entire workspace

Implementation:

pub fn workspace_symbols(
    &self,
    params: WorkspaceSymbolParams,
) -> Result<Vec<SymbolInformation>> {
    let query = params.query.to_lowercase();
    let mut results = Vec::new();

    for (name, symbol) in &self.symbols {
        if name.to_lowercase().contains(&query) {
            results.push(SymbolInformation {
                name: name.clone(),
                kind: symbol.kind,
                location: Location {
                    uri: symbol.uri.clone(),
                    range: symbol.range,
                },
                ..Default::default()
            });
        }
    }

    // Sort by relevance
    results.sort_by(|a, b| {
        let a_starts = a.name.to_lowercase().starts_with(&query);
        let b_starts = b.name.to_lowercase().starts_with(&query);
        b_starts.cmp(&a_starts)
    });

    Ok(results)
}

Find References

Find All Usages:

  • Variable references
  • Function calls
  • Type references

Example:

F helper() -> i64 { 42 }  # Definition

F main() -> i64 {
    x := helper()  # Reference 1
    y := helper()  # Reference 2
    x + y
}

Right-click → Find All References shows both usages.

Rename Symbol

Safe Refactoring:

  • Rename variables
  • Rename functions
  • Rename types
  • Update all references

Example:

# Before:
F oldName() -> i64 { 42 }
F main() -> i64 { oldName() }

# Rename oldName → newName:
F newName() -> i64 { 42 }
F main() -> i64 { newName() }

Implementation:

pub fn rename(
    &self,
    params: RenameParams,
) -> Result<Option<WorkspaceEdit>> {
    let position = params.text_document_position.position;
    let uri = &params.text_document_position.text_document.uri;
    let new_name = params.new_name;

    let doc = self.documents.get(uri)?;
    let old_name = self.word_at_position(doc, position)?;

    // Find all references
    let references = self.find_references(&old_name)?;

    // Create text edits
    let mut changes = HashMap::new();
    for reference in references {
        let edits = changes.entry(reference.uri).or_insert(Vec::new());
        edits.push(TextEdit {
            range: reference.range,
            new_text: new_name.clone(),
        });
    }

    Ok(Some(WorkspaceEdit {
        changes: Some(changes),
        ..Default::default()
    }))
}

Code Actions

Quick Fixes:

  • Import missing modules
  • Add missing type annotations
  • Remove unused variables
  • Extract function
  • Inline variable

Example:

F main() -> i64 {
    unused := 42  # Light bulb icon → "Remove unused variable"
}

Formatting

Automatic Code Formatting:

  • Format on save
  • Format on type
  • Format selection
  • Format document

Configuration:

{
    "vais.format.indentSize": 4,
    "vais.format.maxLineLength": 100,
    "vais.format.insertSpaces": true
}

Signature Help

Parameter Hints: Shows parameter information while typing function calls.

Example:

F complex(a: i64, b: f64, c: String) -> i64 { 0 }

F main() -> i64 {
    # Typing 'complex(' shows:
    # complex(a: i64, b: f64, c: String) -> i64
    #         ^^^^^  ------  --------
    complex(42,
    #          ^ Shows: b: f64
}

Semantic Tokens

Syntax Highlighting:

  • Keywords (F, S, E, I, L, M, R)
  • Functions
  • Variables
  • Types
  • Operators
  • Comments
  • Strings
  • Numbers

Token Types:

  • namespace
  • type
  • class
  • enum
  • interface
  • struct
  • function
  • variable
  • parameter
  • property
  • keyword
  • comment
  • string
  • number
  • operator

Inlay Hints

Type Hints: Shows inferred types inline:

F main() -> i64 {
    x := 42        # : i64
    y := 3.14      # : f64
    s := "hello"   # : String
}

Folding Ranges

Code Folding:

  • Function bodies
  • Struct definitions
  • Impl blocks
  • Comments

Example:

F long_function() -> i64 { # [▼]
    # Many lines of code...
    0
}
# Can fold to:
F long_function() -> i64 { ... }

Performance Features

Incremental Parsing

Only re-parse changed portions of the document:

pub fn incremental_parse(&mut self, changes: Vec<TextDocumentContentChangeEvent>) {
    for change in changes {
        // Only reparse affected ranges
        let affected = self.get_affected_range(&change);
        self.reparse_range(affected);
    }
}

Lazy Symbol Resolution

Symbols are resolved on-demand for better performance.

Caching

  • Parse tree caching
  • Symbol table caching
  • Type information caching

Configuration

VS Code settings.json:

{
    "vais.lsp.enable": true,
    "vais.lsp.trace.server": "verbose",
    "vais.diagnostics.enable": true,
    "vais.completion.enable": true,
    "vais.hover.enable": true,
    "vais.format.enable": true,
    "vais.inlayHints.enable": true
}

Implementation Details

Language Server Architecture:

┌─────────────────────────────────────────┐
│         VSCode Extension                │
└───────────────┬─────────────────────────┘
                │ JSON-RPC
┌───────────────▼─────────────────────────┐
│      Vais Language Server (Rust)        │
├─────────────────────────────────────────┤
│  ┌──────────┐  ┌──────────┐            │
│  │  Parser  │  │  Type    │            │
│  │          │  │  Checker │            │
│  └──────────┘  └──────────┘            │
│  ┌──────────┐  ┌──────────┐            │
│  │ Symbol   │  │ Diagnostics          │
│  │ Table    │  │          │            │
│  └──────────┘  └──────────┘            │
└─────────────────────────────────────────┘

Protocol Version: LSP 3.17 / 1.18

Transport: JSON-RPC over stdio

Testing

# Run LSP tests
cargo test -p vais-lsp

# Test specific feature
cargo test -p vais-lsp -- completion

# Integration tests
cargo test -p vais-lsp --test integration

Performance Metrics

Typical response times:

  • Completion: <10ms
  • Hover: <5ms
  • Go to definition: <5ms
  • Diagnostics: <100ms
  • Formatting: <50ms

Future Enhancements

Planned features:

  • Call hierarchy
  • Type hierarchy
  • Code lens
  • Semantic highlighting improvements
  • Better error recovery
  • Incremental type checking

Conclusion

The Vais LSP implementation provides a complete IDE experience with all essential LSP 1.18 features, enabling productive development with real-time feedback and intelligent code assistance.