on master help sign in
Reimplement keep-pipe, it needs to manipulate selections, not text
ce7ad2bBlaž Hrastnik
book/src/keymap.md+7−7
@@ -124,13 +124,13 @@ in reverse, or searching via smartcase.
124
124
125### Shell
125### Shell
126
126
127−| Key | Description | Command |
127+| Key | Description | Command |
128−| ------ | ----------- | ------- |
128+| ------ | ----------- | ------- |
129−| `\|` | Pipe each selection through shell command, replacing with output | `shell_pipe` |
129+| `\|` | Pipe each selection through shell command, replacing with output | `shell_pipe` |
130−| `A-\|` | Pipe each selection into shell command, ignoring output | `shell_pipe_to` |
130+| `A-\|` | Pipe each selection into shell command, ignoring output | `shell_pipe_to` |
131−| `!` | Run shell command, inserting output before each selection | `shell_insert_output` |
131+| `!` | Run shell command, inserting output before each selection | `shell_insert_output` |
132−| `A-!` | Run shell command, appending output after each selection | `shell_append_output` |
132+| `A-!` | Run shell command, appending output after each selection | `shell_append_output` |
133−| `$` | Pipe each selection into shell command, removing if the command exits >0 | `shell_keep_pipe` |
133+| `$` | Pipe each selection into shell command, keep selections where command returned 0 | `shell_keep_pipe` |
134
134
135## Select / extend mode
135## Select / extend mode
136
136
helix-term/src/commands.rs+71−27
@@ -4299,7 +4299,6 @@ enum ShellBehavior {
4299 Ignore,
4299 Ignore,
4300 Insert,
4300 Insert,
4301 Append,
4301 Append,
4302− Filter,
4303}
4302}
4304
4303
4305fn shell_pipe(cx: &mut Context) {
4304fn shell_pipe(cx: &mut Context) {
@@ -4319,7 +4318,56 @@ fn shell_append_output(cx: &mut Context) {
4319}
4318}
4320
4319
4321fn shell_keep_pipe(cx: &mut Context) {
4320fn shell_keep_pipe(cx: &mut Context) {
4321+ let prompt = Prompt::new(
4322− shell(cx, "keep-pipe:".into(), ShellBehavior::Filter);
4322+ "keep-pipe:".into(),
4323+ Some('|'),
4324+ |_input: &str| Vec::new(),
4325+ move |cx: &mut compositor::Context, input: &str, event: PromptEvent| {
4326+ let shell = &cx.editor.config.shell;
4327+ if event != PromptEvent::Validate {
4328+ return;
4329+ }
4330+ if input.is_empty() {
4331+ return;
4332+ }
4333+ let (view, doc) = current!(cx.editor);
4334+ let selection = doc.selection(view.id);
4335+
4336+ let mut ranges = SmallVec::with_capacity(selection.len());
4337+ let old_index = selection.primary_index();
4338+ let mut index: Option<usize> = None;
4339+ let text = doc.text().slice(..);
4340+
4341+ for (i, range) in selection.ranges().iter().enumerate() {
4342+ let fragment = range.fragment(text);
4343+ let (_output, success) = match shell_impl(shell, input, Some(fragment.as_bytes())) {
4344+ Ok(result) => result,
4345+ Err(err) => {
4346+ cx.editor.set_error(err.to_string());
4347+ return;
4348+ }
4349+ };
4350+
4351+ // if the process exits successfully, keep the selection
4352+ if success {
4353+ ranges.push(*range);
4354+ if i >= old_index && index.is_none() {
4355+ index = Some(ranges.len() - 1);
4356+ }
4357+ }
4358+ }
4359+
4360+ if ranges.is_empty() {
4361+ cx.editor.set_error("No selections remaining".to_string());
4362+ return;
4363+ }
4364+
4365+ let index = index.unwrap_or_else(|| ranges.len() - 1);
4366+ doc.set_selection(view.id, Selection::new(ranges, index));
4367+ },
4368+ );
4369+
4370+ cx.push_layer(Box::new(prompt));
4323}
4371}
4324
4372
4325fn shell_impl(
4373fn shell_impl(
@@ -4329,6 +4377,10 @@ fn shell_impl(
4329) -> anyhow::Result<(Tendril, bool)> {
4377) -> anyhow::Result<(Tendril, bool)> {
4330 use std::io::Write;
4378 use std::io::Write;
4331 use std::process::{Command, Stdio};
4379 use std::process::{Command, Stdio};
4380+ if shell.is_empty() {
4381+ bail!("No shell set");
4382+ }
4383+
4332 let mut process = match Command::new(&shell[0])
4384 let mut process = match Command::new(&shell[0])
4333 .args(&shell[1..])
4385 .args(&shell[1..])
4334 .arg(cmd)
4386 .arg(cmd)
@@ -4349,11 +4401,8 @@ fn shell_impl(
4349 }
4401 }
4350 let output = process.wait_with_output()?;
4402 let output = process.wait_with_output()?;
4351
4403
4352− if !output.status.success() {
4353− if !output.stderr.is_empty() {
4404+ if !output.stderr.is_empty() {
4354− log::error!("Shell error: {}", String::from_utf8_lossy(&output.stderr));
4405+ log::error!("Shell error: {}", String::from_utf8_lossy(&output.stderr));
4355− }
4356− bail!("Command failed");
4357 }
4406 }
4358
4407
4359 let tendril = Tendril::try_from_byte_slice(&output.stdout)
4408 let tendril = Tendril::try_from_byte_slice(&output.stdout)
@@ -4362,12 +4411,8 @@ fn shell_impl(
4362}
4411}
4363
4412
4364fn shell(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBehavior) {
4413fn shell(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBehavior) {
4365− if cx.editor.config.shell.is_empty() {
4366− cx.editor.set_error("No shell set".to_owned());
4367− return;
4368− }
4369 let pipe = match behavior {
4414 let pipe = match behavior {
4370− ShellBehavior::Replace | ShellBehavior::Ignore | ShellBehavior::Filter => true,
4415+ ShellBehavior::Replace | ShellBehavior::Ignore => true,
4371 ShellBehavior::Insert | ShellBehavior::Append => false,
4416 ShellBehavior::Insert | ShellBehavior::Append => false,
4372 };
4417 };
4373 let prompt = Prompt::new(
4418 let prompt = Prompt::new(
@@ -4379,6 +4424,9 @@ fn shell(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBehavior) {
4379 if event != PromptEvent::Validate {
4424 if event != PromptEvent::Validate {
4380 return;
4425 return;
4381 }
4426 }
4427+ if input.is_empty() {
4428+ return;
4429+ }
4382 let (view, doc) = current!(cx.editor);
4430 let (view, doc) = current!(cx.editor);
4383 let selection = doc.selection(view.id);
4431 let selection = doc.selection(view.id);
4384
4432
@@ -4396,22 +4444,18 @@ fn shell(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBehavior) {
4396 }
4444 }
4397 };
4445 };
4398
4446
4399− if behavior != ShellBehavior::Filter {
4447+ if !success {
4400− let (from, to) = match behavior {
4448+ cx.editor.set_error("Command failed".to_string());
4401− ShellBehavior::Replace => (range.from(), range.to()),
4449+ return;
4402− ShellBehavior::Insert => (range.from(), range.from()),
4403− ShellBehavior::Append => (range.to(), range.to()),
4404− _ => (range.from(), range.from()),
4405− };
4406− changes.push((from, to, Some(output)));
4407− } else {
4408− // if the process exits successfully, keep the selection, otherwise delete it.
4409− changes.push((
4410− range.from(),
4411− if success { range.from() } else { range.to() },
4412− None,
4413− ));
4414 }
4450 }
4451+
4452+ let (from, to) = match behavior {
4453+ ShellBehavior::Replace => (range.from(), range.to()),
4454+ ShellBehavior::Insert => (range.from(), range.from()),
4455+ ShellBehavior::Append => (range.to(), range.to()),
4456+ _ => (range.from(), range.from()),
4457+ };
4458+ changes.push((from, to, Some(output)));
4415 }
4459 }
4416
4460
4417 if behavior != ShellBehavior::Ignore {
4461 if behavior != ShellBehavior::Ignore {