Reimplement keep-pipe, it needs to manipulate selections, not text

This commit is contained in:
Blaž Hrastnik 2021-09-01 11:01:19 +09:00
parent dc609cafb5
commit ce7ad2beb5
2 changed files with 78 additions and 34 deletions

View file

@ -124,13 +124,13 @@ in reverse, or searching via smartcase.
### Shell ### Shell
| Key | Description | Command | | Key | Description | Command |
| ------ | ----------- | ------- | | ------ | ----------- | ------- |
| `\|` | Pipe each selection through shell command, replacing with output | `shell_pipe` | | `\|` | Pipe each selection through shell command, replacing with output | `shell_pipe` |
| `A-\|` | Pipe each selection into shell command, ignoring output | `shell_pipe_to` | | `A-\|` | Pipe each selection into shell command, ignoring output | `shell_pipe_to` |
| `!` | Run shell command, inserting output before each selection | `shell_insert_output` | | `!` | Run shell command, inserting output before each selection | `shell_insert_output` |
| `A-!` | Run shell command, appending output after each selection | `shell_append_output` | | `A-!` | Run shell command, appending output after each selection | `shell_append_output` |
| `$` | Pipe each selection into shell command, removing if the command exits >0 | `shell_keep_pipe` | | `$` | Pipe each selection into shell command, keep selections where command returned 0 | `shell_keep_pipe` |
## Select / extend mode ## Select / extend mode

View file

@ -4299,7 +4299,6 @@ enum ShellBehavior {
Ignore, Ignore,
Insert, Insert,
Append, Append,
Filter,
} }
fn shell_pipe(cx: &mut Context) { fn shell_pipe(cx: &mut Context) {
@ -4319,7 +4318,56 @@ fn shell_append_output(cx: &mut Context) {
} }
fn shell_keep_pipe(cx: &mut Context) { fn shell_keep_pipe(cx: &mut Context) {
shell(cx, "keep-pipe:".into(), ShellBehavior::Filter); let prompt = Prompt::new(
"keep-pipe:".into(),
Some('|'),
|_input: &str| Vec::new(),
move |cx: &mut compositor::Context, input: &str, event: PromptEvent| {
let shell = &cx.editor.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 ranges = SmallVec::with_capacity(selection.len());
let old_index = selection.primary_index();
let mut index: Option<usize> = None;
let text = doc.text().slice(..);
for (i, range) in selection.ranges().iter().enumerate() {
let fragment = range.fragment(text);
let (_output, success) = match shell_impl(shell, input, Some(fragment.as_bytes())) {
Ok(result) => result,
Err(err) => {
cx.editor.set_error(err.to_string());
return;
}
};
// if the process exits successfully, keep the selection
if success {
ranges.push(*range);
if i >= old_index && index.is_none() {
index = Some(ranges.len() - 1);
}
}
}
if ranges.is_empty() {
cx.editor.set_error("No selections remaining".to_string());
return;
}
let index = index.unwrap_or_else(|| ranges.len() - 1);
doc.set_selection(view.id, Selection::new(ranges, index));
},
);
cx.push_layer(Box::new(prompt));
} }
fn shell_impl( fn shell_impl(
@ -4329,6 +4377,10 @@ fn shell_impl(
) -> anyhow::Result<(Tendril, bool)> { ) -> anyhow::Result<(Tendril, bool)> {
use std::io::Write; use std::io::Write;
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
if shell.is_empty() {
bail!("No shell set");
}
let mut process = match Command::new(&shell[0]) let mut process = match Command::new(&shell[0])
.args(&shell[1..]) .args(&shell[1..])
.arg(cmd) .arg(cmd)
@ -4349,11 +4401,8 @@ fn shell_impl(
} }
let output = process.wait_with_output()?; let output = process.wait_with_output()?;
if !output.status.success() { if !output.stderr.is_empty() {
if !output.stderr.is_empty() { log::error!("Shell error: {}", String::from_utf8_lossy(&output.stderr));
log::error!("Shell error: {}", String::from_utf8_lossy(&output.stderr));
}
bail!("Command failed");
} }
let tendril = Tendril::try_from_byte_slice(&output.stdout) let tendril = Tendril::try_from_byte_slice(&output.stdout)
@ -4362,12 +4411,8 @@ fn shell_impl(
} }
fn shell(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBehavior) { fn shell(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBehavior) {
if cx.editor.config.shell.is_empty() {
cx.editor.set_error("No shell set".to_owned());
return;
}
let pipe = match behavior { let pipe = match behavior {
ShellBehavior::Replace | ShellBehavior::Ignore | ShellBehavior::Filter => true, ShellBehavior::Replace | ShellBehavior::Ignore => true,
ShellBehavior::Insert | ShellBehavior::Append => false, ShellBehavior::Insert | ShellBehavior::Append => false,
}; };
let prompt = Prompt::new( let prompt = Prompt::new(
@ -4379,6 +4424,9 @@ fn shell(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBehavior) {
if event != PromptEvent::Validate { if event != PromptEvent::Validate {
return; return;
} }
if input.is_empty() {
return;
}
let (view, doc) = current!(cx.editor); let (view, doc) = current!(cx.editor);
let selection = doc.selection(view.id); let selection = doc.selection(view.id);
@ -4396,22 +4444,18 @@ fn shell(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBehavior) {
} }
}; };
if behavior != ShellBehavior::Filter { if !success {
let (from, to) = match behavior { cx.editor.set_error("Command failed".to_string());
ShellBehavior::Replace => (range.from(), range.to()), return;
ShellBehavior::Insert => (range.from(), range.from()),
ShellBehavior::Append => (range.to(), range.to()),
_ => (range.from(), range.from()),
};
changes.push((from, to, Some(output)));
} else {
// if the process exits successfully, keep the selection, otherwise delete it.
changes.push((
range.from(),
if success { range.from() } else { range.to() },
None,
));
} }
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 { if behavior != ShellBehavior::Ignore {