From 3c76d750a7ffdebfb1f06d37d8bc0162ed0dfbf8 Mon Sep 17 00:00:00 2001 From: L-Trump Date: Sat, 14 Sep 2024 04:39:35 +0800 Subject: [PATCH] feat: execute helix command from pipe output --- book/src/generated/typable-cmd.md | 1 + book/src/keymap.md | 1 + helix-term/src/commands.rs | 49 ++++++++++++++++++++++++++----- helix-term/src/commands/typed.rs | 15 ++++++++++ helix-term/src/keymap/default.rs | 1 + 5 files changed, 60 insertions(+), 7 deletions(-) diff --git a/book/src/generated/typable-cmd.md b/book/src/generated/typable-cmd.md index f48e1490a038..69538af31127 100644 --- a/book/src/generated/typable-cmd.md +++ b/book/src/generated/typable-cmd.md @@ -81,6 +81,7 @@ | `:append-output` | Run shell command, appending output after each selection. | | `:pipe` | Pipe each selection to the shell command. | | `:pipe-to` | Pipe each selection to the shell command, ignoring output. | +| `:pipe-execute` | Pipe each selection to the shell command, execute output as helix command. | | `:run-shell-command`, `:sh` | Run a shell command | | `:reset-diff-change`, `:diffget`, `:diffg` | Reset the diff change at the cursor position. | | `:clear-register` | Clear given register. If no argument is provided, clear all registers. | diff --git a/book/src/keymap.md b/book/src/keymap.md index e7ae6ae4779e..7724c4e5f3bd 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -108,6 +108,7 @@ Normal mode is the default mode when you launch helix. You can return to it from | `!` | Run shell command, inserting output before each selection | `shell_insert_output` | | `Alt-!` | Run shell command, appending output after each selection | `shell_append_output` | | `$` | Pipe each selection into shell command, keep selections where command returned 0 | `shell_keep_pipe` | +| `Alt-$` | Pipe each selection into shell command, execute output as helix command | `shell_keep_pipe` | ### Selection manipulation diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 6e037a471ffc..02dbf4dc768a 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -63,6 +63,7 @@ use crate::{ }; use crate::job::{self, Jobs}; +use std::str::FromStr; use std::{ cmp::Ordering, collections::{HashMap, HashSet}, @@ -555,6 +556,7 @@ impl MappableCommand { dap_disable_exceptions, "Disable exception breakpoints", shell_pipe, "Pipe selections through shell command", shell_pipe_to, "Pipe selections into shell command ignoring output", + shell_pipe_execute, "Pipe selections into shell command, execute output as command", shell_insert_output, "Insert shell command output before selections", shell_append_output, "Append shell command output after selections", shell_keep_pipe, "Filter selections with shell predicate", @@ -5693,6 +5695,7 @@ enum ShellBehavior { Ignore, Insert, Append, + Execute, } fn shell_pipe(cx: &mut Context) { @@ -5703,6 +5706,10 @@ fn shell_pipe_to(cx: &mut Context) { shell_prompt(cx, "pipe-to:".into(), ShellBehavior::Ignore); } +fn shell_pipe_execute(cx: &mut Context) { + shell_prompt(cx, "pipe-execute:".into(), ShellBehavior::Execute); +} + fn shell_insert_output(cx: &mut Context) { shell_prompt(cx, "insert-output:".into(), ShellBehavior::Insert); } @@ -5829,7 +5836,7 @@ async fn shell_impl_async( fn shell(cx: &mut compositor::Context, cmd: &str, behavior: &ShellBehavior) { let pipe = match behavior { - ShellBehavior::Replace | ShellBehavior::Ignore => true, + ShellBehavior::Replace | ShellBehavior::Ignore | ShellBehavior::Execute => true, ShellBehavior::Insert | ShellBehavior::Append => false, }; @@ -5876,6 +5883,7 @@ fn shell(cx: &mut compositor::Context, cmd: &str, behavior: &ShellBehavior) { ShellBehavior::Replace => (range.from(), range.to(), range.len()), ShellBehavior::Insert => (range.from(), range.from(), 0), ShellBehavior::Append => (range.to(), range.to(), 0), + ShellBehavior::Execute => (range.from(), range.from(), 0), _ => (range.from(), range.from(), 0), }; @@ -5896,16 +5904,43 @@ fn shell(cx: &mut compositor::Context, cmd: &str, behavior: &ShellBehavior) { changes.push((from, to, Some(output))); } - if behavior != &ShellBehavior::Ignore { - let transaction = Transaction::change(doc.text(), changes.into_iter()) - .with_selection(Selection::new(ranges, selection.primary_index())); - doc.apply(&transaction, view.id); - doc.append_changes_to_history(view); - } + let mut commands = Vec::with_capacity(changes.len()); + match behavior { + ShellBehavior::Ignore => (), + ShellBehavior::Execute => { + for (_, _, command_text) in changes { + if let Some(command_text) = command_text { + if let Ok(command) = MappableCommand::from_str(&command_text[..]) { + commands.push(command) + } + } + } + } + _ => { + let transaction = Transaction::change(doc.text(), changes.into_iter()) + .with_selection(Selection::new(ranges, selection.primary_index())); + doc.apply(&transaction, view.id); + doc.append_changes_to_history(view); + } + }; // after replace cursor may be out of bounds, do this to // make sure cursor is in view and update scroll as well view.ensure_cursor_in_view(doc, config.scrolloff); + + if behavior == &ShellBehavior::Execute { + for command in commands { + let mut ctx = Context { + register: None, + count: None, + editor: cx.editor, + callback: Vec::new(), + on_next_key_callback: None, + jobs: cx.jobs, + }; + command.execute(&mut ctx); + } + } } fn shell_prompt(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBehavior) { diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 7ad0369fc1bd..bcdf4a694386 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -2263,6 +2263,14 @@ fn pipe_to( pipe_impl(cx, args, event, &ShellBehavior::Ignore) } +fn pipe_execute( + cx: &mut compositor::Context, + args: &[Cow], + event: PromptEvent, +) -> anyhow::Result<()> { + pipe_impl(cx, args, event, &ShellBehavior::Execute) +} + fn pipe(cx: &mut compositor::Context, args: &[Cow], event: PromptEvent) -> anyhow::Result<()> { pipe_impl(cx, args, event, &ShellBehavior::Replace) } @@ -3092,6 +3100,13 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ fun: pipe_to, signature: CommandSignature::none(), }, + TypableCommand { + name: "pipe-execute", + aliases: &[], + doc: "Pipe each selection to the shell command, execute output as command.", + fun: pipe_execute, + signature: CommandSignature::none(), + }, TypableCommand { name: "run-shell-command", aliases: &["sh"], diff --git a/helix-term/src/keymap/default.rs b/helix-term/src/keymap/default.rs index 5a3e8eed42da..78e80a3c89fe 100644 --- a/helix-term/src/keymap/default.rs +++ b/helix-term/src/keymap/default.rs @@ -328,6 +328,7 @@ pub fn default() -> HashMap { "!" => shell_insert_output, "A-!" => shell_append_output, "$" => shell_keep_pipe, + "A-$" => shell_pipe_execute, "C-z" => suspend, "C-a" => increment,