Skip to content

Commit 48581bb

Browse files
committed
Add semantic tokens for undef and unused labels
1 parent abe3a61 commit 48581bb

File tree

6 files changed

+221
-18
lines changed

6 files changed

+221
-18
lines changed

Cargo.lock

Lines changed: 15 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ doctest = false
3636
[dependencies]
3737
anyhow = "1.0.69"
3838
chrono = { version = "0.4.23", default-features = false, features = ["std"] }
39-
clap = { version = "4.1.6", features = ["derive"] }
39+
clap = { version = "4.1.8", features = ["derive"] }
4040
crossbeam-channel = "0.5.6"
4141
dashmap = "5.4.0"
4242
dirs = "4.0.0"
@@ -69,6 +69,7 @@ thiserror = "1.0.38"
6969
threadpool = "1.8.1"
7070
titlecase = "2.2.1"
7171
unicode-normalization = "0.1.22"
72+
bitflags = "2.0.1"
7273

7374
[dependencies.salsa]
7475
git = "https://github.com/salsa-rs/salsa"

src/features.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@ pub mod inlay_hint;
1010
pub mod link;
1111
pub mod reference;
1212
pub mod rename;
13+
pub mod semantic_tokens;
1314
pub mod symbol;
1415
pub mod workspace_command;

src/features/semantic_tokens.rs

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
mod label;
2+
3+
use bitflags::bitflags;
4+
use lsp_types::{
5+
Position, Range, SemanticToken, SemanticTokenModifier, SemanticTokenType, SemanticTokens,
6+
SemanticTokensLegend, Url,
7+
};
8+
use rowan::TextRange;
9+
10+
use crate::{
11+
db::Workspace,
12+
util::{line_index::LineIndex, line_index_ext::LineIndexExt},
13+
Db,
14+
};
15+
16+
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
17+
#[repr(u32)]
18+
pub enum TokenKind {
19+
Label = 0,
20+
}
21+
22+
bitflags! {
23+
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
24+
pub struct TokenModifiers: u32 {
25+
const NONE = 0;
26+
const UNDEFINED = 1;
27+
const UNUSED = 2;
28+
}
29+
}
30+
31+
pub fn legend() -> SemanticTokensLegend {
32+
SemanticTokensLegend {
33+
token_types: vec![SemanticTokenType::new("label")],
34+
token_modifiers: vec![
35+
SemanticTokenModifier::new("undefined"),
36+
SemanticTokenModifier::new("unused"),
37+
],
38+
}
39+
}
40+
41+
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
42+
pub struct Token {
43+
pub range: TextRange,
44+
pub kind: TokenKind,
45+
pub modifiers: TokenModifiers,
46+
}
47+
48+
#[derive(Debug, Default)]
49+
pub struct TokenBuilder {
50+
tokens: Vec<Token>,
51+
}
52+
53+
impl TokenBuilder {
54+
pub fn push(&mut self, token: Token) {
55+
self.tokens.push(token);
56+
}
57+
58+
pub fn finish(mut self, line_index: &LineIndex) -> SemanticTokens {
59+
let mut data = Vec::new();
60+
61+
self.tokens.sort_by_key(|token| token.range.start());
62+
63+
let mut last_pos = Position::new(0, 0);
64+
for token in self.tokens {
65+
let range = line_index.line_col_lsp_range(token.range);
66+
let length = range.end.character - range.start.character;
67+
let token_type = token.kind as u32;
68+
let token_modifiers_bitset = token.modifiers.bits();
69+
70+
if range.start.line > last_pos.line {
71+
let delta_line = range.start.line - last_pos.line;
72+
let delta_start = range.start.character;
73+
data.push(SemanticToken {
74+
delta_line,
75+
delta_start,
76+
length,
77+
token_type,
78+
token_modifiers_bitset,
79+
});
80+
} else {
81+
let delta_line = 0;
82+
let delta_start = last_pos.character - range.start.character;
83+
data.push(SemanticToken {
84+
delta_line,
85+
delta_start,
86+
length,
87+
token_type,
88+
token_modifiers_bitset,
89+
});
90+
}
91+
92+
last_pos = range.end;
93+
}
94+
95+
SemanticTokens {
96+
result_id: None,
97+
data,
98+
}
99+
}
100+
}
101+
102+
pub fn find_all(db: &dyn Db, uri: &Url, viewport: Range) -> Option<SemanticTokens> {
103+
let workspace = Workspace::get(db);
104+
let document = workspace.lookup_uri(db, uri)?;
105+
let viewport = document.line_index(db).offset_lsp_range(viewport);
106+
let mut builder = TokenBuilder::default();
107+
label::find(db, document, viewport, &mut builder);
108+
Some(builder.finish(document.line_index(db)))
109+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
use rowan::TextRange;
2+
3+
use crate::{
4+
db::{analysis::label, Document, Workspace},
5+
Db,
6+
};
7+
8+
use super::{Token, TokenBuilder, TokenKind, TokenModifiers};
9+
10+
pub fn find(
11+
db: &dyn Db,
12+
document: Document,
13+
viewport: TextRange,
14+
builder: &mut TokenBuilder,
15+
) -> Option<()> {
16+
let labels = document.parse(db).as_tex()?.analyze(db).labels(db);
17+
for label in labels
18+
.iter()
19+
.filter(|label| viewport.intersect(label.range(db)).is_some())
20+
{
21+
let name = label.name(db).text(db);
22+
let modifiers = match label.origin(db) {
23+
label::Origin::Definition(_) => {
24+
if !is_label_referenced(db, document, name) {
25+
TokenModifiers::UNUSED
26+
} else {
27+
TokenModifiers::NONE
28+
}
29+
}
30+
label::Origin::Reference(_) | label::Origin::ReferenceRange(_) => {
31+
if !is_label_defined(db, document, name) {
32+
TokenModifiers::UNDEFINED
33+
} else {
34+
TokenModifiers::NONE
35+
}
36+
}
37+
};
38+
39+
let range = label.range(db);
40+
builder.push(Token {
41+
range,
42+
kind: TokenKind::Label,
43+
modifiers,
44+
});
45+
}
46+
47+
Some(())
48+
}
49+
50+
fn is_label_defined(db: &dyn Db, child: Document, name: &str) -> bool {
51+
Workspace::get(db)
52+
.related(db, child)
53+
.iter()
54+
.filter_map(|document| document.parse(db).as_tex())
55+
.flat_map(|data| data.analyze(db).labels(db))
56+
.filter(|label| matches!(label.origin(db), label::Origin::Definition(_)))
57+
.any(|label| label.name(db).text(db) == name)
58+
}
59+
60+
fn is_label_referenced(db: &dyn Db, child: Document, name: &str) -> bool {
61+
Workspace::get(db)
62+
.related(db, child)
63+
.iter()
64+
.filter_map(|document| document.parse(db).as_tex())
65+
.flat_map(|data| data.analyze(db).labels(db))
66+
.filter(|label| {
67+
matches!(
68+
label.origin(db),
69+
label::Origin::Reference(_) | label::Origin::ReferenceRange(_)
70+
)
71+
})
72+
.any(|label| label.name(db).text(db) == name)
73+
}

src/server.rs

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ use crate::{
2828
build::{self, BuildParams, BuildResult, BuildStatus},
2929
completion::{self, builder::CompletionItemData},
3030
definition, folding, formatting, forward_search, highlight, hover, inlay_hint, link,
31-
reference, rename, symbol,
31+
reference, rename, semantic_tokens, symbol,
3232
workspace_command::{change_environment, clean, dep_graph},
3333
},
3434
normalize_uri,
@@ -179,6 +179,14 @@ impl Server {
179179
..Default::default()
180180
}),
181181
inlay_hint_provider: Some(OneOf::Left(true)),
182+
semantic_tokens_provider: Some(
183+
SemanticTokensServerCapabilities::SemanticTokensOptions(SemanticTokensOptions {
184+
work_done_progress_options: Default::default(),
185+
legend: semantic_tokens::legend(),
186+
range: Some(true),
187+
full: Some(SemanticTokensFullOptions::Bool(false)),
188+
}),
189+
),
182190
..ServerCapabilities::default()
183191
}
184192
}
@@ -707,14 +715,6 @@ impl Server {
707715
Ok(())
708716
}
709717

710-
fn semantic_tokens_range(
711-
&self,
712-
_id: RequestId,
713-
_params: SemanticTokensRangeParams,
714-
) -> Result<()> {
715-
Ok(())
716-
}
717-
718718
fn build(&mut self, id: RequestId, params: BuildParams) -> Result<()> {
719719
let mut uri = params.text_document.uri;
720720
normalize_uri(&mut uri);
@@ -814,6 +814,18 @@ impl Server {
814814
Ok(())
815815
}
816816

817+
fn semantic_tokens_range(
818+
&mut self,
819+
id: RequestId,
820+
params: SemanticTokensRangeParams,
821+
) -> Result<()> {
822+
self.run_with_db(id, move |db| {
823+
semantic_tokens::find_all(db, &params.text_document.uri, params.range)
824+
});
825+
826+
Ok(())
827+
}
828+
817829
fn handle_file_event(&mut self, event: notify::Event) {
818830
let mut changed = false;
819831

0 commit comments

Comments
 (0)