1use std::path::PathBuf;
7#[cfg(not(target_arch = "wasm32"))]
8use std::process::Command;
9
10#[derive(Debug, Clone)]
15pub struct ServerConfig {
16 pub inlay_hints_enabled: bool,
18 pub inlay_hints_parameter_hints: bool,
20 pub inlay_hints_type_hints: bool,
22 pub inlay_hints_chained_hints: bool,
24 pub inlay_hints_max_length: usize,
26
27 pub test_runner_enabled: bool,
29 pub test_runner_command: String,
31 pub test_runner_args: Vec<String>,
33 pub test_runner_timeout: u64,
35
36 pub telemetry_enabled: bool,
38}
39
40impl Default for ServerConfig {
41 fn default() -> Self {
42 Self {
43 inlay_hints_enabled: true,
44 inlay_hints_parameter_hints: true,
45 inlay_hints_type_hints: true,
46 inlay_hints_chained_hints: false,
47 inlay_hints_max_length: 30,
48 test_runner_enabled: true,
49 test_runner_command: "perl".to_string(),
50 test_runner_args: vec![],
51 test_runner_timeout: 60000,
52 telemetry_enabled: false,
53 }
54 }
55}
56
57impl ServerConfig {
58 pub fn update_from_value(&mut self, settings: &serde_json::Value) {
60 if let Some(inlay) = settings.get("inlayHints") {
61 if let Some(enabled) = inlay.get("enabled").and_then(|v| v.as_bool()) {
62 self.inlay_hints_enabled = enabled;
63 }
64 if let Some(param) = inlay.get("parameterHints").and_then(|v| v.as_bool()) {
65 self.inlay_hints_parameter_hints = param;
66 }
67 if let Some(type_hints) = inlay.get("typeHints").and_then(|v| v.as_bool()) {
68 self.inlay_hints_type_hints = type_hints;
69 }
70 if let Some(chained) = inlay.get("chainedHints").and_then(|v| v.as_bool()) {
71 self.inlay_hints_chained_hints = chained;
72 }
73 if let Some(max_len) = inlay.get("maxLength").and_then(|v| v.as_u64()) {
74 self.inlay_hints_max_length = max_len as usize;
75 }
76 }
77
78 if let Some(test) = settings.get("testRunner") {
79 if let Some(enabled) = test.get("enabled").and_then(|v| v.as_bool()) {
80 self.test_runner_enabled = enabled;
81 }
82 if let Some(cmd) = test.get("command").and_then(|v| v.as_str()) {
83 self.test_runner_command = cmd.to_string();
84 }
85 if let Some(args) = test.get("args").and_then(|v| v.as_array()) {
86 self.test_runner_args =
87 args.iter().filter_map(|v| v.as_str().map(|s| s.to_string())).collect();
88 }
89 if let Some(timeout) = test.get("timeout").and_then(|v| v.as_u64()) {
90 self.test_runner_timeout = timeout;
91 }
92 }
93
94 if let Some(telemetry) = settings.get("telemetry")
95 && let Some(enabled) = telemetry.get("enabled").and_then(|v| v.as_bool())
96 {
97 self.telemetry_enabled = enabled;
98 }
99 }
100}
101
102#[derive(Debug, Clone)]
107pub struct WorkspaceConfig {
108 pub include_paths: Vec<String>,
111
112 pub use_system_inc: bool,
115
116 system_inc_cache: Option<Vec<PathBuf>>,
118
119 pub resolution_timeout_ms: u64,
122}
123
124impl Default for WorkspaceConfig {
125 fn default() -> Self {
126 Self {
127 include_paths: vec!["lib".to_string(), ".".to_string(), "local/lib/perl5".to_string()],
128 use_system_inc: false,
129 system_inc_cache: None,
130 resolution_timeout_ms: 50,
131 }
132 }
133}
134
135impl WorkspaceConfig {
136 pub fn update_from_value(&mut self, settings: &serde_json::Value) {
138 if let Some(workspace) = settings.get("workspace") {
139 if let Some(paths) = workspace.get("includePaths").and_then(|v| v.as_array()) {
140 self.include_paths =
141 paths.iter().filter_map(|v| v.as_str().map(|s| s.to_string())).collect();
142 }
143 if let Some(use_inc) = workspace.get("useSystemInc").and_then(|v| v.as_bool()) {
144 if use_inc != self.use_system_inc {
145 self.system_inc_cache = None;
146 }
147 self.use_system_inc = use_inc;
148 }
149 if let Some(timeout) = workspace.get("resolutionTimeout").and_then(|v| v.as_u64()) {
150 self.resolution_timeout_ms = timeout;
151 }
152 }
153 }
154
155 pub fn get_system_inc(&mut self) -> &[PathBuf] {
157 if !self.use_system_inc {
158 return &[];
159 }
160
161 if self.system_inc_cache.is_none() {
162 self.system_inc_cache = Some(Self::fetch_perl_inc());
163 }
164
165 self.system_inc_cache.as_deref().unwrap_or(&[])
166 }
167
168 #[cfg(not(target_arch = "wasm32"))]
169 fn fetch_perl_inc() -> Vec<PathBuf> {
170 let output = Command::new("perl").args(["-e", "print join(\"\\n\", @INC)"]).output();
171
172 match output {
173 Ok(out) if out.status.success() => String::from_utf8_lossy(&out.stdout)
174 .lines()
175 .filter(|line| !line.is_empty() && *line != ".")
176 .map(PathBuf::from)
177 .collect(),
178 _ => Vec::new(),
179 }
180 }
181
182 #[cfg(target_arch = "wasm32")]
183 fn fetch_perl_inc() -> Vec<PathBuf> {
184 Vec::new()
185 }
186}
187
188#[cfg(test)]
189mod tests {
190 use super::{ServerConfig, WorkspaceConfig};
191 use serde_json::json;
192
193 #[test]
194 fn server_config_updates_selected_fields() {
195 let mut config = ServerConfig::default();
196 config.update_from_value(&json!({
197 "inlayHints": { "enabled": false, "maxLength": 42 },
198 "testRunner": { "enabled": false, "command": "prove", "args": ["-l"] },
199 "telemetry": { "enabled": true }
200 }));
201
202 assert!(!config.inlay_hints_enabled);
203 assert_eq!(config.inlay_hints_max_length, 42);
204 assert!(!config.test_runner_enabled);
205 assert_eq!(config.test_runner_command, "prove");
206 assert_eq!(config.test_runner_args, vec!["-l"]);
207 assert!(config.telemetry_enabled);
208 }
209
210 #[test]
211 fn workspace_config_defaults_include_common_paths() {
212 let config = WorkspaceConfig::default();
213 assert_eq!(config.include_paths, vec!["lib", ".", "local/lib/perl5"]);
214 assert!(!config.use_system_inc);
215 assert_eq!(config.resolution_timeout_ms, 50);
216 }
217}