Skip to main content

ai_agent/utils/plugins/
lsp_plugin_integration.rs

1// Source: ~/claudecode/openclaudecode/src/utils/plugins/lspPluginIntegration.rs
2#![allow(dead_code)]
3
4use std::collections::HashMap;
5use std::path::Path;
6
7use serde::{Deserialize, Serialize};
8
9use super::plugin_directories::get_plugin_data_dir;
10use super::plugin_options_storage::{
11    get_plugin_storage_id, load_plugin_options, substitute_plugin_variables,
12    substitute_user_config_variables,
13};
14use crate::plugin::types::{LoadedPlugin, PluginError};
15
16/// LSP server configuration.
17#[derive(Serialize, Deserialize, Debug, Clone)]
18pub struct LspServerConfig {
19    pub command: String,
20    pub args: Option<Vec<String>>,
21    pub env: Option<HashMap<String, String>>,
22    pub workspace_folder: Option<String>,
23    #[serde(rename = "extensionToLanguage")]
24    pub extension_to_language: Option<HashMap<String, String>>,
25}
26
27/// Scoped LSP server configuration.
28#[derive(Debug, Clone)]
29pub struct ScopedLspServerConfig {
30    pub config: LspServerConfig,
31    pub scope: String,
32    pub source: String,
33}
34
35/// Load LSP server configurations from a plugin.
36pub async fn load_plugin_lsp_servers(
37    plugin: &LoadedPlugin,
38    errors: &mut Vec<PluginError>,
39) -> Result<Option<HashMap<String, LspServerConfig>>, String> {
40    let mut servers: HashMap<String, LspServerConfig> = HashMap::new();
41
42    // Check for .lsp.json file in plugin directory
43    let lsp_json_path = Path::new(&plugin.path).join(".lsp.json");
44    if lsp_json_path.exists() {
45        match tokio::fs::read_to_string(&lsp_json_path).await {
46            Ok(content) => {
47                match serde_json::from_str::<HashMap<String, LspServerConfig>>(&content) {
48                    Ok(parsed) => {
49                        servers.extend(parsed);
50                    }
51                    Err(e) => {
52                        log::error!(
53                            "LSP config validation failed for .lsp.json in plugin {}: {}",
54                            plugin.name,
55                            e
56                        );
57                    }
58                }
59            }
60            Err(e) if e.kind() != std::io::ErrorKind::NotFound => {
61                log::error!("Failed to read .lsp.json in plugin {}: {}", plugin.name, e);
62            }
63            _ => {}
64        }
65    }
66
67    if servers.is_empty() {
68        Ok(None)
69    } else {
70        Ok(Some(servers))
71    }
72}
73
74/// Resolve environment variables for plugin LSP servers.
75pub fn resolve_plugin_lsp_environment(
76    config: &LspServerConfig,
77    plugin: &LoadedPlugin,
78    user_config: Option<&HashMap<String, serde_json::Value>>,
79) -> LspServerConfig {
80    let resolve_value = |value: &str| -> String {
81        let mut resolved = substitute_plugin_variables(value, &plugin.path, &plugin.source);
82
83        if let Some(uc) = user_config {
84            resolved = substitute_user_config_variables(&resolved, uc);
85        }
86
87        expand_env_vars_in_string(&resolved)
88    };
89
90    let mut resolved = config.clone();
91
92    resolved.command = resolve_value(&config.command);
93
94    if let Some(ref args) = config.args {
95        resolved.args = Some(args.iter().map(|a| resolve_value(a)).collect());
96    }
97
98    let mut resolved_env: HashMap<String, String> = HashMap::new();
99    resolved_env.insert("CLAUDE_PLUGIN_ROOT".to_string(), plugin.path.clone());
100    resolved_env.insert(
101        "CLAUDE_PLUGIN_DATA".to_string(),
102        get_plugin_data_dir(&plugin.source),
103    );
104
105    if let Some(ref env) = config.env {
106        for (key, value) in env {
107            if key != "CLAUDE_PLUGIN_ROOT" && key != "CLAUDE_PLUGIN_DATA" {
108                resolved_env.insert(key.clone(), resolve_value(value));
109            }
110        }
111    }
112    resolved.env = Some(resolved_env);
113
114    if let Some(ref wf) = config.workspace_folder {
115        resolved.workspace_folder = Some(resolve_value(wf));
116    }
117
118    resolved
119}
120
121fn expand_env_vars_in_string(value: &str) -> String {
122    let mut result = value.to_string();
123    let mut start = 0;
124    while let Some(pos) = result[start..].find("${") {
125        let abs_pos = start + pos;
126        if let Some(end) = result[abs_pos + 2..].find('}') {
127            let var_name = &result[abs_pos + 2..abs_pos + 2 + end];
128            if let Ok(env_value) = std::env::var(var_name) {
129                result.replace_range(abs_pos..abs_pos + 2 + end + 1, &env_value);
130                start = abs_pos + env_value.len();
131                continue;
132            }
133        }
134        start = abs_pos + 2;
135    }
136    result
137}
138
139/// Add plugin scope to LSP server configs.
140pub fn add_plugin_scope_to_lsp_servers(
141    servers: HashMap<String, LspServerConfig>,
142    plugin_name: &str,
143) -> HashMap<String, ScopedLspServerConfig> {
144    servers
145        .into_iter()
146        .map(|(name, config)| {
147            let scoped_name = format!("plugin:{}:{}", plugin_name, name);
148            (
149                scoped_name,
150                ScopedLspServerConfig {
151                    config,
152                    scope: "dynamic".to_string(),
153                    source: plugin_name.to_string(),
154                },
155            )
156        })
157        .collect()
158}
159
160/// Extract all LSP servers from loaded plugins.
161pub async fn extract_lsp_servers_from_plugins(
162    plugins: &[LoadedPlugin],
163    errors: &mut Vec<PluginError>,
164) -> HashMap<String, ScopedLspServerConfig> {
165    let mut all_servers = HashMap::new();
166
167    for plugin in plugins {
168        if !plugin.enabled.unwrap_or(true) {
169            continue;
170        }
171
172        if let Ok(Some(servers)) = load_plugin_lsp_servers(plugin, errors).await {
173            let scoped_servers = add_plugin_scope_to_lsp_servers(servers, &plugin.name);
174            log::debug!(
175                "Loaded {} LSP servers from plugin {}",
176                scoped_servers.len(),
177                plugin.name
178            );
179            all_servers.extend(scoped_servers);
180        }
181    }
182
183    all_servers
184}