Skip to content

Commit

Permalink
Make completion and parser work together, fixes #125 and fixes #245
Browse files Browse the repository at this point in the history
  • Loading branch information
gnodet committed Apr 5, 2018
1 parent df01bed commit b84705a
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 20 deletions.
23 changes: 23 additions & 0 deletions reader/src/main/java/org/jline/reader/CompletingParsedLine.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright (c) 2002-2018, the original author or authors.
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
*
* http://www.opensource.org/licenses/bsd-license.php
*/
package org.jline.reader;

/**
* An extension of {@link ParsedLine} that, being aware of the quoting and escaping rules
* of the {@link org.jline.reader.Parser} that produced it, knows if and how a completion candidate
* should be escaped/quoted.
*
* @author Eric Bottard
*/
@FunctionalInterface
public interface CompletingParsedLine {

CharSequence emit(CharSequence candidate);

}
85 changes: 66 additions & 19 deletions reader/src/main/java/org/jline/reader/impl/DefaultParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,13 @@
*/
package org.jline.reader.impl;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.*;
import java.util.function.Predicate;

import org.jline.reader.CompletingParsedLine;
import org.jline.reader.EOFError;
import org.jline.reader.ParsedLine;
import org.jline.reader.Parser;
import org.jline.reader.Parser.ParseContext;

public class DefaultParser implements Parser {

Expand Down Expand Up @@ -149,7 +147,8 @@ public ParsedLine parse(final String line, final int cursor, ParseContext contex
? "quote" : "dquote");
}

return new ArgumentList(line, words, wordIndex, wordCursor, cursor);
String openingQuote = quoteStart >= 0 ? line.substring(quoteStart, quoteStart + 1) : null;
return new ArgumentList(line, words, wordIndex, wordCursor, cursor, openingQuote);
}

/**
Expand All @@ -173,13 +172,13 @@ public boolean isQuoteChar(final CharSequence buffer, final int pos) {
if (pos < 0) {
return false;
}

for (int i = 0; (quoteChars != null) && (i < quoteChars.length); i++) {
if (buffer.charAt(pos) == quoteChars[i]) {
return !isEscaped(buffer, pos);
if (quoteChars != null) {
for (char e : quoteChars) {
if (e == buffer.charAt(pos)) {
return !isEscaped(buffer, pos);
}
}
}

return false;
}

Expand All @@ -190,13 +189,13 @@ public boolean isEscapeChar(final CharSequence buffer, final int pos) {
if (pos < 0) {
return false;
}

for (int i = 0; (escapeChars != null) && (i < escapeChars.length); i++) {
if (buffer.charAt(pos) == escapeChars[i]) {
return !isEscaped(buffer, pos); // escape escape
if (escapeChars != null) {
for (char e : escapeChars) {
if (e == buffer.charAt(pos)) {
return !isEscaped(buffer, pos);
}
}
}

return false;
}

Expand All @@ -214,7 +213,6 @@ public boolean isEscaped(final CharSequence buffer, final int pos) {
if (pos <= 0) {
return false;
}

return isEscapeChar(buffer, pos - 1);
}

Expand All @@ -227,12 +225,34 @@ public boolean isDelimiterChar(CharSequence buffer, int pos) {
return Character.isWhitespace(buffer.charAt(pos));
}

private boolean isRawEscapeChar(char key) {
if (escapeChars != null) {
for (char e : escapeChars) {
if (e == key) {
return true;
}
}
}
return false;
}

private boolean isRawQuoteChar(char key) {
if (quoteChars != null) {
for (char e : quoteChars) {
if (e == key) {
return true;
}
}
}
return false;
}

/**
* The result of a delimited buffer.
*
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
*/
public static class ArgumentList implements ParsedLine
public class ArgumentList implements ParsedLine, CompletingParsedLine
{
private final String line;

Expand All @@ -244,12 +264,15 @@ public static class ArgumentList implements ParsedLine

private final int cursor;

public ArgumentList(final String line, final List<String> words, final int wordIndex, final int wordCursor, final int cursor) {
private final String openingQuote;

public ArgumentList(final String line, final List<String> words, final int wordIndex, final int wordCursor, final int cursor, final String openingQuote) {
this.line = line;
this.words = Collections.unmodifiableList(Objects.requireNonNull(words));
this.wordIndex = wordIndex;
this.wordCursor = wordCursor;
this.cursor = cursor;
this.openingQuote = openingQuote;
}

public int wordIndex() {
Expand Down Expand Up @@ -280,6 +303,30 @@ public String line() {
return line;
}

public CharSequence emit(CharSequence candidate) {
StringBuilder sb = new StringBuilder(candidate);
Predicate<Integer> needToBeEscaped;
// Completion is protected by an opening quote:
// Delimiters (spaces) don't need to be escaped, nor do other quotes, but everything else does.
// Also, close the quote at the end
if (openingQuote != null) {
needToBeEscaped = i -> isRawEscapeChar(sb.charAt(i)) || String.valueOf(sb.charAt(i)).equals(openingQuote);
}
// No quote protection, need to escape everything: delimiter chars (spaces), quote chars
// and escapes themselves
else {
needToBeEscaped = i -> isDelimiterChar(sb, i) || isRawEscapeChar(sb.charAt(i)) || isRawQuoteChar(sb.charAt(i));
}
for (int i = 0; i < sb.length(); i++) {
if (needToBeEscaped.test(i)) {
sb.insert(i++, escapeChars[0]);
}
}
if (openingQuote != null) {
sb.append(openingQuote);
}
return sb;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3993,7 +3993,8 @@ else if (isSet(Option.RECOGNIZE_EXACT)) {
buf.move(line.word().length() - line.wordCursor());
buf.backspace(line.word().length());
}
buf.write(completion.value());
CompletingParsedLine cpl = (line instanceof CompletingParsedLine) ? ((CompletingParsedLine) line) : t -> t;
buf.write(cpl.emit(completion.value()));
if (completion.complete()) {
if (buf.currChar() != ' ') {
buf.write(" ");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@

public class CompletionTest extends ReaderTestSupport {

@Test
public void testCompleteEscape() throws IOException {
reader.setCompleter(new StringsCompleter("foo bar"));
assertBuffer("foo\\ bar ", new TestBuffer("fo\t"));
assertBuffer("\"foo bar\" ", new TestBuffer("\"fo\t"));
}

@Test
public void testListAndMenu() throws IOException {
reader.setCompleter(new StringsCompleter("foo", "foobar"));
Expand Down

0 comments on commit b84705a

Please sign in to comment.