/** * Copyright (c) 2005-2011 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.jdt.ui.actions.OpenAction; import org.eclipse.jface.action.IAction; import org.eclipse.jface.dialogs.ErrorDialog; import org.eclipse.jface.dialogs.MessageDialog; 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.jface.viewers.StructuredSelection; import org.eclipse.swt.graphics.Image; 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.core.parser.IParserObserver; import org.python.pydev.core.parser.ISimpleNode; 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.codecompletion.revisited.PythonPathHelper; import org.python.pydev.editor.codecompletion.revisited.javaintegration.AbstractJavaClassModule; import org.python.pydev.editor.codecompletion.revisited.javaintegration.JavaDefinition; 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; /** * 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. */ public void parserChanged(ISimpleNode root, IAdaptable file, IDocument doc) { editToReparse.getParser().removeParseListener(this); //we'll only listen for this single parse doFindIfLast(); } /** * We want to work in the event of parse errors too. */ 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 */ 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 = 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() { public void run() { ElementListSelectionDialog dialog = new ElementListSelectionDialog(shell, new ILabelProvider() { public Image getImage(Object element) { return PyCodeCompletionImages.getImageForType(IToken.TYPE_PACKAGE); } 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; } public void addListener(ILabelProviderListener listener) { } public void dispose() { } public boolean isLabelProperty(Object element, String property) { return false; } public void removeListener(ILabelProviderListener listener) { } }); 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) { File f = (File) itemPointer.file; String filename = f.getName(); if (PythonPathHelper.isValidSourceFile(filename) || filename.indexOf('.') == -1 || //treating files without any extension! (itemPointer.zipFilePath != null && PythonPathHelper.isValidSourceFile(itemPointer.zipFilePath))) { final PyOpenAction openAction = (PyOpenAction) pyEdit.getAction(PyEdit.ACTION_OPEN); openAction.run(itemPointer); } else if (itemPointer.definition instanceof JavaDefinition) { //note that it will only be able to find a java definition if JDT is actually available //so, we don't have to care about JDTNotAvailableExceptions here. JavaDefinition javaDefinition = (JavaDefinition) itemPointer.definition; OpenAction openAction = new OpenAction(pyEdit.getSite()); StructuredSelection selection = new StructuredSelection(new Object[] { javaDefinition.javaElement }); openAction.run(selection); } else { String message; if (itemPointer.definition != null && itemPointer.definition.module instanceof AbstractJavaClassModule) { AbstractJavaClassModule module = (AbstractJavaClassModule) itemPointer.definition.module; message = "The definition was found at: " + f.toString() + "\n" + "as the java module: " + module.getName(); } else { message = "The definition was found at: " + f.toString() + "\n" + "(which cannot be opened because it is a compiled extension)"; } MessageDialog.openInformation(shell, "Compiled Extension file", message); } } /** * @return an array of ItemPointer with the definitions found * @throws MisconfigurationException * @throws TooManyMatchesException */ public ItemPointer[] findDefinition(PyEdit pyEdit) throws TooManyMatchesException, MisconfigurationException { IPyRefactoring pyRefactoring = AbstractPyRefactoring.getPyRefactoring(); return pyRefactoring.findDefinition(getRefactoringRequest()); } /** * As we're not using the default refactoring cycle, this method is not even called */ protected String perform(IAction action, IProgressMonitor monitor) throws Exception { return null; } }