feat: add panes and shared-buffer splits

This commit is contained in:
2026-10-11 16:49:31 +02:00
parent 0b7d4d2c1f
commit ad9b214b8f
9 changed files with 2997 additions and 778 deletions
+75 -10
View File
@@ -145,6 +145,7 @@ struct TrackedDocument {
#[derive(Clone, Copy)]
struct InvocationSource {
buffer_id: usize,
pane_id: usize,
revision: u64,
}
@@ -270,6 +271,11 @@ impl EditorSession {
&self.editor
}
/// Rendering-only mutable access; document synchronization stays in session dispatch.
pub(crate) fn editor_mut(&mut self) -> &mut Editor {
&mut self.editor
}
pub(crate) fn set_diff_viewport(
&mut self,
comparison_id: u64,
@@ -313,11 +319,14 @@ impl EditorSession {
}
fn start_diff_request(&mut self) {
if self.editor.diff().is_none() {
if self.editor.workspace_diff().is_none() {
self.diff_worker = None;
return;
}
if self.editor.diff().is_some_and(|diff| !diff.loading)
if self
.editor
.workspace_diff()
.is_some_and(|diff| !diff.loading)
&& let Some(worker) = &mut self.diff_worker
&& let Some(id) = worker.pending.take()
{
@@ -328,13 +337,7 @@ impl EditorSession {
return;
};
if self.diff_worker.is_none() {
let root = self
.editor
.path()
.and_then(Path::parent)
.filter(|path| !path.as_os_str().is_empty())
.unwrap_or_else(|| self.editor.project_root())
.to_path_buf();
let root = self.editor.diff_root();
match DiffWorker::start(root) {
Ok(worker) => self.diff_worker = Some(worker),
Err(error) => {
@@ -396,6 +399,7 @@ impl EditorSession {
let context = self.editor.context();
let source = InvocationSource {
buffer_id: context.buffer_id,
pane_id: self.editor.pane_id(),
revision: context.revision,
};
self.requests.entry("core".into()).or_default().latest = Some((id, source));
@@ -431,6 +435,7 @@ impl EditorSession {
.map(|id| {
let source = InvocationSource {
buffer_id: context.buffer_id,
pane_id: self.editor.pane_id(),
revision: context.revision,
};
requests.pending.insert(id, source);
@@ -484,6 +489,7 @@ impl EditorSession {
if let Some(requests) = self.requests.get_mut("core")
&& requests.latest.is_some_and(|(_, source)| {
source.buffer_id != self.editor.buffer_id()
|| source.pane_id != self.editor.pane_id()
|| source.revision != self.editor.text_revision()
|| self.lsp_source_cursor != Some(self.editor.cursor())
|| self.editor.mode() != self.lsp_source_mode
@@ -618,6 +624,7 @@ impl EditorSession {
.is_some_and(|(id, latest)| {
request_id == Some(id)
&& latest.buffer_id == source.buffer_id
&& latest.pane_id == self.editor.pane_id()
&& latest.revision == source.revision
})
}
@@ -674,6 +681,7 @@ impl EditorSession {
}
let source = InvocationSource {
buffer_id: *buffer_id,
pane_id: self.editor.pane_id(),
revision: *revision,
};
if self.completion_is_current(update.request_id, source)
@@ -720,6 +728,7 @@ impl EditorSession {
} => {
let source = InvocationSource {
buffer_id: *buffer_id,
pane_id: self.editor.pane_id(),
revision: *revision,
};
if self.request_is_current(owner, update.request_id, source)
@@ -738,6 +747,7 @@ impl EditorSession {
} => {
let source = InvocationSource {
buffer_id: *buffer_id,
pane_id: self.editor.pane_id(),
revision: *revision,
};
if self.hover_is_current(owner, update.request_id, source) {
@@ -786,6 +796,7 @@ impl EditorSession {
} => {
let source = InvocationSource {
buffer_id,
pane_id: self.editor.pane_id(),
revision,
};
if self.request_is_current(owner, update.request_id, source) {
@@ -805,6 +816,7 @@ impl EditorSession {
} => {
let source = InvocationSource {
buffer_id,
pane_id: self.editor.pane_id(),
revision,
};
if self.hover_is_current(owner, update.request_id, source) {
@@ -823,6 +835,7 @@ impl EditorSession {
} => {
let source = InvocationSource {
buffer_id,
pane_id: self.editor.pane_id(),
revision,
};
if self.completion_is_current(update.request_id, source) {
@@ -857,7 +870,10 @@ impl EditorSession {
let Some(source) = source else {
return Err("plugin response has an unknown invocation ID".into());
};
if !self.source_is_current(source) || self.editor.buffer_id() != source.buffer_id {
if !self.source_is_current(source)
|| self.editor.buffer_id() != source.buffer_id
|| self.editor.pane_id() != source.pane_id
{
return Ok(false);
}
if effects
@@ -948,6 +964,53 @@ mod tests {
static NEXT_FIXTURE: AtomicU64 = AtomicU64::new(0);
#[test]
fn render_swaps_preserve_session_sources_and_pending_effects_do_not_cross_panes() {
let fixture = Fixture::new();
let path = fixture.file("buffer.txt", "source");
let mut session = EditorSession::open(Some(path), vec![], None).unwrap();
let source = InvocationSource {
pane_id: session.editor.pane_id(),
buffer_id: session.editor.buffer_id(),
revision: session.editor.text_revision(),
};
session
.requests
.entry("fixture".into())
.or_default()
.pending
.insert(1, source);
session.requests.entry("fixture".into()).or_default().latest = Some((1, source));
session.handle(Input::Char('|'));
assert_eq!(session.editor.buffer_id(), source.buffer_id);
assert_eq!(session.editor.cursor(), Cursor::default());
assert!(!session.request_is_current("fixture", Some(1), source));
assert!(
!session
.apply_response("fixture", 1, vec![Effect::Insert("wrong pane".into())])
.unwrap()
);
session.requests.entry("core".into()).or_default().latest = Some((
2,
InvocationSource {
pane_id: session.editor.pane_id(),
..source
},
));
session.lsp_source_cursor = Some(session.editor.cursor());
session.lsp_source_mode = session.editor.mode();
session.editor_mut().with_render_pane(1, |pane| {
assert_eq!(pane.pane_id(), 1);
assert_eq!(pane.lines(), ["source"]);
});
session.synchronize_documents();
assert!(session.requests["core"].latest.is_some());
assert_eq!(session.editor.pane_id(), 2);
session.handle(Input::Alt('h'));
assert!(session.requests["core"].latest.is_none());
assert_eq!(session.editor.lines(), ["source"]);
}
#[test]
fn native_semantic_batches_are_atomic_and_malformed_updates_clear_hidden_overlays() {
use crate::plugin::{SemanticToken, SemanticTokenKind};
@@ -3412,6 +3475,7 @@ end
session.editor.handle(Input::BackTab);
let source = InvocationSource {
buffer_id: session.editor.buffer_id(),
pane_id: session.editor.pane_id(),
revision: session.editor.text_revision(),
};
session
@@ -3447,6 +3511,7 @@ end
session.editor.scroll_diff(Focus::Before, false);
let readonly = InvocationSource {
buffer_id: session.editor.buffer_id(),
pane_id: session.editor.pane_id(),
revision: session.editor.text_revision(),
};
session