1use eure::query::{
4 CompletionItem, CompletionKind, DiagnosticMessage, DiagnosticSeverity, GetFileDiagnostics,
5 GetSemanticTokens, SemanticToken, TextFile, get_completions, get_hover,
6};
7use lsp_types::{
8 CompletionItem as LspCompletionItem, CompletionItemKind, CompletionTextEdit, Diagnostic,
9 DiagnosticSeverity as LspSeverity, Documentation, Hover, HoverContents, MarkupContent,
10 MarkupKind, NumberOrString, Position, Range, SemanticToken as LspSemanticToken, SemanticTokens,
11 TextEdit,
12};
13use query_flow::{Db, QueryError, query};
14
15pub fn lsp_completion(
23 db: &impl Db,
24 file: &TextFile,
25 offset: u32,
26) -> Result<Vec<LspCompletionItem>, QueryError> {
27 let items = get_completions(db, file, offset)?;
28 let source: std::sync::Arc<eure::query::TextFileContent> = db.asset(file.clone())?;
29 let line_offsets = compute_line_offsets(source.get());
30 Ok(items
31 .iter()
32 .map(|item| convert_completion_item(item, source.get(), &line_offsets))
33 .collect())
34}
35
36fn convert_completion_item(
37 item: &CompletionItem,
38 source: &str,
39 line_offsets: &[usize],
40) -> LspCompletionItem {
41 let range = Range {
42 start: offset_to_lsp_position(item.replace.start as usize, source, line_offsets),
43 end: offset_to_lsp_position(item.replace.end as usize, source, line_offsets),
44 };
45 LspCompletionItem {
46 label: item.label.clone(),
47 kind: Some(convert_completion_kind(item.kind)),
48 detail: item.detail.clone(),
49 documentation: item.documentation.as_ref().map(|value| {
50 Documentation::MarkupContent(MarkupContent {
51 kind: MarkupKind::Markdown,
52 value: value.clone(),
53 })
54 }),
55 deprecated: item.deprecated.then_some(true),
56 filter_text: Some(item.label.clone()),
57 text_edit: Some(CompletionTextEdit::Edit(TextEdit {
58 range,
59 new_text: item.label.clone(),
60 })),
61 ..Default::default()
62 }
63}
64
65fn convert_completion_kind(kind: CompletionKind) -> CompletionItemKind {
66 match kind {
67 CompletionKind::Field => CompletionItemKind::FIELD,
68 CompletionKind::Extension => CompletionItemKind::PROPERTY,
69 CompletionKind::Variant => CompletionItemKind::ENUM_MEMBER,
70 CompletionKind::Value => CompletionItemKind::VALUE,
71 }
72}
73
74pub fn lsp_hover(db: &impl Db, file: &TextFile, offset: u32) -> Result<Option<Hover>, QueryError> {
83 let Some(hover) = get_hover(db, file, offset)? else {
84 return Ok(None);
85 };
86 let source: std::sync::Arc<eure::query::TextFileContent> = db.asset(file.clone())?;
87 let line_offsets = compute_line_offsets(source.get());
88 Ok(Some(Hover {
89 contents: HoverContents::Markup(MarkupContent {
90 kind: MarkupKind::Markdown,
91 value: hover.contents,
92 }),
93 range: Some(Range {
94 start: offset_to_lsp_position(hover.span.start as usize, source.get(), &line_offsets),
95 end: offset_to_lsp_position(hover.span.end as usize, source.get(), &line_offsets),
96 }),
97 }))
98}
99
100pub fn lsp_definition(
102 db: &impl Db,
103 file: &TextFile,
104 offset: u32,
105) -> Result<Vec<lsp_types::LocationLink>, QueryError> {
106 let definitions = eure::query::get_definition(db, file, offset)?;
107 let source = db.asset(file.clone())?;
108 let offsets = compute_line_offsets(source.get());
109 definitions
110 .into_iter()
111 .map(|definition| {
112 let target = db.asset(definition.file.clone())?;
113 let target_offsets = compute_line_offsets(target.get());
114 Ok(lsp_types::LocationLink {
115 origin_selection_range: Some(Range {
116 start: offset_to_lsp_position(
117 definition.origin.start as usize,
118 source.get(),
119 &offsets,
120 ),
121 end: offset_to_lsp_position(
122 definition.origin.end as usize,
123 source.get(),
124 &offsets,
125 ),
126 }),
127 target_uri: crate::uri_utils::text_file_to_uri(&definition.file).parse()?,
128 target_range: Range {
129 start: offset_to_lsp_position(
130 definition.range.start as usize,
131 target.get(),
132 &target_offsets,
133 ),
134 end: offset_to_lsp_position(
135 definition.range.end as usize,
136 target.get(),
137 &target_offsets,
138 ),
139 },
140 target_selection_range: Range {
141 start: offset_to_lsp_position(
142 definition.selection.start as usize,
143 target.get(),
144 &target_offsets,
145 ),
146 end: offset_to_lsp_position(
147 definition.selection.end as usize,
148 target.get(),
149 &target_offsets,
150 ),
151 },
152 })
153 })
154 .collect()
155}
156
157pub fn position_to_offset(source: &str, position: Position) -> usize {
162 let line_offsets = compute_line_offsets(source);
163 let Some(&line_start) = line_offsets.get(position.line as usize) else {
164 return source.len();
165 };
166 let line_end = line_offsets
167 .get(position.line as usize + 1)
168 .map(|&next| next - 1)
169 .unwrap_or(source.len());
170 let line = &source[line_start..line_end];
171 let mut utf16_units = 0u32;
172 for (byte_index, c) in line.char_indices() {
173 if utf16_units >= position.character {
174 return line_start + byte_index;
175 }
176 utf16_units += c.len_utf16() as u32;
177 }
178 line_end
179}
180
181#[query]
185pub fn lsp_semantic_tokens(
186 db: &impl Db,
187 file: TextFile,
188 source: String,
189) -> Result<SemanticTokens, QueryError> {
190 let tokens = db.query(GetSemanticTokens::new(file.clone()))?;
191 Ok(convert_tokens(&tokens, &source))
192}
193
194#[query]
200pub fn lsp_diagnostics(
201 db: &impl Db,
202 file: TextFile,
203) -> Result<Vec<(TextFile, Vec<Diagnostic>)>, QueryError> {
204 let diagnostics = db.query(GetFileDiagnostics::new(file.clone()))?;
205
206 let mut by_file: std::collections::HashMap<TextFile, Vec<DiagnosticMessage>> =
208 std::collections::HashMap::new();
209
210 by_file.insert(file, vec![]);
212
213 for d in diagnostics.iter() {
214 by_file.entry(d.file.clone()).or_default().push(d.clone());
215 }
216
217 let mut result = Vec::new();
219 for (diag_file, file_diagnostics) in by_file {
220 let source: std::sync::Arc<eure::query::TextFileContent> = db.asset(diag_file.clone())?;
221 let line_offsets = compute_line_offsets(source.get());
222 let lsp_diagnostics: Vec<Diagnostic> = file_diagnostics
223 .iter()
224 .map(|d| convert_diagnostic(d, source.get(), &line_offsets))
225 .collect();
226 result.push((diag_file, lsp_diagnostics));
227 }
228
229 Ok(result)
230}
231
232#[query]
237pub fn lsp_file_diagnostics(db: &impl Db, file: TextFile) -> Result<Vec<Diagnostic>, QueryError> {
238 let diagnostics = db.query(GetFileDiagnostics::new(file.clone()))?;
239
240 let source: std::sync::Arc<eure::query::TextFileContent> = db.asset(file.clone())?;
242 let line_offsets = compute_line_offsets(source.get());
243
244 let lsp_diagnostics: Vec<Diagnostic> = diagnostics
246 .iter()
247 .filter(|d| d.file == file) .map(|d| convert_diagnostic(d, source.get(), &line_offsets))
249 .collect();
250
251 Ok(lsp_diagnostics)
252}
253
254fn convert_tokens(tokens: &[SemanticToken], source: &str) -> SemanticTokens {
262 let line_offsets = compute_line_offsets(source);
263
264 let mut data = Vec::new();
265 let mut prev_line = 0u32;
266 let mut prev_start = 0u32;
267
268 for token in tokens {
269 let start = token.start as usize;
270 let end = start + token.length as usize;
271 let (line, char) = offset_to_position(start, source, &line_offsets);
272 let length = byte_len_to_utf16_len(source, start, end);
273
274 let delta_line = line - prev_line;
275 let delta_start = if delta_line == 0 {
276 char - prev_start
277 } else {
278 char
279 };
280
281 data.push(LspSemanticToken {
282 delta_line,
283 delta_start,
284 length,
285 token_type: token.token_type as u32,
286 token_modifiers_bitset: token.modifiers,
287 });
288
289 prev_line = line;
290 prev_start = char;
291 }
292
293 SemanticTokens {
294 result_id: None,
295 data,
296 }
297}
298
299fn convert_diagnostic(msg: &DiagnosticMessage, source: &str, line_offsets: &[usize]) -> Diagnostic {
301 let start = offset_to_lsp_position(msg.start, source, line_offsets);
302 let end = offset_to_lsp_position(msg.end, source, line_offsets);
303
304 Diagnostic {
305 range: Range { start, end },
306 severity: Some(convert_severity(msg.severity)),
307 code: msg.code.clone().map(NumberOrString::String),
308 code_description: None,
309 source: Some("eure".to_string()),
310 message: msg.message.clone(),
311 related_information: None,
312 tags: None,
313 data: None,
314 }
315}
316
317fn convert_severity(severity: DiagnosticSeverity) -> LspSeverity {
319 match severity {
320 DiagnosticSeverity::Error => LspSeverity::ERROR,
321 DiagnosticSeverity::Warning => LspSeverity::WARNING,
322 DiagnosticSeverity::Info => LspSeverity::INFORMATION,
323 DiagnosticSeverity::Hint => LspSeverity::HINT,
324 }
325}
326
327fn compute_line_offsets(source: &str) -> Vec<usize> {
331 let mut offsets = vec![0];
332 for (i, c) in source.char_indices() {
333 if c == '\n' {
334 offsets.push(i + 1);
335 }
336 }
337 offsets
338}
339
340fn offset_to_position(offset: usize, source: &str, line_offsets: &[usize]) -> (u32, u32) {
344 let line = line_offsets.iter().rposition(|&o| o <= offset).unwrap_or(0);
345 let line_start = line_offsets[line];
346 let end = offset.min(source.len());
348 let line_content = &source[line_start..end];
349 let utf16_offset: usize = line_content.chars().map(|c| c.len_utf16()).sum();
350 (line as u32, utf16_offset as u32)
351}
352
353fn offset_to_lsp_position(offset: usize, source: &str, line_offsets: &[usize]) -> Position {
355 let (line, character) = offset_to_position(offset, source, line_offsets);
356 Position { line, character }
357}
358
359fn byte_len_to_utf16_len(source: &str, start: usize, end: usize) -> u32 {
361 let end = end.min(source.len());
362 let start = start.min(end);
363 source[start..end]
364 .chars()
365 .map(|c| c.len_utf16())
366 .sum::<usize>() as u32
367}
368
369#[cfg(test)]
370mod tests {
371 use super::*;
372
373 #[test]
374 fn test_compute_line_offsets() {
375 let source = "hello\nworld\n";
376 let offsets = compute_line_offsets(source);
377 assert_eq!(offsets, vec![0, 6, 12]);
378 }
379
380 #[test]
381 fn test_offset_to_position_ascii() {
382 let source = "hello\nworld\n";
383 let offsets = compute_line_offsets(source);
384 assert_eq!(offset_to_position(0, source, &offsets), (0, 0));
385 assert_eq!(offset_to_position(5, source, &offsets), (0, 5));
386 assert_eq!(offset_to_position(6, source, &offsets), (1, 0));
387 assert_eq!(offset_to_position(11, source, &offsets), (1, 5));
388 }
389
390 #[test]
391 fn test_offset_to_position_utf8() {
392 let source = "日本語\ntest";
394 let offsets = compute_line_offsets(source);
395 assert_eq!(offset_to_position(0, source, &offsets), (0, 0));
397 assert_eq!(offset_to_position(3, source, &offsets), (0, 1));
399 assert_eq!(offset_to_position(6, source, &offsets), (0, 2));
401 assert_eq!(offset_to_position(9, source, &offsets), (0, 3));
403 assert_eq!(offset_to_position(10, source, &offsets), (1, 0));
405 }
406
407 #[test]
408 fn test_offset_to_position_emoji() {
409 let source = "😀a";
411 let offsets = compute_line_offsets(source);
412 assert_eq!(offset_to_position(0, source, &offsets), (0, 0));
414 assert_eq!(offset_to_position(4, source, &offsets), (0, 2));
416 assert_eq!(offset_to_position(5, source, &offsets), (0, 3));
418 }
419
420 #[test]
421 fn test_position_to_offset() {
422 let source = "日本語\ntest";
423 assert_eq!(position_to_offset(source, Position::new(0, 0)), 0);
424 assert_eq!(position_to_offset(source, Position::new(0, 2)), 6);
425 assert_eq!(position_to_offset(source, Position::new(0, 3)), 9);
426 assert_eq!(position_to_offset(source, Position::new(0, 10)), 9);
428 assert_eq!(position_to_offset(source, Position::new(1, 4)), 14);
429 assert_eq!(position_to_offset(source, Position::new(5, 0)), 14);
431 assert_eq!(position_to_offset("😀a", Position::new(0, 2)), 4);
433 }
434
435 #[test]
436 fn test_byte_len_to_utf16_len() {
437 assert_eq!(byte_len_to_utf16_len("hello", 0, 5), 5);
439 assert_eq!(byte_len_to_utf16_len("日本語", 0, 9), 3);
441 assert_eq!(byte_len_to_utf16_len("😀", 0, 4), 2);
443 }
444}