From 8b9d97cd64d872d5aeac82f6cd2fb44a501076d2 Mon Sep 17 00:00:00 2001
From: gwenn <gtreguier@gmail.com>
Date: Wed, 24 Aug 2016 20:34:39 +0200
Subject: [PATCH] Introduce longest_common_prefix function

---
 src/completion.rs | 49 +++++++++++++++++++++++++++++++++++++++++++++++
 src/lib.rs        | 18 +++++++++++++++--
 2 files changed, 65 insertions(+), 2 deletions(-)

diff --git a/src/completion.rs b/src/completion.rs
index 042c9873..0c9ebbd4 100644
--- a/src/completion.rs
+++ b/src/completion.rs
@@ -159,6 +159,33 @@ pub fn extract_word<'l>(line: &'l str,
     }
 }
 
+pub fn longest_common_prefix(candidates: &[String]) -> Option<String> {
+    if candidates.is_empty() {
+        return None;
+    } else if candidates.len() == 1 {
+        return Some(candidates[0].clone());
+    }
+    let mut longest_common_prefix = 0;
+    'o: loop {
+        for i in 0..candidates.len() - 1 {
+            let b1 = candidates[i].as_bytes();
+            let b2 = candidates[i + 1].as_bytes();
+            if b1.len() <= longest_common_prefix || b2.len() <= longest_common_prefix ||
+               b1[i] != b2[i] {
+                break 'o;
+            }
+        }
+        longest_common_prefix += 1;
+    }
+    while !candidates[0].is_char_boundary(longest_common_prefix) {
+        longest_common_prefix -= 1;
+    }
+    if longest_common_prefix == 0 {
+        return None;
+    }
+    Some(String::from(&candidates[0][0..longest_common_prefix]))
+}
+
 #[cfg(test)]
 mod tests {
     use std::collections::BTreeSet;
@@ -170,4 +197,26 @@ mod tests {
         assert_eq!((4, "/usr/local/b"),
                    super::extract_word(line, line.len(), &break_chars));
     }
+
+    #[test]
+    pub fn longest_common_prefix() {
+        let mut candidates = vec![];
+        let lcp = super::longest_common_prefix(&candidates);
+        assert!(lcp.is_none());
+
+        let c1 = String::from("User");
+        candidates.push(c1.clone());
+        let lcp = super::longest_common_prefix(&candidates);
+        assert_eq!(Some(c1.clone()), lcp);
+
+        let c2 = String::from("Users");
+        candidates.push(c2.clone());
+        let lcp = super::longest_common_prefix(&candidates);
+        assert_eq!(Some(c1), lcp);
+
+        let c3 = String::from("");
+        candidates.push(c3.clone());
+        let lcp = super::longest_common_prefix(&candidates);
+        assert!(lcp.is_none());
+    }
 }
diff --git a/src/lib.rs b/src/lib.rs
index efcf868a..1dca7924 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -46,7 +46,7 @@ use std::sync::atomic;
 #[cfg(unix)]
 use nix::sys::signal;
 
-use completion::Completer;
+use completion::{Completer, longest_common_prefix};
 use consts::KeyPress;
 use history::History;
 use line_buffer::{LineBuffer, MAX_LINE, WordAction};
@@ -587,9 +587,23 @@ fn complete_line<R: Read>(rdr: &mut tty::RawReader<R>,
             }
         }
         Ok(Some(key))
-    } else {
+    } else if CompletionMode::List == completion_mode {
+        // beep if ambiguous
+        if candidates.len() > 1 {
+            try!(beep());
+        }
+        if let Some(lcp) = longest_common_prefix(&candidates) {
+            // if we can extend the item, extend it and return to main loop
+            if lcp.len() > s.line.pos() - start {
+                completer.update(&mut s.line, start, &lcp);
+                try!(s.refresh_line());
+                return Ok(None);
+            }
+        }
         // TODO ...
         Ok(None)
+    } else {
+        Ok(None)
     }
 }
 
-- 
GitLab