ricecoder_external_lsp/client/
capabilities.rs1use serde::{Deserialize, Serialize};
4use serde_json::{json, Value};
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct ClientCapabilities {
9 #[serde(skip_serializing_if = "Option::is_none")]
11 pub text_document: Option<TextDocumentClientCapabilities>,
12 #[serde(skip_serializing_if = "Option::is_none")]
14 pub workspace: Option<WorkspaceClientCapabilities>,
15 #[serde(skip_serializing_if = "Option::is_none")]
17 pub general: Option<GeneralClientCapabilities>,
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct TextDocumentClientCapabilities {
23 #[serde(skip_serializing_if = "Option::is_none")]
25 pub synchronization: Option<SynchronizationCapability>,
26 #[serde(skip_serializing_if = "Option::is_none")]
28 pub completion: Option<CompletionCapability>,
29 #[serde(skip_serializing_if = "Option::is_none")]
31 pub hover: Option<HoverCapability>,
32 #[serde(skip_serializing_if = "Option::is_none")]
34 pub publish_diagnostics: Option<PublishDiagnosticsCapability>,
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct SynchronizationCapability {
40 #[serde(skip_serializing_if = "Option::is_none")]
42 pub did_save: Option<bool>,
43 #[serde(skip_serializing_if = "Option::is_none")]
45 pub will_save: Option<bool>,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct CompletionCapability {
51 #[serde(skip_serializing_if = "Option::is_none")]
53 pub completion_item: Option<CompletionItemCapability>,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct CompletionItemCapability {
59 #[serde(skip_serializing_if = "Option::is_none")]
61 pub snippet_support: Option<bool>,
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct HoverCapability {
67 #[serde(skip_serializing_if = "Option::is_none")]
69 pub content_format: Option<Vec<String>>,
70}
71
72#[derive(Debug, Clone, Serialize, Deserialize)]
74pub struct PublishDiagnosticsCapability {
75 #[serde(skip_serializing_if = "Option::is_none")]
77 pub related_information: Option<bool>,
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct WorkspaceClientCapabilities {
83 #[serde(skip_serializing_if = "Option::is_none")]
85 pub workspace_folders: Option<bool>,
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize)]
90pub struct GeneralClientCapabilities {
91 #[serde(skip_serializing_if = "Option::is_none")]
93 pub regular_expressions: Option<RegularExpressionCapability>,
94}
95
96#[derive(Debug, Clone, Serialize, Deserialize)]
98pub struct RegularExpressionCapability {
99 pub engine: String,
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize)]
105pub struct ServerCapabilities {
106 #[serde(skip_serializing_if = "Option::is_none")]
108 pub completion_provider: Option<Value>,
109 #[serde(skip_serializing_if = "Option::is_none")]
111 pub hover_provider: Option<Value>,
112 #[serde(skip_serializing_if = "Option::is_none")]
114 pub definition_provider: Option<Value>,
115 #[serde(skip_serializing_if = "Option::is_none")]
117 pub references_provider: Option<Value>,
118 #[serde(skip_serializing_if = "Option::is_none")]
120 pub document_symbol_provider: Option<Value>,
121 #[serde(skip_serializing_if = "Option::is_none")]
123 pub workspace_symbol_provider: Option<Value>,
124 #[serde(skip_serializing_if = "Option::is_none")]
126 pub code_action_provider: Option<Value>,
127 #[serde(skip_serializing_if = "Option::is_none")]
129 pub text_document_sync: Option<Value>,
130 #[serde(skip_serializing_if = "Option::is_none")]
132 pub diagnostic_provider: Option<Value>,
133}
134
135pub struct CapabilityNegotiator;
137
138impl CapabilityNegotiator {
139 pub fn new() -> Self {
141 Self
142 }
143
144 pub fn default_client_capabilities() -> ClientCapabilities {
146 ClientCapabilities {
147 text_document: Some(TextDocumentClientCapabilities {
148 synchronization: Some(SynchronizationCapability {
149 did_save: Some(true),
150 will_save: Some(false),
151 }),
152 completion: Some(CompletionCapability {
153 completion_item: Some(CompletionItemCapability {
154 snippet_support: Some(true),
155 }),
156 }),
157 hover: Some(HoverCapability {
158 content_format: Some(vec!["markdown".to_string(), "plaintext".to_string()]),
159 }),
160 publish_diagnostics: Some(PublishDiagnosticsCapability {
161 related_information: Some(true),
162 }),
163 }),
164 workspace: Some(WorkspaceClientCapabilities {
165 workspace_folders: Some(true),
166 }),
167 general: Some(GeneralClientCapabilities {
168 regular_expressions: Some(RegularExpressionCapability {
169 engine: "ECMAScript".to_string(),
170 }),
171 }),
172 }
173 }
174
175 pub fn create_initialize_params(
177 process_id: Option<u32>,
178 root_path: Option<String>,
179 root_uri: Option<String>,
180 ) -> Value {
181 let mut params = json!({
182 "processId": process_id,
183 "capabilities": Self::default_client_capabilities(),
184 });
185
186 if let Some(root_path) = root_path {
187 params["rootPath"] = json!(root_path);
188 }
189
190 if let Some(root_uri) = root_uri {
191 params["rootUri"] = json!(root_uri);
192 }
193
194 params
195 }
196
197 pub fn supports_capability(
199 capabilities: &ServerCapabilities,
200 capability: &str,
201 ) -> bool {
202 match capability {
203 "completion" => capabilities.completion_provider.is_some(),
204 "hover" => capabilities.hover_provider.is_some(),
205 "definition" => capabilities.definition_provider.is_some(),
206 "references" => capabilities.references_provider.is_some(),
207 "documentSymbol" => capabilities.document_symbol_provider.is_some(),
208 "workspaceSymbol" => capabilities.workspace_symbol_provider.is_some(),
209 "codeAction" => capabilities.code_action_provider.is_some(),
210 "diagnostics" => capabilities.diagnostic_provider.is_some(),
211 _ => false,
212 }
213 }
214
215 pub fn get_supported_capabilities(capabilities: &ServerCapabilities) -> Vec<String> {
217 let mut supported = Vec::new();
218
219 if capabilities.completion_provider.is_some() {
220 supported.push("completion".to_string());
221 }
222 if capabilities.hover_provider.is_some() {
223 supported.push("hover".to_string());
224 }
225 if capabilities.definition_provider.is_some() {
226 supported.push("definition".to_string());
227 }
228 if capabilities.references_provider.is_some() {
229 supported.push("references".to_string());
230 }
231 if capabilities.document_symbol_provider.is_some() {
232 supported.push("documentSymbol".to_string());
233 }
234 if capabilities.workspace_symbol_provider.is_some() {
235 supported.push("workspaceSymbol".to_string());
236 }
237 if capabilities.code_action_provider.is_some() {
238 supported.push("codeAction".to_string());
239 }
240 if capabilities.diagnostic_provider.is_some() {
241 supported.push("diagnostics".to_string());
242 }
243
244 supported
245 }
246}
247
248impl Default for CapabilityNegotiator {
249 fn default() -> Self {
250 Self::new()
251 }
252}
253
254#[cfg(test)]
255mod tests {
256 use super::*;
257
258 #[test]
259 fn test_default_client_capabilities() {
260 let caps = CapabilityNegotiator::default_client_capabilities();
261
262 assert!(caps.text_document.is_some());
263 assert!(caps.workspace.is_some());
264 assert!(caps.general.is_some());
265
266 let text_doc = caps.text_document.unwrap();
267 assert!(text_doc.synchronization.is_some());
268 assert!(text_doc.completion.is_some());
269 assert!(text_doc.hover.is_some());
270 }
271
272 #[test]
273 fn test_create_initialize_params() {
274 let params = CapabilityNegotiator::create_initialize_params(
275 Some(1234),
276 Some("/path/to/project".to_string()),
277 Some("file:///path/to/project".to_string()),
278 );
279
280 assert_eq!(params["processId"], 1234);
281 assert_eq!(params["rootPath"], "/path/to/project");
282 assert_eq!(params["rootUri"], "file:///path/to/project");
283 assert!(params["capabilities"].is_object());
284 }
285
286 #[test]
287 fn test_supports_capability() {
288 let caps = ServerCapabilities {
289 completion_provider: Some(json!({})),
290 hover_provider: Some(json!({})),
291 definition_provider: None,
292 references_provider: None,
293 document_symbol_provider: None,
294 workspace_symbol_provider: None,
295 code_action_provider: None,
296 diagnostic_provider: None,
297 text_document_sync: None,
298 };
299
300 assert!(CapabilityNegotiator::supports_capability(&caps, "completion"));
301 assert!(CapabilityNegotiator::supports_capability(&caps, "hover"));
302 assert!(!CapabilityNegotiator::supports_capability(&caps, "definition"));
303 }
304
305 #[test]
306 fn test_get_supported_capabilities() {
307 let caps = ServerCapabilities {
308 completion_provider: Some(json!({})),
309 hover_provider: Some(json!({})),
310 definition_provider: Some(json!({})),
311 references_provider: None,
312 document_symbol_provider: None,
313 workspace_symbol_provider: None,
314 code_action_provider: None,
315 diagnostic_provider: None,
316 text_document_sync: None,
317 };
318
319 let supported = CapabilityNegotiator::get_supported_capabilities(&caps);
320
321 assert!(supported.contains(&"completion".to_string()));
322 assert!(supported.contains(&"hover".to_string()));
323 assert!(supported.contains(&"definition".to_string()));
324 assert_eq!(supported.len(), 3);
325 }
326}