Cleanup (formatting)
[idea/community.git] / platform / core-impl / src / com / intellij / psi / impl / DocumentCommitProcessor.java
1 /*
2  * Copyright 2000-2015 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;
17
18 import com.intellij.lang.ASTNode;
19 import com.intellij.openapi.application.ApplicationManager;
20 import com.intellij.openapi.diagnostic.Attachment;
21 import com.intellij.openapi.diagnostic.Logger;
22 import com.intellij.openapi.editor.Document;
23 import com.intellij.openapi.progress.EmptyProgressIndicator;
24 import com.intellij.openapi.progress.ProgressIndicator;
25 import com.intellij.openapi.project.Project;
26 import com.intellij.openapi.util.Comparing;
27 import com.intellij.openapi.util.TextRange;
28 import com.intellij.openapi.util.text.StringUtil;
29 import com.intellij.pom.PomManager;
30 import com.intellij.pom.PomModel;
31 import com.intellij.pom.event.PomModelEvent;
32 import com.intellij.pom.impl.PomTransactionBase;
33 import com.intellij.pom.tree.TreeAspect;
34 import com.intellij.pom.tree.TreeAspectEvent;
35 import com.intellij.psi.PsiDocumentManager;
36 import com.intellij.psi.PsiFile;
37 import com.intellij.psi.PsiLock;
38 import com.intellij.psi.codeStyle.CodeStyleManager;
39 import com.intellij.psi.impl.source.PsiFileImpl;
40 import com.intellij.psi.impl.source.text.DiffLog;
41 import com.intellij.psi.impl.source.tree.FileElement;
42 import com.intellij.psi.impl.source.tree.ForeignLeafPsiElement;
43 import com.intellij.psi.impl.source.tree.TreeUtil;
44 import com.intellij.psi.text.BlockSupport;
45 import com.intellij.util.Processor;
46 import org.jetbrains.annotations.NonNls;
47 import org.jetbrains.annotations.NotNull;
48 import org.jetbrains.annotations.Nullable;
49
50 public abstract class DocumentCommitProcessor {
51   private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.DocumentCommitThread");
52
53   public abstract void commitSynchronously(@NotNull Document document, @NotNull Project project);
54   public abstract void commitAsynchronously(@NotNull final Project project, @NotNull final Document document, @NonNls @NotNull Object reason);
55
56   protected static class CommitTask {
57     @NotNull public final Document document;
58     @NotNull public final Project project;
59
60     // when queued it's not started
61     // when dequeued it's started
62     // when failed it's canceled
63     @NotNull public final ProgressIndicator indicator; // progress to commit this doc under.
64     @NotNull public final Object reason;
65     public boolean removed; // task marked as removed, should be ignored.
66
67     public CommitTask(@NotNull Document document,
68                       @NotNull Project project,
69                       @NotNull ProgressIndicator indicator,
70                       @NotNull Object reason) {
71       this.document = document;
72       this.project = project;
73       this.indicator = indicator;
74       this.reason = reason;
75     }
76
77     @NonNls
78     @Override
79     public String toString() {
80       return "Project: " + project.getName()
81              + ", Doc: "+ document +" ("+  StringUtil.first(document.getImmutableCharSequence(), 12, true).toString().replaceAll("\n", " ")+")"
82              +(indicator.isCanceled() ? " (Canceled)" : "") + (removed ? "Removed" : "");
83     }
84
85     @Override
86     public boolean equals(Object o) {
87       if (this == o) return true;
88       if (!(o instanceof CommitTask)) return false;
89
90       CommitTask task = (CommitTask)o;
91
92       return document.equals(task.document) && project.equals(task.project);
93     }
94
95     @Override
96     public int hashCode() {
97       int result = document.hashCode();
98       result = 31 * result + project.hashCode();
99       return result;
100     }
101   }
102
103   // public for Upsource
104   @Nullable("returns runnable to execute under write action in AWT to finish the commit")
105   public Processor<Document> doCommit(@NotNull final CommitTask task,
106                                       @NotNull final PsiFile file,
107                                       final boolean synchronously) {
108     Document document = task.document;
109     final long startDocModificationTimeStamp = document.getModificationStamp();
110     final FileElement myTreeElementBeingReparsedSoItWontBeCollected = ((PsiFileImpl)file).calcTreeElement();
111     final CharSequence chars = document.getImmutableCharSequence();
112     final TextRange changedPsiRange = getChangedPsiRange(file, myTreeElementBeingReparsedSoItWontBeCollected, chars);
113     if (changedPsiRange == null) {
114       return null;
115     }
116
117     final Boolean data = document.getUserData(BlockSupport.DO_NOT_REPARSE_INCREMENTALLY);
118     if (data != null) {
119       document.putUserData(BlockSupport.DO_NOT_REPARSE_INCREMENTALLY, null);
120       file.putUserData(BlockSupport.DO_NOT_REPARSE_INCREMENTALLY, data);
121     }
122
123     BlockSupport blockSupport = BlockSupport.getInstance(file.getProject());
124     final DiffLog diffLog = blockSupport.reparseRange(file, changedPsiRange, chars, task.indicator);
125
126     return new Processor<Document>() {
127       @Override
128       public boolean process(Document document) {
129         ApplicationManager.getApplication().assertWriteAccessAllowed();
130         log("Finishing", task, synchronously, document.getModificationStamp(), startDocModificationTimeStamp);
131         if (document.getModificationStamp() != startDocModificationTimeStamp ||
132             ((PsiDocumentManagerBase)PsiDocumentManager.getInstance(file.getProject())).getCachedViewProvider(document) != file.getViewProvider()) {
133           return false; // optimistic locking failed
134         }
135
136         doActualPsiChange(file, diffLog);
137
138         assertAfterCommit(document, file, myTreeElementBeingReparsedSoItWontBeCollected);
139
140         return true;
141       }
142     };
143   }
144
145   private static int getLeafMatchingLength(CharSequence leafText, CharSequence pattern, int patternIndex, int finalPatternIndex, int direction) {
146     int leafIndex = direction == 1 ? 0 : leafText.length() - 1;
147     int finalLeafIndex = direction == 1 ? leafText.length() - 1 : 0;
148     int result = 0;
149     while (leafText.charAt(leafIndex) == pattern.charAt(patternIndex)) {
150       result++;
151       if (leafIndex == finalLeafIndex || patternIndex == finalPatternIndex) {
152         break;
153       }
154       leafIndex += direction;
155       patternIndex += direction;
156     }
157     return result;
158   }
159
160   private static int getMatchingLength(@NotNull FileElement treeElement, @NotNull CharSequence text, boolean fromStart) {
161     int patternIndex = fromStart ? 0 : text.length() - 1;
162     int finalPatternIndex = fromStart ? text.length() - 1 : 0;
163     int direction = fromStart ? 1 : -1;
164     ASTNode leaf = fromStart ? TreeUtil.findFirstLeaf(treeElement, false) : TreeUtil.findLastLeaf(treeElement, false);
165     int result = 0;
166     while (leaf != null && (fromStart ? patternIndex <= finalPatternIndex : patternIndex >= finalPatternIndex)) {
167       if (!(leaf instanceof ForeignLeafPsiElement)) {
168         CharSequence chars = leaf.getChars();
169         if (chars.length() > 0) {
170           int matchingLength = getLeafMatchingLength(chars, text, patternIndex, finalPatternIndex, direction);
171           result += matchingLength;
172           if (matchingLength != chars.length()) {
173             break;
174           }
175           patternIndex += (fromStart ? matchingLength : -matchingLength);
176         }
177       }
178       leaf = fromStart ? TreeUtil.nextLeaf(leaf, false) : TreeUtil.prevLeaf(leaf, false);
179     }
180     return result;
181   }
182
183   @Nullable
184   public static TextRange getChangedPsiRange(@NotNull PsiFile file, @NotNull FileElement treeElement, @NotNull CharSequence newDocumentText) {
185     int psiLength = treeElement.getTextLength();
186     if (!file.getViewProvider().supportsIncrementalReparse(file.getLanguage())) {
187       return new TextRange(0, psiLength);
188     }
189
190     int commonPrefixLength = getMatchingLength(treeElement, newDocumentText, true);
191     if (commonPrefixLength == newDocumentText.length() && newDocumentText.length() == psiLength) {
192       return null;
193     }
194
195     int commonSuffixLength = Math.min(getMatchingLength(treeElement, newDocumentText, false), psiLength - commonPrefixLength);
196     return new TextRange(commonPrefixLength, psiLength - commonSuffixLength);
197   }
198
199   public static void doActualPsiChange(@NotNull final PsiFile file, @NotNull final DiffLog diffLog) {
200     CodeStyleManager.getInstance(file.getProject()).performActionWithFormatterDisabled(new Runnable() {
201       @Override
202       public void run() {
203         synchronized (PsiLock.LOCK) {
204           file.getViewProvider().beforeContentsSynchronized();
205
206           final Document document = file.getViewProvider().getDocument();
207           PsiDocumentManagerBase documentManager = (PsiDocumentManagerBase)PsiDocumentManager.getInstance(file.getProject());
208           PsiToDocumentSynchronizer.DocumentChangeTransaction transaction = documentManager.getSynchronizer().getTransaction(document);
209
210           final PsiFileImpl fileImpl = (PsiFileImpl)file;
211
212           if (transaction == null) {
213             final PomModel model = PomManager.getModel(fileImpl.getProject());
214
215             model.runTransaction(new PomTransactionBase(fileImpl, model.getModelAspect(TreeAspect.class)) {
216               @Override
217               public PomModelEvent runInner() {
218                 return new TreeAspectEvent(model, diffLog.performActualPsiChange(file));
219               }
220             });
221           }
222           else {
223             diffLog.performActualPsiChange(file);
224           }
225         }
226       }
227     });
228   }
229
230   private void assertAfterCommit(@NotNull Document document,
231                                  @NotNull final PsiFile file,
232                                  @NotNull FileElement myTreeElementBeingReparsedSoItWontBeCollected) {
233     if (myTreeElementBeingReparsedSoItWontBeCollected.getTextLength() != document.getTextLength()) {
234       final String documentText = document.getText();
235       String fileText = file.getText();
236       LOG.error("commitDocument left PSI inconsistent: " + file +
237                 "; file len=" + myTreeElementBeingReparsedSoItWontBeCollected.getTextLength() +
238                 "; doc len=" + document.getTextLength() +
239                 "; doc.getText() == file.getText(): " + Comparing.equal(fileText, documentText),
240                 new Attachment("file psi text", fileText),
241                 new Attachment("old text", documentText));
242
243       file.putUserData(BlockSupport.DO_NOT_REPARSE_INCREMENTALLY, Boolean.TRUE);
244       try {
245         BlockSupport blockSupport = BlockSupport.getInstance(file.getProject());
246         final DiffLog diffLog = blockSupport.reparseRange(file, new TextRange(0, documentText.length()), documentText, createProgressIndicator());
247         doActualPsiChange(file, diffLog);
248
249         if (myTreeElementBeingReparsedSoItWontBeCollected.getTextLength() != document.getTextLength()) {
250           LOG.error("PSI is broken beyond repair in: " + file);
251         }
252       }
253       finally {
254         file.putUserData(BlockSupport.DO_NOT_REPARSE_INCREMENTALLY, null);
255       }
256     }
257   }
258
259   public void log(@NonNls String msg, @Nullable CommitTask task, boolean synchronously, @NonNls Object... args) {
260   }
261
262   @NotNull
263   protected ProgressIndicator createProgressIndicator() {
264     return new EmptyProgressIndicator();
265   }
266 }