feat: preserve diff context during LSP navigation
This commit is contained in:
+132
-6
@@ -166,6 +166,7 @@ pub struct EditorSession {
|
||||
lsp_request_id: u64,
|
||||
lsp_source_cursor: Option<Cursor>,
|
||||
lsp_source_mode: Mode,
|
||||
lsp_source_view: Option<(usize, Option<u64>)>,
|
||||
pending_hover: bool,
|
||||
pending_completion: bool,
|
||||
hover_owner: Option<String>,
|
||||
@@ -264,6 +265,7 @@ impl EditorSession {
|
||||
lsp_request_id: 0,
|
||||
lsp_source_cursor: None,
|
||||
lsp_source_mode: Mode::Normal,
|
||||
lsp_source_view: None,
|
||||
pending_hover: false,
|
||||
pending_completion: false,
|
||||
hover_owner: None,
|
||||
@@ -299,10 +301,12 @@ impl EditorSession {
|
||||
|
||||
pub(crate) fn scroll_diff(&mut self, focus: Focus, down: bool) {
|
||||
self.editor.scroll_diff(focus, down);
|
||||
self.synchronize_documents();
|
||||
}
|
||||
|
||||
pub(crate) fn scroll_diff_horizontal(&mut self, focus: Focus, right: bool, width: usize) {
|
||||
self.editor.scroll_diff_horizontal(focus, right, width);
|
||||
self.synchronize_documents();
|
||||
}
|
||||
|
||||
pub fn handle(&mut self, input: Input) {
|
||||
@@ -401,11 +405,16 @@ impl EditorSession {
|
||||
|
||||
fn dispatch(&mut self, invocation: Invocation) {
|
||||
if invocation.plugin == "core" {
|
||||
if !self.editor.document_actions_available()
|
||||
|| (invocation.command == "lsp.completion" && self.editor.mutation_blocked())
|
||||
{
|
||||
return;
|
||||
}
|
||||
let result = match invocation.command.as_str() {
|
||||
"lsp.definition" | "lsp.hover" | "lsp.completion" => {
|
||||
if let Some(id) = self.lsp_request_id.checked_add(1) {
|
||||
self.lsp_request_id = id;
|
||||
let context = self.editor.context();
|
||||
let context = self.editor.lsp_context();
|
||||
let source = InvocationSource {
|
||||
buffer_id: context.buffer_id,
|
||||
pane_id: self.editor.pane_id(),
|
||||
@@ -414,6 +423,10 @@ impl EditorSession {
|
||||
self.requests.entry("core".into()).or_default().latest = Some((id, source));
|
||||
self.lsp_source_cursor = Some(self.editor.cursor());
|
||||
self.lsp_source_mode = self.editor.mode();
|
||||
self.lsp_source_view = Some((
|
||||
self.editor.tab_id(),
|
||||
self.editor.diff().and_then(|diff| diff.comparison_id),
|
||||
));
|
||||
self.pending_hover = invocation.command == "lsp.hover";
|
||||
self.pending_completion = invocation.command == "lsp.completion";
|
||||
match invocation.command.as_str() {
|
||||
@@ -502,10 +515,17 @@ impl EditorSession {
|
||||
|| source.revision != self.editor.text_revision()
|
||||
|| self.lsp_source_cursor != Some(self.editor.cursor())
|
||||
|| self.editor.mode() != self.lsp_source_mode
|
||||
|| self.lsp_source_view
|
||||
!= Some((
|
||||
self.editor.tab_id(),
|
||||
self.editor.diff().and_then(|diff| diff.comparison_id),
|
||||
))
|
||||
|| !self.editor.document_actions_available()
|
||||
})
|
||||
{
|
||||
requests.latest = None;
|
||||
self.lsp_source_cursor = None;
|
||||
self.lsp_source_view = None;
|
||||
self.pending_hover = false;
|
||||
self.pending_completion = false;
|
||||
}
|
||||
@@ -517,9 +537,15 @@ impl EditorSession {
|
||||
if !self.editor.should_quit() {
|
||||
for document in self.editor.interaction_documents() {
|
||||
let path = document.path.map(Path::to_string_lossy);
|
||||
let root = self
|
||||
.editor
|
||||
.interaction_document_root(document.id)
|
||||
.to_string_lossy();
|
||||
let previous = self.documents.get(&document.id);
|
||||
let renamed = previous
|
||||
.is_some_and(|previous| previous.snapshot.path.as_deref() != path.as_deref());
|
||||
let renamed = previous.is_some_and(|previous| {
|
||||
previous.snapshot.path.as_deref() != path.as_deref()
|
||||
|| previous.snapshot.root.as_str() != root.as_ref()
|
||||
});
|
||||
if renamed {
|
||||
events.push(DocumentEvent {
|
||||
event: DocumentEventKind::Close,
|
||||
@@ -534,7 +560,7 @@ impl EditorSession {
|
||||
id: document.id,
|
||||
revision: document.revision,
|
||||
path: path.map(|path| path.into_owned()),
|
||||
root: self.editor.project_root().to_string_lossy().into_owned(),
|
||||
root: root.into_owned(),
|
||||
text: document.lines.join("\n"),
|
||||
};
|
||||
events.push(DocumentEvent {
|
||||
@@ -636,14 +662,19 @@ impl EditorSession {
|
||||
request_id: Option<u64>,
|
||||
source: InvocationSource,
|
||||
) -> bool {
|
||||
if self.editor.diff().is_some() && !self.editor.diff_interacting() {
|
||||
if !self.editor.document_actions_available() {
|
||||
return false;
|
||||
}
|
||||
self.editor.buffer_id() == source.buffer_id
|
||||
&& self.source_is_current(source)
|
||||
&& (owner != "core"
|
||||
|| (self.lsp_source_cursor == Some(self.editor.cursor())
|
||||
&& self.editor.mode() == self.lsp_source_mode))
|
||||
&& self.editor.mode() == self.lsp_source_mode
|
||||
&& self.lsp_source_view
|
||||
== Some((
|
||||
self.editor.tab_id(),
|
||||
self.editor.diff().and_then(|diff| diff.comparison_id),
|
||||
))))
|
||||
&& self
|
||||
.requests
|
||||
.get(owner)
|
||||
@@ -755,7 +786,16 @@ impl EditorSession {
|
||||
revision,
|
||||
path,
|
||||
..
|
||||
}
|
||||
| UpdateEffect::NavigateUtf16 {
|
||||
buffer_id,
|
||||
revision,
|
||||
path,
|
||||
..
|
||||
} => {
|
||||
if owner != "core" && matches!(effect, UpdateEffect::NavigateUtf16 { .. }) {
|
||||
return Err("UTF-16 navigation is a native LSP update only".into());
|
||||
}
|
||||
let source = InvocationSource {
|
||||
buffer_id: *buffer_id,
|
||||
pane_id: self.editor.pane_id(),
|
||||
@@ -827,6 +867,26 @@ impl EditorSession {
|
||||
.editor
|
||||
.replace_validated_semantic_tokens(buffer_id, revision, tokens);
|
||||
}
|
||||
UpdateEffect::NavigateUtf16 {
|
||||
buffer_id,
|
||||
revision,
|
||||
path,
|
||||
range,
|
||||
target_range,
|
||||
} => {
|
||||
let source = InvocationSource {
|
||||
buffer_id,
|
||||
pane_id: self.editor.pane_id(),
|
||||
revision,
|
||||
};
|
||||
if self.request_is_current(owner, update.request_id, source) {
|
||||
if let Err(error) = self.editor.navigate_lsp_to(path, range, target_range) {
|
||||
self.editor
|
||||
.set_message(format!("LSP: definition failed: {error}"));
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
UpdateEffect::Navigate {
|
||||
buffer_id,
|
||||
revision,
|
||||
@@ -1050,6 +1110,68 @@ mod tests {
|
||||
sync::atomic::{AtomicU64, Ordering},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn native_navigation_resolves_utf16_and_validates_full_ranges_atomically() {
|
||||
use crate::plugin::Utf16Range;
|
||||
|
||||
let fixture = Fixture::new();
|
||||
let source = fixture.file("buffer.txt", "source");
|
||||
let target = fixture.file("target.txt", "A😀e\u{301}Z\n");
|
||||
let mut session = EditorSession::open(Some(source.clone()), vec![], None).unwrap();
|
||||
session.dispatch(Invocation {
|
||||
plugin: "core".into(),
|
||||
command: "lsp.definition".into(),
|
||||
args: String::new(),
|
||||
});
|
||||
let buffer_id = session.editor.buffer_id();
|
||||
let revision = session.editor.text_revision();
|
||||
let request_id = session.lsp_request_id;
|
||||
let span = Utf16Range {
|
||||
start: (0, 5),
|
||||
end: (0, 6),
|
||||
};
|
||||
let invalid = Utf16Range {
|
||||
end: (9, 0),
|
||||
..span
|
||||
};
|
||||
let update = |range, target_range| PluginUpdate {
|
||||
request_id: Some(request_id),
|
||||
effects: vec![UpdateEffect::NavigateUtf16 {
|
||||
buffer_id,
|
||||
revision,
|
||||
path: target.clone(),
|
||||
range,
|
||||
target_range,
|
||||
}],
|
||||
};
|
||||
for (range, target_range) in [(invalid, None), (span, Some(invalid))] {
|
||||
assert!(
|
||||
session
|
||||
.apply_update("core", update(range, target_range))
|
||||
.unwrap()
|
||||
);
|
||||
assert_eq!(session.editor.buffer_id(), buffer_id);
|
||||
assert_eq!(session.editor.path(), Some(source.as_path()));
|
||||
assert_eq!(session.editor.buffer_count(), 1);
|
||||
assert_eq!(session.editor.tab_count(), 1);
|
||||
assert_eq!(session.editor.cursor(), Cursor::default());
|
||||
assert!(session.editor.message().contains("definition failed"));
|
||||
}
|
||||
assert!(session.apply_update("fixture", update(span, None)).is_err());
|
||||
assert_eq!(session.editor.buffer_id(), buffer_id);
|
||||
assert!(
|
||||
session
|
||||
.apply_update("core", update(span, Some(span)))
|
||||
.unwrap()
|
||||
);
|
||||
assert_eq!(session.editor.path(), Some(target.as_path()));
|
||||
assert_eq!(session.editor.cursor(), Cursor { row: 0, column: 3 });
|
||||
assert_eq!(session.editor.tab_count(), 1);
|
||||
session.handle(Input::Ctrl('o'));
|
||||
assert_eq!(session.editor.path(), Some(source.as_path()));
|
||||
assert_eq!(session.editor.cursor(), Cursor::default());
|
||||
}
|
||||
|
||||
static NEXT_FIXTURE: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
#[test]
|
||||
@@ -1087,6 +1209,10 @@ mod tests {
|
||||
));
|
||||
session.lsp_source_cursor = Some(session.editor.cursor());
|
||||
session.lsp_source_mode = session.editor.mode();
|
||||
session.lsp_source_view = Some((
|
||||
session.editor.tab_id(),
|
||||
session.editor.diff().and_then(|diff| diff.comparison_id),
|
||||
));
|
||||
session.editor_mut().with_render_pane(1, |pane| {
|
||||
assert_eq!(pane.pane_id(), 1);
|
||||
assert_eq!(pane.lines(), ["source"]);
|
||||
|
||||
Reference in New Issue
Block a user