mirror of
https://github.com/processing/processing4.git
synced 2026-02-02 13:21:07 +01:00
breaking out these static classes to be on their own
This commit is contained in:
200
java/src/processing/mode/java/pdex/InspectMode.java
Normal file
200
java/src/processing/mode/java/pdex/InspectMode.java
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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("&", "&").replace(">", ">").replace("<", "<");
|
||||
String highlight = line.substring(highlightStartOffset, highlightStopOffset)
|
||||
.replace("&", "&").replace(">", ">").replace("<", "<");
|
||||
String post = line.substring(highlightStopOffset)
|
||||
.replace("&", "&").replace(">", ">").replace("<", "<");
|
||||
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;
|
||||
|
||||
308
java/src/processing/mode/java/pdex/Rename.java
Normal file
308
java/src/processing/mode/java/pdex/Rename.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
329
java/src/processing/mode/java/pdex/ShowUsage.java
Normal file
329
java/src/processing/mode/java/pdex/ShowUsage.java
Normal 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("&", "&").replace(">", ">").replace("<", "<");
|
||||
String highlight = line.substring(highlightStartOffset, highlightStopOffset)
|
||||
.replace("&", "&").replace(">", ">").replace("<", "<");
|
||||
String post = line.substring(highlightStopOffset)
|
||||
.replace("&", "&").replace(">", ">").replace("<", "<");
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user