fix: preserve semantic roles in terminal palette highlighting

This commit is contained in:
2026-10-09 23:51:48 +02:00
parent 9a96706c36
commit d310be6e6e
3 changed files with 69 additions and 25 deletions
+67 -23
View File
@@ -1080,9 +1080,9 @@ fn semantic_style(token: &SemanticToken) -> Style {
use SemanticTokenKind::*;
let readonly = token.modifiers & SemanticTokenModifier::Readonly as u16 != 0;
let color = match token.kind {
Namespace | Type | Class | Enum | Interface | Struct | TypeParameter => Color::Cyan,
Namespace => Color::Gray,
Type | Class | Enum | Interface | Struct | TypeParameter => Color::Cyan,
Function | Method | Event | Macro => Color::LightBlue,
Parameter | Variable | Property if readonly => Color::Yellow,
Parameter | Variable => Color::LightCyan,
Property => Color::Blue,
EnumMember | Number => Color::Yellow,
@@ -1091,6 +1091,9 @@ fn semantic_style(token: &SemanticToken) -> Style {
Comment => Color::DarkGray,
};
let mut style = Style::default().fg(color);
if readonly && matches!(token.kind, Parameter | Variable | Property) {
style = style.add_modifier(ratatui::style::Modifier::ITALIC);
}
if token.modifiers
& (SemanticTokenModifier::Declaration as u16 | SemanticTokenModifier::Definition as u16)
!= 0
@@ -1655,6 +1658,60 @@ end
}
}
#[test]
fn semantic_roles_remain_distinct_when_values_and_fields_are_readonly() {
let mut editor = Editor::open(None, vec![]).unwrap();
editor
.apply(vec![crate::editor::Effect::Replace(
"ns Type local frozen field fixed param frozenparam".into(),
)])
.unwrap();
let readonly = SemanticTokenModifier::Readonly as u16;
editor
.replace_semantic_tokens(
editor.buffer_id(),
editor.text_revision(),
Some(Arc::from([
semantic_token(0, 0, 2, SemanticTokenKind::Namespace, 0),
semantic_token(0, 3, 7, SemanticTokenKind::Type, 0),
semantic_token(0, 8, 13, SemanticTokenKind::Variable, 0),
semantic_token(0, 14, 20, SemanticTokenKind::Variable, readonly),
semantic_token(0, 21, 26, SemanticTokenKind::Property, 0),
semantic_token(0, 27, 32, SemanticTokenKind::Property, readonly),
semantic_token(0, 33, 38, SemanticTokenKind::Parameter, 0),
semantic_token(0, 39, 50, SemanticTokenKind::Parameter, readonly),
])),
)
.unwrap();
let mut terminal = Terminal::new(ratatui::backend::TestBackend::new(60, 4)).unwrap();
terminal
.draw(|frame| BufferViewports::default().draw(frame, &editor))
.unwrap();
let buffer = terminal.backend().buffer();
let foregrounds = [0, 3, 8, 14, 21, 27, 33, 39].map(|column| buffer[(column + 2, 0)].fg);
assert_ne!(foregrounds[0], foregrounds[1]);
assert_ne!(foregrounds[1], foregrounds[2]);
assert_ne!(foregrounds[2], foregrounds[4]);
assert_eq!(foregrounds[2], foregrounds[3]);
assert_eq!(foregrounds[4], foregrounds[5]);
assert_eq!(foregrounds[6], foregrounds[7]);
for (column, is_readonly) in [
(0, false),
(3, false),
(8, false),
(14, true),
(21, false),
(27, true),
(33, false),
(39, true),
] {
assert_eq!(
buffer[(column + 2, 0)].modifier.contains(Modifier::ITALIC),
is_readonly,
);
}
}
#[test]
fn semantic_colors_modifiers_and_syntax_fallback_cover_whole_graphemes_and_clipped_tabs() {
let directory = Directory::new();
@@ -1700,23 +1757,12 @@ end
)
.unwrap();
let mut syntax = SyntaxCache::new();
for (offset, width, caret, colors) in [
(2, 5, 4, vec![Color::Yellow; 5]),
(5, 2, 4, vec![Color::Reset, Color::Yellow]),
(7, 2, 5, vec![Color::Yellow; 2]),
(8, 2, 6, vec![Color::Reset, Color::DarkGray]),
(
0,
5,
2,
vec![
Color::DarkGray,
Color::DarkGray,
Color::Yellow,
Color::Yellow,
Color::Reset,
],
),
for (offset, width, caret, coverage) in [
(2, 5, 4, vec![true; 5]),
(5, 2, 4, vec![false, true]),
(7, 2, 5, vec![true; 2]),
(8, 2, 6, vec![false; 2]),
(0, 5, 2, vec![false, false, true, true, false]),
] {
editor.handle(Input::Home);
for _ in 0..caret {
@@ -1743,12 +1789,10 @@ end
Paragraph::new(visible_line(source, offset, usize::from(width)))
.render(text_area, &mut expected);
let buffer = terminal.backend().buffer();
for (cell, color) in colors.into_iter().enumerate() {
for (cell, semantic_cell) in coverage.into_iter().enumerate() {
let position = (text_area.x + cell as u16, text_area.y);
assert_eq!(buffer[position].symbol(), expected[position].symbol());
assert_eq!(rendered[position].fg, color, "left={offset}, cell={cell}");
assert_eq!(rendered[position].bg, Color::Reset);
let semantic_cell = color == Color::Yellow;
assert_eq!(
rendered[position].modifier.contains(Modifier::BOLD),
semantic_cell
@@ -1766,7 +1810,7 @@ end
}
assert_eq!(
rendered[position].modifier.contains(Modifier::ITALIC),
offset == 0 && cell == 0
semantic_cell || (offset == 0 && cell == 0)
);
}
assert_eq!(buffer[(area.x, area.y)].fg, Color::Yellow);