Skip to content

Commit

Permalink
Merge branch 'mattirn-brackets'
Browse files Browse the repository at this point in the history
  • Loading branch information
gnodet committed Jan 29, 2019
2 parents e1d6bc1 + 2d1e01e commit cfa7d0c
Show file tree
Hide file tree
Showing 4 changed files with 185 additions and 7 deletions.
8 changes: 8 additions & 0 deletions builtins/src/test/java/org/jline/example/Example.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.jline.keymap.KeyMap;
import org.jline.reader.*;
import org.jline.reader.impl.DefaultParser;
import org.jline.reader.impl.DefaultParser.Bracket;
import org.jline.reader.impl.LineReaderImpl;
import org.jline.reader.impl.completer.ArgumentCompleter;
import org.jline.reader.impl.completer.StringsCompleter;
Expand Down Expand Up @@ -130,6 +131,12 @@ public static void main(String[] args) throws IOException {
p.setEofOnUnclosedQuote(true);
parser = p;
break label;
case "brackets":
prompt = "long-prompt> ";
DefaultParser p2 = new DefaultParser();
p2.eofOnUnclosedBracket(Bracket.CURLY,Bracket.ROUND,Bracket.SQUARE);
parser = p2;
break label;
case "foo":
completer = new ArgumentCompleter(
new StringsCompleter("foo11", "foo12", "foo13"),
Expand Down Expand Up @@ -228,6 +235,7 @@ public void complete(LineReader reader, ParsedLine line, List<Candidate> candida
.terminal(terminal)
.completer(completer)
.parser(parser)
.variable(LineReader.SECONDARY_PROMPT_PATTERN, "%M%P > ")
.build();

if (timer) {
Expand Down
151 changes: 146 additions & 5 deletions reader/src/main/java/org/jline/reader/impl/DefaultParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@
import org.jline.reader.Parser;

public class DefaultParser implements Parser {

public enum Bracket {
ROUND, // ()
CURLY, // {}
SQUARE, // []
ANGLE; // <>
}

private char[] quoteChars = {'\'', '"'};

Expand All @@ -25,7 +32,11 @@ public class DefaultParser implements Parser {
private boolean eofOnUnclosedQuote;

private boolean eofOnEscapedNewLine;

private char[] openingBrackets = null;

private char[] closingBrackets = null;

//
// Chainable setters
//
Expand All @@ -45,6 +56,11 @@ public DefaultParser eofOnUnclosedQuote(boolean eofOnUnclosedQuote) {
return this;
}

public DefaultParser eofOnUnclosedBracket(Bracket... brackets){
setEofOnUnclosedBracket(brackets);
return this;
}

public DefaultParser eofOnEscapedNewLine(boolean eofOnEscapedNewLine) {
this.eofOnEscapedNewLine = eofOnEscapedNewLine;
return this;
Expand Down Expand Up @@ -86,6 +102,39 @@ public boolean isEofOnEscapedNewLine() {
return eofOnEscapedNewLine;
}

public void setEofOnUnclosedBracket(Bracket... brackets){
if (brackets == null) {
openingBrackets = null;
closingBrackets = null;
} else {
Set<Bracket> bs = new HashSet<>(Arrays.asList(brackets));
openingBrackets = new char[bs.size()];
closingBrackets = new char[bs.size()];
int i = 0;
for (Bracket b : bs) {
switch (b) {
case ROUND:
openingBrackets[i] = '(';
closingBrackets[i] = ')';
break;
case CURLY:
openingBrackets[i] = '{';
closingBrackets[i] = '}';
break;
case SQUARE:
openingBrackets[i] = '[';
closingBrackets[i] = ']';
break;
case ANGLE:
openingBrackets[i] = '<';
closingBrackets[i] = '>';
break;
}
i++;
}
}
}

public ParsedLine parse(final String line, final int cursor, ParseContext context) {
List<String> words = new LinkedList<>();
StringBuilder current = new StringBuilder();
Expand All @@ -95,6 +144,8 @@ public ParsedLine parse(final String line, final int cursor, ParseContext contex
int rawWordCursor = -1;
int rawWordLength = -1;
int rawWordStart = 0;
BracketChecker bracketChecker = new BracketChecker();
boolean quotedWord = false;

for (int i = 0; (line != null) && (i < line.length()); i++) {
// once we reach the cursor, set the
Expand All @@ -110,12 +161,20 @@ public ParsedLine parse(final String line, final int cursor, ParseContext contex
if (quoteStart < 0 && isQuoteChar(line, i)) {
// Start a quote block
quoteStart = i;
if (current.length()==0) {
quotedWord = true;
} else {
current.append(line.charAt(i));
}
} else if (quoteStart >= 0 && line.charAt(quoteStart) == line.charAt(i) && !isEscaped(line, i)) {
// End quote block
quoteStart = -1;
if (rawWordCursor >= 0 && rawWordLength < 0) {
if (!quotedWord) {
current.append(line.charAt(i));
} else if (rawWordCursor >= 0 && rawWordLength < 0) {
rawWordLength = i - rawWordStart + 1;
}
quoteStart = -1;
quotedWord = false;
} else if (quoteStart < 0 && isDelimiter(line, i)) {
// Delimiter
if (current.length() > 0) {
Expand All @@ -129,6 +188,7 @@ public ParsedLine parse(final String line, final int cursor, ParseContext contex
} else {
if (!isEscapeChar(line, i)) {
current.append(line.charAt(i));
bracketChecker.check(line, i);
}
}
}
Expand All @@ -154,8 +214,14 @@ public ParsedLine parse(final String line, final int cursor, ParseContext contex
throw new EOFError(-1, -1, "Missing closing quote", line.charAt(quoteStart) == '\''
? "quote" : "dquote");
}
if (bracketChecker.isOpeningBracketMissing() && context != ParseContext.COMPLETE) {
throw new EOFError(-1, -1, "Missing opening bracket", "missing: " + bracketChecker.getMissingOpeningBracket());
}
if (bracketChecker.isClosingBracketMissing() && context != ParseContext.COMPLETE) {
throw new EOFError(-1, -1, "Missing closing brackets", "add: " + bracketChecker.getMissingClosingBrackets());
}

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

Expand Down Expand Up @@ -274,6 +340,67 @@ private boolean isRawQuoteChar(char key) {
return false;
}

private class BracketChecker {
private int missingOpeningBracket = -1;
private List<Integer> nested = new ArrayList<>();

public BracketChecker(){}

public void check(final CharSequence buffer, final int pos){
if (openingBrackets == null || pos < 0) {
return;
}
int bid = bracketId(openingBrackets, buffer, pos);
if (bid >= 0) {
nested.add(bid);
} else {
bid = bracketId(closingBrackets, buffer, pos);
if (bid >= 0) {
if (!nested.isEmpty() && bid == nested.get(nested.size()-1)) {
nested.remove(nested.size()-1);
} else {
missingOpeningBracket = bid;
}
}
}
}

public boolean isOpeningBracketMissing(){
return missingOpeningBracket != -1;
}

public String getMissingOpeningBracket(){
if (!isOpeningBracketMissing()) {
return null;
}
return Character.toString(openingBrackets[missingOpeningBracket]);
}

public boolean isClosingBracketMissing(){
return !nested.isEmpty();
}

public String getMissingClosingBrackets(){
if (!isClosingBracketMissing()) {
return null;
}
StringBuilder out = new StringBuilder();
for (int i = nested.size() - 1; i > -1; i--) {
out.append(closingBrackets[nested.get(i)]);
}
return out.toString();
}

private int bracketId(final char[] brackets, final CharSequence buffer, final int pos){
for (int i=0; i < brackets.length; i++) {
if (buffer.charAt(pos) == brackets[i]) {
return i;
}
}
return -1;
}
}

/**
* The result of a delimited buffer.
*
Expand Down Expand Up @@ -362,13 +489,27 @@ public CharSequence escape(CharSequence candidate, boolean complete) {
StringBuilder sb = new StringBuilder(candidate);
Predicate<Integer> needToBeEscaped;
String quote = openingQuote;
boolean middleQuotes = false;
if (openingQuote==null) {
for (int i=0; i < sb.length(); i++) {
if (isQuoteChar(sb, i)) {
middleQuotes = true;
break;
}
}
}
if (escapeChars != null) {
// 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);
}
// Completion is protected by middle quotes:
// Delimiters (spaces) don't need to be escaped, nor do quotes, but everything else does.
else if (middleQuotes) {
needToBeEscaped = i -> isRawEscapeChar(sb.charAt(i));
}
// No quote protection, need to escape everything: delimiter chars (spaces), quote chars
// and escapes themselves
else {
Expand All @@ -379,7 +520,7 @@ public CharSequence escape(CharSequence candidate, boolean complete) {
sb.insert(i++, escapeChars[0]);
}
}
} else if (openingQuote == null) {
} else if (openingQuote == null && !middleQuotes) {
for (int i = 0; i < sb.length(); i++) {
if (isDelimiterChar(sb, i)) {
quote = "'";
Expand Down Expand Up @@ -407,4 +548,4 @@ public int rawWordLength() {
}
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ public void testQuotedDelimit() {

delimited = parser.parse("1 '2 3'", 0);
assertEquals(Arrays.asList("1", "2 3"), delimited.words());

delimited = parser.parse("0'1 2' 3", 0);
assertEquals(Arrays.asList("0'1 2'", "3"), delimited.words());

delimited = parser.parse("'01 '2 3", 0);
assertEquals(Arrays.asList("01 2", "3"), delimited.words());
}

@Test
Expand All @@ -61,7 +67,7 @@ public void testMixedQuotes() {
assertEquals(Arrays.asList("1' '2", "3"), delimited.words());

delimited = parser.parse("'1\" 2' 3\"", 0);
assertEquals(Arrays.asList("1\" 2", "3"), delimited.words());
assertEquals(Arrays.asList("1\" 2", "3\""), delimited.words());
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,27 @@ public void escapeChars() throws Exception {
assertBuffer("bar'f", new TestBuffer("bar'f").tab());
}

}
@Test
public void middleQuotesEscapeCharsNull() throws Exception {
DefaultParser dp = (DefaultParser) reader.getParser();
dp.setEscapeChars(null);
reader.setVariable(LineReader.ERRORS, 0);
reader.setParser(dp);
reader.setCompleter(new StringsCompleter("/foo?name='foo bar'","/foo?name='foo qux'"));

assertBuffer("/foo?name='foo ", new TestBuffer("/f").tab());
assertBuffer("/foo?name='foo bar' ", new TestBuffer("/foo?name='foo b").tab());
}

@Test
public void middleQuotesEscapeChars() throws Exception {
DefaultParser dp = (DefaultParser) reader.getParser();
dp.setEscapeChars(new char[] { '\\' });
reader.setVariable(LineReader.ERRORS, 0);
reader.setParser(dp);
reader.setCompleter(new StringsCompleter("/foo?name='foo bar'","/foo?name='foo qux'"));

assertBuffer("/foo?name='foo ", new TestBuffer("/f").tab());
assertBuffer("/foo?name='foo bar' ", new TestBuffer("/foo?name='foo b").tab());
}
}

0 comments on commit cfa7d0c

Please sign in to comment.