/** * 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. */ package com.python.pydev.analysis.tabnanny; import java.util.ArrayList; import java.util.List; import org.eclipse.core.resources.IMarker; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.python.pydev.core.IIndentPrefs; import org.python.pydev.core.Tuple3; import org.python.pydev.core.docutils.PySelection; import org.python.pydev.core.log.Log; import org.python.pydev.parser.fastparser.TabNannyDocIterator; import com.aptana.shared_core.string.FastStringBuffer; import com.python.pydev.analysis.IAnalysisPreferences; import com.python.pydev.analysis.messages.IMessage; import com.python.pydev.analysis.messages.Message; /** * Name was gotten from the python library... but the implementation is very different ;-) * * @author Fabio */ public class TabNanny { /** * Analyze the doc for mixed tabs and indents with the wrong number of chars. * @param monitor * * @return a list with the error messages to be shown to the user. */ public static List<IMessage> analyzeDoc(IDocument doc, IAnalysisPreferences analysisPrefs, String moduleName, IIndentPrefs indentPrefs, IProgressMonitor monitor) { ArrayList<IMessage> ret = new ArrayList<IMessage>(); //don't even try to gather indentation errors if they should be ignored. if (analysisPrefs.getSeverityForType(IAnalysisPreferences.TYPE_INDENTATION_PROBLEM) == IMarker.SEVERITY_INFO) { return ret; } List<Tuple3<String, Integer, Boolean>> foundTabs = new ArrayList<Tuple3<String, Integer, Boolean>>(); List<Tuple3<String, Integer, Boolean>> foundSpaces = new ArrayList<Tuple3<String, Integer, Boolean>>(); TabNannyDocIterator it; try { it = new TabNannyDocIterator(doc); } catch (BadLocationException e) { return ret; } while (it.hasNext()) { Tuple3<String, Integer, Boolean> indentation; try { indentation = it.next(); } catch (BadLocationException e) { return ret; } //it can actually be in both (if we have spaces and tabs in the same indent line). if (indentation.o1.indexOf('\t') != -1) { foundTabs.add(indentation); } if (indentation.o1.indexOf(' ') != -1) { foundSpaces.add(indentation); } if (monitor.isCanceled()) { return ret; } } int spacesFoundSize = foundSpaces.size(); int tabsFoundSize = foundTabs.size(); if (spacesFoundSize == 0 && tabsFoundSize == 0) { //nothing to do here... (no indents available) return ret; } //let's discover whether we should mark the tabs found as errors or the spaces found... boolean markTabsAsError; //if we found the same number of indents for tabs and spaces, let's use the user-prefs to decide what to do if (spacesFoundSize == tabsFoundSize) { //ok, we have both, spaces and tabs... let's see what the user actually wants markTabsAsError = indentPrefs.getUseSpaces(false); } else if (tabsFoundSize > spacesFoundSize) { //let's see what appears more in the file (and mark the other as error). markTabsAsError = false; } else { markTabsAsError = true; } List<Tuple3<String, Integer, Boolean>> errorsAre; List<Tuple3<String, Integer, Boolean>> validsAre; String errorMsg; char errorChar; if (markTabsAsError) { validsAre = foundSpaces; errorsAre = foundTabs; errorMsg = "Mixed Indentation: Tab found"; errorChar = '\t'; createBadIndentForSpacesMessages(doc, analysisPrefs, indentPrefs, ret, validsAre, monitor); } else { validsAre = foundTabs; errorsAre = foundSpaces; errorMsg = "Mixed Indentation: Spaces found"; errorChar = ' '; } createMixedErrorMessages(doc, analysisPrefs, ret, errorsAre, errorMsg, errorChar, monitor); return ret; } /** * Creates the errors that are related to a bad indentation (number of space chars is not ok). * @param monitor */ private static void createBadIndentForSpacesMessages(IDocument doc, IAnalysisPreferences analysisPrefs, IIndentPrefs indentPrefs, ArrayList<IMessage> ret, List<Tuple3<String, Integer, Boolean>> validsAre, IProgressMonitor monitor) { int tabWidth = indentPrefs.getTabWidth(); //if we're analyzing the spaces, let's mark invalid indents (tabs are not searched for those because //a tab always marks a full indent). FastStringBuffer buffer = new FastStringBuffer(); for (Tuple3<String, Integer, Boolean> indentation : validsAre) { if (monitor.isCanceled()) { return; } if (!indentation.o3) { //if it does not have more contents (its only whitespaces), let's keep on going! continue; } String indentStr = indentation.o1; if (indentStr.indexOf("\t") != -1) { continue; //the ones that appear in tabs and spaces should not be analyzed here (they'll have their own error messages). } int lenFound = indentStr.length(); int extraChars = lenFound % tabWidth; if (extraChars != 0) { Integer offset = indentation.o2; int startLine = PySelection.getLineOfOffset(doc, offset) + 1; int startCol = 1; int endCol = startCol + lenFound; buffer.clear(); ret.add(new Message(IAnalysisPreferences.TYPE_INDENTATION_PROBLEM, buffer.append("Bad Indentation (") .append(lenFound).append(" spaces)").toString(), startLine, startLine, startCol, endCol, analysisPrefs)); } } } /** * Creates the errors that are related to the mixed indentation. * @param monitor */ private static void createMixedErrorMessages(IDocument doc, IAnalysisPreferences analysisPrefs, ArrayList<IMessage> ret, List<Tuple3<String, Integer, Boolean>> errorsAre, String errorMsg, char errorChar, IProgressMonitor monitor) { for (Tuple3<String, Integer, Boolean> indentation : errorsAre) { if (monitor.isCanceled()) { return; } Integer offset = indentation.o2; int startLine = PySelection.getLineOfOffset(doc, offset) + 1; IRegion region; try { region = doc.getLineInformationOfOffset(offset); int startCol = offset - region.getOffset() + 1; String indentationString = indentation.o1; int charIndex = indentationString.indexOf(errorChar); startCol += charIndex; //now, get the endCol int endCol = startCol; int indentationStringLen = indentationString.length(); //endCol starts at 1, but string access starts at 0 (so <= is needed) while (endCol <= indentationStringLen) { if (indentationString.charAt(endCol - 1) == errorChar) { endCol++; } else { break; } } ret.add(new Message(IAnalysisPreferences.TYPE_INDENTATION_PROBLEM, errorMsg, startLine, startLine, startCol, endCol, analysisPrefs)); } catch (BadLocationException e) { Log.log(e); } } } }