blob: d0156eda28d7927726f694e83bf46bf975ad5da5 [file] [log] [blame]
/*
* 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 java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.felix.gogo.runtime.CommandSessionImpl;
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.apache.felix.service.command.CommandSession;
import org.apache.felix.service.command.Function;
import org.jline.reader.LineReader;
import org.jline.reader.LineReader.RegionType;
import org.jline.reader.impl.DefaultHighlighter;
import org.jline.utils.AttributedString;
import org.jline.utils.AttributedStringBuilder;
import org.jline.utils.AttributedStyle;
import org.jline.utils.WCWidth;
public class Highlighter extends DefaultHighlighter {
public static final String DEFAULT_HIGHLIGHTER_COLORS = "rs=35:st=32:nu=32:co=32:va=36:vn=36:fu=94:bf=91:re=90";
private final CommandSession session;
public Highlighter(CommandSession session) {
this.session = session;
}
public AttributedString highlight(LineReader reader, String buffer) {
try {
Program program = null;
List<Token> tokens = null;
List<Statement> statements = null;
String repaired = buffer;
while (program == null) {
try {
org.apache.felix.gogo.runtime.Parser parser = new org.apache.felix.gogo.runtime.Parser(repaired);
program = parser.program();
tokens = parser.tokens();
statements = parser.statements();
} catch (EOFError e) {
repaired = repaired + " " + e.repair();
// Make sure we don't loop forever
if (repaired.length() > buffer.length() + 1024) {
return new AttributedStringBuilder().append(buffer).toAttributedString();
}
}
}
Map<String, String> colors = Posix.getColorMap(session, "HIGHLIGHTER", DEFAULT_HIGHLIGHTER_COLORS);
int underlineStart = -1;
int underlineEnd = -1;
int negativeStart = -1;
int negativeEnd = -1;
String search = reader.getSearchTerm();
if (search != null && search.length() > 0) {
underlineStart = buffer.indexOf(search);
if (underlineStart >= 0) {
underlineEnd = underlineStart + search.length() - 1;
}
}
if (reader.getRegionActive() != RegionType.NONE) {
negativeStart = reader.getRegionMark();
negativeEnd = reader.getBuffer().cursor();
if (negativeStart > negativeEnd) {
int x = negativeEnd;
negativeEnd = negativeStart;
negativeStart = x;
}
if (reader.getRegionActive() == RegionType.LINE) {
while (negativeStart > 0 && reader.getBuffer().atChar(negativeStart - 1) != '\n') {
negativeStart--;
}
while (negativeEnd < reader.getBuffer().length() - 1 && reader.getBuffer().atChar(negativeEnd + 1) != '\n') {
negativeEnd++;
}
}
}
Type[] types = new Type[repaired.length()];
Arrays.fill(types, Type.Unknown);
int cur = 0;
for (Token token : tokens) {
// We're on the repair side, so exit now
if (token.start() >= buffer.length()) {
break;
}
if (token.start() > cur) {
cur = token.start();
}
// Find corresponding statement
Statement statement = null;
for (int i = statements.size() - 1; i >= 0; i--) {
Statement s = statements.get(i);
if (s.start() <= cur && cur < s.start() + s.length()) {
statement = s;
break;
}
}
// Reserved tokens
Type type = Type.Unknown;
if (Token.eq(token, "{")
|| Token.eq(token, "}")
|| Token.eq(token, "(")
|| Token.eq(token, ")")
|| Token.eq(token, "[")
|| Token.eq(token, "]")
|| Token.eq(token, "|")
|| Token.eq(token, ";")
|| Token.eq(token, "=")) {
type = Type.Reserved;
} else if (token.charAt(0) == '\'' || token.charAt(0) == '"') {
type = Type.String;
} else if (token.toString().matches("^[-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?$")) {
type = Type.Number;
} else if (token.charAt(0) == '$') {
type = Type.Variable;
} else if (((Set<?>) session.get(CommandSessionImpl.CONSTANTS)).contains(token.toString())
|| Token.eq(token, "null") || Token.eq(token, "false") || Token.eq(token, "true")) {
type = Type.Constant;
} else {
boolean isFirst = statement != null && statement.tokens().size() > 0
&& token == statement.tokens().get(0);
boolean isThirdWithNext = statement != null && statement.tokens().size() > 3
&& token == statement.tokens().get(2);
boolean isAssign = statement != null && statement.tokens().size() > 1
&& Token.eq(statement.tokens().get(1), "=");
if (isFirst && isAssign) {
type = Type.VariableName;
}
if (isFirst && !isAssign || isAssign && isThirdWithNext) {
Object v = session.get(Shell.resolve(session, token.toString()));
type = (v instanceof Function) ? Type.Function : Type.BadFunction;
}
}
Arrays.fill(types, token.start(), Math.min(token.start() + token.length(), types.length), type);
cur = Math.min(token.start() + token.length(), buffer.length());
}
if (buffer.length() < repaired.length()) {
Arrays.fill(types, buffer.length(), repaired.length(), Type.Repair);
}
AttributedStringBuilder sb = new AttributedStringBuilder();
for (int i = 0; i < repaired.length(); i++) {
sb.style(AttributedStyle.DEFAULT);
applyStyle(sb, colors, types[i]);
if (i >= underlineStart && i <= underlineEnd) {
sb.style(sb.style().underline());
}
if (i >= negativeStart && i <= negativeEnd) {
sb.style(sb.style().inverse());
}
char c = repaired.charAt(i);
if (c == '\t' || c == '\n') {
sb.append(c);
} else if (c < 32) {
sb.style(sb.style().inverseNeg())
.append('^')
.append((char) (c + '@'))
.style(sb.style().inverseNeg());
} else {
int w = WCWidth.wcwidth(c);
if (w > 0) {
sb.append(c);
}
}
}
return sb.toAttributedString();
} catch (SyntaxError e) {
return super.highlight(reader, buffer);
}
}
private void applyStyle(AttributedStringBuilder sb, Map<String, String> colors, Type type) {
Posix.applyStyle(sb, colors, type.color);
}
enum Type {
Reserved("rs"),
String("st"),
Number("nu"),
Variable("va"),
VariableName("vn"),
Function("fu"),
BadFunction("bf"),
Constant("co"),
Unknown("un"),
Repair("re");
private final String color;
Type(String color) {
this.color = color;
}
}
}