| Index: editor/tools/plugins/com.google.dart.tools.ui/src/com/google/dart/tools/ui/internal/refactoring/reorg/RenameLinkedMode.java
|
| diff --git a/editor/tools/plugins/com.google.dart.tools.ui/src/com/google/dart/tools/ui/internal/refactoring/reorg/RenameLinkedMode.java b/editor/tools/plugins/com.google.dart.tools.ui/src/com/google/dart/tools/ui/internal/refactoring/reorg/RenameLinkedMode.java
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..3c6c6ce8465175185afa39b4b46a08947be80819
|
| --- /dev/null
|
| +++ b/editor/tools/plugins/com.google.dart.tools.ui/src/com/google/dart/tools/ui/internal/refactoring/reorg/RenameLinkedMode.java
|
| @@ -0,0 +1,534 @@
|
| +package com.google.dart.tools.ui.internal.refactoring.reorg;
|
| +
|
| +import com.google.dart.compiler.ast.DartIdentifier;
|
| +import com.google.dart.compiler.ast.DartNode;
|
| +import com.google.dart.compiler.ast.DartUnit;
|
| +import com.google.dart.compiler.ast.DartVariable;
|
| +import com.google.dart.tools.core.dom.NodeFinder;
|
| +import com.google.dart.tools.core.model.CompilationUnit;
|
| +import com.google.dart.tools.core.model.DartConventions;
|
| +import com.google.dart.tools.core.model.DartElement;
|
| +import com.google.dart.tools.core.model.DartVariableDeclaration;
|
| +import com.google.dart.tools.internal.corext.dom.LinkedNodeFinder;
|
| +import com.google.dart.tools.ui.DartToolsPlugin;
|
| +import com.google.dart.tools.ui.internal.refactoring.RenameSupport;
|
| +import com.google.dart.tools.ui.internal.text.correction.proposals.LinkedNamesAssistProposal.DeleteBlockingExitPolicy;
|
| +import com.google.dart.tools.ui.internal.text.editor.ASTProvider;
|
| +import com.google.dart.tools.ui.internal.text.editor.CompilationUnitEditor;
|
| +import com.google.dart.tools.ui.internal.text.editor.EditorHighlightingSynchronizer;
|
| +import com.google.dart.tools.ui.internal.text.editor.EditorUtility;
|
| +import com.google.dart.tools.ui.internal.util.DartModelUtil;
|
| +
|
| +import org.eclipse.core.commands.operations.IOperationHistory;
|
| +import org.eclipse.core.commands.operations.IUndoContext;
|
| +import org.eclipse.core.commands.operations.IUndoableOperation;
|
| +import org.eclipse.core.commands.operations.OperationHistoryFactory;
|
| +import org.eclipse.core.runtime.Assert;
|
| +import org.eclipse.core.runtime.CoreException;
|
| +import org.eclipse.core.runtime.IProgressMonitor;
|
| +import org.eclipse.core.runtime.IStatus;
|
| +import org.eclipse.core.runtime.Status;
|
| +import org.eclipse.jface.operation.IRunnableWithProgress;
|
| +import org.eclipse.jface.text.BadLocationException;
|
| +import org.eclipse.jface.text.DocumentEvent;
|
| +import org.eclipse.jface.text.IDocument;
|
| +import org.eclipse.jface.text.IEditingSupport;
|
| +import org.eclipse.jface.text.IEditingSupportRegistry;
|
| +import org.eclipse.jface.text.IRegion;
|
| +import org.eclipse.jface.text.ITextViewerExtension6;
|
| +import org.eclipse.jface.text.IUndoManager;
|
| +import org.eclipse.jface.text.IUndoManagerExtension;
|
| +import org.eclipse.jface.text.link.ILinkedModeListener;
|
| +import org.eclipse.jface.text.link.LinkedModeModel;
|
| +import org.eclipse.jface.text.link.LinkedModeUI;
|
| +import org.eclipse.jface.text.link.LinkedModeUI.ExitFlags;
|
| +import org.eclipse.jface.text.link.LinkedPosition;
|
| +import org.eclipse.jface.text.link.LinkedPositionGroup;
|
| +import org.eclipse.jface.text.source.ISourceViewer;
|
| +import org.eclipse.jface.text.source.SourceViewer;
|
| +import org.eclipse.swt.SWT;
|
| +import org.eclipse.swt.custom.StyledText;
|
| +import org.eclipse.swt.events.VerifyEvent;
|
| +import org.eclipse.swt.graphics.GC;
|
| +import org.eclipse.swt.graphics.Image;
|
| +import org.eclipse.swt.graphics.Point;
|
| +import org.eclipse.swt.widgets.Composite;
|
| +import org.eclipse.swt.widgets.Control;
|
| +import org.eclipse.swt.widgets.Display;
|
| +import org.eclipse.swt.widgets.Label;
|
| +import org.eclipse.swt.widgets.Shell;
|
| +import org.eclipse.ui.texteditor.link.EditorLinkedModeUI;
|
| +
|
| +import java.lang.reflect.InvocationTargetException;
|
| +import java.util.Arrays;
|
| +import java.util.Comparator;
|
| +
|
| +public class RenameLinkedMode {
|
| +
|
| + private class EditorSynchronizer implements ILinkedModeListener {
|
| + @Override
|
| + public void left(LinkedModeModel model, int flags) {
|
| + linkedModeLeft();
|
| + if ((flags & ILinkedModeListener.UPDATE_CARET) != 0) {
|
| + doRename(fShowPreview);
|
| + }
|
| + }
|
| +
|
| + @Override
|
| + public void resume(LinkedModeModel model, int flags) {
|
| + }
|
| +
|
| + @Override
|
| + public void suspend(LinkedModeModel model) {
|
| + }
|
| + }
|
| +
|
| + private class ExitPolicy extends DeleteBlockingExitPolicy {
|
| + public ExitPolicy(IDocument document) {
|
| + super(document);
|
| + }
|
| +
|
| + @Override
|
| + public ExitFlags doExit(LinkedModeModel model, VerifyEvent event, int offset, int length) {
|
| + fShowPreview = (event.stateMask & SWT.CTRL) != 0
|
| + && (event.character == SWT.CR || event.character == SWT.LF);
|
| + return super.doExit(model, event, offset, length);
|
| + }
|
| + }
|
| +
|
| + private class FocusEditingSupport implements IEditingSupport {
|
| + @Override
|
| + public boolean isOriginator(DocumentEvent event, IRegion subjectRegion) {
|
| + return false; //leave on external modification outside positions
|
| + }
|
| +
|
| + @Override
|
| + public boolean ownsFocusShell() {
|
| + if (fInfoPopup == null) {
|
| + return false;
|
| + }
|
| + if (fInfoPopup.ownsFocusShell()) {
|
| + return true;
|
| + }
|
| +
|
| + Shell editorShell = fEditor.getSite().getShell();
|
| + Shell activeShell = editorShell.getDisplay().getActiveShell();
|
| + if (editorShell == activeShell) {
|
| + return true;
|
| + }
|
| + return false;
|
| + }
|
| + }
|
| +
|
| + private static RenameLinkedMode fgActiveLinkedMode;
|
| +
|
| + public static RenameLinkedMode getActiveLinkedMode() {
|
| + if (fgActiveLinkedMode != null) {
|
| + ISourceViewer viewer = fgActiveLinkedMode.fEditor.getViewer();
|
| + if (viewer != null) {
|
| + StyledText textWidget = viewer.getTextWidget();
|
| + if (textWidget != null && !textWidget.isDisposed()) {
|
| + return fgActiveLinkedMode;
|
| + }
|
| + }
|
| + // make sure we don't hold onto the active linked mode if anything went wrong with canceling:
|
| + fgActiveLinkedMode = null;
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + private final CompilationUnitEditor fEditor;
|
| +
|
| + private final DartElement fDartElement;
|
| +
|
| + private RenameInformationPopup fInfoPopup;
|
| + private Point fOriginalSelection;
|
| +
|
| + private String fOriginalName;
|
| + private LinkedPosition fNamePosition;
|
| + private LinkedModeModel fLinkedModeModel;
|
| + private LinkedPositionGroup fLinkedPositionGroup;
|
| + private final FocusEditingSupport fFocusEditingSupport;
|
| +
|
| + private boolean fShowPreview;
|
| +
|
| + /**
|
| + * The operation on top of the undo stack when the rename is {@link #start()}ed, or
|
| + * <code>null</code> if rename has not been started or the undo stack was empty.
|
| + *
|
| + * @since 3.5
|
| + */
|
| + private IUndoableOperation fStartingUndoOperation;
|
| +
|
| + public RenameLinkedMode(DartElement element, CompilationUnitEditor editor) {
|
| + Assert.isNotNull(element);
|
| + Assert.isNotNull(editor);
|
| + fEditor = editor;
|
| + fDartElement = element;
|
| + fFocusEditingSupport = new FocusEditingSupport();
|
| + }
|
| +
|
| + public void cancel() {
|
| + if (fLinkedModeModel != null) {
|
| + fLinkedModeModel.exit(ILinkedModeListener.NONE);
|
| + }
|
| + linkedModeLeft();
|
| + }
|
| +
|
| +// private void startAnimation() {
|
| +// //TODO:
|
| +// // - switch off if animations disabled
|
| +// // - show rectangle around target for 500ms after animation
|
| +// Shell shell= fEditor.getSite().getShell();
|
| +// StyledText textWidget= fEditor.getViewer().getTextWidget();
|
| +//
|
| +// // from popup:
|
| +// Rectangle startRect= fPopup.getBounds();
|
| +//
|
| +// // from editor:
|
| +//// Point startLoc= textWidget.getParent().toDisplay(textWidget.getLocation());
|
| +//// Point startSize= textWidget.getSize();
|
| +//// Rectangle startRect= new Rectangle(startLoc.x, startLoc.y, startSize.x, startSize.y);
|
| +//
|
| +// // from hell:
|
| +//// Rectangle startRect= shell.getClientArea();
|
| +//
|
| +// Point caretLocation= textWidget.getLocationAtOffset(textWidget.getCaretOffset());
|
| +// Point displayLocation= textWidget.toDisplay(caretLocation);
|
| +// Rectangle targetRect= new Rectangle(displayLocation.x, displayLocation.y, 0, 0);
|
| +//
|
| +// RectangleAnimation anim= new RectangleAnimation(shell, startRect, targetRect);
|
| +// anim.schedule();
|
| +// }
|
| +
|
| + public LinkedPosition getCurrentLinkedPosition() {
|
| + Point selection = fEditor.getViewer().getSelectedRange();
|
| + int start = selection.x;
|
| + int end = start + selection.y;
|
| + LinkedPosition[] positions = fLinkedPositionGroup.getPositions();
|
| + for (int i = 0; i < positions.length; i++) {
|
| + LinkedPosition position = positions[i];
|
| + if (position.includes(start) && position.includes(end)) {
|
| + return position;
|
| + }
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + public boolean isCaretInLinkedPosition() {
|
| + return getCurrentLinkedPosition() != null;
|
| + }
|
| +
|
| + public boolean isEnabled() {
|
| + try {
|
| + String newName = fNamePosition.getContent();
|
| + if (fOriginalName.equals(newName)) {
|
| + return false;
|
| + }
|
| + /*
|
| + * TODO: use DartRenameProcessor#checkNewElementName(String) but make sure implementations
|
| + * don't access outdated Dart Model (cache all necessary information before starting linked
|
| + * mode).
|
| + */
|
| + // TODO(scheglov) we may be rename not only variable, but also method, type, etc
|
| + return DartConventions.validateVariableName(newName).isOK();
|
| + } catch (BadLocationException e) {
|
| + return false;
|
| + }
|
| +
|
| + }
|
| +
|
| + public boolean isOriginalName() {
|
| + try {
|
| + String newName = fNamePosition.getContent();
|
| + return fOriginalName.equals(newName);
|
| + } catch (BadLocationException e) {
|
| + return false;
|
| + }
|
| + }
|
| +
|
| + public void start() {
|
| + if (getActiveLinkedMode() != null) {
|
| + // for safety; should already be handled in RenameDartElementAction
|
| + fgActiveLinkedMode.startFullDialog();
|
| + return;
|
| + }
|
| +
|
| + ISourceViewer viewer = fEditor.getViewer();
|
| + IDocument document = viewer.getDocument();
|
| + fOriginalSelection = viewer.getSelectedRange();
|
| + int offset = fOriginalSelection.x;
|
| +
|
| + try {
|
| + DartUnit root = ASTProvider.getASTProvider().getAST(getCompilationUnit(),
|
| + ASTProvider.WAIT_YES, null);
|
| +
|
| + fLinkedPositionGroup = new LinkedPositionGroup();
|
| + DartNode selectedNode = NodeFinder.perform(root, fOriginalSelection.x, fOriginalSelection.y);
|
| + if (selectedNode instanceof DartVariable) {
|
| + selectedNode = ((DartVariable) selectedNode).getName();
|
| + }
|
| + if (!(selectedNode instanceof DartIdentifier)) {
|
| + return; // TODO: show dialog
|
| + }
|
| + DartIdentifier nameNode = (DartIdentifier) selectedNode;
|
| +
|
| + if (viewer instanceof ITextViewerExtension6) {
|
| + IUndoManager undoManager = ((ITextViewerExtension6) viewer).getUndoManager();
|
| + if (undoManager instanceof IUndoManagerExtension) {
|
| + IUndoManagerExtension undoManagerExtension = (IUndoManagerExtension) undoManager;
|
| + IUndoContext undoContext = undoManagerExtension.getUndoContext();
|
| + IOperationHistory operationHistory = OperationHistoryFactory.getOperationHistory();
|
| + fStartingUndoOperation = operationHistory.getUndoOperation(undoContext);
|
| + }
|
| + }
|
| +
|
| + fOriginalName = nameNode.getName();
|
| + final int pos = nameNode.getSourceInfo().getOffset();
|
| + DartNode[] sameNodes = LinkedNodeFinder.findByNode(root, nameNode);
|
| +
|
| + //TODO: copied from LinkedNamesAssistProposal#apply(..):
|
| + // sort for iteration order, starting with the node @ offset
|
| + Arrays.sort(sameNodes, new Comparator<DartNode>() {
|
| + @Override
|
| + public int compare(DartNode o1, DartNode o2) {
|
| + return rank(o1) - rank(o2);
|
| + }
|
| +
|
| + /**
|
| + * Returns the absolute rank of an <code>DartNode</code>. Nodes preceding <code>pos</code>
|
| + * are ranked last.
|
| + *
|
| + * @param node the node to compute the rank for
|
| + * @return the rank of the node with respect to the invocation offset
|
| + */
|
| + private int rank(DartNode node) {
|
| + int relativeRank = node.getSourceInfo().getOffset() + node.getSourceInfo().getLength()
|
| + - pos;
|
| + if (relativeRank < 0) {
|
| + return Integer.MAX_VALUE + relativeRank;
|
| + } else {
|
| + return relativeRank;
|
| + }
|
| + }
|
| + });
|
| + for (int i = 0; i < sameNodes.length; i++) {
|
| + DartNode elem = sameNodes[i];
|
| + LinkedPosition linkedPosition = new LinkedPosition(document,
|
| + elem.getSourceInfo().getOffset(), elem.getSourceInfo().getLength(), i);
|
| + if (i == 0) {
|
| + fNamePosition = linkedPosition;
|
| + }
|
| + fLinkedPositionGroup.addPosition(linkedPosition);
|
| + }
|
| +
|
| + fLinkedModeModel = new LinkedModeModel();
|
| + fLinkedModeModel.addGroup(fLinkedPositionGroup);
|
| + fLinkedModeModel.forceInstall();
|
| + fLinkedModeModel.addLinkingListener(new EditorHighlightingSynchronizer(fEditor));
|
| + fLinkedModeModel.addLinkingListener(new EditorSynchronizer());
|
| +
|
| + LinkedModeUI ui = new EditorLinkedModeUI(fLinkedModeModel, viewer);
|
| + ui.setExitPosition(viewer, offset, 0, Integer.MAX_VALUE);
|
| + ui.setExitPolicy(new ExitPolicy(document));
|
| + ui.enter();
|
| +
|
| + viewer.setSelectedRange(fOriginalSelection.x, fOriginalSelection.y); // by default, full word is selected; restore original selection
|
| +
|
| + if (viewer instanceof IEditingSupportRegistry) {
|
| + IEditingSupportRegistry registry = (IEditingSupportRegistry) viewer;
|
| + registry.register(fFocusEditingSupport);
|
| + }
|
| +
|
| + openSecondaryPopup();
|
| +// startAnimation();
|
| + fgActiveLinkedMode = this;
|
| +
|
| + } catch (BadLocationException e) {
|
| + DartToolsPlugin.log(e);
|
| + }
|
| + }
|
| +
|
| + public void startFullDialog() {
|
| + cancel();
|
| +
|
| + try {
|
| + String newName = fNamePosition.getContent();
|
| + RenameSupport renameSupport = undoAndCreateRenameSupport(newName);
|
| + if (renameSupport != null) {
|
| + renameSupport.openDialog(fEditor.getSite().getShell());
|
| + }
|
| + } catch (CoreException e) {
|
| + DartToolsPlugin.log(e);
|
| + } catch (BadLocationException e) {
|
| + DartToolsPlugin.log(e);
|
| + }
|
| + }
|
| +
|
| + void doRename(boolean showPreview) {
|
| + cancel();
|
| +
|
| + Image image = null;
|
| + Label label = null;
|
| +
|
| + fShowPreview |= showPreview;
|
| + try {
|
| + ISourceViewer viewer = fEditor.getViewer();
|
| + if (viewer instanceof SourceViewer) {
|
| + SourceViewer sourceViewer = (SourceViewer) viewer;
|
| + Control viewerControl = sourceViewer.getControl();
|
| + if (viewerControl instanceof Composite) {
|
| + Composite composite = (Composite) viewerControl;
|
| + Display display = composite.getDisplay();
|
| +
|
| + // Flush pending redraw requests:
|
| + while (!display.isDisposed() && display.readAndDispatch()) {
|
| + }
|
| +
|
| + // Copy editor area:
|
| + GC gc = new GC(composite);
|
| + Point size;
|
| + try {
|
| + size = composite.getSize();
|
| + image = new Image(gc.getDevice(), size.x, size.y);
|
| + gc.copyArea(image, 0, 0);
|
| + } finally {
|
| + gc.dispose();
|
| + gc = null;
|
| + }
|
| +
|
| + // Persist editor area while executing refactoring:
|
| + label = new Label(composite, SWT.NONE);
|
| + label.setImage(image);
|
| + label.setBounds(0, 0, size.x, size.y);
|
| + label.moveAbove(null);
|
| + }
|
| + }
|
| +
|
| + String newName = fNamePosition.getContent();
|
| + if (fOriginalName.equals(newName)) {
|
| + return;
|
| + }
|
| + RenameSupport renameSupport = undoAndCreateRenameSupport(newName);
|
| + if (renameSupport == null) {
|
| + return;
|
| + }
|
| +
|
| + Shell shell = fEditor.getSite().getShell();
|
| + boolean executed;
|
| + if (fShowPreview) { // could have been updated by undoAndCreateRenameSupport(..)
|
| + executed = renameSupport.openDialog(shell, true);
|
| + } else {
|
| + renameSupport.perform(shell, fEditor.getSite().getWorkbenchWindow());
|
| + executed = true;
|
| + }
|
| + if (executed) {
|
| + restoreFullSelection();
|
| + }
|
| + DartModelUtil.reconcile(getCompilationUnit());
|
| + } catch (CoreException ex) {
|
| + DartToolsPlugin.log(ex);
|
| + } catch (InterruptedException ex) {
|
| + // canceling is OK -> redo text changes in that case?
|
| + } catch (InvocationTargetException ex) {
|
| + DartToolsPlugin.log(ex);
|
| + } catch (BadLocationException e) {
|
| + DartToolsPlugin.log(e);
|
| + } finally {
|
| + if (label != null) {
|
| + label.dispose();
|
| + }
|
| + if (image != null) {
|
| + image.dispose();
|
| + }
|
| + }
|
| + }
|
| +
|
| + private CompilationUnit getCompilationUnit() {
|
| + return (CompilationUnit) EditorUtility.getEditorInputDartElement(fEditor, false);
|
| + }
|
| +
|
| + private void linkedModeLeft() {
|
| + fgActiveLinkedMode = null;
|
| + if (fInfoPopup != null) {
|
| + fInfoPopup.close();
|
| + }
|
| +
|
| + ISourceViewer viewer = fEditor.getViewer();
|
| + if (viewer instanceof IEditingSupportRegistry) {
|
| + IEditingSupportRegistry registry = (IEditingSupportRegistry) viewer;
|
| + registry.unregister(fFocusEditingSupport);
|
| + }
|
| + }
|
| +
|
| + private void openSecondaryPopup() {
|
| + fInfoPopup = new RenameInformationPopup(fEditor, this);
|
| + fInfoPopup.open();
|
| + }
|
| +
|
| + private void restoreFullSelection() {
|
| + if (fOriginalSelection.y != 0) {
|
| + int originalOffset = fOriginalSelection.x;
|
| + LinkedPosition[] positions = fLinkedPositionGroup.getPositions();
|
| + for (int i = 0; i < positions.length; i++) {
|
| + LinkedPosition position = positions[i];
|
| + if (!position.isDeleted() && position.includes(originalOffset)) {
|
| + fEditor.getViewer().setSelectedRange(position.offset, position.length);
|
| + return;
|
| + }
|
| + }
|
| + }
|
| + }
|
| +
|
| + private RenameSupport undoAndCreateRenameSupport(String newName) throws CoreException {
|
| + // Assumption: the linked mode model should be shut down by now.
|
| +
|
| + final ISourceViewer viewer = fEditor.getViewer();
|
| +
|
| + try {
|
| + if (!fOriginalName.equals(newName)) {
|
| + fEditor.getSite().getWorkbenchWindow().run(false, true, new IRunnableWithProgress() {
|
| + @Override
|
| + public void run(IProgressMonitor monitor) throws InvocationTargetException,
|
| + InterruptedException {
|
| + if (viewer instanceof ITextViewerExtension6) {
|
| + IUndoManager undoManager = ((ITextViewerExtension6) viewer).getUndoManager();
|
| + if (undoManager instanceof IUndoManagerExtension) {
|
| + IUndoManagerExtension undoManagerExtension = (IUndoManagerExtension) undoManager;
|
| + IUndoContext undoContext = undoManagerExtension.getUndoContext();
|
| + IOperationHistory operationHistory = OperationHistoryFactory.getOperationHistory();
|
| + while (undoManager.undoable()) {
|
| + if (fStartingUndoOperation != null
|
| + && fStartingUndoOperation.equals(operationHistory.getUndoOperation(undoContext))) {
|
| + return;
|
| + }
|
| + undoManager.undo();
|
| + }
|
| + }
|
| + }
|
| + }
|
| + });
|
| + }
|
| + } catch (InvocationTargetException e) {
|
| + throw new CoreException(new Status(IStatus.ERROR, DartToolsPlugin.getPluginId(),
|
| + ReorgMessages.RenameLinkedMode_error_saving_editor, e));
|
| + } catch (InterruptedException e) {
|
| + // canceling is OK
|
| + return null;
|
| + } finally {
|
| + DartModelUtil.reconcile(getCompilationUnit());
|
| + }
|
| +
|
| + viewer.setSelectedRange(fOriginalSelection.x, fOriginalSelection.y);
|
| +
|
| + if (newName.length() == 0) {
|
| + return null;
|
| + }
|
| +
|
| +// RenameDartElementDescriptor descriptor = createRenameDescriptor(fDartElement, newName);
|
| +// RenameSupport renameSupport = RenameSupport.create(descriptor);
|
| + return RenameSupport.create((DartVariableDeclaration) fDartElement, newName,
|
| + RenameSupport.UPDATE_REFERENCES);
|
| + }
|
| +
|
| +}
|
|
|