diff --git a/demo/src/main/java/org/apache/felix/gogo/jline/ParsedLineImpl.java b/demo/src/main/java/org/apache/felix/gogo/jline/ParsedLineImpl.java new file mode 100644 index 000000000..a03740b70 --- /dev/null +++ b/demo/src/main/java/org/apache/felix/gogo/jline/ParsedLineImpl.java @@ -0,0 +1,205 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.felix.gogo.jline; + +import org.apache.felix.gogo.runtime.Parser.Program; +import org.apache.felix.gogo.runtime.Token; +import org.jline.reader.CompletingParsedLine; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; + +// +// TODO: remove when implemented in Felix Gogo JLine +// +public class ParsedLineImpl implements CompletingParsedLine { + + private final Program program; + private final String source; + private final int cursor; + private final List tokens; + private final int wordIndex; + private final int wordCursor; + private final CharSequence rawWord; + private final int rawWordCursor; + + public ParsedLineImpl(Program program, Token line, int cursor, List tokens) { + this.program = program; + this.source = line.toString(); + this.cursor = cursor - line.start(); + this.tokens = new ArrayList<>(); + for (Token token : tokens) { + this.tokens.add(unquote(token, null).toString()); + } + int wi = tokens.size(); + int wc = 0; + if (cursor >= 0) { + for (int i = 0; i < tokens.size(); i++) { + Token t = tokens.get(i); + if (t.start() > cursor) { + wi = i; + wc = 0; + this.tokens.add(i, ""); + break; + } + if (t.start() + t.length() >= cursor) { + wi = i; + wc = cursor - t.start(); + break; + } + } + } + if (wi == tokens.size()) { + this.tokens.add(""); + rawWord = ""; + wordCursor = 0; + } else { + rawWord = tokens.get(wi); + int[] c = new int[] { wc }; + unquote(rawWord, c); + wordCursor = c[0]; + } + wordIndex = wi; + rawWordCursor = wc; + } + + public String word() { + return tokens.get(wordIndex()); + } + + public int wordCursor() { + return wordCursor; + } + + public int wordIndex() { + return wordIndex; + } + + public List words() { + return tokens; + } + + public String line() { + return source; + } + + public int cursor() { + return cursor; + } + + public Program program() { + return program; + } + + public int rawWordCursor() { + return rawWordCursor; + } + + public int rawWordLength() { + return rawWord.length(); + } + + public CharSequence escape(CharSequence str, boolean complete) { + StringBuilder sb = new StringBuilder(str); + Predicate needToBeEscaped; + char quote = 0; + char first = rawWord.length() > 0 ? rawWord.charAt(0) : 0; + if (first == '\'') { + quote = '\''; + needToBeEscaped = i -> i == '\''; + } else if (first == '"') { + quote = '"'; + needToBeEscaped = i -> i == '"'; + } else { + needToBeEscaped = i -> i == ' ' || i == '\t'; + } + for (int i = 0; i < sb.length(); i++) { + if (needToBeEscaped.test(str.charAt(i))) { + sb.insert(i++, '\\'); + } + } + if (quote != 0) { + sb.insert(0, quote); + if (complete) { + sb.append(quote); + } + } + return sb; + } + + private CharSequence unquote(CharSequence arg, int[] cursor) { + boolean hasEscape = false; + for (int i = 0; i < arg.length(); i++) { + int c = arg.charAt(i); + if (c == '\\' || c == '"' || c == '\'') { + hasEscape = true; + break; + } + } + if (!hasEscape) { + return arg; + } + boolean singleQuoted = false; + boolean doubleQuoted = false; + boolean escaped = false; + StringBuilder buf = new StringBuilder(arg.length()); + for (int i = 0; i < arg.length(); i++) { + if (cursor != null && cursor[0] == i) { + cursor[0] = buf.length(); + cursor = null; + } + char c = arg.charAt(i); + if (doubleQuoted && escaped) { + if (c != '"' && c != '\\' && c != '$' && c != '%') { + buf.append('\\'); + } + buf.append(c); + escaped = false; + } else if (escaped) { + buf.append(c); + escaped = false; + } else if (singleQuoted) { + if (c == '\'') { + singleQuoted = false; + } else { + buf.append(c); + } + } else if (doubleQuoted) { + if (c == '\\') { + escaped = true; + } else if (c == '\"') { + doubleQuoted = false; + } else { + buf.append(c); + } + } else if (c == '\\') { + escaped = true; + } else if (c == '\'') { + singleQuoted = true; + } else if (c == '"') { + doubleQuoted = true; + } else { + buf.append(c); + } + } + return buf.toString(); + } + +} diff --git a/demo/src/main/java/org/apache/felix/gogo/jline/Parser.java b/demo/src/main/java/org/apache/felix/gogo/jline/Parser.java new file mode 100644 index 000000000..63f715a86 --- /dev/null +++ b/demo/src/main/java/org/apache/felix/gogo/jline/Parser.java @@ -0,0 +1,96 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.felix.gogo.jline; + +import org.apache.felix.gogo.runtime.EOFError; +import org.apache.felix.gogo.runtime.Parser.Program; +import org.apache.felix.gogo.runtime.Parser.Statement; +import org.apache.felix.gogo.runtime.SyntaxError; +import org.apache.felix.gogo.runtime.Token; +import org.jline.reader.ParsedLine; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +// +// TODO: remove when implemented in Felix Gogo JLine +// +public class Parser implements org.jline.reader.Parser { + + public ParsedLine parse(String line, int cursor, ParseContext context) throws org.jline.reader.SyntaxError { + try { + return doParse(line, cursor, context); + } catch (EOFError e) { + throw new org.jline.reader.EOFError(e.line(), e.column(), e.getMessage(), e.missing()); + } catch (SyntaxError e) { + throw new org.jline.reader.SyntaxError(e.line(), e.column(), e.getMessage()); + } + } + + private ParsedLine doParse(String line, int cursor, ParseContext parseContext) throws SyntaxError { + Program program = null; + List statements = null; + String repaired = line; + while (program == null) { + try { + org.apache.felix.gogo.runtime.Parser parser = new org.apache.felix.gogo.runtime.Parser(repaired); + program = parser.program(); + statements = parser.statements(); + } catch (EOFError e) { + // Make sure we don't loop forever + if (parseContext == ParseContext.COMPLETE && repaired.length() < line.length() + 1024) { + repaired = repaired + " " + e.repair(); + } else { + throw e; + } + } + } + // Find corresponding statement + Statement statement = null; + for (int i = statements.size() - 1; i >= 0; i--) { + Statement s = statements.get(i); + if (s.start() <= cursor) { + boolean isOk = true; + // check if there are only spaces after the previous statement + if (s.start() + s.length() < cursor) { + for (int j = s.start() + s.length(); isOk && j < cursor; j++) { + isOk = Character.isWhitespace(line.charAt(j)); + } + } + statement = s; + break; + } + } + if (statement != null && statement.tokens() != null && !statement.tokens().isEmpty()) { + if (repaired != line) { + Token stmt = statement.subSequence(0, line.length() - statement.start()); + List tokens = new ArrayList<>(statement.tokens()); + Token last = tokens.get(tokens.size() - 1); + tokens.set(tokens.size() - 1, last.subSequence(0, line.length() - last.start())); + return new ParsedLineImpl(program, stmt, cursor, tokens); + } + return new ParsedLineImpl(program, statement, cursor, statement.tokens()); + } else { + // TODO: + return new ParsedLineImpl(program, program, cursor, Collections.singletonList(program)); + } + } + +}