Skip to content

Commit d661ae6

Browse files
committed
Handle escaped chars in completions
1 parent d814a6b commit d661ae6

File tree

1 file changed

+75
-7
lines changed

1 file changed

+75
-7
lines changed

src/completions.rs

Lines changed: 75 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ mod parse {
2525
use nom::{
2626
branch::alt,
2727
bytes::complete::{escaped_transform, is_not, tag},
28-
character::complete::{char, space1},
28+
character::complete::{char, space0, space1},
2929
combinator::{cut, eof, opt, value},
3030
error::{ErrorKind, ParseError},
3131
IResult,
@@ -144,20 +144,32 @@ mod parse {
144144
}
145145

146146
fn parse_last_arg(input: &str) -> IResult<&str, (String, &str)> {
147-
let (input, _) = space1(input)?;
147+
let (input, _) = space0(input)?;
148148

149149
let old_input = input;
150150
let (input, arg) = opt(parse_unclosed_quote)(input)?;
151151

152152
Ok((input, (arg.unwrap_or_default(), old_input)))
153153
}
154154

155+
fn parse_trailing_last_arg(input: &str) -> IResult<&str, (String, &str)> {
156+
let (input, _) = space1(input)?;
157+
158+
parse_last_arg(input)
159+
}
160+
155161
/// Returns a list with the parsed strings and a raw version of the last string to be stripped
156162
/// from the input before completing.
157163
pub fn parse_started_strings(input: &str) -> IResult<&str, (Vec<String>, &str)> {
158164
let (input, (mut args, mut last_arg_raw)) =
159165
separated_list0_last_raw(space1, parse_string)(input)?;
160-
let (input, end_arg) = opt(parse_last_arg)(input)?;
166+
167+
let (input, end_arg) = if args.is_empty() {
168+
opt(parse_last_arg)(input)?
169+
} else {
170+
opt(parse_trailing_last_arg)(input)?
171+
};
172+
161173
let (input, _) = eof(input)?;
162174

163175
if let Some((arg, end_arg_raw)) = end_arg {
@@ -171,6 +183,45 @@ mod parse {
171183

172184
Ok((input, (args, last_arg_raw)))
173185
}
186+
187+
#[cfg(test)]
188+
mod tests {
189+
use super::*;
190+
191+
#[test]
192+
fn parse_strings() {
193+
let text = "some normal args";
194+
let parsed = parse_started_strings(text).unwrap();
195+
assert_eq!(("", (vec!["some".into(), "normal".into(), "args".into()], "args")), parsed);
196+
197+
let text = "started ";
198+
let parsed = parse_started_strings(text).unwrap();
199+
assert_eq!(("", (vec!["started".into(), "".into()], "")), parsed);
200+
201+
let text = "args \"with quotes\"";
202+
let parsed = parse_started_strings(text).unwrap();
203+
assert_eq!(
204+
("", (vec!["args".into(), "with quotes".into()], "\"with quotes\"")),
205+
parsed
206+
);
207+
208+
let text = "and \"started ";
209+
let parsed = parse_started_strings(text).unwrap();
210+
assert_eq!(("", (vec!["and".into(), "started ".into()], "\"started ")), parsed);
211+
212+
let text = "\"only started ";
213+
let parsed = parse_started_strings(text).unwrap();
214+
assert_eq!(("", (vec!["only started ".into()], "\"only started ")), parsed);
215+
216+
let text = " ";
217+
let parsed = parse_started_strings(text).unwrap();
218+
assert_eq!(("", (vec!["".into()], "")), parsed);
219+
220+
let text = "escaped\\ spaces here";
221+
let parsed = parse_started_strings(text).unwrap();
222+
assert_eq!(("", (vec!["escaped spaces".into(), "here".into()], "here")), parsed);
223+
}
224+
}
174225
}
175226

176227
/// Tab completion for user IDs.
@@ -405,7 +456,7 @@ fn complete_cmdarg(
405456
let orig_cursor = cursor.clone();
406457
*cursor = new_cursor;
407458

408-
let completions = match cmd.name.as_str() {
459+
let mut completions = match cmd.name.as_str() {
409460
"invite" => complete_iamb_invite(args, store),
410461

411462
"keys" => complete_iamb_keys(args, input, orig_cursor, cursor),
@@ -443,8 +494,14 @@ fn complete_cmdarg(
443494
"space" => complete_iamb_space(args, store),
444495

445496
"upload" | "download" | "open" => {
446-
*cursor = orig_cursor;
447-
complete_path(input, cursor)
497+
if input.get_char_at_cursor(cursor) == Some('"') {
498+
// Use the escaped instead of the qouted filename.
499+
let mut args = args;
500+
vec![args.pop().unwrap()]
501+
} else {
502+
*cursor = orig_cursor;
503+
return complete_path(input, cursor);
504+
}
448505
},
449506

450507
"logout" => complete_iamb_logout(args, store),
@@ -458,11 +515,22 @@ fn complete_cmdarg(
458515
#[cfg(test)]
459516
panic!("trying to complete unknown subcommand `{}`", _cmd);
460517

518+
#[cfg(not(test))]
461519
vec![]
462520
},
463521
};
464522

465-
// TODO: escape stuff including with paths
523+
completions.iter_mut().for_each(|completion| {
524+
if completion.contains(['\\', ' ', '#', '%', '"', '|']) {
525+
*completion = completion
526+
.replace('\\', "\\\\")
527+
.replace(' ', "\\ ")
528+
.replace('#', "\\#")
529+
.replace('%', "\\%")
530+
.replace('"', "\\\"")
531+
.replace('|', "\\|");
532+
}
533+
});
466534

467535
completions
468536
}

0 commit comments

Comments
 (0)