aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--book/src/generated/typable-cmd.md1
-rw-r--r--helix-term/src/commands.rs94
-rw-r--r--helix-term/src/commands/typed.rs17
3 files changed, 67 insertions, 45 deletions
diff --git a/book/src/generated/typable-cmd.md b/book/src/generated/typable-cmd.md
index bb5da3bb..0b591ba4 100644
--- a/book/src/generated/typable-cmd.md
+++ b/book/src/generated/typable-cmd.md
@@ -61,3 +61,4 @@
| `: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. |
+| `:pipe` | Pipe each selection to the shell command. |
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index 886ee62d..90226c53 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -4150,19 +4150,19 @@ enum ShellBehavior {
}
fn shell_pipe(cx: &mut Context) {
- shell(cx, "pipe:".into(), ShellBehavior::Replace);
+ shell_prompt(cx, "pipe:".into(), ShellBehavior::Replace);
}
fn shell_pipe_to(cx: &mut Context) {
- shell(cx, "pipe-to:".into(), ShellBehavior::Ignore);
+ shell_prompt(cx, "pipe-to:".into(), ShellBehavior::Ignore);
}
fn shell_insert_output(cx: &mut Context) {
- shell(cx, "insert-output:".into(), ShellBehavior::Insert);
+ shell_prompt(cx, "insert-output:".into(), ShellBehavior::Insert);
}
fn shell_append_output(cx: &mut Context) {
- shell(cx, "append-output:".into(), ShellBehavior::Append);
+ shell_prompt(cx, "append-output:".into(), ShellBehavior::Append);
}
fn shell_keep_pipe(cx: &mut Context) {
@@ -4256,65 +4256,69 @@ fn shell_impl(
Ok((tendril, output.status.success()))
}
-fn shell(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBehavior) {
+fn shell(cx: &mut compositor::Context, cmd: &str, behavior: &ShellBehavior) {
let pipe = match behavior {
ShellBehavior::Replace | ShellBehavior::Ignore => true,
ShellBehavior::Insert | ShellBehavior::Append => false,
};
+ let config = cx.editor.config();
+ let shell = &config.shell;
+ let (view, doc) = current!(cx.editor);
+ let selection = doc.selection(view.id);
+
+ let mut changes = Vec::with_capacity(selection.len());
+ let text = doc.text().slice(..);
+
+ for range in selection.ranges() {
+ let fragment = range.fragment(text);
+ let (output, success) = match shell_impl(shell, cmd, pipe.then(|| fragment.as_bytes())) {
+ Ok(result) => result,
+ Err(err) => {
+ cx.editor.set_error(err.to_string());
+ return;
+ }
+ };
+
+ if !success {
+ cx.editor.set_error("Command failed");
+ return;
+ }
+
+ let (from, to) = match behavior {
+ ShellBehavior::Replace => (range.from(), range.to()),
+ ShellBehavior::Insert => (range.from(), range.from()),
+ ShellBehavior::Append => (range.to(), range.to()),
+ _ => (range.from(), range.from()),
+ };
+ changes.push((from, to, Some(output)));
+ }
+
+ if behavior != &ShellBehavior::Ignore {
+ let transaction = Transaction::change(doc.text(), changes.into_iter());
+ doc.apply(&transaction, view.id);
+ }
+
+ // 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);
+}
+
+fn shell_prompt(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBehavior) {
ui::prompt(
cx,
prompt,
Some('|'),
ui::completers::none,
move |cx, input: &str, event: PromptEvent| {
- let config = cx.editor.config();
- let shell = &config.shell;
if event != PromptEvent::Validate {
return;
}
if input.is_empty() {
return;
}
- let (view, doc) = current!(cx.editor);
- let selection = doc.selection(view.id);
-
- let mut changes = Vec::with_capacity(selection.len());
- let text = doc.text().slice(..);
-
- for range in selection.ranges() {
- let fragment = range.fragment(text);
- let (output, success) =
- match shell_impl(shell, input, pipe.then(|| fragment.as_bytes())) {
- Ok(result) => result,
- Err(err) => {
- cx.editor.set_error(err.to_string());
- return;
- }
- };
-
- if !success {
- cx.editor.set_error("Command failed");
- return;
- }
-
- let (from, to) = match behavior {
- ShellBehavior::Replace => (range.from(), range.to()),
- ShellBehavior::Insert => (range.from(), range.from()),
- ShellBehavior::Append => (range.to(), range.to()),
- _ => (range.from(), range.from()),
- };
- changes.push((from, to, Some(output)));
- }
-
- if behavior != ShellBehavior::Ignore {
- let transaction = Transaction::change(doc.text(), changes.into_iter());
- doc.apply(&transaction, view.id);
- }
- // 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);
+ shell(cx, input, &behavior);
},
);
}
diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs
index d158388f..9a5298bb 100644
--- a/helix-term/src/commands/typed.rs
+++ b/helix-term/src/commands/typed.rs
@@ -1067,6 +1067,16 @@ fn refresh_config(
Ok(())
}
+fn pipe(
+ cx: &mut compositor::Context,
+ args: &[Cow<str>],
+ _event: PromptEvent,
+) -> anyhow::Result<()> {
+ ensure!(!args.is_empty(), "Shell command required");
+ shell(cx, &args.join(" "), &ShellBehavior::Replace);
+ Ok(())
+}
+
pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
TypableCommand {
name: "quit",
@@ -1495,6 +1505,13 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
fun: open_config,
completer: None,
},
+ TypableCommand {
+ name: "pipe",
+ aliases: &[],
+ doc: "Pipe each selection to the shell command.",
+ fun: pipe,
+ completer: None,
+ },
];
pub static TYPABLE_COMMAND_MAP: Lazy<HashMap<&'static str, &'static TypableCommand>> =