on master help sign in
Pipe typable command (#1972)
2eca290Thomas
book/src/generated/typable-cmd.md+1
@@ -61,3 +61,4 @@
61| `:tree-sitter-subtree`, `:ts-subtree` | Display tree sitter subtree under cursor, primarily for debugging queries. |
61| `:tree-sitter-subtree`, `:ts-subtree` | Display tree sitter subtree under cursor, primarily for debugging queries. |
62| `:config-reload` | Refreshes helix's config. |
62| `:config-reload` | Refreshes helix's config. |
63| `:config-open` | Open the helix config.toml file. |
63| `:config-open` | Open the helix config.toml file. |
64+| `:pipe` | Pipe each selection to the shell command. |
helix-term/src/commands.rs+49−45
@@ -4150,19 +4150,19 @@ enum ShellBehavior {
4150}
4150}
4151
4151
4152fn shell_pipe(cx: &mut Context) {
4152fn shell_pipe(cx: &mut Context) {
4153− shell(cx, "pipe:".into(), ShellBehavior::Replace);
4153+ shell_prompt(cx, "pipe:".into(), ShellBehavior::Replace);
4154}
4154}
4155
4155
4156fn shell_pipe_to(cx: &mut Context) {
4156fn shell_pipe_to(cx: &mut Context) {
4157− shell(cx, "pipe-to:".into(), ShellBehavior::Ignore);
4157+ shell_prompt(cx, "pipe-to:".into(), ShellBehavior::Ignore);
4158}
4158}
4159
4159
4160fn shell_insert_output(cx: &mut Context) {
4160fn shell_insert_output(cx: &mut Context) {
4161− shell(cx, "insert-output:".into(), ShellBehavior::Insert);
4161+ shell_prompt(cx, "insert-output:".into(), ShellBehavior::Insert);
4162}
4162}
4163
4163
4164fn shell_append_output(cx: &mut Context) {
4164fn shell_append_output(cx: &mut Context) {
4165− shell(cx, "append-output:".into(), ShellBehavior::Append);
4165+ shell_prompt(cx, "append-output:".into(), ShellBehavior::Append);
4166}
4166}
4167
4167
4168fn shell_keep_pipe(cx: &mut Context) {
4168fn shell_keep_pipe(cx: &mut Context) {
@@ -4256,65 +4256,69 @@ fn shell_impl(
4256 Ok((tendril, output.status.success()))
4256 Ok((tendril, output.status.success()))
4257}
4257}
4258
4258
4259−fn shell(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBehavior) {
4259+fn shell(cx: &mut compositor::Context, cmd: &str, behavior: &ShellBehavior) {
4260 let pipe = match behavior {
4260 let pipe = match behavior {
4261 ShellBehavior::Replace | ShellBehavior::Ignore => true,
4261 ShellBehavior::Replace | ShellBehavior::Ignore => true,
4262 ShellBehavior::Insert | ShellBehavior::Append => false,
4262 ShellBehavior::Insert | ShellBehavior::Append => false,
4263 };
4263 };
4264
4264
4265+ let config = cx.editor.config();
4266+ let shell = &config.shell;
4267+ let (view, doc) = current!(cx.editor);
4268+ let selection = doc.selection(view.id);
4269+
4270+ let mut changes = Vec::with_capacity(selection.len());
4271+ let text = doc.text().slice(..);
4272+
4273+ for range in selection.ranges() {
4274+ let fragment = range.fragment(text);
4275+ let (output, success) = match shell_impl(shell, cmd, pipe.then(|| fragment.as_bytes())) {
4276+ Ok(result) => result,
4277+ Err(err) => {
4278+ cx.editor.set_error(err.to_string());
4279+ return;
4280+ }
4281+ };
4282+
4283+ if !success {
4284+ cx.editor.set_error("Command failed");
4285+ return;
4286+ }
4287+
4288+ let (from, to) = match behavior {
4289+ ShellBehavior::Replace => (range.from(), range.to()),
4290+ ShellBehavior::Insert => (range.from(), range.from()),
4291+ ShellBehavior::Append => (range.to(), range.to()),
4292+ _ => (range.from(), range.from()),
4293+ };
4294+ changes.push((from, to, Some(output)));
4295+ }
4296+
4297+ if behavior != &ShellBehavior::Ignore {
4298+ let transaction = Transaction::change(doc.text(), changes.into_iter());
4299+ doc.apply(&transaction, view.id);
4300+ }
4301+
4302+ // after replace cursor may be out of bounds, do this to
4303+ // make sure cursor is in view and update scroll as well
4304+ view.ensure_cursor_in_view(doc, config.scrolloff);
4305+}
4306+
4307+fn shell_prompt(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBehavior) {
4265 ui::prompt(
4308 ui::prompt(
4266 cx,
4309 cx,
4267 prompt,
4310 prompt,
4268 Some('|'),
4311 Some('|'),
4269 ui::completers::none,
4312 ui::completers::none,
4270 move |cx, input: &str, event: PromptEvent| {
4313 move |cx, input: &str, event: PromptEvent| {
4271− let config = cx.editor.config();
4272− let shell = &config.shell;
4273 if event != PromptEvent::Validate {
4314 if event != PromptEvent::Validate {
4274 return;
4315 return;
4275 }
4316 }
4276 if input.is_empty() {
4317 if input.is_empty() {
4277 return;
4318 return;
4278 }
4319 }
4279− let (view, doc) = current!(cx.editor);
4280− let selection = doc.selection(view.id);
4281−
4282− let mut changes = Vec::with_capacity(selection.len());
4283− let text = doc.text().slice(..);
4284−
4285− for range in selection.ranges() {
4286− let fragment = range.fragment(text);
4287− let (output, success) =
4288− match shell_impl(shell, input, pipe.then(|| fragment.as_bytes())) {
4289− Ok(result) => result,
4290− Err(err) => {
4291− cx.editor.set_error(err.to_string());
4292− return;
4293− }
4294− };
4295−
4296− if !success {
4297− cx.editor.set_error("Command failed");
4298− return;
4299− }
4300−
4301− let (from, to) = match behavior {
4302− ShellBehavior::Replace => (range.from(), range.to()),
4303− ShellBehavior::Insert => (range.from(), range.from()),
4304− ShellBehavior::Append => (range.to(), range.to()),
4305− _ => (range.from(), range.from()),
4306− };
4307− changes.push((from, to, Some(output)));
4308− }
4309−
4310− if behavior != ShellBehavior::Ignore {
4311− let transaction = Transaction::change(doc.text(), changes.into_iter());
4312− doc.apply(&transaction, view.id);
4313− }
4314
4320
4315− // after replace cursor may be out of bounds, do this to
4321+ shell(cx, input, &behavior);
4316− // make sure cursor is in view and update scroll as well
4317− view.ensure_cursor_in_view(doc, config.scrolloff);
4318 },
4322 },
4319 );
4323 );
4320}
4324}
helix-term/src/commands/typed.rs+17
@@ -1067,6 +1067,16 @@ fn refresh_config(
1067 Ok(())
1067 Ok(())
1068}
1068}
1069
1069
1070+fn pipe(
1071+ cx: &mut compositor::Context,
1072+ args: &[Cow<str>],
1073+ _event: PromptEvent,
1074+) -> anyhow::Result<()> {
1075+ ensure!(!args.is_empty(), "Shell command required");
1076+ shell(cx, &args.join(" "), &ShellBehavior::Replace);
1077+ Ok(())
1078+}
1079+
1070pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
1080pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
1071 TypableCommand {
1081 TypableCommand {
1072 name: "quit",
1082 name: "quit",
@@ -1495,6 +1505,13 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
1495 fun: open_config,
1505 fun: open_config,
1496 completer: None,
1506 completer: None,
1497 },
1507 },
1508+ TypableCommand {
1509+ name: "pipe",
1510+ aliases: &[],
1511+ doc: "Pipe each selection to the shell command.",
1512+ fun: pipe,
1513+ completer: None,
1514+ },
1498 ];
1515 ];
1499
1516
1500pub static TYPABLE_COMMAND_MAP: Lazy<HashMap<&'static str, &'static TypableCommand>> =
1517pub static TYPABLE_COMMAND_MAP: Lazy<HashMap<&'static str, &'static TypableCommand>> =