@@ -4299,7 +4299,6 @@ enum ShellBehavior {
4305fn shell_pipe(cx: &mut Context) {
4304fn shell_pipe(cx: &mut Context) {
@@ -4319,7 +4318,56 @@ fn shell_append_output(cx: &mut Context) {
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(),
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 {
4330+ if input.is_empty() {
4333+ let (view, doc) = current!(cx.editor);
4334+ let selection = doc.selection(view.id);
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(..);
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,
4346+ cx.editor.set_error(err.to_string());
4351+ // if the process exits successfully, keep the selection
4353+ ranges.push(*range);
4354+ if i >= old_index && index.is_none() {
4355+ index = Some(ranges.len() - 1);
4360+ if ranges.is_empty() {
4361+ cx.editor.set_error("No selections remaining".to_string());
4365+ let index = index.unwrap_or_else(|| ranges.len() - 1);
4366+ doc.set_selection(view.id, Selection::new(ranges, index));
4370+ cx.push_layer(Box::new(prompt));
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");
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(
4350 let output = process.wait_with_output()?;
4402 let output = process.wait_with_output()?;
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));
4356− bail!("Command failed");
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(
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());
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,
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 {
4427+ if input.is_empty() {
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);
@@ -4396,22 +4444,18 @@ fn shell(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBehavior) {
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()),
4406− changes.push((from, to, Some(output)));
4408− // if the process exits successfully, keep the selection, otherwise delete it.
4411− if success { range.from() } else { range.to() },
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()),
4458+ changes.push((from, to, Some(output)));
4417 if behavior != ShellBehavior::Ignore {
4461 if behavior != ShellBehavior::Ignore {