Dave Jarvis' Repositories

git clone https://repo.autonoma.ca/repo/keenwrite.git

MarkdownSyntaxHighlighter: support overlapping styles

AuthorKarl Tauber <email>
Date2015-07-26 17:24:44 GMT+0200
Commit13171aa1f3e0829a20946013ef40ecac5ae5e453
Parentb3ec481
Delta73 lines added, 15 lines removed, 58-line increase
src/main/java/org/markdownwriterfx/editor/MarkdownSyntaxHighlighter.java
package org.markdownwriterfx.editor;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
implements Visitor
{
- private int textLength;
- private StyleSpansBuilder<Collection<String>> spansBuilder;
- private int nextIndex;
+ private enum StyleClass {
+ strong,
+ em,
+
+ // headers
+ h1,
+ h2,
+ h3,
+ h4,
+ h5,
+ h6,
+ };
+
+ /**
+ * style bits (1 << StyleClass.ordinal()) for each character
+ * simplifies implementation of overlapping styles
+ */
+ private int[] styleClassBits;
static void highlight(StyleClassedTextArea textArea, RootNode astRoot) {
+ assert StyleClass.values().length <= 32;
assert Platform.isFxApplicationThread();
private StyleSpans<Collection<String>> computeHighlighting(RootNode astRoot, int textLength) {
- this.textLength = textLength;
+ styleClassBits = new int[textLength];
- spansBuilder = new StyleSpansBuilder<>();
- nextIndex = 0;
+ // visit all nodes
astRoot.accept(this);
- spansBuilder.add(Collections.emptyList(), textLength - nextIndex);
+
+ // build style spans
+ StyleSpansBuilder<Collection<String>> spansBuilder = new StyleSpansBuilder<>();
+ if (styleClassBits.length > 0) {
+ int spanStart = 0;
+ int previousBits = styleClassBits[0];
+
+ for (int i = 1; i < styleClassBits.length; i++) {
+ int bits = styleClassBits[i];
+ if (bits == previousBits)
+ continue;
+
+ spansBuilder.add(toStyleClasses(previousBits), i - spanStart);
+
+ spanStart = i;
+ previousBits = bits;
+ }
+ spansBuilder.add(toStyleClasses(previousBits), styleClassBits.length - spanStart);
+ } else
+ spansBuilder.add(Collections.emptyList(), 0);
return spansBuilder.create();
+ }
+
+ private Collection<String> toStyleClasses(int bits) {
+ if (bits == 0)
+ return Collections.emptyList();
+
+ Collection<String> styleClasses = new ArrayList<>(1);
+ for (StyleClass styleClass : StyleClass.values()) {
+ if ((bits & (1 << styleClass.ordinal())) != 0)
+ styleClasses.add(styleClass.name());
+ }
+ return styleClasses;
}
@Override
public void visit(HeaderNode node) {
- setStyleClass(node, "h" + node.getLevel());
+ StyleClass styleClass;
+ switch (node.getLevel()) {
+ case 1: styleClass = StyleClass.h1; break;
+ case 2: styleClass = StyleClass.h2; break;
+ case 3: styleClass = StyleClass.h3; break;
+ case 4: styleClass = StyleClass.h4; break;
+ case 5: styleClass = StyleClass.h5; break;
+ case 6: styleClass = StyleClass.h6; break;
+ default: return;
+ }
+ setStyleClass(node, styleClass);
+ visitChildren(node);
}
@Override
public void visit(StrongEmphSuperNode node) {
- setStyleClass(node, node.isStrong() ? "strong" : "em");
+ setStyleClass(node, node.isStrong() ? StyleClass.strong : StyleClass.em);
}
}
- private void setStyleClass(Node node, String styleClass) {
+ private void setStyleClass(Node node, StyleClass styleClass) {
// because PegDownProcessor.prepareSource() adds two trailing newlines
// to the text before parsing, we need to limit the end index
- int startIndex = node.getStartIndex();
- int endIndex = Math.min(node.getEndIndex(), textLength);
+ int start = node.getStartIndex();
+ int end = Math.min(node.getEndIndex(), styleClassBits.length);
+ int styleBit = 1 << styleClass.ordinal();
- spansBuilder.add(Collections.emptyList(), startIndex - nextIndex);
- spansBuilder.add(Collections.singleton(styleClass), endIndex - startIndex);
- nextIndex = endIndex;
+ for (int i = start; i < end; i++)
+ styleClassBits[i] |= styleBit;
}
}