1use line_index::LineIndex;
2use log::info;
3use rowan::{TextRange, TextSize};
4use salsa::Setter;
5use serde::{Deserialize, Serialize};
6use squawk_ide::builtins::builtins_line_index;
7use squawk_ide::db::{self, Database, File};
8use squawk_ide::folding_ranges::{FoldKind, folding_ranges};
9use squawk_ide::goto_definition::FileId;
10use squawk_ide::semantic_tokens::{SemanticTokenType, semantic_tokens};
11use squawk_syntax::ast::AstNode;
12use wasm_bindgen::prelude::*;
13use web_sys::js_sys::Error;
14
15const SEMANTIC_TOKEN_TYPES: &[&str] = &[
16 "comment",
17 "function",
18 "keyword",
19 "namespace",
20 "number",
21 "operator",
22 "parameter",
23 "property",
24 "string",
25 "struct",
26 "type",
27 "variable",
28];
29
30const SEMANTIC_TOKEN_MODIFIERS: &[&str] = &["declaration", "definition", "readonly"];
31
32fn semantic_token_type_name(ty: SemanticTokenType) -> &'static str {
33 match ty {
34 SemanticTokenType::Bool | SemanticTokenType::Keyword => "keyword",
35 SemanticTokenType::Comment => "comment",
36 SemanticTokenType::Function => "function",
37 SemanticTokenType::Name | SemanticTokenType::NameRef => "variable",
38 SemanticTokenType::Number => "number",
39 SemanticTokenType::Operator | SemanticTokenType::Punctuation => "operator",
40 SemanticTokenType::Parameter | SemanticTokenType::PositionalParam => "parameter",
41 SemanticTokenType::String => "string",
42 SemanticTokenType::Type => "type",
43 }
44}
45
46fn semantic_token_type_index(ty: SemanticTokenType) -> u32 {
47 let name = semantic_token_type_name(ty);
48 SEMANTIC_TOKEN_TYPES
49 .iter()
50 .position(|it| *it == name)
51 .unwrap() as u32
52}
53
54struct EncodedSemanticToken {
55 line: u32,
56 start: u32,
57 length: u32,
58 token_type: SemanticTokenType,
59 modifiers: u32,
60}
61
62struct SemanticTokenEncoder {
63 data: Vec<u32>,
64 prev_line: u32,
65 prev_start: u32,
66}
67
68impl SemanticTokenEncoder {
69 fn with_capacity(token_count: usize) -> Self {
70 Self {
71 data: Vec::with_capacity(token_count * 5),
72 prev_line: 0,
73 prev_start: 0,
74 }
75 }
76
77 fn push(&mut self, token: EncodedSemanticToken) {
78 let delta_line = token.line - self.prev_line;
79 let delta_start = if delta_line == 0 {
80 token.start - self.prev_start
81 } else {
82 token.start
83 };
84
85 self.data.extend_from_slice(&[
86 delta_line,
87 delta_start,
88 token.length,
89 semantic_token_type_index(token.token_type),
90 token.modifiers,
91 ]);
92
93 self.prev_line = token.line;
94 self.prev_start = token.start;
95 }
96
97 fn finish(self) -> Vec<u32> {
98 self.data
99 }
100}
101
102#[wasm_bindgen(start)]
103pub fn run() {
104 use log::Level;
105
106 #[cfg(feature = "console_error_panic_hook")]
113 console_error_panic_hook::set_once();
114 console_log::init_with_level(Level::Debug).expect("Initializing logger went wrong.");
115 info!("init!");
116}
117
118#[wasm_bindgen]
119pub struct SquawkDatabase {
120 db: Database,
121 file: Option<File>,
122}
123
124#[wasm_bindgen]
125#[allow(clippy::new_without_default)]
126impl SquawkDatabase {
127 #[wasm_bindgen(constructor)]
128 pub fn new() -> SquawkDatabase {
129 SquawkDatabase {
130 db: Database::default(),
131 file: None,
132 }
133 }
134
135 pub fn open_file(&mut self, content: String) {
136 let file = File::new(&self.db, content.into());
137 self.file = Some(file);
138 }
139
140 pub fn update_file(&mut self, content: String) {
141 if let Some(file) = self.file {
142 file.set_content(&mut self.db).to(content.into());
143 }
144 }
145
146 fn file(&self) -> Result<File, Error> {
147 self.file
148 .ok_or_else(|| Error::new("No file open. Call open_file first."))
149 }
150
151 pub fn dump_cst(&self) -> Result<String, Error> {
152 let file = self.file()?;
153 let parse = db::parse(&self.db, file);
154 Ok(format!("{:#?}", parse.syntax_node()))
155 }
156
157 pub fn dump_tokens(&self) -> Result<String, Error> {
158 let file = self.file()?;
159 let content = file.content(&self.db);
160 let tokens = squawk_lexer::tokenize(content);
161 let mut start = 0;
162 let mut out = String::new();
163 for token in tokens {
164 let end = start + token.len;
165 let text = &content[start as usize..(end) as usize];
166 out += &format!("{:?}@{start}..{end} {:?}\n", token.kind, text);
167 start += token.len;
168 }
169 Ok(out)
170 }
171
172 pub fn lint(&self) -> Result<JsValue, Error> {
173 let file = self.file()?;
174 let content = file.content(&self.db);
175 let mut linter = squawk_linter::Linter::with_all_rules();
176 let parse = db::parse(&self.db, file);
177 let parse_errors = parse.errors();
178
179 let line_index = db::line_index(&self.db, file);
180
181 let parse_errors = parse_errors.iter().map(|x| {
182 let range_start = x.range().start();
183 let range_end = x.range().end();
184 let start = line_index.line_col(range_start);
185 let end = line_index.line_col(range_end);
186 let start = line_index
187 .to_wide(line_index::WideEncoding::Utf16, start)
188 .unwrap();
189 let end = line_index
190 .to_wide(line_index::WideEncoding::Utf16, end)
191 .unwrap();
192 LintError {
193 severity: Severity::Error,
194 code: "syntax-error".to_string(),
195 message: x.message().to_string(),
196 start_line_number: start.line,
197 start_column: start.col,
198 end_line_number: end.line,
199 end_column: end.col,
200 range_start: range_start.into(),
201 range_end: range_end.into(),
202 messages: vec![],
203 fix: None,
204 }
205 });
206
207 let lint_errors = linter.lint(&parse, content);
208 let errors = lint_errors.into_iter().map(|x| {
209 let start = line_index.line_col(x.text_range.start());
210 let end = line_index.line_col(x.text_range.end());
211 let start = line_index
212 .to_wide(line_index::WideEncoding::Utf16, start)
213 .unwrap();
214 let end = line_index
215 .to_wide(line_index::WideEncoding::Utf16, end)
216 .unwrap();
217
218 let messages = x.help.into_iter().collect();
219
220 let fix = x.fix.map(|fix| {
221 let edits = fix
222 .edits
223 .into_iter()
224 .map(|edit| {
225 let start_pos = line_index.line_col(edit.text_range.start());
226 let end_pos = line_index.line_col(edit.text_range.end());
227 let start_wide = line_index
228 .to_wide(line_index::WideEncoding::Utf16, start_pos)
229 .unwrap();
230 let end_wide = line_index
231 .to_wide(line_index::WideEncoding::Utf16, end_pos)
232 .unwrap();
233
234 TextEdit {
235 start_line_number: start_wide.line,
236 start_column: start_wide.col,
237 end_line_number: end_wide.line,
238 end_column: end_wide.col,
239 text: edit.text.unwrap_or_default(),
240 }
241 })
242 .collect();
243
244 Fix {
245 title: fix.title,
246 edits,
247 }
248 });
249
250 LintError {
251 code: x.code.to_string(),
252 range_start: x.text_range.start().into(),
253 range_end: x.text_range.end().into(),
254 message: x.message.clone(),
255 messages,
256 severity: Severity::Warning,
257 start_line_number: start.line,
258 start_column: start.col,
259 end_line_number: end.line,
260 end_column: end.col,
261 fix,
262 }
263 });
264
265 let mut errors_to_dump = errors.chain(parse_errors).collect::<Vec<_>>();
266 errors_to_dump.sort_by_key(|k| (k.start_line_number, k.start_column));
267
268 serde_wasm_bindgen::to_value(&errors_to_dump).map_err(into_error)
269 }
270
271 pub fn goto_definition(&self, line: u32, col: u32) -> Result<JsValue, Error> {
272 let file = self.file()?;
273 let current_line_index = db::line_index(&self.db, file);
274 let offset = position_to_offset(¤t_line_index, line, col)?;
275 let builtins_li = builtins_line_index(&self.db);
276 let result = squawk_ide::goto_definition::goto_definition(&self.db, file, offset);
277
278 let response: Vec<LocationRange> = result
279 .into_iter()
280 .map(|location| {
281 let range = location.range;
282 let (file, line_index) = match location.file {
283 FileId::Current => ("current", ¤t_line_index),
284 FileId::Builtins => ("builtins", &builtins_li),
285 };
286 let start = line_index.line_col(range.start());
287 let end = line_index.line_col(range.end());
288 let start_wide = line_index
289 .to_wide(line_index::WideEncoding::Utf16, start)
290 .unwrap();
291 let end_wide = line_index
292 .to_wide(line_index::WideEncoding::Utf16, end)
293 .unwrap();
294
295 LocationRange {
296 file: file.to_string(),
297 start_line: start_wide.line,
298 start_column: start_wide.col,
299 end_line: end_wide.line,
300 end_column: end_wide.col,
301 }
302 })
303 .collect();
304
305 serde_wasm_bindgen::to_value(&response).map_err(into_error)
306 }
307
308 pub fn hover(&self, line: u32, col: u32) -> Result<JsValue, Error> {
309 let file = self.file()?;
310 let line_index = db::line_index(&self.db, file);
311 let offset = position_to_offset(&line_index, line, col)?;
312 let result = squawk_ide::hover::hover(&self.db, file, offset);
313
314 serde_wasm_bindgen::to_value(&result).map_err(into_error)
315 }
316
317 pub fn find_references(&self, line: u32, col: u32) -> Result<JsValue, Error> {
318 let file = self.file()?;
319 let line_index = db::line_index(&self.db, file);
320 let offset = position_to_offset(&line_index, line, col)?;
321 let references = squawk_ide::find_references::find_references(&self.db, file, offset);
322 let builtins_li = builtins_line_index(&self.db);
323 let locations: Vec<LocationRange> = references
324 .iter()
325 .map(|loc| {
326 let (li, file) = match loc.file {
327 FileId::Current => (&line_index, "current"),
328 FileId::Builtins => (&builtins_li, "builtins"),
329 };
330 let start = li.line_col(loc.range.start());
331 let end = li.line_col(loc.range.end());
332 let start_wide = li.to_wide(line_index::WideEncoding::Utf16, start).unwrap();
333 let end_wide = li.to_wide(line_index::WideEncoding::Utf16, end).unwrap();
334
335 LocationRange {
336 file: file.to_string(),
337 start_line: start_wide.line,
338 start_column: start_wide.col,
339 end_line: end_wide.line,
340 end_column: end_wide.col,
341 }
342 })
343 .collect();
344
345 serde_wasm_bindgen::to_value(&locations).map_err(into_error)
346 }
347
348 pub fn document_symbols(&self) -> Result<JsValue, Error> {
349 let file = self.file()?;
350 let line_index = db::line_index(&self.db, file);
351 let symbols = squawk_ide::document_symbols::document_symbols(&self.db, file);
352
353 let converted: Vec<WasmDocumentSymbol> = symbols
354 .into_iter()
355 .map(|s| convert_document_symbol(&line_index, s))
356 .collect();
357
358 serde_wasm_bindgen::to_value(&converted).map_err(into_error)
359 }
360
361 pub fn code_actions(&self, line: u32, col: u32) -> Result<JsValue, Error> {
362 let file = self.file()?;
363 let line_index = db::line_index(&self.db, file);
364 let offset = position_to_offset(&line_index, line, col)?;
365 let actions = squawk_ide::code_actions::code_actions(&self.db, file, offset);
366
367 let converted = actions.map(|actions| {
368 actions
369 .into_iter()
370 .map(|action| {
371 let edits = action
372 .edits
373 .into_iter()
374 .map(|edit| {
375 let start_pos = line_index.line_col(edit.text_range.start());
376 let end_pos = line_index.line_col(edit.text_range.end());
377 let start_wide = line_index
378 .to_wide(line_index::WideEncoding::Utf16, start_pos)
379 .unwrap();
380 let end_wide = line_index
381 .to_wide(line_index::WideEncoding::Utf16, end_pos)
382 .unwrap();
383
384 TextEdit {
385 start_line_number: start_wide.line,
386 start_column: start_wide.col,
387 end_line_number: end_wide.line,
388 end_column: end_wide.col,
389 text: edit.text.unwrap_or_default(),
390 }
391 })
392 .collect();
393
394 WasmCodeAction {
395 title: action.title,
396 edits,
397 kind: match action.kind {
398 squawk_ide::code_actions::ActionKind::QuickFix => "quickfix",
399 squawk_ide::code_actions::ActionKind::RefactorRewrite => {
400 "refactor.rewrite"
401 }
402 }
403 .to_string(),
404 }
405 })
406 .collect::<Vec<_>>()
407 });
408
409 serde_wasm_bindgen::to_value(&converted).map_err(into_error)
410 }
411
412 pub fn inlay_hints(&self) -> Result<JsValue, Error> {
413 let file = self.file()?;
414 let line_index = db::line_index(&self.db, file);
415 let hints = squawk_ide::inlay_hints::inlay_hints(&self.db, file);
416
417 let converted: Vec<WasmInlayHint> = hints
418 .into_iter()
419 .map(|hint| {
420 let position = line_index.line_col(hint.position);
421 let position_wide = line_index
422 .to_wide(line_index::WideEncoding::Utf16, position)
423 .unwrap();
424
425 WasmInlayHint {
426 line: position_wide.line,
427 column: position_wide.col,
428 label: hint.label,
429 kind: match hint.kind {
430 squawk_ide::inlay_hints::InlayHintKind::Type => "type",
431 squawk_ide::inlay_hints::InlayHintKind::Parameter => "parameter",
432 }
433 .to_string(),
434 }
435 })
436 .collect();
437
438 serde_wasm_bindgen::to_value(&converted).map_err(into_error)
439 }
440
441 pub fn folding_ranges(&self) -> Result<JsValue, Error> {
442 let file = self.file()?;
443 let line_index = db::line_index(&self.db, file);
444 let folds = folding_ranges(&self.db, file);
445
446 let converted: Vec<WasmFoldingRange> = folds
447 .into_iter()
448 .map(|fold| {
449 let start = line_index.line_col(fold.range.start());
450 let end = line_index.line_col(fold.range.end());
451 let start_wide = line_index
452 .to_wide(line_index::WideEncoding::Utf16, start)
453 .unwrap();
454 let end_wide = line_index
455 .to_wide(line_index::WideEncoding::Utf16, end)
456 .unwrap();
457
458 WasmFoldingRange {
459 start_line: start_wide.line,
460 end_line: end_wide.line,
461 kind: match fold.kind {
462 FoldKind::Comment => "comment",
463 _ => "region",
464 }
465 .to_string(),
466 }
467 })
468 .collect();
469
470 serde_wasm_bindgen::to_value(&converted).map_err(into_error)
471 }
472
473 pub fn selection_ranges(&self, positions: Vec<JsValue>) -> Result<JsValue, Error> {
474 let file = self.file()?;
475 let parse = db::parse(&self.db, file);
476 let line_index = db::line_index(&self.db, file);
477 let tree = parse.tree();
478 let root = tree.syntax();
479
480 let mut results: Vec<Vec<WasmSelectionRange>> = vec![];
481
482 for pos in positions {
483 let pos: Position = serde_wasm_bindgen::from_value(pos).map_err(into_error)?;
484 let offset = position_to_offset(&line_index, pos.line, pos.column)?;
485
486 let mut ranges = vec![];
487 let mut range = TextRange::new(offset, offset);
488
489 for _ in 0..20 {
490 let next = squawk_ide::expand_selection::extend_selection(root, range);
491 if next == range {
492 break;
493 }
494
495 let start = line_index.line_col(next.start());
496 let end = line_index.line_col(next.end());
497 let start_wide = line_index
498 .to_wide(line_index::WideEncoding::Utf16, start)
499 .unwrap();
500 let end_wide = line_index
501 .to_wide(line_index::WideEncoding::Utf16, end)
502 .unwrap();
503
504 ranges.push(WasmSelectionRange {
505 start_line: start_wide.line,
506 start_column: start_wide.col,
507 end_line: end_wide.line,
508 end_column: end_wide.col,
509 });
510
511 range = next;
512 }
513
514 results.push(ranges);
515 }
516
517 serde_wasm_bindgen::to_value(&results).map_err(into_error)
518 }
519
520 pub fn semantic_tokens(&self) -> Result<Vec<u32>, Error> {
521 let file = self.file()?;
522 let line_index = db::line_index(&self.db, file);
523 let content = file.content(&self.db);
524 let tokens = semantic_tokens(&self.db, file, None);
525
526 let mut encoder = SemanticTokenEncoder::with_capacity(tokens.len());
527
528 for token in &tokens {
530 for mut text_range in line_index.lines(token.range) {
534 if content[text_range].ends_with('\n') {
535 text_range =
536 TextRange::new(text_range.start(), text_range.end() - TextSize::of('\n'));
537 }
538 let start_lc = line_index.line_col(text_range.start());
539 let end_lc = line_index.line_col(text_range.end());
540 let start_wide = line_index
541 .to_wide(line_index::WideEncoding::Utf16, start_lc)
542 .unwrap();
543 let end_wide = line_index
544 .to_wide(line_index::WideEncoding::Utf16, end_lc)
545 .unwrap();
546
547 encoder.push(EncodedSemanticToken {
548 line: start_wide.line,
549 start: start_wide.col,
550 length: end_wide.col - start_wide.col,
551 token_type: token.token_type,
552 modifiers: 0,
554 });
555 }
556 }
557
558 Ok(encoder.finish())
559 }
560
561 pub fn semantic_tokens_legend() -> Result<JsValue, Error> {
562 let legend = SemanticTokensLegend {
563 token_types: SEMANTIC_TOKEN_TYPES.to_vec(),
564 token_modifiers: SEMANTIC_TOKEN_MODIFIERS.to_vec(),
565 };
566 serde_wasm_bindgen::to_value(&legend).map_err(into_error)
567 }
568
569 pub fn completion(&self, line: u32, col: u32) -> Result<JsValue, Error> {
570 let file = self.file()?;
571 let line_index = db::line_index(&self.db, file);
572 let offset = position_to_offset(&line_index, line, col)?;
573 let items = squawk_ide::completion::completion(&self.db, file, offset);
574
575 let converted: Vec<WasmCompletionItem> = items
576 .into_iter()
577 .map(|item| WasmCompletionItem {
578 label: item.label,
579 kind: match item.kind {
580 squawk_ide::completion::CompletionItemKind::Keyword => "keyword",
581 squawk_ide::completion::CompletionItemKind::Table => "table",
582 squawk_ide::completion::CompletionItemKind::Column => "column",
583 squawk_ide::completion::CompletionItemKind::Function => "function",
584 squawk_ide::completion::CompletionItemKind::Schema => "schema",
585 squawk_ide::completion::CompletionItemKind::Type => "type",
586 squawk_ide::completion::CompletionItemKind::Snippet => "snippet",
587 squawk_ide::completion::CompletionItemKind::Operator => "operator",
588 }
589 .to_string(),
590 detail: item.detail,
591 insert_text: item.insert_text,
592 insert_text_format: item.insert_text_format.map(|fmt| {
593 match fmt {
594 squawk_ide::completion::CompletionInsertTextFormat::PlainText => {
595 "plainText"
596 }
597 squawk_ide::completion::CompletionInsertTextFormat::Snippet => "snippet",
598 }
599 .to_string()
600 }),
601 trigger_completion_after_insert: item.trigger_completion_after_insert,
602 })
603 .collect();
604
605 serde_wasm_bindgen::to_value(&converted).map_err(into_error)
606 }
607}
608
609fn into_error<E: std::fmt::Display>(err: E) -> Error {
610 Error::new(&err.to_string())
611}
612
613fn position_to_offset(
614 line_index: &LineIndex,
615 line: u32,
616 col: u32,
617) -> Result<rowan::TextSize, Error> {
618 let wide_pos = line_index::WideLineCol { line, col };
619
620 let pos = line_index
621 .to_utf8(line_index::WideEncoding::Utf16, wide_pos)
622 .ok_or_else(|| Error::new("Invalid position"))?;
623
624 line_index
625 .offset(pos)
626 .ok_or_else(|| Error::new("Invalid position offset"))
627}
628
629fn convert_document_symbol(
630 line_index: &LineIndex,
631 symbol: squawk_ide::document_symbols::DocumentSymbol,
632) -> WasmDocumentSymbol {
633 let full_start = line_index.line_col(symbol.full_range.start());
634 let full_end = line_index.line_col(symbol.full_range.end());
635 let full_start_wide = line_index
636 .to_wide(line_index::WideEncoding::Utf16, full_start)
637 .unwrap();
638 let full_end_wide = line_index
639 .to_wide(line_index::WideEncoding::Utf16, full_end)
640 .unwrap();
641
642 let focus_start = line_index.line_col(symbol.focus_range.start());
643 let focus_end = line_index.line_col(symbol.focus_range.end());
644 let focus_start_wide = line_index
645 .to_wide(line_index::WideEncoding::Utf16, focus_start)
646 .unwrap();
647 let focus_end_wide = line_index
648 .to_wide(line_index::WideEncoding::Utf16, focus_end)
649 .unwrap();
650
651 WasmDocumentSymbol {
652 name: symbol.name,
653 detail: symbol.detail,
654 kind: match symbol.kind {
655 squawk_ide::document_symbols::DocumentSymbolKind::Schema => "schema",
656 squawk_ide::document_symbols::DocumentSymbolKind::Table => "table",
657 squawk_ide::document_symbols::DocumentSymbolKind::View => "view",
658 squawk_ide::document_symbols::DocumentSymbolKind::MaterializedView => {
659 "materialized_view"
660 }
661 squawk_ide::document_symbols::DocumentSymbolKind::Function => "function",
662 squawk_ide::document_symbols::DocumentSymbolKind::Aggregate => "aggregate",
663 squawk_ide::document_symbols::DocumentSymbolKind::Procedure => "procedure",
664 squawk_ide::document_symbols::DocumentSymbolKind::EventTrigger => "event_trigger",
665 squawk_ide::document_symbols::DocumentSymbolKind::Role => "role",
666 squawk_ide::document_symbols::DocumentSymbolKind::Policy => "policy",
667 squawk_ide::document_symbols::DocumentSymbolKind::Type => "type",
668 squawk_ide::document_symbols::DocumentSymbolKind::Enum => "enum",
669 squawk_ide::document_symbols::DocumentSymbolKind::Index => "index",
670 squawk_ide::document_symbols::DocumentSymbolKind::Domain => "domain",
671 squawk_ide::document_symbols::DocumentSymbolKind::Sequence => "sequence",
672 squawk_ide::document_symbols::DocumentSymbolKind::Trigger => "trigger",
673 squawk_ide::document_symbols::DocumentSymbolKind::Tablespace => "tablespace",
674 squawk_ide::document_symbols::DocumentSymbolKind::Database => "database",
675 squawk_ide::document_symbols::DocumentSymbolKind::Server => "server",
676 squawk_ide::document_symbols::DocumentSymbolKind::Extension => "extension",
677 squawk_ide::document_symbols::DocumentSymbolKind::Column => "column",
678 squawk_ide::document_symbols::DocumentSymbolKind::Variant => "variant",
679 squawk_ide::document_symbols::DocumentSymbolKind::Cursor => "cursor",
680 squawk_ide::document_symbols::DocumentSymbolKind::PreparedStatement => {
681 "prepared_statement"
682 }
683 squawk_ide::document_symbols::DocumentSymbolKind::Channel => "channel",
684 }
685 .to_string(),
686 start_line: full_start_wide.line,
687 start_column: full_start_wide.col,
688 end_line: full_end_wide.line,
689 end_column: full_end_wide.col,
690 selection_start_line: focus_start_wide.line,
691 selection_start_column: focus_start_wide.col,
692 selection_end_line: focus_end_wide.line,
693 selection_end_column: focus_end_wide.col,
694 children: symbol
695 .children
696 .into_iter()
697 .map(|child| convert_document_symbol(line_index, child))
698 .collect(),
699 }
700}
701
702#[expect(unused)]
703#[derive(Serialize)]
704enum Severity {
705 Hint,
706 Info,
707 Warning,
708 Error,
709}
710
711#[derive(Serialize)]
712struct LintError {
713 severity: Severity,
714 code: String,
715 message: String,
716 start_line_number: u32,
717 start_column: u32,
718 end_line_number: u32,
719 end_column: u32,
720 range_start: usize,
721 range_end: usize,
722 messages: Vec<String>,
723 fix: Option<Fix>,
724}
725
726#[derive(Serialize)]
727struct Fix {
728 title: String,
729 edits: Vec<TextEdit>,
730}
731
732#[derive(Serialize)]
733struct TextEdit {
734 start_line_number: u32,
735 start_column: u32,
736 end_line_number: u32,
737 end_column: u32,
738 text: String,
739}
740
741#[derive(Serialize)]
742struct LocationRange {
743 file: String,
744 start_line: u32,
745 start_column: u32,
746 end_line: u32,
747 end_column: u32,
748}
749
750#[derive(Serialize)]
751struct WasmCodeAction {
752 title: String,
753 edits: Vec<TextEdit>,
754 kind: String,
755}
756
757#[derive(Serialize)]
758struct WasmDocumentSymbol {
759 name: String,
760 detail: Option<String>,
761 kind: String,
762 start_line: u32,
763 start_column: u32,
764 end_line: u32,
765 end_column: u32,
766 selection_start_line: u32,
767 selection_start_column: u32,
768 selection_end_line: u32,
769 selection_end_column: u32,
770 children: Vec<WasmDocumentSymbol>,
771}
772
773#[derive(Serialize)]
774struct WasmInlayHint {
775 line: u32,
776 column: u32,
777 label: String,
778 kind: String,
779}
780
781#[derive(Serialize)]
782struct WasmFoldingRange {
783 start_line: u32,
784 end_line: u32,
785 kind: String,
786}
787
788#[derive(Serialize)]
789struct WasmSelectionRange {
790 start_line: u32,
791 start_column: u32,
792 end_line: u32,
793 end_column: u32,
794}
795
796#[derive(Serialize)]
797struct SemanticTokensLegend {
798 #[serde(rename = "tokenTypes")]
799 token_types: Vec<&'static str>,
800 #[serde(rename = "tokenModifiers")]
801 token_modifiers: Vec<&'static str>,
802}
803
804#[derive(Serialize)]
805struct WasmCompletionItem {
806 label: String,
807 kind: String,
808 detail: Option<String>,
809 insert_text: Option<String>,
810 insert_text_format: Option<String>,
811 trigger_completion_after_insert: bool,
812}
813
814#[derive(Deserialize)]
815struct Position {
816 line: u32,
817 column: u32,
818}