Skip to content

Commit

Permalink
add reflow command (#2128)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
vlmutolo and Vince Mutolo authored May 2, 2022
1 parent 567ddef commit f9baced
Show file tree
Hide file tree
Showing 7 changed files with 84 additions and 0 deletions.
27 changes: 27 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions book/src/generated/typable-cmd.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
| `:get-option`, `:get` | Get the current value of a config option. |
| `:sort` | Sort ranges in selection. |
| `: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. |
| `:config-reload` | Refreshes helix's config. |
| `:config-open` | Open the helix config.toml file. |
Expand Down
1 change: 1 addition & 0 deletions helix-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ encoding_rs = "0.8"
chrono = { version = "0.4", default-features = false, features = ["alloc", "std"] }

etcetera = "0.3"
textwrap = "0.15.0"

[dev-dependencies]
quickcheck = { version = "1", default-features = false }
1 change: 1 addition & 0 deletions helix-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pub mod syntax;
pub mod test;
pub mod textobject;
mod transaction;
pub mod wrap;

pub mod unicode {
pub use unicode_general_category as category;
Expand Down
1 change: 1 addition & 0 deletions helix-core/src/syntax.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ pub struct LanguageConfiguration {
pub shebangs: Vec<String>, // interpreter(s) associated with language
pub roots: Vec<String>, // these indicate project roots <.git, Cargo.toml>
pub comment_token: Option<String>,
pub max_line_length: Option<usize>,

#[serde(default, skip_serializing, deserialize_with = "deserialize_lsp_config")]
pub config: Option<serde_json::Value>,
Expand Down
7 changes: 7 additions & 0 deletions helix-core/src/wrap.rs
Original file line number Diff line number Diff line change
@@ -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()
}
46 changes: 46 additions & 0 deletions helix-term/src/commands/typed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1051,6 +1051,45 @@ fn sort_impl(
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(
cx: &mut compositor::Context,
_args: &[Cow<str>],
Expand Down Expand Up @@ -1570,6 +1609,13 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
fun: sort_reverse,
completer: None,
},
TypableCommand {
name: "reflow",
aliases: &[],
doc: "Hard-wrap the current selection of lines to a given width.",
fun: reflow,
completer: None,
},
TypableCommand {
name: "tree-sitter-subtree",
aliases: &["ts-subtree"],
Expand Down

0 comments on commit f9baced

Please sign in to comment.