vim: Sort whole buffer when no range is specified (#42376)

- Introduce a `default_range` field to `VimCommand`, to be optionally
  used when no range is specified for the command
- Update `VimCommand.parse` to take into consideration the
  `default_range`
- Introduce `CommandRange::buffer` to obtain the `CommandRange` which
  corresponds to the whole buffer
- Update the `VimCommand` definitions for both `sort` and `sort i` to
  default to the whole buffer when no range is specified

Closes #41750 

Release Notes:

- Improved vim's `:sort` command to sort the buffer's content when no
selection is used
This commit is contained in:
Dino
2025-11-11 10:04:30 +00:00
committed by GitHub
parent 46db753f79
commit dcf56144b5
+131 -2
View File
@@ -725,6 +725,8 @@ struct VimCommand {
args: Option<
Box<dyn Fn(Box<dyn Action>, String) -> Option<Box<dyn Action>> + Send + Sync + 'static>,
>,
/// Optional range Range to use if no range is specified.
default_range: Option<CommandRange>,
range: Option<
Box<
dyn Fn(Box<dyn Action>, &CommandRange) -> Option<Box<dyn Action>>
@@ -793,6 +795,11 @@ impl VimCommand {
self
}
fn default_range(mut self, range: CommandRange) -> Self {
self.default_range = Some(range);
self
}
fn count(mut self) -> Self {
self.has_count = true;
self
@@ -923,6 +930,7 @@ impl VimCommand {
self.args.as_ref()?(action, args)?
};
let range = range.as_ref().or(self.default_range.as_ref());
if let Some(range) = range {
self.range.as_ref().and_then(|f| f(action, range))
} else {
@@ -1121,6 +1129,7 @@ impl CommandRange {
self.end.as_ref().unwrap_or(&self.start)
}
/// Convert the `CommandRange` into a `Range<MultiBufferRow>`.
pub(crate) fn buffer_range(
&self,
vim: &Vim,
@@ -1152,6 +1161,14 @@ impl CommandRange {
None
}
}
/// The `CommandRange` representing the entire buffer.
fn buffer() -> Self {
Self {
start: Position::Line { row: 1, offset: 0 },
end: Some(Position::LastLine { offset: 0 }),
}
}
}
fn generate_commands(_: &App) -> Vec<VimCommand> {
@@ -1421,8 +1438,12 @@ fn generate_commands(_: &App) -> Vec<VimCommand> {
VimCommand::new(("delm", "arks"), ArgumentRequired)
.bang(DeleteMarks::AllLocal)
.args(|_, args| Some(DeleteMarks::Marks(args).boxed_clone())),
VimCommand::new(("sor", "t"), SortLinesCaseSensitive).range(select_range),
VimCommand::new(("sort i", ""), SortLinesCaseInsensitive).range(select_range),
VimCommand::new(("sor", "t"), SortLinesCaseSensitive)
.range(select_range)
.default_range(CommandRange::buffer()),
VimCommand::new(("sort i", ""), SortLinesCaseInsensitive)
.range(select_range)
.default_range(CommandRange::buffer()),
VimCommand::str(("E", "xplore"), "project_panel::ToggleFocus"),
VimCommand::str(("H", "explore"), "project_panel::ToggleFocus"),
VimCommand::str(("L", "explore"), "project_panel::ToggleFocus"),
@@ -2898,4 +2919,112 @@ mod test {
);
});
}
#[gpui::test]
async fn test_sort_commands(cx: &mut TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
cx.set_state(
indoc! {"
«hornet
quirrel
elderbug
cornifer
idaˇ»
"},
Mode::Visual,
);
cx.simulate_keystrokes(": sort");
cx.simulate_keystrokes("enter");
cx.assert_state(
indoc! {"
ˇcornifer
elderbug
hornet
ida
quirrel
"},
Mode::Normal,
);
// Assert that, by default, `:sort` takes case into consideration.
cx.set_state(
indoc! {"
«hornet
quirrel
Elderbug
cornifer
idaˇ»
"},
Mode::Visual,
);
cx.simulate_keystrokes(": sort");
cx.simulate_keystrokes("enter");
cx.assert_state(
indoc! {"
ˇElderbug
cornifer
hornet
ida
quirrel
"},
Mode::Normal,
);
// Assert that, if the `i` option is passed, `:sort` ignores case.
cx.set_state(
indoc! {"
«hornet
quirrel
Elderbug
cornifer
idaˇ»
"},
Mode::Visual,
);
cx.simulate_keystrokes(": sort space i");
cx.simulate_keystrokes("enter");
cx.assert_state(
indoc! {"
ˇcornifer
Elderbug
hornet
ida
quirrel
"},
Mode::Normal,
);
// When no range is provided, sorts the whole buffer.
cx.set_state(
indoc! {"
ˇhornet
quirrel
elderbug
cornifer
ida
"},
Mode::Normal,
);
cx.simulate_keystrokes(": sort");
cx.simulate_keystrokes("enter");
cx.assert_state(
indoc! {"
ˇcornifer
elderbug
hornet
ida
quirrel
"},
Mode::Normal,
);
}
}