Escape filenames in command completion

This changes the completion items to be rendered with shellword
escaping, so a file `a b.txt` is rendered as `a\ b.txt` which matches
how it should be inputted.
pull/4626/head
Michael Davis 2 years ago committed by Blaž Hrastnik
parent 1536a65289
commit 3d283b2ca4

@ -1,9 +1,9 @@
use std::borrow::Cow; use std::borrow::Cow;
/// Auto escape for shellwords usage. /// Auto escape for shellwords usage.
pub fn escape(input: &str) -> Cow<'_, str> { pub fn escape(input: Cow<str>) -> Cow<str> {
if !input.chars().any(|x| x.is_ascii_whitespace()) { if !input.chars().any(|x| x.is_ascii_whitespace()) {
Cow::Borrowed(input) input
} else if cfg!(unix) { } else if cfg!(unix) {
Cow::Owned(input.chars().fold(String::new(), |mut buf, c| { Cow::Owned(input.chars().fold(String::new(), |mut buf, c| {
if c.is_ascii_whitespace() { if c.is_ascii_whitespace() {
@ -311,15 +311,15 @@ mod test {
#[test] #[test]
#[cfg(unix)] #[cfg(unix)]
fn test_escaping_unix() { fn test_escaping_unix() {
assert_eq!(escape("foobar"), Cow::Borrowed("foobar")); assert_eq!(escape("foobar".into()), Cow::Borrowed("foobar"));
assert_eq!(escape("foo bar"), Cow::Borrowed("foo\\ bar")); assert_eq!(escape("foo bar".into()), Cow::Borrowed("foo\\ bar"));
assert_eq!(escape("foo\tbar"), Cow::Borrowed("foo\\\tbar")); assert_eq!(escape("foo\tbar".into()), Cow::Borrowed("foo\\\tbar"));
} }
#[test] #[test]
#[cfg(windows)] #[cfg(windows)]
fn test_escaping_windows() { fn test_escaping_windows() {
assert_eq!(escape("foobar"), Cow::Borrowed("foobar")); assert_eq!(escape("foobar".into()), Cow::Borrowed("foobar"));
assert_eq!(escape("foo bar"), Cow::Borrowed("\"foo bar\"")); assert_eq!(escape("foo bar".into()), Cow::Borrowed("\"foo bar\""));
} }
} }

@ -2219,6 +2219,7 @@ pub(super) fn command_mode(cx: &mut Context) {
completer(editor, part) completer(editor, part)
.into_iter() .into_iter()
.map(|(range, file)| { .map(|(range, file)| {
let file = shellwords::escape(file);
// offset ranges to input // offset ranges to input
let offset = input.len() - part.len(); let offset = input.len() - part.len();
let range = (range.start + offset)..; let range = (range.start + offset)..;

@ -1,6 +1,5 @@
use crate::compositor::{Component, Compositor, Context, Event, EventResult}; use crate::compositor::{Component, Compositor, Context, Event, EventResult};
use crate::{alt, ctrl, key, shift, ui}; use crate::{alt, ctrl, key, shift, ui};
use helix_core::shellwords;
use helix_view::input::KeyEvent; use helix_view::input::KeyEvent;
use helix_view::keyboard::KeyCode; use helix_view::keyboard::KeyCode;
use std::{borrow::Cow, ops::RangeFrom}; use std::{borrow::Cow, ops::RangeFrom};
@ -336,10 +335,7 @@ impl Prompt {
let (range, item) = &self.completion[index]; let (range, item) = &self.completion[index];
// since we are using shellwords to parse arguments, make sure self.line.replace_range(range.clone(), item);
// that whitespace in files is properly escaped.
let item = shellwords::escape(item);
self.line.replace_range(range.clone(), &item);
self.move_end(); self.move_end();
} }

Loading…
Cancel
Save