pull/13731/merge
Piotr Kwarciński 2025-07-24 10:19:26 +02:00 committed by GitHub
commit 880d9f1363
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 83 additions and 5 deletions

View File

@ -167,6 +167,8 @@
| `extend_to_line_start` | Extend to line start | select: `` <home> `` |
| `extend_to_first_nonwhitespace` | Extend to first non-blank in line | |
| `extend_to_line_end` | Extend to line end | select: `` <end> `` |
| `undo_selection` | Go to previous selection | normal: `` <A-k> ``, select: `` <A-k> `` |
| `redo_selection` | Go to next selection | normal: `` <A-w> ``, select: `` <A-w> `` |
| `extend_to_line_end_newline` | Extend to line end | |
| `signature_help` | Show signature help | |
| `smart_tab` | Insert tab if all cursors have all whitespace to their left; otherwise, run a separate command. | insert: `` <tab> `` |

View File

@ -44,7 +44,7 @@ use helix_core::{
Selection, SmallVec, Syntax, Tendril, Transaction,
};
use helix_view::{
document::{FormatterError, Mode, SCRATCH_BUFFER_NAME},
document::{FormatterError, Mode, SelectionDirection, SCRATCH_BUFFER_NAME},
editor::Action,
info::Info,
input::KeyEvent,
@ -467,6 +467,8 @@ impl MappableCommand {
extend_to_line_start, "Extend to line start",
extend_to_first_nonwhitespace, "Extend to first non-blank in line",
extend_to_line_end, "Extend to line end",
undo_selection, "Go to previous selection",
redo_selection, "Go to next selection",
extend_to_line_end_newline, "Extend to line end",
signature_help, "Show signature help",
smart_tab, "Insert tab if all cursors have all whitespace to their left; otherwise, run a separate command.",
@ -840,6 +842,18 @@ fn goto_line_end(cx: &mut Context) {
)
}
fn undo_selection(cx: &mut Context) {
let count = cx.count();
let (view, doc) = current!(cx.editor);
doc.select_history(view.id, SelectionDirection::Undo(count));
}
fn redo_selection(cx: &mut Context) {
let count = cx.count();
let (view, doc) = current!(cx.editor);
doc.select_history(view.id, SelectionDirection::Redo(count));
}
fn extend_to_line_end(cx: &mut Context) {
let (view, doc) = current!(cx.editor);
goto_line_end_impl(view, doc, Movement::Extend)

View File

@ -86,6 +86,8 @@ pub fn default() -> HashMap<Mode, KeyTrie> {
"S" => split_selection,
";" => collapse_selection,
"A-;" => flip_selections,
"A-w" => redo_selection,
"A-k" => undo_selection,
"A-o" | "A-up" => expand_selection,
"A-i" | "A-down" => shrink_selection,
"A-I" | "A-S-down" => select_all_children,

View File

@ -142,6 +142,7 @@ pub struct Document {
pub(crate) id: DocumentId,
text: Rope,
selections: HashMap<ViewId, Selection>,
selections_history: HashMap<ViewId, SelectionHistory>,
view_data: HashMap<ViewId, ViewData>,
pub active_snippet: Option<ActiveSnippet>,
@ -216,6 +217,33 @@ pub struct Document {
syn_loader: Arc<ArcSwap<syntax::Loader>>,
}
#[derive(Debug, PartialEq, Eq)]
pub enum SelectionDirection {
Undo(usize),
Redo(usize),
}
#[derive(Debug)]
pub struct SelectionHistory {
selections: Vec<Selection>,
current: usize,
}
impl SelectionHistory {
pub fn goto(&mut self, direction: SelectionDirection) -> Option<&Selection> {
let mut position = match direction {
SelectionDirection::Undo(count) => self.current.checked_sub(count)?,
SelectionDirection::Redo(count) => self.current.checked_add(count)?,
};
let max = self.selections.len() - 1;
if position > max {
position = max
}
self.current = position;
self.selections.get(self.current)
}
}
#[derive(Debug, Clone, Default)]
pub struct DocumentColorSwatches {
pub color_swatches: Vec<InlineAnnotation>,
@ -300,6 +328,7 @@ impl fmt::Debug for Document {
.field("inlay_hints_oudated", &self.inlay_hints_oudated)
.field("text_annotations", &self.inlay_hints)
.field("view_data", &self.view_data)
.field("selections_history", &self.selections_history)
.field("path", &self.path)
.field("encoding", &self.encoding)
.field("restore_cursor", &self.restore_cursor)
@ -700,6 +729,7 @@ impl Document {
has_bom,
text,
selections: HashMap::default(),
selections_history: HashMap::default(),
inlay_hints: HashMap::default(),
inlay_hints_oudated: false,
view_data: Default::default(),
@ -1320,17 +1350,43 @@ impl Document {
Ok(())
}
/// Select text within the [`Document`].
pub fn set_selection(&mut self, view_id: ViewId, selection: Selection) {
fn select(&mut self, view_id: ViewId, selection: Selection) {
// TODO: use a transaction?
self.selections
.insert(view_id, selection.ensure_invariants(self.text().slice(..)));
self.selections.insert(view_id, selection);
helix_event::dispatch(SelectionDidChange {
doc: self,
view: view_id,
})
}
/// Select text within the [`Document`].
pub fn set_selection(&mut self, view_id: ViewId, selection: Selection) {
// TODO: use a transaction?
let selection = selection.ensure_invariants(self.text().slice(..));
self.save_selection_to_history(view_id, selection.clone());
self.select(view_id, selection);
}
fn save_selection_to_history(&mut self, view_id: ViewId, selection: Selection) {
let entry = self
.selections_history
.entry(view_id)
.or_insert(SelectionHistory {
selections: vec![],
current: 0,
});
entry.selections.push(selection);
entry.current = entry.selections.len() - 1;
}
pub fn select_history(&mut self, view_id: ViewId, direction: SelectionDirection) {
self.selections_history
.get_mut(&view_id)
.and_then(|history| history.goto(direction).cloned())
.into_iter()
.for_each(|selection| self.select(view_id, selection));
}
/// Find the origin selection of the text in a document, i.e. where
/// a single cursor would go if it were on the first grapheme. If
/// the text is empty, returns (0, 0).
@ -1367,6 +1423,7 @@ impl Document {
/// Remove a view's selection and inlay hints from this document.
pub fn remove_view(&mut self, view_id: ViewId) {
self.selections.remove(&view_id);
self.selections_history.remove(&view_id);
self.inlay_hints.remove(&view_id);
self.jump_labels.remove(&view_id);
}
@ -1412,6 +1469,9 @@ impl Document {
.ensure_invariants(self.text.slice(..));
}
// Reset the selection history after any change
self.selections_history.clear();
for view_data in self.view_data.values_mut() {
view_data.view_position.anchor = transaction
.changes()