2 * Copyright 2000-2015 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.intellij.psi.impl;
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;
50 public abstract class DocumentCommitProcessor {
51 private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.DocumentCommitThread");
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);
56 protected static class CommitTask {
57 @NotNull public final Document document;
58 @NotNull public final Project project;
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.
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;
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" : "");
86 public boolean equals(Object o) {
87 if (this == o) return true;
88 if (!(o instanceof CommitTask)) return false;
90 CommitTask task = (CommitTask)o;
92 return document.equals(task.document) && project.equals(task.project);
96 public int hashCode() {
97 int result = document.hashCode();
98 result = 31 * result + project.hashCode();
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) {
117 final Boolean data = document.getUserData(BlockSupport.DO_NOT_REPARSE_INCREMENTALLY);
119 document.putUserData(BlockSupport.DO_NOT_REPARSE_INCREMENTALLY, null);
120 file.putUserData(BlockSupport.DO_NOT_REPARSE_INCREMENTALLY, data);
123 BlockSupport blockSupport = BlockSupport.getInstance(file.getProject());
124 final DiffLog diffLog = blockSupport.reparseRange(file, changedPsiRange, chars, task.indicator);
126 return new Processor<Document>() {
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
136 doActualPsiChange(file, diffLog);
138 assertAfterCommit(document, file, myTreeElementBeingReparsedSoItWontBeCollected);
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;
149 while (leafText.charAt(leafIndex) == pattern.charAt(patternIndex)) {
151 if (leafIndex == finalLeafIndex || patternIndex == finalPatternIndex) {
154 leafIndex += direction;
155 patternIndex += direction;
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);
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()) {
175 patternIndex += (fromStart ? matchingLength : -matchingLength);
178 leaf = fromStart ? TreeUtil.nextLeaf(leaf, false) : TreeUtil.prevLeaf(leaf, false);
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);
190 int commonPrefixLength = getMatchingLength(treeElement, newDocumentText, true);
191 if (commonPrefixLength == newDocumentText.length() && newDocumentText.length() == psiLength) {
195 int commonSuffixLength = Math.min(getMatchingLength(treeElement, newDocumentText, false), psiLength - commonPrefixLength);
196 return new TextRange(commonPrefixLength, psiLength - commonSuffixLength);
199 public static void doActualPsiChange(@NotNull final PsiFile file, @NotNull final DiffLog diffLog) {
200 CodeStyleManager.getInstance(file.getProject()).performActionWithFormatterDisabled(new Runnable() {
203 synchronized (PsiLock.LOCK) {
204 file.getViewProvider().beforeContentsSynchronized();
206 final Document document = file.getViewProvider().getDocument();
207 PsiDocumentManagerBase documentManager = (PsiDocumentManagerBase)PsiDocumentManager.getInstance(file.getProject());
208 PsiToDocumentSynchronizer.DocumentChangeTransaction transaction = documentManager.getSynchronizer().getTransaction(document);
210 final PsiFileImpl fileImpl = (PsiFileImpl)file;
212 if (transaction == null) {
213 final PomModel model = PomManager.getModel(fileImpl.getProject());
215 model.runTransaction(new PomTransactionBase(fileImpl, model.getModelAspect(TreeAspect.class)) {
217 public PomModelEvent runInner() {
218 return new TreeAspectEvent(model, diffLog.performActualPsiChange(file));
223 diffLog.performActualPsiChange(file);
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));
243 file.putUserData(BlockSupport.DO_NOT_REPARSE_INCREMENTALLY, Boolean.TRUE);
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);
249 if (myTreeElementBeingReparsedSoItWontBeCollected.getTextLength() != document.getTextLength()) {
250 LOG.error("PSI is broken beyond repair in: " + file);
254 file.putUserData(BlockSupport.DO_NOT_REPARSE_INCREMENTALLY, null);
259 public void log(@NonNls String msg, @Nullable CommitTask task, boolean synchronously, @NonNls Object... args) {
263 protected ProgressIndicator createProgressIndicator() {
264 return new EmptyProgressIndicator();