1use lattice_core::Buffer;
15use lattice_protocol::Position;
16
17fn is_word_char_byte(b: u8) -> bool {
22 b.is_ascii_alphanumeric() || b == b'_'
23}
24
25pub fn word_under_cursor(buffer: &Buffer, cursor: Position) -> Option<String> {
32 let line = buffer.line(cursor.line)?;
33 let bytes = line.as_bytes();
34 let byte_idx = cursor.byte as usize;
35 if byte_idx >= bytes.len() || !is_word_char_byte(bytes[byte_idx]) {
36 return None;
37 }
38 let mut start = byte_idx;
39 while start > 0 && is_word_char_byte(bytes[start - 1]) {
40 start -= 1;
41 }
42 let mut end = byte_idx;
43 while end < bytes.len() && is_word_char_byte(bytes[end]) {
44 end += 1;
45 }
46 if start == end {
47 return None;
48 }
49 Some(String::from_utf8_lossy(&bytes[start..end]).into_owned())
50}
51
52pub fn symbol_kind_glyph(kind: lattice_lsp::lsp_types::SymbolKind) -> &'static str {
57 use lattice_lsp::lsp_types::SymbolKind as K;
58 match kind {
59 K::FILE => "📄",
60 K::MODULE | K::NAMESPACE | K::PACKAGE => "📦",
61 K::CLASS | K::INTERFACE => "🅒",
62 K::METHOD | K::FUNCTION => "Æ’",
63 K::CONSTRUCTOR => "🅒",
64 K::PROPERTY | K::FIELD => "•",
65 K::VARIABLE => "v",
66 K::CONSTANT => "K",
67 K::STRING | K::NUMBER | K::BOOLEAN | K::ARRAY | K::OBJECT => "≡",
68 K::ENUM | K::ENUM_MEMBER => "🅔",
69 K::STRUCT => "🅢",
70 K::EVENT => "🅔",
71 K::OPERATOR => "⊕",
72 K::TYPE_PARAMETER => "T",
73 _ => "?",
74 }
75}
76
77pub fn symbol_information_to_row(
81 sym: &lattice_lsp::lsp_types::SymbolInformation,
82) -> Option<lattice_lsp::cache::SymbolRow> {
83 let path = lattice_lsp::actor::uri_to_path(&sym.location.uri)?;
84 Some(lattice_lsp::cache::SymbolRow {
85 name: sym.name.clone(),
86 kind_glyph: symbol_kind_glyph(sym.kind),
87 container: sym.container_name.clone(),
88 depth: 0,
89 path,
90 line: sym.location.range.start.line,
91 col: sym.location.range.start.character,
92 })
93}
94
95pub fn flatten_document_symbol_response(
102 resp: lattice_lsp::lsp_types::DocumentSymbolResponse,
103 path: &std::path::Path,
104 out: &mut Vec<lattice_lsp::cache::SymbolRow>,
105) {
106 match resp {
107 lattice_lsp::lsp_types::DocumentSymbolResponse::Flat(syms) => {
108 for sym in syms {
109 if let Some(row) = symbol_information_to_row(&sym) {
110 out.push(row);
111 }
112 }
113 }
114 lattice_lsp::lsp_types::DocumentSymbolResponse::Nested(syms) => {
115 fn walk(
116 syms: Vec<lattice_lsp::lsp_types::DocumentSymbol>,
117 path: &std::path::Path,
118 depth: u32,
119 out: &mut Vec<lattice_lsp::cache::SymbolRow>,
120 ) {
121 for sym in syms {
122 out.push(lattice_lsp::cache::SymbolRow {
123 name: sym.name.clone(),
124 kind_glyph: symbol_kind_glyph(sym.kind),
125 container: None,
126 depth,
127 path: path.to_path_buf(),
128 line: sym.selection_range.start.line,
129 col: sym.selection_range.start.character,
130 });
131 if let Some(children) = sym.children {
132 walk(children, path, depth + 1, out);
133 }
134 }
135 }
136 walk(syms, path, 0, out);
137 }
138 }
139}
140
141pub async fn workspace_symbol_to_row(
149 handle: &lattice_lsp::ServerHandle,
150 sym: lattice_lsp::lsp_types::WorkspaceSymbol,
151 token: &lattice_protocol::CancellationToken,
152) -> Option<lattice_lsp::cache::SymbolRow> {
153 use lattice_lsp::lsp_types::OneOf;
154 let (path, line, col) = match &sym.location {
155 OneOf::Left(loc) => (
156 lattice_lsp::actor::uri_to_path(&loc.uri)?,
157 loc.range.start.line,
158 loc.range.start.character,
159 ),
160 OneOf::Right(wsl) => {
161 let path = lattice_lsp::actor::uri_to_path(&wsl.uri)?;
162 if !handle.capabilities().workspace_symbol_resolve_provider() {
166 (path, 0, 0)
167 } else {
168 match handle
169 .workspace_symbol_resolve(sym.clone(), token.clone())
170 .await
171 {
172 Ok(resolved) => match resolved.location {
173 OneOf::Left(loc) => (
174 lattice_lsp::actor::uri_to_path(&loc.uri).unwrap_or(path),
175 loc.range.start.line,
176 loc.range.start.character,
177 ),
178 OneOf::Right(_) => (path, 0, 0),
179 },
180 Err(_) => (path, 0, 0),
181 }
182 }
183 }
184 };
185 Some(lattice_lsp::cache::SymbolRow {
186 name: sym.name,
187 kind_glyph: symbol_kind_glyph(sym.kind),
188 container: sym.container_name,
189 depth: 0,
190 path,
191 line,
192 col,
193 })
194}
195
196pub fn signature_help_to_markdown(sh: &lattice_lsp::lsp_types::SignatureHelp) -> String {
203 if sh.signatures.is_empty() {
204 return String::new();
205 }
206 let active_sig_idx = sh.active_signature.unwrap_or(0) as usize;
207 let sig = sh
208 .signatures
209 .get(active_sig_idx)
210 .or_else(|| sh.signatures.first())
211 .expect("non-empty checked above");
212 let mut out = String::new();
213 out.push_str("```text\n");
216 out.push_str(&sig.label);
217 out.push_str("\n```\n");
218 if let Some(active_param_idx) = sig.active_parameter.or(sh.active_parameter)
221 && let Some(params) = sig.parameters.as_ref()
222 && let Some(param) = params.get(active_param_idx as usize)
223 {
224 let label_str = match ¶m.label {
225 lattice_lsp::lsp_types::ParameterLabel::Simple(s) => s.clone(),
226 lattice_lsp::lsp_types::ParameterLabel::LabelOffsets(_) => String::new(),
227 };
228 if !label_str.is_empty() {
229 out.push_str(&format!("\n**param:** `{label_str}`\n"));
230 }
231 if let Some(doc) = param.documentation.as_ref() {
232 let doc_str = match doc {
233 lattice_lsp::lsp_types::Documentation::String(s) => s.clone(),
234 lattice_lsp::lsp_types::Documentation::MarkupContent(mc) => mc.value.clone(),
235 };
236 if !doc_str.is_empty() {
237 out.push('\n');
238 out.push_str(&doc_str);
239 out.push('\n');
240 }
241 }
242 }
243 if let Some(doc) = sig.documentation.as_ref() {
245 let doc_str = match doc {
246 lattice_lsp::lsp_types::Documentation::String(s) => s.clone(),
247 lattice_lsp::lsp_types::Documentation::MarkupContent(mc) => mc.value.clone(),
248 };
249 if !doc_str.is_empty() {
250 out.push('\n');
251 out.push_str(&doc_str);
252 out.push('\n');
253 }
254 }
255 out
256}
257
258pub fn completion_kind_glyph(
264 kind: Option<lattice_lsp::lsp_types::CompletionItemKind>,
265) -> &'static str {
266 use lattice_lsp::lsp_types::CompletionItemKind as K;
267 match kind {
268 Some(K::FUNCTION) | Some(K::METHOD) | Some(K::CONSTRUCTOR) => "Æ’",
269 Some(K::VARIABLE) | Some(K::FIELD) | Some(K::PROPERTY) => "v",
270 Some(K::CONSTANT) => "K",
271 Some(K::CLASS) | Some(K::INTERFACE) => "🅒",
272 Some(K::STRUCT) => "🅢",
273 Some(K::ENUM) | Some(K::ENUM_MEMBER) => "🅔",
274 Some(K::MODULE) => "📦",
275 Some(K::FILE) | Some(K::FOLDER) => "📄",
276 Some(K::SNIPPET) => "✂",
277 Some(K::KEYWORD) => "K",
278 Some(K::TEXT) => "≡",
279 Some(K::REFERENCE) => "→",
280 _ => "?",
281 }
282}
283
284pub fn definition_response_to_locations(
294 resp: lattice_lsp::lsp_types::GotoDefinitionResponse,
295) -> Vec<lattice_lsp::lsp_types::Location> {
296 match resp {
297 lattice_lsp::lsp_types::GotoDefinitionResponse::Scalar(loc) => vec![loc],
298 lattice_lsp::lsp_types::GotoDefinitionResponse::Array(locs) => locs,
299 lattice_lsp::lsp_types::GotoDefinitionResponse::Link(links) => links
300 .into_iter()
301 .map(|l| lattice_lsp::lsp_types::Location {
302 uri: l.target_uri,
303 range: l.target_selection_range,
307 })
308 .collect(),
309 }
310}
311
312pub fn app_to_lsp_position(
317 buffer: &Buffer,
318 p: Position,
319) -> Option<lattice_lsp::lsp_types::Position> {
320 let line_text = buffer.line(p.line)?;
321 let character = lattice_lsp::position::utf8_byte_to_utf16_column(&line_text, p.byte);
322 Some(lattice_lsp::lsp_types::Position {
323 line: p.line,
324 character,
325 })
326}
327
328pub fn range_covers(
345 range: lattice_lsp::lsp_types::Range,
346 position: lattice_lsp::lsp_types::Position,
347) -> bool {
348 let after_start =
349 (range.start.line, range.start.character) <= (position.line, position.character);
350 let before_end = (position.line, position.character) <= (range.end.line, range.end.character);
351 after_start && before_end
352}
353
354pub fn call_hierarchy_to_row(
359 item: &lattice_lsp::lsp_types::CallHierarchyItem,
360 related_to: &str,
361) -> lattice_lsp::cache::SymbolRow {
362 let path = lattice_lsp::actor::uri_to_path(&item.uri).unwrap_or_default();
363 lattice_lsp::cache::SymbolRow {
364 name: item.name.clone(),
365 kind_glyph: symbol_kind_glyph(item.kind),
366 container: Some(format!("in {related_to}")),
367 depth: 0,
368 path,
369 line: item.selection_range.start.line,
370 col: item.selection_range.start.character,
371 }
372}
373
374pub fn type_hierarchy_to_row(
379 item: &lattice_lsp::lsp_types::TypeHierarchyItem,
380 related_to: &str,
381) -> lattice_lsp::cache::SymbolRow {
382 let path = lattice_lsp::actor::uri_to_path(&item.uri).unwrap_or_default();
383 lattice_lsp::cache::SymbolRow {
384 name: item.name.clone(),
385 kind_glyph: symbol_kind_glyph(item.kind),
386 container: Some(format!("of {related_to}")),
387 depth: 0,
388 path,
389 line: item.selection_range.start.line,
390 col: item.selection_range.start.character,
391 }
392}
393
394pub fn code_action_kind_glyph(
398 kind: Option<&lattice_lsp::lsp_types::CodeActionKind>,
399) -> &'static str {
400 use lattice_lsp::lsp_types::CodeActionKind as K;
401 let Some(kind) = kind else {
402 return "?";
403 };
404 if *kind == K::QUICKFIX {
405 "🛠"
406 } else if *kind == K::REFACTOR {
407 "â™»"
408 } else if *kind == K::REFACTOR_EXTRACT {
409 "↗"
410 } else if *kind == K::REFACTOR_INLINE {
411 "↘"
412 } else if *kind == K::REFACTOR_REWRITE {
413 "↺"
414 } else if *kind == K::SOURCE {
415 "★"
416 } else if *kind == K::SOURCE_ORGANIZE_IMPORTS {
417 "≡"
418 } else if *kind == K::SOURCE_FIX_ALL {
419 "✓"
420 } else {
421 "?"
422 }
423}
424
425pub fn prepare_rename_placeholder(
429 resp: &lattice_lsp::lsp_types::PrepareRenameResponse,
430) -> Option<String> {
431 match resp {
432 lattice_lsp::lsp_types::PrepareRenameResponse::RangeWithPlaceholder {
433 placeholder, ..
434 } => Some(placeholder.clone()),
435 lattice_lsp::lsp_types::PrepareRenameResponse::Range(_) => None,
436 lattice_lsp::lsp_types::PrepareRenameResponse::DefaultBehavior { .. } => None,
437 }
438}
439
440pub fn cursor_inside_range(
445 cursor: lattice_protocol::Position,
446 range: &lattice_lsp::lsp_types::Range,
447) -> bool {
448 let line = cursor.line;
449 let col = cursor.byte;
450 let start = (range.start.line, range.start.character);
451 let end = (range.end.line, range.end.character);
452 let here = (line, col);
453 here >= start && here < end
454}
455
456pub fn flatten_selection_range_chain(
460 head: &lattice_lsp::lsp_types::SelectionRange,
461) -> Vec<lattice_lsp::lsp_types::Range> {
462 let mut out = Vec::new();
463 let mut cur = Some(head);
464 while let Some(node) = cur {
465 out.push(node.range);
466 cur = node.parent.as_deref();
467 }
468 out
469}
470
471pub fn hover_contents_to_markdown(contents: &lattice_lsp::lsp_types::HoverContents) -> String {
472 fn marked_to_markdown(m: &lattice_lsp::lsp_types::MarkedString) -> String {
473 match m {
474 lattice_lsp::lsp_types::MarkedString::String(s) => s.clone(),
475 lattice_lsp::lsp_types::MarkedString::LanguageString(ls) => {
476 format!("```{}\n{}\n```", ls.language, ls.value)
477 }
478 }
479 }
480 match contents {
481 lattice_lsp::lsp_types::HoverContents::Scalar(m) => marked_to_markdown(m),
482 lattice_lsp::lsp_types::HoverContents::Array(items) => items
483 .iter()
484 .map(marked_to_markdown)
485 .collect::<Vec<_>>()
486 .join("\n\n"),
487 lattice_lsp::lsp_types::HoverContents::Markup(m) => m.value.clone(),
488 }
489}