From 46dfbd0991cbce5baef2c11d68938f8f48ee2398 Mon Sep 17 00:00:00 2001 From: Nikola Milenkovic Date: Wed, 30 Apr 2025 00:09:31 +0100 Subject: [PATCH 1/2] feat: implement buffer jumplist j.a=":ab" j.d=":rb" j.0=":j 0" j.1=":j 1" j.2=":j 2" j.3=":j 3" j.4=":j 4" --- helix-term/src/commands.rs | 10 ++++ helix-term/src/commands/typed.rs | 78 ++++++++++++++++++++++++++++++++ helix-view/src/editor.rs | 7 +++ 3 files changed, 95 insertions(+) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 2669d8dd0..da215548f 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -3141,6 +3141,7 @@ fn buffer_picker(cx: &mut Context) { is_modified: bool, is_current: bool, focused_at: std::time::Instant, + index_in_buffer_jumplist: Option, } let new_meta = |doc: &Document| BufferMeta { @@ -3149,6 +3150,11 @@ fn buffer_picker(cx: &mut Context) { is_modified: doc.is_modified(), is_current: doc.id() == current, focused_at: doc.focused_at, + index_in_buffer_jumplist: cx + .editor + .buffer_jumplist + .iter() + .position(|id| id == &doc.id()), }; let mut items = cx @@ -3171,6 +3177,10 @@ fn buffer_picker(cx: &mut Context) { if meta.is_current { flags.push('*'); } + if let Some(index) = meta.index_in_buffer_jumplist { + flags.push_str(&index.to_string()); + } + flags.into() }), PickerColumn::new("path", |meta: &BufferMeta, _| { diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index c35ff714a..3e6d61a09 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -99,6 +99,51 @@ fn force_quit(cx: &mut compositor::Context, _args: Args, event: PromptEvent) -> Ok(()) } +fn buffer_jump(cx: &mut compositor::Context, args: Args, event: PromptEvent) -> anyhow::Result<()> { + if event != PromptEvent::Validate { + return Ok(()); + } + + let pos: usize = args.first().unwrap_or("0").parse().unwrap_or(0); + if let Some(doc_id) = cx.editor.buffer_jumplist.get(pos) { + cx.editor.switch(*doc_id, Action::Replace); + } + + return Ok(()); +} + +fn add_buffer_to_jumplist( + cx: &mut compositor::Context, + _args: Args, + event: PromptEvent, +) -> anyhow::Result<()> { + if event != PromptEvent::Validate { + return Ok(()); + } + + let current = view!(cx.editor).doc; + cx.editor.buffer_jumplist.push(current); + + return Ok(()); +} + +fn remove_buffer_from_jumplist( + cx: &mut compositor::Context, + _args: Args, + event: PromptEvent, +) -> anyhow::Result<()> { + if event != PromptEvent::Validate { + return Ok(()); + } + + let current = view!(cx.editor).doc; + cx.editor + .buffer_jumplist + .retain(|doc_id| *doc_id != current); + + return Ok(()); +} + fn open(cx: &mut compositor::Context, args: Args, event: PromptEvent) -> anyhow::Result<()> { if event != PromptEvent::Validate { return Ok(()); @@ -2619,6 +2664,39 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ ..Signature::DEFAULT }, }, + TypableCommand { + name: "jump-to-buffer", + aliases: &["j"], + doc: "Switch to a document buffer using its index in the buffer jumplist.", + fun: buffer_jump, + completer: CommandCompleter::all(completers::filename), + signature: Signature { + positionals: (1, None), + ..Signature::DEFAULT + }, + }, + TypableCommand { + name: "add-buffer-to-jumplist", + aliases: &["ab"], + doc: "Add the current document buffer to the buffer jumplist.", + fun: add_buffer_to_jumplist, + completer: CommandCompleter::all(completers::filename), + signature: Signature { + positionals: (0, None), + ..Signature::DEFAULT + }, + }, + TypableCommand { + name: "remove-buffer-from-jumplist", + aliases: &["rb"], + doc: "Remove the current document buffer from the buffer jumplist.", + fun: remove_buffer_from_jumplist, + completer: CommandCompleter::all(completers::filename), + signature: Signature { + positionals: (0, None), + ..Signature::DEFAULT + }, + }, TypableCommand { name: "buffer-close", aliases: &["bc", "bclose"], diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index eab291e44..d9b8ee454 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -1123,6 +1123,9 @@ pub struct Editor { pub mouse_down_range: Option, pub cursor_cache: CursorCache, + + /// Stores a manually curated list of document IDs for navigation. + pub buffer_jumplist: Vec, } pub type Motion = Box; @@ -1245,6 +1248,7 @@ impl Editor { handlers, mouse_down_range: None, cursor_cache: CursorCache::default(), + buffer_jumplist: Vec::new(), } } @@ -1824,6 +1828,9 @@ impl Editor { // This will also disallow any follow-up writes self.saves.remove(&doc_id); + // Removes the document from the buffer jumplist when closed. + self.buffer_jumplist.retain(|doc| *doc != doc_id); + enum Action { Close(ViewId), ReplaceDoc(ViewId, DocumentId), From 29f58ee9ba1437700d59c8b87922479287fc5640 Mon Sep 17 00:00:00 2001 From: Nikola Milenkovic Date: Wed, 30 Apr 2025 00:30:19 +0100 Subject: [PATCH 2/2] feat: add commands for buffer jumplist to typable-cmd.md --- book/src/generated/typable-cmd.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/book/src/generated/typable-cmd.md b/book/src/generated/typable-cmd.md index 219f6b95f..7c81105b5 100644 --- a/book/src/generated/typable-cmd.md +++ b/book/src/generated/typable-cmd.md @@ -3,6 +3,9 @@ | `:quit`, `:q` | Close the current view. | | `:quit!`, `:q!` | Force close the current view, ignoring unsaved changes. | | `:open`, `:o`, `:edit`, `:e` | Open a file from disk into the current view. | +| `:jump-to-buffer`, `:j` | Switch to a document buffer using its index in the buffer jumplist. | +| `:add-buffer-to-jumplist`, `:ab` | Add the current document buffer to the buffer jumplist. | +| `:remove-buffer-from-jumplist`, `:rb` | Remove the current document buffer from the buffer jumplist. | | `:buffer-close`, `:bc`, `:bclose` | Close the current buffer. | | `:buffer-close!`, `:bc!`, `:bclose!` | Close the current buffer forcefully, ignoring unsaved changes. | | `:buffer-close-others`, `:bco`, `:bcloseother` | Close all buffers but the currently focused one. |