breaking out these static classes to be on their own

This commit is contained in:
Ben Fry
2018-03-26 11:51:39 -04:00
parent 77b23d797e
commit fcc6550960
4 changed files with 838 additions and 761 deletions

View File

@@ -0,0 +1,200 @@
package processing.mode.java.pdex;
import java.awt.EventQueue;
import java.awt.event.InputEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.util.function.Predicate;
import javax.swing.JMenuItem;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclaration;
import processing.app.Language;
import processing.app.Messages;
import processing.app.Platform;
import processing.mode.java.JavaEditor;
import processing.mode.java.JavaMode;
import processing.mode.java.pdex.PreprocessedSketch.SketchInterval;
import static processing.mode.java.pdex.ASTUtils.getSimpleNameAt;
import static processing.mode.java.pdex.ASTUtils.resolveBinding;
class InspectMode {
final JavaEditor editor;
final PreprocessingService pps;
final ShowUsage usage;
boolean inspectModeEnabled;
boolean isMouse1Down;
boolean isMouse2Down;
boolean isHotkeyDown;
Predicate<MouseEvent> mouseEventHotkeyTest = Platform.isMacOS() ?
InputEvent::isMetaDown : InputEvent::isControlDown;
Predicate<KeyEvent> keyEventHotkeyTest = Platform.isMacOS() ?
e -> e.getKeyCode() == KeyEvent.VK_META :
e -> e.getKeyCode() == KeyEvent.VK_CONTROL;
InspectMode(JavaEditor editor, PreprocessingService pps, ShowUsage usage) {
this.editor = editor;
this.pps = pps;
this.usage = usage;
// Add listeners
JMenuItem showUsageItem = new JMenuItem(Language.text("editor.popup.jump_to_declaration"));
showUsageItem.addActionListener(e -> handleInspect());
editor.getTextArea().getRightClickPopup().add(showUsageItem);
editor.getJavaTextArea().getPainter().addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
isMouse1Down = isMouse1Down || (e.getButton() == MouseEvent.BUTTON1);
isMouse2Down = isMouse2Down || (e.getButton() == MouseEvent.BUTTON2);
}
@Override
public void mouseReleased(MouseEvent e) {
boolean releasingMouse1 = e.getButton() == MouseEvent.BUTTON1;
boolean releasingMouse2 = e.getButton() == MouseEvent.BUTTON2;
if (JavaMode.inspectModeHotkeyEnabled && inspectModeEnabled &&
isMouse1Down && releasingMouse1) {
handleInspect(e);
} else if (!inspectModeEnabled && isMouse2Down && releasingMouse2) {
handleInspect(e);
}
isMouse1Down = isMouse1Down && !releasingMouse1;
isMouse2Down = isMouse2Down && !releasingMouse2;
}
});
editor.getJavaTextArea().getPainter().addMouseMotionListener(new MouseAdapter() {
@Override
public void mouseDragged(MouseEvent e) {
if (editor.isSelectionActive()) {
// Mouse was dragged too much, disable
inspectModeEnabled = false;
// Cancel possible mouse 2 press
isMouse2Down = false;
}
}
@Override
public void mouseMoved(MouseEvent e) {
isMouse1Down = false;
isMouse2Down = false;
isHotkeyDown = mouseEventHotkeyTest.test(e);
inspectModeEnabled = isHotkeyDown;
}
});
editor.getJavaTextArea().addMouseWheelListener(new MouseAdapter() {
@Override
public void mouseWheelMoved(MouseWheelEvent e) {
// Editor was scrolled while mouse 1 was pressed, disable
if (isMouse1Down) inspectModeEnabled = false;
}
});
editor.getJavaTextArea().addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
isHotkeyDown = isHotkeyDown || keyEventHotkeyTest.test(e);
// Enable if hotkey was just pressed and mouse 1 is not down
inspectModeEnabled = inspectModeEnabled || (!isMouse1Down && isHotkeyDown);
}
@Override
public void keyReleased(KeyEvent e) {
isHotkeyDown = isHotkeyDown && !keyEventHotkeyTest.test(e);
// Disable if hotkey was just released
inspectModeEnabled = inspectModeEnabled && isHotkeyDown;
}
});
}
void handleInspect() {
int off = editor.getSelectionStart();
int tabIndex = editor.getSketch().getCurrentCodeIndex();
pps.whenDoneBlocking(ps -> handleInspect(ps, tabIndex, off));
}
// Thread: EDT
void handleInspect(MouseEvent evt) {
int off = editor.getJavaTextArea().xyToOffset(evt.getX(), evt.getY());
if (off < 0) return;
int tabIndex = editor.getSketch().getCurrentCodeIndex();
pps.whenDoneBlocking(ps -> handleInspect(ps, tabIndex, off));
}
// Thread: worker
private void handleInspect(PreprocessedSketch ps, int tabIndex, int offset) {
ASTNode root = ps.compilationUnit;
int javaOffset = ps.tabOffsetToJavaOffset(tabIndex, offset);
SimpleName simpleName = getSimpleNameAt(root, javaOffset, javaOffset);
if (simpleName == null) {
Messages.log("no simple name found at click location");
return;
}
IBinding binding = resolveBinding(simpleName);
if (binding == null) {
Messages.log("binding not resolved");
return;
}
String key = binding.getKey();
ASTNode decl = ps.compilationUnit.findDeclaringNode(key);
if (decl == null) {
Messages.log("decl not found, showing usage instead");
usage.findUsageAndUpdateTree(ps, binding);
return;
}
SimpleName declName = null;
switch (binding.getKind()) {
case IBinding.TYPE: declName = ((TypeDeclaration) decl).getName(); break;
case IBinding.METHOD: declName = ((MethodDeclaration) decl).getName(); break;
case IBinding.VARIABLE: declName = ((VariableDeclaration) decl).getName(); break;
}
if (declName == null) {
Messages.log("decl name not found " + decl);
return;
}
if (declName.equals(simpleName)) {
usage.findUsageAndUpdateTree(ps, binding);
} else {
Messages.log("found declaration, offset " + decl.getStartPosition() + ", name: " + declName);
SketchInterval si = ps.mapJavaToSketch(declName);
if (!ps.inRange(si)) return;
EventQueue.invokeLater(() -> {
editor.highlight(si.tabIndex, si.startTabOffset, si.stopTabOffset);
});
}
}
void dispose() {
// Nothing to do
}
}

View File

@@ -8,33 +8,15 @@ import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclaration;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Rectangle;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Deque;
import java.util.HashMap;
import java.util.List;
@@ -46,48 +28,29 @@ import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.JTree;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeModel;
import processing.app.Language;
import processing.app.Messages;
import processing.app.Platform;
import processing.app.Problem;
import processing.app.Sketch;
import processing.app.SketchCode;
import processing.app.syntax.SyntaxDocument;
import processing.app.ui.EditorStatus;
import processing.app.ui.Toolkit;
import processing.app.ui.ZoomTreeCellRenderer;
import processing.mode.java.JavaEditor;
import processing.mode.java.JavaMode;
import processing.mode.java.pdex.PreprocessedSketch.SketchInterval;
import static processing.mode.java.pdex.ASTUtils.*;
public class PDEX {
@@ -112,8 +75,8 @@ public class PDEX {
errorChecker = new ErrorChecker(editor, pps);
inspectMode = new InspectMode(editor, pps);
showUsage = new ShowUsage(editor, pps);
inspectMode = new InspectMode(editor, pps, showUsage);
rename = new Rename(editor, pps, showUsage);
if (SHOW_DEBUG_TREE) {
debugTree = new DebugTree(editor, pps);
@@ -187,729 +150,6 @@ public class PDEX {
}
private class InspectMode {
boolean inspectModeEnabled;
boolean isMouse1Down;
boolean isMouse2Down;
boolean isHotkeyDown;
Predicate<MouseEvent> mouseEventHotkeyTest = Platform.isMacOS() ?
InputEvent::isMetaDown : InputEvent::isControlDown;
Predicate<KeyEvent> keyEventHotkeyTest = Platform.isMacOS() ?
e -> e.getKeyCode() == KeyEvent.VK_META :
e -> e.getKeyCode() == KeyEvent.VK_CONTROL;
JavaEditor editor;
PreprocessingService pps;
InspectMode(JavaEditor editor, PreprocessingService pps) {
this.editor = editor;
this.pps = pps;
// Add listeners
JMenuItem showUsageItem = new JMenuItem(Language.text("editor.popup.jump_to_declaration"));
showUsageItem.addActionListener(e -> handleInspect());
editor.getTextArea().getRightClickPopup().add(showUsageItem);
editor.getJavaTextArea().getPainter().addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
isMouse1Down = isMouse1Down || (e.getButton() == MouseEvent.BUTTON1);
isMouse2Down = isMouse2Down || (e.getButton() == MouseEvent.BUTTON2);
}
@Override
public void mouseReleased(MouseEvent e) {
boolean releasingMouse1 = e.getButton() == MouseEvent.BUTTON1;
boolean releasingMouse2 = e.getButton() == MouseEvent.BUTTON2;
if (JavaMode.inspectModeHotkeyEnabled && inspectModeEnabled &&
isMouse1Down && releasingMouse1) {
handleInspect(e);
} else if (!inspectModeEnabled && isMouse2Down && releasingMouse2) {
handleInspect(e);
}
isMouse1Down = isMouse1Down && !releasingMouse1;
isMouse2Down = isMouse2Down && !releasingMouse2;
}
});
editor.getJavaTextArea().getPainter().addMouseMotionListener(new MouseAdapter() {
@Override
public void mouseDragged(MouseEvent e) {
if (editor.isSelectionActive()) {
// Mouse was dragged too much, disable
inspectModeEnabled = false;
// Cancel possible mouse 2 press
isMouse2Down = false;
}
}
@Override
public void mouseMoved(MouseEvent e) {
isMouse1Down = false;
isMouse2Down = false;
isHotkeyDown = mouseEventHotkeyTest.test(e);
inspectModeEnabled = isHotkeyDown;
}
});
editor.getJavaTextArea().addMouseWheelListener(new MouseAdapter() {
@Override
public void mouseWheelMoved(MouseWheelEvent e) {
// Editor was scrolled while mouse 1 was pressed, disable
if (isMouse1Down) inspectModeEnabled = false;
}
});
editor.getJavaTextArea().addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
isHotkeyDown = isHotkeyDown || keyEventHotkeyTest.test(e);
// Enable if hotkey was just pressed and mouse 1 is not down
inspectModeEnabled = inspectModeEnabled || (!isMouse1Down && isHotkeyDown);
}
@Override
public void keyReleased(KeyEvent e) {
isHotkeyDown = isHotkeyDown && !keyEventHotkeyTest.test(e);
// Disable if hotkey was just released
inspectModeEnabled = inspectModeEnabled && isHotkeyDown;
}
});
}
void handleInspect() {
int off = editor.getSelectionStart();
int tabIndex = editor.getSketch().getCurrentCodeIndex();
pps.whenDoneBlocking(ps -> handleInspect(ps, tabIndex, off));
}
// Thread: EDT
void handleInspect(MouseEvent evt) {
int off = editor.getJavaTextArea().xyToOffset(evt.getX(), evt.getY());
if (off < 0) return;
int tabIndex = editor.getSketch().getCurrentCodeIndex();
pps.whenDoneBlocking(ps -> handleInspect(ps, tabIndex, off));
}
// Thread: worker
private void handleInspect(PreprocessedSketch ps, int tabIndex, int offset) {
ASTNode root = ps.compilationUnit;
int javaOffset = ps.tabOffsetToJavaOffset(tabIndex, offset);
SimpleName simpleName = getSimpleNameAt(root, javaOffset, javaOffset);
if (simpleName == null) {
Messages.log("no simple name found at click location");
return;
}
IBinding binding = resolveBinding(simpleName);
if (binding == null) {
Messages.log("binding not resolved");
return;
}
String key = binding.getKey();
ASTNode decl = ps.compilationUnit.findDeclaringNode(key);
if (decl == null) {
Messages.log("decl not found, showing usage instead");
showUsage.findUsageAndUpdateTree(ps, binding);
return;
}
SimpleName declName = null;
switch (binding.getKind()) {
case IBinding.TYPE: declName = ((TypeDeclaration) decl).getName(); break;
case IBinding.METHOD: declName = ((MethodDeclaration) decl).getName(); break;
case IBinding.VARIABLE: declName = ((VariableDeclaration) decl).getName(); break;
}
if (declName == null) {
Messages.log("decl name not found " + decl);
return;
}
if (declName.equals(simpleName)) {
showUsage.findUsageAndUpdateTree(ps, binding);
} else {
Messages.log("found declaration, offset " + decl.getStartPosition() + ", name: " + declName);
SketchInterval si = ps.mapJavaToSketch(declName);
if (!ps.inRange(si)) return;
EventQueue.invokeLater(() -> {
editor.highlight(si.tabIndex, si.startTabOffset, si.stopTabOffset);
});
}
}
void dispose() {
// Nothing to do
}
}
static private class ShowUsage {
final JDialog window;
final JTree tree;
final JavaEditor editor;
final PreprocessingService pps;
final Consumer<PreprocessedSketch> reloadListener;
IBinding binding;
ShowUsage(JavaEditor editor, PreprocessingService pps) {
this.editor = editor;
this.pps = pps;
// Add show usage option
JMenuItem showUsageItem =
new JMenuItem(Language.text("editor.popup.show_usage"));
showUsageItem.addActionListener(e -> handleShowUsage());
editor.getTextArea().getRightClickPopup().add(showUsageItem);
reloadListener = this::reloadShowUsage;
{ // Show Usage window
window = new JDialog(editor);
window.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
window.setAutoRequestFocus(false);
window.addComponentListener(new ComponentAdapter() {
@Override
public void componentHidden(ComponentEvent e) {
binding = null;
tree.setModel(null);
pps.unregisterListener(reloadListener);
}
@Override
public void componentShown(ComponentEvent e) {
pps.registerListener(reloadListener);
}
});
window.setSize(Toolkit.zoom(300, 400));
window.setFocusableWindowState(false);
Toolkit.setIcon(window);
JScrollPane sp2 = new JScrollPane();
tree = new JTree();
ZoomTreeCellRenderer renderer =
new ZoomTreeCellRenderer(editor.getMode());
tree.setCellRenderer(renderer);
renderer.setLeafIcon(null);
renderer.setClosedIcon(null);
renderer.setOpenIcon(null);
renderer.setBackgroundSelectionColor(new Color(228, 248, 246));
renderer.setBorderSelectionColor(new Color(0, 0, 0, 0));
renderer.setTextSelectionColor(Color.BLACK);
sp2.setViewportView(tree);
window.add(sp2);
}
tree.addTreeSelectionListener(e -> {
if (tree.getLastSelectedPathComponent() != null) {
DefaultMutableTreeNode tnode =
(DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
if (tnode.getUserObject() instanceof ShowUsageTreeNode) {
ShowUsageTreeNode node = (ShowUsageTreeNode) tnode.getUserObject();
editor.highlight(node.tabIndex, node.startTabOffset, node.stopTabOffset);
}
}
});
}
// Thread: EDT
void handleShowUsage() {
int startOffset = editor.getSelectionStart();
int stopOffset = editor.getSelectionStop();
int tabIndex = editor.getSketch().getCurrentCodeIndex();
pps.whenDoneBlocking(ps -> handleShowUsage(ps, tabIndex, startOffset, stopOffset));
}
// Thread: worker
void handleShowUsage(PreprocessedSketch ps, int tabIndex,
int startTabOffset, int stopTabOffset) {
// Map offsets
int startJavaOffset = ps.tabOffsetToJavaOffset(tabIndex, startTabOffset);
int stopJavaOffset = ps.tabOffsetToJavaOffset(tabIndex, stopTabOffset);
// Find the node
SimpleName name = ASTUtils.getSimpleNameAt(ps.compilationUnit, startJavaOffset, stopJavaOffset);
if (name == null) {
editor.statusMessage("Cannot find any name under cursor", EditorStatus.NOTICE);
return;
}
// Find binding
IBinding binding = ASTUtils.resolveBinding(name);
if (binding == null) {
editor.statusMessage("Cannot find usages, try to fix errors in your code first",
EditorStatus.NOTICE);
return;
}
findUsageAndUpdateTree(ps, binding);
}
// Thread: worker
void findUsageAndUpdateTree(PreprocessedSketch ps, IBinding binding) {
this.binding = binding;
// Get label
String bindingType = "";
switch (binding.getKind()) {
case IBinding.METHOD:
IMethodBinding method = (IMethodBinding) binding;
if (method.isConstructor()) bindingType = "Constructor";
else bindingType = "Method";
break;
case IBinding.TYPE:
bindingType = "Type";
break;
case IBinding.VARIABLE:
IVariableBinding variable = (IVariableBinding) binding;
if (variable.isField()) bindingType = "Field";
else if (variable.isParameter()) bindingType = "Parameter";
else if (variable.isEnumConstant()) bindingType = "Enum constant";
else bindingType = "Local variable";
break;
}
// Find usages, map to tree nodes, add to root node
String bindingKey = binding.getKey();
List<SketchInterval> intervals =
findAllOccurrences(ps.compilationUnit, bindingKey).stream()
.map(ps::mapJavaToSketch)
// remove occurrences which fall into generated header
.filter(ps::inRange)
// remove empty intervals (happens when occurence was inserted)
.filter(in -> in.startPdeOffset < in.stopPdeOffset)
.collect(Collectors.toList());
int usageCount = intervals.size();
// Get element name from PDE code if possible, otherwise use one from Java
String elementName = intervals.stream()
.findAny()
.map(si -> ps.pdeCode.substring(si.startPdeOffset, si.stopPdeOffset))
.orElseGet(binding::getName);
// Create root node
DefaultMutableTreeNode rootNode =
new DefaultMutableTreeNode(bindingType + ": " + elementName);
intervals.stream()
// Convert to TreeNodes
.map(in -> ShowUsageTreeNode.fromSketchInterval(ps, in))
// Group by tab index
.collect(Collectors.groupingBy(node -> node.tabIndex))
// Stream Map Entries of (tab index) <-> (List<ShowUsageTreeNode>)
.entrySet().stream()
// Sort by tab index
.sorted(Comparator.comparing(Map.Entry::getKey))
.map(entry -> {
Integer tabIndex = entry.getKey();
List<ShowUsageTreeNode> nodes = entry.getValue();
int count = nodes.size();
String usageLabel = count == 1 ? "usage" : "usages";
// Create new DefaultMutableTreeNode for this tab
String tabLabel = "<html><font color=#222222>" +
ps.sketch.getCode(tabIndex).getPrettyName() +
"</font> <font color=#999999>" + count + " " + usageLabel + "</font></html>";
DefaultMutableTreeNode tabNode = new DefaultMutableTreeNode(tabLabel);
// Stream nodes belonging to this tab
nodes.stream()
// Convert TreeNodes to DefaultMutableTreeNodes
.map(DefaultMutableTreeNode::new)
// Add all as children of tab node
.forEach(tabNode::add);
return tabNode;
})
// Add all tab nodes as children of root node
.forEach(rootNode::add);
TreeModel treeModel = new DefaultTreeModel(rootNode);
// Update tree
EventQueue.invokeLater(() -> {
tree.setModel(treeModel);
// Expand all nodes
for (int i = 0; i < tree.getRowCount(); i++) {
tree.expandRow(i);
}
tree.setRootVisible(true);
if (!window.isVisible()) {
window.setVisible(true);
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice defaultScreen = ge.getDefaultScreenDevice();
Rectangle rect = defaultScreen.getDefaultConfiguration().getBounds();
int maxX = (int) rect.getMaxX() - window.getWidth();
int x = Math.min(editor.getX() + editor.getWidth(), maxX);
int y = (x == maxX) ? 10 : editor.getY();
window.setLocation(x, y);
}
window.toFront();
window.setTitle("Usage of \"" + elementName + "\" : " +
usageCount + " time(s)");
});
}
// Thread: worker
void reloadShowUsage(PreprocessedSketch ps) {
if (binding != null) {
findUsageAndUpdateTree(ps, binding);
}
}
void hide() {
window.setVisible(false);
}
void dispose() {
if (window != null) {
window.dispose();
}
}
}
static private class ShowUsageTreeNode {
final int tabIndex;
final int startTabOffset;
final int stopTabOffset;
final String text;
ShowUsageTreeNode(int tabIndex, int startTabOffset, int stopTabOffset, String text) {
this.tabIndex = tabIndex;
this.startTabOffset = startTabOffset;
this.stopTabOffset = stopTabOffset;
this.text = text;
}
static ShowUsageTreeNode fromSketchInterval(PreprocessedSketch ps, SketchInterval in) {
int lineStartPdeOffset = ps.pdeCode.lastIndexOf('\n', in.startPdeOffset) + 1;
int lineStopPdeOffset = ps.pdeCode.indexOf('\n', in.stopPdeOffset);
if (lineStopPdeOffset == -1) lineStopPdeOffset = ps.pdeCode.length();
int highlightStartOffset = in.startPdeOffset - lineStartPdeOffset;
int highlightStopOffset = in.stopPdeOffset - lineStartPdeOffset;
int tabLine = ps.tabOffsetToTabLine(in.tabIndex, in.startTabOffset);
// TODO: what a mess
String line = ps.pdeCode.substring(lineStartPdeOffset, lineStopPdeOffset);
String pre = line.substring(0, highlightStartOffset)
.replace("&", "&amp;").replace(">", "&gt;").replace("<", "&lt;");
String highlight = line.substring(highlightStartOffset, highlightStopOffset)
.replace("&", "&amp;").replace(">", "&gt;").replace("<", "&lt;");
String post = line.substring(highlightStopOffset)
.replace("&", "&amp;").replace(">", "&gt;").replace("<", "&lt;");
line = pre + "<font color=#222222><b>" + highlight + "</b></font>" + post;
line = line.trim();
String text = "<html><font color=#bbbbbb>" +
(tabLine + 1) + "</font> <font color=#777777>" + line + "</font></html>";
return new ShowUsageTreeNode(in.tabIndex, in.startTabOffset, in.stopTabOffset, text);
}
@Override
public String toString() {
return text;
}
}
static private class Rename {
final JavaEditor editor;
final PreprocessingService pps;
final ShowUsage showUsage;
final JDialog window;
final JTextField textField;
final JLabel oldNameLabel;
IBinding binding;
PreprocessedSketch ps;
Rename(JavaEditor editor, PreprocessingService pps, ShowUsage showUsage) {
this.editor = editor;
this.pps = pps;
this.showUsage = showUsage;
// Add rename option
JMenuItem renameItem = new JMenuItem(Language.text("editor.popup.rename"));
renameItem.addActionListener(e -> handleRename());
editor.getTextArea().getRightClickPopup().add(renameItem);
window = new JDialog(editor);
window.setTitle("Enter new name:");
window.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
window.setModal(true);
window.setResizable(false);
window.addComponentListener(new ComponentAdapter() {
@Override
public void componentHidden(ComponentEvent e) {
binding = null;
ps = null;
}
});
window.setSize(Toolkit.zoom(250, 130));
window.setLayout(new BoxLayout(window.getContentPane(), BoxLayout.Y_AXIS));
Toolkit.setIcon(window);
final int b = Toolkit.zoom(5);
{ // Top panel
// Text field
textField = new JTextField();
textField.setPreferredSize(Toolkit.zoom(150, 60));
// Old name label
oldNameLabel = new JLabel();
oldNameLabel.setText("Current Name: ");
// Top panel
JPanel panelTop = new JPanel();
panelTop.setLayout(new BoxLayout(panelTop, BoxLayout.Y_AXIS));
panelTop.setBorder(BorderFactory.createEmptyBorder(b, b, b, b));
panelTop.add(textField);
panelTop.add(Box.createRigidArea(Toolkit.zoom(0, 10)));
panelTop.add(oldNameLabel);
window.add(panelTop);
}
{ // Bottom panel
JButton showUsageButton = new JButton("Show Usage");
showUsageButton.addActionListener(e -> {
showUsage.findUsageAndUpdateTree(ps, binding);
window.setVisible(false);
});
JButton renameButton = new JButton("Rename");
renameButton.addActionListener(e -> {
final String newName = textField.getText().trim();
if (!newName.isEmpty()) {
if (newName.length() >= 1 &&
newName.chars().limit(1).allMatch(Character::isUnicodeIdentifierStart) &&
newName.chars().skip(1).allMatch(Character::isUnicodeIdentifierPart)) {
rename(ps, binding, newName);
window.setVisible(false);
} else {
String msg = String.format("'%s' is not a valid name", newName);
JOptionPane.showMessageDialog(editor, msg, "Naming is Hard",
JOptionPane.PLAIN_MESSAGE);
}
}
});
JPanel panelBottom = new JPanel();
panelBottom.setLayout(new BoxLayout(panelBottom, BoxLayout.X_AXIS));
panelBottom.setBorder(BorderFactory.createEmptyBorder(b, b, b, b));
panelBottom.add(Box.createHorizontalGlue());
panelBottom.add(showUsageButton);
panelBottom.add(Box.createRigidArea(Toolkit.zoom(15, 0)));
panelBottom.add(renameButton);
window.add(panelBottom);
}
window.setMinimumSize(window.getSize());
}
// Thread: EDT
void handleRename() {
int startOffset = editor.getSelectionStart();
int stopOffset = editor.getSelectionStop();
int tabIndex = editor.getSketch().getCurrentCodeIndex();
pps.whenDoneBlocking(ps -> handleRename(ps, tabIndex, startOffset, stopOffset));
}
// Thread: worker
void handleRename(PreprocessedSketch ps, int tabIndex, int startTabOffset, int stopTabOffset) {
if (ps.hasSyntaxErrors) {
editor.statusMessage("Can't perform action until syntax errors are fixed",
EditorStatus.WARNING);
return;
}
ASTNode root = ps.compilationUnit;
// Map offsets
int startJavaOffset = ps.tabOffsetToJavaOffset(tabIndex, startTabOffset);
int stopJavaOffset = ps.tabOffsetToJavaOffset(tabIndex, stopTabOffset);
// Find the node
SimpleName name = getSimpleNameAt(root, startJavaOffset, stopJavaOffset);
if (name == null) {
editor.statusMessage("Highlight the class/function/variable name first",
EditorStatus.NOTICE);
return;
}
// Find binding
IBinding binding = resolveBinding(name);
if (binding == null) {
editor.statusMessage(name.getIdentifier() + " isn't defined in this sketch, " +
"so it cannot be renamed", EditorStatus.ERROR);
return;
}
ASTNode decl = ps.compilationUnit.findDeclaringNode(binding.getKey());
if (decl == null) {
editor.statusMessage(name.getIdentifier() + " isn't defined in this sketch, " +
"so it cannot be renamed", EditorStatus.ERROR);
return;
}
// Display the rename dialog
EventQueue.invokeLater(() -> {
if (!window.isVisible()) {
this.ps = ps;
this.binding = binding;
oldNameLabel.setText("Current name: " + binding.getName());
textField.setText(binding.getName());
textField.requestFocus();
textField.selectAll();
int x = editor.getX() + (editor.getWidth() - window.getWidth()) / 2;
int y = editor.getY() + (editor.getHeight() - window.getHeight()) / 2;
window.setLocation(x, y);
window.setVisible(true);
window.toFront();
}
});
}
// Thread: EDT (we can't allow user to mess with sketch while renaming)
void rename(PreprocessedSketch ps, IBinding binding, String newName) {
CompilationUnit root = ps.compilationUnit;
// Renaming constructor should rename class
if (binding.getKind() == IBinding.METHOD) {
IMethodBinding method = (IMethodBinding) binding;
if (method.isConstructor()) {
binding = method.getDeclaringClass();
}
}
ASTNode decl = root.findDeclaringNode(binding.getKey());
if (decl == null) return;
showUsage.hide();
List<SimpleName> occurrences = new ArrayList<>();
occurrences.addAll(findAllOccurrences(root, binding.getKey()));
// Renaming class should rename all constructors
if (binding.getKind() == IBinding.TYPE) {
ITypeBinding type = (ITypeBinding) binding;
//type = type.getErasure();
IMethodBinding[] methods = type.getDeclaredMethods();
Arrays.stream(methods)
.filter(IMethodBinding::isConstructor)
.flatMap(c -> findAllOccurrences(root, c.getKey()).stream())
.forEach(occurrences::add);
}
Map<Integer, List<SketchInterval>> mappedNodes = occurrences.stream()
.map(ps::mapJavaToSketch)
.filter(ps::inRange)
.collect(Collectors.groupingBy(interval -> interval.tabIndex));
Sketch sketch = ps.sketch;
editor.startCompoundEdit();
mappedNodes.entrySet().forEach(entry -> {
int tabIndex = entry.getKey();
SketchCode sketchCode = sketch.getCode(tabIndex);
SyntaxDocument document = (SyntaxDocument) sketchCode.getDocument();
List<SketchInterval> nodes = entry.getValue();
nodes.stream()
// Replace from the end so all unprocess offsets stay valid
.sorted(Comparator.comparing((SketchInterval si) -> si.startTabOffset).reversed())
.forEach(si -> {
// Make sure offsets are in bounds
int documentLength = document.getLength();
if (si.startTabOffset >= 0 && si.startTabOffset <= documentLength &&
si.stopTabOffset >= 0 && si.stopTabOffset <= documentLength) {
// Replace the code
int length = si.stopTabOffset - si.startTabOffset;
try {
document.remove(si.startTabOffset, length);
document.insertString(si.startTabOffset, newName, null);
} catch (BadLocationException e) { /* Whatever */ }
}
});
try {
sketchCode.setProgram(document.getText(0, document.getLength()));
} catch (BadLocationException e) { /* Whatever */ }
sketchCode.setModified(true);
});
editor.stopCompoundEdit();
editor.repaintHeader();
int currentTabIndex = sketch.getCurrentCodeIndex();
final int currentOffset = editor.getCaretOffset();
int precedingIntervals =
(int) mappedNodes.getOrDefault(currentTabIndex, Collections.emptyList())
.stream()
.filter(interval -> interval.stopTabOffset < currentOffset)
.count();
int intervalLengthDiff = newName.length() - binding.getName().length();
int offsetDiff = precedingIntervals * intervalLengthDiff;
editor.getTextArea().setCaretPosition(currentOffset + offsetDiff);
}
void dispose() {
if (window != null) {
window.dispose();
}
}
}
static private class DebugTree {
final JDialog window;
final JTree tree;

View File

@@ -0,0 +1,308 @@
package processing.mode.java.pdex;
import java.awt.EventQueue;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.text.BadLocationException;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.SimpleName;
import processing.app.Language;
import processing.app.Sketch;
import processing.app.SketchCode;
import processing.app.syntax.SyntaxDocument;
import processing.app.ui.EditorStatus;
import processing.app.ui.Toolkit;
import processing.mode.java.JavaEditor;
import processing.mode.java.pdex.PreprocessedSketch.SketchInterval;
import static processing.mode.java.pdex.ASTUtils.findAllOccurrences;
import static processing.mode.java.pdex.ASTUtils.getSimpleNameAt;
import static processing.mode.java.pdex.ASTUtils.resolveBinding;
class Rename {
final JavaEditor editor;
final PreprocessingService pps;
final ShowUsage showUsage;
final JDialog window;
final JTextField textField;
final JLabel oldNameLabel;
IBinding binding;
PreprocessedSketch ps;
Rename(JavaEditor editor, PreprocessingService pps, ShowUsage showUsage) {
this.editor = editor;
this.pps = pps;
this.showUsage = showUsage;
// Add rename option
JMenuItem renameItem = new JMenuItem(Language.text("editor.popup.rename"));
renameItem.addActionListener(e -> handleRename());
editor.getTextArea().getRightClickPopup().add(renameItem);
window = new JDialog(editor);
window.setTitle("Enter new name:");
window.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
window.setModal(true);
window.setResizable(false);
window.addComponentListener(new ComponentAdapter() {
@Override
public void componentHidden(ComponentEvent e) {
binding = null;
ps = null;
}
});
window.setSize(Toolkit.zoom(250, 130));
window.setLayout(new BoxLayout(window.getContentPane(), BoxLayout.Y_AXIS));
Toolkit.setIcon(window);
final int b = Toolkit.zoom(5);
{ // Top panel
// Text field
textField = new JTextField();
textField.setPreferredSize(Toolkit.zoom(150, 60));
// Old name label
oldNameLabel = new JLabel();
oldNameLabel.setText("Current Name: ");
// Top panel
JPanel panelTop = new JPanel();
panelTop.setLayout(new BoxLayout(panelTop, BoxLayout.Y_AXIS));
panelTop.setBorder(BorderFactory.createEmptyBorder(b, b, b, b));
panelTop.add(textField);
panelTop.add(Box.createRigidArea(Toolkit.zoom(0, 10)));
panelTop.add(oldNameLabel);
window.add(panelTop);
}
{ // Bottom panel
JButton showUsageButton = new JButton("Show Usage");
showUsageButton.addActionListener(e -> {
showUsage.findUsageAndUpdateTree(ps, binding);
window.setVisible(false);
});
JButton renameButton = new JButton("Rename");
renameButton.addActionListener(e -> {
final String newName = textField.getText().trim();
if (!newName.isEmpty()) {
if (newName.length() >= 1 &&
newName.chars().limit(1).allMatch(Character::isUnicodeIdentifierStart) &&
newName.chars().skip(1).allMatch(Character::isUnicodeIdentifierPart)) {
rename(ps, binding, newName);
window.setVisible(false);
} else {
String msg = String.format("'%s' is not a valid name", newName);
JOptionPane.showMessageDialog(editor, msg, "Naming is Hard",
JOptionPane.PLAIN_MESSAGE);
}
}
});
JPanel panelBottom = new JPanel();
panelBottom.setLayout(new BoxLayout(panelBottom, BoxLayout.X_AXIS));
panelBottom.setBorder(BorderFactory.createEmptyBorder(b, b, b, b));
panelBottom.add(Box.createHorizontalGlue());
panelBottom.add(showUsageButton);
panelBottom.add(Box.createRigidArea(Toolkit.zoom(15, 0)));
panelBottom.add(renameButton);
window.add(panelBottom);
}
window.setMinimumSize(window.getSize());
}
// Thread: EDT
void handleRename() {
int startOffset = editor.getSelectionStart();
int stopOffset = editor.getSelectionStop();
int tabIndex = editor.getSketch().getCurrentCodeIndex();
pps.whenDoneBlocking(ps -> handleRename(ps, tabIndex, startOffset, stopOffset));
}
// Thread: worker
void handleRename(PreprocessedSketch ps, int tabIndex, int startTabOffset, int stopTabOffset) {
if (ps.hasSyntaxErrors) {
editor.statusMessage("Can't perform action until syntax errors are fixed",
EditorStatus.WARNING);
return;
}
ASTNode root = ps.compilationUnit;
// Map offsets
int startJavaOffset = ps.tabOffsetToJavaOffset(tabIndex, startTabOffset);
int stopJavaOffset = ps.tabOffsetToJavaOffset(tabIndex, stopTabOffset);
// Find the node
SimpleName name = getSimpleNameAt(root, startJavaOffset, stopJavaOffset);
if (name == null) {
editor.statusMessage("Highlight the class/function/variable name first",
EditorStatus.NOTICE);
return;
}
// Find binding
IBinding binding = resolveBinding(name);
if (binding == null) {
editor.statusMessage(name.getIdentifier() + " isn't defined in this sketch, " +
"so it cannot be renamed", EditorStatus.ERROR);
return;
}
ASTNode decl = ps.compilationUnit.findDeclaringNode(binding.getKey());
if (decl == null) {
editor.statusMessage(name.getIdentifier() + " isn't defined in this sketch, " +
"so it cannot be renamed", EditorStatus.ERROR);
return;
}
// Display the rename dialog
EventQueue.invokeLater(() -> {
if (!window.isVisible()) {
this.ps = ps;
this.binding = binding;
oldNameLabel.setText("Current name: " + binding.getName());
textField.setText(binding.getName());
textField.requestFocus();
textField.selectAll();
int x = editor.getX() + (editor.getWidth() - window.getWidth()) / 2;
int y = editor.getY() + (editor.getHeight() - window.getHeight()) / 2;
window.setLocation(x, y);
window.setVisible(true);
window.toFront();
}
});
}
// Thread: EDT (we can't allow user to mess with sketch while renaming)
void rename(PreprocessedSketch ps, IBinding binding, String newName) {
CompilationUnit root = ps.compilationUnit;
// Renaming constructor should rename class
if (binding.getKind() == IBinding.METHOD) {
IMethodBinding method = (IMethodBinding) binding;
if (method.isConstructor()) {
binding = method.getDeclaringClass();
}
}
ASTNode decl = root.findDeclaringNode(binding.getKey());
if (decl == null) return;
showUsage.hide();
List<SimpleName> occurrences = new ArrayList<>();
occurrences.addAll(findAllOccurrences(root, binding.getKey()));
// Renaming class should rename all constructors
if (binding.getKind() == IBinding.TYPE) {
ITypeBinding type = (ITypeBinding) binding;
//type = type.getErasure();
IMethodBinding[] methods = type.getDeclaredMethods();
Arrays.stream(methods)
.filter(IMethodBinding::isConstructor)
.flatMap(c -> findAllOccurrences(root, c.getKey()).stream())
.forEach(occurrences::add);
}
Map<Integer, List<SketchInterval>> mappedNodes = occurrences.stream()
.map(ps::mapJavaToSketch)
.filter(ps::inRange)
.collect(Collectors.groupingBy(interval -> interval.tabIndex));
Sketch sketch = ps.sketch;
editor.startCompoundEdit();
mappedNodes.entrySet().forEach(entry -> {
int tabIndex = entry.getKey();
SketchCode sketchCode = sketch.getCode(tabIndex);
SyntaxDocument document = (SyntaxDocument) sketchCode.getDocument();
List<SketchInterval> nodes = entry.getValue();
nodes.stream()
// Replace from the end so all unprocess offsets stay valid
.sorted(Comparator.comparing((SketchInterval si) -> si.startTabOffset).reversed())
.forEach(si -> {
// Make sure offsets are in bounds
int documentLength = document.getLength();
if (si.startTabOffset >= 0 && si.startTabOffset <= documentLength &&
si.stopTabOffset >= 0 && si.stopTabOffset <= documentLength) {
// Replace the code
int length = si.stopTabOffset - si.startTabOffset;
try {
document.remove(si.startTabOffset, length);
document.insertString(si.startTabOffset, newName, null);
} catch (BadLocationException e) { /* Whatever */ }
}
});
try {
sketchCode.setProgram(document.getText(0, document.getLength()));
} catch (BadLocationException e) { /* Whatever */ }
sketchCode.setModified(true);
});
editor.stopCompoundEdit();
editor.repaintHeader();
int currentTabIndex = sketch.getCurrentCodeIndex();
final int currentOffset = editor.getCaretOffset();
int precedingIntervals =
(int) mappedNodes.getOrDefault(currentTabIndex, Collections.emptyList())
.stream()
.filter(interval -> interval.stopTabOffset < currentOffset)
.count();
int intervalLengthDiff = newName.length() - binding.getName().length();
int offsetDiff = precedingIntervals * intervalLengthDiff;
editor.getTextArea().setCaretPosition(currentOffset + offsetDiff);
}
void dispose() {
if (window != null) {
window.dispose();
}
}
}

View File

@@ -0,0 +1,329 @@
package processing.mode.java.pdex;
import static processing.mode.java.pdex.ASTUtils.findAllOccurrences;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Rectangle;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeModel;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.SimpleName;
import processing.app.Language;
import processing.app.ui.EditorStatus;
import processing.app.ui.Toolkit;
import processing.app.ui.ZoomTreeCellRenderer;
import processing.mode.java.JavaEditor;
import processing.mode.java.pdex.PreprocessedSketch.SketchInterval;
class ShowUsage {
final JDialog window;
final JTree tree;
final JavaEditor editor;
final PreprocessingService pps;
final Consumer<PreprocessedSketch> reloadListener;
IBinding binding;
ShowUsage(JavaEditor editor, PreprocessingService pps) {
this.editor = editor;
this.pps = pps;
// Add show usage option
JMenuItem showUsageItem =
new JMenuItem(Language.text("editor.popup.show_usage"));
showUsageItem.addActionListener(e -> handleShowUsage());
editor.getTextArea().getRightClickPopup().add(showUsageItem);
reloadListener = this::reloadShowUsage;
{ // Show Usage window
window = new JDialog(editor);
window.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
window.setAutoRequestFocus(false);
window.addComponentListener(new ComponentAdapter() {
@Override
public void componentHidden(ComponentEvent e) {
binding = null;
tree.setModel(null);
pps.unregisterListener(reloadListener);
}
@Override
public void componentShown(ComponentEvent e) {
pps.registerListener(reloadListener);
}
});
window.setSize(Toolkit.zoom(300, 400));
window.setFocusableWindowState(false);
Toolkit.setIcon(window);
JScrollPane sp2 = new JScrollPane();
tree = new JTree();
ZoomTreeCellRenderer renderer =
new ZoomTreeCellRenderer(editor.getMode());
tree.setCellRenderer(renderer);
renderer.setLeafIcon(null);
renderer.setClosedIcon(null);
renderer.setOpenIcon(null);
renderer.setBackgroundSelectionColor(new Color(228, 248, 246));
renderer.setBorderSelectionColor(new Color(0, 0, 0, 0));
renderer.setTextSelectionColor(Color.BLACK);
sp2.setViewportView(tree);
window.add(sp2);
}
tree.addTreeSelectionListener(e -> {
if (tree.getLastSelectedPathComponent() != null) {
DefaultMutableTreeNode tnode =
(DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
if (tnode.getUserObject() instanceof ShowUsageTreeNode) {
ShowUsageTreeNode node = (ShowUsageTreeNode) tnode.getUserObject();
editor.highlight(node.tabIndex, node.startTabOffset, node.stopTabOffset);
}
}
});
}
// Thread: EDT
void handleShowUsage() {
int startOffset = editor.getSelectionStart();
int stopOffset = editor.getSelectionStop();
int tabIndex = editor.getSketch().getCurrentCodeIndex();
pps.whenDoneBlocking(ps -> handleShowUsage(ps, tabIndex, startOffset, stopOffset));
}
// Thread: worker
void handleShowUsage(PreprocessedSketch ps, int tabIndex,
int startTabOffset, int stopTabOffset) {
// Map offsets
int startJavaOffset = ps.tabOffsetToJavaOffset(tabIndex, startTabOffset);
int stopJavaOffset = ps.tabOffsetToJavaOffset(tabIndex, stopTabOffset);
// Find the node
SimpleName name = ASTUtils.getSimpleNameAt(ps.compilationUnit, startJavaOffset, stopJavaOffset);
if (name == null) {
editor.statusMessage("Cannot find any name under cursor", EditorStatus.NOTICE);
return;
}
// Find binding
IBinding binding = ASTUtils.resolveBinding(name);
if (binding == null) {
editor.statusMessage("Cannot find usages, try to fix errors in your code first",
EditorStatus.NOTICE);
return;
}
findUsageAndUpdateTree(ps, binding);
}
// Thread: worker
void findUsageAndUpdateTree(PreprocessedSketch ps, IBinding binding) {
this.binding = binding;
// Get label
String bindingType = "";
switch (binding.getKind()) {
case IBinding.METHOD:
IMethodBinding method = (IMethodBinding) binding;
if (method.isConstructor()) bindingType = "Constructor";
else bindingType = "Method";
break;
case IBinding.TYPE:
bindingType = "Type";
break;
case IBinding.VARIABLE:
IVariableBinding variable = (IVariableBinding) binding;
if (variable.isField()) bindingType = "Field";
else if (variable.isParameter()) bindingType = "Parameter";
else if (variable.isEnumConstant()) bindingType = "Enum constant";
else bindingType = "Local variable";
break;
}
// Find usages, map to tree nodes, add to root node
String bindingKey = binding.getKey();
List<SketchInterval> intervals =
findAllOccurrences(ps.compilationUnit, bindingKey).stream()
.map(ps::mapJavaToSketch)
// remove occurrences which fall into generated header
.filter(ps::inRange)
// remove empty intervals (happens when occurence was inserted)
.filter(in -> in.startPdeOffset < in.stopPdeOffset)
.collect(Collectors.toList());
int usageCount = intervals.size();
// Get element name from PDE code if possible, otherwise use one from Java
String elementName = intervals.stream()
.findAny()
.map(si -> ps.pdeCode.substring(si.startPdeOffset, si.stopPdeOffset))
.orElseGet(binding::getName);
// Create root node
DefaultMutableTreeNode rootNode =
new DefaultMutableTreeNode(bindingType + ": " + elementName);
intervals.stream()
// Convert to TreeNodes
.map(in -> ShowUsageTreeNode.fromSketchInterval(ps, in))
// Group by tab index
.collect(Collectors.groupingBy(node -> node.tabIndex))
// Stream Map Entries of (tab index) <-> (List<ShowUsageTreeNode>)
.entrySet().stream()
// Sort by tab index
.sorted(Comparator.comparing(Map.Entry::getKey))
.map(entry -> {
Integer tabIndex = entry.getKey();
List<ShowUsageTreeNode> nodes = entry.getValue();
int count = nodes.size();
String usageLabel = count == 1 ? "usage" : "usages";
// Create new DefaultMutableTreeNode for this tab
String tabLabel = "<html><font color=#222222>" +
ps.sketch.getCode(tabIndex).getPrettyName() +
"</font> <font color=#999999>" + count + " " + usageLabel + "</font></html>";
DefaultMutableTreeNode tabNode = new DefaultMutableTreeNode(tabLabel);
// Stream nodes belonging to this tab
nodes.stream()
// Convert TreeNodes to DefaultMutableTreeNodes
.map(DefaultMutableTreeNode::new)
// Add all as children of tab node
.forEach(tabNode::add);
return tabNode;
})
// Add all tab nodes as children of root node
.forEach(rootNode::add);
TreeModel treeModel = new DefaultTreeModel(rootNode);
// Update tree
EventQueue.invokeLater(() -> {
tree.setModel(treeModel);
// Expand all nodes
for (int i = 0; i < tree.getRowCount(); i++) {
tree.expandRow(i);
}
tree.setRootVisible(true);
if (!window.isVisible()) {
window.setVisible(true);
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice defaultScreen = ge.getDefaultScreenDevice();
Rectangle rect = defaultScreen.getDefaultConfiguration().getBounds();
int maxX = (int) rect.getMaxX() - window.getWidth();
int x = Math.min(editor.getX() + editor.getWidth(), maxX);
int y = (x == maxX) ? 10 : editor.getY();
window.setLocation(x, y);
}
window.toFront();
window.setTitle("Usage of \"" + elementName + "\" : " +
usageCount + " time(s)");
});
}
// Thread: worker
void reloadShowUsage(PreprocessedSketch ps) {
if (binding != null) {
findUsageAndUpdateTree(ps, binding);
}
}
void hide() {
window.setVisible(false);
}
void dispose() {
if (window != null) {
window.dispose();
}
}
}
class ShowUsageTreeNode {
final int tabIndex;
final int startTabOffset;
final int stopTabOffset;
final String text;
ShowUsageTreeNode(int tabIndex, int startTabOffset, int stopTabOffset, String text) {
this.tabIndex = tabIndex;
this.startTabOffset = startTabOffset;
this.stopTabOffset = stopTabOffset;
this.text = text;
}
static ShowUsageTreeNode fromSketchInterval(PreprocessedSketch ps, SketchInterval in) {
int lineStartPdeOffset = ps.pdeCode.lastIndexOf('\n', in.startPdeOffset) + 1;
int lineStopPdeOffset = ps.pdeCode.indexOf('\n', in.stopPdeOffset);
if (lineStopPdeOffset == -1) lineStopPdeOffset = ps.pdeCode.length();
int highlightStartOffset = in.startPdeOffset - lineStartPdeOffset;
int highlightStopOffset = in.stopPdeOffset - lineStartPdeOffset;
int tabLine = ps.tabOffsetToTabLine(in.tabIndex, in.startTabOffset);
// TODO: what a mess
String line = ps.pdeCode.substring(lineStartPdeOffset, lineStopPdeOffset);
String pre = line.substring(0, highlightStartOffset)
.replace("&", "&amp;").replace(">", "&gt;").replace("<", "&lt;");
String highlight = line.substring(highlightStartOffset, highlightStopOffset)
.replace("&", "&amp;").replace(">", "&gt;").replace("<", "&lt;");
String post = line.substring(highlightStopOffset)
.replace("&", "&amp;").replace(">", "&gt;").replace("<", "&lt;");
line = pre + "<font color=#222222><b>" + highlight + "</b></font>" + post;
line = line.trim();
String text = "<html><font color=#bbbbbb>" +
(tabLine + 1) + "</font> <font color=#777777>" + line + "</font></html>";
return new ShowUsageTreeNode(in.tabIndex, in.startTabOffset, in.stopTabOffset, text);
}
@Override
public String toString() {
return text;
}
}