Bind editor style layers to properties
This commit is contained in:
@ -17,11 +17,11 @@ import java.util.Stack;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.reactfx.Subscription;
|
||||
import org.reactfx.value.Val;
|
||||
|
||||
import net.sourceforge.pmd.lang.LanguageVersion;
|
||||
import net.sourceforge.pmd.lang.ast.Node;
|
||||
import net.sourceforge.pmd.lang.symboltable.NameOccurrence;
|
||||
import net.sourceforge.pmd.util.fxdesigner.app.AbstractController;
|
||||
import net.sourceforge.pmd.util.fxdesigner.app.CompositeSelectionSource;
|
||||
import net.sourceforge.pmd.util.fxdesigner.app.DesignerRoot;
|
||||
@ -133,7 +133,11 @@ public class MainDesignerController extends AbstractController<AbstractControlle
|
||||
|
||||
setupAuxclasspathMenuItem.setOnAction(e -> sourceEditorController.showAuxclasspathSetupPopup());
|
||||
|
||||
openEventLogMenuItem.setOnAction(e -> eventLogController.getValue().showPopup());
|
||||
openEventLogMenuItem.setOnAction(e -> {
|
||||
EventLogController wizard = eventLogController.getValue();
|
||||
Subscription parentToWizSubscription = wizard.errorNodesProperty().values().subscribe(sourceEditorController.currentErrorNodesProperty()::setValue);
|
||||
wizard.showPopup(parentToWizSubscription);
|
||||
});
|
||||
openEventLogMenuItem.textProperty().bind(
|
||||
getLogger().numNewLogEntriesProperty().map(i -> "Event log (" + (i > 0 ? i : "no") + " new)")
|
||||
);
|
||||
@ -145,10 +149,12 @@ public class MainDesignerController extends AbstractController<AbstractControlle
|
||||
protected void afterChildrenInit() {
|
||||
updateRecentFilesMenu();
|
||||
refreshAST(); // initial refreshing
|
||||
sourceEditorController.moveCaret(0, 0);
|
||||
|
||||
sourceEditorController.currentRuleResultsProperty().bind(xpathPanelController.currentResultsProperty());
|
||||
|
||||
// this is the only place where getSelectionEvents is called
|
||||
getSelectionEvents().distinct().subscribe(n -> CompositeSelectionSource.super.bubbleDown(n));
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -199,15 +205,6 @@ public class MainDesignerController extends AbstractController<AbstractControlle
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Highlight a list of name occurrences.
|
||||
*
|
||||
* @param occurrences May be empty but never null.
|
||||
*/
|
||||
public void highlightAsNameOccurences(List<NameOccurrence> occurrences) {
|
||||
sourceEditorController.highlightNameOccurrences(occurrences);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs an XPath (2.0) query on the current AST.
|
||||
* Performs no side effects.
|
||||
@ -223,31 +220,6 @@ public class MainDesignerController extends AbstractController<AbstractControlle
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handles nodes that potentially caused an error.
|
||||
* This can for example highlight nodes on the
|
||||
* editor. Effects can be reset with {@link #resetSelectedErrorNodes()}.
|
||||
*
|
||||
* @param n Node
|
||||
*/
|
||||
public void handleSelectedNodeInError(List<Node> n) {
|
||||
resetSelectedErrorNodes();
|
||||
sourceEditorController.highlightErrorNodes(n);
|
||||
}
|
||||
|
||||
public void resetSelectedErrorNodes() {
|
||||
sourceEditorController.clearErrorNodes();
|
||||
}
|
||||
|
||||
public void resetXPathResults() {
|
||||
sourceEditorController.clearXPathHighlight();
|
||||
}
|
||||
|
||||
/** Replaces previously highlighted XPath results with the given nodes. */
|
||||
public void highlightXPathResults(List<Node> nodes) {
|
||||
sourceEditorController.highlightXPathResults(nodes);
|
||||
}
|
||||
|
||||
private void showLicensePopup() {
|
||||
Alert licenseAlert = new Alert(AlertType.INFORMATION);
|
||||
licenseAlert.setWidth(500);
|
||||
|
@ -8,13 +8,9 @@ import static net.sourceforge.pmd.util.fxdesigner.util.DesignerIteratorUtil.pare
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.reactfx.EventStream;
|
||||
@ -27,9 +23,6 @@ import net.sourceforge.pmd.lang.ast.Node;
|
||||
import net.sourceforge.pmd.lang.ast.xpath.Attribute;
|
||||
import net.sourceforge.pmd.lang.metrics.LanguageMetricsProvider;
|
||||
import net.sourceforge.pmd.lang.symboltable.NameDeclaration;
|
||||
import net.sourceforge.pmd.lang.symboltable.NameOccurrence;
|
||||
import net.sourceforge.pmd.lang.symboltable.Scope;
|
||||
import net.sourceforge.pmd.lang.symboltable.ScopedNode;
|
||||
import net.sourceforge.pmd.util.fxdesigner.app.AbstractController;
|
||||
import net.sourceforge.pmd.util.fxdesigner.app.NodeSelectionSource;
|
||||
import net.sourceforge.pmd.util.fxdesigner.model.MetricResult;
|
||||
@ -122,13 +115,6 @@ public class NodeInfoPanelController extends AbstractController<MainDesignerCont
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean alwaysHandleSelection() {
|
||||
// it needs to handle name occurrences
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Displays info about a node. If null, the panels are reset.
|
||||
*
|
||||
@ -149,49 +135,6 @@ public class NodeInfoPanelController extends AbstractController<MainDesignerCont
|
||||
Platform.runLater(() -> displayAttributes(node));
|
||||
Platform.runLater(() -> displayMetrics(node));
|
||||
displayScopes(node);
|
||||
|
||||
if (node instanceof ScopedNode) {
|
||||
// not null as well
|
||||
highlightNameOccurences((ScopedNode) node);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void highlightNameOccurences(ScopedNode node) {
|
||||
|
||||
// For MethodNameDeclaration the scope is the method scope, which is not the scope it is declared
|
||||
// in but the scope it declares! That means that getDeclarations().get(declaration) returns null
|
||||
// and no name occurrences are found. We thus look in the parent, but ultimately the name occurrence
|
||||
// finder is broken since it can't find e.g. the use of a method in another scope. Plus in case of
|
||||
// overloads both overloads are reported to have a usage.
|
||||
|
||||
// Plus this is some serious law of Demeter breaking there...
|
||||
|
||||
Set<NameDeclaration> candidates = new HashSet<>(node.getScope().getDeclarations().keySet());
|
||||
|
||||
Optional.ofNullable(node.getScope().getParent())
|
||||
.map(Scope::getDeclarations)
|
||||
.map(Map::keySet)
|
||||
.ifPresent(candidates::addAll);
|
||||
|
||||
List<NameOccurrence> occurrences =
|
||||
candidates.stream()
|
||||
.filter(nd -> node.equals(nd.getNode()))
|
||||
.findFirst()
|
||||
.map(nd -> {
|
||||
// nd.getScope() != nd.getNode().getScope()?? wtf?
|
||||
|
||||
List<NameOccurrence> usages = nd.getNode().getScope().getDeclarations().get(nd);
|
||||
|
||||
if (usages == null) {
|
||||
usages = nd.getNode().getScope().getParent().getDeclarations().get(nd);
|
||||
}
|
||||
|
||||
return usages;
|
||||
})
|
||||
.orElse(Collections.emptyList());
|
||||
|
||||
parent.highlightAsNameOccurences(occurrences);
|
||||
}
|
||||
|
||||
|
||||
|
@ -12,10 +12,14 @@ import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.IntFunction;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ -27,7 +31,10 @@ import org.reactfx.value.Var;
|
||||
import net.sourceforge.pmd.lang.Language;
|
||||
import net.sourceforge.pmd.lang.LanguageVersion;
|
||||
import net.sourceforge.pmd.lang.ast.Node;
|
||||
import net.sourceforge.pmd.lang.symboltable.NameDeclaration;
|
||||
import net.sourceforge.pmd.lang.symboltable.NameOccurrence;
|
||||
import net.sourceforge.pmd.lang.symboltable.Scope;
|
||||
import net.sourceforge.pmd.lang.symboltable.ScopedNode;
|
||||
import net.sourceforge.pmd.util.ClasspathClassLoader;
|
||||
import net.sourceforge.pmd.util.fxdesigner.app.AbstractController;
|
||||
import net.sourceforge.pmd.util.fxdesigner.app.CompositeSelectionSource;
|
||||
@ -79,6 +86,8 @@ public class SourceEditorController extends AbstractController<MainDesignerContr
|
||||
private final ASTManager astManager;
|
||||
|
||||
private final Var<Node> currentFocusNode = Var.newSimpleVar(null);
|
||||
private final Var<List<Node>> currentRuleResults = Var.newSimpleVar(Collections.emptyList());
|
||||
private final Var<List<Node>> currentErrorNodes = Var.newSimpleVar(Collections.emptyList());
|
||||
|
||||
private final Var<List<File>> auxclasspathFiles = Var.newSimpleVar(emptyList());
|
||||
private final Val<ClassLoader> auxclasspathClassLoader = auxclasspathFiles.map(fileList -> {
|
||||
@ -132,12 +141,15 @@ public class SourceEditorController extends AbstractController<MainDesignerContr
|
||||
|
||||
codeEditorArea.setParagraphGraphicFactory(lineNumberFactory());
|
||||
|
||||
currentRuleResultsProperty().values().subscribe(this::highlightXPathResults);
|
||||
currentErrorNodesProperty().values().subscribe(this::highlightErrorNodes);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void afterParentInit() {
|
||||
DesignerUtil.rewire(astManager.languageVersionProperty(), languageVersionUIProperty);
|
||||
moveCaret(0, 0);
|
||||
}
|
||||
|
||||
|
||||
@ -237,24 +249,6 @@ public class SourceEditorController extends AbstractController<MainDesignerContr
|
||||
}
|
||||
|
||||
|
||||
/** Clears the error nodes. */
|
||||
public void clearErrorNodes() {
|
||||
codeEditorArea.clearStyleLayer(StyleLayerIds.ERROR);
|
||||
}
|
||||
|
||||
|
||||
/** Clears the name occurrences. */
|
||||
public void clearNameOccurences() {
|
||||
codeEditorArea.clearStyleLayer(StyleLayerIds.NAME_OCCURENCE);
|
||||
}
|
||||
|
||||
|
||||
/** Clears the highlighting of XPath results. */
|
||||
public void clearXPathHighlight() {
|
||||
codeEditorArea.clearStyleLayer(StyleLayerIds.XPATH_RESULT);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Highlights the given node (or nothing if null).
|
||||
* Removes highlighting on the previously highlighted node.
|
||||
@ -274,23 +268,63 @@ public class SourceEditorController extends AbstractController<MainDesignerContr
|
||||
|
||||
// editor is only restyled if the selection has changed
|
||||
Platform.runLater(() -> codeEditorArea.styleNodes(node == null ? emptyList() : singleton(node), StyleLayerIds.FOCUS, true));
|
||||
if (node instanceof ScopedNode) {
|
||||
// not null as well
|
||||
Platform.runLater(() -> highlightNameOccurrences(getNameOccurrences((ScopedNode) node)));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private List<NameOccurrence> getNameOccurrences(ScopedNode node) {
|
||||
|
||||
// For MethodNameDeclaration the scope is the method scope, which is not the scope it is declared
|
||||
// in but the scope it declares! That means that getDeclarations().get(declaration) returns null
|
||||
// and no name occurrences are found. We thus look in the parent, but ultimately the name occurrence
|
||||
// finder is broken since it can't find e.g. the use of a method in another scope. Plus in case of
|
||||
// overloads both overloads are reported to have a usage.
|
||||
|
||||
// Plus this is some serious law of Demeter breaking there...
|
||||
|
||||
Set<NameDeclaration> candidates = new HashSet<>(node.getScope().getDeclarations().keySet());
|
||||
|
||||
Optional.ofNullable(node.getScope().getParent())
|
||||
.map(Scope::getDeclarations)
|
||||
.map(Map::keySet)
|
||||
.ifPresent(candidates::addAll);
|
||||
|
||||
return candidates.stream()
|
||||
.filter(nd -> node.equals(nd.getNode()))
|
||||
.findFirst()
|
||||
.map(nd -> {
|
||||
// nd.getScope() != nd.getNode().getScope()?? wtf?
|
||||
|
||||
List<NameOccurrence> usages = nd.getNode().getScope().getDeclarations().get(nd);
|
||||
|
||||
if (usages == null) {
|
||||
usages = nd.getNode().getScope().getParent().getDeclarations().get(nd);
|
||||
}
|
||||
|
||||
return usages;
|
||||
})
|
||||
.orElse(Collections.emptyList());
|
||||
}
|
||||
|
||||
|
||||
/** Highlights xpath results (xpath highlight). */
|
||||
public void highlightXPathResults(Collection<? extends Node> nodes) {
|
||||
private void highlightXPathResults(Collection<? extends Node> nodes) {
|
||||
codeEditorArea.styleNodes(nodes, StyleLayerIds.XPATH_RESULT, true);
|
||||
}
|
||||
|
||||
|
||||
/** Highlights name occurrences (secondary highlight). */
|
||||
public void highlightNameOccurrences(Collection<? extends NameOccurrence> occs) {
|
||||
private void highlightNameOccurrences(Collection<? extends NameOccurrence> occs) {
|
||||
codeEditorArea.styleNodes(occs.stream().map(NameOccurrence::getLocation).collect(Collectors.toList()), StyleLayerIds.NAME_OCCURENCE, true);
|
||||
}
|
||||
|
||||
|
||||
/** Highlights nodes that are in error (secondary highlight). */
|
||||
public void highlightErrorNodes(Collection<? extends Node> nodes) {
|
||||
private void highlightErrorNodes(Collection<? extends Node> nodes) {
|
||||
codeEditorArea.styleNodes(nodes, StyleLayerIds.ERROR, true);
|
||||
if (!nodes.isEmpty()) {
|
||||
scrollEditorToNode(nodes.iterator().next());
|
||||
@ -298,6 +332,16 @@ public class SourceEditorController extends AbstractController<MainDesignerContr
|
||||
}
|
||||
|
||||
|
||||
public Var<List<Node>> currentRuleResultsProperty() {
|
||||
return currentRuleResults;
|
||||
}
|
||||
|
||||
|
||||
public Var<List<Node>> currentErrorNodesProperty() {
|
||||
return currentErrorNodes;
|
||||
}
|
||||
|
||||
|
||||
/** Scroll the editor to a node and makes it visible. */
|
||||
private void scrollEditorToNode(Node node) {
|
||||
|
||||
@ -318,13 +362,8 @@ public class SourceEditorController extends AbstractController<MainDesignerContr
|
||||
}
|
||||
|
||||
|
||||
public void clearStyleLayers() {
|
||||
codeEditorArea.clearStyleLayers();
|
||||
}
|
||||
|
||||
|
||||
/** Moves the caret to a position and makes the view follow it. */
|
||||
public void moveCaret(int line, int column) {
|
||||
private void moveCaret(int line, int column) {
|
||||
codeEditorArea.moveTo(line, column);
|
||||
codeEditorArea.requestFollowCaret();
|
||||
}
|
||||
|
@ -98,6 +98,8 @@ public class XPathPanelController extends AbstractController<MainDesignerControl
|
||||
@FXML
|
||||
private ListView<TextAwareNodeWrapper> xpathResultListView;
|
||||
|
||||
private final Var<List<Node>> currentResults = Var.newSimpleVar(Collections.emptyList());
|
||||
|
||||
// ui property
|
||||
private Var<String> xpathVersionUIProperty = Var.newSimpleVar(XPathRuleQuery.XPATH_2_0);
|
||||
|
||||
@ -261,7 +263,7 @@ public class XPathPanelController extends AbstractController<MainDesignerControl
|
||||
xpath,
|
||||
ruleBuilder.getRuleProperties()));
|
||||
xpathResultListView.setItems(results.stream().map(parent::wrapNode).collect(Collectors.toCollection(LiveArrayList::new)));
|
||||
parent.highlightXPathResults(results);
|
||||
this.currentResults.setValue(results);
|
||||
violationsTitledPane.setTitle("Matched nodes (" + results.size() + ")");
|
||||
// Notify that everything went OK so we can avoid logging very recent exceptions
|
||||
raiseParsableXPathFlag();
|
||||
@ -280,7 +282,7 @@ public class XPathPanelController extends AbstractController<MainDesignerControl
|
||||
|
||||
public void invalidateResults(boolean error) {
|
||||
xpathResultListView.getItems().clear();
|
||||
parent.resetXPathResults();
|
||||
this.currentResults.setValue(Collections.emptyList());
|
||||
violationsTitledPane.setTitle("Matched nodes" + (error ? "\t(error)" : ""));
|
||||
}
|
||||
|
||||
@ -309,6 +311,9 @@ public class XPathPanelController extends AbstractController<MainDesignerControl
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
public Val<List<Node>> currentResultsProperty() {
|
||||
return currentResults;
|
||||
}
|
||||
|
||||
public String getXpathExpression() {
|
||||
return xpathExpressionArea.getText();
|
||||
|
@ -4,16 +4,16 @@
|
||||
|
||||
package net.sourceforge.pmd.util.fxdesigner.popups;
|
||||
|
||||
import static org.reactfx.EventStreams.valuesOf;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.Duration;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
import org.reactfx.EventStreams;
|
||||
import org.reactfx.Subscription;
|
||||
import org.reactfx.value.Val;
|
||||
import org.reactfx.value.Var;
|
||||
@ -175,25 +175,12 @@ public final class EventLogController extends AbstractController<MainDesignerCon
|
||||
private Subscription bindPopupToThisController() {
|
||||
|
||||
Subscription binding =
|
||||
EventStreams.valuesOf(eventLogTableView.getSelectionModel().selectedItemProperty())
|
||||
.distinct()
|
||||
.subscribe(this::onExceptionSelectionChanges);
|
||||
valuesOf(eventLogTableView.getSelectionModel().selectedItemProperty())
|
||||
.distinct()
|
||||
.subscribe(this::onExceptionSelectionChanges);
|
||||
|
||||
binding = binding.and(
|
||||
EventStreams.valuesOf(eventLogTableView.focusedProperty())
|
||||
.successionEnds(Duration.ofMillis(100))
|
||||
.subscribe(b -> {
|
||||
if (b) {
|
||||
parent.handleSelectedNodeInError(selectedErrorNodes.getValue());
|
||||
} else {
|
||||
parent.resetSelectedErrorNodes();
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
binding = binding.and(
|
||||
selectedErrorNodes.values().subscribe(parent::handleSelectedNodeInError)
|
||||
);
|
||||
// reset error nodes on closing
|
||||
binding = binding.and(() -> selectedErrorNodes.setValue(Collections.emptyList()));
|
||||
|
||||
SortedList<LogEntry> logEntries = new SortedList<>(getLogger().getLog(), Comparator.reverseOrder());
|
||||
eventLogTableView.itemsProperty().setValue(logEntries);
|
||||
@ -211,8 +198,9 @@ public final class EventLogController extends AbstractController<MainDesignerCon
|
||||
|
||||
|
||||
private void handleSelectedEntry(LogEntry entry) {
|
||||
selectedErrorNodes.setValue(Collections.emptyList());
|
||||
|
||||
if (entry == null) {
|
||||
selectedErrorNodes.setValue(Collections.emptyList());
|
||||
return;
|
||||
}
|
||||
|
||||
@ -224,14 +212,15 @@ public final class EventLogController extends AbstractController<MainDesignerCon
|
||||
}
|
||||
|
||||
|
||||
public void showPopup() {
|
||||
public void showPopup(Subscription extSub) {
|
||||
myPopupStage.show();
|
||||
popupBinding = bindPopupToThisController();
|
||||
popupBinding = bindPopupToThisController().and(extSub);
|
||||
eventLogTableView.refresh();
|
||||
myPopupStage.setOnCloseRequest(e -> hidePopup());
|
||||
}
|
||||
|
||||
|
||||
public void hidePopup() {
|
||||
private void hidePopup() {
|
||||
myPopupStage.hide();
|
||||
popupBinding.unsubscribe();
|
||||
popupBinding = () -> {};
|
||||
@ -244,6 +233,10 @@ public final class EventLogController extends AbstractController<MainDesignerCon
|
||||
}
|
||||
|
||||
|
||||
public Val<List<Node>> errorNodesProperty() {
|
||||
return selectedErrorNodes;
|
||||
}
|
||||
|
||||
private Val<String> titleProperty() {
|
||||
return parent.getLogger().numNewLogEntriesProperty().map(i -> "Event log (" + (i > 0 ? i : "no") + " new)");
|
||||
}
|
||||
|
Reference in New Issue
Block a user