Skip to main content

termesh_core/
lsp.rs

1//! Protocol-neutral language-server state and session messages (ADR-0011).
2//!
3//! CLI and wire details stay in `termesh-lsp`; these types live here because the
4//! application message bus and single-owner model must carry them without depending
5//! on a backend.
6
7use std::path::PathBuf;
8
9use crate::{LspRequestId, LspServerId};
10
11/// A position in a document. `character` counts **UTF-16 code units**, which is what the
12/// protocol speaks. The editor speaks char offsets; `termesh_editor::position` converts.
13#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
14pub struct TextPosition {
15    pub line: u32,
16    pub character: u32,
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub struct TextRange {
21    pub start: TextPosition,
22    pub end: TextPosition,
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
26pub enum DiagnosticSeverity {
27    Error,
28    Warning,
29    Info,
30    Hint,
31}
32
33/// Which producer reported this. Cargo and a language server surface the same rustc
34/// diagnostics, so the problems panel needs to tell them apart to deduplicate.
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum DiagnosticOrigin {
37    LanguageServer,
38    Task,
39}
40
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub struct Diagnostic {
43    pub path: PathBuf,
44    pub range: TextRange,
45    pub severity: DiagnosticSeverity,
46    pub origin: DiagnosticOrigin,
47    pub source: String,
48    pub code: Option<String>,
49    pub message: String,
50}
51
52#[derive(Debug, Clone, PartialEq, Eq)]
53pub struct Location {
54    pub path: PathBuf,
55    pub range: TextRange,
56}
57
58#[derive(Debug, Clone, PartialEq, Eq)]
59pub struct TextEdit {
60    pub path: PathBuf,
61    pub range: TextRange,
62    pub new_text: String,
63}
64
65/// A set of edits across one or more files. `version` is the wire version the server
66/// authored against, when it supplied one.
67#[derive(Debug, Clone, Default, PartialEq, Eq)]
68pub struct WorkspaceEdit {
69    pub edits: Vec<TextEdit>,
70    pub versions: Vec<(PathBuf, u64)>,
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74pub enum SymbolKind {
75    File,
76    Module,
77    Struct,
78    Enum,
79    Trait,
80    Function,
81    Method,
82    Field,
83    Constant,
84    Variable,
85    TypeAlias,
86    Macro,
87    Other,
88}
89
90#[derive(Debug, Clone, PartialEq, Eq)]
91pub struct DocumentSymbol {
92    pub name: String,
93    pub kind: SymbolKind,
94    pub detail: Option<String>,
95    pub range: TextRange,
96    pub children: Vec<DocumentSymbol>,
97}
98
99#[derive(Debug, Clone, PartialEq, Eq)]
100pub struct SymbolLocation {
101    pub name: String,
102    pub kind: SymbolKind,
103    pub container: Option<String>,
104    pub location: Location,
105}
106
107#[derive(Debug, Clone, PartialEq, Eq)]
108pub struct CompletionItem {
109    pub label: String,
110    pub detail: Option<String>,
111    pub kind: SymbolKind,
112    /// What to insert. Never derived from `label` at the call site.
113    pub insert_text: String,
114    pub edit: Option<TextEdit>,
115}
116
117#[derive(Debug, Clone, PartialEq, Eq)]
118pub struct HoverText {
119    pub text: String,
120    pub range: Option<TextRange>,
121    pub truncated: bool,
122}
123
124#[derive(Debug, Clone, PartialEq, Eq)]
125pub struct CodeAction {
126    pub title: String,
127    pub kind: Option<String>,
128    pub edit: Option<WorkspaceEdit>,
129}
130
131/// One replaced span, or a whole-document replacement when `range` is `None`.
132#[derive(Debug, Clone, PartialEq, Eq)]
133pub struct TextChange {
134    pub range: Option<TextRange>,
135    pub text: String,
136}
137
138#[derive(Debug, Clone, PartialEq, Eq)]
139pub enum WatchedFileChange {
140    Created(PathBuf),
141    Changed(PathBuf),
142    Deleted(PathBuf),
143}
144
145/// Every response-bearing variant carries its correlation id first, so the model's
146/// `active_*` guard can drop a superseded reply.
147#[derive(Debug, Clone, PartialEq, Eq)]
148pub enum LspRequest {
149    Start {
150        server: LspServerId,
151        root: PathBuf,
152        command: Vec<String>,
153        language: String,
154        /// Raw JSON for `initializationOptions`, parsed at the wire boundary.
155        ///
156        /// A string rather than a `serde_json::Value` because `core` has zero
157        /// dependencies. Rust needs none of this; Eclipse JDT LS and pyright do not
158        /// start usefully without it, and carrying the field now keeps a later
159        /// language a recipe change instead of a `protocol.rs` change.
160        initialization_options: Option<String>,
161    },
162    DidOpen {
163        path: PathBuf,
164        language_id: String,
165        version: u64,
166        text: String,
167    },
168    DidChange {
169        path: PathBuf,
170        version: u64,
171        change: TextChange,
172    },
173    DidSave {
174        path: PathBuf,
175    },
176    DidClose {
177        path: PathBuf,
178    },
179    WatchedFilesChanged {
180        changes: Vec<WatchedFileChange>,
181    },
182    /// Ask a server to refresh project metadata affected by configuration files.
183    /// The protocol translator chooses the vendor method; core stays neutral.
184    ReloadProject {
185        paths: Vec<PathBuf>,
186    },
187    Definition {
188        id: LspRequestId,
189        path: PathBuf,
190        position: TextPosition,
191    },
192    Hover {
193        id: LspRequestId,
194        path: PathBuf,
195        position: TextPosition,
196    },
197    Completion {
198        id: LspRequestId,
199        path: PathBuf,
200        position: TextPosition,
201    },
202    References {
203        id: LspRequestId,
204        path: PathBuf,
205        position: TextPosition,
206    },
207    DocumentSymbols {
208        id: LspRequestId,
209        path: PathBuf,
210    },
211    WorkspaceSymbols {
212        id: LspRequestId,
213        query: String,
214    },
215    Rename {
216        id: LspRequestId,
217        path: PathBuf,
218        position: TextPosition,
219        new_name: String,
220    },
221    CodeActions {
222        id: LspRequestId,
223        path: PathBuf,
224        range: TextRange,
225    },
226    Formatting {
227        id: LspRequestId,
228        path: PathBuf,
229    },
230    Cancel {
231        id: LspRequestId,
232    },
233    Shutdown,
234}
235
236#[derive(Debug, Clone, Copy, PartialEq, Eq)]
237pub enum LspFailureKind {
238    NotInstalled,
239    Handshake,
240    Transport,
241    Server,
242    Unsupported,
243}
244
245#[derive(Debug, Clone, PartialEq, Eq)]
246pub struct LspFailure {
247    pub kind: LspFailureKind,
248    pub message: String,
249}
250
251pub type LspResult<T> = Result<T, LspFailure>;
252
253#[derive(Debug, Clone, PartialEq, Eq)]
254pub enum LspEvent {
255    Started,
256    Ready,
257    Indexing { message: String, percent: Option<u8> },
258    Diagnostics { path: PathBuf, version: Option<u64>, items: Vec<Diagnostic> },
259    Definition { id: LspRequestId, locations: Vec<Location> },
260    Hover { id: LspRequestId, hover: Option<HoverText> },
261    Completion { id: LspRequestId, items: Vec<CompletionItem> },
262    References { id: LspRequestId, locations: Vec<Location> },
263    DocumentSymbols { id: LspRequestId, symbols: Vec<DocumentSymbol> },
264    WorkspaceSymbols { id: LspRequestId, symbols: Vec<SymbolLocation> },
265    Rename { id: LspRequestId, edit: WorkspaceEdit },
266    CodeActions { id: LspRequestId, actions: Vec<CodeAction> },
267    Formatting { id: LspRequestId, edits: Vec<TextEdit> },
268    Failed { id: Option<LspRequestId>, failure: LspFailure },
269    Unavailable { message: String },
270    Exited { code: Option<i32> },
271}