ASTGen: break the revised stuff out into PDEX

This commit is contained in:
Jakub Valtar
2016-05-06 12:36:40 +02:00
parent 2796026cf6
commit 7b13d1d2dd
7 changed files with 1065 additions and 1030 deletions

View File

@@ -29,10 +29,10 @@ import processing.app.ui.Toolkit;
import processing.mode.java.debug.LineBreakpoint;
import processing.mode.java.debug.LineHighlight;
import processing.mode.java.debug.LineID;
import processing.mode.java.pdex.ASTGenerator;
import processing.mode.java.pdex.ErrorCheckerService;
import processing.mode.java.pdex.ImportStatement;
import processing.mode.java.pdex.JavaTextArea;
import processing.mode.java.pdex.PDEX;
import processing.mode.java.pdex.Problem;
import processing.mode.java.pdex.SourceUtils;
import processing.mode.java.preproc.PdePreprocessor;
@@ -71,6 +71,7 @@ public class JavaEditor extends Editor {
private boolean javaTabWarned;
protected ErrorCheckerService errorCheckerService;
protected PDEX pdex;
protected List<Problem> problems = Collections.emptyList();
@@ -183,6 +184,21 @@ public class JavaEditor extends Editor {
}
errorCheckerService.start();
errorCheckerService.notifySketchChanged();
pdex = new PDEX(this, errorCheckerService);
// Add ctrl+click listener
getJavaTextArea().getPainter().addMouseListener(new MouseAdapter() {
public void mouseReleased(MouseEvent evt) {
if (evt.getButton() == MouseEvent.BUTTON1) {
if ((evt.isControlDown() && !Platform.isMacOS()) || evt.isMetaDown()) {
handleCtrlClick(evt);
}
} else if (evt.getButton() == MouseEvent.BUTTON2) {
handleCtrlClick(evt);
}
}
});
}
@@ -212,6 +228,7 @@ public class JavaEditor extends Editor {
if (errorCheckerService != null) {
if (hasJavaTabsChanged) {
errorCheckerService.handleHasJavaTabsChange(hasJavaTabs);
pdex.handleHasJavaTabsChange(hasJavaTabs);
if (hasJavaTabs) {
setProblemList(Collections.emptyList());
}
@@ -1358,6 +1375,7 @@ public class JavaEditor extends Editor {
inspector.dispose();
}
errorCheckerService.stop();
pdex.dispose();
super.dispose();
}
@@ -2795,25 +2813,31 @@ public class JavaEditor extends Editor {
/** Handle refactor operation */
private void handleRefactor() {
Messages.log("Caret at:" + textarea.getLineText(textarea.getCaretLine()));
ASTGenerator astGenerator = errorCheckerService.getASTGenerator();
int startOffset = getSelectionStart();
int stopOffset = getSelectionStop();
int tabIndex = sketch.getCurrentCodeIndex();
astGenerator.handleRename(tabIndex, startOffset, stopOffset);
pdex.handleRename(tabIndex, startOffset, stopOffset);
}
/** Handle show usage operation */
private void handleShowUsage() {
Messages.log("Caret at:" + textarea.getLineText(textarea.getCaretLine()));
ASTGenerator astGenerator = errorCheckerService.getASTGenerator();
int startOffset = getSelectionStart();
int stopOffset = getSelectionStop();
int tabIndex = sketch.getCurrentCodeIndex();
astGenerator.handleShowUsage(tabIndex, startOffset, stopOffset);
pdex.handleShowUsage(tabIndex, startOffset, stopOffset);
}
/** Handle ctrl+click */
private void handleCtrlClick(MouseEvent evt) {
int off = getJavaTextArea().xyToOffset(evt.getX(), evt.getY());
if (off < 0) return;
int tabIndex = sketch.getCurrentCodeIndex();
pdex.handleCtrlClick(tabIndex, off);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,183 @@
package processing.mode.java.pdex;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.ClassInstanceCreation;
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.MethodDeclaration;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.NodeFinder;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;
import processing.app.Messages;
public class ASTUtils {
public static ASTNode getASTNodeAt(ASTNode root, int startJavaOffset, int stopJavaOffset) {
Messages.log("* getASTNodeAt");
int length = stopJavaOffset - startJavaOffset;
NodeFinder f = new NodeFinder(root, startJavaOffset, length);
ASTNode node = f.getCoveredNode();
if (node == null) {
node = f.getCoveringNode();
}
if (node == null) {
Messages.log("no node found");
} else {
Messages.log("found " + node.getClass().getSimpleName());
}
return node;
}
public static SimpleName getSimpleNameAt(ASTNode root, int startJavaOffset, int stopJavaOffset) {
Messages.log("* getSimpleNameAt");
// Find node at offset
ASTNode node = getASTNodeAt(root, startJavaOffset, stopJavaOffset);
SimpleName result = null;
if (node == null) {
result = null;
} else if (node.getNodeType() == ASTNode.SIMPLE_NAME) {
result = (SimpleName) node;
} else {
// Return SimpleName with highest coverage
List<SimpleName> simpleNames = getSimpleNameChildren(node);
if (!simpleNames.isEmpty()) {
// Compute coverage <selection x node>
int[] coverages = simpleNames.stream()
.mapToInt(name -> {
int start = name.getStartPosition();
int stop = start + name.getLength();
return Math.min(stop, stopJavaOffset) -
Math.max(startJavaOffset, start);
})
.toArray();
// Select node with highest coverage
int maxIndex = IntStream.range(0, simpleNames.size())
.filter(i -> coverages[i] >= 0)
.reduce((i, j) -> coverages[i] > coverages[j] ? i : j)
.orElse(-1);
if (maxIndex == -1) return null;
result = simpleNames.get(maxIndex);
}
}
if (result == null) {
Messages.log("no simple name found");
} else {
Messages.log("found " + node.toString());
}
return result;
}
public static List<SimpleName> getSimpleNameChildren(ASTNode node) {
List<SimpleName> simpleNames = new ArrayList<>();
node.accept(new ASTVisitor() {
@Override
public boolean visit(SimpleName simpleName) {
simpleNames.add(simpleName);
return super.visit(simpleName);
}
});
return simpleNames;
}
public static IBinding resolveBinding(SimpleName node) {
IBinding binding = node.resolveBinding();
if (binding == null) return null;
// Fix constructor call/declaration being resolved as type
if (binding.getKind() == IBinding.TYPE) {
ASTNode context = node;
// Go up until we find non Name or Type node
// stop if context is type argument (parent is also Name/Type, but unrelated)
while (isNameOrType(context) &&
!context.getLocationInParent().getId().equals("typeArguments")) {
context = context.getParent();
}
switch (context.getNodeType()) {
case ASTNode.METHOD_DECLARATION:
MethodDeclaration decl = (MethodDeclaration) context;
if (decl.isConstructor()) {
binding = decl.resolveBinding();
}
break;
case ASTNode.CLASS_INSTANCE_CREATION:
ClassInstanceCreation cic = (ClassInstanceCreation) context;
binding = cic.resolveConstructorBinding();
break;
}
}
if (binding == null) return null;
// Normalize parametrized and raw bindings into generic bindings
switch (binding.getKind()) {
case IBinding.TYPE:
ITypeBinding type = (ITypeBinding) binding;
if (type.isParameterizedType() || type.isRawType()) {
binding = type.getErasure();
}
break;
case IBinding.METHOD:
IMethodBinding method = (IMethodBinding) binding;
ITypeBinding declaringClass = method.getDeclaringClass();
if (declaringClass.isParameterizedType() ||
declaringClass.isRawType()) {
IMethodBinding[] methods = declaringClass.getErasure().getDeclaredMethods();
IMethodBinding generic = Arrays.stream(methods)
.filter(method::overrides)
.findAny().orElse(null);
if (generic != null) method = generic;
}
if (method.isParameterizedMethod() || method.isRawMethod()) {
method = method.getMethodDeclaration();
}
binding = method;
break;
}
return binding;
}
public static boolean isNameOrType(ASTNode node) {
return node instanceof Name || node instanceof Type;
}
protected static List<SimpleName> findAllOccurrences(ASTNode root, String bindingKey) {
List<SimpleName> occurences = new ArrayList<>();
root.getRoot().accept(new ASTVisitor() {
@Override
public boolean visit(SimpleName name) {
IBinding binding = resolveBinding(name);
if (binding != null && bindingKey.equals(binding.getKey())) {
occurences.add(name);
}
return super.visit(name);
}
});
return occurences;
}
}

View File

@@ -99,11 +99,6 @@ public class ErrorCheckerService {
*/
private volatile boolean running;
/**
* ASTGenerator for operations on AST
*/
protected final ASTGenerator astGenerator;
/**
* Error checking doesn't happen before this interval has ellapsed since the
* last request() call.
@@ -137,7 +132,6 @@ public class ErrorCheckerService {
public ErrorCheckerService(JavaEditor editor) {
this.editor = editor;
astGenerator = new ASTGenerator(editor, this);
isEnabled = !editor.hasJavaTabs();
isContinuousCheckEnabled = JavaMode.errorCheckEnabled;
registerDoneListener(errorHandlerListener);
@@ -170,8 +164,6 @@ public class ErrorCheckerService {
Messages.loge("problem in error checker loop", e);
}
}
astGenerator.getGui().disposeAllWindows();
}
@@ -303,11 +295,6 @@ public class ErrorCheckerService {
}
public ASTGenerator getASTGenerator() {
return astGenerator;
}
protected final DocumentListener sketchChangedListener = new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
@@ -929,9 +916,6 @@ public class ErrorCheckerService {
notifySketchChanged();
} else {
preprocessingTask.cancel(false);
if (astGenerator.getGui().showUsageBinding != null) {
astGenerator.getGui().showUsageWindow.setVisible(false);
}
}
}

View File

@@ -90,6 +90,8 @@ public class JavaTextArea extends JEditTextArea {
prevMMotionListeners = painter.getMouseMotionListeners();
prevKeyListeners = editor.getKeyListeners();
suggestionGenerator = new ASTGenerator();
tweakMode = false;
}
@@ -254,6 +256,8 @@ public class JavaTextArea extends JEditTextArea {
}
ASTGenerator suggestionGenerator;
SwingWorker<Void, Void> suggestionWorker = null;
volatile boolean suggestionRunning = false;
@@ -331,8 +335,7 @@ public class JavaTextArea extends JEditTextArea {
if (phrase != null) {
List<CompletionCandidate> candidates;
ASTGenerator astGenerator = editor.getErrorChecker().getASTGenerator();
candidates = astGenerator.preparePredictions(ps, phrase, lineNumber);
candidates = suggestionGenerator.preparePredictions(ps, phrase, lineNumber);
if (!suggestionRequested) {

View File

@@ -47,7 +47,6 @@ import javax.swing.text.Utilities;
import processing.app.Messages;
import processing.app.Mode;
import processing.app.Platform;
import processing.app.SketchCode;
import processing.app.syntax.SyntaxDocument;
import processing.app.syntax.TextAreaDefaults;
@@ -77,20 +76,6 @@ public class JavaTextAreaPainter extends TextAreaPainter
public JavaTextAreaPainter(final JavaTextArea textArea, TextAreaDefaults defaults) {
super(textArea, defaults);
addMouseListener(new MouseAdapter() {
public void mouseReleased(MouseEvent evt) {
if (!getJavaEditor().hasJavaTabs()) { // Ctrl + Click disabled for java tabs
if (evt.getButton() == MouseEvent.BUTTON1) {
if ((evt.isControlDown() && !Platform.isMacOS()) || evt.isMetaDown()) {
handleCtrlClick(evt);
}
} else if (evt.getButton() == MouseEvent.BUTTON2) {
handleCtrlClick(evt);
}
}
}
});
// Handle mouse clicks to toggle breakpoints
addMouseListener(new MouseAdapter() {
long lastTime; // OS X seems to be firing multiple mouse events
@@ -120,19 +105,6 @@ public class JavaTextAreaPainter extends TextAreaPainter
cursorType = Cursor.DEFAULT_CURSOR;
}
void handleCtrlClick(MouseEvent evt) {
int off = textArea.xyToOffset(evt.getX(), evt.getY());
if (off < 0) return;
int tabIndex = getEditor().getSketch().getCurrentCodeIndex();
ASTGenerator astGenerator = getJavaEditor().getErrorChecker().getASTGenerator();
astGenerator.handleCtrlClick(tabIndex, off);
}
/**
* Paint a line. Paints the gutter (with background color and text) then the
* line (background color and text).

View File

@@ -0,0 +1,843 @@
package processing.mode.java.pdex;
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.IVariableBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.StructuralPropertyDescriptor;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclaration;
import java.awt.Dimension;
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.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
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.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.JTree;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeModel;
import processing.app.Messages;
import processing.app.Sketch;
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.*;
public class PDEX {
private static final boolean SHOW_DEBUG_TREE = true;
private boolean enabled = true;
private ShowUsage showUsage;
private Rename rename;
private DebugTree debugTree;
private JavaEditor editor;
private ErrorCheckerService ecs;
public PDEX(JavaEditor editor, ErrorCheckerService ecs) {
this.editor = editor;
this.ecs = ecs;
this.enabled = !editor.hasJavaTabs();
showUsage = new ShowUsage(editor, ecs);
rename = new Rename(editor);
if (SHOW_DEBUG_TREE) {
debugTree = new DebugTree(editor, ecs);
}
}
public void handleShowUsage(int tabIndex, int startTabOffset, int stopTabOffset) {
Messages.log("* handleShowUsage");
if (!enabled) return; // show usage disabled if java tabs
ecs.acceptWhenDone(ps -> showUsage.findUsageAndUpdateTree(ps, tabIndex, startTabOffset, stopTabOffset));
}
public void handleRename(int tabIndex, int startTabOffset, int stopTabOffset) {
Messages.log("* handleRename");
if (!enabled) return; // refactoring disabled w/ java tabs
ecs.acceptWhenDone(ps -> rename.handleRename(ps, tabIndex, startTabOffset, stopTabOffset));
}
public void handleCtrlClick(int tabIndex, int offset) {
Messages.log("* handleCtrlClick");
if (!enabled) return; // disabled w/ java tabs
ecs.acceptWhenDone(ps -> handleCtrlClick(ps, tabIndex, offset));
}
public void handleHasJavaTabsChange(boolean hasJavaTabs) {
enabled = !hasJavaTabs;
if (!enabled) {
showUsage.hide();
}
}
public void dispose() {
showUsage.dispose();
rename.dispose();
if (debugTree != null) {
debugTree.dispose();
}
}
// Thread: worker
private void handleCtrlClick(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);
EventQueue.invokeLater(() -> {
editor.highlight(si.tabIndex, si.startTabOffset, si.stopTabOffset);
});
}
}
private class ShowUsage {
final JDialog window;
final JTree tree;
final JavaEditor editor;
final ErrorCheckerService ecs;
final Consumer<PreprocessedSketch> reloadListener;
IBinding binding;
ShowUsage(JavaEditor editor, ErrorCheckerService ecs) {
this.editor = editor;
this.ecs = ecs;
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) {
// Delete references to ASTNodes so that whole AST can be GC'd
binding = null;
tree.setModel(null);
ecs.unregisterDoneListener(reloadListener);
}
@Override
public void componentShown(ComponentEvent e) {
ecs.registerDoneListener(reloadListener);
}
});
window.setSize(300, 400);
Toolkit.setIcon(window);
JScrollPane sp2 = new JScrollPane();
tree = new JTree();
DefaultTreeCellRenderer renderer = (DefaultTreeCellRenderer) tree.getCellRenderer();
renderer.setLeafIcon(null);
renderer.setClosedIcon(null);
renderer.setOpenIcon(null);
sp2.setViewportView(tree);
window.add(sp2);
}
tree.addTreeSelectionListener(e -> {
if (tree.getLastSelectedPathComponent() == null) {
return;
}
DefaultMutableTreeNode tnode = (DefaultMutableTreeNode) tree
.getLastSelectedPathComponent();
if (tnode.getUserObject() instanceof ShowUsageTreeNode) {
ShowUsageTreeNode node = (ShowUsageTreeNode) tnode.getUserObject();
editor.highlight(node.tabIndex, node.startTabOffset, node.stopTabOffset);
}
});
}
// Thread: worker
void findUsageAndUpdateTree(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) return;
// Find binding
IBinding binding = ASTUtils.resolveBinding(name);
if (binding == null) return;
this.binding = binding;
findUsageAndUpdateTree(ps, binding);
}
// Thread: worker
void findUsageAndUpdateTree(PreprocessedSketch ps, IBinding 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;
}
String elementName = binding.getName();
// Create root node
DefaultMutableTreeNode rootNode =
new DefaultMutableTreeNode(bindingType + ": " + elementName);
int usageCount;
{ // 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)
// TODO: this has to be fixed with better token mapping
// remove occurrences which fall into generated header
.filter(in -> in.tabIndex != 0 ||
(in.startTabOffset >= 0 && in.stopTabOffset > 0))
.collect(Collectors.toList());
usageCount = intervals.size();
Map<Integer, List<ShowUsageTreeNode>> tabGroupedTreeNodes = intervals.stream()
// Convert to TreeNodes
.map(in -> ShowUsageTreeNode.fromSketchInterval(ps, in))
// Group by tab
.collect(Collectors.groupingBy(node -> node.tabIndex));
tabGroupedTreeNodes.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);
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();
}
}
}
private static 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;
}
}
private class Rename {
final JDialog window;
final JTextField textField;
final JLabel oldNameLabel;
final JavaEditor editor;
IBinding binding;
PreprocessedSketch ps;
Rename(JavaEditor editor) {
this.editor = editor;
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(250, 130);
window.setLayout(new BoxLayout(window.getContentPane(), BoxLayout.Y_AXIS));
Toolkit.setIcon(window);
{ // Top panel
// Text field
textField = new JTextField();
textField.setPreferredSize(new Dimension(150, 60));
// Old name label
oldNameLabel = new JLabel();
oldNameLabel.setText("Old Name: ");
// Top panel
JPanel panelTop = new JPanel();
panelTop.setLayout(new BoxLayout(panelTop, BoxLayout.Y_AXIS));
panelTop.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
panelTop.add(textField);
panelTop.add(Box.createRigidArea(new Dimension(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 -> {
if (textField.getText().length() == 0) {
return;
}
String newName = textField.getText().trim();
boolean isNewNameValid = newName.length() >= 1 &&
newName.chars().limit(1).allMatch(Character::isUnicodeIdentifierStart) &&
newName.chars().skip(1).allMatch(Character::isUnicodeIdentifierPart);
if (!isNewNameValid) {
JOptionPane.showMessageDialog(new JFrame(), "'" + newName
+ "' isn't a valid name.", "Uh oh..", JOptionPane.PLAIN_MESSAGE);
} else {
rename(ps, binding, newName);
window.setVisible(false);
}
});
JPanel panelBottom = new JPanel();
panelBottom.setLayout(new BoxLayout(panelBottom, BoxLayout.X_AXIS));
panelBottom.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
panelBottom.add(Box.createHorizontalGlue());
panelBottom.add(showUsageButton);
panelBottom.add(Box.createRigidArea(new Dimension(15, 0)));
panelBottom.add(renameButton);
window.add(panelBottom);
}
window.setMinimumSize(window.getSize());
}
// 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;
window.setLocation(editor.getX()
+ (editor.getWidth() - window.getWidth()) / 2,
editor.getY()
+ (editor.getHeight() - window.getHeight())
/ 2);
oldNameLabel.setText("Current name: " + binding.getName());
textField.setText(binding.getName());
textField.requestFocus();
textField.selectAll();
window.setVisible(true);
window.toFront();
int x = editor.getX() + (editor.getWidth() - window.getWidth()) / 2;
int y = editor.getY() + (editor.getHeight() - window.getHeight()) / 2;
window.setLocation(x, y);
}
});
}
// 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)
.collect(Collectors.groupingBy(interval -> interval.tabIndex));
Sketch sketch = ps.sketch;
editor.startCompoundEdit();
int currentTabIndex = sketch.getCurrentCodeIndex();
final int currentOffset = editor.getCaretOffset();
mappedNodes.entrySet().forEach(entry -> {
int tabIndex = entry.getKey();
sketch.setCurrentCode(tabIndex);
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 length = editor.getTextArea().getDocumentLength();
if (si.startTabOffset >= 0 && si.startTabOffset <= length &&
si.stopTabOffset >= 0 && si.stopTabOffset <= length) {
// Replace the code
editor.getTextArea().select(si.startTabOffset, si.stopTabOffset);
editor.getTextArea().setSelectedText(newName);
}
});
sketch.setModified(true);
});
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;
sketch.setCurrentCode(currentTabIndex);
editor.getTextArea().setCaretPosition(currentOffset + offsetDiff);
editor.stopCompoundEdit();
}
void dispose() {
if (window != null) {
window.dispose();
}
}
}
private static class DebugTree {
final JDialog window;
final JTree tree;
final Consumer<PreprocessedSketch> updateListener;
DebugTree(JavaEditor editor, ErrorCheckerService ecs) {
updateListener = this::buildAndUpdateTree;
window = new JDialog(editor);
tree = new JTree() {
@Override
public String convertValueToText(Object value, boolean selected,
boolean expanded, boolean leaf,
int row, boolean hasFocus) {
if (value instanceof DefaultMutableTreeNode) {
DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode) value;
Object o = treeNode.getUserObject();
if (o instanceof ASTNode) {
ASTNode node = (ASTNode) o;
return ASTGenerator.getNodeAsString(node);
}
}
return super.convertValueToText(value, selected, expanded, leaf, row, hasFocus);
}
};
window.addComponentListener(new ComponentAdapter() {
@Override
public void componentHidden(ComponentEvent e) {
ecs.unregisterDoneListener(updateListener);
tree.setModel(null);
}
});
window.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
window.setBounds(new Rectangle(680, 100, 460, 620));
window.setTitle("AST View - " + editor.getSketch().getName());
JScrollPane sp = new JScrollPane();
sp.setViewportView(tree);
window.add(sp);
ecs.acceptWhenDone(updateListener);
ecs.registerDoneListener(updateListener);
tree.addTreeSelectionListener(e -> {
if (tree.getLastSelectedPathComponent() == null) {
return;
}
DefaultMutableTreeNode tnode =
(DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
if (tnode.getUserObject() instanceof ASTNode) {
ASTNode node = (ASTNode) tnode.getUserObject();
ecs.acceptWhenDone(ps -> {
SketchInterval si = ps.mapJavaToSketch(node);
EventQueue.invokeLater(() -> {
editor.highlight(si.tabIndex, si.startTabOffset, si.stopTabOffset);
});
});
}
});
}
void dispose() {
if (window != null) {
window.dispose();
}
}
// Thread: worker
void buildAndUpdateTree(PreprocessedSketch ps) {
CompilationUnit cu = ps.compilationUnit;
if (cu.types().isEmpty()){
Messages.loge("No Type found in CU");
return;
}
ASTNode type0 = (ASTNode) cu.types().get(0);
DefaultMutableTreeNode codeTree = new DefaultMutableTreeNode(type0);
visitRecur(type0, codeTree);
EventQueue.invokeLater(() -> {
if (tree.hasFocus() || window.hasFocus()) {
return;
}
tree.setModel(new DefaultTreeModel(codeTree));
((DefaultTreeModel) tree.getModel()).reload();
tree.validate();
if (!window.isVisible()) {
window.setVisible(true);
}
});
}
// Thread: worker
/**
* Generates AST Swing component
* @param node
* @param tnode
*/
void visitRecur(ASTNode node, DefaultMutableTreeNode tnode) {
// TODO: nuke this, use ASTVisitor
Iterator<StructuralPropertyDescriptor> it =
node.structuralPropertiesForType().iterator();
//Base.loge("Props of " + node.getClass().getName());
DefaultMutableTreeNode ctnode;
while (it.hasNext()) {
StructuralPropertyDescriptor prop = it.next();
if (prop.isChildProperty() || prop.isSimpleProperty()) {
if (node.getStructuralProperty(prop) != null) {
// System.out
// .println(node.getStructuralProperty(prop) + " -> " + (prop));
if (node.getStructuralProperty(prop) instanceof ASTNode) {
ASTNode cnode = (ASTNode) node.getStructuralProperty(prop);
if (isAddableASTNode(cnode)) {
ctnode = new DefaultMutableTreeNode(node.getStructuralProperty(prop));
tnode.add(ctnode);
visitRecur(cnode, ctnode);
}
} else {
tnode.add(new DefaultMutableTreeNode(node
.getStructuralProperty(prop)));
}
}
} else if (prop.isChildListProperty()) {
List<ASTNode> nodelist = (List<ASTNode>) node.getStructuralProperty(prop);
for (ASTNode cnode : nodelist) {
if (isAddableASTNode(cnode)) {
ctnode = new DefaultMutableTreeNode(cnode);
tnode.add(ctnode);
visitRecur(cnode, ctnode);
} else {
visitRecur(cnode, tnode);
}
}
}
}
}
boolean isAddableASTNode(ASTNode node) {
switch (node.getNodeType()) {
// case ASTNode.STRING_LITERAL:
// case ASTNode.NUMBER_LITERAL:
// case ASTNode.BOOLEAN_LITERAL:
// case ASTNode.NULL_LITERAL:
// return false;
default:
return true;
}
}
}
}