895fb578101843e20573afefc89ff6aa25d145d1
[idea/community.git] / platform / lang-impl / src / com / intellij / psi / impl / source / codeStyle / CodeStyleManagerRunnable.java
1 /*
2  * Copyright 2000-2010 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
98       if (builder instanceof FormattingModelBuilderEx) {
99         myIndentOptions = ((FormattingModelBuilderEx)builder).getIndentOptionsToUse(file, new FormatTextRanges(mySignificantRange, true), mySettings);
100       }
101       if (myIndentOptions == null) {
102         myIndentOptions = mySettings.getIndentOptions(file.getFileType());
103       }
104
105       myModel = CoreFormatterUtil.buildModel(builder, file, mySettings, myMode);
106
107       if (document != null && useDocumentBaseFormattingModel()) {
108         myModel = new DocumentBasedFormattingModel(myModel.getRootBlock(), document, myCodeStyleManager.getProject(), mySettings,
109                                                    file.getFileType(), file);
110       }
111
112       final T result = doPerform(offset, range);
113       if (result != null) {
114         return result;
115       }
116     }
117     return defaultValue;
118   }
119
120   protected boolean useDocumentBaseFormattingModel() {
121     return true;
122   }
123
124   protected T adjustResultForInjected(T result, DocumentWindow documentWindow) {
125     return result;
126   }
127
128   protected T computeValueInsidePlainComment(PsiFile file, int offset, T defaultValue) {
129     return defaultValue;
130   }
131
132   @Nullable
133   protected abstract T doPerform(int offset, TextRange range);
134
135   private static boolean isInsidePlainComment(int offset, @Nullable PsiElement element) {
136     if (!(element instanceof PsiComment) || !element.getTextRange().contains(offset)) {
137       return false;
138     }
139
140     if (element instanceof PsiLanguageInjectionHost && InjectedLanguageUtil.hasInjections((PsiLanguageInjectionHost)element)) {
141       return false;
142     }
143
144     return true;
145   }
146
147   private static TextRange getSignificantRange(final PsiFile file, final int offset) {
148     final ASTNode elementAtOffset =
149       SourceTreeToPsiMap.psiElementToTree(CodeStyleManagerImpl.findElementInTreeWithFormatterEnabled(file, offset));
150     if (elementAtOffset == null) {
151       int significantRangeStart = CharArrayUtil.shiftBackward(file.getText(), offset - 1, "\r\t ");
152       return new TextRange(significantRangeStart, offset);
153     }
154
155     final FormattingModelBuilder builder = LanguageFormatting.INSTANCE.forContext(file);
156     final TextRange textRange = builder.getRangeAffectingIndent(file, offset, elementAtOffset);
157     if (textRange != null) {
158       return textRange;
159     }
160
161     return elementAtOffset.getTextRange();
162   }
163 }