From a3a115b62ebb7c0828661159aa27772b5d9d2fc5 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Tue, 23 Jan 2018 09:28:21 +0100 Subject: [PATCH] Improve ordering of candidates and groups, fixes #205 and fixes #210 --- .../org/jline/reader/impl/LineReaderImpl.java | 39 +++++++++++++------ .../org/jline/reader/impl/CompletionTest.java | 2 +- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/reader/src/main/java/org/jline/reader/impl/LineReaderImpl.java b/reader/src/main/java/org/jline/reader/impl/LineReaderImpl.java index 25a6c4481..345896dd2 100644 --- a/reader/src/main/java/org/jline/reader/impl/LineReaderImpl.java +++ b/reader/src/main/java/org/jline/reader/impl/LineReaderImpl.java @@ -27,14 +27,10 @@ import java.util.ListIterator; import java.util.Map; import java.util.Map.Entry; -import java.util.NavigableMap; import java.util.Objects; import java.util.Set; import java.util.TreeMap; -import java.util.function.Function; -import java.util.function.IntBinaryOperator; -import java.util.function.Predicate; -import java.util.function.Supplier; +import java.util.function.*; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -3828,8 +3824,7 @@ protected boolean doComplete(CompletionType lst, boolean useMenu, boolean prefix int errors = getInt(ERRORS, DEFAULT_ERRORS); // Build a list of sorted candidates - NavigableMap> sortedCandidates = - new TreeMap<>(caseInsensitive ? String.CASE_INSENSITIVE_ORDER : null); + Map> sortedCandidates = new HashMap<>(); for (Candidate cand : candidates) { sortedCandidates .computeIfAbsent(AttributedString.fromAnsi(cand.value()).toString(), s -> new ArrayList<>()) @@ -3993,6 +3988,15 @@ else if (isSet(Option.RECOGNIZE_EXACT)) { return true; } + protected Comparator getCandidateComparator(boolean caseInsensitive, String word) { + String wdi = caseInsensitive ? word.toLowerCase() : word; + ToIntFunction wordDistance = w -> distance(wdi, caseInsensitive ? w.toLowerCase() : w); + return Comparator + .comparing(Candidate::value, Comparator.comparingInt(wordDistance)) + .thenComparing(Candidate::value, Comparator.comparingInt(String::length)) + .thenComparing(Comparator.naturalOrder()); + } + protected String getOthersGroupName() { return getString(OTHERS_GROUP_NAME, DEFAULT_OTHERS_GROUP_NAME); } @@ -4001,6 +4005,12 @@ protected String getOriginalGroupName() { return getString(ORIGINAL_GROUP_NAME, DEFAULT_ORIGINAL_GROUP_NAME); } + + protected Comparator getGroupComparator() { + return Comparator.comparingInt(s -> getOthersGroupName().equals(s) ? 1 : getOriginalGroupName().equals(s) ? -1 : 0) + .thenComparing(String::toLowerCase, Comparator.naturalOrder()); + } + private void mergeCandidates(List possible) { // Merge candidates if the have the same key Map> keyedCandidates = new HashMap<>(); @@ -4335,9 +4345,12 @@ protected boolean doList(List possible, String completed, boolean run .filter(c -> caseInsensitive ? c.value().toLowerCase().startsWith(current.toLowerCase()) : c.value().startsWith(current)) + .sorted(getCandidateComparator(caseInsensitive, current)) .collect(Collectors.toList()); } else { - cands = possible; + cands = possible.stream() + .sorted(getCandidateComparator(caseInsensitive, current)) + .collect(Collectors.toList()); } post = () -> { AttributedString t = insertSecondaryPrompts(AttributedStringBuilder.append(prompt, buf.toString()), new ArrayList<>()); @@ -4423,13 +4436,17 @@ protected PostResult computePost(List possible, Candidate selection, protected PostResult computePost(List possible, Candidate selection, List ordered, String completed, Function wcwidth, int width, boolean autoGroup, boolean groupName, boolean rowsFirst) { List strings = new ArrayList<>(); if (groupName) { - LinkedHashMap> sorted = new LinkedHashMap<>(); + Comparator groupComparator = getGroupComparator(); + Map> sorted; + sorted = groupComparator != null + ? new TreeMap<>(groupComparator) + : new LinkedHashMap<>(); for (Candidate cand : possible) { String group = cand.group(); - sorted.computeIfAbsent(group != null ? group : "", s -> new TreeMap<>()) + sorted.computeIfAbsent(group != null ? group : "", s -> new LinkedHashMap<>()) .put(cand.value(), cand); } - for (Map.Entry> entry : sorted.entrySet()) { + for (Map.Entry> entry : sorted.entrySet()) { String group = entry.getKey(); if (group.isEmpty() && sorted.size() > 1) { group = getOthersGroupName(); diff --git a/reader/src/test/java/org/jline/reader/impl/CompletionTest.java b/reader/src/test/java/org/jline/reader/impl/CompletionTest.java index b59d9ab94..020d79c27 100644 --- a/reader/src/test/java/org/jline/reader/impl/CompletionTest.java +++ b/reader/src/test/java/org/jline/reader/impl/CompletionTest.java @@ -69,7 +69,7 @@ public void testListAndMenu() throws IOException { assertFalse(reader.list); assertFalse(reader.menu); - assertBuffer("foo", new TestBuffer("fo\t\t")); + assertBuffer("foobar", new TestBuffer("fo\t\t")); assertFalse(reader.list); assertTrue(reader.menu);