Rust · 12822 bytes Raw Blame History
1 //! LSP type definitions
2 //!
3 //! Core types used throughout the LSP client implementation.
4 //!
5 //! Note: Some types and methods are for planned features.
6 #![allow(dead_code)]
7
8 use std::collections::HashMap;
9 use std::path::PathBuf;
10
11 /// Position in a document (0-based line and character)
12 #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
13 pub struct Position {
14 pub line: u32,
15 pub character: u32,
16 }
17
18 impl Position {
19 pub fn new(line: u32, character: u32) -> Self {
20 Self { line, character }
21 }
22 }
23
24 /// Range in a document
25 #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
26 pub struct Range {
27 pub start: Position,
28 pub end: Position,
29 }
30
31 impl Range {
32 pub fn new(start: Position, end: Position) -> Self {
33 Self { start, end }
34 }
35
36 pub fn point(pos: Position) -> Self {
37 Self {
38 start: pos,
39 end: pos,
40 }
41 }
42 }
43
44 /// Location in a document
45 #[derive(Debug, Clone, PartialEq, Eq)]
46 pub struct Location {
47 pub uri: String,
48 pub range: Range,
49 }
50
51 impl Location {
52 /// Convert URI to file path
53 pub fn to_path(&self) -> Option<PathBuf> {
54 if self.uri.starts_with("file://") {
55 Some(PathBuf::from(&self.uri[7..]))
56 } else {
57 None
58 }
59 }
60 }
61
62 /// Text edit operation
63 #[derive(Debug, Clone, PartialEq, Eq)]
64 pub struct TextEdit {
65 pub range: Range,
66 pub new_text: String,
67 }
68
69 /// Workspace edit (multiple file edits)
70 #[derive(Debug, Clone, Default)]
71 pub struct WorkspaceEdit {
72 pub changes: HashMap<String, Vec<TextEdit>>,
73 }
74
75 /// Diagnostic severity levels
76 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
77 pub enum DiagnosticSeverity {
78 Error = 1,
79 Warning = 2,
80 Information = 3,
81 Hint = 4,
82 }
83
84 impl DiagnosticSeverity {
85 pub fn from_u32(value: u32) -> Option<Self> {
86 match value {
87 1 => Some(Self::Error),
88 2 => Some(Self::Warning),
89 3 => Some(Self::Information),
90 4 => Some(Self::Hint),
91 _ => None,
92 }
93 }
94 }
95
96 /// A diagnostic message from the language server
97 #[derive(Debug, Clone)]
98 pub struct Diagnostic {
99 pub range: Range,
100 pub severity: Option<DiagnosticSeverity>,
101 pub code: Option<String>,
102 pub source: Option<String>,
103 pub message: String,
104 }
105
106 /// Completion item kind
107 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
108 pub enum CompletionItemKind {
109 Text = 1,
110 Method = 2,
111 Function = 3,
112 Constructor = 4,
113 Field = 5,
114 Variable = 6,
115 Class = 7,
116 Interface = 8,
117 Module = 9,
118 Property = 10,
119 Unit = 11,
120 Value = 12,
121 Enum = 13,
122 Keyword = 14,
123 Snippet = 15,
124 Color = 16,
125 File = 17,
126 Reference = 18,
127 Folder = 19,
128 EnumMember = 20,
129 Constant = 21,
130 Struct = 22,
131 Event = 23,
132 Operator = 24,
133 TypeParameter = 25,
134 }
135
136 impl CompletionItemKind {
137 pub fn from_u32(value: u32) -> Option<Self> {
138 match value {
139 1 => Some(Self::Text),
140 2 => Some(Self::Method),
141 3 => Some(Self::Function),
142 4 => Some(Self::Constructor),
143 5 => Some(Self::Field),
144 6 => Some(Self::Variable),
145 7 => Some(Self::Class),
146 8 => Some(Self::Interface),
147 9 => Some(Self::Module),
148 10 => Some(Self::Property),
149 11 => Some(Self::Unit),
150 12 => Some(Self::Value),
151 13 => Some(Self::Enum),
152 14 => Some(Self::Keyword),
153 15 => Some(Self::Snippet),
154 16 => Some(Self::Color),
155 17 => Some(Self::File),
156 18 => Some(Self::Reference),
157 19 => Some(Self::Folder),
158 20 => Some(Self::EnumMember),
159 21 => Some(Self::Constant),
160 22 => Some(Self::Struct),
161 23 => Some(Self::Event),
162 24 => Some(Self::Operator),
163 25 => Some(Self::TypeParameter),
164 _ => None,
165 }
166 }
167
168 pub fn icon(&self) -> &'static str {
169 match self {
170 Self::Text => "t",
171 Self::Method => "m",
172 Self::Function => "f",
173 Self::Constructor => "C",
174 Self::Field => "F",
175 Self::Variable => "v",
176 Self::Class => "c",
177 Self::Interface => "i",
178 Self::Module => "M",
179 Self::Property => "p",
180 Self::Unit => "u",
181 Self::Value => "V",
182 Self::Enum => "E",
183 Self::Keyword => "k",
184 Self::Snippet => "s",
185 Self::Color => "#",
186 Self::File => "f",
187 Self::Reference => "r",
188 Self::Folder => "D",
189 Self::EnumMember => "e",
190 Self::Constant => "K",
191 Self::Struct => "S",
192 Self::Event => "!",
193 Self::Operator => "o",
194 Self::TypeParameter => "T",
195 }
196 }
197 }
198
199 /// A completion item
200 #[derive(Debug, Clone)]
201 pub struct CompletionItem {
202 pub label: String,
203 pub kind: Option<CompletionItemKind>,
204 pub detail: Option<String>,
205 pub documentation: Option<String>,
206 pub insert_text: Option<String>,
207 pub text_edit: Option<TextEdit>,
208 pub sort_text: Option<String>,
209 pub filter_text: Option<String>,
210 }
211
212 /// Symbol kind (for document/workspace symbols)
213 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
214 pub enum SymbolKind {
215 File = 1,
216 Module = 2,
217 Namespace = 3,
218 Package = 4,
219 Class = 5,
220 Method = 6,
221 Property = 7,
222 Field = 8,
223 Constructor = 9,
224 Enum = 10,
225 Interface = 11,
226 Function = 12,
227 Variable = 13,
228 Constant = 14,
229 String = 15,
230 Number = 16,
231 Boolean = 17,
232 Array = 18,
233 Object = 19,
234 Key = 20,
235 Null = 21,
236 EnumMember = 22,
237 Struct = 23,
238 Event = 24,
239 Operator = 25,
240 TypeParameter = 26,
241 }
242
243 impl SymbolKind {
244 pub fn from_u32(value: u32) -> Option<Self> {
245 match value {
246 1 => Some(Self::File),
247 2 => Some(Self::Module),
248 3 => Some(Self::Namespace),
249 4 => Some(Self::Package),
250 5 => Some(Self::Class),
251 6 => Some(Self::Method),
252 7 => Some(Self::Property),
253 8 => Some(Self::Field),
254 9 => Some(Self::Constructor),
255 10 => Some(Self::Enum),
256 11 => Some(Self::Interface),
257 12 => Some(Self::Function),
258 13 => Some(Self::Variable),
259 14 => Some(Self::Constant),
260 15 => Some(Self::String),
261 16 => Some(Self::Number),
262 17 => Some(Self::Boolean),
263 18 => Some(Self::Array),
264 19 => Some(Self::Object),
265 20 => Some(Self::Key),
266 21 => Some(Self::Null),
267 22 => Some(Self::EnumMember),
268 23 => Some(Self::Struct),
269 24 => Some(Self::Event),
270 25 => Some(Self::Operator),
271 26 => Some(Self::TypeParameter),
272 _ => None,
273 }
274 }
275
276 pub fn icon(&self) -> &'static str {
277 match self {
278 Self::File => "󰈔",
279 Self::Module => "󰆧",
280 Self::Namespace => "󰅩",
281 Self::Package => "󰏗",
282 Self::Class => "󰠱",
283 Self::Method => "󰆧",
284 Self::Property => "󰜢",
285 Self::Field => "󰜢",
286 Self::Constructor => "󰆧",
287 Self::Enum => "󰒻",
288 Self::Interface => "󰜰",
289 Self::Function => "󰊕",
290 Self::Variable => "󰀫",
291 Self::Constant => "󰏿",
292 Self::String => "󰀬",
293 Self::Number => "󰎠",
294 Self::Boolean => "󰨙",
295 Self::Array => "󰅪",
296 Self::Object => "󰅩",
297 Self::Key => "󰌋",
298 Self::Null => "󰟢",
299 Self::EnumMember => "󰒻",
300 Self::Struct => "󰙅",
301 Self::Event => "󰉁",
302 Self::Operator => "󰆕",
303 Self::TypeParameter => "󰊄",
304 }
305 }
306 }
307
308 /// A document symbol
309 #[derive(Debug, Clone)]
310 pub struct DocumentSymbol {
311 pub name: String,
312 pub kind: SymbolKind,
313 pub range: Range,
314 pub selection_range: Range,
315 pub children: Vec<DocumentSymbol>,
316 }
317
318 /// Hover information
319 #[derive(Debug, Clone)]
320 pub struct HoverInfo {
321 pub contents: String,
322 pub range: Option<Range>,
323 }
324
325 /// Server capabilities
326 #[derive(Debug, Clone, Default)]
327 pub struct Capabilities {
328 pub completion: bool,
329 pub hover: bool,
330 pub definition: bool,
331 pub references: bool,
332 pub rename: bool,
333 pub code_actions: bool,
334 pub formatting: bool,
335 pub diagnostics: bool,
336 pub document_symbols: bool,
337 pub workspace_symbols: bool,
338 pub signature_help: bool,
339 }
340
341 impl Capabilities {
342 pub fn all() -> Self {
343 Self {
344 completion: true,
345 hover: true,
346 definition: true,
347 references: true,
348 rename: true,
349 code_actions: true,
350 formatting: true,
351 diagnostics: true,
352 document_symbols: true,
353 workspace_symbols: true,
354 signature_help: true,
355 }
356 }
357 }
358
359 /// Configuration for an LSP server
360 #[derive(Debug, Clone)]
361 pub struct ServerConfig {
362 pub name: String,
363 pub language: String,
364 pub command: Vec<String>,
365 pub file_patterns: Vec<String>,
366 pub capabilities: Capabilities,
367 }
368
369 impl ServerConfig {
370 pub fn new(name: &str, language: &str, command: Vec<&str>) -> Self {
371 Self {
372 name: name.to_string(),
373 language: language.to_string(),
374 command: command.into_iter().map(String::from).collect(),
375 file_patterns: Vec::new(),
376 capabilities: Capabilities::all(),
377 }
378 }
379
380 pub fn with_patterns(mut self, patterns: Vec<&str>) -> Self {
381 self.file_patterns = patterns.into_iter().map(String::from).collect();
382 self
383 }
384
385 pub fn with_capabilities(mut self, caps: Capabilities) -> Self {
386 self.capabilities = caps;
387 self
388 }
389 }
390
391 /// Language ID detection from file extension
392 pub fn detect_language(path: &str) -> Option<&'static str> {
393 let ext = path.rsplit('.').next()?;
394 match ext.to_lowercase().as_str() {
395 "rs" => Some("rust"),
396 "py" | "pyw" => Some("python"),
397 "js" | "mjs" | "cjs" => Some("javascript"),
398 "ts" | "mts" | "cts" => Some("typescript"),
399 "tsx" => Some("typescriptreact"),
400 "jsx" => Some("javascriptreact"),
401 "c" | "h" => Some("c"),
402 "cpp" | "cc" | "cxx" | "hpp" | "hxx" | "hh" => Some("cpp"),
403 "go" => Some("go"),
404 "java" => Some("java"),
405 "kt" | "kts" => Some("kotlin"),
406 "swift" => Some("swift"),
407 "rb" | "erb" => Some("ruby"),
408 "php" => Some("php"),
409 "cs" => Some("csharp"),
410 "fs" | "fsi" | "fsx" => Some("fsharp"),
411 "scala" | "sc" => Some("scala"),
412 "hs" | "lhs" => Some("haskell"),
413 "lua" => Some("lua"),
414 "pl" | "pm" => Some("perl"),
415 "r" | "R" => Some("r"),
416 "jl" => Some("julia"),
417 "ex" | "exs" => Some("elixir"),
418 "erl" | "hrl" => Some("erlang"),
419 "clj" | "cljs" | "cljc" => Some("clojure"),
420 "f90" | "f95" | "f03" | "f08" | "for" | "ftn" => Some("fortran"),
421 "zig" => Some("zig"),
422 "nim" => Some("nim"),
423 "odin" => Some("odin"),
424 "v" => Some("v"),
425 "d" => Some("d"),
426 "sh" | "bash" => Some("shellscript"),
427 "zsh" => Some("shellscript"),
428 "fish" => Some("fish"),
429 "ps1" | "psm1" => Some("powershell"),
430 "sql" => Some("sql"),
431 "html" | "htm" => Some("html"),
432 "css" => Some("css"),
433 "scss" => Some("scss"),
434 "less" => Some("less"),
435 "json" => Some("json"),
436 "jsonc" => Some("jsonc"),
437 "yaml" | "yml" => Some("yaml"),
438 "toml" => Some("toml"),
439 "xml" => Some("xml"),
440 "md" | "markdown" => Some("markdown"),
441 "dockerfile" => Some("dockerfile"),
442 "tf" | "tfvars" => Some("terraform"),
443 "nix" => Some("nix"),
444 "ml" | "mli" => Some("ocaml"),
445 "dart" => Some("dart"),
446 "groovy" | "gradle" => Some("groovy"),
447 "vue" => Some("vue"),
448 "svelte" => Some("svelte"),
449 "elm" => Some("elm"),
450 "asm" | "s" => Some("asm"),
451 "cmake" => Some("cmake"),
452 "proto" => Some("proto"),
453 "graphql" | "gql" => Some("graphql"),
454 _ => None,
455 }
456 }
457
458 /// Convert file path to LSP URI
459 pub fn path_to_uri(path: &str) -> String {
460 if path.starts_with('/') {
461 format!("file://{}", path)
462 } else {
463 format!("file:///{}", path)
464 }
465 }
466
467 /// Convert LSP URI to file path
468 pub fn uri_to_path(uri: &str) -> Option<String> {
469 if uri.starts_with("file://") {
470 Some(uri[7..].to_string())
471 } else {
472 None
473 }
474 }
475