/*******************************************************************************
* Copyright (c) 2005, 2015 Zend Technologies and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Zend Technologies - initial API and implementation
*******************************************************************************/
package org.eclipse.php.refactoring.ui.rename;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Comparator;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.Assert;
import org.eclipse.dltk.core.DLTKCore;
import org.eclipse.dltk.core.IModelElement;
import org.eclipse.dltk.core.ISourceModule;
import org.eclipse.dltk.core.ScriptModelUtil;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.text.*;
import org.eclipse.jface.text.link.ILinkedModeListener;
import org.eclipse.jface.text.link.LinkedModeModel;
import org.eclipse.jface.text.link.LinkedModeUI.ExitFlags;
import org.eclipse.jface.text.link.LinkedModeUI.IExitPolicy;
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.php.core.ast.nodes.*;
import org.eclipse.php.core.compiler.ast.nodes.NamespaceReference;
import org.eclipse.php.internal.core.ast.locator.PHPElementConciliator;
import org.eclipse.php.internal.core.corext.dom.NodeFinder;
import org.eclipse.php.internal.core.search.IOccurrencesFinder.OccurrenceLocation;
import org.eclipse.php.internal.ui.editor.EditorHighlightingSynchronizer;
import org.eclipse.php.internal.ui.editor.PHPStructuredEditor;
import org.eclipse.php.internal.ui.editor.PHPStructuredTextViewer;
import org.eclipse.php.refactoring.core.LinkedNodeFinder;
import org.eclipse.php.refactoring.core.utils.ASTUtils;
import org.eclipse.php.refactoring.ui.RefactoringUIPlugin;
import org.eclipse.php.refactoring.ui.utils.PHPConventionsUtil;
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.*;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IURIEditorInput;
import org.eclipse.ui.texteditor.link.EditorLinkedModeUI;
public class RenameLinkedMode {
private class FocusEditingSupport implements IEditingSupport {
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;
}
public boolean isOriginator(DocumentEvent event, IRegion subjectRegion) {
return false; // leave on external modification outside positions
}
}
private class EditorSynchronizer implements ILinkedModeListener {
public void left(LinkedModeModel model, int flags) {
linkedModeLeft();
if ((flags & ILinkedModeListener.UPDATE_CARET) != 0) {
doRename(fShowPreview);
}
}
public void resume(LinkedModeModel model, int flags) {
}
public void suspend(LinkedModeModel model) {
}
}
private class ExitPolicy implements IExitPolicy {
private IDocument fDocument;
public ExitPolicy(IDocument document) {
fDocument = document;
}
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);
if (length == 0 && (event.character == SWT.BS || event.character == SWT.DEL)) {
LinkedPosition position = model
.findPosition(new PHPElementLinkedPosition(fDocument, offset, 0, LinkedPositionGroup.NO_STOP));
if (position != null) {
if (event.character == SWT.BS) {
if (offset - 1 < position.getOffset()) {
// skip backspace at beginning of linked position
event.doit = false;
}
} else /* event.character == SWT.DEL */ {
if (offset + 1 > position.getOffset() + position.getLength()) {
// skip delete at end of linked position
event.doit = false;
}
}
}
}
return null; // don't change behavior
}
}
private static RenameLinkedMode fgActiveLinkedMode;
private final PHPStructuredEditor fEditor;
// private final IModelElement fJavaElement;
private RenameInformationPopup fInfoPopup;
private Point fOriginalSelection;
private String fOriginalName;
private PHPElementLinkedPosition fNamePosition;
private LinkedModeModel fLinkedModeModel;
private LinkedPositionGroup fLinkedPositionGroup;
private final FocusEditingSupport fFocusEditingSupport;
private boolean fShowPreview;
// private Program root;
private ASTNode selectedNode;
public RenameLinkedMode(IModelElement element, PHPStructuredEditor editor) {
Assert.isNotNull(editor);
fEditor = editor;
// fJavaElement = element;
fFocusEditingSupport = new FocusEditingSupport();
}
public static RenameLinkedMode getActiveLinkedMode() {
if (fgActiveLinkedMode != null) {
ISourceViewer viewer = fgActiveLinkedMode.fEditor.getTextViewer();
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;
}
public void start() {
if (getActiveLinkedMode() != null) {
// for safety; should already be handled in RenameJavaElementAction
fgActiveLinkedMode.startFullDialog();
return;
}
TextViewer viewer = fEditor.getTextViewer();
IDocument document = viewer.getDocument();
fOriginalSelection = viewer.getSelectedRange();
int offset = fOriginalSelection.x;
IEditorInput input = fEditor.getEditorInput();
// Not applicable to other editor input.
// E.g. external file.
if (!(input instanceof IFileEditorInput)) {
if (input instanceof IURIEditorInput) {
MessageDialog.openError(fEditor.getEditorSite().getShell(), Messages.RenameLinkedMode_1,
Messages.RenameLinkedMode_3);
} else {
MessageDialog.openError(fEditor.getEditorSite().getShell(), Messages.RenameLinkedMode_1,
Messages.RenameLinkedMode_4);
}
return;
}
IFile file = ((IFileEditorInput) input).getFile();
ISourceModule sourceModule = DLTKCore.createSourceModuleFrom(file);
if (sourceModule == null) {
MessageDialog.openError(fEditor.getEditorSite().getShell(), Messages.RenameLinkedMode_1,
Messages.RenameLinkedMode_2);
return;
}
try {
Program root = ASTUtils.createProgramFromSource(sourceModule);
fLinkedPositionGroup = new LinkedPositionGroup();
selectedNode = NodeFinder.perform(root, fOriginalSelection.x, fOriginalSelection.y);
if (selectedNode == null) {
MessageDialog.openError(fEditor.getEditorSite().getShell(), Messages.RenameLinkedMode_1,
Messages.RenameLinkedMode_4);
return;
}
if (((ASTNode) selectedNode).getParent() instanceof NamespaceName) {
NamespaceName namespaceName = (NamespaceName) ((ASTNode) selectedNode).getParent();
if (namespaceName.segments() != null && namespaceName.segments().size() > 0) {
if (!namespaceName.segments().get(namespaceName.segments().size() - 1).equals(selectedNode)) {
MessageDialog.openError(fEditor.getEditorSite().getShell(), Messages.RenameLinkedMode_1,
Messages.RenameLinkedMode_4);
return;
}
}
}
fOriginalName = getCurrentElementName(selectedNode);
final int pos = selectedNode.getStart();
OccurrenceLocation[] sameNodes = LinkedNodeFinder.findByNode(root, selectedNode);
// TODO: copied from LinkedNamesAssistProposal#apply(..):
// sort for iteration order, starting with the node @ offset
Arrays.sort(sameNodes, new Comparator<OccurrenceLocation>() {
public int compare(OccurrenceLocation o1, OccurrenceLocation o2) {
return rank(o1) - rank(o2);
}
/**
* Returns the absolute rank of an <code>ASTNode</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(OccurrenceLocation node) {
int relativeRank = node.getOffset() + node.getLength() - pos;
if (relativeRank < 0)
return Integer.MAX_VALUE + relativeRank;
else
return relativeRank;
}
});
String name = ""; //$NON-NLS-1$
for (int i = 0; i < sameNodes.length; i++) {
OccurrenceLocation elem = sameNodes[i];
PHPElementLinkedPosition linkedPosition = null;
String oriName = document.get(elem.getOffset(), elem.getLength());
int index = oriName.lastIndexOf(NamespaceReference.NAMESPACE_SEPARATOR);
if (index > 0) {
linkedPosition = new PHPElementLinkedPosition(document, elem.getOffset() + (index + 1),
elem.getLength() - (index + 1), i);
} else if (index == 0) {
linkedPosition = new PHPElementLinkedPosition(document, elem.getOffset() + (index + 1),
elem.getLength() - (index + 1), i);
} else {
linkedPosition = new PHPElementLinkedPosition(document, elem.getOffset(), elem.getLength(), i);
}
if (i == 0) {
fNamePosition = linkedPosition;
name = getName(linkedPosition);
}
// make sure the name of the locations are the same.
if (isSameString(linkedPosition, name)) {
fLinkedPositionGroup.addPosition(linkedPosition);
}
}
fLinkedModeModel = new LinkedModeModel();
fLinkedModeModel.addGroup(fLinkedPositionGroup);
fLinkedModeModel.forceInstall();
fLinkedModeModel.addLinkingListener(new EditorHighlightingSynchronizer(fEditor));
fLinkedModeModel.addLinkingListener(new EditorSynchronizer());
EditorLinkedModeUI ui = new EditorLinkedModeUI(fLinkedModeModel, viewer);
ui.setExitPosition(viewer, offset, 0, Integer.MAX_VALUE);
ui.setExitPolicy(new ExitPolicy(document));
((PHPStructuredTextViewer) viewer).setFireSelectionChanged(false);
ui.enter();
String selectedText = ""; //$NON-NLS-1$
if (fOriginalSelection.y == 0) {
// place the cursor under the name
selectedText = name;
} else {
selectedText = viewer.getTextWidget().getText(fOriginalSelection.x,
fOriginalSelection.x + fOriginalSelection.y - 1);
}
if (selectedText.startsWith("$")) { //$NON-NLS-1$
viewer.setSelectedRange(fOriginalSelection.x + 1, fOriginalSelection.y - 1);
} else {
viewer.setSelectedRange(fOriginalSelection.x, fOriginalSelection.y); // by
// default,full
// word
// is
// selected;
// restore
// original
// selection
}
IEditingSupportRegistry registry = viewer;
registry.register(fFocusEditingSupport);
openSecondaryPopup();
// startAnimation();
fgActiveLinkedMode = this;
} catch (Exception e) {
MessageDialog.openError(fEditor.getEditorSite().getShell(), Messages.RenameLinkedMode_1,
Messages.RenameLinkedMode_4);
}
}
private boolean isSameString(PHPElementLinkedPosition linkedPosition, String name) throws BadLocationException {
return name.equals(getName(linkedPosition));
}
private String getName(PHPElementLinkedPosition linkedPosition) throws BadLocationException {
IDocument document = fEditor.getDocument();
String name = document.get(linkedPosition.offset, linkedPosition.length);
return name;
}
void doRename(boolean showPreview) {
cancel();
Image image = null;
Label label = null;
fShowPreview |= showPreview;
try {
SourceViewer sourceViewer = fEditor.getTextViewer();
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);
} else {
renameSupport.perform(shell, fEditor.getSite().getWorkbenchWindow());
executed = true;
}
if (executed) {
restoreFullSelection();
}
IFile file = ((IFileEditorInput) fEditor.getEditorInput()).getFile();
ISourceModule sourceModule = DLTKCore.createSourceModuleFrom(file);
Program program = ASTUtils.createProgramFromSource(sourceModule);
ScriptModelUtil.reconcile(sourceModule);
fEditor.reconciled(program, true, new NullProgressMonitor());
} catch (Exception e) {
// TODO: handle the errors.
} finally {
if (label != null)
label.dispose();
if (image != null)
image.dispose();
}
}
public void cancel() {
if (fLinkedModeModel != null) {
fLinkedModeModel.exit(ILinkedModeListener.NONE);
}
linkedModeLeft();
}
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.getTextViewer().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.getTextViewer();
try {
if (!fOriginalName.equals(newName)) {
fEditor.getSite().getWorkbenchWindow().run(false, true, new IRunnableWithProgress() {
public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
if (viewer instanceof ITextViewerExtension6) {
IUndoManager undoManager = ((ITextViewerExtension6) viewer).getUndoManager();
if (undoManager != null && undoManager.undoable()) {
undoManager.undo();
}
}
}
});
}
} catch (InvocationTargetException e) {
throw new CoreException(
new Status(IStatus.ERROR, RefactoringUIPlugin.PLUGIN_ID, Messages.RenameLinkedMode_0, e));
} catch (InterruptedException e) {
// canceling is OK
return null;
} finally {
IFile file = ((IFileEditorInput) fEditor.getEditorInput()).getFile();
ISourceModule sourceModule = DLTKCore.createSourceModuleFrom(file);
try {
Program program = ASTUtils.createProgramFromSource(sourceModule);
ScriptModelUtil.reconcile(sourceModule);
fEditor.reconciled(program, true, new NullProgressMonitor());
} catch (Exception e) {
}
}
viewer.setSelectedRange(fOriginalSelection.x, fOriginalSelection.y);
final int elementType = PHPElementConciliator.concile(selectedNode);
RenameSupport renameSupport = RenameSupport.create(
selectedNode.getProgramRoot().getSourceModule().getResource(), elementType, selectedNode, newName,
RenameSupport.UPDATE_REFERENCES);
return renameSupport;
}
public void startFullDialog() {
cancel();
try {
String newName = fNamePosition.getContent();
RenameSupport renameSupport = undoAndCreateRenameSupport(newName);
if (renameSupport != null)
renameSupport.openDialog(fEditor.getSite().getShell());
} catch (CoreException e) {
} catch (BadLocationException e) {
}
}
private void linkedModeLeft() {
fgActiveLinkedMode = null;
if (fInfoPopup != null) {
fInfoPopup.close();
}
TextViewer viewer = fEditor.getTextViewer();
viewer.unregister(fFocusEditingSupport);
((PHPStructuredTextViewer) viewer).setFireSelectionChanged(true);
}
private void openSecondaryPopup() {
fInfoPopup = new RenameInformationPopup(fEditor, this);
fInfoPopup.open();
}
public boolean isCaretInLinkedPosition() {
return getCurrentLinkedPosition() != null;
}
public LinkedPosition getCurrentLinkedPosition() {
Point selection = fEditor.getTextViewer().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 isEnabled() {
try {
String newName = fNamePosition.getContent();
if (fOriginalName.equals(newName))
return false;
return PHPConventionsUtil.validateIdentifier(newName);
} catch (BadLocationException e) {
return false;
}
}
public boolean isOriginalName() {
try {
String newName = fNamePosition.getContent();
return fOriginalName.equals(newName);
} catch (BadLocationException e) {
return false;
}
}
public String getCurrentElementName(ASTNode identifier) {
if (identifier instanceof Variable) {
Identifier id = (Identifier) ((Variable) identifier).getName();
return id.getName();
}
if (identifier instanceof Identifier) {
return ((Identifier) identifier).getName();
}
if (identifier.getType() == ASTNode.SCALAR) {
final String stringValue = ((Scalar) identifier).getStringValue();
if (((Scalar) identifier).getStringValue().charAt(0) == '"') {
return stringValue.substring(1, stringValue.length() - 1);
} else {
return stringValue;
}
}
return identifier.toString();
}
}