/******************************************************************************* * Copyright (c) 2000, 2016 QNX Software Systems and others. * 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: * QNX Software Systems - Initial API and implementation * Sergey Prigogin (Google) * Anton Leherbauer (Wind River Systems) * IBM Corporation *******************************************************************************/ package org.eclipse.cdt.internal.formatter; import java.util.HashMap; import java.util.Map; import org.eclipse.cdt.core.CCorePlugin; import org.eclipse.cdt.core.dom.ast.IASTDeclaration; import org.eclipse.cdt.core.dom.ast.IASTFileLocation; import org.eclipse.cdt.core.dom.ast.IASTNode; import org.eclipse.cdt.core.dom.ast.IASTNodeSelector; import org.eclipse.cdt.core.dom.ast.IASTPreprocessorMacroExpansion; import org.eclipse.cdt.core.dom.ast.IASTPreprocessorStatement; import org.eclipse.cdt.core.dom.ast.IASTStatement; import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit; import org.eclipse.cdt.core.dom.ast.gnu.cpp.GPPLanguage; import org.eclipse.cdt.core.formatter.CodeFormatter; import org.eclipse.cdt.core.formatter.DefaultCodeFormatterConstants; import org.eclipse.cdt.core.formatter.DefaultCodeFormatterOptions; import org.eclipse.cdt.core.index.IIndex; import org.eclipse.cdt.core.model.CModelException; import org.eclipse.cdt.core.model.CoreModel; import org.eclipse.cdt.core.model.ILanguage; import org.eclipse.cdt.core.model.ITranslationUnit; import org.eclipse.cdt.core.model.IWorkingCopy; import org.eclipse.cdt.core.parser.FileContent; import org.eclipse.cdt.core.parser.IScannerInfo; import org.eclipse.cdt.core.parser.IncludeFileContentProvider; import org.eclipse.cdt.core.parser.ParserUtil; import org.eclipse.cdt.core.parser.ScannerInfo; import org.eclipse.cdt.internal.core.dom.parser.ASTQueries; import org.eclipse.cdt.internal.core.util.TextUtil; import org.eclipse.core.resources.IFile; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IStatus; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.Region; import org.eclipse.text.edits.TextEdit; public class CCodeFormatter extends CodeFormatter { private DefaultCodeFormatterOptions preferences; private Map<String, ?> options; public CCodeFormatter() { this(DefaultCodeFormatterOptions.getDefaultSettings()); } public CCodeFormatter(DefaultCodeFormatterOptions preferences) { this(preferences, null); } public CCodeFormatter(DefaultCodeFormatterOptions defaultCodeFormatterOptions, Map<String, ?> options) { setOptions(options); if (defaultCodeFormatterOptions != null) { preferences.set(defaultCodeFormatterOptions.getMap()); } } public CCodeFormatter(Map<String, ?> options) { this(null, options); } @Override public String createIndentationString(final int indentationLevel) { if (indentationLevel < 0) { throw new IllegalArgumentException(); } int tabs= 0; int spaces= 0; switch (preferences.tab_char) { case DefaultCodeFormatterOptions.SPACE: spaces= indentationLevel * preferences.tab_size; break; case DefaultCodeFormatterOptions.TAB: tabs= indentationLevel; break; case DefaultCodeFormatterOptions.MIXED: int tabSize= preferences.tab_size; int spaceEquivalents= indentationLevel * preferences.indentation_size; tabs= spaceEquivalents / tabSize; spaces= spaceEquivalents % tabSize; break; default: return EMPTY_STRING; } if (tabs == 0 && spaces == 0) { return EMPTY_STRING; } StringBuilder buffer= new StringBuilder(tabs + spaces); for (int i= 0; i < tabs; i++) { buffer.append('\t'); } for (int i= 0; i < spaces; i++) { buffer.append(' '); } return buffer.toString(); } @Override public void setOptions(Map<String, ?> options) { if (options != null) { this.options= options; Map<String, String> formatterPrefs= new HashMap<String, String>(options.size()); for (String key : options.keySet()) { Object value= options.get(key); if (value instanceof String) { formatterPrefs.put(key, (String) value); } } preferences= new DefaultCodeFormatterOptions(formatterPrefs); } else { this.options= CCorePlugin.getOptions(); preferences= DefaultCodeFormatterOptions.getDefaultSettings(); } } @Override public TextEdit format(int kind, String source, int offset, int length, int indentationLevel, String lineSeparator) { preferences.initial_indentation_level = indentationLevel; return format(kind, source, new IRegion[] { new Region(offset, length) }, lineSeparator)[0]; } @Override public TextEdit[] format(int kind, String source, IRegion[] regions, String lineSeparator) { TextEdit[] edits= new TextEdit[regions.length]; if (lineSeparator != null) { preferences.line_separator = lineSeparator; } else { preferences.line_separator = System.getProperty("line.separator"); //$NON-NLS-1$ } ITranslationUnit tu = getTranslationUnit(source); if (tu != null) { IIndex index; try { index = CCorePlugin.getIndexManager().getIndex(tu.getCProject()); index.acquireReadLock(); } catch (CoreException e) { throw new AbortFormatting(e); } catch (InterruptedException e) { return null; } IASTTranslationUnit ast; try { try { ast= tu.getAST(index, ITranslationUnit.AST_SKIP_INDEXED_HEADERS); } catch (CoreException e) { throw new AbortFormatting(e); } formatRegions(source, regions, edits, ast); } finally { index.releaseReadLock(); } } else { IncludeFileContentProvider contentProvider = IncludeFileContentProvider.getSavedFilesProvider(); IScannerInfo scanInfo = new ScannerInfo(); FileContent content = FileContent.create("<text>", source.toCharArray()); //$NON-NLS-1$ ILanguage language= (ILanguage) options.get(DefaultCodeFormatterConstants.FORMATTER_LANGUAGE); if (language == null) { language= GPPLanguage.getDefault(); } IASTTranslationUnit ast; try { ast= language.getASTTranslationUnit(content, scanInfo, contentProvider, null, 0, ParserUtil.getParserLogService()); formatRegions(source, regions, edits, ast); } catch (CoreException e) { throw new AbortFormatting(e); } } return edits; } private void formatRegions(String source, IRegion[] regions, TextEdit[] edits, IASTTranslationUnit ast) { for (int i = 0; i < regions.length; i++) { IRegion region = regions[i]; if (shouldFormatWholeStatements()) { // An empty region is replaced by the region containing the line corresponding to // the offset and all statements overlapping with that line. region = getLineOrStatementRegion(source, region, ast); } CodeFormatterVisitor codeFormatter = new CodeFormatterVisitor(preferences, region.getOffset(), region.getLength()); edits[i] = codeFormatter.format(source, ast); IStatus status= codeFormatter.getStatus(); if (!status.isOK()) { CCorePlugin.log(status); } } } private boolean shouldFormatWholeStatements() { Object obj = options.get(DefaultCodeFormatterConstants.FORMATTER_STATEMENT_SCOPE); return obj instanceof Boolean && ((Boolean) obj).booleanValue(); } /** * Returns the smallest region containing the lines overlapping with the given region and all * statements overlapping with those lines. */ private IRegion getLineOrStatementRegion(String source, IRegion region, IASTTranslationUnit ast) { int start = TextUtil.getLineStart(source, region.getOffset()); int end = TextUtil.skipToNextLine(source, region.getOffset() + region.getLength()); IASTNode node = findOverlappingPreprocessorStatement(start, end, ast); if (node != null) { IASTFileLocation location = node.getFileLocation(); int nodeOffset = location.getNodeOffset(); if (nodeOffset < start) start = nodeOffset; int nodeEnd = nodeOffset + location.getNodeLength(); if (nodeEnd > end) end = nodeEnd; return new Region(start, end - start); } IASTNodeSelector nodeSelector = ast.getNodeSelector(null); for (int pos = start; pos < end;) { node = nodeSelector.findFirstContainedNode(pos, end - pos); if (node != null) { IASTNode containedNode = node; node = ASTQueries.findAncestorWithType(containedNode, IASTStatement.class); if (node == null) node = ASTQueries.findAncestorWithType(containedNode, IASTDeclaration.class); if (node == null) node = ASTQueries.findAncestorWithType(containedNode, IASTPreprocessorMacroExpansion.class); } if (node == null) break; IASTFileLocation location = node.getFileLocation(); int nodeOffset = location.getNodeOffset(); if (nodeOffset < start) start = nodeOffset; int nodeEnd = nodeOffset + location.getNodeLength(); if (nodeEnd > end) end = nodeEnd; pos = nodeEnd; } return new Region(start, end - start); } private IASTNode findOverlappingPreprocessorStatement(int start, int end, IASTTranslationUnit ast) { IASTPreprocessorStatement[] statements = ast.getAllPreprocessorStatements(); int low = 0; int high = statements.length; while (low < high) { int mid = (low + high) >>> 1; IASTPreprocessorStatement statement = statements[mid]; IASTFileLocation location = statement.getFileLocation(); if (location == null) { low = mid + 1; } else { int statementOffset = location.getNodeOffset(); if (statementOffset >= end) { high = mid; } else if (statementOffset + location.getNodeLength() <= start) { low = mid + 1; } else { return statement; } } } return null; } private ITranslationUnit getTranslationUnit(String source) { ITranslationUnit tu= (ITranslationUnit) options.get(DefaultCodeFormatterConstants.FORMATTER_TRANSLATION_UNIT); if (tu == null) { IFile file= (IFile) options.get(DefaultCodeFormatterConstants.FORMATTER_CURRENT_FILE); if (file != null) { tu= (ITranslationUnit) CoreModel.getDefault().create(file); } } if (tu != null && source != null) { try { // Create a private working copy and set it contents to source. if (tu.isWorkingCopy()) tu = ((IWorkingCopy) tu).getOriginalElement(); tu = tu.getWorkingCopy(); tu.getBuffer().setContents(source); } catch (CModelException e) { throw new AbortFormatting(e); } } return tu; } }