package processing.mode.java;
import static processing.mode.java.ASTUtils.findAllOccurrences;
import static processing.mode.java.ASTUtils.getSimpleNameAt;
import static processing.mode.java.ASTUtils.resolveBinding;
import java.awt.Dimension;
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.Box;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JRootPane;
import javax.swing.JTextField;
import javax.swing.WindowConstants;
import javax.swing.border.EmptyBorder;
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.Platform;
import processing.app.Sketch;
import processing.app.SketchCode;
import processing.app.syntax.SyntaxDocument;
import processing.app.ui.EditorStatus;
import processing.app.ui.Toolkit;
class Rename {
final JavaEditor editor;
final PreprocService pps;
final ShowUsage showUsage;
final JDialog window;
final JTextField textField;
final JLabel oldNameLabel;
IBinding binding;
PreprocSketch ps;
Rename(JavaEditor editor, PreprocService 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);
JRootPane rootPane = window.getRootPane();
window.setTitle("Rename");
window.setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE);
Toolkit.registerWindowCloseKeys(rootPane, e -> window.setVisible(false));
Toolkit.setIcon(window);
window.setModal(true);
window.setResizable(false);
window.addComponentListener(new ComponentAdapter() {
@Override
public void componentHidden(ComponentEvent e) {
binding = null;
ps = null;
}
});
Box windowBox = Box.createVerticalBox();
windowBox.setBorder(new EmptyBorder(20, 20, 20, 20));
final int GAP = Toolkit.zoom(5);
{ // old name
Box oldBox = Box.createHorizontalBox();
oldNameLabel = new JLabel("Current Name: ");
oldBox.add(oldNameLabel);
//oldBox.add(Box.createHorizontalStrut(10));
oldBox.add(Box.createHorizontalGlue());
windowBox.add(oldBox);
windowBox.add(Box.createVerticalStrut(GAP));
}
{ // new name
Box newBox = Box.createHorizontalBox();
JLabel newNameLabel = new JLabel("New Name: ");
newBox.add(newNameLabel);
newBox.add(textField = new JTextField(20));
newBox.add(Box.createHorizontalGlue());
windowBox.add(newBox);
windowBox.add(Box.createVerticalStrut(GAP*2));
}
{ // button 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.chars().limit(1).allMatch(Character::isJavaIdentifierStart) &&
newName.chars().skip(1).allMatch(Character::isJavaIdentifierPart)) {
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);
}
}
});
rootPane.setDefaultButton(renameButton);
Box buttonBox = Box.createHorizontalBox();
buttonBox.add(Box.createHorizontalGlue());
buttonBox.add(showUsageButton);
if (!Platform.isMacOS()) {
buttonBox.add(Box.createHorizontalStrut(GAP));
}
buttonBox.add(renameButton);
buttonBox.add(Box.createHorizontalGlue());
Dimension showDim = showUsageButton.getPreferredSize();
Dimension renameDim = renameButton.getPreferredSize();
final int niceSize = Math.max(showDim.width, renameDim.width) + GAP;
final Dimension buttonDim = new Dimension(niceSize, showDim.height);
showUsageButton.setPreferredSize(buttonDim);
renameButton.setPreferredSize(buttonDim);
windowBox.add(buttonBox);
//window.add(panelBottom);
}
window.add(windowBox);
window.pack();
//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(PreprocSketch ps, int tabIndex, int startTabOffset, int stopTabOffset) {
if (ps.hasSyntaxErrors) {
editor.statusMessage("Cannot rename 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(PreprocSketch 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