mirror of
https://github.com/processing/processing4.git
synced 2026-06-16 04:26:26 +02:00
319 lines
10 KiB
Java
319 lines
10 KiB
Java
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<SimpleName> occurrences =
|
|
new ArrayList<>(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.forEach((key, nodes) -> {
|
|
int tabIndex = key;
|
|
SketchCode sketchCode = sketch.getCode(tabIndex);
|
|
|
|
SyntaxDocument document = (SyntaxDocument) sketchCode.getDocument();
|
|
|
|
nodes.stream()
|
|
// Replace from the end so all unprocessed 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();
|
|
}
|
|
}
|
|
} |