CPP-3103: Conditionally uncompiled code unexpectedly formatted
[idea/community.git] / platform / lang-impl / src / com / intellij / psi / formatter / PsiBasedFormattingModel.java
1 /*
2  * Copyright 2000-2012 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
17 package com.intellij.psi.formatter;
18
19 import com.intellij.formatting.Block;
20 import com.intellij.formatting.FormattingDocumentModel;
21 import com.intellij.formatting.FormattingModelEx;
22 import com.intellij.lang.ASTNode;
23 import com.intellij.openapi.diagnostic.Logger;
24 import com.intellij.openapi.project.Project;
25 import com.intellij.openapi.util.TextRange;
26 import com.intellij.psi.PsiDocumentManager;
27 import com.intellij.psi.PsiElement;
28 import com.intellij.psi.PsiFile;
29 import com.intellij.psi.TokenType;
30 import com.intellij.psi.codeStyle.CodeStyleManager;
31 import com.intellij.psi.impl.source.SourceTreeToPsiMap;
32 import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
33 import org.jetbrains.annotations.NotNull;
34 import org.jetbrains.annotations.Nullable;
35
36 public class PsiBasedFormattingModel implements FormattingModelEx {
37
38   private static final Logger LOG = Logger.getInstance("#com.intellij.psi.formatter.PsiBasedFormattingModel");
39
40   private final Project myProject;
41   private final ASTNode myASTNode;
42   private final FormattingDocumentModelImpl myDocumentModel;
43   @NotNull private final Block myRootBlock;
44   protected boolean myCanModifyAllWhiteSpaces = false;
45
46   public PsiBasedFormattingModel(final PsiFile file,
47                                  @NotNull final Block rootBlock,
48                                  final FormattingDocumentModelImpl documentModel) {
49     myASTNode = SourceTreeToPsiMap.psiElementToTree(file);
50     myDocumentModel = documentModel;
51     myRootBlock = rootBlock;
52     myProject = file.getProject();
53   }
54
55
56
57   @Override
58   public TextRange replaceWhiteSpace(TextRange textRange, String whiteSpace) {
59     return replaceWhiteSpace(textRange, null, whiteSpace);
60   }
61
62   @Override
63   public TextRange replaceWhiteSpace(TextRange textRange, ASTNode nodeAfter, String whiteSpace) {
64     String whiteSpaceToUse
65       = myDocumentModel.adjustWhiteSpaceIfNecessary(whiteSpace, textRange.getStartOffset(), textRange.getEndOffset(), nodeAfter, true).toString();
66     final String wsReplaced = replaceWithPSI(textRange, whiteSpaceToUse);
67
68     if (wsReplaced != null){
69       return new TextRange(textRange.getStartOffset(), textRange.getStartOffset() + wsReplaced.length());
70     } else {
71       return textRange;
72     }
73   }
74
75   @Override
76   public TextRange shiftIndentInsideRange(ASTNode node, TextRange textRange, int shift) {
77     return textRange; // TODO: Remove this method from here...
78   }
79
80   @Override
81   public void commitChanges() {
82   }
83
84
85   @Nullable
86   private String replaceWithPSI(final TextRange textRange, final String whiteSpace) {
87     final int offset = textRange.getEndOffset();
88     ASTNode leafElement = findElementAt(offset);
89
90     if (leafElement != null) {
91       if (leafElement.getPsi() instanceof PsiFile) {
92         return null;
93       } else {
94         if (!leafElement.getPsi().isValid()) {
95           String message = "Invalid element found in '\n" +
96                            myASTNode.getText() +
97                            "\n' at " +
98                            offset +
99                            "(" +
100                            myASTNode.getText().substring(offset, Math.min(offset + 10, myASTNode.getTextLength()));
101           LOG.error(message);
102         }
103         return replaceWithPsiInLeaf(textRange, whiteSpace, leafElement);
104       }
105     } else if (textRange.getEndOffset() == myASTNode.getTextLength()){
106
107       CodeStyleManager.getInstance(myProject).performActionWithFormatterDisabled(new Runnable() {
108         @Override
109         public void run() {
110           FormatterUtil.replaceLastWhiteSpace(myASTNode, whiteSpace, textRange);
111         }
112       });
113
114       return whiteSpace;
115     } else {
116       return null;
117     }
118   }
119
120   @Nullable
121   protected String replaceWithPsiInLeaf(final TextRange textRange, final String whiteSpace, final ASTNode leafElement) {
122     if (!myCanModifyAllWhiteSpaces) {
123       if (leafElement.getElementType() == TokenType.WHITE_SPACE) return null;
124     }
125
126     CodeStyleManager.getInstance(myProject).performActionWithFormatterDisabled(new Runnable() {
127       @Override
128       public void run() {
129         FormatterUtil.replaceWhiteSpace(whiteSpace, leafElement, TokenType.WHITE_SPACE, textRange);
130       }
131     });
132
133     return whiteSpace;
134   }
135
136   @Nullable
137   protected ASTNode findElementAt(final int offset) {
138     PsiFile containingFile = myASTNode.getPsi().getContainingFile();
139     Project project = containingFile.getProject();
140     assert !PsiDocumentManager.getInstance(project).isUncommited(myDocumentModel.getDocument());
141     // TODO:default project can not be used for injections, because latter might wants (unavailable) indices
142     PsiElement psiElement = project.isDefault() ? null : InjectedLanguageUtil.findInjectedElementNoCommit(containingFile, offset);
143     if (psiElement == null) psiElement = containingFile.findElementAt(offset);
144     if (psiElement == null) return null;
145     return psiElement.getNode();
146   }
147
148   @Override
149   @NotNull
150   public FormattingDocumentModel getDocumentModel() {
151     return myDocumentModel;
152   }
153
154   @Override
155   @NotNull
156   public Block getRootBlock() {
157     return myRootBlock;
158   }
159   
160   public void canModifyAllWhiteSpaces() {
161     myCanModifyAllWhiteSpaces = true;
162   }
163 }