Skip to content

Commit bb77ba1

Browse files
author
Alex Cooke
committed
Address review feedback: introduce structs, macro, and rename API
- Replace bare (u32, u32) tuples with TokenClassification and CachedToken structs - Replace DecodedToken test tuple with named struct - Add define_token_types! macro to keep index constants and legend in sync - Use SrcPos::cmp for sorting instead of manual line/character comparison - Move in_range to Range::overlaps_lines in vhdl_lang - Rename Project::semantic_tokens to find_all_entity_references - Add source file filter in search_decl to guard against cross-file decl_pos - Match ExternalObjectClass directly instead of converting to ObjectClass - Skip multi-line tokens in encode instead of computing wrong length
1 parent c39677f commit bb77ba1

5 files changed

Lines changed: 172 additions & 100 deletions

File tree

vhdl_lang/src/data/source.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,11 @@ impl Range {
244244
pub fn contains(&self, position: Position) -> bool {
245245
self.start <= position && self.end >= position
246246
}
247+
248+
/// Check if two ranges overlap by line (ignoring character positions).
249+
pub fn overlaps_lines(&self, other: &Range) -> bool {
250+
self.start.line <= other.end.line && self.end.line >= other.start.line
251+
}
247252
}
248253

249254
/// A lexical range within a specific source file.

vhdl_lang/src/project.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -335,8 +335,8 @@ impl Project {
335335
self.root.find_all_references_in_source(source, ent)
336336
}
337337

338-
/// Collect all (position, entity) pairs in a source file for semantic token support.
339-
pub fn semantic_tokens(&self, source: &Source) -> Vec<(SrcPos, EntRef<'_>)> {
338+
/// Collect all (position, entity) pairs in a source file.
339+
pub fn find_all_entity_references(&self, source: &Source) -> Vec<(SrcPos, EntRef<'_>)> {
340340
use crate::ast::search::SemanticTokenCollector;
341341
let mut collector = SemanticTokenCollector::new(&self.root, source);
342342
let _ = self.root.search_source(source, &mut collector);

vhdl_ls/src/vhdl_server.rs

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ pub struct VHDLServer {
6464
use_external_config: bool,
6565
project: Project,
6666
diagnostic_cache: FnvHashMap<Url, Vec<vhdl_lang::Diagnostic>>,
67-
semantic_token_cache: FnvHashMap<Url, Vec<(vhdl_lang::Range, u32, u32)>>,
67+
semantic_token_cache: FnvHashMap<Url, Vec<semantic_tokens::CachedToken>>,
6868
init_params: Option<InitializeParams>,
6969
config_file: Option<PathBuf>,
7070
severity_map: SeverityMap,
@@ -1017,8 +1017,15 @@ lib.files = [
10171017
)
10181018
}
10191019

1020-
/// Decode delta-encoded semantic tokens to (line, start, length, token_type, modifiers).
1021-
fn decode_semantic_tokens(tokens: &[SemanticToken]) -> Vec<(u32, u32, u32, u32, u32)> {
1020+
struct DecodedToken {
1021+
line: u32,
1022+
start: u32,
1023+
length: u32,
1024+
token_type: u32,
1025+
modifiers: u32,
1026+
}
1027+
1028+
fn decode_semantic_tokens(tokens: &[SemanticToken]) -> Vec<DecodedToken> {
10221029
let mut result = Vec::new();
10231030
let mut line = 0u32;
10241031
let mut start = 0u32;
@@ -1029,29 +1036,25 @@ lib.files = [
10291036
} else {
10301037
start += tok.delta_start;
10311038
}
1032-
result.push((
1039+
result.push(DecodedToken {
10331040
line,
10341041
start,
1035-
tok.length,
1036-
tok.token_type,
1037-
tok.token_modifiers_bitset,
1038-
));
1042+
length: tok.length,
1043+
token_type: tok.token_type,
1044+
modifiers: tok.token_modifiers_bitset,
1045+
});
10391046
}
10401047
result
10411048
}
10421049

1043-
fn token_at(
1044-
decoded: &[(u32, u32, u32, u32, u32)],
1045-
line: u32,
1046-
character: u32,
1047-
) -> Option<(u32, u32)> {
1050+
fn token_at(decoded: &[DecodedToken], line: u32, character: u32) -> Option<(u32, u32)> {
10481051
decoded
10491052
.iter()
1050-
.find(|(l, s, len, _, _)| *l == line && *s <= character && character < s + len)
1051-
.map(|(_, _, _, tt, m)| (*tt, *m))
1053+
.find(|t| t.line == line && t.start <= character && character < t.start + t.length)
1054+
.map(|t| (t.token_type, t.modifiers))
10521055
}
10531056

1054-
fn get_semantic_tokens(server: &mut VHDLServer, uri: &Url) -> Vec<(u32, u32, u32, u32, u32)> {
1057+
fn get_semantic_tokens(server: &mut VHDLServer, uri: &Url) -> Vec<DecodedToken> {
10551058
let result = server
10561059
.semantic_tokens_full(&SemanticTokensParams {
10571060
text_document: TextDocumentIdentifier { uri: uri.clone() },

vhdl_ls/src/vhdl_server/semantic_tokens.rs

Lines changed: 144 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -3,142 +3,204 @@ use lsp_types::*;
33
use vhdl_lang::ast::ExternalObjectClass;
44
use vhdl_lang::{AnyEntKind, Concurrent, Object, Overloaded, Type};
55

6-
// Semantic token type indices — order must match TOKEN_TYPES
7-
const VARIABLE: u32 = 0;
8-
const PARAMETER: u32 = 1;
9-
const PROPERTY: u32 = 2;
10-
const ENUM_MEMBER: u32 = 3;
11-
const FUNCTION: u32 = 4;
12-
const TYPE: u32 = 5;
13-
const CLASS: u32 = 6;
14-
const NAMESPACE: u32 = 7;
15-
const STRUCT: u32 = 8;
16-
const ENUM: u32 = 9;
6+
/// Generates token type index constants and the TOKEN_TYPES legend array
7+
/// from a single declaration, keeping the two in sync automatically.
8+
macro_rules! define_token_types {
9+
( $( ($const:ident = $lsp_type:expr) ),+ $(,)? ) => {
10+
define_token_types!(@consts 0, $( $const, )+);
11+
12+
pub const TOKEN_TYPES: &[SemanticTokenType] = &[
13+
$( $lsp_type, )+
14+
];
15+
};
16+
17+
// Base case
18+
(@consts $idx:expr, ) => {};
19+
// Recursive case: assign current index, increment for the rest
20+
(@consts $idx:expr, $const:ident, $( $rest:ident, )*) => {
21+
const $const: u32 = $idx;
22+
define_token_types!(@consts ($idx + 1), $( $rest, )*);
23+
};
24+
}
25+
26+
define_token_types! {
27+
(VARIABLE = SemanticTokenType::VARIABLE), // signals, variables, constants, files
28+
(PARAMETER = SemanticTokenType::PARAMETER), // subprogram parameters
29+
(PROPERTY = SemanticTokenType::PROPERTY), // attributes, record fields
30+
(ENUM_MEMBER = SemanticTokenType::ENUM_MEMBER), // enum literals
31+
(FUNCTION = SemanticTokenType::FUNCTION), // functions, procedures
32+
(TYPE = SemanticTokenType::TYPE), // types (general)
33+
(CLASS = SemanticTokenType::CLASS), // protected types, components
34+
(NAMESPACE = SemanticTokenType::NAMESPACE), // libraries, design units, labels
35+
(STRUCT = SemanticTokenType::STRUCT), // record types
36+
(ENUM = SemanticTokenType::ENUM), // enum types
37+
}
1738

1839
// Semantic token modifier bits
1940
const MOD_READONLY: u32 = 1 << 0;
2041

21-
pub const TOKEN_TYPES: &[SemanticTokenType] = &[
22-
SemanticTokenType::VARIABLE, // 0: signals, variables, constants, files
23-
SemanticTokenType::PARAMETER, // 1: subprogram parameters
24-
SemanticTokenType::PROPERTY, // 2: attributes, record fields
25-
SemanticTokenType::ENUM_MEMBER, // 3: enum literals
26-
SemanticTokenType::FUNCTION, // 4: functions, procedures
27-
SemanticTokenType::TYPE, // 5: types (general)
28-
SemanticTokenType::CLASS, // 6: protected types, components
29-
SemanticTokenType::NAMESPACE, // 7: libraries, design units, labels
30-
SemanticTokenType::STRUCT, // 8: record types
31-
SemanticTokenType::ENUM, // 9: enum types
32-
];
33-
3442
pub const TOKEN_MODIFIERS: &[SemanticTokenModifier] = &[
3543
SemanticTokenModifier::READONLY, // bit 0: constants, generics
3644
];
3745

38-
fn object_token(obj: &Object) -> (u32, u32) {
46+
/// Classification of a VHDL entity into an LSP semantic token.
47+
struct TokenClassification {
48+
token_type: u32,
49+
modifiers: u32,
50+
}
51+
52+
/// A resolved semantic token ready for caching and encoding.
53+
pub(crate) struct CachedToken {
54+
pub range: vhdl_lang::Range,
55+
pub token_type: u32,
56+
pub modifiers: u32,
57+
}
58+
59+
fn object_token(obj: &Object) -> TokenClassification {
3960
if obj.is_param() {
40-
return (PARAMETER, 0);
61+
return TokenClassification {
62+
token_type: PARAMETER,
63+
modifiers: 0,
64+
};
4165
}
4266
if obj.is_generic() || obj.is_constant() {
43-
return (VARIABLE, MOD_READONLY);
67+
return TokenClassification {
68+
token_type: VARIABLE,
69+
modifiers: MOD_READONLY,
70+
};
71+
}
72+
TokenClassification {
73+
token_type: VARIABLE,
74+
modifiers: 0,
4475
}
45-
(VARIABLE, 0)
4676
}
4777

48-
fn overloaded_token(o: &Overloaded) -> (u32, u32) {
78+
fn overloaded_token(o: &Overloaded) -> TokenClassification {
4979
match o {
50-
Overloaded::EnumLiteral(_) => (ENUM_MEMBER, 0),
80+
Overloaded::EnumLiteral(_) => TokenClassification {
81+
token_type: ENUM_MEMBER,
82+
modifiers: 0,
83+
},
5184
Overloaded::Alias(inner) => overloaded_token(inner.kind()),
52-
_ => (FUNCTION, 0),
85+
_ => TokenClassification {
86+
token_type: FUNCTION,
87+
modifiers: 0,
88+
},
5389
}
5490
}
5591

56-
fn type_token(t: &Type) -> (u32, u32) {
92+
fn type_token(t: &Type) -> TokenClassification {
5793
match t {
58-
Type::Enum(_) => (ENUM, 0),
59-
Type::Record(_) => (STRUCT, 0),
60-
Type::Protected(..) => (CLASS, 0),
94+
Type::Enum(_) => TokenClassification {
95+
token_type: ENUM,
96+
modifiers: 0,
97+
},
98+
Type::Record(_) => TokenClassification {
99+
token_type: STRUCT,
100+
modifiers: 0,
101+
},
102+
Type::Protected(..) => TokenClassification {
103+
token_type: CLASS,
104+
modifiers: 0,
105+
},
61106
Type::Subtype(sub) => type_token(sub.type_mark().kind()),
62107
Type::Alias(t) => type_token(t.kind()),
63-
_ => (TYPE, 0),
108+
_ => TokenClassification {
109+
token_type: TYPE,
110+
modifiers: 0,
111+
},
64112
}
65113
}
66114

67-
fn to_semantic_token(kind: &AnyEntKind) -> Option<(u32, u32)> {
115+
fn classify(kind: &AnyEntKind) -> Option<TokenClassification> {
68116
let result = match kind {
69117
AnyEntKind::Object(obj) => object_token(obj),
70118
AnyEntKind::DeferredConstant(_)
71119
| AnyEntKind::LoopParameter(_)
72-
| AnyEntKind::PhysicalLiteral(_) => (VARIABLE, MOD_READONLY),
120+
| AnyEntKind::PhysicalLiteral(_) => TokenClassification {
121+
token_type: VARIABLE,
122+
modifiers: MOD_READONLY,
123+
},
73124
AnyEntKind::Overloaded(o) => overloaded_token(o),
74125
AnyEntKind::Type(t) => type_token(t),
75-
AnyEntKind::Component(_) => (CLASS, 0),
76-
AnyEntKind::Attribute(_) | AnyEntKind::ElementDeclaration(_) => (PROPERTY, 0),
77-
AnyEntKind::Library | AnyEntKind::Design(_) => (NAMESPACE, 0),
78-
AnyEntKind::View(_) => (TYPE, 0),
79-
AnyEntKind::File(_) | AnyEntKind::InterfaceFile(_) => (VARIABLE, 0),
126+
AnyEntKind::Component(_) => TokenClassification {
127+
token_type: CLASS,
128+
modifiers: 0,
129+
},
130+
AnyEntKind::Attribute(_) | AnyEntKind::ElementDeclaration(_) => TokenClassification {
131+
token_type: PROPERTY,
132+
modifiers: 0,
133+
},
134+
AnyEntKind::Library | AnyEntKind::Design(_) => TokenClassification {
135+
token_type: NAMESPACE,
136+
modifiers: 0,
137+
},
138+
AnyEntKind::View(_) => TokenClassification {
139+
token_type: TYPE,
140+
modifiers: 0,
141+
},
142+
AnyEntKind::File(_) | AnyEntKind::InterfaceFile(_) => TokenClassification {
143+
token_type: VARIABLE,
144+
modifiers: 0,
145+
},
80146
AnyEntKind::ObjectAlias { base_object, .. } => object_token(base_object.object()),
81147
AnyEntKind::ExternalAlias { class, .. } => match class {
82-
ExternalObjectClass::Constant => (VARIABLE, MOD_READONLY),
83-
_ => (VARIABLE, 0),
148+
ExternalObjectClass::Constant => TokenClassification {
149+
token_type: VARIABLE,
150+
modifiers: MOD_READONLY,
151+
},
152+
_ => TokenClassification {
153+
token_type: VARIABLE,
154+
modifiers: 0,
155+
},
156+
},
157+
AnyEntKind::Concurrent(Some(Concurrent::Instance), _) => TokenClassification {
158+
token_type: CLASS,
159+
modifiers: 0,
84160
},
85-
AnyEntKind::Concurrent(Some(Concurrent::Instance), _) => (CLASS, 0),
86161
AnyEntKind::Concurrent(..) | AnyEntKind::Sequential(..) => return None,
87162
};
88163
Some(result)
89164
}
90165

91-
/// Check if a token overlaps the filter range by line.
92-
/// Character-level precision is not needed as clients request full-line ranges.
93-
fn in_range(token_range: &vhdl_lang::Range, filter: &vhdl_lang::Range) -> bool {
94-
token_range.start.line <= filter.end.line && token_range.end.line >= filter.start.line
95-
}
96-
97166
/// Map and sort raw tokens from the AST walk into cacheable form.
98167
fn map_and_sort(
99-
raw_tokens: Vec<(vhdl_lang::SrcPos, vhdl_lang::EntRef<'_>)>,
100-
) -> Vec<(vhdl_lang::Range, u32, u32)> {
101-
let mut tokens: Vec<_> = raw_tokens
168+
mut raw_tokens: Vec<(vhdl_lang::SrcPos, vhdl_lang::EntRef<'_>)>,
169+
) -> Vec<CachedToken> {
170+
raw_tokens.sort_by(|(pos_a, _), (pos_b, _)| pos_a.cmp(pos_b));
171+
172+
raw_tokens
102173
.into_iter()
103174
.filter_map(|(pos, ent)| {
104-
let (token_type, token_modifiers) = to_semantic_token(ent.kind())?;
105-
let range = pos.range();
106-
Some((range, token_type, token_modifiers))
175+
let cls = classify(ent.kind())?;
176+
Some(CachedToken {
177+
range: pos.range(),
178+
token_type: cls.token_type,
179+
modifiers: cls.modifiers,
180+
})
107181
})
108-
.collect();
109-
110-
tokens.sort_by(|a, b| {
111-
a.0.start
112-
.line
113-
.cmp(&b.0.start.line)
114-
.then(a.0.start.character.cmp(&b.0.start.character))
115-
});
116-
117-
tokens
182+
.collect()
118183
}
119184

120185
/// Delta-encode sorted tokens, optionally filtering to a range.
121-
fn encode(
122-
tokens: &[(vhdl_lang::Range, u32, u32)],
123-
range_filter: Option<&vhdl_lang::Range>,
124-
) -> Vec<SemanticToken> {
186+
fn encode(tokens: &[CachedToken], range_filter: Option<&vhdl_lang::Range>) -> Vec<SemanticToken> {
125187
let mut semantic_tokens = Vec::with_capacity(tokens.len());
126188
let mut prev_line = 0u32;
127189
let mut prev_start = 0u32;
128190

129-
for (range, token_type, token_modifiers) in tokens {
191+
for token in tokens {
130192
if let Some(filter) = range_filter {
131-
if !in_range(range, filter) {
193+
if !token.range.overlaps_lines(filter) {
132194
continue;
133195
}
134196
}
135197

136-
let line = range.start.line;
137-
let start = range.start.character;
138-
if range.start.line != range.end.line {
198+
let line = token.range.start.line;
199+
let start = token.range.start.character;
200+
if token.range.start.line != token.range.end.line {
139201
continue; // Skip multi-line tokens; identifiers never span lines
140202
}
141-
let length = range.end.character - range.start.character;
203+
let length = token.range.end.character - token.range.start.character;
142204

143205
let delta_line = line - prev_line;
144206
let delta_start = if delta_line == 0 {
@@ -151,8 +213,8 @@ fn encode(
151213
delta_line,
152214
delta_start,
153215
length,
154-
token_type: *token_type,
155-
token_modifiers_bitset: *token_modifiers,
216+
token_type: token.token_type,
217+
token_modifiers_bitset: token.modifiers,
156218
});
157219

158220
prev_line = line;
@@ -164,10 +226,10 @@ fn encode(
164226

165227
impl VHDLServer {
166228
/// Get or compute the cached semantic tokens for a file.
167-
fn cached_semantic_tokens(&mut self, uri: &Url) -> Option<&[(vhdl_lang::Range, u32, u32)]> {
229+
fn cached_semantic_tokens(&mut self, uri: &Url) -> Option<&[CachedToken]> {
168230
if !self.semantic_token_cache.contains_key(uri) {
169231
let source = self.project.get_source(&uri_to_file_name(uri))?;
170-
let raw_tokens = self.project.semantic_tokens(&source);
232+
let raw_tokens = self.project.find_all_entity_references(&source);
171233
let tokens = map_and_sort(raw_tokens);
172234
self.semantic_token_cache.insert(uri.clone(), tokens);
173235
}

0 commit comments

Comments
 (0)