Disable formatter ranges in formatter off/on tags [CR-IC-2916]

This commit is contained in:
Rustam Vishnyakov
2013-11-06 15:52:44 +04:00
parent f760c44f35
commit 4f9d3f038b
5 changed files with 260 additions and 50 deletions
@@ -1,6 +1,13 @@
import java.util.*;
public class CollectionTest {
/**
* Normal JavaDoc, can
* be
* formatted.
* @param args
* Arguments.
*/
public static void main(String[] args) {
int size;
HashSet collection = new HashSet();
@@ -22,4 +29,34 @@ public class CollectionTest {
System.out.print(iterator.next() + " ");
}
}
// @formatter:off
/**
* And please don't touch this:
* @param x
* These are my nice comments.
* @param y
* And yet another one.
*/
public void doSomething(String x, String y) {
}
// @formatter:on
/**
* It's OK to format this comment.
* @param z
* Parameter Z.
*/
public void doSomethingElse(String z) {}
// @formatter:off
/**
* This comment must be preserved too.
* @param i
* Parameter I.
* @param j
* Parameter J.
*/
public void evenMore(int i, int j) {}
// @formatter:on
}
@@ -1,6 +1,13 @@
import java.util.*;
public class CollectionTest {
/**
* Normal JavaDoc, can
* be
* formatted.
*
* @param args Arguments.
*/
public static void main(String[] args) {
int size;
HashSet collection = new HashSet();
@@ -22,4 +29,35 @@ public class CollectionTest {
System.out.print(iterator.next() + " ");
}
}
// @formatter:off
/**
* And please don't touch this:
* @param x
* These are my nice comments.
* @param y
* And yet another one.
*/
public void doSomething(String x, String y) {
}
// @formatter:on
/**
* It's OK to format this comment.
*
* @param z Parameter Z.
*/
public void doSomethingElse(String z) {
}
// @formatter:off
/**
* This comment must be preserved too.
* @param i
* Parameter I.
* @param j
* Parameter J.
*/
public void evenMore(int i, int j) {}
// @formatter:on
}
@@ -0,0 +1,156 @@
/*
* 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();
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;
}
}
}
}
@@ -17,12 +17,8 @@
package com.intellij.formatting;
import com.intellij.diagnostic.LogMessageEx;
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.PsiComment;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
@@ -38,7 +34,6 @@ import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
/**
* Allows to build {@link AbstractBlockWrapper formatting block wrappers} for the target {@link Block formatting blocks}.
@@ -54,13 +49,11 @@ class InitialInfoBuilder {
private final int myPositionOfInterest;
@NotNull
private final FormattingProgressCallback myProgressCallback;
private final FormatterTagHandler myFormatterTagHandler;
private final CommonCodeStyleSettings.IndentOptions myOptions;
private final CodeStyleSettings mySettings;
private final Stack<State> myStates = new Stack<State>();
private enum FormatterTag {ON, OFF, NONE}
private WhiteSpace myCurrentWhiteSpace;
private CompositeBlockWrapper myRootBlockWrapper;
private LeafBlockWrapper myPreviousBlock;
@@ -86,7 +79,7 @@ class InitialInfoBuilder {
myOptions = options;
myPositionOfInterest = positionOfInterest;
myReadOnlyMode = false;
mySettings = settings;
myFormatterTagHandler = new FormatterTagHandler(settings);
}
public static InitialInfoBuilder prepareToBuildBlocksSequentially(Block root,
@@ -297,7 +290,7 @@ class InitialInfoBuilder {
info.arrangeParentTextRange();
}
switch (getFormatterTag(rootBlock)) {
switch (myFormatterTagHandler.getFormatterTag(rootBlock)) {
case ON:
myReadOnlyMode = false;
break;
@@ -416,42 +409,6 @@ class InitialInfoBuilder {
LogMessageEx.error(LOG, messageBuffer.toString(), buffer.toString());
}
private 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();
PsiElement element = node.getPsi();
if (element != null && element instanceof PsiComment) {
CharSequence nodeChars = node.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;
}
/**
* We want to wrap {@link Block code blocks} sequentially, hence, need to store a processing state and continue from the point
* where we stopped the processing last time.
@@ -79,9 +79,11 @@ public class CodeFormatterFacade {
= new Key<Boolean>("WRAP_LONG_LINE_DURING_FORMATTING_IN_PROGRESS_KEY");
private final CodeStyleSettings mySettings;
private final FormatterTagHandler myTagHandler;
public CodeFormatterFacade(CodeStyleSettings settings) {
mySettings = settings;
myTagHandler = new FormatterTagHandler(settings);
}
public ASTNode processElement(ASTNode element) {
@@ -253,7 +255,7 @@ public class CodeFormatterFacade {
}
}
private static TextRange preprocess(@NotNull final ASTNode node, @NotNull TextRange range) {
private TextRange preprocess(@NotNull final ASTNode node, @NotNull TextRange range) {
TextRange result = range;
PsiElement psi = node.getPsi();
if (!psi.isValid()) {
@@ -324,13 +326,33 @@ public class CodeFormatterFacade {
}
}
for(PreFormatProcessor processor: Extensions.getExtensions(PreFormatProcessor.EP_NAME)) {
result = processor.process(node, result);
if (!mySettings.FORMATTER_TAGS_ENABLED) {
for(PreFormatProcessor processor: Extensions.getExtensions(PreFormatProcessor.EP_NAME)) {
result = processor.process(node, result);
}
}
else {
result = preprocessEnabledRanges(node, result);
}
return result;
}
private TextRange preprocessEnabledRanges(@NotNull final ASTNode node, @NotNull TextRange range) {
TextRange result = TextRange.create(range.getStartOffset(), range.getEndOffset());
List<TextRange> enabledRanges = myTagHandler.getEnabledRanges(node, result);
int delta = 0;
for (TextRange enabledRange : enabledRanges) {
enabledRange = enabledRange.shiftRight(delta);
for (PreFormatProcessor processor : Extensions.getExtensions(PreFormatProcessor.EP_NAME)) {
TextRange processedRange = processor.process(node, enabledRange);
delta += processedRange.getLength() - enabledRange.getLength();
}
}
result = result.grown(delta);
return result;
}
@NotNull
private static Collection<PsiLanguageInjectionHost> collectInjectionHosts(@NotNull PsiFile file, @NotNull TextRange range) {
Stack<PsiElement> toProcess = new Stack<PsiElement>();