helix: Add support for line length in reflow command (#52152)

Dino created

* Add `editor::RewrapOptions::line_length` to, optionally, override the
  line length used when rewrapping text.
* Update `editor::Editor::rewrap_impl` to prefer
  `editor::RewrapOptions::line_length`, when set.
* Add a `line_length` field to the `vim::rewrap::Rewrap` action.
* Update the `:reflow` vim command with `vim::command::VimCommand::args`
  so as to be able to parse the provided argument as `usize`, ensuring
  that no effect is taken if the argument can't be parsed as such.

Release Notes:

- N/A

Change summary

crates/editor/src/editor.rs    |  4 +
crates/git_ui/src/git_panel.rs |  1 
crates/vim/src/command.rs      | 61 ++++++++++++++++++++++++++++++-----
crates/vim/src/rewrap.rs       | 20 ++++++-----
4 files changed, 67 insertions(+), 19 deletions(-)

Detailed changes

crates/editor/src/editor.rs ๐Ÿ”—

@@ -1869,6 +1869,7 @@ pub enum MultibufferSelectionMode {
 pub struct RewrapOptions {
     pub override_language_settings: bool,
     pub preserve_existing_whitespace: bool,
+    pub line_length: Option<usize>,
 }
 
 impl Editor {
@@ -5150,6 +5151,7 @@ impl Editor {
                         RewrapOptions {
                             override_language_settings: true,
                             preserve_existing_whitespace: true,
+                            line_length: None,
                         },
                         cx,
                     )
@@ -13721,7 +13723,7 @@ impl Editor {
                 continue;
             };
 
-            let wrap_column = self.hard_wrap.unwrap_or_else(|| {
+            let wrap_column = options.line_length.or(self.hard_wrap).unwrap_or_else(|| {
                 buffer
                     .language_settings_at(Point::new(start_row, 0), cx)
                     .preferred_line_length as usize

crates/git_ui/src/git_panel.rs ๐Ÿ”—

@@ -2276,6 +2276,7 @@ impl GitPanel {
                 RewrapOptions {
                     override_language_settings: false,
                     preserve_existing_whitespace: true,
+                    line_length: None,
                 },
                 cx,
             );

crates/vim/src/command.rs ๐Ÿ”—

@@ -1726,7 +1726,15 @@ fn generate_commands(_: &App) -> Vec<VimCommand> {
         )
         .range(wrap_count),
         VimCommand::new(("j", "oin"), JoinLines).range(select_range),
-        VimCommand::new(("reflow", ""), Rewrap).range(select_range),
+        VimCommand::new(("reflow", ""), Rewrap { line_length: None })
+            .range(select_range)
+            .args(|_action, args| {
+                args.parse::<usize>().map_or(None, |length| {
+                    Some(Box::new(Rewrap {
+                        line_length: Some(length),
+                    }))
+                })
+            }),
         VimCommand::new(("fo", "ld"), editor::actions::FoldSelectedRanges).range(act_on_range),
         VimCommand::new(("foldo", "pen"), editor::actions::UnfoldLines)
             .bang(editor::actions::UnfoldRecursive)
@@ -3550,7 +3558,7 @@ mod test {
 
         cx.set_state(
             indoc! {"
-                ห‡0123456789 0123456789 0123456789 0123456789
+                ห‡0123456789 0123456789
             "},
             Mode::Normal,
         );
@@ -3560,8 +3568,6 @@ mod test {
 
         cx.assert_state(
             indoc! {"
-                0123456789
-                0123456789
                 0123456789
                 ห‡0123456789
             "},
@@ -3570,22 +3576,59 @@ mod test {
 
         cx.set_state(
             indoc! {"
-                ยซ0123456789 0123456789ห‡ยป
-                0123456789 0123456789
+                ห‡0123456789 0123456789
             "},
             Mode::VisualLine,
         );
 
-        cx.simulate_keystrokes(": reflow");
+        cx.simulate_keystrokes("shift-v : reflow");
         cx.simulate_keystrokes("enter");
 
         cx.assert_state(
             indoc! {"
-                ห‡0123456789
                 0123456789
-                0123456789 0123456789
+                ห‡0123456789
             "},
             Mode::Normal,
         );
+
+        cx.set_state(
+            indoc! {"
+                ห‡0123 4567 0123 4567
+            "},
+            Mode::VisualLine,
+        );
+
+        cx.simulate_keystrokes(": reflow space 7");
+        cx.simulate_keystrokes("enter");
+
+        cx.assert_state(
+            indoc! {"
+                ห‡0123
+                4567
+                0123
+                4567
+            "},
+            Mode::Normal,
+        );
+
+        // Assert that, if `:reflow` is invoked with an invalid argument, it
+        // does not actually have any effect in the buffer's contents.
+        cx.set_state(
+            indoc! {"
+                ห‡0123 4567 0123 4567
+            "},
+            Mode::VisualLine,
+        );
+
+        cx.simulate_keystrokes(": reflow space a");
+        cx.simulate_keystrokes("enter");
+
+        cx.assert_state(
+            indoc! {"
+                ห‡0123 4567 0123 4567
+            "},
+            Mode::VisualLine,
+        );
     }
 }

crates/vim/src/rewrap.rs ๐Ÿ”—

@@ -1,19 +1,20 @@
 use crate::{Vim, motion::Motion, object::Object, state::Mode};
 use collections::HashMap;
 use editor::{Bias, Editor, RewrapOptions, SelectionEffects, display_map::ToDisplayPoint};
-use gpui::{Context, Window, actions};
+use gpui::{Action, Context, Window};
 use language::SelectionGoal;
+use schemars::JsonSchema;
+use serde::Deserialize;
 
-actions!(
-    vim,
-    [
-        /// Rewraps the selected text to fit within the line width.
-        Rewrap
-    ]
-);
+/// Rewraps the selected text to fit within the line width.
+#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)]
+#[action(namespace = vim)]
+pub(crate) struct Rewrap {
+    pub line_length: Option<usize>,
+}
 
 pub(crate) fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
-    Vim::action(editor, cx, |vim, _: &Rewrap, window, cx| {
+    Vim::action(editor, cx, |vim, action: &Rewrap, window, cx| {
         vim.record_current_action(cx);
         Vim::take_count(cx);
         Vim::take_forced_motion(cx);
@@ -24,6 +25,7 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
                 editor.rewrap_impl(
                     RewrapOptions {
                         override_language_settings: true,
+                        line_length: action.line_length,
                         ..Default::default()
                     },
                     cx,