diff --git a/book/src/generated/typable-cmd.md b/book/src/generated/typable-cmd.md index 219f6b95f..ce43dfa5f 100644 --- a/book/src/generated/typable-cmd.md +++ b/book/src/generated/typable-cmd.md @@ -89,3 +89,6 @@ | `:read`, `:r` | Load a file into buffer | | `:echo` | Prints the given arguments to the statusline. | | `:noop` | Does nothing. | +| `:align-text-left`, `:atl` | Align text to the left | +| `:align-text-center`, `:atc` | Center-align text, optionally pass a number overriding the current document's text width | +| `:align-text-right`, `:atr` | Align text to the right, optionally pass a number overriding the current document's text width | diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 2013a9d81..b1b92e7a8 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -204,6 +204,84 @@ fn buffer_gather_paths_impl(editor: &mut Editor, args: Args) -> Vec document_ids } +fn align_text_impl( + cx: &mut compositor::Context, + args: Args, + event: PromptEvent, + format: fn(usize, &str) -> String, +) -> anyhow::Result<()> { + if event != PromptEvent::Validate { + return Ok(()); + } + let editor_text_width = cx.editor.config().text_width; + let (view, doc) = current!(cx.editor); + let text = doc.text().slice(..); + + let custom_text_width = args.first().map(|arg| { + arg.parse::() + .map_err(|_| anyhow!("Could not parse argument as a number: {arg}")) + }); + + let text_width = custom_text_width.unwrap_or(Ok(doc + .language_config() + .and_then(|c| c.text_width) + .unwrap_or(editor_text_width)))?; + + let lines = get_lines(doc, view.id); + let mut changes = Vec::with_capacity(lines.len()); + + for line_idx in lines { + let line = doc.text().line(line_idx); + let old_line = line.to_string(); + + let aligned_line = format(text_width, old_line.trim()); + let aligned_line = aligned_line.trim_end(); + let tendril = Tendril::from(aligned_line); + + changes.push(( + text.line_to_char(line_idx), + text.line_to_char(line_idx + 1) - doc.line_ending.len_chars(), + Some(tendril), + )) + } + + let transaction = Transaction::change(doc.text(), changes.into_iter()); + + doc.apply(&transaction, view.id); + + Ok(()) +} + +fn align_text_left( + cx: &mut compositor::Context, + args: Args, + event: PromptEvent, +) -> anyhow::Result<()> { + align_text_impl(cx, args, event, |text_width, text| { + format!("{: anyhow::Result<()> { + align_text_impl(cx, args, event, |text_width, text| { + format!("{:^text_width$}", text) + }) +} + +fn align_text_right( + cx: &mut compositor::Context, + args: Args, + event: PromptEvent, +) -> anyhow::Result<()> { + align_text_impl(cx, args, event, |text_width, text| { + format!("{:>text_width$}", text) + }) +} + fn buffer_close( cx: &mut compositor::Context, args: Args, @@ -3567,6 +3645,39 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ ..Signature::DEFAULT }, }, + TypableCommand { + name: "align-text-left", + aliases: &["atl"], + doc: "Align text to the left", + fun: align_text_left, + completer: CommandCompleter::none(), + signature: Signature { + positionals: (0, Some(0)), + ..Signature::DEFAULT + }, + }, + TypableCommand { + name: "align-text-center", + aliases: &["atc"], + doc: "Center-align text, optionally pass a number overriding the current document's text width", + fun: align_text_center, + completer: CommandCompleter::none(), + signature: Signature { + positionals: (0, Some(1)), + ..Signature::DEFAULT + }, + }, + TypableCommand { + name: "align-text-right", + aliases: &["atr"], + doc: "Align text to the right, optionally pass a number overriding the current document's text width", + fun: align_text_right, + completer: CommandCompleter::none(), + signature: Signature { + positionals: (0, Some(1)), + ..Signature::DEFAULT + }, + }, ]; pub static TYPABLE_COMMAND_MAP: Lazy> = diff --git a/helix-term/tests/test/commands.rs b/helix-term/tests/test/commands.rs index 29f76cfb8..66fa03656 100644 --- a/helix-term/tests/test/commands.rs +++ b/helix-term/tests/test/commands.rs @@ -2,6 +2,7 @@ use helix_term::application::Application; use super::*; +mod align_text; mod insert; mod movement; mod write; diff --git a/helix-term/tests/test/commands/align_text.rs b/helix-term/tests/test/commands/align_text.rs new file mode 100644 index 000000000..795cae565 --- /dev/null +++ b/helix-term/tests/test/commands/align_text.rs @@ -0,0 +1,65 @@ +use super::*; + +const IN: &str = indoc! {" + #[pub fn docgen() -> Result<(), DynError> { + use crate::docgen::*; + write(TYPABLE_COMMANDS_MD_OUTPUT, &typable_commands()?); + write(STATIC_COMMANDS_MD_OUTPUT, &static_commands()?); + write(LANG_SUPPORT_MD_OUTPUT, &lang_features()?); + Ok(()) + }\n|]#"}; + +#[tokio::test(flavor = "multi_thread")] +async fn left() -> anyhow::Result<()> { + test(( + IN, + ":align-text-left", + indoc! {"\ + #[pub fn docgen() -> Result<(), DynError> { + use crate::docgen::*; + write(TYPABLE_COMMANDS_MD_OUTPUT, &typable_commands()?); + write(STATIC_COMMANDS_MD_OUTPUT, &static_commands()?); + write(LANG_SUPPORT_MD_OUTPUT, &lang_features()?); + Ok(()) + }\n|]#"}, + )) + .await?; + + Ok(()) +} +#[tokio::test(flavor = "multi_thread")] +async fn center() -> anyhow::Result<()> { + test(( + IN, + ":align-text-center", + indoc! {"\ + #[ pub fn docgen() -> Result<(), DynError> { + use crate::docgen::*; + write(TYPABLE_COMMANDS_MD_OUTPUT, &typable_commands()?); + write(STATIC_COMMANDS_MD_OUTPUT, &static_commands()?); + write(LANG_SUPPORT_MD_OUTPUT, &lang_features()?); + Ok(()) + }\n|]#"}, + )) + .await?; + + Ok(()) +} +#[tokio::test(flavor = "multi_thread")] +async fn right() -> anyhow::Result<()> { + test(( + IN, + ":align-text-right", + indoc! {"\ + #[ pub fn docgen() -> Result<(), DynError> { + use crate::docgen::*; + write(TYPABLE_COMMANDS_MD_OUTPUT, &typable_commands()?); + write(STATIC_COMMANDS_MD_OUTPUT, &static_commands()?); + write(LANG_SUPPORT_MD_OUTPUT, &lang_features()?); + Ok(()) + }\n|]#"}, + )) + .await?; + + Ok(()) +}