/** * Copyright (c) 2005-2013 by Appcelerator, Inc. All Rights Reserved. * Licensed under the terms of the Eclipse Public License (EPL). * Please see the license.txt included with this distribution for details. * Any modifications to this file must keep this entire header intact. */ /* * Created on May 21, 2004 * */ package com.python.pydev.refactoring.actions; import java.io.File; import java.util.HashSet; import java.util.Set; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.action.IAction; import org.eclipse.jface.dialogs.ErrorDialog; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IDocumentExtension4; import org.eclipse.jface.viewers.ILabelProvider; import org.eclipse.jface.viewers.ILabelProviderListener; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.dialogs.ElementListSelectionDialog; import org.eclipse.ui.progress.UIJob; import org.python.pydev.core.IToken; import org.python.pydev.core.MisconfigurationException; import org.python.pydev.core.docutils.PySelection; import org.python.pydev.core.log.Log; import org.python.pydev.editor.PyEdit; import org.python.pydev.editor.actions.PyOpenAction; import org.python.pydev.editor.actions.refactoring.PyRefactorAction; import org.python.pydev.editor.codecompletion.PyCodeCompletionImages; import org.python.pydev.editor.model.ItemPointer; import org.python.pydev.editor.refactoring.AbstractPyRefactoring; import org.python.pydev.editor.refactoring.IPyRefactoring; import org.python.pydev.editor.refactoring.RefactoringRequest; import org.python.pydev.editor.refactoring.TooManyMatchesException; import org.python.pydev.parser.PyParser; import org.python.pydev.plugin.PydevPlugin; import org.python.pydev.shared_core.model.ISimpleNode; import org.python.pydev.shared_core.parsing.IParserObserver; import org.python.pydev.shared_ui.EditorUtils; /** * This is a refactoring action, but it does not follow the default cycle -- so, it overrides the run * and always uses the same cycle... because in this case, we do not need any additional information * before starting the refactoring... the go to definition always only depends upon the current * selected text -- and if more than 1 match is found, it asks the user to select the one that * is more likely the match) * * @author Fabio Zadrozny */ public class PyGoToDefinition extends PyRefactorAction { /** * We do some additional checking because the default backend * @return true if the conditions are ok and false otherwise */ protected boolean areRefactorPreconditionsOK(RefactoringRequest request) { // we're working with dirty editors now (through pushTemporaryModule/popTemporaryModule) // // if (request.pyEdit.isDirty()){ // request.pyEdit.doSave(null); // } return true; } /** * This class makes the parse when all reparses have finished. */ private class FindParserObserver implements IParserObserver { /** * Lock for accessing askReparse. */ private Object lock; /** * A set with all the reparses asked. When all finish, we'll do the find. */ private Set<PyEdit> askReparse; /** * This is the editor which this action is listening in the reparse (will remove it from * askReparse and when empty, will proceed to do the find). */ private PyEdit editToReparse; public FindParserObserver(PyEdit editToReparse, Set<PyEdit> askReparse, Object lock) { this.editToReparse = editToReparse; this.askReparse = askReparse; this.lock = lock; } /** * As soon as the reparse is done, this method is called. */ @Override public void parserChanged(ISimpleNode root, IAdaptable file, IDocument doc, long docModificationStamp) { editToReparse.getParser().removeParseListener(this); //we'll only listen for this single parse doFindIfLast(); } /** * We want to work in the event of parse errors too. */ @Override public void parserError(Throwable error, IAdaptable file, IDocument doc) { editToReparse.getParser().removeParseListener(this); //we'll only listen for this single parse doFindIfLast(); } /** * Remove the editor from askReparse and if it's the last one, do the find. */ private void doFindIfLast() { synchronized (lock) { askReparse.remove(editToReparse); if (askReparse.size() > 0) { return; //not the last one (we'll only do the find when all are reparsed. } } /** * Create an ui job to actually make the find. */ UIJob job = new UIJob("Find") { @Override public IStatus runInUIThread(IProgressMonitor monitor) { try { findDefinitionsAndOpen(true); } catch (Throwable e) { Log.log(e); } return Status.OK_STATUS; } }; job.setPriority(Job.INTERACTIVE); job.schedule(); } } /** * Overrides the run and calls -- and the whole default refactoring cycle from the beggining, * because unlike most refactoring operations, this one can work with dirty editors. * @return */ @Override public void run(IAction action) { workbenchWindow = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); IEditorPart[] dirtyEditors = workbenchWindow.getActivePage().getDirtyEditors(); Set<PyEdit> askReparse = new HashSet<PyEdit>(); for (IEditorPart iEditorPart : dirtyEditors) { if (iEditorPart instanceof PyEdit) { PyEdit pyEdit = (PyEdit) iEditorPart; long astModificationTimeStamp = pyEdit.getAstModificationTimeStamp(); IDocument doc = pyEdit.getDocument(); if (astModificationTimeStamp != -1 && astModificationTimeStamp == (((IDocumentExtension4) doc).getModificationStamp())) { //All OK, the ast is synched! } else { askReparse.add(pyEdit); } } } if (askReparse.size() == 0) { findDefinitionsAndOpen(true); } else { //We don't have a match: ask for a reparse Object lock = new Object(); for (PyEdit pyEdit : askReparse) { IParserObserver observer = new FindParserObserver(pyEdit, askReparse, lock); PyParser parser = pyEdit.getParser(); parser.addParseListener(observer); //it will analyze when the next parse is finished parser.forceReparse(); } } } public ItemPointer[] findDefinitionsAndOpen(boolean doOpenDefinition) { request = null; ps = new PySelection(getTextEditor()); final PyEdit pyEdit = getPyEdit(); RefactoringRequest refactoringRequest; try { refactoringRequest = getRefactoringRequest(); } catch (MisconfigurationException e1) { Log.log(e1); return new ItemPointer[0]; } final Shell shell = EditorUtils.getShell(); try { if (areRefactorPreconditionsOK(refactoringRequest)) { ItemPointer[] defs = findDefinition(pyEdit); if (doOpenDefinition) { openDefinition(defs, pyEdit, shell); } return defs; } } catch (Exception e) { Log.log(e); String msg = e.getMessage(); if (msg == null) { msg = "Unable to get error msg (null)"; } ErrorDialog.openError(shell, "Error", "Unable to do requested action", new Status(Status.ERROR, PydevPlugin.getPluginID(), 0, msg, e)); } return null; } /** * Opens a given definition directly or asks the user to choose one of the passed definitions * * @param defs item pointers with the definitions available for opening. * @param pyEdit pyedit where the action to open the definition was started * @param shell the shell to be used to show dialogs */ public static void openDefinition(ItemPointer[] defs, final PyEdit pyEdit, final Shell shell) { if (defs == null) { shell.getDisplay().beep(); return; } HashSet<ItemPointer> set = new HashSet<ItemPointer>(); for (ItemPointer pointer : defs) { if (pointer.file != null) { set.add(pointer); } } final ItemPointer[] where = set.toArray(new ItemPointer[0]); if (where == null) { shell.getDisplay().beep(); return; } if (where.length > 0) { if (where.length == 1) { ItemPointer itemPointer = where[0]; doOpen(itemPointer, pyEdit, shell); } else { //the user has to choose which is the correct definition... final Display disp = shell.getDisplay(); disp.syncExec(new Runnable() { @Override public void run() { ElementListSelectionDialog dialog = new ElementListSelectionDialog(shell, new ILabelProvider() { @Override public Image getImage(Object element) { return PyCodeCompletionImages.getImageForType(IToken.TYPE_PACKAGE); } @Override public String getText(Object element) { ItemPointer pointer = (ItemPointer) element; File f = (File) (pointer).file; int line = pointer.start.line; return f.getName() + " (" + f.getParent() + ") - line:" + line; } @Override public void addListener(ILabelProviderListener listener) { } @Override public void dispose() { } @Override public boolean isLabelProperty(Object element, String property) { return false; } @Override public void removeListener(ILabelProviderListener listener) { } }) { @Override protected Control createContents(Composite parent) { Control ret = super.createContents(parent); org.python.pydev.plugin.PydevPlugin .setCssId(parent, "py-go-to-definition-dialog", true); return ret; } @Override public boolean isHelpAvailable() { return false; } @Override protected void updateStatus(IStatus status) { super.updateStatus(status); PydevPlugin.fixSelectionStatusDialogStatusLineColor(this, this.getDialogArea() .getBackground()); } }; dialog.setTitle("Found matches"); dialog.setTitle("Select the one you believe matches most your search."); dialog.setElements(where); dialog.open(); Object[] result = dialog.getResult(); if (result != null && result.length > 0) { doOpen((ItemPointer) result[0], pyEdit, shell); } } }); } } else { shell.getDisplay().beep(); } } /** * @param itemPointer this is the item pointer that gives the location that should be opened * @param pyEdit the editor (so that we can gen the open action) * @param shell */ private static void doOpen(ItemPointer itemPointer, PyEdit pyEdit, Shell shell) { new PyOpenAction().run(itemPointer, pyEdit.getProject(), pyEdit.getSite()); } /** * @return an array of ItemPointer with the definitions found * @throws MisconfigurationException * @throws TooManyMatchesException * @throws BadLocationException */ public ItemPointer[] findDefinition(PyEdit pyEdit) throws TooManyMatchesException, MisconfigurationException, BadLocationException { IPyRefactoring pyRefactoring = AbstractPyRefactoring.getPyRefactoring(); return pyRefactoring.findDefinition(getRefactoringRequest()); } /** * As we're not using the default refactoring cycle, this method is not even called */ @Override protected String perform(IAction action, IProgressMonitor monitor) throws Exception { return null; } }