/**
* 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 Apr 29, 2006
*/
package com.python.pydev.refactoring.markoccurrences;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.ui.texteditor.IDocumentProvider;
import org.python.pydev.core.MisconfigurationException;
import org.python.pydev.core.docutils.ParsingUtils;
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.refactoring.PyRefactorAction;
import org.python.pydev.editor.codefolding.PySourceViewer;
import org.python.pydev.editor.refactoring.RefactoringRequest;
import org.python.pydev.parser.jython.SimpleNode;
import org.python.pydev.parser.jython.ast.Name;
import org.python.pydev.parser.visitors.scope.ASTEntry;
import org.python.pydev.shared_core.string.TextSelectionUtils;
import org.python.pydev.shared_core.structure.Tuple;
import org.python.pydev.shared_ui.editor.BaseEditor;
import org.python.pydev.shared_ui.mark_occurrences.BaseMarkOccurrencesJob;
import com.python.pydev.refactoring.ui.MarkOccurrencesPreferencesPage;
import com.python.pydev.refactoring.wizards.rename.PyReferenceSearcher;
/**
* This is a 'low-priority' thread. It acts as a singleton. Requests to mark the occurrences
* will be forwarded to it, so, it should sleep for a while and then check for a request.
*
* If the request actually happened, it will go on to process it, otherwise it will sleep some more.
*
* @author Fabio
*/
public class MarkOccurrencesJob extends BaseMarkOccurrencesJob {
private final static class TextBasedLocalMarkOccurrencesRequest extends MarkOccurrencesRequest {
private String currToken;
public TextBasedLocalMarkOccurrencesRequest(String currToken) {
super(true);
this.currToken = currToken;
}
}
private final static class PyMarkOccurrencesRequest extends MarkOccurrencesRequest {
private final RefactoringRequest refactoringRequest;
private final PyReferenceSearcher pyReferenceSearcher;
public PyMarkOccurrencesRequest(boolean proceedWithMarkOccurrences,
RefactoringRequest refactoringRequest,
PyReferenceSearcher pyReferenceSearcher) {
super(proceedWithMarkOccurrences);
this.refactoringRequest = refactoringRequest;
this.pyReferenceSearcher = pyReferenceSearcher;
}
public Set<ASTEntry> getOccurrences() {
return pyReferenceSearcher.getLocalReferences(refactoringRequest);
}
public String getInitialName() {
return refactoringRequest.initialName;
}
}
public MarkOccurrencesJob(WeakReference<BaseEditor> editor, TextSelectionUtils ps) {
super(editor, ps);
}
private static final Set<String> LOCAL_TEXT_SEARCHES_ON = new HashSet<String>();
static {
LOCAL_TEXT_SEARCHES_ON.add("assert");
LOCAL_TEXT_SEARCHES_ON.add("break");
LOCAL_TEXT_SEARCHES_ON.add("continue");
LOCAL_TEXT_SEARCHES_ON.add("del");
LOCAL_TEXT_SEARCHES_ON.add("lambda");
LOCAL_TEXT_SEARCHES_ON.add("nonlocal");
LOCAL_TEXT_SEARCHES_ON.add("global");
LOCAL_TEXT_SEARCHES_ON.add("pass");
LOCAL_TEXT_SEARCHES_ON.add("print");
LOCAL_TEXT_SEARCHES_ON.add("raise");
LOCAL_TEXT_SEARCHES_ON.add("return");
}
/**
* @return a tuple with the refactoring request, the processor and a boolean indicating if all pre-conditions succedded.
* @throws MisconfigurationException
*/
@Override
protected MarkOccurrencesRequest createRequest(BaseEditor baseEditor,
IDocumentProvider documentProvider, IProgressMonitor monitor) throws BadLocationException,
OperationCanceledException, CoreException, MisconfigurationException {
if (!MarkOccurrencesPreferencesPage.useMarkOccurrences()) {
return new PyMarkOccurrencesRequest(false, null, null);
}
PyEdit pyEdit = (PyEdit) baseEditor;
//ok, the editor is still there wit ha document... move on
PyRefactorAction pyRefactorAction = getRefactorAction(pyEdit);
String currToken = this.ps.getCurrToken().o1;
if (LOCAL_TEXT_SEARCHES_ON.contains(currToken) && IDocument.DEFAULT_CONTENT_TYPE
.equals(ParsingUtils.getContentType(this.ps.getDoc(), this.ps.getAbsoluteCursorOffset()))) {
return new TextBasedLocalMarkOccurrencesRequest(currToken);
}
final RefactoringRequest req = getRefactoringRequest(pyEdit, pyRefactorAction,
PySelection.fromTextSelection(this.ps));
if (req == null || !req.nature.getRelatedInterpreterManager().isConfigured()) { //we check if it's configured because it may still be a stub...
return new PyMarkOccurrencesRequest(false, null, null);
}
PyReferenceSearcher searcher = new PyReferenceSearcher(req);
//to see if a new request was not created in the meantime (in which case this one will be cancelled)
if (monitor.isCanceled()) {
return new PyMarkOccurrencesRequest(false, null, null);
}
try {
searcher.prepareSearch(req);
if (monitor.isCanceled()) {
return new PyMarkOccurrencesRequest(false, null, null);
}
searcher.search(req);
if (monitor.isCanceled()) {
return new PyMarkOccurrencesRequest(false, null, null);
}
// Ok, search succeeded.
return new PyMarkOccurrencesRequest(true, req, searcher);
} catch (PyReferenceSearcher.SearchException | BadLocationException e) {
// Suppress search failures.
return new PyMarkOccurrencesRequest(false, null, null);
} catch (Throwable e) {
throw new RuntimeException("Error in occurrences while analyzing modName:" + req.moduleName
+ " initialName:" + req.initialName + " line (start at 0):" + req.ps.getCursorLine(), e);
}
}
/**
* @param markOccurrencesRequest
* @return true if the annotations were removed and added without any problems and false otherwise
*/
@Override
protected synchronized Map<Annotation, Position> getAnnotationsToAddAsMap(final BaseEditor baseEditor,
IAnnotationModel annotationModel, MarkOccurrencesRequest markOccurrencesRequest, IProgressMonitor monitor)
throws BadLocationException {
PyEdit pyEdit = (PyEdit) baseEditor;
PySourceViewer viewer = pyEdit.getPySourceViewer();
if (viewer == null || monitor.isCanceled()) {
return null;
}
if (monitor.isCanceled()) {
return null;
}
if (markOccurrencesRequest instanceof TextBasedLocalMarkOccurrencesRequest) {
TextBasedLocalMarkOccurrencesRequest textualMarkOccurrencesRequest = (TextBasedLocalMarkOccurrencesRequest) markOccurrencesRequest;
PySelection pySelection = PySelection.fromTextSelection(ps);
Tuple<Integer, Integer> startEndLines = pySelection.getCurrentMethodStartEndLines();
int initialOffset = pySelection.getAbsoluteCursorOffset(startEndLines.o1, 0);
int finalOffset = pySelection.getEndLineOffset(startEndLines.o2);
List<IRegion> occurrences = ps.searchOccurrences(textualMarkOccurrencesRequest.currToken);
if (occurrences.size() == 0) {
return null;
}
Map<Annotation, Position> toAddAsMap = new HashMap<Annotation, Position>();
for (Iterator<IRegion> it = occurrences.iterator(); it.hasNext();) {
IRegion iRegion = it.next();
if (iRegion.getOffset() < initialOffset || iRegion.getOffset() > finalOffset) {
continue;
}
try {
Annotation annotation = new Annotation(getOccurrenceAnnotationsType(), false, "occurrence");
Position position = new Position(iRegion.getOffset(), iRegion.getLength());
toAddAsMap.put(annotation, position);
} catch (Exception e) {
Log.log(e);
}
}
return toAddAsMap;
}
PyMarkOccurrencesRequest pyMarkOccurrencesRequest = (PyMarkOccurrencesRequest) markOccurrencesRequest;
Set<ASTEntry> occurrences = pyMarkOccurrencesRequest.getOccurrences();
if (occurrences == null) {
if (DEBUG) {
System.out.println("Occurrences == null");
}
return null;
}
IDocument doc = pyEdit.getDocument();
Map<Annotation, Position> toAddAsMap = new HashMap<Annotation, Position>();
boolean markOccurrencesInStrings = MarkOccurrencesPreferencesPage.useMarkOccurrencesInStrings();
//get the annotations to add
for (ASTEntry entry : occurrences) {
if (!markOccurrencesInStrings) {
if (entry.node instanceof Name) {
Name name = (Name) entry.node;
if (name.ctx == Name.Artificial) {
continue;
}
}
}
SimpleNode node = entry.getNameNode();
IRegion lineInformation = doc.getLineInformation(node.beginLine - 1);
try {
Annotation annotation = new Annotation(getOccurrenceAnnotationsType(), false, "occurrence");
Position position = new Position(lineInformation.getOffset() + node.beginColumn - 1,
pyMarkOccurrencesRequest.getInitialName().length());
toAddAsMap.put(annotation, position);
} catch (Exception e) {
Log.log(e);
}
}
return toAddAsMap;
}
/**
* @param pyEdit the editor where we should look for the occurrences
* @param pyRefactorAction the action that will return the initial refactoring request
* @param ps the pyselection used (if null it will be created in this method)
* @return a refactoring request suitable for finding the locals in the file
* @throws BadLocationException
* @throws MisconfigurationException
*/
public static RefactoringRequest getRefactoringRequest(final PyEdit pyEdit, PyRefactorAction pyRefactorAction,
PySelection ps) throws BadLocationException, MisconfigurationException {
final RefactoringRequest req = pyRefactorAction.getRefactoringRequest();
req.ps = ps;
req.fillInitialNameAndOffset();
req.inputName = "foo";
req.setAdditionalInfo(RefactoringRequest.FIND_DEFINITION_IN_ADDITIONAL_INFO, false);
req.setAdditionalInfo(RefactoringRequest.FIND_REFERENCES_ONLY_IN_LOCAL_SCOPE, true);
return req;
}
/**
* @param pyEdit the editor that will have this action
* @return the action (with the pyedit attached to it)
*/
public static PyRefactorAction getRefactorAction(PyEdit pyEdit) {
PyRefactorAction pyRefactorAction = new PyRefactorAction() {
@Override
protected String perform(IAction action, IProgressMonitor monitor) throws Exception {
throw new RuntimeException("Perform should not be called in this case.");
}
};
pyRefactorAction.setEditor(pyEdit);
return pyRefactorAction;
}
private static final String ANNOTATIONS_CACHE_KEY = "MarkOccurrencesJob Annotations";
private static final String OCCURRENCE_ANNOTATION_TYPE = "com.python.pydev.occurrences";
@Override
protected String getOccurrenceAnnotationsCacheKey() {
return ANNOTATIONS_CACHE_KEY;
}
@Override
protected String getOccurrenceAnnotationsType() {
return OCCURRENCE_ANNOTATION_TYPE;
}
/**
* This is the function that should be called when we want to schedule a request for
* a mark occurrences job.
*/
public static synchronized void scheduleRequest(WeakReference<BaseEditor> editor2, TextSelectionUtils ps) {
BaseMarkOccurrencesJob.scheduleRequest(new MarkOccurrencesJob(editor2, ps));
}
public static synchronized void scheduleRequest(WeakReference<BaseEditor> editor2, TextSelectionUtils ps,
int time) {
BaseMarkOccurrencesJob.scheduleRequest(new MarkOccurrencesJob(editor2, ps), time);
}
}