/** * Aptana Studio * Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved. * Licensed under the terms of the GNU Public License (GPL) v3 (with exceptions). * Please see the license.html included with this distribution for details. * Any modifications to this file must keep this entire header intact. */ package com.aptana.editor.sass; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.SubMonitor; 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.Region; import org.eclipse.jface.text.source.projection.ProjectionAnnotation; import com.aptana.editor.common.text.reconciler.IFoldingComputer; import com.aptana.parsing.ast.IParseRootNode; public class SassFoldingComputer implements IFoldingComputer { private IDocument document; public SassFoldingComputer(IDocument document) { this.document = document; } /* * (non-Javadoc) * @see com.aptana.editor.common.text.reconciler.IFoldingComputer#emitFoldingRegions(boolean, * org.eclipse.core.runtime.IProgressMonitor) */ public Map<ProjectionAnnotation, Position> emitFoldingRegions(boolean initialReconcile, IProgressMonitor monitor, IParseRootNode ast) { int lineCount = getDocument().getNumberOfLines(); if (lineCount <= 1) { return Collections.emptyMap(); } Map<ProjectionAnnotation, Position> positions = new HashMap<ProjectionAnnotation, Position>(); SubMonitor sub = SubMonitor.convert(monitor, lineCount); try { int lineNum = 0; int fLastIndent = -1; IRegion fLastLineRegion = new Region(0, 0); Map<Integer, Integer> starts = new HashMap<Integer, Integer>(); // Iterate over lines of the document String src = getDocument().get(); String[] lines = src.split("\r?\n|\r"); //$NON-NLS-1$ // $codepro.audit.disable platformSpecificLineSeparator for (String line : lines) { if (sub.isCanceled()) { return positions; } int indent = getIndentLevel(line); IRegion lineRegion = getDocument().getLineInformation(lineNum); if (fLastIndent == -1) { starts.put(0, 0); } // If the indent increased here, then last line was start of a folding block else if (indent > fLastIndent) { starts.put(fLastIndent, fLastLineRegion.getOffset()); } // Indent shrank, so last line was end of folding block else if (indent < fLastIndent) { List<Integer> toRemove = new ArrayList<Integer>(); // Any start with an indent greater than "indent" is now closed for (Map.Entry<Integer, Integer> entry : starts.entrySet()) { if (entry.getKey() >= indent) { positions.put( new ProjectionAnnotation(), new Position(entry.getValue(), (fLastLineRegion.getOffset() + fLastLineRegion.getLength() + 1) - entry.getValue())); toRemove.add(entry.getKey()); } } for (Integer item : toRemove) { starts.remove(item); } } fLastIndent = indent; fLastLineRegion = lineRegion; lineNum++; sub.worked(1); } // Do we have any leftover opens? Close them! for (Map.Entry<Integer, Integer> entry : starts.entrySet()) { positions.put(new ProjectionAnnotation(), new Position(entry.getValue(), getDocument().getLength() - entry.getValue())); } } catch (BadLocationException e) { SassPlugin.getDefault().getLog().log(new Status(IStatus.ERROR, SassPlugin.PLUGIN_ID, e.getMessage(), e)); } finally { if (sub != null) { sub.done(); } } return positions; } private int getIndentLevel(String line) { int size = 0; if (line == null) { return size; } int spaces = 0; for (int i = 0; i < line.length(); i++) { char c = line.charAt(i); if (c == ' ') { spaces++; } else if (c == '\t') { size++; } else { break; } } // TODO check prefs for determining width of indent. Assume 2 for now. return size + (spaces >> 1); // shift operator to divide by 2 } protected IDocument getDocument() { return document; } }