/******************************************************************************* * Copyright (c) Gil Barash - chookapp@yahoo.com * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Gil Barash - initial API and implementation * *******************************************************************************/ package com.chookapp.org.bracketeer.cdt; import java.util.ArrayList; import java.util.Collections; import java.util.EmptyStackException; import java.util.List; import java.util.Stack; import org.eclipse.cdt.core.dom.ast.IASTFileLocation; import org.eclipse.cdt.core.dom.ast.IASTPreprocessorElifStatement; import org.eclipse.cdt.core.dom.ast.IASTPreprocessorElseStatement; import org.eclipse.cdt.core.dom.ast.IASTPreprocessorEndifStatement; import org.eclipse.cdt.core.dom.ast.IASTPreprocessorIfStatement; import org.eclipse.cdt.core.dom.ast.IASTPreprocessorIfdefStatement; import org.eclipse.cdt.core.dom.ast.IASTPreprocessorIfndefStatement; import org.eclipse.cdt.core.dom.ast.IASTPreprocessorStatement; import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit; import org.eclipse.cdt.core.model.ICElement; import org.eclipse.cdt.core.model.ITranslationUnit; //import org.eclipse.cdt.internal.core.model.ASTCache; //import org.eclipse.cdt.internal.ui.editor.ASTProvider; import org.eclipse.cdt.ui.CDTUITools; import org.eclipse.cdt.ui.text.ICPartitions; import org.eclipse.core.runtime.CoreException; 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.TextUtilities; import org.eclipse.jface.text.source.ICharacterPairMatcher; import org.eclipse.ui.IEditorPart; import com.chookapp.org.bracketeer.cdt.core.internals.CPairMatcher; import com.chookapp.org.bracketeer.common.BracketsPair; import com.chookapp.org.bracketeer.common.IBracketeerProcessingContainer; import com.chookapp.org.bracketeer.common.SingleBracket; import com.chookapp.org.bracketeer.common.Utils; import com.chookapp.org.bracketeer.extensionpoint.BracketeerProcessor; public class BracketeerCdtProcessor extends BracketeerProcessor { protected final static char[] BRACKETS = { '{', '}', '(', ')', '[', ']', '<', '>' }; /* Lonely brackets is different from BRACKETS because matching an * angular bracket is heuristic. So I don't want to have false positives */ protected final static String LONELY_BRACKETS = "()[]{}"; //$NON-NLS-1$ private CPairMatcher _matcher; private ICElement _celem; private IDocument _doc; private IASTTranslationUnit _ast; // @SuppressWarnings("restriction") // class AstRunner implements ASTCache.ASTRunnable // { // IBracketeerProcessingContainer _container; // // public AstRunner(IBracketeerProcessingContainer container) // { // _container = container; // } // // @Override // public IStatus runOnAST(ILanguage lang, IASTTranslationUnit ast) throws CoreException // { // ClosingBracketHintVisitor visitor = new ClosingBracketHintVisitor(_container, // _cancelProcessing, // _hintConf); // ast.accept(visitor); // return Status.OK_STATUS; // } // // } public BracketeerCdtProcessor(IEditorPart part, IDocument doc) { super(doc); _celem = CDTUITools.getEditorInputCElement(part.getEditorInput()); _matcher = new CPairMatcher(BRACKETS); _doc = doc; } private BracketsPair getMatchingPair(int offset) throws BadLocationException { IRegion region = _matcher.match(_doc, offset); if( region == null ) return null; if( region.getLength() < 1 ) throw new RuntimeException(Messages.BracketeerCdtProcessor_ErrLength); boolean isAnchorOpening = (ICharacterPairMatcher.LEFT == _matcher.getAnchor()); int targetOffset = isAnchorOpening ? (region.getOffset() + region.getLength()) : (region.getOffset() + 1); offset--; targetOffset--; if( isAnchorOpening ) return new BracketsPair(offset, _doc.getChar(offset), targetOffset, _doc.getChar(targetOffset)); else return new BracketsPair(targetOffset, _doc.getChar(targetOffset), offset, _doc.getChar(offset)); } private SingleBracket getLonelyBracket(int offset, List<Position> inactiveCode) throws BadLocationException { final int charOffset = offset - 1; char prevChar; prevChar = _doc.getChar(Math.max(charOffset, 0)); if (LONELY_BRACKETS.indexOf(prevChar) == -1) return null; final String partition= TextUtilities.getContentType(_doc, ICPartitions.C_PARTITIONING, charOffset, false); for( String partName : ICPartitions.ALL_CPARTITIONS ) { if (partName.equals( partition )) return null; for (Position pos : inactiveCode) { if(pos.includes(offset)) return null; } } return new SingleBracket(charOffset, Utils.isOpenningBracket(prevChar), prevChar); } @Override protected void processDocument(IDocument doc, IBracketeerProcessingContainer container) { if(Activator.DEBUG) Activator.trace("starting process..."); //$NON-NLS-1$ try { _doc = doc; updateAst(); processBrackets(container); processAst(container); } catch (BadLocationException e) { _cancelProcessing.set(true); } if(Activator.DEBUG) Activator.trace("process ended (" + _cancelProcessing + ")"); //$NON-NLS-1$ //$NON-NLS-2$ } private void processBrackets(IBracketeerProcessingContainer container) throws BadLocationException { List<Position> inactiveCode = collectInactiveCodePositions(_ast); _matcher.updateInactiveCodePositions(inactiveCode); for(int i = 1; i < _doc.getLength()+1; i++) { if( _cancelProcessing.get() ) break; BracketsPair pair = getMatchingPair(i); if(pair != null) { if(Activator.DEBUG) Activator.trace("matching pair added: " + pair.toString()); //$NON-NLS-1$ container.add(pair); continue; } SingleBracket single = getLonelyBracket(i, inactiveCode); if( single != null ) container.add(single); } } // @SuppressWarnings("restriction") private void processAst(IBracketeerProcessingContainer container) throws BadLocationException { if(_ast == null ) return; // AstRunner runner = new AstRunner(container); // ASTProvider provider = CUIPlugin.getDefault().getASTProvider(); // // if( provider.runOnAST(_celem, ASTProvider.WAIT_ACTIVE_ONLY, null, runner) == Status.OK_STATUS) // return; ClosingBracketHintVisitor visitor = new ClosingBracketHintVisitor(container, _cancelProcessing, _hintConf); _ast.accept(visitor); //runner.runOnAST(null, ast); IASTPreprocessorStatement[] stmts = _ast.getAllPreprocessorStatements(); PreprocessorVisitor preVisotor = new PreprocessorVisitor(container, _cancelProcessing, _hintConf); preVisotor.visit(stmts); } private void updateAst() { try { _ast = null; if( _celem == null ) return; ITranslationUnit tu = (ITranslationUnit) _celem; IASTTranslationUnit ast; ast = tu.getAST(null, ITranslationUnit.AST_SKIP_ALL_HEADERS | ITranslationUnit.AST_CONFIGURE_USING_SOURCE_CONTEXT | ITranslationUnit.AST_SKIP_TRIVIAL_EXPRESSIONS_IN_AGGREGATE_INITIALIZERS | ITranslationUnit.AST_PARSE_INACTIVE_CODE); _ast = ast; } catch (CoreException e) { Activator.log(e); } } /** * copied from org.eclipse.cdt.internal.ui.editor.InactiveCodeHighlighting. * * Collect source positions of preprocessor-hidden branches * in the given translation unit. * * @param translationUnit the {@link IASTTranslationUnit}, may be <code>null</code> * @return a {@link List} of {@link IRegion}s */ private List<Position> collectInactiveCodePositions(IASTTranslationUnit translationUnit) { if (translationUnit == null) { return Collections.emptyList(); } String fileName = translationUnit.getFilePath(); if (fileName == null) { return Collections.emptyList(); } List<Position> positions = new ArrayList<Position>(); int inactiveCodeStart = -1; boolean inInactiveCode = false; Stack<Boolean> inactiveCodeStack = new Stack<Boolean>(); IASTPreprocessorStatement[] preprocStmts = translationUnit.getAllPreprocessorStatements(); for (IASTPreprocessorStatement statement : preprocStmts) { IASTFileLocation floc= statement.getFileLocation(); if (floc == null || !fileName.equals(floc.getFileName())) { // preprocessor directive is from a different file continue; } if (statement instanceof IASTPreprocessorIfStatement) { IASTPreprocessorIfStatement ifStmt = (IASTPreprocessorIfStatement)statement; inactiveCodeStack.push(Boolean.valueOf(inInactiveCode)); if (!ifStmt.taken()) { if (!inInactiveCode) { inactiveCodeStart = floc.getNodeOffset(); inInactiveCode = true; } } } else if (statement instanceof IASTPreprocessorIfdefStatement) { IASTPreprocessorIfdefStatement ifdefStmt = (IASTPreprocessorIfdefStatement)statement; inactiveCodeStack.push(Boolean.valueOf(inInactiveCode)); if (!ifdefStmt.taken()) { if (!inInactiveCode) { inactiveCodeStart = floc.getNodeOffset(); inInactiveCode = true; } } } else if (statement instanceof IASTPreprocessorIfndefStatement) { IASTPreprocessorIfndefStatement ifndefStmt = (IASTPreprocessorIfndefStatement)statement; inactiveCodeStack.push(Boolean.valueOf(inInactiveCode)); if (!ifndefStmt.taken()) { if (!inInactiveCode) { inactiveCodeStart = floc.getNodeOffset(); inInactiveCode = true; } } } else if (statement instanceof IASTPreprocessorElseStatement) { IASTPreprocessorElseStatement elseStmt = (IASTPreprocessorElseStatement)statement; if (!elseStmt.taken() && !inInactiveCode) { inactiveCodeStart = floc.getNodeOffset(); inInactiveCode = true; } else if (elseStmt.taken() && inInactiveCode) { int inactiveCodeEnd = floc.getNodeOffset(); positions.add(createInactiveCodePosition(inactiveCodeStart, inactiveCodeEnd, false)); inInactiveCode = false; } } else if (statement instanceof IASTPreprocessorElifStatement) { IASTPreprocessorElifStatement elifStmt = (IASTPreprocessorElifStatement)statement; if (!elifStmt.taken() && !inInactiveCode) { inactiveCodeStart = floc.getNodeOffset(); inInactiveCode = true; } else if (elifStmt.taken() && inInactiveCode) { int inactiveCodeEnd = floc.getNodeOffset(); positions.add(createInactiveCodePosition(inactiveCodeStart, inactiveCodeEnd, false)); inInactiveCode = false; } } else if (statement instanceof IASTPreprocessorEndifStatement) { try { boolean wasInInactiveCode = inactiveCodeStack.pop().booleanValue(); if (inInactiveCode && !wasInInactiveCode) { int inactiveCodeEnd = floc.getNodeOffset() + floc.getNodeLength(); positions.add(createInactiveCodePosition(inactiveCodeStart, inactiveCodeEnd, true)); } inInactiveCode = wasInInactiveCode; } catch( EmptyStackException e) {} } } if (inInactiveCode) { // handle unterminated #if - http://bugs.eclipse.org/255018 int inactiveCodeEnd = _doc.getLength(); positions.add(createInactiveCodePosition(inactiveCodeStart, inactiveCodeEnd, true)); } return positions; } /** * Create a highlight position aligned to start at a line offset. The region's start is * decreased to the line offset, and the end offset decreased to the line start if * <code>inclusive</code> is <code>false</code>. * * @param startOffset the start offset of the region to align * @param endOffset the (exclusive) end offset of the region to align * @param inclusive whether the last line should be included or not * @param key the highlight key * @return a position aligned for background highlighting */ private Position createInactiveCodePosition(int startOffset, int endOffset, boolean inclusive) { final IDocument document= _doc; try { if (document != null) { int start= document.getLineOfOffset(startOffset); int end= document.getLineOfOffset(endOffset); startOffset= document.getLineOffset(start); if (!inclusive) { endOffset= document.getLineOffset(end); } } } catch (BadLocationException x) { // concurrent modification? } return new Position(startOffset, endOffset - startOffset); } }