vim: Add global marks (#25702)
Closes https://github.com/zed-industries/zed/issues/13111 Release Notes: - vim: Added global marks `'[A-Z]` - vim: Added persistence for global (and local) marks. When re-opening the same workspace your previous marks will be available. --------- Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
This commit is contained in:
parent
148131786f
commit
265caed15e
18 changed files with 982 additions and 281 deletions
|
@ -76,7 +76,7 @@ impl Vim {
|
|||
}
|
||||
});
|
||||
});
|
||||
vim.copy_selections_content(editor, motion.linewise(), cx);
|
||||
vim.copy_selections_content(editor, motion.linewise(), window, cx);
|
||||
editor.insert("", window, cx);
|
||||
editor.refresh_inline_completion(true, false, window, cx);
|
||||
});
|
||||
|
@ -107,7 +107,7 @@ impl Vim {
|
|||
});
|
||||
});
|
||||
if objects_found {
|
||||
vim.copy_selections_content(editor, false, cx);
|
||||
vim.copy_selections_content(editor, false, window, cx);
|
||||
editor.insert("", window, cx);
|
||||
editor.refresh_inline_completion(true, false, window, cx);
|
||||
}
|
||||
|
|
|
@ -60,7 +60,7 @@ impl Vim {
|
|||
}
|
||||
});
|
||||
});
|
||||
vim.copy_selections_content(editor, motion.linewise(), cx);
|
||||
vim.copy_selections_content(editor, motion.linewise(), window, cx);
|
||||
editor.insert("", window, cx);
|
||||
|
||||
// Fixup cursor position after the deletion
|
||||
|
@ -148,7 +148,7 @@ impl Vim {
|
|||
}
|
||||
});
|
||||
});
|
||||
vim.copy_selections_content(editor, false, cx);
|
||||
vim.copy_selections_content(editor, false, window, cx);
|
||||
editor.insert("", window, cx);
|
||||
|
||||
// Fixup cursor position after the deletion
|
||||
|
|
|
@ -1,39 +1,34 @@
|
|||
use std::{ops::Range, sync::Arc};
|
||||
use std::{ops::Range, path::Path, sync::Arc};
|
||||
|
||||
use editor::{
|
||||
display_map::{DisplaySnapshot, ToDisplayPoint},
|
||||
movement,
|
||||
scroll::Autoscroll,
|
||||
Anchor, Bias, DisplayPoint,
|
||||
Anchor, Bias, DisplayPoint, Editor, MultiBuffer,
|
||||
};
|
||||
use gpui::{Context, Window};
|
||||
use gpui::{Context, Entity, EntityId, UpdateGlobal, Window};
|
||||
use language::SelectionGoal;
|
||||
use text::Point;
|
||||
use ui::App;
|
||||
use workspace::OpenOptions;
|
||||
|
||||
use crate::{
|
||||
motion::{self, Motion},
|
||||
state::Mode,
|
||||
state::{Mark, Mode, VimGlobals},
|
||||
Vim,
|
||||
};
|
||||
|
||||
impl Vim {
|
||||
pub fn create_mark(
|
||||
&mut self,
|
||||
text: Arc<str>,
|
||||
tail: bool,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let Some(anchors) = self.update_editor(window, cx, |_, editor, _, _| {
|
||||
editor
|
||||
pub fn create_mark(&mut self, text: Arc<str>, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.update_editor(window, cx, |vim, editor, window, cx| {
|
||||
let anchors = editor
|
||||
.selections
|
||||
.disjoint_anchors()
|
||||
.iter()
|
||||
.map(|s| if tail { s.tail() } else { s.head() })
|
||||
.collect::<Vec<_>>()
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
self.marks.insert(text.to_string(), anchors);
|
||||
.map(|s| s.head())
|
||||
.collect::<Vec<_>>();
|
||||
vim.set_mark(text.to_string(), anchors, editor.buffer(), window, cx);
|
||||
});
|
||||
self.clear_operator(window, cx);
|
||||
}
|
||||
|
||||
|
@ -55,7 +50,7 @@ impl Vim {
|
|||
let mut ends = vec![];
|
||||
let mut reversed = vec![];
|
||||
|
||||
self.update_editor(window, cx, |_, editor, _, cx| {
|
||||
self.update_editor(window, cx, |vim, editor, window, cx| {
|
||||
let (map, selections) = editor.selections.all_display(cx);
|
||||
for selection in selections {
|
||||
let end = movement::saturating_left(&map, selection.end);
|
||||
|
@ -69,13 +64,121 @@ impl Vim {
|
|||
);
|
||||
reversed.push(selection.reversed)
|
||||
}
|
||||
vim.set_mark("<".to_string(), starts, editor.buffer(), window, cx);
|
||||
vim.set_mark(">".to_string(), ends, editor.buffer(), window, cx);
|
||||
});
|
||||
|
||||
self.marks.insert("<".to_string(), starts);
|
||||
self.marks.insert(">".to_string(), ends);
|
||||
self.stored_visual_mode.replace((mode, reversed));
|
||||
}
|
||||
|
||||
fn open_buffer_mark(
|
||||
&mut self,
|
||||
line: bool,
|
||||
entity_id: EntityId,
|
||||
anchors: Vec<Anchor>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let Some(workspace) = self.workspace(window) else {
|
||||
return;
|
||||
};
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
let item = workspace.items(cx).find(|item| {
|
||||
item.act_as::<Editor>(cx)
|
||||
.is_some_and(|editor| editor.read(cx).buffer().entity_id() == entity_id)
|
||||
});
|
||||
let Some(item) = item.cloned() else {
|
||||
return;
|
||||
};
|
||||
if let Some(pane) = workspace.pane_for(item.as_ref()) {
|
||||
pane.update(cx, |pane, cx| {
|
||||
if let Some(index) = pane.index_for_item(item.as_ref()) {
|
||||
pane.activate_item(index, true, true, window, cx);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
item.act_as::<Editor>(cx).unwrap().update(cx, |editor, cx| {
|
||||
let map = editor.snapshot(window, cx);
|
||||
let mut ranges: Vec<Range<Anchor>> = Vec::new();
|
||||
for mut anchor in anchors {
|
||||
if line {
|
||||
let mut point = anchor.to_display_point(&map.display_snapshot);
|
||||
point = motion::first_non_whitespace(&map.display_snapshot, false, point);
|
||||
anchor = map
|
||||
.display_snapshot
|
||||
.buffer_snapshot
|
||||
.anchor_before(point.to_point(&map.display_snapshot));
|
||||
}
|
||||
|
||||
if ranges.last() != Some(&(anchor..anchor)) {
|
||||
ranges.push(anchor..anchor);
|
||||
}
|
||||
}
|
||||
|
||||
editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
||||
s.select_anchor_ranges(ranges)
|
||||
});
|
||||
})
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
fn open_path_mark(
|
||||
&mut self,
|
||||
line: bool,
|
||||
path: Arc<Path>,
|
||||
points: Vec<Point>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let Some(workspace) = self.workspace(window) else {
|
||||
return;
|
||||
};
|
||||
let task = workspace.update(cx, |workspace, cx| {
|
||||
workspace.open_abs_path(
|
||||
path.to_path_buf(),
|
||||
OpenOptions {
|
||||
visible: Some(workspace::OpenVisible::All),
|
||||
focus: Some(true),
|
||||
..Default::default()
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
cx.spawn_in(window, |this, mut cx| async move {
|
||||
let editor = task.await?;
|
||||
this.update_in(&mut cx, |_, window, cx| {
|
||||
if let Some(editor) = editor.act_as::<Editor>(cx) {
|
||||
editor.update(cx, |editor, cx| {
|
||||
let map = editor.snapshot(window, cx);
|
||||
let points: Vec<_> = points
|
||||
.into_iter()
|
||||
.map(|p| {
|
||||
if line {
|
||||
let point = p.to_display_point(&map.display_snapshot);
|
||||
motion::first_non_whitespace(
|
||||
&map.display_snapshot,
|
||||
false,
|
||||
point,
|
||||
)
|
||||
.to_point(&map.display_snapshot)
|
||||
} else {
|
||||
p
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
||||
s.select_ranges(points.into_iter().map(|p| p..p))
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
pub fn jump(
|
||||
&mut self,
|
||||
text: Arc<str>,
|
||||
|
@ -84,25 +187,22 @@ impl Vim {
|
|||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.pop_operator(window, cx);
|
||||
|
||||
let anchors = match &*text {
|
||||
"{" | "}" => self.update_editor(window, cx, |_, editor, _, cx| {
|
||||
let (map, selections) = editor.selections.all_display(cx);
|
||||
selections
|
||||
.into_iter()
|
||||
.map(|selection| {
|
||||
let point = if &*text == "{" {
|
||||
movement::start_of_paragraph(&map, selection.head(), 1)
|
||||
} else {
|
||||
movement::end_of_paragraph(&map, selection.head(), 1)
|
||||
};
|
||||
map.buffer_snapshot
|
||||
.anchor_before(point.to_offset(&map, Bias::Left))
|
||||
})
|
||||
.collect::<Vec<Anchor>>()
|
||||
}),
|
||||
"." => self.change_list.last().cloned(),
|
||||
_ => self.marks.get(&*text).cloned(),
|
||||
let mark = self
|
||||
.update_editor(window, cx, |vim, editor, window, cx| {
|
||||
vim.get_mark(&text, editor, window, cx)
|
||||
})
|
||||
.flatten();
|
||||
let anchors = match mark {
|
||||
None => None,
|
||||
Some(Mark::Local(anchors)) => Some(anchors),
|
||||
Some(Mark::Buffer(entity_id, anchors)) => {
|
||||
self.open_buffer_mark(line, entity_id, anchors, window, cx);
|
||||
return;
|
||||
}
|
||||
Some(Mark::Path(path, points)) => {
|
||||
self.open_path_mark(line, path, points, window, cx);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let Some(mut anchors) = anchors else { return };
|
||||
|
@ -144,7 +244,7 @@ impl Vim {
|
|||
}
|
||||
}
|
||||
|
||||
if !should_jump {
|
||||
if !should_jump && !ranges.is_empty() {
|
||||
editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
||||
s.select_anchor_ranges(ranges)
|
||||
});
|
||||
|
@ -158,6 +258,62 @@ impl Vim {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_mark(
|
||||
&mut self,
|
||||
name: String,
|
||||
anchors: Vec<Anchor>,
|
||||
buffer_entity: &Entity<MultiBuffer>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
let Some(workspace) = self.workspace(window) else {
|
||||
return;
|
||||
};
|
||||
let entity_id = workspace.entity_id();
|
||||
Vim::update_globals(cx, |vim_globals, cx| {
|
||||
let Some(marks_state) = vim_globals.marks.get(&entity_id) else {
|
||||
return;
|
||||
};
|
||||
marks_state.update(cx, |ms, cx| {
|
||||
ms.set_mark(name.clone(), buffer_entity, anchors, cx);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
pub fn get_mark(
|
||||
&self,
|
||||
name: &str,
|
||||
editor: &mut Editor,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Option<Mark> {
|
||||
if matches!(name, "{" | "}" | "(" | ")") {
|
||||
let (map, selections) = editor.selections.all_display(cx);
|
||||
let anchors = selections
|
||||
.into_iter()
|
||||
.map(|selection| {
|
||||
let point = match name {
|
||||
"{" => movement::start_of_paragraph(&map, selection.head(), 1),
|
||||
"}" => movement::end_of_paragraph(&map, selection.head(), 1),
|
||||
"(" => motion::sentence_backwards(&map, selection.head(), 1),
|
||||
")" => motion::sentence_forwards(&map, selection.head(), 1),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
map.buffer_snapshot
|
||||
.anchor_before(point.to_offset(&map, Bias::Left))
|
||||
})
|
||||
.collect::<Vec<Anchor>>();
|
||||
return Some(Mark::Local(anchors));
|
||||
}
|
||||
VimGlobals::update_global(cx, |globals, cx| {
|
||||
let workspace_id = self.workspace(window)?.entity_id();
|
||||
globals
|
||||
.marks
|
||||
.get_mut(&workspace_id)?
|
||||
.update(cx, |ms, cx| ms.get_mark(name, editor.buffer(), cx))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn jump_motion(
|
||||
|
|
|
@ -50,7 +50,7 @@ impl Vim {
|
|||
.filter(|sel| sel.len() > 1 && vim.mode != Mode::VisualLine);
|
||||
|
||||
if !action.preserve_clipboard && vim.mode.is_visual() {
|
||||
vim.copy_selections_content(editor, vim.mode == Mode::VisualLine, cx);
|
||||
vim.copy_selections_content(editor, vim.mode == Mode::VisualLine, window, cx);
|
||||
}
|
||||
|
||||
let (display_map, current_selections) = editor.selections.all_adjusted_display(cx);
|
||||
|
|
|
@ -75,7 +75,7 @@ impl Vim {
|
|||
}
|
||||
})
|
||||
});
|
||||
vim.copy_selections_content(editor, line_mode, cx);
|
||||
vim.copy_selections_content(editor, line_mode, window, cx);
|
||||
let selections = editor.selections.all::<Point>(cx).into_iter();
|
||||
let edits = selections.map(|selection| (selection.start..selection.end, ""));
|
||||
editor.edit(edits, cx);
|
||||
|
|
|
@ -36,7 +36,7 @@ impl Vim {
|
|||
motion.expand_selection(map, selection, times, true, &text_layout_details);
|
||||
});
|
||||
});
|
||||
vim.yank_selections_content(editor, motion.linewise(), cx);
|
||||
vim.yank_selections_content(editor, motion.linewise(), window, cx);
|
||||
editor.change_selections(None, window, cx, |s| {
|
||||
s.move_with(|_, selection| {
|
||||
let (head, goal) = original_positions.remove(&selection.id).unwrap();
|
||||
|
@ -66,7 +66,7 @@ impl Vim {
|
|||
start_positions.insert(selection.id, start_position);
|
||||
});
|
||||
});
|
||||
vim.yank_selections_content(editor, false, cx);
|
||||
vim.yank_selections_content(editor, false, window, cx);
|
||||
editor.change_selections(None, window, cx, |s| {
|
||||
s.move_with(|_, selection| {
|
||||
let (head, goal) = start_positions.remove(&selection.id).unwrap();
|
||||
|
@ -82,6 +82,7 @@ impl Vim {
|
|||
&mut self,
|
||||
editor: &mut Editor,
|
||||
linewise: bool,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Editor>,
|
||||
) {
|
||||
self.copy_ranges(
|
||||
|
@ -94,6 +95,7 @@ impl Vim {
|
|||
.iter()
|
||||
.map(|s| s.range())
|
||||
.collect(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
|
@ -102,6 +104,7 @@ impl Vim {
|
|||
&mut self,
|
||||
editor: &mut Editor,
|
||||
linewise: bool,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Editor>,
|
||||
) {
|
||||
self.copy_ranges(
|
||||
|
@ -114,6 +117,7 @@ impl Vim {
|
|||
.iter()
|
||||
.map(|s| s.range())
|
||||
.collect(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
|
@ -124,28 +128,35 @@ impl Vim {
|
|||
linewise: bool,
|
||||
is_yank: bool,
|
||||
selections: Vec<Range<Point>>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Editor>,
|
||||
) {
|
||||
let buffer = editor.buffer().read(cx).snapshot(cx);
|
||||
let mut text = String::new();
|
||||
let mut clipboard_selections = Vec::with_capacity(selections.len());
|
||||
let mut ranges_to_highlight = Vec::new();
|
||||
|
||||
self.marks.insert(
|
||||
self.set_mark(
|
||||
"[".to_string(),
|
||||
selections
|
||||
.iter()
|
||||
.map(|s| buffer.anchor_before(s.start))
|
||||
.collect(),
|
||||
editor.buffer(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
self.marks.insert(
|
||||
self.set_mark(
|
||||
"]".to_string(),
|
||||
selections
|
||||
.iter()
|
||||
.map(|s| buffer.anchor_after(s.end))
|
||||
.collect(),
|
||||
editor.buffer(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
|
||||
let mut text = String::new();
|
||||
let mut clipboard_selections = Vec::with_capacity(selections.len());
|
||||
let mut ranges_to_highlight = Vec::new();
|
||||
|
||||
{
|
||||
let mut is_first = true;
|
||||
for selection in selections.iter() {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue