Skip to main content

increparse_lsp/
simple.rs

1//! The builder-shaped `Language` implementation for the common case.
2
3use std::sync::Arc;
4
5use increparse::Engine;
6use lsp_types::{CompletionResponse, Diagnostic, DocumentSymbol, Hover, Location, SymbolKind};
7
8use crate::document::Document;
9use crate::encoding::PositionEncoding;
10use crate::server::Language;
11use crate::FailedNode;
12
13/// The type of the [`SimpleLanguage::diagnostic_fn`] hook.
14pub type DiagnosticFn<C> =
15    dyn Fn(&Document<C>, &FailedNode<'_, C>) -> Option<Diagnostic> + Send + Sync;
16
17/// The type of the [`SimpleLanguage::extra_diagnostics`] hook.
18pub type ExtraDiagnosticsFn<C> = dyn Fn(&Document<C>) -> Vec<Diagnostic> + Send + Sync;
19
20/// The type of the [`SimpleLanguage::symbols_fn`] hook.
21pub type SymbolsFn<C> = dyn Fn(&Document<C>) -> Vec<DocumentSymbol> + Send + Sync;
22
23/// The type of the [`SimpleLanguage::label_fn`] hook.
24pub type LabelFn<C> = dyn Fn(&C) -> Option<NodeLabel> + Send + Sync;
25
26/// A [`Language`] built from closures — the Rust equivalent of a Lua
27/// language definition.
28///
29/// For the common server you never implement the [`Language`] trait;
30/// describe the language and hand the builder to
31/// [`serve`](crate::serve):
32///
33/// ```
34/// use increparse::Engine;
35/// use increparse_lsp::{Document, FailedNode, SimpleLanguage};
36/// use lsp_types::Diagnostic;
37///
38/// # #[derive(Clone, Debug, PartialEq, Eq)]
39/// # enum Ctx { File }
40/// # fn build(engine: Engine<Ctx>) {
41/// let language = SimpleLanguage::new(engine, Ctx::File)
42///     .diagnostic_fn(|_doc, node| {
43///         Some(Diagnostic { message: "could not parse".into(), ..Diagnostic::default() })
44///     })
45///     .label_fn(|ctx| Some(increparse_lsp::NodeLabel::new("region")));
46/// # let _ = language;
47/// # }
48/// ```
49pub struct SimpleLanguage<C> {
50    engine: Engine<C>,
51    root_ctx: C,
52    encoding: PositionEncoding,
53    diagnostic_fn: Option<Arc<DiagnosticFn<C>>>,
54    extra_diagnostics_fn: Option<Arc<ExtraDiagnosticsFn<C>>>,
55    symbols_fn: Option<Arc<SymbolsFn<C>>>,
56    label_fn: Option<Arc<LabelFn<C>>>,
57    describe_fn: Option<Arc<DescribeFn<C>>>,
58    hover_fn: Option<Arc<HoverFn<C>>>,
59    definition_fn: Option<Arc<DefinitionFn<C>>>,
60    completion_fn: Option<Arc<CompletionFn<C>>>,
61    code_action_fn: Option<Arc<CodeActionFn<C>>>,
62}
63
64/// The type of the [`SimpleLanguage::describe_fn`] hook: describe a
65/// context in one sentence and the skeleton turns it into hover contents.
66pub type DescribeFn<C> = dyn Fn(&C) -> Option<String> + Send + Sync;
67
68/// The type of the [`SimpleLanguage::hover_fn`] hook (full control).
69pub type HoverFn<C> = dyn Fn(&Document<C>, usize) -> Option<Hover> + Send + Sync;
70
71/// The type of the [`SimpleLanguage::definition_fn`] hook.
72pub type DefinitionFn<C> = dyn Fn(&Document<C>, usize) -> Option<Vec<Location>> + Send + Sync;
73
74/// The type of the [`SimpleLanguage::completion_fn`] hook.
75pub type CompletionFn<C> = dyn Fn(&Document<C>, usize) -> Option<CompletionResponse> + Send + Sync;
76
77/// The type of the [`SimpleLanguage::code_action_fn`] hook: code
78/// actions (typically quickfixes) for a range.
79pub type CodeActionFn<C> =
80    dyn Fn(&Document<C>, lsp_types::Range) -> Vec<lsp_types::CodeAction> + Send + Sync;
81
82/// A display name (and optional detail) for one tree node — what
83/// [`SimpleLanguage::label_fn`] returns to power the outline view.
84#[derive(Debug, Clone, PartialEq, Eq)]
85pub struct NodeLabel {
86    /// The symbol's name.
87    pub name: String,
88    /// Optional detail shown next to the name (e.g. a parameter list).
89    pub detail: Option<String>,
90    /// The outline kind (defaults to [`SymbolKind::FUNCTION`] — override it
91    /// for languages whose named things are rules, sections, selectors,
92    /// recipes, ...).
93    pub kind: SymbolKind,
94}
95
96impl NodeLabel {
97    /// Creates a label with no detail and the default kind.
98    pub fn new(name: impl Into<String>) -> Self {
99        Self {
100            name: name.into(),
101            detail: None,
102            kind: SymbolKind::FUNCTION,
103        }
104    }
105
106    /// Sets the detail string.
107    pub fn detail(mut self, detail: impl Into<String>) -> Self {
108        self.detail = Some(detail.into());
109        self
110    }
111
112    /// Sets the outline kind — a language's named things need not be
113    /// functions.
114    pub fn kind(mut self, kind: SymbolKind) -> Self {
115        self.kind = kind;
116        self
117    }
118}
119
120impl<C: Clone + PartialEq + Send + 'static> SimpleLanguage<C> {
121    /// Creates a builder over an engine and a root context.
122    pub fn new(engine: Engine<C>, root_ctx: C) -> Self {
123        Self {
124            engine,
125            root_ctx,
126            encoding: PositionEncoding::Utf16,
127            diagnostic_fn: None,
128            extra_diagnostics_fn: None,
129            symbols_fn: None,
130            label_fn: None,
131            describe_fn: None,
132            hover_fn: None,
133            definition_fn: None,
134            completion_fn: None,
135            code_action_fn: None,
136        }
137    }
138
139    /// Sets the negotiated position encoding (default UTF-16).
140    pub fn encoding(mut self, encoding: PositionEncoding) -> Self {
141        self.encoding = encoding;
142        self
143    }
144
145    /// How a failing region becomes a diagnostic; `None` stays silent.
146    /// Diagnostics returned with an empty range are filled in from the
147    /// node's span.
148    pub fn diagnostic_fn(
149        mut self,
150        f: impl Fn(&Document<C>, &FailedNode<'_, C>) -> Option<Diagnostic> + Send + Sync + 'static,
151    ) -> Self {
152        self.diagnostic_fn = Some(Arc::new(f));
153        self
154    }
155
156    /// Diagnostics that do not come from parse failures — lint-rule
157    /// violations, style warnings, anything computed from the settled
158    /// document. Called once per publish, after the parse pass; its output
159    /// is appended to the parse diagnostics.
160    pub fn extra_diagnostics(
161        mut self,
162        f: impl Fn(&Document<C>) -> Vec<Diagnostic> + Send + Sync + 'static,
163    ) -> Self {
164        self.extra_diagnostics_fn = Some(Arc::new(f));
165        self
166    }
167
168    /// Full-control outline symbols. Mutually exclusive in spirit with
169    /// [`label_fn`](Self::label_fn) — when both are set, this wins.
170    pub fn symbols_fn(
171        mut self,
172        f: impl Fn(&Document<C>) -> Vec<DocumentSymbol> + Send + Sync + 'static,
173    ) -> Self {
174        self.symbols_fn = Some(Arc::new(f));
175        self
176    }
177
178    /// Names tree nodes; every named node becomes an outline symbol with
179    /// the node's range — symbols without writing a tree walk.
180    pub fn label_fn(mut self, f: impl Fn(&C) -> Option<NodeLabel> + Send + Sync + 'static) -> Self {
181        self.label_fn = Some(Arc::new(f));
182        self
183    }
184
185    /// One-sentence hover: describe a context and the skeleton finds the
186    /// node under the cursor, builds the hover contents, and attaches the
187    /// node's range. The friendliest way to add hover — nothing about the
188    /// language's shape is assumed.
189    pub fn describe_fn(mut self, f: impl Fn(&C) -> Option<String> + Send + Sync + 'static) -> Self {
190        self.describe_fn = Some(Arc::new(f));
191        self
192    }
193
194    /// Full-control hover: receives the document and the cursor's byte
195    /// offset (position encoding already handled).
196    pub fn hover_fn(
197        mut self,
198        f: impl Fn(&Document<C>, usize) -> Option<Hover> + Send + Sync + 'static,
199    ) -> Self {
200        self.hover_fn = Some(Arc::new(f));
201        self
202    }
203
204    /// Go-to-definition: return the locations to jump to.
205    pub fn definition_fn(
206        mut self,
207        f: impl Fn(&Document<C>, usize) -> Option<Vec<Location>> + Send + Sync + 'static,
208    ) -> Self {
209        self.definition_fn = Some(Arc::new(f));
210        self
211    }
212
213    /// Completions for the cursor position.
214    pub fn completion_fn(
215        mut self,
216        f: impl Fn(&Document<C>, usize) -> Option<CompletionResponse> + Send + Sync + 'static,
217    ) -> Self {
218        self.completion_fn = Some(Arc::new(f));
219        self
220    }
221
222    /// Code actions for a range — typically quickfixes for the
223    /// diagnostics published there.
224    pub fn code_action_fn(
225        mut self,
226        f: impl Fn(&Document<C>, lsp_types::Range) -> Vec<lsp_types::CodeAction>
227            + Send
228            + Sync
229            + 'static,
230    ) -> Self {
231        self.code_action_fn = Some(Arc::new(f));
232        self
233    }
234}
235
236/// `C` must additionally be `Sync` because the stored closures accept
237/// `&C` from any thread.
238impl<C: Clone + PartialEq + Send + Sync + 'static> Language<C> for SimpleLanguage<C> {
239    fn supports_symbols(&self) -> bool {
240        self.symbols_fn.is_some() || self.label_fn.is_some()
241    }
242
243    fn supports_hover(&self) -> bool {
244        self.hover_fn.is_some() || self.describe_fn.is_some()
245    }
246
247    fn supports_definition(&self) -> bool {
248        self.definition_fn.is_some()
249    }
250
251    fn supports_completion(&self) -> bool {
252        self.completion_fn.is_some()
253    }
254
255    fn supports_code_actions(&self) -> bool {
256        self.code_action_fn.is_some()
257    }
258
259    fn code_action(
260        &self,
261        doc: &Document<C>,
262        range: lsp_types::Range,
263    ) -> Vec<lsp_types::CodeAction> {
264        match &self.code_action_fn {
265            Some(f) => f(doc, range),
266            None => Vec::new(),
267        }
268    }
269
270    fn hover(&self, doc: &Document<C>, offset: usize) -> Option<Hover> {
271        if let Some(f) = &self.hover_fn {
272            return f(doc, offset);
273        }
274        let describe = self.describe_fn.as_ref()?;
275        let tree = doc.session().tree();
276        let id = tree.node_at(offset)?;
277        let text = describe(tree.ctx(id))?;
278        Some(Hover {
279            contents: lsp_types::HoverContents::Scalar(lsp_types::MarkedString::String(text)),
280            range: Some(doc.range(tree.span(id))),
281        })
282    }
283
284    fn definition(&self, doc: &Document<C>, offset: usize) -> Option<Vec<Location>> {
285        let f = self.definition_fn.as_ref()?;
286        f(doc, offset)
287    }
288
289    fn completion(&self, doc: &Document<C>, offset: usize) -> Option<CompletionResponse> {
290        let f = self.completion_fn.as_ref()?;
291        f(doc, offset)
292    }
293
294    fn engine(&self) -> &Engine<C> {
295        &self.engine
296    }
297
298    fn root_ctx(&self) -> C {
299        self.root_ctx.clone()
300    }
301
302    fn encoding(&self) -> PositionEncoding {
303        self.encoding
304    }
305
306    fn diagnostic(&self, doc: &Document<C>, node: FailedNode<'_, C>) -> Option<Diagnostic> {
307        let f = self.diagnostic_fn.as_ref()?;
308        f(doc, &node)
309    }
310
311    fn extra_diagnostics(&self, doc: &Document<C>) -> Vec<Diagnostic> {
312        match &self.extra_diagnostics_fn {
313            Some(f) => f(doc),
314            None => Vec::new(),
315        }
316    }
317
318    fn symbols(&self, doc: &Document<C>) -> Vec<DocumentSymbol> {
319        if let Some(f) = &self.symbols_fn {
320            return f(doc);
321        }
322        if let Some(label) = &self.label_fn {
323            let tree = doc.session().tree();
324            return tree
325                .nodes()
326                .filter_map(|id| {
327                    let label = label(tree.ctx(id))?;
328                    let range = doc.range(tree.span(id));
329                    Some(DocumentSymbol {
330                        name: label.name,
331                        detail: label.detail,
332                        kind: label.kind,
333                        range,
334                        selection_range: range,
335                        children: None,
336                        tags: None,
337                        #[allow(deprecated)]
338                        deprecated: None,
339                    })
340                })
341                .collect();
342        }
343        Vec::new()
344    }
345}