Skip to content

Refactoring LSP Integration

Mo Abualruz edited this page Dec 5, 2025 · 1 revision

LSP-First Refactoring Architecture

Status: ✅ Complete

Phase: Phase 2

Last Updated: December 5, 2025


Overview

The Refactoring Engine implements an LSP-first provider priority architecture that leverages external Language Server Protocol (LSP) servers for language-specific refactoring operations. This enables ricecoder to use specialized, maintained language tools instead of building them internally.

Key Features

  • LSP-First Priority: External LSP servers are the highest-priority provider
  • Graceful Fallback: Falls back through configuration, built-in, and generic providers
  • Hot Reload: Detects LSP server availability changes without restart
  • Multi-Language Support: Supports any language with an available LSP server
  • Zero Configuration: Works out-of-the-box with available LSP servers

Provider Priority Chain

The refactoring engine uses a provider priority chain (highest to lowest):

1. External LSP Servers (language-specific, maintained by language communities)
   ↓ (If unavailable or fails)
2. Configured Refactoring Rules (YAML/JSON configuration files)
   ↓ (If unavailable or fails)
3. Built-in Language-Specific Providers (Rust, TypeScript, Python)
   ↓ (If unavailable or fails)
4. Generic Text-Based Refactoring (fallback for any language)

Priority 1: External LSP Servers

When an LSP server is available for a language, the refactoring engine delegates operations to it:

  • Advantages:

    • Leverages language community expertise
    • Automatic updates with language ecosystem
    • Specialized, maintained implementations
    • Support for latest language features
  • Supported LSP Servers:

    • Rust: rust-analyzer
    • TypeScript/JavaScript: tsserver, typescript-language-server
    • Python: pylsp, pyright
    • Go: gopls
    • Java: eclipse.jdt.ls
    • C/C++: clangd

Priority 2: Configured Refactoring Rules

When LSP is unavailable, the engine uses configured refactoring rules:

  • Advantages:

    • Customizable behavior per project
    • No code changes needed
    • Lightweight alternative to LSP
    • Good for simple use cases
  • Configuration Format: YAML/JSON

  • Location: .agent/refactoring/languages/*.yaml

Priority 3: Built-in Language Providers

When LSP and configuration are unavailable, built-in providers are used:

  • Advantages:

    • Works without external dependencies
    • Predictable behavior
    • Good for common operations
    • Fallback for unsupported languages
  • Supported Languages:

    • Rust (via tree-sitter)
    • TypeScript (via tree-sitter)
    • Python (via tree-sitter)

Priority 4: Generic Fallback

When all else fails, generic text-based refactoring is used:

  • Advantages:
    • Works for any language
    • No language knowledge required
    • Better than nothing
    • Enables graceful degradation

How to Use

Automatic LSP Detection

The refactoring engine automatically detects available LSP servers:

use ricecoder_refactoring::{RefactoringEngine, ConfigManager};
use std::sync::Arc;

let config_manager = Arc::new(ConfigManager::new());
let engine = RefactoringEngine::new(config_manager);

// Automatically uses LSP if available, falls back otherwise
let result = engine.preview(&refactoring).await?;

Manual LSP Registration

You can manually register LSP providers:

use ricecoder_refactoring::{LspProviderRegistry, LspServerInfo, LspIntegration};
use std::sync::Arc;

let registry = Arc::new(LspProviderRegistry::new());

// Create LSP provider for Rust
let server_info = LspServerInfo {
    language: "rust".to_string(),
    name: "rust-analyzer".to_string(),
    command: "rust-analyzer".to_string(),
    args: vec![],
};

let provider = LspIntegration::create_lsp_provider("rust", &server_info)?;
registry.register("rust".to_string(), provider)?;

Checking Provider Availability

You can check which provider will be used for a language:

let provider_info = engine.get_provider_info("rust");
match provider_info.as_str() {
    "lsp" => println!("Using LSP provider"),
    "configured" => println!("Using configured provider"),
    "builtin" => println!("Using built-in provider"),
    "generic" => println!("Using generic fallback"),
    _ => println!("Unknown provider"),
}

Configuration

LSP Server Configuration

Configure LSP servers in .agent/refactoring/lsp.yaml:

lsp_servers:
  rust:
    name: rust-analyzer
    command: rust-analyzer
    args: []
    
  typescript:
    name: tsserver
    command: tsserver
    args: []
    
  python:
    name: pylsp
    command: pylsp
    args: []

Refactoring Rules Configuration

Configure fallback rules in .agent/refactoring/languages/rust.yaml:

language: rust
extensions:
  - .rs
rules:
  - name: "unused_variable"
    pattern: "let \\w+ = .*;"
    refactoring_type: "RemoveUnused"
    enabled: true
transformations:
  - name: "rename_function"
    from_pattern: "fn old_name"
    to_pattern: "fn new_name"
    description: "Rename function from old_name to new_name"

Hot Reload

The refactoring engine supports hot reload of LSP server availability and configuration changes:

use ricecoder_refactoring::LspWatcher;
use std::sync::Arc;

let registry = Arc::new(LspProviderRegistry::new());
let watcher = LspWatcher::new(registry);

// Start watching for changes
watcher.start().await?;

// LSP server availability changes are detected automatically
// Configuration changes are reloaded without restart

// Stop watching when done
watcher.stop().await?;

Fallback Behavior

When an LSP server fails or becomes unavailable, the engine automatically falls back to the next provider:

LSP Server Fails
    ↓
Try Configured Rules
    ↓
Try Built-in Provider
    ↓
Use Generic Fallback

This ensures the system never fails completely for unsupported languages.

Examples

Example 1: Rename Function with LSP

use ricecoder_refactoring::{
    RefactoringEngine, Refactoring, RefactoringType, RefactoringTarget,
    RefactoringOptions, ConfigManager,
};
use std::path::PathBuf;
use std::sync::Arc;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let config_manager = Arc::new(ConfigManager::new());
    let engine = RefactoringEngine::new(config_manager);

    let refactoring = Refactoring {
        id: "rename-function".to_string(),
        refactoring_type: RefactoringType::Rename,
        target: RefactoringTarget {
            file: PathBuf::from("src/main.rs"),
            symbol: "old_name".to_string(),
            range: None,
        },
        options: RefactoringOptions::default(),
    };

    // If rust-analyzer is available, it will be used
    // Otherwise, falls back to configured rules or built-in provider
    let result = engine.preview(&refactoring).await?;
    println!("Provider: {}", engine.get_provider_info("rust"));
    println!("Impact: {:?}", result.impact);

    Ok(())
}

Example 2: Graceful Degradation

// For an unsupported language, the system gracefully falls back
let refactoring = Refactoring {
    id: "refactor-unknown".to_string(),
    refactoring_type: RefactoringType::Rename,
    target: RefactoringTarget {
        file: PathBuf::from("src/unknown.xyz"),
        symbol: "symbol".to_string(),
        range: None,
    },
    options: RefactoringOptions::default(),
};

// Uses generic text-based refactoring
let result = engine.preview(&refactoring).await?;
println!("Provider: {}", engine.get_provider_info("xyz")); // "generic"

Troubleshooting

LSP Server Not Detected

Problem: LSP server is installed but not detected

Solution:

  1. Verify LSP server is in PATH: which rust-analyzer
  2. Manually register LSP provider in configuration
  3. Check LSP server logs for errors

Refactoring Fails with LSP

Problem: Refactoring fails when using LSP provider

Solution:

  1. Check LSP server is running: ps aux | grep rust-analyzer
  2. Check LSP server logs for errors
  3. Try with configured rules or built-in provider
  4. Report issue to LSP server project

Configuration Not Reloaded

Problem: Configuration changes not detected

Solution:

  1. Ensure configuration watcher is running
  2. Check file permissions on configuration files
  3. Manually trigger configuration reload
  4. Restart refactoring engine

Performance

  • LSP Provider Lookup: < 50ms
  • Configuration Loading: < 100ms
  • Provider Fallback: < 10ms
  • Hot Reload Check: < 5s interval

Limitations

  • LSP servers must be installed separately
  • LSP server availability depends on system configuration
  • Some refactoring operations may not be supported by all LSP servers
  • Generic fallback provides basic functionality only

See Also


Last updated: December 5, 2025

Clone this wiki locally