diff --git a/TODO.md b/TODO.md index 74f28b3654705b7567c05c19949ae8d06dd40d35..669036659006d78b78eb968b1f0a71894b65c2a7 100644 --- a/TODO.md +++ b/TODO.md @@ -17,7 +17,7 @@ Completion - [ ] 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 adbbbcd47f038b6464ad30ed050b6b0e4469afa4..e249f5110b5bd4f35cb8ade2abf6d50ebb63ba1a 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -3,7 +3,7 @@ extern crate rustyline; 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 @@ static PROMPT: &'static str = ">> "; 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 4eaeba90ea522e8dc516b52995dc084d7ce5c0d9..1d0575321aaff7270ce60a5f35ce9c499b67585c 100644 --- a/src/completion.rs +++ b/src/completion.rs @@ -12,14 +12,47 @@ use line_buffer::LineBuffer; // ("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 @@ pub trait Completer { } 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 Completer for () { } 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 @@ macro_rules! box_completer { ($($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 Default for FilenameCompleter { } 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 @@ fn filename_complete( 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 @@ fn filename_complete( 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 @@ fn filename_complete( 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 extract_word<'l>( } } -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 @@ pub fn longest_common_prefix(candidates: &[String]) -> Option<&str> { } 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 d93037d7abc87325b9955af1dbe78e29f6a4a74b..9d2f876a8be28cc048d69494811310e39dbc23e9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -53,7 +53,7 @@ use unicode_width::UnicodeWidthStr; 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 @@ fn complete_line<R: RawReader, C: Completer>( 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 complete_line<R: RawReader, C: Completer>( } } -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 @@ fn page_completions<R: RawReader>( cols, candidates .into_iter() - .map(|s| s.as_str().width()) + .map(|s| s.display().width()) .max() .unwrap() + min_col_pad, @@ -235,9 +235,9 @@ fn page_completions<R: RawReader>( 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 d3216de36ab613cf7037768856bbb0529d46de55..f2d6ea22e4c60aeb9499e401f4b16e184f7a8c11 100644 --- a/src/test/mod.rs +++ b/src/test/mod.rs @@ -24,6 +24,8 @@ fn init_editor(mode: EditMode, keys: &[KeyPress]) -> Editor<()> { 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"])) }