From 57d034c93705354a30432c9129a7261098cdd3ec Mon Sep 17 00:00:00 2001 From: Thibault Kruse Date: Wed, 12 Aug 2015 21:48:15 +0200 Subject: [PATCH 1/5] Avoid Stacktrace in (test-)log if rc file does not exist --- src/main/java/jline/internal/Configuration.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/jline/internal/Configuration.java b/src/main/java/jline/internal/Configuration.java index 1a74b2ce..26846763 100644 --- a/src/main/java/jline/internal/Configuration.java +++ b/src/main/java/jline/internal/Configuration.java @@ -58,7 +58,13 @@ private static Properties initProperties() { private static void loadProperties(final URL url, final Properties props) throws IOException { Log.debug("Loading properties from: ", url); - InputStream input = url.openStream(); + InputStream input; + try { + input = url.openStream(); + } catch (IOException e) { + Log.debug("Could not load properties from " + url + " : " + e.getMessage()); + return; + } try { props.load(new BufferedInputStream(input)); } From 4b6bdc0a8e34547155f90b6465ea75dad1eb15c8 Mon Sep 17 00:00:00 2001 From: Thibault Kruse Date: Thu, 3 Sep 2015 10:24:22 +0200 Subject: [PATCH 2/5] Add tests and javadoc --- .../console/completer/CompletionHandler.java | 8 ++ .../CandidateListCompletionHandlerTest.java | 99 +++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 src/test/java/jline/console/completer/CandidateListCompletionHandlerTest.java diff --git a/src/main/java/jline/console/completer/CompletionHandler.java b/src/main/java/jline/console/completer/CompletionHandler.java index ef04fbe6..e22aa2ee 100644 --- a/src/main/java/jline/console/completer/CompletionHandler.java +++ b/src/main/java/jline/console/completer/CompletionHandler.java @@ -22,5 +22,13 @@ */ public interface CompletionHandler { + /** + * execute completion, by showing candidates as alternatives, and possibly + * inserting a candidate at position, removing all characters between + * position and current cursol location. + * + * @param position start position in buffer for candidates + * @throws IOException + */ boolean complete(ConsoleReader reader, List candidates, int position) throws IOException; } diff --git a/src/test/java/jline/console/completer/CandidateListCompletionHandlerTest.java b/src/test/java/jline/console/completer/CandidateListCompletionHandlerTest.java new file mode 100644 index 00000000..f983bc80 --- /dev/null +++ b/src/test/java/jline/console/completer/CandidateListCompletionHandlerTest.java @@ -0,0 +1,99 @@ +package jline.console.completer; + +import jline.console.ConsoleReaderTestSupport; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.fusesource.jansi.Ansi.Attribute.INTENSITY_BOLD; +import static org.fusesource.jansi.Ansi.ansi; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class CandidateListCompletionHandlerTest extends ConsoleReaderTestSupport { + + @Test + public void testCompleteNoCandidates() throws Exception { + CandidateListCompletionHandler handler = new CandidateListCompletionHandler(); + List candidates = new ArrayList(); + assertTrue(handler.complete(console, candidates, 0)); + + assertEquals("", console.getCursorBuffer().toString()); + } + + @Test + public void testCompleteOneCandidate() throws Exception { + CandidateListCompletionHandler handler = new CandidateListCompletionHandler(); + List candidates = new ArrayList(); + candidates.add("foo"); + assertTrue(handler.complete(console, candidates, 0)); + assertEquals("foo ", console.getCursorBuffer().toString()); + } + + @Test + public void testCompleteOneCandidatePrefix() throws Exception { + CandidateListCompletionHandler handler = new CandidateListCompletionHandler(); + List candidates = new ArrayList(); + candidates.add("foo"); + String buffer = "the "; + console.putString(buffer); + console.moveCursor(buffer.length()); + assertTrue(handler.complete(console, candidates, buffer.length())); + assertEquals("the foo ", console.getCursorBuffer().toString()); + } + + @Test + public void testCompleteOneCandidateNoWhitespace() throws Exception { + CandidateListCompletionHandler handler = new CandidateListCompletionHandler(); + handler.setPrintSpaceAfterFullCompletion(false); + List candidates = new ArrayList(); + candidates.add("foo"); + assertTrue(handler.complete(console, candidates, 0)); + assertEquals("foo", console.getCursorBuffer().toString()); + } + + @Test + public void testCompleteOneCandidateANSI() throws Exception { + CandidateListCompletionHandler handler = new CandidateListCompletionHandler(); + //handler.setStripAnsi(true); + List candidates = new ArrayList(); + candidates.add(ansi().a(INTENSITY_BOLD).a("foo").reset().toString()); + assertTrue(handler.complete(console, candidates, 0)); + assertEquals("foo ", console.getCursorBuffer().toString()); + } + + @Test + public void testCompleteMultiCandidate() throws Exception { + CandidateListCompletionHandler handler = new CandidateListCompletionHandler(); + //handler.setStripAnsi(true); + List candidates = new ArrayList(); + candidates.add("foobar"); + candidates.add("foobuz"); + assertTrue(handler.complete(console, candidates, 0)); + assertEquals("foob", console.getCursorBuffer().toString()); + } + + @Test + public void testCompleteMultiCandidateANSI() throws Exception { + CandidateListCompletionHandler handler = new CandidateListCompletionHandler(); + handler.setStripAnsi(true); + List candidates = new ArrayList(); + candidates.add(ansi().a(INTENSITY_BOLD).a("foobar").reset().toString()); + candidates.add(ansi().a(INTENSITY_BOLD).a("foobuz").reset().toString()); + assertTrue(handler.complete(console, candidates, 0)); + assertEquals("foob", console.getCursorBuffer().toString()); + } + + @Test + public void testCompleteMultiCandidateANSIDisabled() throws Exception { + CandidateListCompletionHandler handler = new CandidateListCompletionHandler(); + List candidates = new ArrayList(); + candidates.add(ansi().a(INTENSITY_BOLD).a("foobar").reset().toString()); + candidates.add(ansi().a(INTENSITY_BOLD).a("foobuz").reset().toString()); + assertTrue(handler.complete(console, candidates, 0)); + assertTrue(console.getCursorBuffer().toString().endsWith("foob")); + assertFalse(console.getCursorBuffer().toString().startsWith("foob")); + } +} From 48664558cfd6dd98a792961c31c796f0cd9fd116 Mon Sep 17 00:00:00 2001 From: Thibault Kruse Date: Thu, 3 Sep 2015 11:09:29 +0200 Subject: [PATCH 3/5] Fix #213, fix #214 CompletionHandler: Add final Space outside setBuffer(), and independently of cursor position This causes spaces to be added where they woult not have been added before --- .../CandidateListCompletionHandler.java | 25 +++++++++----- .../java/jline/console/ConsoleReaderTest.java | 2 +- .../CandidateListCompletionHandlerTest.java | 34 +++++++++++++++++++ 3 files changed, 51 insertions(+), 10 deletions(-) diff --git a/src/main/java/jline/console/completer/CandidateListCompletionHandler.java b/src/main/java/jline/console/completer/CandidateListCompletionHandler.java index e8eb6f3d..7ef6dacb 100644 --- a/src/main/java/jline/console/completer/CandidateListCompletionHandler.java +++ b/src/main/java/jline/console/completer/CandidateListCompletionHandler.java @@ -64,19 +64,26 @@ public boolean complete(final ConsoleReader reader, final List can if (candidates.size() == 1) { String value = Ansi.stripAnsi(candidates.get(0).toString()); - if (buf.cursor == buf.buffer.length() - && printSpaceAfterFullCompletion - && !value.endsWith(" ")) { - value += " "; + // no insert if the only candidate is the same as the current buffer + if (buf.length() >= pos + value.length() && + value.equals(buf.toString().substring(pos, pos + value.length()))) { + reader.setCursorPosition(pos + value.length()); + } else { + setBuffer(reader, value, pos); } - // fail if the only candidate is the same as the current buffer - if (value.equals(buf.toString())) { - return false; + if (printSpaceAfterFullCompletion + && !value.endsWith(" ")) { + // at end of buffer or next char is not blank already + if ((reader.getCursorBuffer().cursor >= reader.getCursorBuffer().length() || + reader.getCursorBuffer().buffer.toString().charAt(reader.getCursorBuffer().cursor) != ' ')) { + reader.putString(" "); + } else { + // if blank existed, move beyond it + reader.moveCursor(1); + } } - setBuffer(reader, value, pos); - return true; } else if (candidates.size() > 1) { diff --git a/src/test/java/jline/console/ConsoleReaderTest.java b/src/test/java/jline/console/ConsoleReaderTest.java index 663bc33c..e7f3011a 100644 --- a/src/test/java/jline/console/ConsoleReaderTest.java +++ b/src/test/java/jline/console/ConsoleReaderTest.java @@ -642,7 +642,7 @@ public void testComplete() throws Exception { out.write("read and\033[D\033[D\t\n".getBytes()); - assertEquals("read andnd", console.readLine()); + assertEquals("read and ", console.readLine()); out.close(); } diff --git a/src/test/java/jline/console/completer/CandidateListCompletionHandlerTest.java b/src/test/java/jline/console/completer/CandidateListCompletionHandlerTest.java index f983bc80..56f80ceb 100644 --- a/src/test/java/jline/console/completer/CandidateListCompletionHandlerTest.java +++ b/src/test/java/jline/console/completer/CandidateListCompletionHandlerTest.java @@ -32,6 +32,40 @@ public void testCompleteOneCandidate() throws Exception { assertEquals("foo ", console.getCursorBuffer().toString()); } + @Test + public void testCompleteOneCandidateInsert() throws Exception { + CandidateListCompletionHandler handler = new CandidateListCompletionHandler(); + List candidates = new ArrayList(); + candidates.add("foobar"); + console.putString("foo"); + console.setCursorPosition(0); + assertTrue(handler.complete(console, candidates, 0)); + assertEquals("foobar foo", console.getCursorBuffer().toString()); + } + + @Test + public void testCompleteOneCandidateInsertMiddle() throws Exception { + CandidateListCompletionHandler handler = new CandidateListCompletionHandler(); + List candidates = new ArrayList(); + candidates.add("foo"); + console.putString("the fis"); + console.setCursorPosition(5); + assertTrue(handler.complete(console, candidates, 4)); + assertEquals("the foo is", console.getCursorBuffer().toString()); + assertEquals(8, console.getCursorBuffer().cursor); + } + + @Test + public void testCompleteOneCandidateInsertMiddleWhitespace() throws Exception { + CandidateListCompletionHandler handler = new CandidateListCompletionHandler(); + List candidates = new ArrayList(); + candidates.add("foo"); + console.putString("the f is"); + console.setCursorPosition(5); + assertTrue(handler.complete(console, candidates, 4)); + assertEquals("the foo is", console.getCursorBuffer().toString()); + } + @Test public void testCompleteOneCandidatePrefix() throws Exception { CandidateListCompletionHandler handler = new CandidateListCompletionHandler(); From 3a36a01b70d71bc934169d4124d6744bb2561a3d Mon Sep 17 00:00:00 2001 From: Thibault Kruse Date: Thu, 3 Sep 2015 12:39:13 +0200 Subject: [PATCH 4/5] fix #214: extract methods for easier specialization --- .../CandidateListCompletionHandler.java | 62 ++++++++++++------- 1 file changed, 40 insertions(+), 22 deletions(-) diff --git a/src/main/java/jline/console/completer/CandidateListCompletionHandler.java b/src/main/java/jline/console/completer/CandidateListCompletionHandler.java index 7ef6dacb..82822eac 100644 --- a/src/main/java/jline/console/completer/CandidateListCompletionHandler.java +++ b/src/main/java/jline/console/completer/CandidateListCompletionHandler.java @@ -63,28 +63,7 @@ public boolean complete(final ConsoleReader reader, final List can // if there is only one completion, then fill in the buffer if (candidates.size() == 1) { String value = Ansi.stripAnsi(candidates.get(0).toString()); - - // no insert if the only candidate is the same as the current buffer - if (buf.length() >= pos + value.length() && - value.equals(buf.toString().substring(pos, pos + value.length()))) { - reader.setCursorPosition(pos + value.length()); - } else { - setBuffer(reader, value, pos); - } - - if (printSpaceAfterFullCompletion - && !value.endsWith(" ")) { - // at end of buffer or next char is not blank already - if ((reader.getCursorBuffer().cursor >= reader.getCursorBuffer().length() || - reader.getCursorBuffer().buffer.toString().charAt(reader.getCursorBuffer().cursor) != ' ')) { - reader.putString(" "); - } else { - // if blank existed, move beyond it - reader.moveCursor(1); - } - } - - return true; + return completeSingleCandidate(reader, pos, buf, value); } else if (candidates.size() > 1) { String value = getUnambiguousCompletions(candidates); @@ -99,6 +78,45 @@ else if (candidates.size() > 1) { return true; } + protected boolean completeSingleCandidate(ConsoleReader reader, int pos, CursorBuffer buf, String value) throws IOException { + // no insert if the only candidate is the same as the current buffer + if (buf.length() >= pos + value.length() && + value.equals(buf.toString().substring(pos, pos + value.length()))) { + reader.setCursorPosition(pos + value.length()); + } else { + setBuffer(reader, value, pos); + } + + if (printSpaceAfterFullCompletion + && !value.endsWith(" ")) { + doPrintSpaceAfterFullCOmpletion(reader); + } + + return true; + } + + /** + * This method is called after completing a candidate that + * does not end with a blank, when the option printSpaceAfterFullCompletion is true. + * + * The standard behavior is to insert a blank unless the next char is a blank, + * wherever the cursor is in the buffer, and to move the cursor beyond the + * inserted / existing blank. + * + * @param reader + * @throws IOException + */ + protected void doPrintSpaceAfterFullCOmpletion(ConsoleReader reader) throws IOException { + // at end of buffer or next char is not blank already + if ((reader.getCursorBuffer().cursor >= reader.getCursorBuffer().length() || + reader.getCursorBuffer().buffer.toString().charAt(reader.getCursorBuffer().cursor) != ' ')) { + reader.putString(" "); + } else { + // if blank existed, move beyond it + reader.moveCursor(1); + } + } + public static void setBuffer(final ConsoleReader reader, final CharSequence value, final int offset) throws IOException { From 75407cf003cc4ee15dee49e0c46ba5603c4bc8fb Mon Sep 17 00:00:00 2001 From: Thibault Kruse Date: Thu, 3 Sep 2015 10:26:46 +0200 Subject: [PATCH 5/5] fix #207: Option to make CandidateCompletionHandler consume matchin chars --- .../CandidateListCompletionHandler.java | 57 +++++++++- .../CandidateListCompletionHandlerTest.java | 106 +++++++++++++++++- 2 files changed, 160 insertions(+), 3 deletions(-) diff --git a/src/main/java/jline/console/completer/CandidateListCompletionHandler.java b/src/main/java/jline/console/completer/CandidateListCompletionHandler.java index 82822eac..8507594b 100644 --- a/src/main/java/jline/console/completer/CandidateListCompletionHandler.java +++ b/src/main/java/jline/console/completer/CandidateListCompletionHandler.java @@ -37,6 +37,13 @@ public class CandidateListCompletionHandler private boolean printSpaceAfterFullCompletion = true; private boolean stripAnsi; + /** + * if true, existing text after cursor matchinga completion to insert + * will not be pushed back behind the completion, but replaced + * by the completion + */ + private boolean consumeMatchingSuffix = false; + public boolean getPrintSpaceAfterFullCompletion() { return printSpaceAfterFullCompletion; } @@ -45,6 +52,14 @@ public void setPrintSpaceAfterFullCompletion(boolean printSpaceAfterFullCompleti this.printSpaceAfterFullCompletion = printSpaceAfterFullCompletion; } + public boolean getConsumeMatchingSuffix() { + return consumeMatchingSuffix; + } + + public void setConsumeMatchingSuffix(boolean consumeMatchingSuffix) { + this.consumeMatchingSuffix = consumeMatchingSuffix; + } + public boolean isStripAnsi() { return stripAnsi; } @@ -117,17 +132,55 @@ protected void doPrintSpaceAfterFullCOmpletion(ConsoleReader reader) throws IOEx } } - public static void setBuffer(final ConsoleReader reader, final CharSequence value, final int offset) throws + public void setBuffer(final ConsoleReader reader, final CharSequence value, final int offset) throws IOException { + if (getConsumeMatchingSuffix()) { + // consume only if prefix matches + int commonPrefixLength = greatestCommonPrefixLength(value, + reader.getCursorBuffer().buffer.toString().substring(offset)); + if (commonPrefixLength == value.length()) { + // nothing to do other than advancing the cursor + reader.setCursorPosition(offset + value.length()); + return; + } + } + int suffixStart = 0; + // backspace cursor to start of completion while ((reader.getCursorBuffer().cursor > offset) && reader.backspace()) { - // empty + suffixStart++; + } + + if (getConsumeMatchingSuffix()) { + int currentVirtualPos = offset; + String currentBuffer = reader.getCursorBuffer().buffer.toString(); + while ( + suffixStart < value.length() // value still has chars to delete + && currentBuffer.length() > currentVirtualPos // buffer still has chars to delete + // character to delete matches value suffix + && currentBuffer.charAt(currentVirtualPos) == value.charAt(suffixStart) + // do delete + && reader.delete()) { + suffixStart ++; + currentVirtualPos++; + } } reader.putString(value); reader.setCursorPosition(offset + value.length()); } + static int greatestCommonPrefixLength(final CharSequence a, final CharSequence b) { + int minLength = Math.min(a.length(), b.length()); + int i = 0; + for (; i < minLength; i++) { + if (a.charAt(i) != b.charAt(i)) { + break; + } + } + return i; + } + /** * Print out the candidates. If the size of the candidates is greater than the * {@link ConsoleReader#getAutoprintThreshold}, they prompt with a warning. diff --git a/src/test/java/jline/console/completer/CandidateListCompletionHandlerTest.java b/src/test/java/jline/console/completer/CandidateListCompletionHandlerTest.java index 56f80ceb..3879d907 100644 --- a/src/test/java/jline/console/completer/CandidateListCompletionHandlerTest.java +++ b/src/test/java/jline/console/completer/CandidateListCompletionHandlerTest.java @@ -14,6 +14,14 @@ public class CandidateListCompletionHandlerTest extends ConsoleReaderTestSupport { + @Test + public void testGreatestCommonPrefixLength() { + assertEquals(0, CandidateListCompletionHandler.greatestCommonPrefixLength("foo", "bar")); + assertEquals(0, CandidateListCompletionHandler.greatestCommonPrefixLength("foo", "")); + assertEquals(3, CandidateListCompletionHandler.greatestCommonPrefixLength("foo", "foobar")); + assertEquals(3, CandidateListCompletionHandler.greatestCommonPrefixLength("foobar", "foogle")); + } + @Test public void testCompleteNoCandidates() throws Exception { CandidateListCompletionHandler handler = new CandidateListCompletionHandler(); @@ -42,7 +50,7 @@ public void testCompleteOneCandidateInsert() throws Exception { assertTrue(handler.complete(console, candidates, 0)); assertEquals("foobar foo", console.getCursorBuffer().toString()); } - + @Test public void testCompleteOneCandidateInsertMiddle() throws Exception { CandidateListCompletionHandler handler = new CandidateListCompletionHandler(); @@ -66,6 +74,102 @@ public void testCompleteOneCandidateInsertMiddleWhitespace() throws Exception { assertEquals("the foo is", console.getCursorBuffer().toString()); } + @Test + public void testCompleteOneCandidateOverwritePartial() throws Exception { + CandidateListCompletionHandler handler = new CandidateListCompletionHandler(); + handler.setConsumeMatchingSuffix(true); + List candidates = new ArrayList(); + candidates.add("foobar"); + console.putString("foo"); + console.setCursorPosition(0); + assertTrue(handler.complete(console, candidates, 0)); + assertEquals("foobar ", console.getCursorBuffer().toString()); + } + + @Test + public void testCompleteOneCandidateOverwriteNonMatchingPrefix() throws Exception { + CandidateListCompletionHandler handler = new CandidateListCompletionHandler(); + handler.setConsumeMatchingSuffix(true); + List candidates = new ArrayList(); + candidates.add("foobar"); + console.putString("bum"); + console.setCursorPosition(0); + assertTrue(handler.complete(console, candidates, 0)); + assertEquals("foobar bum", console.getCursorBuffer().toString()); + } + + @Test + public void testCompleteOneCandidateOverwritePartialWithin() throws Exception { + CandidateListCompletionHandler handler = new CandidateListCompletionHandler(); + handler.setConsumeMatchingSuffix(true); + List candidates = new ArrayList(); + candidates.add("foobar"); + console.putString("foo"); + console.setCursorPosition(2); + assertTrue(handler.complete(console, candidates, 0)); + assertEquals("foobar ", console.getCursorBuffer().toString()); + } + + @Test + public void testCompleteOneCandidateOverwritePartialEnd() throws Exception { + CandidateListCompletionHandler handler = new CandidateListCompletionHandler(); + handler.setConsumeMatchingSuffix(true); + List candidates = new ArrayList(); + candidates.add("foobar"); + console.putString("foo"); + console.setCursorPosition(3); + assertTrue(handler.complete(console, candidates, 0)); + assertEquals("foobar ", console.getCursorBuffer().toString()); + } + + @Test + public void testCompleteOneCandidateOverwriteFull() throws Exception { + CandidateListCompletionHandler handler = new CandidateListCompletionHandler(); + handler.setConsumeMatchingSuffix(true); + List candidates = new ArrayList(); + candidates.add("foobar"); + console.putString("the foobar"); + console.setCursorPosition(4); + assertTrue(handler.complete(console, candidates, 4)); + assertEquals("the foobar ", console.getCursorBuffer().toString()); + } + + @Test + public void testCompleteOneCandidateOverwriteFullMiddle() throws Exception { + CandidateListCompletionHandler handler = new CandidateListCompletionHandler(); + handler.setConsumeMatchingSuffix(true); + List candidates = new ArrayList(); + candidates.add("foobar"); + console.putString("the foobar"); + console.setCursorPosition(7); + assertTrue(handler.complete(console, candidates, 4)); + assertEquals("the foobar ", console.getCursorBuffer().toString()); + } + + @Test + public void testCompleteOneCandidateOverwriteFullEnd() throws Exception { + CandidateListCompletionHandler handler = new CandidateListCompletionHandler(); + handler.setConsumeMatchingSuffix(true); + List candidates = new ArrayList(); + candidates.add("foobar"); + console.putString("the foobar"); + console.setCursorPosition(10); + assertTrue(handler.complete(console, candidates, 4)); + assertEquals("the foobar ", console.getCursorBuffer().toString()); + } + + @Test + public void testCompleteOneCandidateOverwriteNonMatchingSuffix() throws Exception { + CandidateListCompletionHandler handler = new CandidateListCompletionHandler(); + handler.setConsumeMatchingSuffix(true); + List candidates = new ArrayList(); + candidates.add("foobar"); + console.putString("foobum"); + console.setCursorPosition(0); + assertTrue(handler.complete(console, candidates, 0)); + assertEquals("foobar um", console.getCursorBuffer().toString()); + } + @Test public void testCompleteOneCandidatePrefix() throws Exception { CandidateListCompletionHandler handler = new CandidateListCompletionHandler();