CPP-3103: Conditionally uncompiled code unexpectedly formatted
[idea/community.git] / platform / lang-impl / src / com / intellij / psi / impl / source / codeStyle / CodeStyleManagerRunnable.java
1 /*
2  * Copyright 2000-2014 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.psi.impl.source.codeStyle;
17
18 import com.intellij.formatting.*;
19 import com.intellij.injected.editor.DocumentWindow;
20 import com.intellij.lang.ASTNode;
21 import com.intellij.lang.LanguageFormatting;
22 import com.intellij.lang.injection.InjectedLanguageManager;
23 import com.intellij.openapi.editor.Document;
24 import com.intellij.openapi.util.TextRange;
25 import com.intellij.psi.*;
26 import com.intellij.psi.codeStyle.CodeStyleSettings;
27 import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
28 import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
29 import com.intellij.psi.formatter.DocumentBasedFormattingModel;
30 import com.intellij.psi.impl.source.SourceTreeToPsiMap;
31 import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
32 import com.intellij.psi.util.PsiUtilCore;
33 import com.intellij.util.text.CharArrayUtil;
34 import org.jetbrains.annotations.NotNull;
35 import org.jetbrains.annotations.Nullable;
36
37 /**
38  * @author nik
39  */
40 abstract class CodeStyleManagerRunnable<T> {
41   protected CodeStyleSettings mySettings;
42   protected CommonCodeStyleSettings.IndentOptions myIndentOptions;
43   protected FormattingModel myModel;
44   protected TextRange mySignificantRange;
45   private final CodeStyleManagerImpl myCodeStyleManager;
46   @NotNull private final FormattingMode myMode;
47
48   CodeStyleManagerRunnable(CodeStyleManagerImpl codeStyleManager, @NotNull FormattingMode mode) {
49     myCodeStyleManager = codeStyleManager;
50     myMode = mode;
51   }
52
53   public T perform(PsiFile file, int offset, @Nullable TextRange range, T defaultValue) {
54     if (file instanceof PsiCompiledFile) {
55       file = ((PsiCompiledFile)file).getDecompiledPsiFile();
56     }
57
58     PsiDocumentManager documentManager = PsiDocumentManager.getInstance(myCodeStyleManager.getProject());
59     Document document = documentManager.getDocument(file);
60     if (document instanceof DocumentWindow) {
61       final DocumentWindow documentWindow = (DocumentWindow)document;
62       final PsiFile topLevelFile = InjectedLanguageManager.getInstance(file.getProject()).getTopLevelFile(file);
63       if (!file.equals(topLevelFile)) {
64         if (range != null) {
65           range = documentWindow.injectedToHost(range);
66         }
67         if (offset != -1) {
68           offset = documentWindow.injectedToHost(offset);
69         }
70         return adjustResultForInjected(perform(topLevelFile, offset, range, defaultValue), documentWindow);
71       }
72     }
73
74     final PsiFile templateFile = PsiUtilCore.getTemplateLanguageFile(file);
75     if (templateFile != null) {
76       file = templateFile;
77       document = documentManager.getDocument(templateFile);
78     }
79
80     PsiElement element = null;
81     if (offset != -1) {
82       element = CodeStyleManagerImpl.findElementInTreeWithFormatterEnabled(file, offset);
83       if (element == null && offset != file.getTextLength()) {
84         return defaultValue;
85       }
86       if (isInsidePlainComment(offset, element)) {
87         return computeValueInsidePlainComment(file, offset, defaultValue);
88       }
89     }
90
91     final FormattingModelBuilder builder = LanguageFormatting.INSTANCE.forContext(file);
92     FormattingModelBuilder elementBuilder = element != null ? LanguageFormatting.INSTANCE.forContext(element) : builder;
93     if (builder != null && elementBuilder != null) {
94       mySettings = CodeStyleSettingsManager.getSettings(myCodeStyleManager.getProject());
95
96       mySignificantRange = offset != -1 ? getSignificantRange(file, offset) : null;
97       myIndentOptions = mySettings.getIndentOptionsByFile(file, mySignificantRange);
98
99       myModel = CoreFormatterUtil.buildModel(builder, file, mySettings, myMode);
100
101       if (document != null && useDocumentBaseFormattingModel()) {
102         myModel = new DocumentBasedFormattingModel(myModel, document, myCodeStyleManager.getProject(), mySettings,
103                                                    file.getFileType(), file);
104       }
105
106       final T result = doPerform(offset, range);
107       if (result != null) {
108         return result;
109       }
110     }
111     return defaultValue;
112   }
113
114   protected boolean useDocumentBaseFormattingModel() {
115     return true;
116   }
117
118   protected T adjustResultForInjected(T result, DocumentWindow documentWindow) {
119     return result;
120   }
121
122   protected T computeValueInsidePlainComment(PsiFile file, int offset, T defaultValue) {
123     return defaultValue;
124   }
125
126   @Nullable
127   protected abstract T doPerform(int offset, TextRange range);
128
129   private static boolean isInsidePlainComment(int offset, @Nullable PsiElement element) {
130     if (!(element instanceof PsiComment) || element instanceof PsiDocCommentBase || !element.getTextRange().contains(offset)) {
131       return false;
132     }
133
134     if (element instanceof PsiLanguageInjectionHost && InjectedLanguageUtil.hasInjections((PsiLanguageInjectionHost)element)) {
135       return false;
136     }
137
138     return true;
139   }
140
141   private static TextRange getSignificantRange(final PsiFile file, final int offset) {
142     final ASTNode elementAtOffset =
143       SourceTreeToPsiMap.psiElementToTree(CodeStyleManagerImpl.findElementInTreeWithFormatterEnabled(file, offset));
144     if (elementAtOffset == null) {
145       int significantRangeStart = CharArrayUtil.shiftBackward(file.getText(), offset - 1, "\r\t ");
146       return new TextRange(Math.max(significantRangeStart, 0), offset);
147     }
148
149     final FormattingModelBuilder builder = LanguageFormatting.INSTANCE.forContext(file);
150     final TextRange textRange = builder.getRangeAffectingIndent(file, offset, elementAtOffset);
151     if (textRange != null) {
152       return textRange;
153     }
154
155     return elementAtOffset.getTextRange();
156   }
157 }