feat: enable LSP semantic token highlighting by default
This commit is contained in:
+166
-40
@@ -390,50 +390,73 @@ impl EditorSession {
|
||||
return Ok(false);
|
||||
}
|
||||
// Validate the whole applicable annotation batch before changing any buffer.
|
||||
for effect in &update.effects {
|
||||
match effect {
|
||||
UpdateEffect::Diagnostics {
|
||||
buffer_id,
|
||||
revision,
|
||||
items,
|
||||
} => {
|
||||
self.editor
|
||||
.validate_diagnostics(*buffer_id, *revision, items)?;
|
||||
}
|
||||
UpdateEffect::Navigate {
|
||||
buffer_id,
|
||||
revision,
|
||||
path,
|
||||
..
|
||||
} => {
|
||||
let source = InvocationSource {
|
||||
buffer_id: *buffer_id,
|
||||
revision: *revision,
|
||||
};
|
||||
if self.request_is_current(owner, update.request_id, source)
|
||||
&& (path.as_os_str().is_empty()
|
||||
|| path.to_string_lossy().chars().any(char::is_control))
|
||||
{
|
||||
return Err(
|
||||
"Navigation path must be nonempty and contain no controls".into()
|
||||
);
|
||||
let validation = (|| -> Result<(), String> {
|
||||
for effect in &update.effects {
|
||||
match effect {
|
||||
UpdateEffect::Diagnostics {
|
||||
buffer_id,
|
||||
revision,
|
||||
items,
|
||||
} => {
|
||||
self.editor
|
||||
.validate_diagnostics(*buffer_id, *revision, items)?;
|
||||
}
|
||||
}
|
||||
UpdateEffect::Hover {
|
||||
buffer_id,
|
||||
revision,
|
||||
text,
|
||||
} => {
|
||||
let source = InvocationSource {
|
||||
buffer_id: *buffer_id,
|
||||
revision: *revision,
|
||||
};
|
||||
if self.hover_is_current(owner, update.request_id, source) {
|
||||
crate::editor::validate_hover_text(text)?;
|
||||
UpdateEffect::SemanticTokens {
|
||||
buffer_id,
|
||||
revision,
|
||||
tokens,
|
||||
} => {
|
||||
if owner != "core" {
|
||||
return Err("Semantic tokens are native LSP updates only".into());
|
||||
}
|
||||
self.editor.validate_semantic_tokens(
|
||||
*buffer_id,
|
||||
*revision,
|
||||
tokens.as_deref(),
|
||||
)?;
|
||||
}
|
||||
UpdateEffect::Navigate {
|
||||
buffer_id,
|
||||
revision,
|
||||
path,
|
||||
..
|
||||
} => {
|
||||
let source = InvocationSource {
|
||||
buffer_id: *buffer_id,
|
||||
revision: *revision,
|
||||
};
|
||||
if self.request_is_current(owner, update.request_id, source)
|
||||
&& (path.as_os_str().is_empty()
|
||||
|| path.to_string_lossy().chars().any(char::is_control))
|
||||
{
|
||||
return Err(
|
||||
"Navigation path must be nonempty and contain no controls".into()
|
||||
);
|
||||
}
|
||||
}
|
||||
UpdateEffect::Hover {
|
||||
buffer_id,
|
||||
revision,
|
||||
text,
|
||||
} => {
|
||||
let source = InvocationSource {
|
||||
buffer_id: *buffer_id,
|
||||
revision: *revision,
|
||||
};
|
||||
if self.hover_is_current(owner, update.request_id, source) {
|
||||
crate::editor::validate_hover_text(text)?;
|
||||
}
|
||||
}
|
||||
UpdateEffect::Message { .. } => {}
|
||||
}
|
||||
UpdateEffect::Message { .. } => {}
|
||||
}
|
||||
Ok(())
|
||||
})();
|
||||
if let Err(error) = validation {
|
||||
if owner == "core" {
|
||||
self.editor.clear_semantic_tokens();
|
||||
}
|
||||
return Err(error);
|
||||
}
|
||||
let mut changed = false;
|
||||
for effect in update.effects {
|
||||
@@ -447,6 +470,15 @@ impl EditorSession {
|
||||
.editor
|
||||
.replace_validated_diagnostics(owner, buffer_id, revision, items);
|
||||
}
|
||||
UpdateEffect::SemanticTokens {
|
||||
buffer_id,
|
||||
revision,
|
||||
tokens,
|
||||
} => {
|
||||
changed |= self
|
||||
.editor
|
||||
.replace_validated_semantic_tokens(buffer_id, revision, tokens);
|
||||
}
|
||||
UpdateEffect::Navigate {
|
||||
buffer_id,
|
||||
revision,
|
||||
@@ -595,6 +627,100 @@ mod tests {
|
||||
|
||||
static NEXT_FIXTURE: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
#[test]
|
||||
fn native_semantic_batches_are_atomic_and_malformed_updates_clear_hidden_overlays() {
|
||||
use crate::plugin::{SemanticToken, SemanticTokenKind};
|
||||
use std::sync::Arc;
|
||||
let fixture = Fixture::new();
|
||||
fixture.file("buffer.txt", "source");
|
||||
let target = fixture.file("target.txt", "target");
|
||||
let mut session = fixture.open("");
|
||||
let token = SemanticToken {
|
||||
start: TextPosition { row: 0, column: 0 },
|
||||
end: TextPosition { row: 0, column: 1 },
|
||||
kind: SemanticTokenKind::Variable,
|
||||
modifiers: 0,
|
||||
};
|
||||
let tokens: Arc<[SemanticToken]> = Arc::from([token]);
|
||||
let semantic = |buffer_id, revision, tokens| UpdateEffect::SemanticTokens {
|
||||
buffer_id,
|
||||
revision,
|
||||
tokens,
|
||||
};
|
||||
let update = |effects| PluginUpdate {
|
||||
request_id: None,
|
||||
effects,
|
||||
};
|
||||
session
|
||||
.apply_update("core", update(vec![semantic(1, 0, Some(tokens.clone()))]))
|
||||
.unwrap();
|
||||
session.execute(&format!("edit {}", target.display()));
|
||||
session
|
||||
.apply_update("core", update(vec![semantic(2, 0, Some(tokens.clone()))]))
|
||||
.unwrap();
|
||||
let diagnostic = crate::plugin::Diagnostic {
|
||||
start: token.start,
|
||||
end: token.end,
|
||||
severity: crate::plugin::DiagnosticSeverity::Warning,
|
||||
message: "unchanged".into(),
|
||||
};
|
||||
session
|
||||
.editor
|
||||
.replace_diagnostics("plugin", 2, 0, vec![diagnostic.clone()])
|
||||
.unwrap();
|
||||
let malformed: Arc<[SemanticToken]> = Arc::from([SemanticToken {
|
||||
end: TextPosition { row: 99, column: 0 },
|
||||
..token
|
||||
}]);
|
||||
let before_message = session.editor.message().to_owned();
|
||||
let result = session.apply_update(
|
||||
"core",
|
||||
update(vec![
|
||||
UpdateEffect::Message {
|
||||
text: "must not apply".into(),
|
||||
},
|
||||
UpdateEffect::Diagnostics {
|
||||
buffer_id: 2,
|
||||
revision: 0,
|
||||
items: vec![diagnostic.clone()],
|
||||
},
|
||||
semantic(1, 0, Some(tokens.clone())),
|
||||
semantic(2, 0, Some(malformed.clone())),
|
||||
]),
|
||||
);
|
||||
assert!(result.is_err());
|
||||
assert_eq!(session.editor.message(), before_message);
|
||||
assert!(session.editor.semantic_tokens().is_empty());
|
||||
assert_eq!(session.editor.diagnostics().count(), 1);
|
||||
session.execute("buffer 1");
|
||||
assert!(session.editor.semantic_tokens().is_empty());
|
||||
assert!(
|
||||
session
|
||||
.apply_update(
|
||||
"core",
|
||||
update(vec![
|
||||
semantic(999, 0, Some(malformed.clone())),
|
||||
semantic(1, 99, Some(malformed)),
|
||||
semantic(1, 0, Some(tokens.clone())),
|
||||
])
|
||||
)
|
||||
.unwrap()
|
||||
);
|
||||
assert!(std::ptr::eq(
|
||||
session.editor.semantic_tokens(),
|
||||
tokens.as_ref()
|
||||
));
|
||||
assert_eq!(session.editor.lines(), ["source"]);
|
||||
assert_eq!(session.editor.text_revision(), 0);
|
||||
assert!(!session.editor.dirty());
|
||||
assert!(
|
||||
session
|
||||
.apply_update("core", update(vec![semantic(1, 0, None)]))
|
||||
.unwrap()
|
||||
);
|
||||
assert!(session.editor.semantic_tokens().is_empty());
|
||||
}
|
||||
|
||||
struct Fixture(PathBuf);
|
||||
|
||||
impl Fixture {
|
||||
|
||||
Reference in New Issue
Block a user