Skip to main content

Syntax Highlighting

JLine provides powerful syntax highlighting capabilities that can enhance the user experience of your command-line application.

Basic Highlighting

To add syntax highlighting to your LineReader, you need to implement the Highlighter interface:

import org.jline.reader.Highlighter;
import org.jline.reader.LineReader;
import org.jline.reader.LineReaderBuilder;
import org.jline.terminal.Terminal;
import org.jline.terminal.TerminalBuilder;
import org.jline.utils.AttributedString;
import org.jline.utils.AttributedStringBuilder;
import org.jline.utils.AttributedStyle;

// Create a simple highlighter
Highlighter highlighter = (reader, buffer, candidates) -> {
// Create a highlighted version of the buffer
AttributedString highlighted = new AttributedStringBuilder()
.append(buffer.toString(), AttributedStyle.DEFAULT.foreground(AttributedStyle.BLUE))
.toAttributedString();

// Add the highlighted buffer to the candidates list
candidates.add(highlighted);

// Return the highlighted buffer
return highlighted;
};

// Create a line reader with the highlighter
Terminal terminal = TerminalBuilder.builder().build();
LineReader reader = LineReaderBuilder.builder()
.terminal(terminal)
.highlighter(highlighter)
.build();

// Now when the user types, the input will be highlighted in blue
String line = reader.readLine("prompt> ");

Syntax-Aware Highlighting

For more sophisticated highlighting, you can create a highlighter that understands your command syntax:

public class CommandHighlighter implements Highlighter {
private static final AttributedStyle COMMAND_STYLE = AttributedStyle.BOLD.foreground(AttributedStyle.RED);
private static final AttributedStyle OPTION_STYLE = AttributedStyle.DEFAULT.foreground(AttributedStyle.BLUE);
private static final AttributedStyle ARG_STYLE = AttributedStyle.DEFAULT.foreground(AttributedStyle.GREEN);

private final Set<String> commands = Set.of("help", "list", "add", "remove", "exit");
private final Set<String> options = Set.of("-v", "--verbose", "-h", "--help", "-f", "--force");

@Override
public AttributedString highlight(LineReader reader, String buffer) {
AttributedStringBuilder builder = new AttributedStringBuilder();

// Simple parsing for demonstration
String[] words = buffer.split("\\s+");
for (int i = 0; i < words.length; i++) {
String word = words[i];

if (i > 0) {
builder.append(" ");
}

if (i == 0 && commands.contains(word)) {
// First word is a command
builder.append(word, COMMAND_STYLE);
} else if (options.contains(word)) {
// Word is an option
builder.append(word, OPTION_STYLE);
} else {
// Word is an argument
builder.append(word, ARG_STYLE);
}
}

return builder.toAttributedString();
}

@Override
public void setErrorPattern(Pattern pattern) {
// Not used in this example
}

@Override
public void setErrorIndex(int errorIndex) {
// Not used in this example
}
}

Highlighting with Regular Expressions

You can use regular expressions for more flexible highlighting:

public class RegexHighlighter implements Highlighter {
private final List<Pair<Pattern, AttributedStyle>> patterns = new ArrayList<>();

public RegexHighlighter() {
// Add patterns with corresponding styles
patterns.add(new Pair<>(Pattern.compile("\\b(help|exit|list|add|remove)\\b"),
AttributedStyle.BOLD.foreground(AttributedStyle.RED)));
patterns.add(new Pair<>(Pattern.compile("\\b(\\d+)\\b"),
AttributedStyle.DEFAULT.foreground(AttributedStyle.GREEN)));
patterns.add(new Pair<>(Pattern.compile("\\b(true|false)\\b"),
AttributedStyle.DEFAULT.foreground(AttributedStyle.YELLOW)));
patterns.add(new Pair<>(Pattern.compile("\"([^\"]*)\""),
AttributedStyle.DEFAULT.foreground(AttributedStyle.MAGENTA)));
}

@Override
public AttributedString highlight(LineReader reader, String buffer) {
AttributedString result = new AttributedString(buffer);

for (Pair<Pattern, AttributedStyle> pattern : patterns) {
Matcher matcher = pattern.getLeft().matcher(buffer);
while (matcher.find()) {
result = result.styleMatches(matcher, pattern.getRight());
}
}

return result;
}

@Override
public void setErrorPattern(Pattern pattern) {
// Not used in this example
}

@Override
public void setErrorIndex(int errorIndex) {
// Not used in this example
}
}

Error Highlighting

JLine can highlight syntax errors:

public class ErrorHighlighter implements Highlighter {
private Pattern errorPattern;
private int errorIndex = -1;

@Override
public AttributedString highlight(LineReader reader, String buffer) {
AttributedStringBuilder builder = new AttributedStringBuilder();
builder.append(buffer);

// Highlight error if present
if (errorIndex >= 0 && errorIndex < buffer.length()) {
builder.styleAt(errorIndex, AttributedStyle.DEFAULT.foreground(AttributedStyle.RED));
}

// Highlight pattern matches
if (errorPattern != null) {
Matcher matcher = errorPattern.matcher(buffer);
while (matcher.find()) {
builder.styleMatches(matcher, AttributedStyle.DEFAULT.foreground(AttributedStyle.RED));
}
}

return builder.toAttributedString();
}

@Override
public void setErrorPattern(Pattern pattern) {
this.errorPattern = pattern;
}

@Override
public void setErrorIndex(int errorIndex) {
this.errorIndex = errorIndex;
}
}

Advanced Highlighting Techniques

Incremental Highlighting

For better performance with long input:

public class IncrementalHighlighter implements Highlighter {
private AttributedString lastHighlighted;
private String lastBuffer = "";

@Override
public AttributedString highlight(LineReader reader, String buffer) {
// If the buffer hasn't changed, return the cached result
if (buffer.equals(lastBuffer) && lastHighlighted != null) {
return lastHighlighted;
}

// Perform highlighting
AttributedStringBuilder builder = new AttributedStringBuilder();
// ... highlighting logic ...

// Cache the result
lastBuffer = buffer;
lastHighlighted = builder.toAttributedString();

return lastHighlighted;
}

// Other methods...
}

Context-Aware Highlighting

Create highlighters that are aware of the current context:

public class ContextAwareHighlighter implements Highlighter {
private final Map<String, Highlighter> contextHighlighters = new HashMap<>();

public ContextAwareHighlighter() {
contextHighlighters.put("sql", new SqlHighlighter());
contextHighlighters.put("java", new JavaHighlighter());
contextHighlighters.put("default", new DefaultHighlighter());
}

@Override
public AttributedString highlight(LineReader reader, String buffer) {
// Get current context from reader variables
String context = (String) reader.getVariable("SYNTAX_CONTEXT");
if (context == null) {
context = "default";
}

// Use the appropriate highlighter for this context
Highlighter contextHighlighter = contextHighlighters.getOrDefault(context,
contextHighlighters.get("default"));
return contextHighlighter.highlight(reader, buffer);
}

// Other methods...
}

Best Practices

  • Keep highlighting logic simple and efficient
  • Use caching for complex highlighting patterns
  • Consider the context when highlighting
  • Use consistent colors for similar elements
  • Test highlighting with various input scenarios
  • Provide a way to disable highlighting for users who prefer plain text
  • Consider accessibility when choosing colors