-
Notifications
You must be signed in to change notification settings - Fork 0
Refactoring LSP Integration
Status: ✅ Complete
Phase: Phase 2
Last Updated: December 5, 2025
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.
- 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
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)
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
- Rust:
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
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)
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
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?;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)?;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"),
}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: []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"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?;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.
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(())
}// 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"Problem: LSP server is installed but not detected
Solution:
- Verify LSP server is in PATH:
which rust-analyzer - Manually register LSP provider in configuration
- Check LSP server logs for errors
Problem: Refactoring fails when using LSP provider
Solution:
- Check LSP server is running:
ps aux | grep rust-analyzer - Check LSP server logs for errors
- Try with configured rules or built-in provider
- Report issue to LSP server project
Problem: Configuration changes not detected
Solution:
- Ensure configuration watcher is running
- Check file permissions on configuration files
- Manually trigger configuration reload
- Restart refactoring engine
- LSP Provider Lookup: < 50ms
- Configuration Loading: < 100ms
- Provider Fallback: < 10ms
- Hot Reload Check: < 5s interval
- 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
- Refactoring Engine - Main refactoring engine documentation
- Refactoring Configuration - Configuration guide
- Refactoring Architecture - Architecture documentation
- LSP-First Architecture - LSP-first architecture guide
Last updated: December 5, 2025