diff --git a/java/testFramework/src/com/intellij/codeInsight/completion/CompletionAutoPopupTestCase.groovy b/java/testFramework/src/com/intellij/codeInsight/completion/CompletionAutoPopupTestCase.groovy
index 591878f2d9f2..6ff3c87f9488 100644
--- a/java/testFramework/src/com/intellij/codeInsight/completion/CompletionAutoPopupTestCase.groovy
+++ b/java/testFramework/src/com/intellij/codeInsight/completion/CompletionAutoPopupTestCase.groovy
@@ -16,9 +16,11 @@
package com.intellij.codeInsight.completion
import com.intellij.codeInsight.CodeInsightSettings
+import com.intellij.codeInsight.completion.impl.CompletionServiceImpl
import com.intellij.codeInsight.editorActions.CompletionAutoPopupHandler
import com.intellij.codeInsight.lookup.LookupManager
import com.intellij.codeInsight.lookup.impl.LookupImpl
+import com.intellij.openapi.editor.ex.DocumentEx
import com.intellij.testFramework.PlatformTestUtil
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase
@@ -39,6 +41,7 @@ abstract class CompletionAutoPopupTestCase extends LightCodeInsightFixtureTestCa
@Override protected void tearDown() {
CompletionAutoPopupHandler.ourTestingAutopopup = false
+ ((DocumentEx) myFixture.editor.document).setModificationStamp(0) // to not let autopopup handler sneak in
edt { superTearDown() }
}
@@ -56,6 +59,16 @@ abstract class CompletionAutoPopupTestCase extends LightCodeInsightFixtureTestCa
}
protected void joinCompletion() {
+ for (i in 0.1000) {
+ if (i==999) {
+ printThreadDump()
+ fail("Could not wait for committed doc")
+ }
+ CompletionPhase phase = CompletionServiceImpl.getCompletionPhase()
+ if (phase != com.intellij.codeInsight.completion.CompletionPhase.NoCompletion) break;
+ Thread.sleep(10)
+ }
+
for (j in 1..4000) {
LookupImpl l = null
edt {
diff --git a/platform/lang-api/src/com/intellij/lang/PsiBuilderFactory.java b/platform/lang-api/src/com/intellij/lang/PsiBuilderFactory.java
index 693f3d7f583c..ac4207ad724c 100644
--- a/platform/lang-api/src/com/intellij/lang/PsiBuilderFactory.java
+++ b/platform/lang-api/src/com/intellij/lang/PsiBuilderFactory.java
@@ -21,7 +21,6 @@ import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.project.Project;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-import org.jetbrains.annotations.TestOnly;
/**
* @author yole
@@ -31,23 +30,28 @@ public abstract class PsiBuilderFactory {
return ServiceManager.getService(PsiBuilderFactory.class);
}
+ @NotNull
public abstract PsiBuilder createBuilder(@NotNull Project project, @NotNull ASTNode chameleon);
+ @NotNull
public abstract PsiBuilder createBuilder(@NotNull Project project, @NotNull LighterLazyParseableNode chameleon);
/**
* @deprecated consider using {@link #createBuilder(com.intellij.openapi.project.Project, ASTNode)} instead.
*/
+ @NotNull
public PsiBuilder createBuilder(@NotNull Project project, @NotNull ASTNode tree, @NotNull Language lang, @NotNull CharSequence seq) {
return createBuilder(project, tree, null, lang, seq);
}
+ @NotNull
public abstract PsiBuilder createBuilder(@NotNull Project project, @NotNull ASTNode chameleon, @Nullable Lexer lexer,
@NotNull Language lang, @NotNull CharSequence seq);
+ @NotNull
public abstract PsiBuilder createBuilder(@NotNull Project project, @NotNull LighterLazyParseableNode chameleon, @Nullable Lexer lexer,
@NotNull Language lang, @NotNull CharSequence seq);
- @TestOnly
- public abstract PsiBuilder createBuilder(@NotNull Lexer lexer, @NotNull Language lang, @NotNull CharSequence seq);
+ @NotNull
+ public abstract PsiBuilder createBuilder(@NotNull ParserDefinition parserDefinition, @NotNull Lexer lexer, @NotNull CharSequence seq);
}
diff --git a/platform/lang-api/src/com/intellij/psi/PsiDocumentManager.java b/platform/lang-api/src/com/intellij/psi/PsiDocumentManager.java
index f6967b37ee31..f5fc91fdfa33 100644
--- a/platform/lang-api/src/com/intellij/psi/PsiDocumentManager.java
+++ b/platform/lang-api/src/com/intellij/psi/PsiDocumentManager.java
@@ -27,6 +27,18 @@ import java.util.EventListener;
* Manages the relationship between documents and PSI trees.
*/
public abstract class PsiDocumentManager {
+ public abstract boolean isCommitted(@NotNull Document document);
+
+ /**
+ * Defer action until all documents are committed
+ * must be called from EDT only
+ *
+ * @param key unique key for this action. Subsequent call with the same key overwrites previous associated action.
+ * @param action to run when all documents committed
+ * @return true if action was run immediately (i.e. all documents are already committed)
+ */
+ public abstract boolean performWhenAllDocumentsAreCommitted(@NotNull Object key, @NotNull Runnable action);
+
/**
* Returns the document manager instance for the specified project.
*
diff --git a/platform/lang-impl/lang-impl.iml b/platform/lang-impl/lang-impl.iml
index d314e50e9da5..f12582d935c0 100644
--- a/platform/lang-impl/lang-impl.iml
+++ b/platform/lang-impl/lang-impl.iml
@@ -23,6 +23,7 @@
+
diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionProgressIndicator.java b/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionProgressIndicator.java
index 0a10b0391fa5..f5adad9c4dff 100644
--- a/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionProgressIndicator.java
+++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionProgressIndicator.java
@@ -647,12 +647,8 @@ public class CompletionProgressIndicator extends ProgressIndicatorBase implement
closeAndFinish(false);
- final CodeCompletionHandlerBase newHandler = new CodeCompletionHandlerBase(myParameters.getCompletionType(), false, isAutopopupCompletion());
- try {
- newHandler.invokeCompletion(project, myEditor, myParameters.getInvocationCount(), false);
- }
- catch (IndexNotReadyException ignored) {
- }
+ CompletionAutoPopupHandler.completeWhenAllDocumentsCommitted(project, myEditor, myParameters.getCompletionType(), false,
+ isAutopopupCompletion(), myParameters.getInvocationCount(), false);
}
});
}
diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonCodeAnalyzerImpl.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonCodeAnalyzerImpl.java
index c69b108b008d..fa32cad8e463 100644
--- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonCodeAnalyzerImpl.java
+++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonCodeAnalyzerImpl.java
@@ -43,6 +43,7 @@ import com.intellij.openapi.editor.markup.MarkupModel;
import com.intellij.openapi.editor.markup.RangeHighlighter;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.fileEditor.FileEditor;
+import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.TextEditor;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.fileTypes.StdFileTypes;
@@ -685,19 +686,31 @@ public class DaemonCodeAnalyzerImpl extends DaemonCodeAnalyzer implements JDOMEx
if (myDisposed || !myProject.isInitialized()) return;
final Collection activeEditors = myDaemonListeners.getSelectedEditors();
if (activeEditors.isEmpty()) return;
- Map passes = new THashMap(activeEditors.size());
- for (FileEditor fileEditor : activeEditors) {
- BackgroundEditorHighlighter highlighter = fileEditor.getBackgroundHighlighter();
- if (highlighter != null) {
- HighlightingPass[] highlightingPasses = highlighter.createPassesForEditor();
- passes.put(fileEditor, highlightingPasses);
+ Editor active = FileEditorManager.getInstance(myProject).getSelectedTextEditor();
+
+ Runnable runnable = new Runnable() {
+ public void run() {
+ Map passes = new THashMap(activeEditors.size());
+ for (FileEditor fileEditor : activeEditors) {
+ BackgroundEditorHighlighter highlighter = fileEditor.getBackgroundHighlighter();
+ if (highlighter != null) {
+ HighlightingPass[] highlightingPasses = highlighter.createPassesForEditor();
+ passes.put(fileEditor, highlightingPasses);
+ }
+ }
+ // cancel all after calling createPasses() since there are perverts {@link com.intellij.util.xml.ui.DomUIFactoryImpl} who are changing PSI there
+ cancelUpdateProgress(true, "Cancel by alarm");
+ myAlarm.cancelAllRequests();
+ DaemonProgressIndicator progress = createUpdateProgress();
+ myPassExecutorService.submitPasses(passes, progress, Job.DEFAULT_PRIORITY);
}
+ };
+ if (active == null) {
+ runnable.run();
+ }
+ else {
+ PsiDocumentManager.getInstance(myProject).performWhenAllDocumentsAreCommitted("start daemon when all committed", runnable);
}
- // cancel all after calling createPasses() since there are perverts {@link com.intellij.util.xml.ui.DomUIFactoryImpl} who are changing PSI there
- cancelUpdateProgress(true, "Cancel by alarm");
- myAlarm.cancelAllRequests();
- DaemonProgressIndicator progress = createUpdateProgress();
- myPassExecutorService.submitPasses(passes, progress, Job.DEFAULT_PRIORITY);
}
};
}
diff --git a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/CompletionAutoPopupHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/CompletionAutoPopupHandler.java
index 212bfa952ea4..023f4e3c9b1e 100644
--- a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/CompletionAutoPopupHandler.java
+++ b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/CompletionAutoPopupHandler.java
@@ -23,14 +23,17 @@ import com.intellij.codeInsight.completion.CompletionType;
import com.intellij.codeInsight.completion.impl.CompletionServiceImpl;
import com.intellij.codeInsight.lookup.LookupManager;
import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.project.DumbService;
+import com.intellij.openapi.project.IndexNotReadyException;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
+import org.jetbrains.annotations.NotNull;
/**
* @author peter
@@ -108,8 +111,51 @@ public class CompletionAutoPopupHandler extends TypedHandlerDelegate {
AutoPopupController.getInstance(project).invokeAutoPopupRunnable(request, CodeInsightSettings.getInstance().AUTO_LOOKUP_DELAY);
}
- public static void invokeAutoPopupCompletion(Project project, final Editor editor) {
- new CodeCompletionHandlerBase(CompletionType.BASIC, false, true).invokeCompletion(project, editor, 0, false);
- }
+ public static void invokeAutoPopupCompletion(final Project project, final Editor editor) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ completeWhenAllDocumentsCommitted(project, editor, CompletionType.BASIC, false, true, 0, false);
+ }
+
+ public static void completeWhenAllDocumentsCommitted(@NotNull final Project project, @NotNull final Editor editor,
+ final CompletionType completionType,
+ final boolean invokedExplicitly,
+ final boolean autopopup,
+ final int time,
+ final boolean hasModifiers) {
+ //if (true) {
+ // new CodeCompletionHandlerBase(completionType, invokedExplicitly, autopopup)
+ // .invokeCompletion(project, editor, time, hasModifiers);
+ // return;
+ //}
+ final Document document = editor.getDocument();
+ final long beforeStamp = document.getModificationStamp();
+ PsiDocumentManager.getInstance(project).performWhenAllDocumentsAreCommitted("start completion when all docs committed", new Runnable() {
+ @Override
+ public void run() {
+ long afterStamp = document.getModificationStamp();
+ if (beforeStamp != afterStamp) {
+ // no luck, will try later
+ return;
+ }
+ // later because we may end up in write action here if there was a synchronous commit
+ ApplicationManager.getApplication().invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ long afterStamp = document.getModificationStamp();
+ if (beforeStamp != afterStamp) {
+ // no luck, will try later
+ return;
+ }
+ try {
+ new CodeCompletionHandlerBase(completionType, invokedExplicitly, autopopup)
+ .invokeCompletion(project, editor, time, hasModifiers);
+ }
+ catch (IndexNotReadyException ignored) {
+ }
+ }
+ }, project.getDisposed());
+ }
+ });
+ }
}
diff --git a/platform/lang-impl/src/com/intellij/lang/impl/PsiBuilderFactoryImpl.java b/platform/lang-impl/src/com/intellij/lang/impl/PsiBuilderFactoryImpl.java
index f1410b208508..1fafb26c2700 100644
--- a/platform/lang-impl/src/com/intellij/lang/impl/PsiBuilderFactoryImpl.java
+++ b/platform/lang-impl/src/com/intellij/lang/impl/PsiBuilderFactoryImpl.java
@@ -21,39 +21,47 @@ import com.intellij.lexer.Lexer;
import com.intellij.openapi.project.Project;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-import org.jetbrains.annotations.TestOnly;
/**
* @author yole
*/
public class PsiBuilderFactoryImpl extends PsiBuilderFactory {
+ @NotNull
@Override
public PsiBuilder createBuilder(@NotNull final Project project, @NotNull final ASTNode chameleon) {
return createBuilder(project, chameleon, null, chameleon.getElementType().getLanguage(), chameleon.getChars());
}
+ @NotNull
@Override
public PsiBuilder createBuilder(@NotNull final Project project, @NotNull final LighterLazyParseableNode chameleon) {
final Language language = chameleon.getTokenType().getLanguage();
- return new PsiBuilderImpl(project, language, createLexer(project, language), chameleon, chameleon.getText());
+ ParserDefinition parserDefinition = LanguageParserDefinitions.INSTANCE.forLanguage(language);
+
+ return new PsiBuilderImpl(project, parserDefinition, createLexer(project, language), chameleon, chameleon.getText());
}
+ @NotNull
@Override
public PsiBuilder createBuilder(@NotNull final Project project,
@NotNull final ASTNode chameleon,
@Nullable final Lexer lexer,
@NotNull final Language lang,
@NotNull final CharSequence seq) {
- return new PsiBuilderImpl(project, lang, (lexer != null ? lexer : createLexer(project, lang)), chameleon, seq);
+ return new PsiBuilderImpl(project, LanguageParserDefinitions.INSTANCE.forLanguage(lang), lexer != null ? lexer : createLexer(project, lang), chameleon, seq);
}
+ @NotNull
@Override
public PsiBuilder createBuilder(@NotNull final Project project,
@NotNull final LighterLazyParseableNode chameleon,
@Nullable final Lexer lexer,
@NotNull final Language lang,
@NotNull final CharSequence seq) {
- return new PsiBuilderImpl(project, lang, (lexer != null ? lexer : createLexer(project, lang)), chameleon, seq);
+ final Language language = chameleon.getTokenType().getLanguage();
+ ParserDefinition parserDefinition = LanguageParserDefinitions.INSTANCE.forLanguage(language);
+
+ return new PsiBuilderImpl(project, parserDefinition, lexer != null ? lexer : createLexer(project, lang), chameleon, seq);
}
private static Lexer createLexer(final Project project, final Language lang) {
@@ -62,10 +70,10 @@ public class PsiBuilderFactoryImpl extends PsiBuilderFactory {
return parserDefinition.createLexer(project);
}
- @TestOnly
+ @NotNull
@Override
- public PsiBuilder createBuilder(@NotNull final Lexer lexer, @NotNull final Language lang, @NotNull final CharSequence seq) {
- ParserDefinition parserDefinition = LanguageParserDefinitions.INSTANCE.forLanguage(lang);
- return new PsiBuilderImpl(lexer, parserDefinition.getWhitespaceTokens(), parserDefinition.getCommentTokens(), seq);
+ public PsiBuilder createBuilder(@NotNull ParserDefinition parserDefinition, @NotNull final Lexer lexer,
+ @NotNull final CharSequence seq) {
+ return new PsiBuilderImpl(null, null, parserDefinition.getWhitespaceTokens(), parserDefinition.getCommentTokens(), lexer, null, seq, null, null);
}
}
diff --git a/platform/lang-impl/src/com/intellij/lang/impl/PsiBuilderImpl.java b/platform/lang-impl/src/com/intellij/lang/impl/PsiBuilderImpl.java
index 8cb085d4778b..57cb3f7f6acc 100644
--- a/platform/lang-impl/src/com/intellij/lang/impl/PsiBuilderImpl.java
+++ b/platform/lang-impl/src/com/intellij/lang/impl/PsiBuilderImpl.java
@@ -19,43 +19,35 @@ package com.intellij.lang.impl;
import com.intellij.lang.*;
import com.intellij.lexer.Lexer;
import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.UserDataHolderBase;
-import com.intellij.pom.PomManager;
-import com.intellij.pom.PomModel;
-import com.intellij.pom.event.PomModelEvent;
-import com.intellij.pom.impl.PomTransactionBase;
-import com.intellij.pom.tree.TreeAspect;
-import com.intellij.pom.tree.TreeAspectEvent;
-import com.intellij.pom.tree.events.TreeChangeEvent;
import com.intellij.psi.PsiErrorElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.TokenType;
import com.intellij.psi.impl.source.CharTableImpl;
-import com.intellij.psi.impl.source.PsiFileImpl;
-import com.intellij.psi.impl.source.text.ASTDiffBuilder;
+import com.intellij.psi.impl.source.text.BlockSupportImpl;
+import com.intellij.psi.impl.source.text.DiffLog;
import com.intellij.psi.impl.source.tree.*;
import com.intellij.psi.text.BlockSupport;
import com.intellij.psi.tree.*;
import com.intellij.util.CharTable;
-import com.intellij.util.IncorrectOperationException;
import com.intellij.util.ThreeState;
import com.intellij.util.TripleFunction;
import com.intellij.util.containers.CollectionFactory;
import com.intellij.util.containers.Convertor;
import com.intellij.util.containers.LimitedPool;
import com.intellij.util.containers.Stack;
-import com.intellij.util.diff.DiffTree;
import com.intellij.util.diff.DiffTreeChangeBuilder;
import com.intellij.util.diff.FlyweightCapableTreeStructure;
import com.intellij.util.diff.ShallowNodeComparator;
import com.intellij.util.text.CharArrayUtil;
+import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-import org.jetbrains.annotations.TestOnly;
import java.lang.reflect.Field;
import java.util.ArrayList;
@@ -94,8 +86,8 @@ public class PsiBuilderImpl extends UserDataHolderBase implements PsiBuilder {
private ITokenTypeRemapper myRemapper;
private WhitespaceSkippedCallback myWhitespaceSkippedCallback;
- private ASTNode myOriginalTree = null;
- private MyTreeStructure myParentLightTree = null;
+ private final ASTNode myOriginalTree;
+ private final MyTreeStructure myParentLightTree;
private static TokenSet ourAnyLanguageWhitespaceTokens = TokenSet.EMPTY;
@@ -133,83 +125,55 @@ public class PsiBuilderImpl extends UserDataHolderBase implements PsiBuilder {
}
};
- private static final String UNBALANCED_MESSAGE =
- "Unbalanced tree. Most probably caused by unbalanced markers. " +
- "Try calling setDebugMode(true) against PsiBuilder passed to identify exact location of the problem.";
-
public static void registerWhitespaceToken(IElementType type) {
ourAnyLanguageWhitespaceTokens = TokenSet.orSet(ourAnyLanguageWhitespaceTokens, TokenSet.create(type));
}
+ public PsiBuilderImpl(@NotNull Project project,
+ PsiFile containingFile,
+ @NotNull ParserDefinition parserDefinition,
+ @NotNull Lexer lexer,
+ CharTable charTable,
+ @NotNull final CharSequence text, ASTNode originalTree, MyTreeStructure parentLightTree) {
+ this(project, containingFile, parserDefinition.getWhitespaceTokens(), parserDefinition.getCommentTokens(), lexer, charTable, text, originalTree, parentLightTree);
+ }
+ public PsiBuilderImpl(Project project,
+ PsiFile containingFile,
+ @NotNull TokenSet whiteSpaces,
+ @NotNull TokenSet comments,
+ @NotNull Lexer lexer,
+ CharTable charTable,
+ @NotNull final CharSequence text, ASTNode originalTree, MyTreeStructure parentLightTree) {
+ myProject = project;
+ myFile = containingFile;
+
+ myText = text;
+ myTextArray = CharArrayUtil.fromSequenceWithoutCopying(text);
+ myLexer = lexer;
+
+ myWhitespaces=whiteSpaces;
+ myComments = comments;
+ myCharTable = charTable;
+ myOriginalTree = originalTree;
+ myParentLightTree = parentLightTree;
+
+ cacheLexemes();
+ }
+
public PsiBuilderImpl(@NotNull final Project project,
- @NotNull final Language lang,
+ @NotNull final ParserDefinition parserDefinition,
@NotNull final Lexer lexer,
@NotNull final ASTNode chameleon,
@NotNull final CharSequence text) {
- myProject = project;
- myFile = SharedImplUtil.getContainingFile(chameleon);
-
- myText = text;
- myTextArray = CharArrayUtil.fromSequenceWithoutCopying(text);
- myLexer = lexer;
-
- final ParserDefinition parserDefinition = LanguageParserDefinitions.INSTANCE.forLanguage(lang);
- assert parserDefinition != null : "ParserDefinition absent for language: " + lang.getID();
- myWhitespaces = parserDefinition.getWhitespaceTokens();
- myComments = parserDefinition.getCommentTokens();
-
- myCharTable = SharedImplUtil.findCharTableByTree(chameleon);
- myOriginalTree = chameleon.getUserData(BlockSupport.TREE_TO_BE_REPARSED);
-
- cacheLexemes();
+ this(project, SharedImplUtil.getContainingFile(chameleon), parserDefinition, lexer, SharedImplUtil.findCharTableByTree(chameleon), text, chameleon.getUserData(BlockSupport.TREE_TO_BE_REPARSED), null);
}
public PsiBuilderImpl(@NotNull final Project project,
- @NotNull final Language lang,
+ @NotNull final ParserDefinition parserDefinition,
@NotNull final Lexer lexer,
@NotNull final LighterLazyParseableNode chameleon,
@NotNull final CharSequence text) {
- myProject = project;
- myFile = chameleon.getContainingFile();
-
- myText = text;
- myTextArray = CharArrayUtil.fromSequenceWithoutCopying(text);
- myLexer = lexer;
-
- final ParserDefinition parserDefinition = LanguageParserDefinitions.INSTANCE.forLanguage(lang);
- assert parserDefinition != null : "ParserDefinition absent for language: " + lang.getID();
- myWhitespaces = parserDefinition.getWhitespaceTokens();
- myComments = parserDefinition.getCommentTokens();
-
- myCharTable = chameleon.getCharTable();
- myParentLightTree = ((LazyParseableToken)chameleon).myParent;
-
- cacheLexemes();
- }
-
- @TestOnly
- public PsiBuilderImpl(@NotNull final Lexer lexer,
- @NotNull final TokenSet whitespaces,
- @NotNull final TokenSet comments,
- @NotNull final CharSequence text) {
- myProject = null;
-
- myText = text;
- myTextArray = CharArrayUtil.fromSequenceWithoutCopying(text);
- myLexer = lexer;
- myWhitespaces = whitespaces;
- myComments = comments;
-
- myCharTable = null;
-
- cacheLexemes();
- }
-
- @TestOnly
- public void setOriginalTree(final ASTNode originalTree) {
- myFile = SharedImplUtil.getContainingFile(originalTree);
- myOriginalTree = originalTree;
- myCharTable = SharedImplUtil.findCharTableByTree(originalTree);
+ this(project, chameleon.getContainingFile(), parserDefinition, lexer, chameleon.getCharTable(), text, null, ((LazyParseableToken)chameleon).myParent);
}
private void cacheLexemes() {
@@ -659,6 +623,7 @@ public class PsiBuilderImpl extends UserDataHolderBase implements PsiBuilder {
}
myTokenTypeChecked = false;
myCurrentLexeme++;
+ ProgressManager.checkCanceled();
}
private void skipWhitespace() {
@@ -896,8 +861,8 @@ public class PsiBuilderImpl extends UserDataHolderBase implements PsiBuilder {
final boolean isTooDeep = myFile != null && BlockSupport.isTooDeep(myFile.getOriginalFile());
if (myOriginalTree != null && !isTooDeep) {
- merge(myOriginalTree, rootMarker);
- throw new BlockSupport.ReparsedSuccessfullyException();
+ DiffLog diffLog = merge(myOriginalTree, rootMarker);
+ throw new BlockSupport.ReparsedSuccessfullyException(diffLog);
}
final ASTNode rootNode = createRootAST(rootMarker);
@@ -929,17 +894,16 @@ public class PsiBuilderImpl extends UserDataHolderBase implements PsiBuilder {
return rootNode;
}
- private static class MyBuilder implements DiffTreeChangeBuilder {
- private final ASTDiffBuilder myDelegate;
+ private static class ConvertFromTokensToASTBuilder implements DiffTreeChangeBuilder {
+ private final DiffTreeChangeBuilder myDelegate;
private final ASTConverter myConverter;
- public MyBuilder(PsiFileImpl file, StartMarker rootNode) {
- myDelegate = new ASTDiffBuilder(file);
+ public ConvertFromTokensToASTBuilder(StartMarker rootNode, DiffTreeChangeBuilder delegate) {
+ myDelegate = delegate;
myConverter = new ASTConverter(rootNode);
}
public void nodeDeleted(@NotNull final ASTNode oldParent, @NotNull final ASTNode oldNode) {
- TreeUtil.ensureParsedRecursively(oldNode);
myDelegate.nodeDeleted(oldParent, oldNode);
}
@@ -948,46 +912,31 @@ public class PsiBuilderImpl extends UserDataHolderBase implements PsiBuilder {
}
public void nodeReplaced(@NotNull final ASTNode oldChild, @NotNull final LighterASTNode newChild) {
- TreeUtil.ensureParsedRecursively(oldChild);
myDelegate.nodeReplaced(oldChild, myConverter.convert((Node)newChild));
}
-
- public TreeChangeEvent getEvent() {
- return myDelegate.getEvent();
- }
}
- private void merge(final ASTNode oldNode, final StartMarker newNode) {
- final PsiFileImpl file = (PsiFileImpl)oldNode.getPsi().getContainingFile();
- final PomModel model = PomManager.getModel(file.getProject());
+ @NonNls private static final String UNBALANCED_MESSAGE =
+ "Unbalanced tree. Most probably caused by unbalanced markers. " +
+ "Try calling setDebugMode(true) against PsiBuilder passed to identify exact location of the problem";
- try {
- model.runTransaction(new PomTransactionBase(file, model.getModelAspect(TreeAspect.class)) {
- public PomModelEvent runInner() throws IncorrectOperationException {
- final MyBuilder builder = new MyBuilder(file, newNode);
+ @NotNull
+ private DiffLog merge(@NotNull final ASTNode oldRoot, @NotNull StartMarker newRoot) {
+ DiffLog diffLog = new DiffLog();
+ final ConvertFromTokensToASTBuilder builder = new ConvertFromTokensToASTBuilder(newRoot, diffLog);
+ final MyTreeStructure treeStructure = new MyTreeStructure(newRoot, null);
+ final MyComparator comparator = new MyComparator(getUserDataUnprotected(CUSTOM_COMPARATOR), treeStructure);
- MyTreeStructure treeStructure = new MyTreeStructure(newNode, null);
- MyComparator comparator = new MyComparator(getUserDataUnprotected(CUSTOM_COMPARATOR), treeStructure);
- DiffTree.diff(new ASTStructure(oldNode), treeStructure, comparator, builder);
- file.subtreeChanged();
-
- return new TreeAspectEvent(model, builder.getEvent());
- }
- });
- }
- catch (IncorrectOperationException e) {
- LOG.error(e);
- }
- catch (Throwable e) {
- throw new RuntimeException(UNBALANCED_MESSAGE, e);
- }
+ BlockSupportImpl.diffTrees(oldRoot, builder, comparator, treeStructure, ProgressManager.getInstance().getProgressIndicator());
+ return diffLog;
}
+ @NotNull
private StartMarker prepareLightTree() {
markTokenTypeChecked();
balanceWhiteSpaces();
- LOG.assertTrue(myProduction.size() > 0, "Parser produced no markers. Text:\n" + myText);
+ LOG.assertTrue(!myProduction.isEmpty(), "Parser produced no markers. Text:\n" + myText);
final StartMarker rootMarker = (StartMarker)myProduction.get(0);
rootMarker.myParent = rootMarker.myFirstChild = rootMarker.myLastChild = rootMarker.myNext = null;
@@ -1193,6 +1142,8 @@ public class PsiBuilderImpl extends UserDataHolderBase implements PsiBuilder {
}
public ThreeState deepEqual(final ASTNode oldNode, final LighterASTNode newNode) {
+ ProgressManager.checkCanceled();
+
boolean oldIsErrorElement = oldNode instanceof PsiErrorElement;
boolean newIsErrorElement = newNode.getTokenType() == TokenType.ERROR_ELEMENT;
if (oldIsErrorElement != newIsErrorElement) return ThreeState.NO;
@@ -1284,7 +1235,7 @@ public class PsiBuilderImpl extends UserDataHolderBase implements PsiBuilder {
private final LimitedPool myLazyPool;
private final StartMarker myRoot;
- public MyTreeStructure(final StartMarker root, @Nullable final MyTreeStructure parentTree) {
+ public MyTreeStructure(@NotNull StartMarker root, @Nullable final MyTreeStructure parentTree) {
if (parentTree == null) {
myPool = new LimitedPool(1000, new LimitedPool.ObjectFactory() {
public void cleanup(final Token token) {
diff --git a/platform/lang-impl/src/com/intellij/pom/core/impl/PomModelImpl.java b/platform/lang-impl/src/com/intellij/pom/core/impl/PomModelImpl.java
index c446a34f3f8a..64b50d1018e8 100644
--- a/platform/lang-impl/src/com/intellij/pom/core/impl/PomModelImpl.java
+++ b/platform/lang-impl/src/com/intellij/pom/core/impl/PomModelImpl.java
@@ -35,9 +35,8 @@ import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiLock;
import com.intellij.psi.impl.PsiDocumentManagerImpl;
-import com.intellij.psi.impl.PsiManagerImpl;
import com.intellij.psi.impl.PsiToDocumentSynchronizer;
-import com.intellij.psi.impl.PsiTreeChangeEventImpl;
+import com.intellij.psi.impl.source.text.BlockSupportImpl;
import com.intellij.psi.impl.source.tree.FileElement;
import com.intellij.psi.impl.source.tree.TreeElement;
import com.intellij.psi.impl.source.tree.TreeUtil;
@@ -189,7 +188,7 @@ public class PomModelImpl extends UserDataHolderBase implements PomModel {
}
}
- if (!throwables.isEmpty()) throw new CompoundRuntimeException(throwables);
+ if (!throwables.isEmpty()) CompoundRuntimeException.doThrow(throwables);
}
@Nullable
@@ -232,7 +231,7 @@ public class PomModelImpl extends UserDataHolderBase implements PomModel {
final PsiDocumentManagerImpl manager = (PsiDocumentManagerImpl)PsiDocumentManager.getInstance(myProject);
final PsiToDocumentSynchronizer synchronizer = manager.getSynchronizer();
final PsiElement changeScope = transaction.getChangeScope();
- sendPsiBeforeEvent(transaction.getChangeScope());
+ BlockSupportImpl.sendPsiBeforeEvent(transaction.getChangeScope());
LOG.assertTrue(changeScope != null);
final PsiFile containingFileByTree = getContainingFileByTree(changeScope);
@@ -241,7 +240,7 @@ public class PomModelImpl extends UserDataHolderBase implements PomModel {
document = manager.getCachedDocument(containingFileByTree);
}
if(document != null) {
- synchronizer.startTransaction(document, transaction.getChangeScope());
+ synchronizer.startTransaction(myProject, document, transaction.getChangeScope());
}
}
@@ -265,17 +264,6 @@ public class PomModelImpl extends UserDataHolderBase implements PomModel {
return psiFile.getNode() != null ? psiFile : null;
}
- private static void sendPsiBeforeEvent(final PsiElement scope) {
- if(!scope.isPhysical()) return;
- final PsiManagerImpl manager = (PsiManagerImpl)scope.getManager();
- PsiTreeChangeEventImpl event = new PsiTreeChangeEventImpl(manager);
- event.setParent(scope);
- event.setFile(scope.getContainingFile());
- event.setOffset(scope.getTextRange().getStartOffset());
- event.setOldLength(scope.getTextLength());
- manager.beforeChildrenChange(event);
- }
-
private PomModelListener[] myListenersArray = null;
private PomModelListener[] getListeners(){
if(myListenersArray != null) return myListenersArray;
diff --git a/platform/lang-api/src/com/intellij/pom/tree/TreeAspect.java b/platform/lang-impl/src/com/intellij/pom/tree/TreeAspect.java
similarity index 100%
rename from platform/lang-api/src/com/intellij/pom/tree/TreeAspect.java
rename to platform/lang-impl/src/com/intellij/pom/tree/TreeAspect.java
diff --git a/platform/lang-api/src/com/intellij/pom/tree/TreeAspectEvent.java b/platform/lang-impl/src/com/intellij/pom/tree/TreeAspectEvent.java
similarity index 100%
rename from platform/lang-api/src/com/intellij/pom/tree/TreeAspectEvent.java
rename to platform/lang-impl/src/com/intellij/pom/tree/TreeAspectEvent.java
diff --git a/platform/lang-api/src/com/intellij/pom/tree/events/ChangeInfo.java b/platform/lang-impl/src/com/intellij/pom/tree/events/ChangeInfo.java
similarity index 100%
rename from platform/lang-api/src/com/intellij/pom/tree/events/ChangeInfo.java
rename to platform/lang-impl/src/com/intellij/pom/tree/events/ChangeInfo.java
diff --git a/platform/lang-api/src/com/intellij/pom/tree/events/ReplaceChangeInfo.java b/platform/lang-impl/src/com/intellij/pom/tree/events/ReplaceChangeInfo.java
similarity index 100%
rename from platform/lang-api/src/com/intellij/pom/tree/events/ReplaceChangeInfo.java
rename to platform/lang-impl/src/com/intellij/pom/tree/events/ReplaceChangeInfo.java
diff --git a/platform/lang-api/src/com/intellij/pom/tree/events/TreeChange.java b/platform/lang-impl/src/com/intellij/pom/tree/events/TreeChange.java
similarity index 77%
rename from platform/lang-api/src/com/intellij/pom/tree/events/TreeChange.java
rename to platform/lang-impl/src/com/intellij/pom/tree/events/TreeChange.java
index ab2cd838c742..59722045fb65 100644
--- a/platform/lang-api/src/com/intellij/pom/tree/events/TreeChange.java
+++ b/platform/lang-impl/src/com/intellij/pom/tree/events/TreeChange.java
@@ -16,26 +16,28 @@
package com.intellij.pom.tree.events;
import com.intellij.lang.ASTNode;
+import org.jetbrains.annotations.NotNull;
/**
* @author ik
*/
public interface TreeChange {
- void addChange(ASTNode child, ChangeInfo changeInfo);
+ void addChange(ASTNode child, @NotNull ChangeInfo changeInfo);
+ @NotNull
ASTNode[] getAffectedChildren();
ChangeInfo getChangeByChild(ASTNode child);
- int getChildOffsetInNewTree(ASTNode child);
+ int getChildOffsetInNewTree(@NotNull ASTNode child);
- void composite(TreeChange treeChange);
+ void composite(@NotNull TreeChange treeChange);
boolean isEmpty();
void removeChange(ASTNode beforeEqualDepth);
- void add(final TreeChange value);
+ void add(@NotNull TreeChange value);
int getOldLength();
}
diff --git a/platform/lang-api/src/com/intellij/pom/tree/events/TreeChangeEvent.java b/platform/lang-impl/src/com/intellij/pom/tree/events/TreeChangeEvent.java
similarity index 100%
rename from platform/lang-api/src/com/intellij/pom/tree/events/TreeChangeEvent.java
rename to platform/lang-impl/src/com/intellij/pom/tree/events/TreeChangeEvent.java
diff --git a/platform/lang-impl/src/com/intellij/pom/tree/events/impl/ChangeInfoImpl.java b/platform/lang-impl/src/com/intellij/pom/tree/events/impl/ChangeInfoImpl.java
index 50d08246a997..eed88e76f408 100644
--- a/platform/lang-impl/src/com/intellij/pom/tree/events/impl/ChangeInfoImpl.java
+++ b/platform/lang-impl/src/com/intellij/pom/tree/events/impl/ChangeInfoImpl.java
@@ -21,23 +21,22 @@ import com.intellij.pom.tree.events.ChangeInfo;
import com.intellij.pom.tree.events.TreeChange;
import com.intellij.psi.impl.source.tree.TreeElement;
import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
public class ChangeInfoImpl implements ChangeInfo {
@NonNls private static final String[] TO_STRING = {"add", "remove", "replace", "changed"};
- public static ChangeInfoImpl create(short type, ASTNode changed){
- switch(type){
- case REPLACE:
- return new ReplaceChangeInfoImpl(changed);
- default:
- return new ChangeInfoImpl(type, changed);
- }
- }
-
private final short type;
private int myOldLength = 0;
- protected ChangeInfoImpl(short type, ASTNode changed){
+ public static ChangeInfoImpl create(short type, @NotNull ASTNode changed){
+ if (type == REPLACE) {
+ throw new IllegalArgumentException("use com.intellij.pom.tree.events.impl.ReplaceChangeInfoImpl");
+ }
+ return new ChangeInfoImpl(type, changed);
+ }
+
+ protected ChangeInfoImpl(short type, @NotNull ASTNode changed){
this.type = type;
myOldLength = type != ADD ? ((TreeElement)changed).getNotCachedLength() : 0;
}
@@ -83,5 +82,4 @@ public class ChangeInfoImpl implements ChangeInfo {
public void setOldLength(int oldTreeLength) {
myOldLength = oldTreeLength;
}
-
}
diff --git a/platform/lang-impl/src/com/intellij/pom/tree/events/impl/ReplaceChangeInfoImpl.java b/platform/lang-impl/src/com/intellij/pom/tree/events/impl/ReplaceChangeInfoImpl.java
index c246099e768b..6b9b675289e7 100644
--- a/platform/lang-impl/src/com/intellij/pom/tree/events/impl/ReplaceChangeInfoImpl.java
+++ b/platform/lang-impl/src/com/intellij/pom/tree/events/impl/ReplaceChangeInfoImpl.java
@@ -21,24 +21,25 @@ import com.intellij.pom.tree.events.ReplaceChangeInfo;
import com.intellij.psi.impl.source.tree.SharedImplUtil;
import com.intellij.psi.impl.source.tree.TreeElement;
import com.intellij.util.CharTable;
+import org.jetbrains.annotations.NotNull;
public class ReplaceChangeInfoImpl extends ChangeInfoImpl implements ReplaceChangeInfo {
- private ASTNode myReplaced;
- private final ASTNode myChanged;
+ private ASTNode myOldNode;
+ private final ASTNode myNewNode;
- public ReplaceChangeInfoImpl(ASTNode changed) {
- super(REPLACE, changed);
- myChanged = changed;
+ public ReplaceChangeInfoImpl(@NotNull ASTNode newNode) {
+ super(REPLACE, newNode);
+ myNewNode = newNode;
}
public ASTNode getReplaced(){
- return myReplaced;
+ return myOldNode;
}
- public void setReplaced(ASTNode replaced) {
- CharTable charTableByTree = SharedImplUtil.findCharTableByTree(myChanged);
+ public void setReplaced(@NotNull ASTNode replaced) {
+ CharTable charTableByTree = myNewNode.getTreeParent() == null ? null : SharedImplUtil.findCharTableByTree(myNewNode);
setOldLength(((TreeElement)replaced).getNotCachedLength());
- myReplaced = replaced;
- myReplaced.putUserData(CharTable.CHAR_TABLE_KEY, charTableByTree);
+ myOldNode = replaced;
+ myOldNode.putUserData(CharTable.CHAR_TABLE_KEY, charTableByTree);
}
}
diff --git a/platform/lang-impl/src/com/intellij/pom/tree/events/impl/TreeChangeImpl.java b/platform/lang-impl/src/com/intellij/pom/tree/events/impl/TreeChangeImpl.java
index 51ae77c54aeb..c2a29076dfaa 100644
--- a/platform/lang-impl/src/com/intellij/pom/tree/events/impl/TreeChangeImpl.java
+++ b/platform/lang-impl/src/com/intellij/pom/tree/events/impl/TreeChangeImpl.java
@@ -25,6 +25,7 @@ import com.intellij.pom.tree.events.TreeChange;
import com.intellij.psi.impl.source.tree.LeafElement;
import com.intellij.psi.impl.source.tree.TreeElement;
import gnu.trove.THashMap;
+import org.jetbrains.annotations.NotNull;
import java.util.*;
@@ -38,7 +39,7 @@ public class TreeChangeImpl implements TreeChange {
myParent = parent;
}
- public void addChange(ASTNode child, ChangeInfo changeInfo) {
+ public void addChange(ASTNode child, @NotNull ChangeInfo changeInfo) {
LOG.assertTrue(child.getTreeParent() == myParent);
final ChangeInfo current = myChanges.get(child);
@@ -53,7 +54,6 @@ public class TreeChangeImpl implements TreeChange {
final ChangeInfo replacedInfo = myChanges.get(replaced);
if(replacedInfo == null){
- //myChanges.put(replaced, ChangeInfo.create(ChangeInfo.REMOVED, replaced, SharedImplUtil.findCharTableByTree(myParent)));
addChangeInternal(child, changeInfo);
}
else{
@@ -87,6 +87,7 @@ public class TreeChangeImpl implements TreeChange {
return;
}
+ // add + remove == no op
if(current != null && current.getChangeType() == ChangeInfo.ADD){
if(changeInfo.getChangeType() == ChangeInfo.REMOVED){
removeChangeInternal(child);
@@ -167,6 +168,7 @@ public class TreeChangeImpl implements TreeChange {
return false;
}
+ @NotNull
public TreeElement[] getAffectedChildren() {
final TreeElement[] treeElements = new TreeElement[myChanges.size()];
int index = 0;
@@ -180,12 +182,12 @@ public class TreeChangeImpl implements TreeChange {
return myChanges.get(child);
}
- public int getChildOffsetInNewTree(ASTNode child) {
+ public int getChildOffsetInNewTree(@NotNull ASTNode child) {
return myParent.getStartOffset() + getNewOffset(child);
}
- public void composite(TreeChange treeChange) {
+ public void composite(@NotNull TreeChange treeChange) {
final TreeChangeImpl change = (TreeChangeImpl)treeChange;
final Set> entries = change.myChanges.entrySet();
for (final Map.Entry entry : entries) {
@@ -201,9 +203,10 @@ public class TreeChangeImpl implements TreeChange {
removeChangeInternal(beforeEqualDepth);
}
- public void add(final TreeChange value) {
+ public void add(@NotNull final TreeChange value) {
final TreeChangeImpl impl = (TreeChangeImpl)value;
LOG.assertTrue(impl.myParent == myParent);
+
for (final Pair pair : impl.myOffsets) {
final ASTNode child = pair.getFirst();
ChangeInfo change = impl.getChangeByChild(child);
@@ -246,8 +249,10 @@ public class TreeChangeImpl implements TreeChange {
break;
case ChangeInfo.REPLACE:
final ASTNode oldReplaced = ((ReplaceChangeInfo)oldChange).getReplaced();
- change = ChangeInfoImpl.create(ChangeInfo.REPLACE, child);
- ((ReplaceChangeInfoImpl)change).setReplaced(oldReplaced);
+ ReplaceChangeInfoImpl rep = new ReplaceChangeInfoImpl(child);
+ rep.setReplaced(oldReplaced);
+ change = rep;
+
break;
}
removeChangeInternal(replaced);
@@ -289,6 +294,7 @@ public class TreeChangeImpl implements TreeChange {
private int getNodeOffset(ASTNode child){
LOG.assertTrue(child.getTreeParent() == myParent);
+
int oldOffsetInParent = 0;
// find last changed element before child
diff --git a/platform/lang-impl/src/com/intellij/psi/impl/DebugUtil.java b/platform/lang-impl/src/com/intellij/psi/impl/DebugUtil.java
index 15d6e58966e3..7d1873f0ca72 100644
--- a/platform/lang-impl/src/com/intellij/psi/impl/DebugUtil.java
+++ b/platform/lang-impl/src/com/intellij/psi/impl/DebugUtil.java
@@ -330,7 +330,7 @@ public class DebugUtil {
}
}
- public static void checkTreeStructure(@Nullable ASTNode anyElement) {
+ public static void doCheckTreeStructure(@Nullable ASTNode anyElement) {
if (anyElement == null) return;
ASTNode root = anyElement;
while (root.getTreeParent() != null) {
@@ -493,9 +493,9 @@ public class DebugUtil {
LOG.error(e);
}
}
- public static void checkTreeStructureIfConfigured(ASTNode element) {
+ public static void checkTreeStructure(ASTNode element) {
if (CHECK){
- checkTreeStructure(element);
+ doCheckTreeStructure(element);
}
}
diff --git a/platform/lang-impl/src/com/intellij/psi/impl/DocumentCommitThread.java b/platform/lang-impl/src/com/intellij/psi/impl/DocumentCommitThread.java
new file mode 100644
index 000000000000..02c48745a1a7
--- /dev/null
+++ b/platform/lang-impl/src/com/intellij/psi/impl/DocumentCommitThread.java
@@ -0,0 +1,432 @@
+/*
+ * Copyright 2000-2011 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.psi.impl;
+
+import com.intellij.codeInsight.daemon.impl.DaemonProgressIndicator;
+import com.intellij.openapi.Disposable;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ex.ApplicationManagerEx;
+import com.intellij.openapi.components.AbstractProjectComponent;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.impl.DocumentImpl;
+import com.intellij.openapi.progress.ProcessCanceledException;
+import com.intellij.openapi.progress.ProgressManager;
+import com.intellij.openapi.progress.impl.ProgressManagerImpl;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.startup.StartupManager;
+import com.intellij.openapi.util.Comparing;
+import com.intellij.openapi.util.Disposer;
+import com.intellij.openapi.util.Key;
+import com.intellij.psi.FileViewProvider;
+import com.intellij.psi.PsiDocumentManager;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiLock;
+import com.intellij.psi.impl.smartPointers.SmartPointerManagerImpl;
+import com.intellij.psi.impl.source.PsiFileImpl;
+import com.intellij.psi.impl.source.text.DiffLog;
+import com.intellij.psi.impl.source.tree.FileElement;
+import com.intellij.psi.text.BlockSupport;
+import com.intellij.util.Processor;
+import com.intellij.util.containers.OrderedSet;
+import com.intellij.util.ui.UIUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * User: cdr
+ */
+public class DocumentCommitThread extends AbstractProjectComponent implements Runnable {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.DocumentCommitThread");
+
+ private final OrderedSet documentsToCommit = new OrderedSet();
+ private volatile boolean isDisposed;
+ public static final Document THE_POISON_PILL = new DocumentImpl(true);
+ private volatile DaemonProgressIndicator myProgressIndicator = new DaemonProgressIndicator();
+ private volatile boolean threadFinished;
+
+ public static DocumentCommitThread getInstance(Project project) {
+ return project.getComponent(DocumentCommitThread.class);
+ }
+
+ public DocumentCommitThread(@NotNull final Project project, @NotNull StartupManager startupManager) {
+ super(project);
+ if (project.isDefault()) return;
+ Runnable runnable = new Runnable() {
+ @Override
+ public void run() {
+ new Thread(DocumentCommitThread.this, "Document commit thread").start();
+ Disposer.register(project, new Disposable() {
+ @Override
+ public void dispose() {
+ stop();
+ }
+ });
+ }
+ };
+ startupManager.runWhenProjectIsInitialized(runnable);
+ }
+
+ private void stop() {
+ isDisposed = true;
+ cancelAndClearAll();
+ boolean added = doQueue(THE_POISON_PILL);
+ assert added : documentsToCommit;
+ while (!threadFinished) {
+ synchronized (documentsToCommit) {
+ try {
+ documentsToCommit.wait(10);
+ }
+ catch (InterruptedException ignored) {
+ }
+ }
+ }
+ }
+
+ private void cancelAndClearAll() {
+ synchronized (documentsToCommit) {
+ documentsToCommit.clear();
+ // let our thread know that next doc in queue is available
+ documentsToCommit.notifyAll();
+ }
+ recreateIndicator();
+ }
+ void cancel() {
+ recreateIndicator();
+ }
+
+ public boolean queueCommit(@NotNull Document document) {
+ assert !isDisposed;
+ PsiFile psiFile = PsiDocumentManager.getInstance(myProject).getCachedPsiFile(document);
+ if (psiFile == null || !psiFile.isPhysical()) return false;
+ if (!doQueue(document)) return false;
+ return PsiDocumentManagerImpl.changeCommitStage(document, PsiDocumentManagerImpl.CommitStage.DIRTY, PsiDocumentManagerImpl.CommitStage.QUEUED_TO_COMMIT);
+ }
+
+ private boolean doQueue(Document document) {
+ boolean added;
+ synchronized (documentsToCommit) {
+ added = documentsToCommit.add(document);
+ // let our thread know that next doc in queue is available
+ documentsToCommit.notifyAll();
+ }
+ return added;
+ }
+
+ void cancelCommit(@NotNull Document document) {
+ synchronized (documentsToCommit) {
+ documentsToCommit.remove(document);
+ // let our thread know that queue must be polled again
+ documentsToCommit.notifyAll();
+ }
+ cancelRunningCommitFor(document);
+ }
+
+ private void cancelRunningCommitFor(@NotNull Document document) {
+ DaemonProgressIndicator indicator = myProgressIndicator;
+ Document runningDoc = indicator.getUserData(RUNNING_COMMIT_DOCUMENT);
+ if (runningDoc == document) {
+ indicator.cancel();
+ }
+ }
+
+ @Override
+ public void run() {
+ threadFinished = false;
+ try {
+ while (!isDisposed) {
+ try {
+ Document document;
+ synchronized (documentsToCommit) {
+ int size = documentsToCommit.size();
+ if (size == 0) {
+ documentsToCommit.wait();
+ continue;
+ }
+ document = documentsToCommit.get(size - 1);
+ }
+ if (document == THE_POISON_PILL) break;
+ boolean success = commit(myProject, document, null, recreateIndicator(), false);
+
+ // ping the thread waiting for close
+ synchronized (documentsToCommit) {
+ if (success) {
+ documentsToCommit.remove(document);
+ }
+ documentsToCommit.notifyAll();
+ }
+ }
+ catch (ProcessCanceledException ignored) {
+ }
+ catch (InterruptedException ignored) {
+ // app must be closing
+ }
+ catch (Throwable e) {
+ LOG.error(e);
+ }
+ }
+ }
+ finally {
+ threadFinished = true;
+ }
+ }
+
+ public boolean commitSynchronously(Project project, @NotNull Document document, PsiFile excludeFile) {
+ ApplicationManager.getApplication().assertWriteAccessAllowed();
+ cancelCommit(document);
+ PsiDocumentManagerImpl.setCommitStage(document, PsiDocumentManagerImpl.CommitStage.QUEUED_TO_COMMIT);
+ boolean success = commit(project, document, excludeFile, new DaemonProgressIndicator(), true);
+ assert success;
+ return success;
+ }
+
+ private static final Key RUNNING_COMMIT_DOCUMENT = Key.create("RUNNING_COMMIT_DOCUMENT");
+ private boolean commit(@NotNull final Project project,
+ @NotNull final Document document,
+ final PsiFile excludeFile,
+ @NotNull final DaemonProgressIndicator indicator,
+ final boolean synchronously) {
+ long start = System.currentTimeMillis();
+ indicator.putUserData(RUNNING_COMMIT_DOCUMENT, document);
+ try {
+ final boolean[] success = new boolean[1];
+ ((ProgressManagerImpl)ProgressManager.getInstance()).executeProcessUnderProgress(new Runnable() {
+ @Override
+ public void run() {
+ success[0] = commitUnderProgress(project, document, excludeFile, indicator, synchronously);
+ }
+ }, indicator);
+ return success[0];
+ }
+ finally {
+ long finish = System.currentTimeMillis();
+ indicator.putUserData(RUNNING_COMMIT_DOCUMENT, null);
+ }
+ }
+
+ private DaemonProgressIndicator recreateIndicator() {
+ myProgressIndicator.cancel();
+ DaemonProgressIndicator indicator = new DaemonProgressIndicator();
+ myProgressIndicator = indicator;
+ return indicator;
+ }
+
+ private boolean commitUnderProgress(@NotNull Project project,
+ @NotNull final Document document,
+ final PsiFile excludeFile,
+ @NotNull final DaemonProgressIndicator indicator,
+ final boolean synchronously) {
+ final PsiDocumentManagerImpl documentManager = (PsiDocumentManagerImpl)PsiDocumentManager.getInstance(project);
+ final List> finishRunnables = new ArrayList>();
+ Runnable runnable = new Runnable() {
+ public void run() {
+ final FileViewProvider viewProvider = documentManager.getCachedViewProvider(document);
+ if (viewProvider == null) return;
+ final List psiFiles = viewProvider.getAllFiles();
+ for (PsiFile file : psiFiles) {
+ if (file.isValid() && file != excludeFile) {
+ Processor finishRunnable = doCommit(document, file, indicator);
+ if (finishRunnable != null) {
+ finishRunnables.add(finishRunnable);
+ }
+ }
+ }
+ }
+ };
+ if (synchronously) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ runnable.run();
+ }
+ else {
+ if (!ApplicationManagerEx.getApplicationEx().tryRunReadAction(runnable)) return false;
+ }
+
+ boolean canceled = indicator.isCanceled();
+ if (synchronously) {
+ assert !canceled;
+ }
+ if (!canceled) {
+ UIUtil.invokeLaterIfNeeded(new Runnable() {
+ @Override
+ public void run() {
+ boolean success = documentManager.finishCommit(document, finishRunnables, synchronously);
+ if (synchronously) {
+ assert success;
+ }
+ if (!success && !documentManager.isCommitted(document)) {
+ // add document back to the queue
+ doQueue(document);
+ }
+ }
+ });
+ }
+ return true;
+ }
+
+ @Nullable("returns runnable to execute under write action in AWT to finish the commit")
+ private static Processor doCommit(@NotNull final Document document, @NotNull final PsiFile file, @NotNull DaemonProgressIndicator indicator) {
+ ((PsiDocumentManagerImpl)PsiDocumentManager.getInstance(file.getProject())).clearTreeHardRef(document);
+ final TextBlock textBlock = PsiDocumentManagerImpl.getTextBlock(file);
+ if (textBlock.isEmpty()) return null;
+ final long startPsiModificationTimeStamp = file.getModificationStamp();
+ final long startDocModificationTimeStamp = document.getModificationStamp();
+ final FileElement myTreeElementBeingReparsedSoItWontBeCollected = ((PsiFileImpl)file).calcTreeElement();
+ if (textBlock.isEmpty()) return null; // if tree was just loaded above textBlock will be cleared by contentsLoaded
+ final CharSequence chars = document.getCharsSequence();
+ final Boolean data = document.getUserData(BlockSupport.DO_NOT_REPARSE_INCREMENTALLY);
+ if (data != null) {
+ document.putUserData(BlockSupport.DO_NOT_REPARSE_INCREMENTALLY, null);
+ file.putUserData(BlockSupport.DO_NOT_REPARSE_INCREMENTALLY, data);
+ }
+ final String oldPsiText =
+ ApplicationManagerEx.getApplicationEx().isInternal() && !ApplicationManagerEx.getApplicationEx().isUnitTestMode()
+ ? myTreeElementBeingReparsedSoItWontBeCollected.getText()
+ : null;
+ int startOffset;
+ int endOffset;
+ int lengthShift;
+ if (file.getViewProvider().supportsIncrementalReparse(file.getLanguage())) {
+ startOffset = textBlock.getStartOffset();
+ int psiEndOffset = textBlock.getPsiEndOffset();
+ endOffset = psiEndOffset;
+ lengthShift = textBlock.getTextEndOffset() - psiEndOffset;
+ }
+ else {
+ startOffset = 0;
+ endOffset = document.getTextLength();
+ lengthShift = document.getTextLength() - myTreeElementBeingReparsedSoItWontBeCollected.getTextLength();
+ }
+ assertBeforeCommit(document, file, textBlock, chars, oldPsiText, myTreeElementBeingReparsedSoItWontBeCollected);
+ BlockSupport blockSupport = BlockSupport.getInstance(file.getProject());
+ final DiffLog diffLog = blockSupport.reparseRange(file, startOffset, endOffset, lengthShift, chars, indicator);
+
+ return new Processor() {
+ @Override
+ public boolean process(Document document) {
+ ApplicationManager.getApplication().assertWriteAccessAllowed();
+
+ //if (file.getModificationStamp() != startPsiModificationTimeStamp) return; // optimistic locking failed
+ if (document.getModificationStamp() != startDocModificationTimeStamp) {
+ return false; // optimistic locking failed
+ }
+
+ try {
+ textBlock.performAtomically(new Runnable() {
+ @Override
+ public void run() {
+ file.getManager().performActionWithFormatterDisabled(new Runnable() {
+ @Override
+ public void run() {
+ synchronized (PsiLock.LOCK) {
+ diffLog.doActualPsiChange(file);
+ }
+ }
+ });
+ }
+ });
+
+ assertAfterCommit(document, file, oldPsiText, myTreeElementBeingReparsedSoItWontBeCollected);
+ }
+ finally {
+ textBlock.clear();
+ SmartPointerManagerImpl.synchronizePointers(file);
+ }
+ return true;
+ }
+ };
+ }
+
+
+ private static void assertBeforeCommit(Document document,
+ PsiFile file,
+ TextBlock textBlock,
+ CharSequence chars,
+ String oldPsiText,
+ FileElement myTreeElementBeingReparsedSoItWontBeCollected) {
+ int startOffset = textBlock.getStartOffset();
+ int psiEndOffset = textBlock.getPsiEndOffset();
+ if (oldPsiText != null) {
+ String msg = "PSI/document inconsistency before reparse: ";
+ if (startOffset >= oldPsiText.length()) {
+ msg += "startOffset=" + oldPsiText + " while text length is " + oldPsiText.length() + "; ";
+ startOffset = oldPsiText.length();
+ }
+
+ String psiPrefix = oldPsiText.substring(0, startOffset);
+ String docPrefix = chars.subSequence(0, startOffset).toString();
+ String psiSuffix = oldPsiText.substring(psiEndOffset);
+ String docSuffix = chars.subSequence(textBlock.getTextEndOffset(), chars.length()).toString();
+ if (!psiPrefix.equals(docPrefix) || !psiSuffix.equals(docSuffix)) {
+ if (!psiPrefix.equals(docPrefix)) {
+ msg = msg + "psiPrefix=" + psiPrefix + "; docPrefix=" + docPrefix + ";";
+ }
+ if (!psiSuffix.equals(docSuffix)) {
+ msg = msg + "psiSuffix=" + psiSuffix + "; docSuffix=" + docSuffix + ";";
+ }
+ throw new AssertionError(msg);
+ }
+ }
+ else if (document.getTextLength() - textBlock.getTextEndOffset() !=
+ myTreeElementBeingReparsedSoItWontBeCollected.getTextLength() - psiEndOffset) {
+ throw new AssertionError("PSI/document inconsistency before reparse: file=" + file);
+ }
+ }
+
+ private static void assertAfterCommit(Document document,
+ final PsiFile file,
+ String oldPsiText,
+ FileElement myTreeElementBeingReparsedSoItWontBeCollected) {
+ if (myTreeElementBeingReparsedSoItWontBeCollected.getTextLength() != document.getTextLength()) {
+ final String documentText = document.getText();
+ if (ApplicationManagerEx.getApplicationEx().isInternal()) {
+ String fileText = file.getText();
+ LOG.error("commitDocument left PSI inconsistent; file len=" + myTreeElementBeingReparsedSoItWontBeCollected.getTextLength() +
+ "; doc len=" + document.getTextLength() +
+ "; doc.getText() == file.getText(): " + Comparing.equal(fileText, documentText) +
+ ";\n file psi text=" + fileText +
+ ";\n doc text=" + documentText +
+ ";\n old psi file text=" + oldPsiText);
+ }
+ else {
+ LOG.error("commitDocument left PSI inconsistent: " + file);
+ }
+
+ file.putUserData(BlockSupport.DO_NOT_REPARSE_INCREMENTALLY, Boolean.TRUE);
+ try {
+ BlockSupport blockSupport = BlockSupport.getInstance(file.getProject());
+ final DiffLog diffLog = blockSupport.reparseRange(file, 0, documentText.length(), 0, documentText, new DaemonProgressIndicator());
+ file.getManager().performActionWithFormatterDisabled(new Runnable() {
+ @Override
+ public void run() {
+ synchronized (PsiLock.LOCK) {
+ diffLog.doActualPsiChange(file);
+ }
+ }
+ });
+
+ if (myTreeElementBeingReparsedSoItWontBeCollected.getTextLength() != document.getTextLength()) {
+ LOG.error("PSI is broken beyond repair in: " + file);
+ }
+ }
+ finally {
+ file.putUserData(BlockSupport.DO_NOT_REPARSE_INCREMENTALLY, null);
+ }
+ }
+ }
+}
diff --git a/platform/lang-impl/src/com/intellij/psi/impl/PsiDocumentManagerImpl.java b/platform/lang-impl/src/com/intellij/psi/impl/PsiDocumentManagerImpl.java
index 0a9583e0058a..df01d97762a9 100644
--- a/platform/lang-impl/src/com/intellij/psi/impl/PsiDocumentManagerImpl.java
+++ b/platform/lang-impl/src/com/intellij/psi/impl/PsiDocumentManagerImpl.java
@@ -20,8 +20,8 @@ import com.intellij.AppTopics;
import com.intellij.injected.editor.DocumentWindow;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.application.Application;
+import com.intellij.openapi.application.ApplicationAdapter;
import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.application.ex.ApplicationManagerEx;
import com.intellij.openapi.components.ProjectComponent;
import com.intellij.openapi.components.SettingsSavingComponent;
import com.intellij.openapi.diagnostic.Logger;
@@ -38,18 +38,18 @@ import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectLocator;
-import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Ref;
+import com.intellij.openapi.util.UserDataHolderEx;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.impl.smartPointers.SmartPointerManagerImpl;
import com.intellij.psi.impl.source.PostprocessReformattingAspect;
import com.intellij.psi.impl.source.PsiFileImpl;
-import com.intellij.psi.impl.source.text.BlockSupportImpl;
import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
import com.intellij.psi.text.BlockSupport;
+import com.intellij.util.Processor;
import com.intellij.util.SmartList;
import com.intellij.util.concurrency.Semaphore;
import com.intellij.util.containers.ContainerUtil;
@@ -67,31 +67,29 @@ import java.util.*;
public class PsiDocumentManagerImpl extends PsiDocumentManager implements ProjectComponent, DocumentListener, SettingsSavingComponent {
private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.PsiDocumentManagerImpl");
private static final Key HARD_REF_TO_PSI = new Key("HARD_REFERENCE_TO_PSI");
- private static final Key KEY_COMMITTING = new Key("Committing");
+ private static final Key COMMIT_STAGE = new Key("Commit stage");
private static final Key> ACTION_AFTER_COMMIT = Key.create("ACTION_AFTER_COMMIT");
private final Project myProject;
private final PsiManager myPsiManager;
- private final Key KEY_TEXT_BLOCK = Key.create("KEY_TEXT_BLOCK");
+ private static final Key KEY_TEXT_BLOCK = Key.create("KEY_TEXT_BLOCK");
private final Set myUncommittedDocuments = Collections.synchronizedSet(new HashSet());
- private final BlockSupportImpl myBlockSupport;
private volatile boolean myIsCommitInProgress;
private final PsiToDocumentSynchronizer mySynchronizer;
private final List myListeners = ContainerUtil.createEmptyCOWList();
private final SmartPointerManagerImpl mySmartPointerManager;
- public PsiDocumentManagerImpl(Project project,
- PsiManager psiManager,
- SmartPointerManager smartPointerManager,
- BlockSupport blockSupport,
- EditorFactory editorFactory,
- MessageBus bus) {
+ public PsiDocumentManagerImpl(@NotNull Project project,
+ @NotNull PsiManager psiManager,
+ @NotNull SmartPointerManager smartPointerManager,
+ @NotNull EditorFactory editorFactory,
+ @NotNull MessageBus bus,
+ @NotNull final DocumentCommitThread documentCommitThread) {
myProject = project;
myPsiManager = psiManager;
mySmartPointerManager = (SmartPointerManagerImpl)smartPointerManager;
- myBlockSupport = (BlockSupportImpl)blockSupport;
mySynchronizer = new PsiToDocumentSynchronizer(this, bus);
myPsiManager.addPsiTreeChangeListener(mySynchronizer);
editorFactory.getEventMulticaster().addDocumentListener(this, myProject);
@@ -106,6 +104,12 @@ public class PsiDocumentManagerImpl extends PsiDocumentManager implements Projec
fireDocumentCreated(document, psiFile);
}
});
+ ApplicationManager.getApplication().addApplicationListener(new ApplicationAdapter() {
+ @Override
+ public void beforeWriteActionStart(Object action) {
+ documentCommitThread.cancel();
+ }
+ }, myProject);
}
public void projectOpened() {
@@ -143,17 +147,11 @@ public class PsiDocumentManagerImpl extends PsiDocumentManager implements Projec
psiFile = getPsiFile(virtualFile);
if (psiFile == null) return null;
- //psiFile.setModificationStamp(document.getModificationStamp());
fireFileCreated(document, psiFile);
return psiFile;
}
- Project getProject() {
- return myProject;
- }
-
-
public static void cachePsi(@NotNull Document document, @NotNull PsiFile file) {
document.putUserData(HARD_REF_TO_PSI, file);
}
@@ -220,26 +218,44 @@ public class PsiDocumentManagerImpl extends PsiDocumentManager implements Projec
public void commitAllDocuments() {
ApplicationManager.getApplication().assertIsDispatchThread();
if (myUncommittedDocuments.isEmpty()) return;
- //long time1 = System.currentTimeMillis();
final Document[] documents = getUncommittedDocuments();
for (Document document : documents) {
commitDocument(document);
}
- //long time2 = System.currentTimeMillis();
- //Statistics.commitTime += (time2 - time1);
+ LOG.assertTrue(!hasUncommitedDocuments(), myUncommittedDocuments);
}
@Override
public void performForCommittedDocument(@NotNull final Document doc, @NotNull final Runnable action) {
final Document document = doc instanceof DocumentWindow ? ((DocumentWindow)doc).getDelegate() : doc;
- if (isUncommited(document)) {
- addRunOnCommit(document, action);
- }
- else {
+ if (isCommitted(document)) {
action.run();
}
+ else {
+ addRunOnCommit(document, action);
+ }
+ }
+
+ private final Map