Simplify syntax highlighting internals
This commit is contained in:
@ -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);
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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";
|
||||
|
@ -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
|
||||
);
|
||||
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user