Simplify syntax highlighting internals

This commit is contained in:
Clément Fournier
2017-09-23 23:49:56 +02:00
parent b8a99a2174
commit facc7c141c
10 changed files with 172 additions and 170 deletions

View File

@ -33,11 +33,10 @@ import net.sourceforge.pmd.util.fxdesigner.util.XMLSettingsLoader;
import net.sourceforge.pmd.util.fxdesigner.util.XMLSettingsSaver;
import net.sourceforge.pmd.util.fxdesigner.util.codearea.AvailableSyntaxHighlightings;
import net.sourceforge.pmd.util.fxdesigner.util.codearea.CustomCodeArea;
import net.sourceforge.pmd.util.fxdesigner.util.codearea.SyntaxHighlightingComputer;
import net.sourceforge.pmd.util.fxdesigner.util.codearea.SyntaxHighlighter;
import net.sourceforge.pmd.util.fxdesigner.view.DesignerWindow;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.binding.StringBinding;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.collections.ObservableList;
@ -95,6 +94,7 @@ public class DesignerWindowPresenter {
// no big deal
}
Designer.getMainStage().setOnCloseRequest(event -> {
try {
saveSettings();
@ -105,6 +105,8 @@ public class DesignerWindowPresenter {
}
});
initializeSyntaxHighlighting();
view.sourceCodeProperty().addListener((observable, oldValue, newValue) -> {
if (model.isRecompilationNeeded(newValue)) {
view.notifyOutdatedAST();
@ -113,23 +115,6 @@ public class DesignerWindowPresenter {
}
});
view.isSyntaxHighlightingEnabledProperty().addListener(((observable, wasEnabled, isEnabled) -> {
if (!wasEnabled && isEnabled) {
SyntaxHighlightingComputer computer = AvailableSyntaxHighlightings.getComputerForLanguage(model.getLanguageVersion().getLanguage());
if (computer != null) {
view.getCodeEditorArea().setSyntaxHighlightingEnabled(computer);
return;
}
} else if (isEnabled) {
return;
}
view.getCodeEditorArea().disableSyntaxHighlighting();
}));
view.onToggleSyntaxHighlightingClicked(null);
view.onToggleSyntaxHighlightingClicked(null);
view.getRefreshASTButton().setOnAction(this::onRefreshASTClicked);
view.getLicenseMenuItem().setOnAction(this::showLicensePopup);
view.getLoadSourceFromFileMenuItem().setOnAction(this::onOpenFileClicked);
@ -142,11 +127,11 @@ public class DesignerWindowPresenter {
/** Creates direct bindings from model properties to UI properties. */
private void bindModelToView() {
ObjectBinding<LanguageVersion> langVersionBinding
= Bindings.createObjectBinding(() -> (LanguageVersion) languageVersionToggleGroup.getSelectedToggle().getUserData(),
languageVersionToggleGroup.selectedToggleProperty());
model.languageVersionProperty().bind(langVersionBinding);
languageVersionToggleGroup.selectedToggleProperty().addListener((observable, oldValue, newValue) -> {
if (newValue != null) {
model.languageVersionProperty().setValue((LanguageVersion) newValue.getUserData());
}
});
ToggleGroup tg = view.getXpathVersionToggleGroup();
@ -244,13 +229,15 @@ public class DesignerWindowPresenter {
LanguageVersion defaultLangVersion = LanguageRegistry.getLanguage("Java").getDefaultVersion();
for (LanguageVersion version : supported) {
RadioMenuItem item = new RadioMenuItem(version.getShortName());
item.setToggleGroup(languageVersionToggleGroup);
item.setUserData(version);
items.add(item);
if (version != null) {
RadioMenuItem item = new RadioMenuItem(version.getShortName());
item.setToggleGroup(languageVersionToggleGroup);
item.setUserData(version);
items.add(item);
if (version.equals(defaultLangVersion)) {
item.setSelected(true);
if (version.equals(defaultLangVersion)) {
item.setSelected(true);
}
}
}
@ -259,6 +246,23 @@ public class DesignerWindowPresenter {
}
private void initializeSyntaxHighlighting() {
view.isSyntaxHighlightingEnabledProperty().addListener(((observable, wasEnabled, isEnabled) -> {
if (!wasEnabled && isEnabled) {
updateSyntaxHighlighter();
} else if (!isEnabled) {
view.getCodeEditorArea().disableSyntaxHighlighting();
}
}));
model.languageVersionProperty().addListener((obs, oldVal, newVal) -> {
if (newVal != null && !newVal.equals(oldVal)) {
updateSyntaxHighlighter();
}
});
}
private void onRefreshASTClicked(ActionEvent event) {
String source = view.getCodeEditorArea().getText();
if (model.isRecompilationNeeded(source)) {
@ -383,14 +387,24 @@ public class DesignerWindowPresenter {
}
}
MenuItem moreItem = new MenuItem();
moreItem.setText("...");
moreItem.setOnAction(this::onOpenFileClicked);
openRecentMenuItems.add(moreItem);
MenuItem clearItem = new MenuItem();
clearItem.setText("Clear recents");
clearItem.setOnAction(e -> recentFiles.clear());
openRecentMenuItems.add(clearItem);
view.getOpenRecentMenu().show();
}
private void updateSyntaxHighlighter() {
SyntaxHighlighter computer = AvailableSyntaxHighlightings.getComputerForLanguage(model.getLanguageVersion().getLanguage());
if (computer != null) {
view.getCodeEditorArea().setSyntaxHighlightingEnabled(computer);
} else {
view.getCodeEditorArea().disableSyntaxHighlighting();
}
}
private void saveSettings() throws IOException {
XMLSettingsSaver saver = XMLSettingsSaver.forFile(SETTINGS_FILE_NAME);

View File

@ -18,15 +18,15 @@ import net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.Java
* @since 6.0.0
*/
public enum AvailableSyntaxHighlightings {
JAVA("java", new SyntaxHighlightingComputer(new JavaSyntaxHighlighter())),
APEX("apex", new SyntaxHighlightingComputer(new ApexSyntaxHighlighter()));
JAVA("java", new JavaSyntaxHighlighter()),
APEX("apex", new ApexSyntaxHighlighter());
private final String language;
private final SyntaxHighlightingComputer computer;
private final SyntaxHighlighter computer;
AvailableSyntaxHighlightings(String languageTerseName, SyntaxHighlightingComputer computer) {
AvailableSyntaxHighlightings(String languageTerseName, SyntaxHighlighter computer) {
this.language = languageTerseName;
this.computer = computer;
}
@ -39,7 +39,7 @@ public enum AvailableSyntaxHighlightings {
*
* @return A highlighting computer if available, otherwise null
*/
public static SyntaxHighlightingComputer getComputerForLanguage(Language language) {
public static SyntaxHighlighter getComputerForLanguage(Language language) {
Optional<AvailableSyntaxHighlightings> found = Arrays.stream(AvailableSyntaxHighlightings.values())
.filter(e -> e.language.equals(language.getTerseName()))
.findFirst();

View File

@ -62,7 +62,7 @@ public class CustomCodeArea extends CodeArea {
}
public void setSyntaxHighlightingEnabled(SyntaxHighlightingComputer computer) {
public void setSyntaxHighlightingEnabled(SyntaxHighlighter computer) {
styleContext.setSyntaxHighlighting(computer);
this.replaceText(0, 0, " ");
this.undo();

View File

@ -35,7 +35,7 @@ class StyleContext implements Iterable<StyleLayer> {
/** Contains the primary highlighting layers. */
private Map<String, StyleLayer> layersById = new HashMap<>();
private SyntaxHighlightingComputer highlightingComputer;
private SyntaxHighlighter highlightingComputer;
private ExecutorService executorService;
private boolean isSyntaxHighlightingEnabled;
@ -72,7 +72,7 @@ class StyleContext implements Iterable<StyleLayer> {
*
* @param computer The computer to use
*/
public void setSyntaxHighlighting(SyntaxHighlightingComputer computer) {
public void setSyntaxHighlighting(SyntaxHighlighter computer) {
isSyntaxHighlightingEnabled = true;
Objects.requireNonNull(computer, "The syntax highlighting computer cannot be null");
@ -95,7 +95,7 @@ class StyleContext implements Iterable<StyleLayer> {
}
private void setSyntaxHighlightingComputer(SyntaxHighlightingComputer computer) {
private void setSyntaxHighlightingComputer(SyntaxHighlighter computer) {
this.highlightingComputer = computer;
if (executorService != null) {
executorService.shutdown();

View File

@ -0,0 +1,101 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.util.fxdesigner.util.codearea;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ExecutorService;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javafx.concurrent.Task;
/**
* Language specific engine for syntax highlighting.
*
* @author Clément Fournier
* @since 6.0.0
*/
public abstract class SyntaxHighlighter {
/**
* Schedules a syntax highlighting update task and returns it.
*
* @param text Text on which to compute the task.
* @param executor Task executor service
*
* @return The scheduled task
*/
Task<List<SpanBound>> computeHighlightingAsync(String text, ExecutorService executor) {
Task<List<SpanBound>> task = new Task<List<SpanBound>>() {
@Override
protected List<SpanBound> call() throws Exception {
return computeHighlighting(text);
}
};
executor.execute(task);
return task;
}
private List<SpanBound> computeHighlighting(String text) {
List<SpanBound> updated = new ArrayList<>();
Matcher matcher = getTokenizerPattern().matcher(text);
int lastKwEnd = 0;
try {
while (matcher.find()) {
String styleClass = null;
for (Entry<String, String> groupToClass : getGroupNameToCssClass().entrySet()) {
if (matcher.group(groupToClass.getKey()) != null) {
styleClass = groupToClass.getValue();
break;
}
}
assert styleClass != null;
updated.add(new SpanBound(lastKwEnd, Collections.emptySet(), true));
updated.add(new SpanBound(matcher.start(), Collections.emptySet(), false));
updated.add(new SpanBound(matcher.start(), Collections.singleton(styleClass), true));
updated.add(new SpanBound(matcher.end(), Collections.singleton(styleClass), false));
lastKwEnd = matcher.end();
}
} catch (StackOverflowError so) {
// matcher.find overflowed, possible when handling huge files with incorrect language
}
return updated;
}
/**
* Gets an ordered map of regex patterns to the CSS class that must be applied. The map must be ordered by
* priority.
*
* @return An ordered map
*/
public abstract Map<String, String> getGroupNameToCssClass();
/**
* Gets the pattern used to tokenize the text. Token groups must be named (syntax is {@code (?<GROUP_NAME>..)}).
* Tokens are mapped to a css class using the {@link #getGroupNameToCssClass()} method.
*
* @return The tokenizer pattern
*/
public abstract Pattern getTokenizerPattern();
/**
* Gets the identifier of the resource file containing appropriate css. This string must be suitable for use within
* a call to {@code getStyleSheets().add()}.
*
* @return The identifier of a css file
*/
public abstract String getCssFileIdentifier();
}

View File

@ -1,72 +0,0 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.util.fxdesigner.util.codearea;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.ExecutorService;
import java.util.regex.Matcher;
import net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.SyntaxHighlighter;
import javafx.concurrent.Task;
/**
* @author Clément Fournier
* @since 6.0.0
*/
public class SyntaxHighlightingComputer {
private final SyntaxHighlighter highlighter;
public SyntaxHighlightingComputer(SyntaxHighlighter highlighter) {
this.highlighter = highlighter;
}
public String getCssFileIdentifier() {
return highlighter.getCssFileIdentifier();
}
Task<List<SpanBound>> computeHighlightingAsync(String text, ExecutorService executor) {
Task<List<SpanBound>> task = new Task<List<SpanBound>>() {
@Override
protected List<SpanBound> call() throws Exception {
return computeHighlighting(text);
}
};
executor.execute(task);
return task;
}
private List<SpanBound> computeHighlighting(String text) {
List<SpanBound> updated = new ArrayList<>();
Matcher matcher = highlighter.getTokenizerPattern().matcher(text);
int lastKwEnd = 0;
while (matcher.find()) {
String styleClass = null;
for (Entry<String, String> groupToClass : highlighter.getGroupNameToCssClass().entrySet()) {
if (matcher.group(groupToClass.getKey()) != null) {
styleClass = groupToClass.getValue();
break;
}
}
assert styleClass != null;
updated.add(new SpanBound(lastKwEnd, Collections.emptySet(), true));
updated.add(new SpanBound(matcher.start(), Collections.emptySet(), false));
updated.add(new SpanBound(matcher.start(), Collections.singleton(styleClass), true));
updated.add(new SpanBound(matcher.end(), Collections.singleton(styleClass), false));
lastKwEnd = matcher.end();
}
return updated;
}
}

View File

@ -9,13 +9,15 @@ import java.util.LinkedHashMap;
import java.util.Map;
import java.util.regex.Pattern;
import net.sourceforge.pmd.util.fxdesigner.util.codearea.SyntaxHighlighter;
/**
* Syntax highlighter for Apex.
*
* @author Clément Fournier
* @since 6.0.0
*/
public class ApexSyntaxHighlighter implements SyntaxHighlighter {
public class ApexSyntaxHighlighter extends SyntaxHighlighter {
private static final String[] KEYWORDS = new String[] {
@ -42,7 +44,7 @@ public class ApexSyntaxHighlighter implements SyntaxHighlighter {
"virtual", "webservice", "when", "where", "while", "yesterday",
"after", "before", "count", "excludes", "first", "includes",
"last", "order", "sharing", "with",
};
};
private static final String KEYWORD_PATTERN = "\\b(" + String.join("|", KEYWORDS) + ")\\b";

View File

@ -9,13 +9,15 @@ import java.util.LinkedHashMap;
import java.util.Map;
import java.util.regex.Pattern;
import net.sourceforge.pmd.util.fxdesigner.util.codearea.SyntaxHighlighter;
/**
* Syntax highlighter for Java.
*
* @author Clément Fournier
* @since 6.0.0
*/
public class JavaSyntaxHighlighter implements SyntaxHighlighter {
public class JavaSyntaxHighlighter extends SyntaxHighlighter {
private static final String[] KEYWORDS = new String[] {
@ -38,19 +40,20 @@ public class JavaSyntaxHighlighter implements SyntaxHighlighter {
private static final String SEMICOLON_PATTERN = ";";
private static final String STRING_PATTERN = "\"([^\"\\\\]|\\\\.)*\"";
private static final String SINGLELINE_COMMENT_PATTERN = "//[^\n]*";
private static final String MULTILINE_COMMENT_PATTERN = "/\\*(.|\\R)*?\\*/";
private static final String MULTILINE_COMMENT_PATTERN = "/\\*.*?\\*/";
private static final String ANNOTATION_PATTERN = "@[\\w]+";
private static final Pattern PATTERN = Pattern.compile(
"(?<SINGLELINECOMMENT>" + SINGLELINE_COMMENT_PATTERN + ")"
+ "|(?<MULTILINECOMMENT>" + MULTILINE_COMMENT_PATTERN + ")"
+ "|(?<KEYWORD>" + KEYWORD_PATTERN + ")"
+ "|(?<ANNOTATION>" + ANNOTATION_PATTERN + ")"
+ "|(?<PAREN>" + PAREN_PATTERN + ")"
+ "|(?<BRACE>" + BRACE_PATTERN + ")"
+ "|(?<BRACKET>" + BRACKET_PATTERN + ")"
+ "|(?<SEMICOLON>" + SEMICOLON_PATTERN + ")"
+ "|(?<STRING>" + STRING_PATTERN + ")"
+ "|(?<ANNOTATION>" + ANNOTATION_PATTERN + ")"
+ "|(?<KEYWORD>" + KEYWORD_PATTERN + ")"
+ "|(?<STRING>" + STRING_PATTERN + ")",
Pattern.DOTALL
);

View File

@ -1,46 +0,0 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting;
import java.util.Map;
import java.util.regex.Pattern;
/**
* Basic syntax highlighter, which colors tokens with CSS style classes.
*
* @author Clément Fournier
* @since 6.0.0
*/
public interface SyntaxHighlighter {
/**
* Gets an ordered map of regex patterns to the CSS class that must be applied. The map must be ordered by
* priority.
*
* @return An ordered map
*/
Map<String, String> getGroupNameToCssClass();
/**
* Gets the pattern used to tokenize the text. Token groups must be named (syntax is {@code (?<GROUP_NAME>..)}).
* Tokens are mapped to a css class using the {@link #getGroupNameToCssClass()} method.
*
* @return The tokenizer pattern
*/
Pattern getTokenizerPattern();
/**
* Gets the identifier of the resource file containing appropriate css. This string must be suitable for use within
* a call to {@code getStyleSheets().add()}.
*
* @return The identifier of a css file
*/
String getCssFileIdentifier();
}

View File

@ -10,7 +10,7 @@
}
#mainCodeEditorArea .semicolon {
-fx-font-weight: bold;
}
#mainCodeEditorArea .annotation {
@ -34,7 +34,7 @@
}
#mainCodeEditorArea .multi-line-comment {
-fx-fill: #4a5d64;
-fx-fill: #4e6971;
-fx-font-style: italic;
}