| /* |
| * 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; |
| } |
| } |
| |
| } |