Skip to content
Snippets Groups Projects
Commit 3b957e64 authored by gwenn's avatar gwenn Committed by GitHub
Browse files

Merge branch 'master' into page-comp

parents 6a3bc5b4 5ef59fea
No related branches found
No related tags found
No related merge requests found
//! Completion API
use std::borrow::Cow::{self, Borrowed, Owned};
use std::collections::BTreeSet;
use std::fs;
use std::path::{self, Path};
......@@ -66,10 +67,14 @@ pub struct FilenameCompleter {
#[cfg(unix)]
static DEFAULT_BREAK_CHARS: [char; 18] = [' ', '\t', '\n', '"', '\\', '\'', '`', '@', '$', '>',
'<', '=', ';', '|', '&', '{', '(', '\0'];
#[cfg(unix)]
static ESCAPE_CHAR: Option<char> = Some('\\');
// Remove \ to make file completion works on windows
#[cfg(windows)]
static DEFAULT_BREAK_CHARS: [char; 17] = [' ', '\t', '\n', '"', '\'', '`', '@', '$', '>',
'<', '=', ';', '|', '&', '{', '(', '\0'];
static DEFAULT_BREAK_CHARS: [char; 17] = [' ', '\t', '\n', '"', '\'', '`', '@', '$', '>', '<',
'=', ';', '|', '&', '{', '(', '\0'];
#[cfg(windows)]
static ESCAPE_CHAR: Option<char> = None;
impl FilenameCompleter {
pub fn new() -> FilenameCompleter {
......@@ -85,13 +90,61 @@ impl Default for FilenameCompleter {
impl Completer for FilenameCompleter {
fn complete(&self, line: &str, pos: usize) -> Result<(usize, Vec<String>)> {
let (start, path) = extract_word(line, pos, &self.break_chars);
let matches = try!(filename_complete(path));
let (start, path) = extract_word(line, pos, ESCAPE_CHAR, &self.break_chars);
let path = unescape(path, ESCAPE_CHAR);
let matches = try!(filename_complete(&path, ESCAPE_CHAR, &self.break_chars));
Ok((start, matches))
}
}
fn filename_complete(path: &str) -> Result<Vec<String>> {
/// Remove escape char
pub fn unescape(input: &str, esc_char: Option<char>) -> Cow<str> {
if esc_char.is_none() {
return Borrowed(input);
}
let esc_char = esc_char.unwrap();
let n = input.chars().filter(|&c| c == esc_char).count();
if n == 0 {
return Borrowed(input);
}
let mut result = String::with_capacity(input.len() - n);
let mut chars = input.chars();
while let Some(ch) = chars.next() {
if ch == esc_char {
if let Some(ch) = chars.next() {
result.push(ch);
}
} else {
result.push(ch);
}
}
Owned(result)
}
pub fn escape(input: String, esc_char: Option<char>, break_chars: &BTreeSet<char>) -> String {
if esc_char.is_none() {
return input;
}
let esc_char = esc_char.unwrap();
let n = input.chars().filter(|c| break_chars.contains(c)).count();
if n == 0 {
return input;
}
let mut result = String::with_capacity(input.len() + n);
for c in input.chars() {
if break_chars.contains(&c) {
result.push(esc_char);
}
result.push(c);
}
result
}
fn filename_complete(path: &str,
esc_char: Option<char>,
break_chars: &BTreeSet<char>)
-> Result<Vec<String>> {
use std::env::{current_dir, home_dir};
let sep = path::MAIN_SEPARATOR;
......@@ -131,7 +184,7 @@ fn filename_complete(path: &str) -> Result<Vec<String>> {
if try!(fs::metadata(entry.path())).is_dir() {
path.push(sep);
}
entries.push(path);
entries.push(escape(path, esc_char, break_chars));
}
}
}
......@@ -144,17 +197,34 @@ fn filename_complete(path: &str) -> Result<Vec<String>> {
/// Return the word and its start position (idx, `line[idx..pos]`) otherwise.
pub fn extract_word<'l>(line: &'l str,
pos: usize,
esc_char: Option<char>,
break_chars: &BTreeSet<char>)
-> (usize, &'l str) {
let line = &line[..pos];
if line.is_empty() {
return (0, line);
}
match line.char_indices().rev().find(|&(_, c)| break_chars.contains(&c)) {
Some((i, c)) => {
let start = i + c.len_utf8();
(start, &line[start..])
let mut start = None;
for (i, c) in line.char_indices().rev() {
if esc_char.is_some() && start.is_some() {
if esc_char.unwrap() == c {
// escaped break char
start = None;
continue;
} else {
break;
}
}
if break_chars.contains(&c) {
start = Some(i + c.len_utf8());
if esc_char.is_none() {
break;
} // else maybe escaped...
}
}
match start {
Some(start) => (start, &line[start..]),
None => (0, line),
}
}
......@@ -195,7 +265,31 @@ mod tests {
let break_chars: BTreeSet<char> = super::DEFAULT_BREAK_CHARS.iter().cloned().collect();
let line = "ls '/usr/local/b";
assert_eq!((4, "/usr/local/b"),
super::extract_word(line, line.len(), &break_chars));
super::extract_word(line, line.len(), Some('\\'), &break_chars));
let line = "ls /User\\ Information";
assert_eq!((3, "/User\\ Information"),
super::extract_word(line, line.len(), Some('\\'), &break_chars));
}
#[test]
pub fn unescape() {
use std::borrow::Cow::{self, Borrowed, Owned};
let input = "/usr/local/b";
assert_eq!(Borrowed(input), super::unescape(input, Some('\\')));
let input = "/User\\ Information";
let result: Cow<str> = Owned(String::from("/User Information"));
assert_eq!(result, super::unescape(input, Some('\\')));
}
#[test]
pub fn escape() {
let break_chars: BTreeSet<char> = super::DEFAULT_BREAK_CHARS.iter().cloned().collect();
let input = String::from("/usr/local/b");
assert_eq!(input.clone(),
super::escape(input, Some('\\'), &break_chars));
let input = String::from("/User Information");
let result = String::from("/User\\ Information");
assert_eq!(result, super::escape(input, Some('\\'), &break_chars));
}
#[test]
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment