Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Goto buffer #4756

Closed
wants to merge 10 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions book/src/generated/typable-cmd.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
| `:buffer-close-others!`, `:bco!`, `:bcloseother!` | Force close all buffers but the currently focused one. |
| `:buffer-close-all`, `:bca`, `:bcloseall` | Close all buffers without quitting. |
| `:buffer-close-all!`, `:bca!`, `:bcloseall!` | Force close all buffers ignoring unsaved changes without quitting. |
| `:buffer-goto`, `:b` | Goto the buffer number <n>. |
| `:buffer-next`, `:bn`, `:bnext` | Goto next buffer. |
| `:buffer-previous`, `:bp`, `:bprev` | Goto previous buffer. |
| `:write`, `:w` | Write changes to disk. Accepts an optional path (:write some/path.txt) |
Expand Down
105 changes: 83 additions & 22 deletions helix-term/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,15 @@ impl MappableCommand {
goto_prev_diag, "Goto previous diagnostic",
goto_line_start, "Goto line start",
goto_line_end, "Goto line end",
goto_first_buffer, "Goto first buffer",
goto_second_buffer, "Goto second buffer",
goto_third_buffer, "Goto third buffer",
goto_fourth_buffer, "Goto fourth buffer",
goto_fifth_buffer, "Goto fifth buffer",
goto_sixth_buffer, "Goto sixth buffer",
goto_seventh_buffer, "Goto seventh buffer",
goto_eight_buffer, "Goto eight buffer",
goto_ninth_buffer, "Goto ninth buffer",
goto_next_buffer, "Goto next buffer",
goto_previous_buffer, "Goto previous buffer",
goto_line_end_newline, "Goto newline at line end",
Expand Down Expand Up @@ -659,36 +668,88 @@ fn goto_line_start(cx: &mut Context) {
)
}

fn goto_first_buffer(cx: &mut Context) {
goto_buffer_by_index_impl(cx.editor, 0);
}

fn goto_second_buffer(cx: &mut Context) {
goto_buffer_by_index_impl(cx.editor, 1);
}

fn goto_third_buffer(cx: &mut Context) {
goto_buffer_by_index_impl(cx.editor, 2);
}

fn goto_fourth_buffer(cx: &mut Context) {
goto_buffer_by_index_impl(cx.editor, 3);
}

fn goto_fifth_buffer(cx: &mut Context) {
goto_buffer_by_index_impl(cx.editor, 4);
}

fn goto_sixth_buffer(cx: &mut Context) {
goto_buffer_by_index_impl(cx.editor, 5);
}

fn goto_seventh_buffer(cx: &mut Context) {
goto_buffer_by_index_impl(cx.editor, 6);
}

fn goto_eight_buffer(cx: &mut Context) {
goto_buffer_by_index_impl(cx.editor, 7);
}

fn goto_ninth_buffer(cx: &mut Context) {
goto_buffer_by_index_impl(cx.editor, 8);
}

fn goto_next_buffer(cx: &mut Context) {
goto_buffer(cx.editor, Direction::Forward);
goto_buffer_by_direction(cx.editor, Direction::Forward)
}

fn goto_previous_buffer(cx: &mut Context) {
goto_buffer(cx.editor, Direction::Backward);
}
goto_buffer_by_direction(cx.editor, Direction::Backward)
}

fn goto_buffer_by_direction(editor: &mut Editor, direction: Direction) {
let doc_id = &view!(editor).doc;
let buffers_len = editor.documents.len();
let current_index = editor
.documents
.keys()
.position(|d| d == doc_id)
.expect("current document was not in documents");

let new_index = match direction {
Direction::Forward if current_index < buffers_len - 1 => current_index + 1,
Direction::Forward => 0, // Would be out of bounds, wrap to front.
Direction::Backward if current_index > 0 => current_index - 1,
Direction::Backward => buffers_len - 1, // Would be out of bounds, wrap to back.
};

fn goto_buffer(editor: &mut Editor, direction: Direction) {
let current = view!(editor).doc;
Comment on lines -670 to -671
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's necessary to change the body of the old goto_buffer function - it has the same behavior before and after except that going to the previous buffer skips from the opposite direction

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mainly replaced the body of the function because I saw nearly identical code in both branches of the match statement. That just triggered my refactoring sense, lol.
After looking into it, using an iterator the way it is used here may be hard to understand / overkill for such a simple task.

except that going to the previous buffer skips from the opposite direction

I'm honestly not certain what you mean by this. buffer-next goes "to the right", and buffer-previous goes "to the left". Have I missed something that the original implementation does?

// Safety: The above logic ensures that the new index is always in bounds.
goto_buffer_by_index_impl(editor, new_index).unwrap();
}

let id = match direction {
Direction::Forward => {
let iter = editor.documents.keys();
let mut iter = iter.skip_while(|id| *id != &current);
iter.next(); // skip current item
iter.next().or_else(|| editor.documents.keys().next())
}
Direction::Backward => {
let iter = editor.documents.keys();
let mut iter = iter.rev().skip_while(|id| *id != &current);
iter.next(); // skip current item
iter.next().or_else(|| editor.documents.keys().rev().next())
}
}
.unwrap();
/// Goto a buffer by providing it's index.
///
/// Note that the index starts from 0.
///
/// # Errors
///
/// Returns an error if the index could not be found - is out of bounds.
fn goto_buffer_by_index_impl(editor: &mut Editor, index: usize) -> anyhow::Result<()> {
let doc_id = editor
.documents
.keys()
.nth(index)
.copied()
.ok_or_else(|| anyhow!("no buffer '{}' in editor", index + 1))?;

let id = *id;
editor.switch(doc_id, Action::Replace);

editor.switch(id, Action::Replace);
Ok(())
}

fn extend_to_line_start(cx: &mut Context) {
Expand Down
47 changes: 43 additions & 4 deletions helix-term/src/commands/typed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,8 @@ fn buffer_next(
return Ok(());
}

goto_buffer(cx.editor, Direction::Forward);
goto_buffer_by_direction(cx.editor, Direction::Forward);

Ok(())
}

Expand All @@ -260,7 +261,38 @@ fn buffer_previous(
return Ok(());
}

goto_buffer(cx.editor, Direction::Backward);
goto_buffer_by_direction(cx.editor, Direction::Backward);

Ok(())
}

/// Goto a buffer by providing <i> (index) as the argument.
///
/// Note that the index starts from 1.
fn goto_buffer_by_index(
cx: &mut compositor::Context,
args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}

let index_str = args
.first()
.ok_or_else(|| anyhow!("requires an argument for <i>"))?
.as_ref();
let index = index_str
.parse::<usize>()
.map_err(|_| anyhow!("'{}' is not a valid index", index_str))?;

if index < 1 {
bail!("indices bellow 1 not supported");
}

// Convert to zero based index
goto_buffer_by_index_impl(cx.editor, index - 1)?;

Ok(())
}

Expand Down Expand Up @@ -1766,14 +1798,14 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
aliases: &["bc", "bclose"],
doc: "Close the current buffer.",
fun: buffer_close,
completer: Some(completers::buffer),
completer: Some(completers::buffer_name),
},
TypableCommand {
name: "buffer-close!",
aliases: &["bc!", "bclose!"],
doc: "Close the current buffer forcefully, ignoring unsaved changes.",
fun: force_buffer_close,
completer: Some(completers::buffer),
completer: Some(completers::buffer_name),
},
TypableCommand {
name: "buffer-close-others",
Expand Down Expand Up @@ -1803,6 +1835,13 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
fun: force_buffer_close_all,
completer: None,
},
TypableCommand {
name: "buffer-goto",
aliases: &["b"],
doc: "Goto the buffer number <n>.",
fun: goto_buffer_by_index,
completer: Some(completers::buffer_index),
},
TypableCommand {
name: "buffer-next",
aliases: &["bn", "bnext"],
Expand Down
9 changes: 9 additions & 0 deletions helix-term/src/keymap/default.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,15 @@ pub fn default() -> HashMap<Mode, Keymap> {
"C" => copy_selection_on_next_line,
"A-C" => copy_selection_on_prev_line,

"C-1" => goto_first_buffer,
"C-2" => goto_second_buffer,
"C-3" => goto_third_buffer,
"C-4" => goto_fourth_buffer,
"C-5" => goto_fifth_buffer,
"C-6" => goto_sixth_buffer,
"C-7" => goto_seventh_buffer,
"C-8" => goto_eight_buffer,
"C-9" => goto_ninth_buffer,

"s" => select_regex,
"A-s" => split_selection_on_newline,
Expand Down
26 changes: 25 additions & 1 deletion helix-term/src/ui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ pub mod completers {
Vec::new()
}

pub fn buffer(editor: &Editor, input: &str) -> Vec<Completion> {
pub fn buffer_name(editor: &Editor, input: &str) -> Vec<Completion> {
let mut names: Vec<_> = editor
.documents
.iter()
Expand Down Expand Up @@ -279,6 +279,30 @@ pub mod completers {
names
}

/// Completes the buffer indices.
///
/// Note that the indices start from 1 instead of 0.
pub fn buffer_index(editor: &Editor, input: &str) -> Vec<Completion> {
let indices = (0..editor.documents.len()).map(|index| Cow::from((index + 1).to_string()));

let matcher = Matcher::default();

let mut matches: Vec<_> = indices
.filter_map(|index| {
matcher
.fuzzy_match(&index, input)
.map(|score| (index, score))
})
.collect();

matches.sort_unstable_by_key(|(_, score)| Reverse(*score));

matches
.into_iter()
.map(|(index, _)| ((0..), index))
.collect()
}

pub fn theme(_editor: &Editor, input: &str) -> Vec<Completion> {
let mut names = theme::Loader::read_names(&helix_loader::runtime_dir().join("themes"));
names.extend(theme::Loader::read_names(
Expand Down