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}