Pipe typable command (#1972)

Co-authored-by: DeviousStoat <devious@stoat.com>
imgbot
Thomas 2 years ago committed by GitHub
parent dc8fef5dd3
commit 2eca2901f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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. |

@ -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);
},
);
}

@ -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>> =

Loading…
Cancel
Save