/* * 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 com.intellij.psi.formatter.common.InjectedLanguageBlockWrapper; 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)) { if (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); } } } else if (block instanceof InjectedLanguageBlockWrapper) { return getFormatterTag(((InjectedLanguageBlockWrapper)block).getOriginal()); } } return FormatterTag.NONE; } protected 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<>(); 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<>(); Collections.sort(myTagInfoList, (tagInfo1, tagInfo2) -> 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() && formatterEnabled) { 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; } } } }