ricecoder_lsp/
types.rs

1//! Core LSP types and data structures
2//!
3//! This module defines all the fundamental types used throughout the LSP integration,
4//! including error types, message types, and domain models.
5
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9/// Result type for LSP operations
10pub type LspResult<T> = Result<T, LspError>;
11
12/// LSP-specific error type
13#[derive(Debug, Clone, Serialize, Deserialize, thiserror::Error)]
14pub enum LspError {
15    /// Parse error
16    #[error("Parse error: {0}")]
17    ParseError(String),
18
19    /// Invalid request
20    #[error("Invalid request: {0}")]
21    InvalidRequest(String),
22
23    /// Method not found
24    #[error("Method not found: {0}")]
25    MethodNotFound(String),
26
27    /// Invalid parameters
28    #[error("Invalid parameters: {0}")]
29    InvalidParams(String),
30
31    /// Internal error
32    #[error("Internal error: {0}")]
33    InternalError(String),
34
35    /// Server error
36    #[error("Server error: {0}")]
37    ServerError(String),
38
39    /// IO error
40    #[error("IO error: {0}")]
41    IoError(String),
42
43    /// Serialization error
44    #[error("Serialization error: {0}")]
45    SerializationError(String),
46
47    /// Timeout error
48    #[error("Timeout error: {0}")]
49    TimeoutError(String),
50}
51
52/// Server state tracking
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54pub enum ServerState {
55    /// Server is initializing
56    Initializing,
57    /// Server is initialized and ready
58    Initialized,
59    /// Server is shutting down
60    ShuttingDown,
61    /// Server is shut down
62    ShutDown,
63}
64
65/// Position in a document (line and character)
66#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
67pub struct Position {
68    /// Line number (0-based)
69    pub line: u32,
70    /// Character offset (0-based)
71    pub character: u32,
72}
73
74impl Position {
75    /// Create a new position
76    pub fn new(line: u32, character: u32) -> Self {
77        Self { line, character }
78    }
79}
80
81/// Range in a document (start and end positions)
82#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
83pub struct Range {
84    /// Start position
85    pub start: Position,
86    /// End position
87    pub end: Position,
88}
89
90impl Range {
91    /// Create a new range
92    pub fn new(start: Position, end: Position) -> Self {
93        Self { start, end }
94    }
95}
96
97/// Symbol kind enumeration
98#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
99#[serde(rename_all = "PascalCase")]
100pub enum SymbolKind {
101    /// Function symbol
102    Function,
103    /// Type symbol
104    Type,
105    /// Variable symbol
106    Variable,
107    /// Constant symbol
108    Constant,
109    /// Module symbol
110    Module,
111    /// Class symbol
112    Class,
113    /// Interface symbol
114    Interface,
115    /// Enum symbol
116    Enum,
117    /// Trait symbol
118    Trait,
119    /// Struct symbol
120    Struct,
121    /// Method symbol
122    Method,
123    /// Property symbol
124    Property,
125    /// Field symbol
126    Field,
127    /// Parameter symbol
128    Parameter,
129}
130
131/// Symbol definition information
132#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
133pub struct Definition {
134    /// File URI where symbol is defined
135    pub uri: String,
136    /// Range of the definition
137    pub range: Range,
138}
139
140/// Symbol reference information
141#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
142pub struct Reference {
143    /// File URI where symbol is referenced
144    pub uri: String,
145    /// Range of the reference
146    pub range: Range,
147}
148
149/// Symbol information
150#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
151pub struct Symbol {
152    /// Symbol name
153    pub name: String,
154    /// Symbol kind
155    pub kind: SymbolKind,
156    /// Range of the symbol
157    pub range: Range,
158    /// Definition location
159    pub definition: Option<Definition>,
160    /// References to this symbol
161    pub references: Vec<Reference>,
162    /// Documentation for the symbol
163    pub documentation: Option<String>,
164}
165
166impl Symbol {
167    /// Create a new symbol
168    pub fn new(name: String, kind: SymbolKind, range: Range) -> Self {
169        Self {
170            name,
171            kind,
172            range,
173            definition: None,
174            references: Vec::new(),
175            documentation: None,
176        }
177    }
178}
179
180/// Diagnostic severity level
181#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
182#[serde(rename_all = "lowercase")]
183pub enum DiagnosticSeverity {
184    /// Error severity
185    Error,
186    /// Warning severity
187    Warning,
188    /// Information severity
189    Information,
190    /// Hint severity
191    Hint,
192}
193
194/// Diagnostic information
195#[derive(Debug, Clone, Serialize, Deserialize)]
196pub struct Diagnostic {
197    /// Range of the diagnostic
198    pub range: Range,
199    /// Severity level
200    pub severity: DiagnosticSeverity,
201    /// Diagnostic message
202    pub message: String,
203    /// Diagnostic code
204    pub code: Option<String>,
205    /// Source of the diagnostic
206    pub source: String,
207    /// Related information
208    pub related_information: Option<Vec<DiagnosticRelatedInformation>>,
209}
210
211impl Diagnostic {
212    /// Create a new diagnostic
213    pub fn new(range: Range, severity: DiagnosticSeverity, message: String) -> Self {
214        Self {
215            range,
216            severity,
217            message,
218            code: None,
219            source: "ricecoder-lsp".to_string(),
220            related_information: None,
221        }
222    }
223}
224
225/// Related diagnostic information
226#[derive(Debug, Clone, Serialize, Deserialize)]
227pub struct DiagnosticRelatedInformation {
228    /// Location of the related information
229    pub location: Location,
230    /// Message for the related information
231    pub message: String,
232}
233
234/// Location in a document
235#[derive(Debug, Clone, Serialize, Deserialize)]
236pub struct Location {
237    /// File URI
238    pub uri: String,
239    /// Range in the file
240    pub range: Range,
241}
242
243/// Code action kind
244#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
245#[serde(rename_all = "camelCase")]
246pub enum CodeActionKind {
247    /// Quick fix
248    QuickFix,
249    /// Refactor
250    Refactor,
251    /// Refactor extract
252    RefactorExtract,
253    /// Refactor inline
254    RefactorInline,
255    /// Refactor rewrite
256    RefactorRewrite,
257    /// Source
258    Source,
259    /// Source organize imports
260    SourceOrganizeImports,
261    /// Source fix all
262    SourceFixAll,
263}
264
265/// Text edit for code modifications
266#[derive(Debug, Clone, Serialize, Deserialize)]
267pub struct TextEdit {
268    /// Range to replace
269    pub range: Range,
270    /// New text
271    pub new_text: String,
272}
273
274/// Workspace edit for multi-file modifications
275#[derive(Debug, Clone, Serialize, Deserialize)]
276pub struct WorkspaceEdit {
277    /// Changes by file URI
278    pub changes: HashMap<String, Vec<TextEdit>>,
279}
280
281impl WorkspaceEdit {
282    /// Create a new workspace edit
283    pub fn new() -> Self {
284        Self {
285            changes: HashMap::new(),
286        }
287    }
288
289    /// Add a text edit for a file
290    pub fn add_edit(&mut self, uri: String, edit: TextEdit) {
291        self.changes.entry(uri).or_default().push(edit);
292    }
293}
294
295impl Default for WorkspaceEdit {
296    fn default() -> Self {
297        Self::new()
298    }
299}
300
301/// Code action
302#[derive(Debug, Clone, Serialize, Deserialize)]
303pub struct CodeAction {
304    /// Action title
305    pub title: String,
306    /// Action kind
307    pub kind: CodeActionKind,
308    /// Workspace edit
309    pub edit: WorkspaceEdit,
310    /// Associated diagnostics
311    pub diagnostics: Option<Vec<Diagnostic>>,
312}
313
314impl CodeAction {
315    /// Create a new code action
316    pub fn new(title: String, kind: CodeActionKind, edit: WorkspaceEdit) -> Self {
317        Self {
318            title,
319            kind,
320            edit,
321            diagnostics: None,
322        }
323    }
324}
325
326/// Markup content kind
327#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
328#[serde(rename_all = "lowercase")]
329pub enum MarkupKind {
330    /// Plain text
331    PlainText,
332    /// Markdown
333    Markdown,
334}
335
336/// Markup content
337#[derive(Debug, Clone, Serialize, Deserialize)]
338pub struct MarkupContent {
339    /// Content kind
340    pub kind: MarkupKind,
341    /// Content value
342    pub value: String,
343}
344
345impl MarkupContent {
346    /// Create plain text content
347    pub fn plain_text(value: String) -> Self {
348        Self {
349            kind: MarkupKind::PlainText,
350            value,
351        }
352    }
353
354    /// Create markdown content
355    pub fn markdown(value: String) -> Self {
356        Self {
357            kind: MarkupKind::Markdown,
358            value,
359        }
360    }
361}
362
363/// Hover information
364#[derive(Debug, Clone, Serialize, Deserialize)]
365pub struct HoverInfo {
366    /// Hover content
367    pub contents: MarkupContent,
368    /// Range of the hover
369    pub range: Option<Range>,
370}
371
372impl HoverInfo {
373    /// Create new hover information
374    pub fn new(contents: MarkupContent) -> Self {
375        Self {
376            contents,
377            range: None,
378        }
379    }
380
381    /// Set the range
382    pub fn with_range(mut self, range: Range) -> Self {
383        self.range = Some(range);
384        self
385    }
386}
387
388/// Semantic information about code
389#[derive(Debug, Clone, Serialize, Deserialize)]
390pub struct SemanticInfo {
391    /// Symbols in the code
392    pub symbols: Vec<Symbol>,
393    /// Imports in the code
394    pub imports: Vec<String>,
395    /// Definitions in the code
396    pub definitions: Vec<Definition>,
397    /// References in the code
398    pub references: Vec<Reference>,
399}
400
401impl SemanticInfo {
402    /// Create new semantic information
403    pub fn new() -> Self {
404        Self {
405            symbols: Vec::new(),
406            imports: Vec::new(),
407            definitions: Vec::new(),
408            references: Vec::new(),
409        }
410    }
411}
412
413impl Default for SemanticInfo {
414    fn default() -> Self {
415        Self::new()
416    }
417}
418
419/// Language enumeration
420#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
421#[serde(rename_all = "lowercase")]
422pub enum Language {
423    /// Rust
424    Rust,
425    /// TypeScript
426    TypeScript,
427    /// Python
428    Python,
429    /// Unknown language
430    Unknown,
431}
432
433impl Language {
434    /// Detect language from file extension
435    pub fn from_extension(ext: &str) -> Self {
436        match ext.to_lowercase().as_str() {
437            "rs" => Language::Rust,
438            "ts" | "tsx" | "js" | "jsx" => Language::TypeScript,
439            "py" => Language::Python,
440            _ => Language::Unknown,
441        }
442    }
443
444    /// Get file extensions for this language
445    pub fn extensions(&self) -> &'static [&'static str] {
446        match self {
447            Language::Rust => &["rs"],
448            Language::TypeScript => &["ts", "tsx", "js", "jsx"],
449            Language::Python => &["py"],
450            Language::Unknown => &[],
451        }
452    }
453
454    /// Convert language to string identifier
455    pub fn as_str(&self) -> &'static str {
456        match self {
457            Language::Rust => "rust",
458            Language::TypeScript => "typescript",
459            Language::Python => "python",
460            Language::Unknown => "unknown",
461        }
462    }
463}
464
465#[cfg(test)]
466mod tests {
467    use super::*;
468
469    #[test]
470    fn test_position_creation() {
471        let pos = Position::new(10, 5);
472        assert_eq!(pos.line, 10);
473        assert_eq!(pos.character, 5);
474    }
475
476    #[test]
477    fn test_range_creation() {
478        let start = Position::new(0, 0);
479        let end = Position::new(1, 0);
480        let range = Range::new(start, end);
481        assert_eq!(range.start, start);
482        assert_eq!(range.end, end);
483    }
484
485    #[test]
486    fn test_symbol_creation() {
487        let range = Range::new(Position::new(0, 0), Position::new(0, 5));
488        let symbol = Symbol::new("test_fn".to_string(), SymbolKind::Function, range);
489        assert_eq!(symbol.name, "test_fn");
490        assert_eq!(symbol.kind, SymbolKind::Function);
491    }
492
493    #[test]
494    fn test_diagnostic_creation() {
495        let range = Range::new(Position::new(0, 0), Position::new(0, 5));
496        let diag = Diagnostic::new(range, DiagnosticSeverity::Error, "Test error".to_string());
497        assert_eq!(diag.severity, DiagnosticSeverity::Error);
498        assert_eq!(diag.message, "Test error");
499    }
500
501    #[test]
502    fn test_language_detection() {
503        assert_eq!(Language::from_extension("rs"), Language::Rust);
504        assert_eq!(Language::from_extension("ts"), Language::TypeScript);
505        assert_eq!(Language::from_extension("py"), Language::Python);
506        assert_eq!(Language::from_extension("unknown"), Language::Unknown);
507    }
508
509    #[test]
510    fn test_markup_content_plain_text() {
511        let content = MarkupContent::plain_text("Hello".to_string());
512        assert_eq!(content.kind, MarkupKind::PlainText);
513        assert_eq!(content.value, "Hello");
514    }
515
516    #[test]
517    fn test_markup_content_markdown() {
518        let content = MarkupContent::markdown("# Hello".to_string());
519        assert_eq!(content.kind, MarkupKind::Markdown);
520        assert_eq!(content.value, "# Hello");
521    }
522
523    #[test]
524    fn test_workspace_edit() {
525        let mut edit = WorkspaceEdit::new();
526        let text_edit = TextEdit {
527            range: Range::new(Position::new(0, 0), Position::new(0, 5)),
528            new_text: "new".to_string(),
529        };
530        edit.add_edit("file://test.rs".to_string(), text_edit);
531        assert_eq!(edit.changes.len(), 1);
532    }
533
534    #[test]
535    fn test_semantic_info() {
536        let info = SemanticInfo::new();
537        assert!(info.symbols.is_empty());
538        assert!(info.imports.is_empty());
539    }
540}