com.google.dart.tools.ui.internal.refactoring.reorg.RenameLinkedMode.java Source code

Java tutorial

Introduction

Here is the source code for com.google.dart.tools.ui.internal.refactoring.reorg.RenameLinkedMode.java

Source

/*
 * Copyright (c) 2012, the Dart project authors.
 * 
 * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 * 
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
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.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.internal.corext.dom.LinkedNodeFinder;
import com.google.dart.tools.internal.corext.refactoring.RefactoringExecutionStarter;
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;

/**
 * @coverage dart.editor.ui.refactoring.ui
 */
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 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();
            if (renameSupport.hasUnresolvedNameReferences()) {
                fShowPreview = true;
            }
            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;
        }

        return RefactoringExecutionStarter.createRenameSupport(fDartElement, newName, 0);
    }

}