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>
This commit is contained in:
parent
567ddef388
commit
f9baced216
7 changed files with 84 additions and 0 deletions
27
Cargo.lock
generated
27
Cargo.lock
generated
|
@ -374,6 +374,7 @@ dependencies = [
|
||||||
"slotmap",
|
"slotmap",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"smartstring",
|
"smartstring",
|
||||||
|
"textwrap",
|
||||||
"toml",
|
"toml",
|
||||||
"tree-sitter",
|
"tree-sitter",
|
||||||
"unicode-general-category",
|
"unicode-general-category",
|
||||||
|
@ -1005,6 +1006,12 @@ dependencies = [
|
||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smawk"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
version = "0.4.4"
|
version = "0.4.4"
|
||||||
|
@ -1044,6 +1051,17 @@ dependencies = [
|
||||||
"unicode-xid",
|
"unicode-xid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "textwrap"
|
||||||
|
version = "0.15.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
|
||||||
|
dependencies = [
|
||||||
|
"smawk",
|
||||||
|
"unicode-linebreak",
|
||||||
|
"unicode-width",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.30"
|
version = "1.0.30"
|
||||||
|
@ -1179,6 +1197,15 @@ version = "0.5.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1218098468b8085b19a2824104c70d976491d247ce194bbd9dc77181150cdfd6"
|
checksum = "1218098468b8085b19a2824104c70d976491d247ce194bbd9dc77181150cdfd6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-linebreak"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3a52dcaab0c48d931f7cc8ef826fa51690a08e1ea55117ef26f89864f532383f"
|
||||||
|
dependencies = [
|
||||||
|
"regex",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-normalization"
|
name = "unicode-normalization"
|
||||||
version = "0.1.19"
|
version = "0.1.19"
|
||||||
|
|
|
@ -59,6 +59,7 @@
|
||||||
| `:get-option`, `:get` | Get the current value of a config option. |
|
| `:get-option`, `:get` | Get the current value of a config option. |
|
||||||
| `:sort` | Sort ranges in selection. |
|
| `:sort` | Sort ranges in selection. |
|
||||||
| `:rsort` | Sort ranges in selection in reverse order. |
|
| `:rsort` | Sort ranges in selection in reverse order. |
|
||||||
|
| `:reflow` | Hard-wrap the current selection of lines to a given width. |
|
||||||
| `:tree-sitter-subtree`, `:ts-subtree` | Display tree sitter subtree under cursor, primarily for debugging queries. |
|
| `:tree-sitter-subtree`, `:ts-subtree` | Display tree sitter subtree under cursor, primarily for debugging queries. |
|
||||||
| `:config-reload` | Refreshes helix's config. |
|
| `:config-reload` | Refreshes helix's config. |
|
||||||
| `:config-open` | Open the helix config.toml file. |
|
| `:config-open` | Open the helix config.toml file. |
|
||||||
|
|
|
@ -41,6 +41,7 @@ encoding_rs = "0.8"
|
||||||
chrono = { version = "0.4", default-features = false, features = ["alloc", "std"] }
|
chrono = { version = "0.4", default-features = false, features = ["alloc", "std"] }
|
||||||
|
|
||||||
etcetera = "0.3"
|
etcetera = "0.3"
|
||||||
|
textwrap = "0.15.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
quickcheck = { version = "1", default-features = false }
|
quickcheck = { version = "1", default-features = false }
|
||||||
|
|
|
@ -27,6 +27,7 @@ pub mod syntax;
|
||||||
pub mod test;
|
pub mod test;
|
||||||
pub mod textobject;
|
pub mod textobject;
|
||||||
mod transaction;
|
mod transaction;
|
||||||
|
pub mod wrap;
|
||||||
|
|
||||||
pub mod unicode {
|
pub mod unicode {
|
||||||
pub use unicode_general_category as category;
|
pub use unicode_general_category as category;
|
||||||
|
|
|
@ -67,6 +67,7 @@ pub struct LanguageConfiguration {
|
||||||
pub shebangs: Vec<String>, // interpreter(s) associated with language
|
pub shebangs: Vec<String>, // interpreter(s) associated with language
|
||||||
pub roots: Vec<String>, // these indicate project roots <.git, Cargo.toml>
|
pub roots: Vec<String>, // these indicate project roots <.git, Cargo.toml>
|
||||||
pub comment_token: Option<String>,
|
pub comment_token: Option<String>,
|
||||||
|
pub max_line_length: Option<usize>,
|
||||||
|
|
||||||
#[serde(default, skip_serializing, deserialize_with = "deserialize_lsp_config")]
|
#[serde(default, skip_serializing, deserialize_with = "deserialize_lsp_config")]
|
||||||
pub config: Option<serde_json::Value>,
|
pub config: Option<serde_json::Value>,
|
||||||
|
|
7
helix-core/src/wrap.rs
Normal file
7
helix-core/src/wrap.rs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
use smartstring::{LazyCompact, SmartString};
|
||||||
|
|
||||||
|
/// Given a slice of text, return the text re-wrapped to fit it
|
||||||
|
/// within the given width.
|
||||||
|
pub fn reflow_hard_wrap(text: &str, max_line_len: usize) -> SmartString<LazyCompact> {
|
||||||
|
textwrap::refill(text, max_line_len).into()
|
||||||
|
}
|
|
@ -1051,6 +1051,45 @@ fn sort_impl(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn reflow(
|
||||||
|
cx: &mut compositor::Context,
|
||||||
|
args: &[Cow<str>],
|
||||||
|
_event: PromptEvent,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let (view, doc) = current!(cx.editor);
|
||||||
|
|
||||||
|
const DEFAULT_MAX_LEN: usize = 79;
|
||||||
|
|
||||||
|
// Find the max line length by checking the following sources in order:
|
||||||
|
// - The passed argument in `args`
|
||||||
|
// - The configured max_line_len for this language in languages.toml
|
||||||
|
// - The const default we set above
|
||||||
|
let max_line_len: usize = args
|
||||||
|
.get(0)
|
||||||
|
.map(|num| num.parse::<usize>())
|
||||||
|
.transpose()?
|
||||||
|
.or_else(|| {
|
||||||
|
doc.language_config()
|
||||||
|
.and_then(|config| config.max_line_length)
|
||||||
|
})
|
||||||
|
.unwrap_or(DEFAULT_MAX_LEN);
|
||||||
|
|
||||||
|
let rope = doc.text();
|
||||||
|
|
||||||
|
let selection = doc.selection(view.id);
|
||||||
|
let transaction = Transaction::change_by_selection(rope, selection, |range| {
|
||||||
|
let fragment = range.fragment(rope.slice(..));
|
||||||
|
let reflowed_text = helix_core::wrap::reflow_hard_wrap(&fragment, max_line_len);
|
||||||
|
|
||||||
|
(range.from(), range.to(), Some(reflowed_text))
|
||||||
|
});
|
||||||
|
|
||||||
|
doc.apply(&transaction, view.id);
|
||||||
|
doc.append_changes_to_history(view.id);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn tree_sitter_subtree(
|
fn tree_sitter_subtree(
|
||||||
cx: &mut compositor::Context,
|
cx: &mut compositor::Context,
|
||||||
_args: &[Cow<str>],
|
_args: &[Cow<str>],
|
||||||
|
@ -1570,6 +1609,13 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
|
||||||
fun: sort_reverse,
|
fun: sort_reverse,
|
||||||
completer: None,
|
completer: None,
|
||||||
},
|
},
|
||||||
|
TypableCommand {
|
||||||
|
name: "reflow",
|
||||||
|
aliases: &[],
|
||||||
|
doc: "Hard-wrap the current selection of lines to a given width.",
|
||||||
|
fun: reflow,
|
||||||
|
completer: None,
|
||||||
|
},
|
||||||
TypableCommand {
|
TypableCommand {
|
||||||
name: "tree-sitter-subtree",
|
name: "tree-sitter-subtree",
|
||||||
aliases: &["ts-subtree"],
|
aliases: &["ts-subtree"],
|
||||||
|
|
Loading…
Add table
Reference in a new issue