|
1 | 1 | use line_index::LineIndex; |
2 | 2 | use log::info; |
3 | | -use rowan::TextRange; |
| 3 | +use rowan::{TextRange, TextSize}; |
4 | 4 | use salsa::Setter; |
5 | 5 | use serde::{Deserialize, Serialize}; |
6 | 6 | use squawk_ide::builtins::builtins_line_index; |
7 | 7 | use squawk_ide::db::{self, Database, File}; |
8 | 8 | use squawk_ide::folding_ranges::{FoldKind, folding_ranges}; |
9 | 9 | use squawk_ide::goto_definition::FileId; |
| 10 | +use squawk_ide::semantic_tokens::{SemanticTokenType, semantic_tokens}; |
10 | 11 | use squawk_syntax::ast::AstNode; |
11 | 12 | use wasm_bindgen::prelude::*; |
12 | 13 | use web_sys::js_sys::Error; |
13 | 14 |
|
| 15 | +const 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 | + |
| 30 | +const SEMANTIC_TOKEN_MODIFIERS: &[&str] = &["declaration", "definition", "readonly"]; |
| 31 | + |
| 32 | +fn 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 | + |
| 46 | +fn 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 | + |
| 54 | +struct EncodedSemanticToken { |
| 55 | + line: u32, |
| 56 | + start: u32, |
| 57 | + length: u32, |
| 58 | + token_type: SemanticTokenType, |
| 59 | + modifiers: u32, |
| 60 | +} |
| 61 | + |
| 62 | +struct SemanticTokenEncoder { |
| 63 | + data: Vec<u32>, |
| 64 | + prev_line: u32, |
| 65 | + prev_start: u32, |
| 66 | +} |
| 67 | + |
| 68 | +impl 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 | + |
14 | 102 | #[wasm_bindgen(start)] |
15 | 103 | pub fn run() { |
16 | 104 | use log::Level; |
@@ -429,6 +517,55 @@ impl SquawkDatabase { |
429 | 517 | serde_wasm_bindgen::to_value(&results).map_err(into_error) |
430 | 518 | } |
431 | 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 | + // Duplicated from squawk-server, fyi |
| 529 | + for token in &tokens { |
| 530 | + // Taken from rust-analyzer, this solves the case where we have a |
| 531 | + // multi line semantic token which isn't supported by the LSP spec. |
| 532 | + // see: https://github.com/rust-lang/rust-analyzer/blob/2efc80078029894eec0699f62ec8d5c1a56af763/crates/rust-analyzer/src/lsp/to_proto.rs#L781C28-L781C28 |
| 533 | + 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 | + // TODO: once we get modifiers going, we'll need to update this |
| 553 | + 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 | + |
432 | 569 | pub fn completion(&self, line: u32, col: u32) -> Result<JsValue, Error> { |
433 | 570 | let file = self.file()?; |
434 | 571 | let line_index = db::line_index(&self.db, file); |
@@ -656,6 +793,14 @@ struct WasmSelectionRange { |
656 | 793 | end_column: u32, |
657 | 794 | } |
658 | 795 |
|
| 796 | +#[derive(Serialize)] |
| 797 | +struct SemanticTokensLegend { |
| 798 | + #[serde(rename = "tokenTypes")] |
| 799 | + token_types: Vec<&'static str>, |
| 800 | + #[serde(rename = "tokenModifiers")] |
| 801 | + token_modifiers: Vec<&'static str>, |
| 802 | +} |
| 803 | + |
659 | 804 | #[derive(Serialize)] |
660 | 805 | struct WasmCompletionItem { |
661 | 806 | label: String, |
|
0 commit comments