Introduce completion Candidate trait
diff --git a/TODO.md b/TODO.md
index 74f28b3..6690366 100644
--- a/TODO.md
+++ b/TODO.md
@@ -17,7 +17,7 @@
- [ ] Windows escape/unescape space in path
- [ ] file completion & escape/unescape (#106)
- [ ] file completion & tilde (#62)
-- [ ] display versus replacement
+- [X] display versus replacement
- [ ] composite/alternate completer (if the current completer returns nothing, try the next one)
Config
diff --git a/examples/example.rs b/examples/example.rs
index adbbbcd..e249f51 100644
--- a/examples/example.rs
+++ b/examples/example.rs
@@ -3,7 +3,7 @@
use log::{Level, LevelFilter, Metadata, Record, SetLoggerError};
-use rustyline::completion::{Completer, FilenameCompleter};
+use rustyline::completion::{Completer, FilenameCompleter, Pair};
use rustyline::error::ReadlineError;
use rustyline::hint::Hinter;
use rustyline::{Cmd, CompletionType, Config, EditMode, Editor, Helper, KeyPress};
@@ -20,7 +20,9 @@
struct MyHelper(FilenameCompleter);
impl Completer for MyHelper {
- fn complete(&self, line: &str, pos: usize) -> Result<(usize, Vec<String>), ReadlineError> {
+ type Candidate = Pair;
+
+ fn complete(&self, line: &str, pos: usize) -> Result<(usize, Vec<Pair>), ReadlineError> {
self.0.complete(line, pos)
}
}
diff --git a/src/completion.rs b/src/completion.rs
index 4eaeba9..1d05753 100644
--- a/src/completion.rs
+++ b/src/completion.rs
@@ -12,14 +12,47 @@
// ("select t.na| from tbl as t")
// TODO: make &self &mut self ???
+/// A completion candidate.
+pub trait Candidate {
+ /// Text to display when listing alternatives.
+ fn display(&self) -> &str;
+ /// Text to insert in line.
+ fn replacement(&self) -> &str;
+}
+
+impl Candidate for String {
+ fn display(&self) -> &str {
+ self.as_str()
+ }
+ fn replacement(&self) -> &str {
+ self.as_str()
+ }
+}
+
+pub struct Pair {
+ pub display: String,
+ pub replacement: String,
+}
+
+impl Candidate for Pair {
+ fn display(&self) -> &str {
+ self.display.as_str()
+ }
+ fn replacement(&self) -> &str {
+ self.replacement.as_str()
+ }
+}
+
/// To be called for tab-completion.
pub trait Completer {
+ type Candidate: Candidate;
+
/// Takes the currently edited `line` with the cursor `pos`ition and
/// returns the start position and the completion candidates for the
/// partial word to be completed.
///
/// ("ls /usr/loc", 11) => Ok((3, vec!["/usr/local/"]))
- fn complete(&self, line: &str, pos: usize) -> Result<(usize, Vec<String>)>;
+ fn complete(&self, line: &str, pos: usize) -> Result<(usize, Vec<Self::Candidate>)>;
/// Updates the edited `line` with the `elected` candidate.
fn update(&self, line: &mut LineBuffer, start: usize, elected: &str) {
let end = line.pos();
@@ -28,6 +61,8 @@
}
impl Completer for () {
+ type Candidate = String;
+
fn complete(&self, _line: &str, _pos: usize) -> Result<(usize, Vec<String>)> {
Ok((0, Vec::with_capacity(0)))
}
@@ -37,7 +72,9 @@
}
impl<'c, C: ?Sized + Completer> Completer for &'c C {
- fn complete(&self, line: &str, pos: usize) -> Result<(usize, Vec<String>)> {
+ type Candidate = C::Candidate;
+
+ fn complete(&self, line: &str, pos: usize) -> Result<(usize, Vec<Self::Candidate>)> {
(**self).complete(line, pos)
}
fn update(&self, line: &mut LineBuffer, start: usize, elected: &str) {
@@ -48,7 +85,9 @@
($($id: ident)*) => {
$(
impl<C: ?Sized + Completer> Completer for $id<C> {
- fn complete(&self, line: &str, pos: usize) -> Result<(usize, Vec<String>)> {
+ type Candidate = C::Candidate;
+
+ fn complete(&self, line: &str, pos: usize) -> Result<(usize, Vec<Self::Candidate>)> {
(**self).complete(line, pos)
}
fn update(&self, line: &mut LineBuffer, start: usize, elected: &str) {
@@ -104,7 +143,9 @@
}
impl Completer for FilenameCompleter {
- fn complete(&self, line: &str, pos: usize) -> Result<(usize, Vec<String>)> {
+ type Candidate = Pair;
+
+ fn complete(&self, line: &str, pos: usize) -> Result<(usize, Vec<Pair>)> {
let (start, path, esc_char, break_chars) =
if let Some((idx, double_quote)) = find_unclosed_quote(&line[..pos]) {
let start = idx + 1;
@@ -179,7 +220,7 @@
path: &str,
esc_char: Option<char>,
break_chars: &BTreeSet<char>,
-) -> Result<Vec<String>> {
+) -> Result<Vec<Pair>> {
use dirs::home_dir;
use std::env::current_dir;
@@ -211,7 +252,7 @@
dir_path.to_path_buf()
};
- let mut entries: Vec<String> = Vec::new();
+ let mut entries: Vec<Pair> = Vec::new();
for entry in try!(dir.read_dir()) {
let entry = try!(entry);
if let Some(s) = entry.file_name().to_str() {
@@ -220,7 +261,10 @@
if try!(fs::metadata(entry.path())).is_dir() {
path.push(sep);
}
- entries.push(escape(path, esc_char, break_chars));
+ entries.push(Pair {
+ display: String::from(s),
+ replacement: escape(path, esc_char, break_chars),
+ });
}
}
}
@@ -266,17 +310,17 @@
}
}
-pub fn longest_common_prefix(candidates: &[String]) -> Option<&str> {
+pub fn longest_common_prefix<C: Candidate>(candidates: &[C]) -> Option<&str> {
if candidates.is_empty() {
return None;
} else if candidates.len() == 1 {
- return Some(&candidates[0]);
+ return Some(&candidates[0].replacement());
}
let mut longest_common_prefix = 0;
'o: loop {
for (i, c1) in candidates.iter().enumerate().take(candidates.len() - 1) {
- let b1 = c1.as_bytes();
- let b2 = candidates[i + 1].as_bytes();
+ let b1 = c1.replacement().as_bytes();
+ let b2 = candidates[i + 1].replacement().as_bytes();
if b1.len() <= longest_common_prefix
|| b2.len() <= longest_common_prefix
|| b1[longest_common_prefix] != b2[longest_common_prefix]
@@ -286,13 +330,14 @@
}
longest_common_prefix += 1;
}
- while !candidates[0].is_char_boundary(longest_common_prefix) {
+ let candidate = candidates[0].replacement();
+ while !candidate.is_char_boundary(longest_common_prefix) {
longest_common_prefix -= 1;
}
if longest_common_prefix == 0 {
return None;
}
- Some(&candidates[0][0..longest_common_prefix])
+ Some(&candidate[0..longest_common_prefix])
}
#[derive(PartialEq)]
diff --git a/src/lib.rs b/src/lib.rs
index d93037d..9d2f876 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -53,7 +53,7 @@
use tty::{RawMode, RawReader, Renderer, Term, Terminal};
-use completion::{longest_common_prefix, Completer};
+use completion::{longest_common_prefix, Candidate, Completer};
pub use config::{CompletionType, Config, EditMode, HistoryDuplicates};
pub use consts::KeyPress;
use edit::State;
@@ -91,7 +91,7 @@
loop {
// Show completion or original buffer
if i < candidates.len() {
- completer.update(&mut s.line, start, &candidates[i]);
+ completer.update(&mut s.line, start, candidates[i].replacement());
try!(s.refresh_line());
} else {
// Restore current edited line
@@ -178,11 +178,11 @@
}
}
-fn page_completions<R: RawReader>(
+fn page_completions<R: RawReader, C: Candidate>(
rdr: &mut R,
s: &mut State,
input_state: &mut InputState,
- candidates: &[String],
+ candidates: &[C],
) -> Result<Option<Cmd>> {
use std::cmp;
@@ -192,7 +192,7 @@
cols,
candidates
.into_iter()
- .map(|s| s.as_str().width())
+ .map(|s| s.display().width())
.max()
.unwrap()
+ min_col_pad,
@@ -235,9 +235,9 @@
for col in 0..num_cols {
let i = (col * num_rows) + row;
if i < candidates.len() {
- let candidate = &candidates[i];
+ let candidate = &candidates[i].display();
ab.push_str(candidate);
- let width = candidate.as_str().width();
+ let width = candidate.width();
if ((col + 1) * num_rows) + row < candidates.len() {
for _ in width..max_width {
ab.push(' ');
diff --git a/src/test/mod.rs b/src/test/mod.rs
index d3216de..f2d6ea2 100644
--- a/src/test/mod.rs
+++ b/src/test/mod.rs
@@ -24,6 +24,8 @@
struct SimpleCompleter;
impl Completer for SimpleCompleter {
+ type Candidate = String;
+
fn complete(&self, line: &str, _pos: usize) -> Result<(usize, Vec<String>)> {
Ok((0, vec![line.to_owned() + "t"]))
}