/* * Copyright 2000-2013 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.intellij.psi.impl.source.codeStyle; import com.intellij.lang.ASTNode; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.XmlRecursiveElementVisitor; import com.intellij.psi.codeStyle.CodeStyleSettings; import com.intellij.psi.codeStyle.CommonCodeStyleSettings; import com.intellij.psi.formatter.FormattingDocumentModelImpl; import com.intellij.psi.xml.*; import com.intellij.util.IncorrectOperationException; import com.intellij.util.text.CharArrayUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; /** * @author lesya */ public class ImportsFormatter extends XmlRecursiveElementVisitor { private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.codeStyle.ImportsFormatter"); private final FormattingDocumentModelImpl myDocumentModel; private final CommonCodeStyleSettings.IndentOptions myIndentOptions; @NonNls private static final String PAGE_DIRECTIVE = "page"; @NonNls private static final String IMPORT_ATT = "import"; private final PostFormatProcessorHelper myPostProcessor; public ImportsFormatter(@NotNull CodeStyleSettings settings, @NotNull PsiFile file) { myPostProcessor = new PostFormatProcessorHelper(settings); myDocumentModel = FormattingDocumentModelImpl.createOn(file); myIndentOptions = settings.getIndentOptionsByFile(file); } @Override public void visitXmlTag(XmlTag tag) { if (checkElementContainsRange(tag)) { super.visitXmlTag(tag); } } private static boolean isPageDirectiveTag(final XmlTag tag) { return PAGE_DIRECTIVE.equals(tag.getName()); } @Override public void visitXmlText(XmlText text) { } @Override public void visitXmlAttribute(XmlAttribute attribute) { if (isPageDirectiveTag(attribute.getParent())) { final XmlAttributeValue valueElement = attribute.getValueElement(); if (valueElement != null && checkRangeContainsElement(attribute) && isImportAttribute(attribute) && PostFormatProcessorHelper .isMultiline(valueElement)) { final int oldLength = attribute.getTextLength(); ASTNode valueToken = findValueToken(valueElement.getNode()); if (valueToken != null) { String newAttributeValue = formatImports(valueToken.getStartOffset(), attribute.getValue()); try { attribute.setValue(newAttributeValue); } catch (IncorrectOperationException e) { LOG.error(e); } finally { updateResultRange(oldLength, attribute.getTextLength()); } } } } } private String formatImports(final int startOffset, final String value) { final StringBuffer result = new StringBuffer(); String offset = calcOffset(startOffset); final String[] imports = value.split(","); if (imports.length >=1) { result.append(imports[0]); for (int i = 1; i < imports.length; i++) { String anImport = imports[i]; result.append(','); result.append('\n'); result.append(offset); result.append(anImport.trim()); } } return result.toString(); } private String calcOffset(final int startOffset) { final StringBuffer result = new StringBuffer(); final int lineStartOffset = myDocumentModel.getLineStartOffset(myDocumentModel.getLineNumber(startOffset)); final int emptyLineEnd = CharArrayUtil.shiftForward(myDocumentModel.getDocument().getCharsSequence(), lineStartOffset, " \t"); final CharSequence spaces = myDocumentModel.getText(new TextRange(lineStartOffset, emptyLineEnd)); if (spaces != null) { result.append(spaces.toString()); } appendSpaces(result, startOffset - emptyLineEnd); return result.toString(); } private void appendSpaces(final StringBuffer result, final int count) { if (myIndentOptions.USE_TAB_CHARACTER && ! myIndentOptions.SMART_TABS) { int tabsCount = count / myIndentOptions.TAB_SIZE; int spaceCount = count - tabsCount * myIndentOptions.TAB_SIZE; StringUtil.repeatSymbol(result, '\t', tabsCount); StringUtil.repeatSymbol(result, ' ', spaceCount); } else { StringUtil.repeatSymbol(result, ' ', count); } } private static ASTNode findValueToken(final ASTNode node) { ASTNode child = node.getFirstChildNode(); while (child != null){ if (child.getElementType() == XmlTokenType.XML_ATTRIBUTE_VALUE_TOKEN) return child; child = child.getTreeNext(); } return null; } private static boolean isImportAttribute(final XmlAttribute attribute) { return IMPORT_ATT.equals(attribute.getName()); } protected void updateResultRange(final int oldTextLength, final int newTextLength) { myPostProcessor.updateResultRange(oldTextLength, newTextLength); } protected boolean checkElementContainsRange(final PsiElement element) { return myPostProcessor.isElementPartlyInRange(element); } protected boolean checkRangeContainsElement(final PsiElement element) { return myPostProcessor.isElementFullyInRange(element); } public PsiElement process(PsiElement formatted) { LOG.assertTrue(formatted.isValid()); formatted.accept(this); return formatted; } public TextRange processText(final PsiFile source, final TextRange rangeToReformat) { myPostProcessor.setResultTextRange(rangeToReformat); source.accept(this); return myPostProcessor.getResultTextRange(); } }