| /* |
| * 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.formatting; |
| |
| import com.intellij.lang.ASTNode; |
| import com.intellij.openapi.util.TextRange; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.psi.PsiComment; |
| import com.intellij.psi.PsiElement; |
| import com.intellij.psi.PsiRecursiveElementVisitor; |
| import com.intellij.psi.codeStyle.CodeStyleSettings; |
| import org.jetbrains.annotations.NotNull; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.List; |
| import java.util.regex.Pattern; |
| |
| /** |
| * @author Rustam Vishnyakov |
| */ |
| public class FormatterTagHandler { |
| |
| public enum FormatterTag {ON, OFF, NONE} |
| private final CodeStyleSettings mySettings; |
| |
| public FormatterTagHandler(CodeStyleSettings settings) { |
| mySettings = settings; |
| } |
| |
| public FormatterTag getFormatterTag(Block block) { |
| if (mySettings.FORMATTER_TAGS_ENABLED && |
| !StringUtil.isEmpty(mySettings.FORMATTER_ON_TAG) && |
| !StringUtil.isEmpty(mySettings.FORMATTER_OFF_TAG) && |
| block instanceof ASTBlock) { |
| ASTNode node = ((ASTBlock)block).getNode(); |
| if (node != null) { |
| PsiElement element = node.getPsi(); |
| if (element != null && element instanceof PsiComment) { |
| return getFormatterTag((PsiComment)element); |
| } |
| } |
| } |
| return FormatterTag.NONE; |
| } |
| |
| private FormatterTag getFormatterTag(@NotNull PsiComment comment) { |
| CharSequence nodeChars = comment.getNode().getChars(); |
| if (mySettings.FORMATTER_TAGS_ACCEPT_REGEXP) { |
| Pattern onPattern = mySettings.getFormatterOnPattern(); |
| Pattern offPattern = mySettings.getFormatterOffPattern(); |
| if (onPattern != null && onPattern.matcher(nodeChars).find()) return FormatterTag.ON; |
| if (offPattern != null && offPattern.matcher(nodeChars).find()) return FormatterTag.OFF; |
| } |
| else { |
| for (int i = 0; i < nodeChars.length(); i++) { |
| if (isFormatterTagAt(nodeChars, i, mySettings.FORMATTER_ON_TAG)) return FormatterTag.ON; |
| if (isFormatterTagAt(nodeChars, i, mySettings.FORMATTER_OFF_TAG)) return FormatterTag.OFF; |
| } |
| } |
| return FormatterTag.NONE; |
| } |
| |
| private static boolean isFormatterTagAt(@NotNull CharSequence s, int pos, @NotNull String tagName) { |
| if (!tagName.isEmpty() && tagName.charAt(0) == s.charAt(pos)) { |
| int end = pos + tagName.length(); |
| if (end <= s.length()) { |
| return StringUtil.equalsIgnoreCase(s.subSequence(pos, end), tagName); |
| } |
| } |
| return false; |
| } |
| |
| public List<TextRange> getEnabledRanges(ASTNode rootNode, TextRange initialRange) { |
| EnabledRangesCollector collector = new EnabledRangesCollector(initialRange); |
| rootNode.getPsi().accept(collector); |
| return collector.getRanges(); |
| } |
| |
| private class EnabledRangesCollector extends PsiRecursiveElementVisitor { |
| private final List<FormatterTagInfo> myTagInfoList = new ArrayList<FormatterTagInfo>(); |
| private final TextRange myInitialRange; |
| |
| private EnabledRangesCollector(TextRange initialRange) { |
| myInitialRange = initialRange; |
| } |
| |
| @Override |
| public void visitComment(PsiComment comment) { |
| FormatterTag tag = getFormatterTag(comment); |
| //noinspection EnumSwitchStatementWhichMissesCases |
| switch (tag) { |
| case OFF: |
| myTagInfoList.add(new FormatterTagInfo(comment.getTextRange().getEndOffset(), FormatterTag.OFF)); |
| break; |
| case ON: |
| myTagInfoList.add(new FormatterTagInfo(comment.getTextRange().getEndOffset(), FormatterTag.ON)); |
| break; |
| } |
| } |
| |
| private List<TextRange> getRanges() { |
| List<TextRange> enabledRanges = new ArrayList<TextRange>(); |
| Collections.sort(myTagInfoList, new Comparator<FormatterTagInfo>() { |
| @Override |
| public int compare(FormatterTagInfo tagInfo1, |
| FormatterTagInfo tagInfo2) { |
| return tagInfo1.offset - tagInfo2.offset; |
| } |
| }); |
| |
| int start = myInitialRange.getStartOffset(); |
| boolean formatterEnabled = true; |
| for (FormatterTagInfo tagInfo: myTagInfoList) { |
| if (tagInfo.tag == FormatterTag.OFF && formatterEnabled) { |
| if (tagInfo.offset > start) { |
| TextRange range = new TextRange(start, tagInfo.offset); |
| enabledRanges.add(range); |
| } |
| formatterEnabled = false; |
| } |
| else if (tagInfo.tag == FormatterTag.ON && !formatterEnabled) { |
| start = Math.max(tagInfo.offset, myInitialRange.getStartOffset()); |
| if (start >= myInitialRange.getEndOffset()) break; |
| formatterEnabled = true; |
| } |
| } |
| if (start < myInitialRange.getEndOffset()) { |
| enabledRanges.add(new TextRange(start, myInitialRange.getEndOffset())); |
| } |
| return enabledRanges; |
| } |
| |
| private class FormatterTagInfo { |
| public int offset; |
| public FormatterTag tag; |
| |
| private FormatterTagInfo(int offset, FormatterTag tag) { |
| this.offset = offset; |
| this.tag = tag; |
| } |
| } |
| } |
| } |