on master help sign in
add reflow command (#2128)

* add reflow command Users need to be able to hard-wrap text for many applications, including comments in code, git commit messages, plaintext documentation, etc. It often falls to the user to manually insert line breaks where appropriate in order to hard-wrap text. This commit introduces the "reflow" command (both in the TUI and core library) to automatically hard-wrap selected text to a given number of characters (defined by Unicode "extended grapheme clusters"). It handles lines with a repeated prefix, such as comments ("//") and indentation. * reflow: consider newlines to be word separators * replace custom reflow impl with textwrap crate * Sync reflow command docs with book * reflow: add default max_line_len language setting Co-authored-by: Vince Mutolo <vince@mutolo.org>

commitf9baced216df60122f2aae08d382931d77901ca9 authorVince Mutolo <vlmutolo@me.com> dateMon 2 May 2022, 10:24 (4 years ago) parent567ddef
7 files changed+84
Cargo.lock+27 book/src/generated/typable-cmd.md+1 helix-core/Cargo.toml+1 helix-core/src/lib.rs+1 helix-core/src/syntax.rs+1 helix-core/src/wrap.rs+7 helix-term/src/commands/typed.rs+46
Cargo.lock+27
@@ -374,6 +374,7 @@ dependencies = [
374374 "slotmap",
375375 "smallvec",
376376 "smartstring",
377+ "textwrap",
377378 "toml",
378379 "tree-sitter",
379380 "unicode-general-category",
@@ -1005,6 +1006,12 @@ dependencies = [
10051006 "version_check",
10061007]
10071008
1009+[[package]]
1010+name = "smawk"
1011+version = "0.3.1"
1012+source = "registry+https://github.com/rust-lang/crates.io-index"
1013+checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043"
1014+
10081015[[package]]
10091016name = "socket2"
10101017version = "0.4.4"
@@ -1044,6 +1051,17 @@ dependencies = [
10441051 "unicode-xid",
10451052]
10461053
1054+[[package]]
1055+name = "textwrap"
1056+version = "0.15.0"
1057+source = "registry+https://github.com/rust-lang/crates.io-index"
1058+checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
1059+dependencies = [
1060+ "smawk",
1061+ "unicode-linebreak",
1062+ "unicode-width",
1063+]
1064+
10471065[[package]]
10481066name = "thiserror"
10491067version = "1.0.30"
@@ -1179,6 +1197,15 @@ version = "0.5.1"
11791197source = "registry+https://github.com/rust-lang/crates.io-index"
11801198checksum = "1218098468b8085b19a2824104c70d976491d247ce194bbd9dc77181150cdfd6"
11811199
1200+[[package]]
1201+name = "unicode-linebreak"
1202+version = "0.1.2"
1203+source = "registry+https://github.com/rust-lang/crates.io-index"
1204+checksum = "3a52dcaab0c48d931f7cc8ef826fa51690a08e1ea55117ef26f89864f532383f"
1205+dependencies = [
1206+ "regex",
1207+]
1208+
11821209[[package]]
11831210name = "unicode-normalization"
11841211version = "0.1.19"
book/src/generated/typable-cmd.md+1
@@ -59,6 +59,7 @@
5959| `:get-option`, `:get` | Get the current value of a config option. |
6060| `:sort` | Sort ranges in selection. |
6161| `:rsort` | Sort ranges in selection in reverse order. |
62+| `:reflow` | Hard-wrap the current selection of lines to a given width. |
6263| `:tree-sitter-subtree`, `:ts-subtree` | Display tree sitter subtree under cursor, primarily for debugging queries. |
6364| `:config-reload` | Refreshes helix's config. |
6465| `:config-open` | Open the helix config.toml file. |
helix-core/Cargo.toml+1
@@ -41,6 +41,7 @@ encoding_rs = "0.8"
4141chrono = { version = "0.4", default-features = false, features = ["alloc", "std"] }
4242
4343etcetera = "0.3"
44+textwrap = "0.15.0"
4445
4546[dev-dependencies]
4647quickcheck = { version = "1", default-features = false }
helix-core/src/lib.rs+1
@@ -27,6 +27,7 @@ pub mod syntax;
2727pub mod test;
2828pub mod textobject;
2929mod transaction;
30+pub mod wrap;
3031
3132pub mod unicode {
3233 pub use unicode_general_category as category;
helix-core/src/syntax.rs+1
@@ -67,6 +67,7 @@ pub struct LanguageConfiguration {
6767 pub shebangs: Vec<String>, // interpreter(s) associated with language
6868 pub roots: Vec<String>, // these indicate project roots <.git, Cargo.toml>
6969 pub comment_token: Option<String>,
70+ pub max_line_length: Option<usize>,
7071
7172 #[serde(default, skip_serializing, deserialize_with = "deserialize_lsp_config")]
7273 pub config: Option<serde_json::Value>,
helix-core/src/wrap.rsnew file+7
@@ -0,0 +1,7 @@
1+use smartstring::{LazyCompact, SmartString};
2+
3+/// Given a slice of text, return the text re-wrapped to fit it
4+/// within the given width.
5+pub fn reflow_hard_wrap(text: &str, max_line_len: usize) -> SmartString<LazyCompact> {
6+ textwrap::refill(text, max_line_len).into()
7+}
helix-term/src/commands/typed.rs+46
@@ -1051,6 +1051,45 @@ fn sort_impl(
10511051 Ok(())
10521052}
10531053
1054+fn reflow(
1055+ cx: &mut compositor::Context,
1056+ args: &[Cow<str>],
1057+ _event: PromptEvent,
1058+) -> anyhow::Result<()> {
1059+ let (view, doc) = current!(cx.editor);
1060+
1061+ const DEFAULT_MAX_LEN: usize = 79;
1062+
1063+ // Find the max line length by checking the following sources in order:
1064+ // - The passed argument in `args`
1065+ // - The configured max_line_len for this language in languages.toml
1066+ // - The const default we set above
1067+ let max_line_len: usize = args
1068+ .get(0)
1069+ .map(|num| num.parse::<usize>())
1070+ .transpose()?
1071+ .or_else(|| {
1072+ doc.language_config()
1073+ .and_then(|config| config.max_line_length)
1074+ })
1075+ .unwrap_or(DEFAULT_MAX_LEN);
1076+
1077+ let rope = doc.text();
1078+
1079+ let selection = doc.selection(view.id);
1080+ let transaction = Transaction::change_by_selection(rope, selection, |range| {
1081+ let fragment = range.fragment(rope.slice(..));
1082+ let reflowed_text = helix_core::wrap::reflow_hard_wrap(&fragment, max_line_len);
1083+
1084+ (range.from(), range.to(), Some(reflowed_text))
1085+ });
1086+
1087+ doc.apply(&transaction, view.id);
1088+ doc.append_changes_to_history(view.id);
1089+
1090+ Ok(())
1091+}
1092+
10541093fn tree_sitter_subtree(
10551094 cx: &mut compositor::Context,
10561095 _args: &[Cow<str>],
@@ -1570,6 +1609,13 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
15701609 fun: sort_reverse,
15711610 completer: None,
15721611 },
1612+ TypableCommand {
1613+ name: "reflow",
1614+ aliases: &[],
1615+ doc: "Hard-wrap the current selection of lines to a given width.",
1616+ fun: reflow,
1617+ completer: None,
1618+ },
15731619 TypableCommand {
15741620 name: "tree-sitter-subtree",
15751621 aliases: &["ts-subtree"],