1use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7use std::path::Path;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct LSPServerConfig {
12 pub name: String,
14 pub command: String,
16 #[serde(default)]
18 pub args: Vec<String>,
19 pub file_extensions: Vec<String>,
21
22 #[serde(default)]
24 pub extension_to_language: HashMap<String, String>,
25 #[serde(default)]
27 pub initialization_options: Option<serde_json::Value>,
28 #[serde(default)]
30 pub settings: Option<serde_json::Value>,
31 #[serde(default)]
33 pub env: HashMap<String, String>,
34 pub workspace_folder: Option<String>,
36 #[serde(default = "default_startup_timeout")]
38 pub startup_timeout: u64,
39 #[serde(default = "default_restart_on_crash")]
41 pub restart_on_crash: bool,
42 #[serde(default = "default_max_restarts")]
44 pub max_restarts: u32,
45 pub source: Option<String>,
47}
48
49fn default_startup_timeout() -> u64 {
50 30000
51}
52fn default_restart_on_crash() -> bool {
53 true
54}
55fn default_max_restarts() -> u32 {
56 3
57}
58
59impl Default for LSPServerConfig {
60 fn default() -> Self {
61 Self {
62 name: String::new(),
63 command: String::new(),
64 args: Vec::new(),
65 file_extensions: Vec::new(),
66 extension_to_language: HashMap::new(),
67 initialization_options: None,
68 settings: None,
69 env: HashMap::new(),
70 workspace_folder: None,
71 startup_timeout: default_startup_timeout(),
72 restart_on_crash: default_restart_on_crash(),
73 max_restarts: default_max_restarts(),
74 source: None,
75 }
76 }
77}
78
79pub type LSPConfigFile = HashMap<String, LSPServerConfig>;
81
82pub fn load_lsp_config_file(workspace_root: &Path) -> Vec<LSPServerConfig> {
84 let search_paths = [
85 workspace_root.join(".lsp.json"),
86 workspace_root.join(".claude/lsp.json"),
87 dirs::home_dir()
88 .map(|h| h.join(".claude/lsp.json"))
89 .unwrap_or_default(),
90 ];
91
92 let mut configs = Vec::new();
93
94 for config_path in &search_paths {
95 if !config_path.exists() {
96 continue;
97 }
98
99 match std::fs::read_to_string(config_path) {
100 Ok(content) => match serde_json::from_str::<LSPConfigFile>(&content) {
101 Ok(config_file) => {
102 for (name, mut config) in config_file {
103 config.name = name;
104 config.source = Some(config_path.display().to_string());
105 configs.push(config);
106 }
107 }
108 Err(e) => {
109 tracing::warn!("解析 LSP 配置文件失败 {}: {}", config_path.display(), e);
110 }
111 },
112 Err(e) => {
113 tracing::warn!("读取 LSP 配置文件失败 {}: {}", config_path.display(), e);
114 }
115 }
116 }
117
118 configs
119}
120
121pub fn default_lsp_configs() -> Vec<LSPServerConfig> {
123 vec![
124 LSPServerConfig {
125 name: "typescript-language-server".to_string(),
126 command: "typescript-language-server".to_string(),
127 args: vec!["--stdio".to_string()],
128 file_extensions: vec![
129 ".ts".to_string(),
130 ".tsx".to_string(),
131 ".js".to_string(),
132 ".jsx".to_string(),
133 ],
134 extension_to_language: [
135 (".ts".to_string(), "typescript".to_string()),
136 (".tsx".to_string(), "typescriptreact".to_string()),
137 (".js".to_string(), "javascript".to_string()),
138 (".jsx".to_string(), "javascriptreact".to_string()),
139 ]
140 .into_iter()
141 .collect(),
142 restart_on_crash: true,
143 max_restarts: 3,
144 ..Default::default()
145 },
146 LSPServerConfig {
147 name: "pyright".to_string(),
148 command: "pyright-langserver".to_string(),
149 args: vec!["--stdio".to_string()],
150 file_extensions: vec![".py".to_string()],
151 extension_to_language: [(".py".to_string(), "python".to_string())]
152 .into_iter()
153 .collect(),
154 restart_on_crash: true,
155 max_restarts: 3,
156 ..Default::default()
157 },
158 LSPServerConfig {
159 name: "rust-analyzer".to_string(),
160 command: "rust-analyzer".to_string(),
161 args: vec![],
162 file_extensions: vec![".rs".to_string()],
163 extension_to_language: [(".rs".to_string(), "rust".to_string())]
164 .into_iter()
165 .collect(),
166 restart_on_crash: true,
167 max_restarts: 3,
168 ..Default::default()
169 },
170 ]
171}