Skip to content

Commit 801e9dc

Browse files
authored
Fix semantic highlightning for attributes on mods (#489)
1 parent 61ad42e commit 801e9dc

File tree

8 files changed

+169
-48
lines changed

8 files changed

+169
-48
lines changed

src/ide/analysis_progress.rs

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ pub struct ProcMacroServerTracker {
2222
}
2323

2424
impl ProcMacroServerTracker {
25+
#[expect(clippy::new_without_default)]
2526
pub fn new() -> Self {
2627
Self {
2728
procmacro_request_submitted: Arc::new(AtomicBool::new(false)),

src/ide/semantic_highlighting/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use crate::lang::db::AnalysisDatabase;
1414
use crate::lang::lsp::LsProtoGroup;
1515

1616
mod encoder;
17-
mod token_kind;
17+
pub mod token_kind;
1818

1919
/// Resolve the semantic tokens of a given file.
2020
pub fn semantic_highlight_full(

src/ide/semantic_highlighting/token_kind.rs

+7-8
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ use lsp_types::SemanticTokenType;
1111

1212
use crate::lang::db::{AnalysisDatabase, LsSemanticGroup};
1313

14-
#[allow(dead_code)]
1514
pub enum SemanticTokenKind {
1615
Namespace,
1716
Class,
@@ -22,6 +21,7 @@ pub enum SemanticTokenKind {
2221
Type,
2322
Parameter,
2423
Variable,
24+
#[allow(dead_code)]
2525
Property,
2626
EnumMember,
2727
Function,
@@ -134,7 +134,7 @@ impl SemanticTokenKind {
134134
_ => {}
135135
};
136136

137-
for lookup_item_id in db.collect_lookup_items_leaf(&node)? {
137+
if let Some(lookup_item_id) = db.find_lookup_item(&node) {
138138
// Resolved items.
139139
if let Some(item) =
140140
db.lookup_resolved_generic_item_by_ptr(lookup_item_id, identifier.stable_ptr())
@@ -176,12 +176,11 @@ impl SemanticTokenKind {
176176
}
177177

178178
// Exprs and patterns..
179-
let Some(function_id) = lookup_item_id.function_with_body() else {
180-
continue;
181-
};
182-
if let Some(expr_path_ptr) = expr_path_ptr {
183-
if db.lookup_pattern_by_ptr(function_id, expr_path_ptr.into()).is_ok() {
184-
return Some(SemanticTokenKind::Variable);
179+
if let Some(function_id) = lookup_item_id.function_with_body() {
180+
if let Some(expr_path_ptr) = expr_path_ptr {
181+
if db.lookup_pattern_by_ptr(function_id, expr_path_ptr.into()).is_ok() {
182+
return Some(SemanticTokenKind::Variable);
183+
}
185184
}
186185
}
187186
}

src/lang/db/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ pub struct AnalysisDatabase {
5656

5757
impl AnalysisDatabase {
5858
/// Creates a new instance of the database.
59+
#[expect(clippy::new_without_default)]
5960
pub fn new() -> Self {
6061
let mut db = Self { storage: Default::default() };
6162

src/testing.rs

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ use crate::server::connection::ConnectionInitializer;
33
use crate::server::schedule::thread::JoinHandle;
44
use anyhow::Result;
55

6+
pub use crate::ide::semantic_highlighting::token_kind::SemanticTokenKind;
7+
68
/// Special object to run the language server in end-to-end tests.
79
pub struct BackendForTesting(Backend);
810

tests/e2e/semantic_tokens/complex.rs

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
use crate::semantic_tokens::semantic_tokens;
2+
use crate::support::insta::test_transform;
3+
4+
#[test]
5+
fn complex() {
6+
test_transform!(semantic_tokens, r#"
7+
enum Foo {
8+
Bar,
9+
Baz,
10+
}
11+
12+
fn main() {
13+
let foo = Foo::Bar;
14+
let foobar: Foo = foo;
15+
}
16+
17+
fn calc(foo: Foo) {}
18+
19+
#[cfg(test)]
20+
mod rectangle {
21+
use super::Foo;
22+
}
23+
24+
mod b {
25+
mod a {
26+
mod trick {
27+
#[test]
28+
struct Foo {}
29+
}
30+
}
31+
}
32+
"#, @r"
33+
<token=keyword>enum</token> <token=enum>Foo</token> {
34+
<token=enumMember>Bar</token>,
35+
<token=enumMember>Baz</token>,
36+
}
37+
38+
<token=keyword>fn</token> <token=function>main</token>() {
39+
<token=keyword>let</token> <token=variable>foo</token> = <token=type>Foo</token>::<token=enumMember>Bar</token>;
40+
<token=keyword>let</token> <token=variable>foobar</token>: <token=type>Foo</token> = <token=variable>foo</token>;
41+
}
42+
43+
<token=keyword>fn</token> <token=function>calc</token>(<token=parameter>foo</token>: <token=type>Foo</token>) {}
44+
45+
#[<token=decorator>cfg</token>(<token=decorator>test</token>)]
46+
<token=keyword>mod</token> <token=class>rectangle</token> {
47+
<token=keyword>use</token> <token=keyword>super</token>::<token=type>Foo</token>;
48+
}
49+
50+
<token=keyword>mod</token> <token=class>b</token> {
51+
<token=keyword>mod</token> <token=class>a</token> {
52+
<token=keyword>mod</token> <token=class>trick</token> {
53+
#[<token=decorator>test</token>]
54+
<token=keyword>struct</token> <token=struct>Foo</token> {}
55+
}
56+
}
57+
}
58+
")
59+
}
60+
61+
#[test]
62+
fn multiline() {
63+
test_transform!(semantic_tokens, r#"
64+
fn main() {
65+
let _ = "
66+
";
67+
}
68+
"#, @r#"
69+
<token=keyword>fn</token> <token=function>main</token>() {
70+
<token=keyword>let</token> _ = <token=string>"</token>
71+
<token=string> "</token>;
72+
}
73+
"#)
74+
}
75+
76+
#[test]
77+
fn on_mod() {
78+
test_transform!(semantic_tokens, r#"
79+
#[cfg(test, 1234)]
80+
mod rectangle { }
81+
"#, @r"
82+
#[<token=decorator>cfg</token>(<token=decorator>test</token>, <token=number>1234</token>)]
83+
<token=keyword>mod</token> <token=class>rectangle</token> { }
84+
")
85+
}
86+
87+
#[test]
88+
fn on_fn() {
89+
test_transform!(semantic_tokens, r#"
90+
#[cfg(test, 1234)]
91+
fn rectangle() { }
92+
"#, @r"
93+
#[<token=decorator>cfg</token>(<token=decorator>test</token>, <token=number>1234</token>)]
94+
<token=keyword>fn</token> <token=function>rectangle</token>() { }
95+
")
96+
}
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,17 @@
1-
use lsp_types::lsp_request;
1+
use lsp_types::{Position, Range, lsp_request};
22

33
use crate::support::cairo_project_toml::CAIRO_PROJECT_TOML_2023_11;
4+
use crate::support::cursor::render_text_with_annotations;
45
use crate::support::sandbox;
6+
use cairo_language_server::testing::SemanticTokenKind;
57

6-
fn caps(base: lsp_types::ClientCapabilities) -> lsp_types::ClientCapabilities {
7-
lsp_types::ClientCapabilities {
8-
text_document: base.text_document.or_else(Default::default).map(|it| {
9-
lsp_types::TextDocumentClientCapabilities {
10-
semantic_tokens: Some(lsp_types::SemanticTokensClientCapabilities {
11-
dynamic_registration: Some(false),
12-
requests: lsp_types::SemanticTokensClientCapabilitiesRequests {
13-
full: Some(lsp_types::SemanticTokensFullOptions::Bool(true)),
14-
..Default::default()
15-
},
16-
..Default::default()
17-
}),
18-
..it
19-
}
20-
}),
21-
..base
22-
}
23-
}
8+
mod complex;
249

25-
#[test]
26-
fn highlights_multiline_tokens() {
10+
fn semantic_tokens(code: &str) -> String {
2711
let mut ls = sandbox! {
2812
files {
2913
"cairo_project.toml" => CAIRO_PROJECT_TOML_2023_11,
30-
"src/lib.cairo" => r#"
31-
fn main() {
32-
let _ = "
33-
";
34-
}
35-
"#,
14+
"src/lib.cairo" => code,
3615
}
3716
client_capabilities = caps;
3817
};
@@ -52,16 +31,50 @@ fn main() {
5231
panic!("expected full tokens")
5332
};
5433

55-
// There is a multiline (2) string, check if 2 consecutive tokens are of type string.
56-
assert!(tokens.data.windows(2).any(|tokens| {
57-
let string_type = 16; // SemanticTokenKind::String.as_u32()
58-
let first = tokens[0];
59-
let second = tokens[1];
34+
let mut line = 0;
35+
let mut character = 0;
36+
37+
let legend = SemanticTokenKind::legend();
38+
39+
let tokens: Vec<_> = tokens
40+
.data
41+
.into_iter()
42+
.map(|token| {
43+
// Reset on new line.
44+
if token.delta_line != 0 {
45+
character = 0;
46+
}
47+
48+
line += token.delta_line;
49+
character += token.delta_start;
50+
51+
let start = Position { character, line };
52+
let end = Position { character: start.character + token.length, ..start };
53+
54+
let token_type = legend[token.token_type as usize].as_str().to_string();
6055

61-
let are_on_consecutive_lines = first.delta_line + 1 == second.delta_line;
62-
let are_both_string =
63-
first.token_type == second.token_type && first.token_type == string_type;
56+
(Range { start, end }, Some(token_type))
57+
})
58+
.collect();
6459

65-
are_both_string && are_on_consecutive_lines
66-
}));
60+
render_text_with_annotations(code, "token", &tokens)
61+
}
62+
63+
fn caps(base: lsp_types::ClientCapabilities) -> lsp_types::ClientCapabilities {
64+
lsp_types::ClientCapabilities {
65+
text_document: base.text_document.or_else(Default::default).map(|it| {
66+
lsp_types::TextDocumentClientCapabilities {
67+
semantic_tokens: Some(lsp_types::SemanticTokensClientCapabilities {
68+
dynamic_registration: Some(false),
69+
requests: lsp_types::SemanticTokensClientCapabilitiesRequests {
70+
full: Some(lsp_types::SemanticTokensFullOptions::Bool(true)),
71+
..Default::default()
72+
},
73+
..Default::default()
74+
}),
75+
..it
76+
}
77+
}),
78+
..base
79+
}
6780
}

tests/e2e/support/cursor.rs

+11-2
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,15 @@ pub fn render_selections(text: &str, ranges: &[Range]) -> String {
188188

189189
/// Adds selection markers for all ranges to the source text with optional attributes to attach.
190190
pub fn render_selections_with_attrs(text: &str, ranges: &[(Range, Option<String>)]) -> String {
191+
render_text_with_annotations(text, "sel", ranges)
192+
}
193+
194+
/// Adds markers for all ranges to the source text with optional attributes to attach.
195+
pub fn render_text_with_annotations(
196+
text: &str,
197+
annotation_name: &str,
198+
ranges: &[(Range, Option<String>)],
199+
) -> String {
191200
let mut text = text.to_owned();
192201
ranges
193202
.iter()
@@ -197,11 +206,11 @@ pub fn render_selections_with_attrs(text: &str, ranges: &[(Range, Option<String>
197206
(
198207
index_in_text(&text, range.start),
199208
format!(
200-
"<sel{attr}>",
209+
"<{annotation_name}{attr}>",
201210
attr = attr.as_ref().map(|val| format!("={val}")).unwrap_or_default()
202211
),
203212
),
204-
(index_in_text(&text, range.end), "</sel>".to_owned()),
213+
(index_in_text(&text, range.end), format!("</{annotation_name}>")),
205214
]
206215
})
207216
.sorted_by_key(|(idx, _)| *idx)

0 commit comments

Comments
 (0)