Skip to main content

editor_core/
symbols.rs

1//! First-class symbol / outline data model.
2//!
3//! This module provides UI-agnostic types for:
4//! - document outline (document symbols, typically hierarchical)
5//! - workspace symbol search results (usually flat, cross-file)
6//!
7//! The goal is to give hosts a stable schema to build:
8//! - outline trees
9//! - fuzzy search over symbols
10//! - navigation/jump lists
11
12/// A half-open character-offset range (`start..end`) in the document.
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub struct SymbolRange {
15    /// Range start offset (inclusive), in Unicode scalar values (`char`) from the start of the document.
16    pub start: usize,
17    /// Range end offset (exclusive), in Unicode scalar values (`char`) from the start of the document.
18    pub end: usize,
19}
20
21impl SymbolRange {
22    /// Create a new symbol range.
23    pub fn new(start: usize, end: usize) -> Self {
24        Self { start, end }
25    }
26}
27
28/// A UTF-16 coordinate used by protocols like LSP.
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub struct Utf16Position {
31    /// Zero-based line index.
32    pub line: u32,
33    /// Zero-based UTF-16 code unit offset within the line.
34    pub character: u32,
35}
36
37impl Utf16Position {
38    /// Create a new UTF-16 position.
39    pub fn new(line: u32, character: u32) -> Self {
40        Self { line, character }
41    }
42}
43
44/// A UTF-16 range (`start..end`) in `(line, character)` coordinates.
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub struct Utf16Range {
47    /// Range start position (inclusive).
48    pub start: Utf16Position,
49    /// Range end position (exclusive).
50    pub end: Utf16Position,
51}
52
53impl Utf16Range {
54    /// Create a new UTF-16 range.
55    pub fn new(start: Utf16Position, end: Utf16Position) -> Self {
56        Self { start, end }
57    }
58}
59
60/// A cross-file symbol location (URI + UTF-16 range).
61#[derive(Debug, Clone, PartialEq, Eq)]
62pub struct SymbolLocation {
63    /// Document URI (LSP-style).
64    pub uri: String,
65    /// Location range (UTF-16).
66    pub range: Utf16Range,
67}
68
69/// A coarse symbol kind tag.
70#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
71pub enum SymbolKind {
72    /// A file-level symbol.
73    File,
74    /// A module symbol.
75    Module,
76    /// A namespace symbol.
77    Namespace,
78    /// A package symbol.
79    Package,
80    /// A class symbol.
81    Class,
82    /// A method symbol.
83    Method,
84    /// A property symbol.
85    Property,
86    /// A field symbol.
87    Field,
88    /// A constructor symbol.
89    Constructor,
90    /// An enum symbol.
91    Enum,
92    /// An interface symbol.
93    Interface,
94    /// A function symbol.
95    Function,
96    /// A variable symbol.
97    Variable,
98    /// A constant symbol.
99    Constant,
100    /// A string literal / string-like symbol.
101    String,
102    /// A numeric symbol.
103    Number,
104    /// A boolean symbol.
105    Boolean,
106    /// An array symbol.
107    Array,
108    /// An object symbol.
109    Object,
110    /// A key symbol.
111    Key,
112    /// A null symbol.
113    Null,
114    /// An enum member symbol.
115    EnumMember,
116    /// A struct symbol.
117    Struct,
118    /// An event symbol.
119    Event,
120    /// An operator symbol.
121    Operator,
122    /// A type parameter symbol.
123    TypeParameter,
124    /// An integration-defined kind value.
125    Custom(u32),
126}
127
128impl SymbolKind {
129    /// Convert an LSP `SymbolKind` numeric value into a [`SymbolKind`].
130    pub fn from_lsp_kind(kind: u32) -> Self {
131        match kind {
132            1 => Self::File,
133            2 => Self::Module,
134            3 => Self::Namespace,
135            4 => Self::Package,
136            5 => Self::Class,
137            6 => Self::Method,
138            7 => Self::Property,
139            8 => Self::Field,
140            9 => Self::Constructor,
141            10 => Self::Enum,
142            11 => Self::Interface,
143            12 => Self::Function,
144            13 => Self::Variable,
145            14 => Self::Constant,
146            15 => Self::String,
147            16 => Self::Number,
148            17 => Self::Boolean,
149            18 => Self::Array,
150            19 => Self::Object,
151            20 => Self::Key,
152            21 => Self::Null,
153            22 => Self::EnumMember,
154            23 => Self::Struct,
155            24 => Self::Event,
156            25 => Self::Operator,
157            26 => Self::TypeParameter,
158            other => Self::Custom(other),
159        }
160    }
161}
162
163/// A single document symbol node (hierarchical).
164#[derive(Debug, Clone, PartialEq, Eq)]
165pub struct DocumentSymbol {
166    /// Symbol name (e.g. function name).
167    pub name: String,
168    /// Optional detail string (e.g. type signature).
169    pub detail: Option<String>,
170    /// Symbol kind.
171    pub kind: SymbolKind,
172    /// Full symbol span (character offsets).
173    pub range: SymbolRange,
174    /// Selection span (character offsets).
175    pub selection_range: SymbolRange,
176    /// Child symbols.
177    pub children: Vec<DocumentSymbol>,
178    /// Optional raw integration payload, encoded as JSON text.
179    pub data_json: Option<String>,
180}
181
182impl DocumentSymbol {
183    /// Collect this node and all descendants in pre-order.
184    pub fn flatten_preorder<'a>(&'a self, out: &mut Vec<&'a DocumentSymbol>) {
185        out.push(self);
186        for child in &self.children {
187            child.flatten_preorder(out);
188        }
189    }
190
191    /// Find all symbols with the given name (pre-order).
192    pub fn find_by_name<'a>(&'a self, name: &str, out: &mut Vec<&'a DocumentSymbol>) {
193        if self.name == name {
194            out.push(self);
195        }
196        for child in &self.children {
197            child.find_by_name(name, out);
198        }
199    }
200}
201
202/// A document outline (top-level symbol list).
203#[derive(Debug, Clone, PartialEq, Eq, Default)]
204pub struct DocumentOutline {
205    /// Top-level symbols.
206    pub symbols: Vec<DocumentSymbol>,
207}
208
209impl DocumentOutline {
210    /// Create a new outline.
211    pub fn new(symbols: Vec<DocumentSymbol>) -> Self {
212        Self { symbols }
213    }
214
215    /// Returns true if there are no symbols.
216    pub fn is_empty(&self) -> bool {
217        self.symbols.is_empty()
218    }
219
220    /// Return the top-level symbol count.
221    pub fn top_level_count(&self) -> usize {
222        self.symbols.len()
223    }
224
225    /// Flatten all symbols in pre-order.
226    pub fn flatten_preorder(&self) -> Vec<&DocumentSymbol> {
227        let mut out = Vec::new();
228        for sym in &self.symbols {
229            sym.flatten_preorder(&mut out);
230        }
231        out
232    }
233
234    /// Find all symbols with the given name (pre-order).
235    pub fn find_by_name(&self, name: &str) -> Vec<&DocumentSymbol> {
236        let mut out = Vec::new();
237        for sym in &self.symbols {
238            sym.find_by_name(name, &mut out);
239        }
240        out
241    }
242}
243
244/// A workspace symbol (cross-file, usually flat).
245#[derive(Debug, Clone, PartialEq, Eq)]
246pub struct WorkspaceSymbol {
247    /// Symbol name.
248    pub name: String,
249    /// Optional detail string.
250    pub detail: Option<String>,
251    /// Symbol kind.
252    pub kind: SymbolKind,
253    /// Symbol location.
254    pub location: SymbolLocation,
255    /// Optional container name (e.g. namespace/module).
256    pub container_name: Option<String>,
257    /// Optional raw integration payload, encoded as JSON text.
258    pub data_json: Option<String>,
259}