From e1063743e8314af942e6a017ebfeeb06acddacaf Mon Sep 17 00:00:00 2001 From: AidanV <84053180+AidanV@users.noreply.github.com> Date: Mon, 15 Dec 2025 09:13:18 -0800 Subject: [PATCH] vim: Fix global mark overwriting inconsistency (#44765) Closes #43963 This issue was caused by the global marks not being deleted. Previously marking the first file `m A` Screenshot From 2025-12-13 01-37-55 followed by marking the second file `m A` Screenshot From 2025-12-13 01-37-42 and navigating back to the first file Screenshot From 2025-12-13 01-37-30 shows that the mark still exists and was not properly deleted. After these changes the global mark in the original file is correctly overwritten. Added regression test for this. Release Notes: - Fixed bug where overwriting global Vim marks was inconsistent --- crates/vim/src/normal/mark.rs | 72 ++++++++++++++++++++++++++++++++++- crates/vim/src/state.rs | 10 ++++- 2 files changed, 79 insertions(+), 3 deletions(-) diff --git a/crates/vim/src/normal/mark.rs b/crates/vim/src/normal/mark.rs index 3bb040511fdd7fa53dd97198ae02b492b0e7359d..a4d85e87b24fa6e2753f0dbcfcbb43be9488f41a 100644 --- a/crates/vim/src/normal/mark.rs +++ b/crates/vim/src/normal/mark.rs @@ -372,9 +372,12 @@ pub fn jump_motion( #[cfg(test)] mod test { + use crate::test::{NeovimBackedTestContext, VimTestContext}; + use editor::Editor; use gpui::TestAppContext; - - use crate::test::NeovimBackedTestContext; + use std::path::Path; + use util::path; + use workspace::{CloseActiveItem, OpenOptions}; #[gpui::test] async fn test_quote_mark(cx: &mut TestAppContext) { @@ -394,4 +397,69 @@ mod test { cx.simulate_shared_keystrokes("^ ` `").await; cx.shared_state().await.assert_eq("Hello, worldˇ!"); } + + #[gpui::test] + async fn test_global_mark_overwrite(cx: &mut TestAppContext) { + let mut cx = VimTestContext::new(cx, true).await; + + let path = Path::new(path!("/first.rs")); + let fs = cx.workspace(|workspace, _, cx| workspace.project().read(cx).fs().clone()); + fs.as_fake().insert_file(path, "one".into()).await; + let path = Path::new(path!("/second.rs")); + fs.as_fake().insert_file(path, "two".into()).await; + + let _ = cx + .workspace(|workspace, window, cx| { + workspace.open_abs_path( + path!("/first.rs").into(), + OpenOptions::default(), + window, + cx, + ) + }) + .await; + + cx.simulate_keystrokes("m A"); + + let _ = cx + .workspace(|workspace, window, cx| { + workspace.open_abs_path( + path!("/second.rs").into(), + OpenOptions::default(), + window, + cx, + ) + }) + .await; + + cx.simulate_keystrokes("m A"); + + let _ = cx + .workspace(|workspace, window, cx| { + workspace.active_pane().update(cx, |pane, cx| { + pane.close_active_item(&CloseActiveItem::default(), window, cx) + }) + }) + .await; + + cx.simulate_keystrokes("m B"); + + cx.simulate_keystrokes("' A"); + + cx.workspace(|workspace, _, cx| { + let active_editor = workspace.active_item_as::(cx).unwrap(); + + let buffer = active_editor + .read(cx) + .buffer() + .read(cx) + .as_singleton() + .unwrap(); + + let file = buffer.read(cx).file().unwrap(); + let file_path = file.as_local().unwrap().abs_path(cx); + + assert_eq!(file_path.to_str().unwrap(), path!("/second.rs")); + }) + } } diff --git a/crates/vim/src/state.rs b/crates/vim/src/state.rs index e96fd3a329e95311eeb73b87b53acbe76939f0cd..2a8aa91063be89ebd616a2f9601f90c912cee8b5 100644 --- a/crates/vim/src/state.rs +++ b/crates/vim/src/state.rs @@ -550,6 +550,10 @@ impl MarksState { let buffer = multibuffer.read(cx).as_singleton(); let abs_path = buffer.as_ref().and_then(|b| self.path_for_buffer(b, cx)); + if self.is_global_mark(&name) && self.global_marks.contains_key(&name) { + self.delete_mark(name.clone(), multibuffer, cx); + } + let Some(abs_path) = abs_path else { self.multibuffer_marks .entry(multibuffer.entity_id()) @@ -573,7 +577,7 @@ impl MarksState { let buffer_id = buffer.read(cx).remote_id(); self.buffer_marks.entry(buffer_id).or_default().insert( - name, + name.clone(), anchors .into_iter() .map(|anchor| anchor.text_anchor) @@ -582,6 +586,10 @@ impl MarksState { if !self.watched_buffers.contains_key(&buffer_id) { self.watch_buffer(MarkLocation::Path(abs_path.clone()), &buffer, cx) } + if self.is_global_mark(&name) { + self.global_marks + .insert(name, MarkLocation::Path(abs_path.clone())); + } self.serialize_buffer_marks(abs_path, &buffer, cx) }