/** * Copyright (c) 2005-2013 by Appcelerator, Inc. All Rights Reserved. * Copyright (c) 2013 by Syapse, 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 25/09/2005 */ package org.python.pydev.editor.actions; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IDocumentExtension4; import org.eclipse.jface.text.Position; import org.eclipse.ui.texteditor.MarkerAnnotation; import org.python.pydev.core.IMiscConstants; 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.codefolding.MarkerAnnotationAndPosition; import org.python.pydev.parser.PyParser; import org.python.pydev.parser.PyParser.IPostParserListener; import org.python.pydev.shared_core.model.ErrorDescription; import org.python.pydev.shared_core.structure.Tuple; /** * * @author Fabio, Jeremy J. Carroll */ class OrganizeImportsFixesUnused { public boolean beforePerformArrangeImports(PySelection ps, PyEdit edit, IFile f) { int oldSelection = ps.getRegion().getOffset(); IDocumentExtension4 doc = (IDocumentExtension4) ps.getDoc(); if (edit != null) { if (!ensureParsed(edit)) { return true; } //Check that the editor time is actually the same as the document time. long docTime = doc.getModificationStamp(); if (docTime != edit.getAstModificationTimeStamp()) { return true; } ErrorDescription errorDescription = edit.getErrorDescription(); if (errorDescription != null) { return true; //Don't remove unused imports if we have syntax errors. } } try { findAndDeleteUnusedImports(ps, edit, doc, f); } catch (Exception e) { Log.log(e); } ps.setSelection(oldSelection, oldSelection); return true; } private void findAndDeleteUnusedImports(PySelection ps, PyEdit edit, IDocumentExtension4 doc, IFile f) throws Exception { Iterator<MarkerAnnotationAndPosition> it; if (edit != null) { it = edit.getPySourceViewer().getMarkerIterator(); } else { IMarker markers[] = f.findMarkers(IMiscConstants.PYDEV_ANALYSIS_PROBLEM_MARKER, true, IResource.DEPTH_ZERO); MarkerAnnotationAndPosition maap[] = new MarkerAnnotationAndPosition[markers.length]; int ix = 0; for (IMarker m : markers) { int start = (Integer) m.getAttribute(IMarker.CHAR_START); int end = (Integer) m.getAttribute(IMarker.CHAR_END); maap[ix++] = new MarkerAnnotationAndPosition(new MarkerAnnotation(m), new Position(start, end - start)); } it = Arrays.asList(maap).iterator(); } ArrayList<MarkerAnnotationAndPosition> unusedImportsMarkers = getUnusedImports(it); sortInReverseDocumentOrder(unusedImportsMarkers); deleteImports(doc, ps, unusedImportsMarkers); } private ArrayList<MarkerAnnotationAndPosition> getUnusedImports(Iterator<MarkerAnnotationAndPosition> it) throws CoreException { ArrayList<MarkerAnnotationAndPosition> unusedImportsMarkers = new ArrayList<MarkerAnnotationAndPosition>(); while (it.hasNext()) { MarkerAnnotationAndPosition marker = it.next(); String type = marker.markerAnnotation.getMarker().getType(); if (type != null && type.equals(IMiscConstants.PYDEV_ANALYSIS_PROBLEM_MARKER)) { Integer attribute = marker.markerAnnotation.getMarker().getAttribute( IMiscConstants.PYDEV_ANALYSIS_TYPE, -1); if (attribute != null) { if (attribute.equals(IMiscConstants.TYPE_UNUSED_IMPORT)) { unusedImportsMarkers.add(marker); } } } } return unusedImportsMarkers; } private void sortInReverseDocumentOrder(ArrayList<MarkerAnnotationAndPosition> unusedImportsMarkers) { Collections.sort(unusedImportsMarkers, new Comparator<MarkerAnnotationAndPosition>() { @Override public int compare(MarkerAnnotationAndPosition arg0, MarkerAnnotationAndPosition arg1) { try { return getCharStart(arg1) - getCharStart(arg0); } catch (CoreException e) { Log.log(e); return 0; } } private int getCharStart(MarkerAnnotationAndPosition arg0) throws CoreException { IMarker marker = arg0.markerAnnotation.getMarker(); int i = (Integer) marker.getAttribute(IMarker.CHAR_START); return i; } }); } private void deleteImports(IDocumentExtension4 doc, PySelection ps, Iterable<MarkerAnnotationAndPosition> markers) throws BadLocationException, CoreException { for (MarkerAnnotationAndPosition m : markers) { deleteImport(ps, m); } } private boolean ensureParsed(PyEdit edit) { //Ok, we have a little problem here: we have to ensure not that only a regular ast parse took place, but //that the analysis of the related document is updated (so that the markers are in-place). //To do that, we ask for a reparse asking to analyze the results in that same thread (without scheduling it). // //Maybe better would be having some extension that allowed to call the analysis of the document directly //(so we don't have to rely on markers in place?) final PyParser parser = edit.getParser(); final boolean[] notified = new boolean[] { false }; final Object sentinel = new Object(); parser.addPostParseListener(new IPostParserListener() { @Override public void participantsNotified(Object... argsToReparse) { synchronized (OrganizeImportsFixesUnused.this) { parser.removePostParseListener(this); if (argsToReparse.length == 2 && argsToReparse[1] == sentinel) { notified[0] = true; OrganizeImportsFixesUnused.this.notify(); } } } }); long initial = System.currentTimeMillis(); while ((System.currentTimeMillis() - initial) < 5000) { if (parser.forceReparse(new Tuple<String, Boolean>( IMiscConstants.ANALYSIS_PARSER_OBSERVER_FORCE_IN_THIS_THREAD, true), sentinel)) { //ok, we were able to schedule it with our parameters, let's wait for its completion... synchronized (this) { try { wait(5000); } catch (InterruptedException e) { } } break; } else { synchronized (this) { try { wait(200); } catch (InterruptedException e) { } } } } //Commented out: in the worse case, we already waited 5 seconds, if because of a racing condition we couldn't decide //that it worked, let's just keep on going hoping that the markers are in place... //if (!notified[0]) { // return false; //} return true; } /** * * This leaves a bit of a mess e.g. "import " or "from " for some later process to clean up. * */ private void deleteImport(PySelection ps, MarkerAnnotationAndPosition markerInfo) throws BadLocationException, CoreException { IMarker marker = markerInfo.markerAnnotation.getMarker(); Integer start = (Integer) marker.getAttribute(IMarker.CHAR_START); Integer end = (Integer) marker.getAttribute(IMarker.CHAR_END); IDocument doc = ps.getDoc(); while (start > 0) { char c; try { c = doc.getChar(start - 1); } catch (Exception e) { break; } if (c == '\r' || c == '\n') { break; } if (Character.isWhitespace(c) || c == ',') { start--; continue; } break; } ps.setSelection(start, end); ps.deleteSelection(); } }