-
Notifications
You must be signed in to change notification settings - Fork 0
External LSP API Reference
Status: ✅ Complete
Phase: Phase 4 - Alpha Polishing
Last Updated: December 5, 2025
This document provides detailed API reference for the External LSP Integration feature. It covers the main interfaces, data structures, and usage examples for developers integrating external LSP servers into ricecoder.
The LspServerRegistry manages all configured LSP servers and provides methods to query and manage them.
pub struct LspServerRegistry {
/// Map of language to server configurations
pub servers: HashMap<String, Vec<LspServerConfig>>,
/// Global settings
pub global: GlobalLspSettings,
}Load registry from a YAML configuration file.
Parameters:
-
path: Path to the configuration file
Returns: Result<LspServerRegistry, ConfigError>
Example:
let registry = LspServerRegistry::load_from_file(
Path::new("~/.ricecoder/lsp-servers.yaml")
)?;Get the first available server configuration for a language.
Parameters:
-
language: Language identifier (e.g., "rust", "typescript")
Returns: Option<&LspServerConfig> - First enabled server for the language, or None
Example:
if let Some(config) = registry.get_server_for_language("rust") {
println!("Using: {}", config.executable);
}Get all server configurations for a language.
Parameters:
-
language: Language identifier
Returns: Vec<&LspServerConfig> - All enabled servers for the language
Example:
let servers = registry.get_all_servers_for_language("typescript");
for server in servers {
println!("Available: {}", server.executable);
}Get the language for a file extension.
Parameters:
-
extension: File extension (e.g., ".rs", ".ts")
Returns: Option<&str> - Language identifier, or None
Example:
if let Some(language) = registry.get_language_for_extension(".rs") {
println!("Language: {}", language);
}Reload configuration from disk.
Returns: Result<(), ConfigError>
Example:
registry.reload()?;The ProcessManager manages the lifecycle of external LSP server processes.
pub struct ProcessManager {
processes: HashMap<String, LspClient>,
config: GlobalLspSettings,
}Create a new process manager.
Parameters:
-
config: Global LSP settings
Returns: ProcessManager
Example:
let config = GlobalLspSettings {
max_processes: 5,
default_timeout_ms: 5000,
enable_fallback: true,
health_check_interval_ms: 30000,
};
let manager = ProcessManager::new(config);Spawn a new LSP server process.
Parameters:
-
config: Server configuration -
workspace_root: Workspace root directory
Returns: Result<LspClient, ExternalLspError>
Example:
let client = manager.spawn_server(
&server_config,
Path::new("/path/to/project")
)?;Get existing server for language or spawn new one.
Parameters:
-
language: Language identifier -
workspace_root: Workspace root directory
Returns: Result<&mut LspClient, ExternalLspError>
Example:
let client = manager.get_or_spawn_server("rust", workspace_root)?;Shutdown a server for a language.
Parameters:
-
language: Language identifier
Returns: Result<(), ExternalLspError>
Example:
manager.shutdown_server("rust")?;Shutdown all managed servers.
Returns: Result<(), ExternalLspError>
Example:
manager.shutdown_all()?;Perform health checks on all servers.
Returns: Result<HashMap<String, HealthStatus>, ExternalLspError>
Example:
let health = manager.health_check_all()?;
for (language, status) in health {
println!("{}: {:?}", language, status);
}The LspClient represents a connection to an external LSP server.
pub struct LspClient {
/// Configuration for this client
pub config: LspServerConfig,
/// Current state
pub state: ClientState,
/// Child process handle
pub process: Option<Child>,
/// Stdin writer for sending requests
pub stdin: Option<ChildStdin>,
/// Pending requests awaiting response
pub pending_requests: HashMap<RequestId, PendingRequest>,
/// Server capabilities (after initialization)
pub capabilities: Option<ServerCapabilities>,
/// Workspace root
pub workspace_root: PathBuf,
}Send a request to the LSP server.
Parameters:
-
method: LSP method name (e.g., "textDocument/completion") -
params: Request parameters as JSON
Returns: Result<serde_json::Value, ExternalLspError>
Example:
let params = json!({
"textDocument": { "uri": "file:///path/to/file.rs" },
"position": { "line": 10, "character": 5 }
});
let result = client.send_request("textDocument/completion", params)?;Send a notification to the LSP server (no response expected).
Parameters:
-
method: LSP method name -
params: Notification parameters as JSON
Returns: Result<(), ExternalLspError>
Example:
let params = json!({
"textDocument": { "uri": "file:///path/to/file.rs" },
"contentChanges": [{ "text": "new content" }]
});
client.send_notification("textDocument/didChange", params)?;Initialize the LSP server connection.
Parameters:
-
workspace_root: Workspace root directory
Returns: Result<ServerCapabilities, ExternalLspError>
Example:
let capabilities = client.initialize(Path::new("/path/to/project"))?;
println!("Completion support: {}", capabilities.completion_provider.is_some());Shutdown the LSP server connection gracefully.
Returns: Result<(), ExternalLspError>
Example:
client.shutdown()?;Perform a health check on the server.
Returns: Result<HealthStatus, ExternalLspError>
Example:
let status = client.health_check()?;
match status {
HealthStatus::Healthy { latency } => println!("Latency: {:?}", latency),
HealthStatus::Unhealthy { reason } => println!("Error: {}", reason),
}Configuration for transforming LSP server responses.
pub struct OutputMappingConfig {
/// Mapping rules for completion items
pub completion: Option<CompletionMappingRules>,
/// Mapping rules for diagnostics
pub diagnostics: Option<DiagnosticsMappingRules>,
/// Mapping rules for hover information
pub hover: Option<HoverMappingRules>,
/// Custom transformation functions (by name)
pub custom_transforms: Option<HashMap<String, String>>,
}pub struct CompletionMappingRules {
/// JSON path to completion items array
pub items_path: String,
/// Field mappings for each completion item
pub field_mappings: HashMap<String, String>,
/// Optional transformation function name
pub transform: Option<String>,
}Example:
let rules = CompletionMappingRules {
items_path: "$.result.items".to_string(),
field_mappings: [
("label".to_string(), "$.label".to_string()),
("detail".to_string(), "$.detail".to_string()),
].iter().cloned().collect(),
transform: None,
};pub struct DiagnosticsMappingRules {
/// JSON path to diagnostics array
pub items_path: String,
/// Field mappings for each diagnostic
pub field_mappings: HashMap<String, String>,
/// Optional transformation function name
pub transform: Option<String>,
}pub struct HoverMappingRules {
/// JSON path to hover content
pub content_path: String,
/// Field mappings for hover data
pub field_mappings: HashMap<String, String>,
/// Optional transformation function name
pub transform: Option<String>,
}Configuration for a single LSP server.
pub struct LspServerConfig {
pub language: String,
pub extensions: Vec<String>,
pub executable: String,
pub args: Vec<String>,
pub env: HashMap<String, String>,
pub init_options: Option<serde_json::Value>,
pub enabled: bool,
pub timeout_ms: u64,
pub max_restarts: u32,
pub idle_timeout_ms: u64,
pub output_mapping: Option<OutputMappingConfig>,
}Global settings for LSP server management.
pub struct GlobalLspSettings {
pub max_processes: usize,
pub default_timeout_ms: u64,
pub enable_fallback: bool,
pub health_check_interval_ms: u64,
}State of an LSP client connection.
pub enum ClientState {
Stopped,
Starting,
Running,
Unhealthy,
ShuttingDown,
Crashed { restart_count: u32 },
}Result of a health check.
pub enum HealthStatus {
Healthy { latency: Duration },
Unhealthy { reason: String },
}Error types for external LSP operations.
pub enum ExternalLspError {
ServerNotFound { executable: String },
SpawnFailed(std::io::Error),
ServerCrashed { reason: String },
Timeout { timeout_ms: u64 },
ProtocolError(String),
InitializationFailed(String),
ConfigError(String),
NoServerForLanguage { language: String },
}use ricecoder_external_lsp::{LspServerRegistry, ProcessManager};
use std::path::Path;
// Load configuration
let registry = LspServerRegistry::load_from_file(
Path::new("~/.ricecoder/lsp-servers.yaml")
)?;
// Create process manager
let mut manager = ProcessManager::new(registry.global.clone());
// Get or spawn server for Rust
let client = manager.get_or_spawn_server("rust", Path::new("/path/to/project"))?;
// Send completion request
let params = serde_json::json!({
"textDocument": { "uri": "file:///path/to/file.rs" },
"position": { "line": 10, "character": 5 }
});
let completions = client.send_request("textDocument/completion", params)?;
// Shutdown
manager.shutdown_all()?;use ricecoder_external_lsp::{
LspServerConfig, OutputMappingConfig, CompletionMappingRules,
};
use std::collections::HashMap;
let config = LspServerConfig {
language: "custom".to_string(),
extensions: vec![".custom".to_string()],
executable: "custom-lsp-server".to_string(),
args: vec![],
env: HashMap::new(),
init_options: None,
enabled: true,
timeout_ms: 5000,
max_restarts: 3,
idle_timeout_ms: 300000,
output_mapping: Some(OutputMappingConfig {
completion: Some(CompletionMappingRules {
items_path: "$.completions".to_string(),
field_mappings: [
("label".to_string(), "$.name".to_string()),
("detail".to_string(), "$.description".to_string()),
].iter().cloned().collect(),
transform: None,
}),
diagnostics: None,
hover: None,
custom_transforms: None,
}),
};// Check health of all servers
let health = manager.health_check_all()?;
for (language, status) in health {
match status {
HealthStatus::Healthy { latency } => {
println!("{}: OK ({}ms)", language, latency.as_millis());
}
HealthStatus::Unhealthy { reason } => {
println!("{}: FAILED ({})", language, reason);
}
}
}// Try external LSP, fall back to internal
let completions = match client.send_request("textDocument/completion", params) {
Ok(result) => {
// Use external LSP completions
parse_lsp_completions(result)
}
Err(e) => {
// Fall back to internal provider
eprintln!("External LSP failed: {}", e);
internal_completion_provider.get_completions(&context)
}
};All operations return Result<T, ExternalLspError>. Handle errors appropriately:
match client.send_request("textDocument/completion", params) {
Ok(result) => {
// Process result
}
Err(ExternalLspError::Timeout { timeout_ms }) => {
eprintln!("Request timed out after {}ms", timeout_ms);
// Fall back to internal provider
}
Err(ExternalLspError::ServerCrashed { reason }) => {
eprintln!("Server crashed: {}", reason);
// Attempt restart or fall back
}
Err(e) => {
eprintln!("Error: {}", e);
// Handle other errors
}
}servers:
rust:
- language: rust
extensions: [".rs"]
executable: rust-analyzerglobal:
max_processes: 5
default_timeout_ms: 5000
enable_fallback: true
health_check_interval_ms: 30000
servers:
rust:
- language: rust
extensions: [".rs"]
executable: rust-analyzer
args: []
env: {}
init_options:
checkOnSave:
command: "clippy"
enabled: true
timeout_ms: 10000
max_restarts: 3
idle_timeout_ms: 300000servers:
custom:
- language: custom
extensions: [".custom"]
executable: custom-lsp-server
output_mapping:
completion:
items_path: "$.completions"
field_mappings:
label: "$.name"
detail: "$.description"- External LSP Integration - Main feature guide
- External LSP Configuration - Configuration reference
- Custom LSP Servers Guide - Guide for custom servers
- LSP Integration - Internal LSP server documentation
Last updated: December 5, 2025