Repository: kkawakam/rustyline (rust)

Issue:
CompletionType::List does not support completion candidates that are shorter than the input
Thank you for maintaining this crate — we use it in [Numbat](https://github.com/sharkdp/numbat) and it works great.

We want to implement a feature where a user could type something like `\hbar` in the REPL, press <kbd>Tab</kbd> and it would be replaced by `ℏ`. This seems to work fine when using `CompletionType::Circular`, but it does not work for `CompletionType::List`. Words will only be replaced if they are longer than the input. This is probably due to this check:

https://github.com/kkawakam/rustyline/blob/fafa0b9ba7cc6fbd21c055ea06ed42be6504ae62/src/lib.rs#L154

Would it be possible to support this use case?

Remark: that check above should probably use `lcp.unicode_width()` instead of `lcp.len()` (?).

Pull request description:
Support completion candidates that are shorter than the input
Fix #748

Patch:
diff --git a/src/lib.rs b/src/lib.rs
index a298b12..972af0f 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -151,7 +151,7 @@ fn complete_line<H: Helper>(
     } else if CompletionType::List == config.completion_type() {
         if let Some(lcp) = longest_common_prefix(&candidates) {
             // if we can extend the item, extend it
-            if lcp.len() > s.line.pos() - start {
+            if lcp.len() > s.line.pos() - start || candidates.len() == 1 {
                 completer.update(&mut s.line, start, lcp, &mut s.changes);
                 s.refresh_line()?;
             }


Test patch:
diff --git a/src/test/mod.rs b/src/test/mod.rs
index 8eca186..6eb317d 100644
--- a/src/test/mod.rs
+++ b/src/test/mod.rs
@@ -1,7 +1,7 @@
 use std::vec::IntoIter;
 
 use crate::completion::Completer;
-use crate::config::{Config, EditMode};
+use crate::config::{CompletionType, Config, EditMode};
 use crate::edit::init_state;
 use crate::highlight::Highlighter;
 use crate::hint::Hinter;
@@ -35,7 +35,16 @@ impl Completer for SimpleCompleter {
         _pos: usize,
         _ctx: &Context<'_>,
     ) -> Result<(usize, Vec<String>)> {
-        Ok((0, vec![line.to_owned() + "t"]))
+        Ok((
+            0,
+            if line == "rus" {
+                vec![line.to_owned() + "t"]
+            } else if line == "\\hbar" {
+                vec!["ℏ".to_owned()]
+            } else {
+                vec![]
+            },
+        ))
     }
 }
 impl Hinter for SimpleCompleter {
@@ -61,7 +70,7 @@ fn complete_line() {
     let mut input_state = InputState::new(&config, &bindings);
     let keys = vec![E::ENTER];
     let mut rdr: IntoIter<KeyEvent> = keys.into_iter();
-    let cmd = super::complete_line(&mut rdr, &mut s, &mut input_state, &Config::default()).unwrap();
+    let cmd = super::complete_line(&mut rdr, &mut s, &mut input_state, &config).unwrap();
     assert_eq!(
         Some(Cmd::AcceptOrInsertLine {
             accept_in_the_middle: true
@@ -72,6 +81,25 @@ fn complete_line() {
     assert_eq!(4, s.line.pos());
 }
 
+#[test]
+fn complete_symbol() {
+    let mut out = Sink::default();
+    let history = crate::history::DefaultHistory::new();
+    let helper = Some(SimpleCompleter);
+    let mut s = init_state(&mut out, "\\hbar", 5, helper.as_ref(), &history);
+    let config = Config::builder()
+        .completion_type(CompletionType::List)
+        .build();
+    let bindings = Bindings::new();
+    let mut input_state = InputState::new(&config, &bindings);
+    let keys = vec![E::ENTER];
+    let mut rdr: IntoIter<KeyEvent> = keys.into_iter();
+    let cmd = super::complete_line(&mut rdr, &mut s, &mut input_state, &config).unwrap();
+    assert_eq!(None, cmd);
+    assert_eq!("ℏ", s.line.as_str());
+    assert_eq!(3, s.line.pos());
+}
+
 // `keys`: keys to press
 // `expected_line`: line after enter key
 fn assert_line(mode: EditMode, keys: &[KeyEvent], expected_line: &str) {