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 actionsWhenAllDocumentsAreCommitted = new LinkedHashMap(); //accessed from EDT only + + // returns true if action has been run immediately + @Override + public boolean performWhenAllDocumentsAreCommitted(@NotNull Object key, @NotNull final Runnable action) { + ApplicationManager.getApplication().assertIsDispatchThread(); + if (myProject.isDisposed()) { + action.run(); + return true; + } + if (hasUncommitedDocuments()) { + actionsWhenAllDocumentsAreCommitted.put(key, action); + return false; + } + + action.run(); + assert actionsWhenAllDocumentsAreCommitted.isEmpty() : actionsWhenAllDocumentsAreCommitted; + return true; } public static void addRunOnCommit(@NotNull Document document, @NotNull Runnable action) { @@ -254,49 +270,75 @@ public class PsiDocumentManagerImpl extends PsiDocumentManager implements Projec public void commitDocument(@NotNull final Document doc) { final Document document = doc instanceof DocumentWindow ? ((DocumentWindow)doc).getDelegate() : doc; - if (isUncommited(document)) { + if (!isCommitted(document)) { doCommit(document, null); } } - private void doCommit(final Document document, final PsiFile excludeFile) { + boolean finishCommit(@NotNull final Document document, final List> finishRunnables, boolean synchronously) { + if (myProject.isDisposed()) return false; + assert !(document instanceof DocumentWindow); + final boolean[] result = {true}; ApplicationManager.getApplication().runWriteAction(new CommitToPsiFileAction(document,myProject) { public void run() { - if (isCommittingDocument(document)) return; - document.putUserData(KEY_COMMITTING, Boolean.TRUE); - + CommitStage stage = getCommitStage(document); + if (stage != CommitStage.QUEUED_TO_COMMIT) { + result[0] = false; + return; // there must be a synchronous commit sneaked in between queued commit and finish commit, or just document changed meanwhile + } + myIsCommitInProgress = true; + boolean success = true; try { - boolean hasCommits = false; - try { - final FileViewProvider viewProvider = getCachedViewProvider(document); - if (viewProvider != null) { - final List psiFiles = viewProvider.getAllFiles(); - for (PsiFile file : psiFiles) { - if (file.isValid() && file != excludeFile) { - hasCommits |= commit(document, file); - } + final FileViewProvider viewProvider = getCachedViewProvider(document); + if (viewProvider != null) { + for (Processor finishRunnable : finishRunnables) { + success = finishRunnable.process(document); + if (!success) { + result[0] = false; + return; } - viewProvider.contentsSynchronized(); } - } - catch (Exception e) { - throw new RuntimeException(e); - } - finally { - myUncommittedDocuments.remove(document); - } - - if (hasCommits) { - InjectedLanguageUtil.commitAllInjectedDocuments(document, myProject); + viewProvider.contentsSynchronized(); } } finally { - document.putUserData(KEY_COMMITTING, null); + if (success) { + myUncommittedDocuments.remove(document); + + boolean changed = changeCommitStage(document, CommitStage.QUEUED_TO_COMMIT, CommitStage.COMMITTED); + assert changed; + + ((DocumentImpl)document).normalizeRangeMarkers(); + InjectedLanguageUtil.commitAllInjectedDocuments(document, myProject); + } + myIsCommitInProgress = false; } } }); + if (result[0]) { + runAfterCommitActions(document); + } + return result[0]; + } + + private void doCommit(@NotNull final Document document, final PsiFile excludeFile) { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + // otherwise there are many clients calling commitAllDocs() on PSI childrenChanged() + if (getSynchronizer().isDocumentAffectedByTransactions(document) && excludeFile == null) return; + + myIsCommitInProgress = true; + //setCommitStage(document, CommitStage.QUEUED_TO_COMMIT); + DocumentCommitThread.getInstance(myProject).commitSynchronously(myProject, document, excludeFile); + myIsCommitInProgress = false; + assert !myUncommittedDocuments.contains(document); + } + }); + } + + private void runAfterCommitActions(@NotNull Document document) { List list; synchronized (ACTION_AFTER_COMMIT) { list = document.getUserData(ACTION_AFTER_COMMIT); @@ -310,6 +352,14 @@ public class PsiDocumentManagerImpl extends PsiDocumentManager implements Projec runnable.run(); } } + + if (!hasUncommitedDocuments() && !actionsWhenAllDocumentsAreCommitted.isEmpty()) { + List keys = new ArrayList(actionsWhenAllDocumentsAreCommitted.keySet()); + for (Object key : keys) { + Runnable action = actionsWhenAllDocumentsAreCommitted.remove(key); + action.run(); + } + } } public void commitOtherFilesAssociatedWithDocument(final Document document, final PsiFile psiFile) { @@ -321,7 +371,7 @@ public class PsiDocumentManagerImpl extends PsiDocumentManager implements Projec } }); } - } + } public T commitAndRunReadAction(@NotNull final Computable computation) { final Ref ref = Ref.create(null); @@ -340,7 +390,7 @@ public class PsiDocumentManagerImpl extends PsiDocumentManager implements Projec runnable.run(); } else{ - LOG.assertTrue(!ApplicationManager.getApplication().isReadAccessAllowed(), "Don't call commitAndRunReadAction inside ReadAction it may cause a deadlock."); + LOG.assertTrue(!ApplicationManager.getApplication().isReadAccessAllowed(), "Don't call commitAndRunReadAction inside ReadAction, it will cause a deadlock otherwise."); final Semaphore s1 = new Semaphore(); final Semaphore s2 = new Semaphore(); @@ -421,137 +471,19 @@ public class PsiDocumentManagerImpl extends PsiDocumentManager implements Projec } } - @SuppressWarnings({"ALL"}) - private ASTNode myTreeElementBeingReparsedSoItWontBeCollected; - - private boolean commit(@NotNull Document document, @NotNull PsiFile file) { - document.putUserData(TEMP_TREE_IN_DOCUMENT_KEY, null); - - TextBlock textBlock = getTextBlock(document, file); - if (textBlock.isEmpty()) return false; - ((DocumentImpl)document).normalizeRangeMarkers(); - myIsCommitInProgress = true; - try { - myTreeElementBeingReparsedSoItWontBeCollected = ((PsiFileImpl)file).calcTreeElement(); - - if (textBlock.isEmpty()) return false ; // if tree was just loaded above textBlock will be cleared by contentsLoaded - - textBlock.lock(); - 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); - myBlockSupport.reparseRange(file, startOffset, endOffset, lengthShift, chars); - assertAfterCommit(document, file, oldPsiText); - } - finally { - textBlock.unlock(); - textBlock.clear(); - - myTreeElementBeingReparsedSoItWontBeCollected = null; - myIsCommitInProgress = false; - - if (mySmartPointerManager != null) { // mock tests - SmartPointerManagerImpl.synchronizePointers(file); - } - } - return true; - } - - private void assertAfterCommit(final Document document, final PsiFile file, final String oldPsiText) { - 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 { - myBlockSupport.reparseRange(file, 0, documentText.length(), 0, documentText); - if (myTreeElementBeingReparsedSoItWontBeCollected.getTextLength() != document.getTextLength()) { - LOG.error("PSI is broken beyond repair in: " + file); - } - } - finally { - file.putUserData(BlockSupport.DO_NOT_REPARSE_INCREMENTALLY, null); - } - } - } - - private void assertBeforeCommit(Document document, - PsiFile file, - TextBlock textBlock, - CharSequence chars, - String oldPsiText) { - 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); - } - } - @NotNull public Document[] getUncommittedDocuments() { return myUncommittedDocuments.toArray(new Document[myUncommittedDocuments.size()]); } public boolean isUncommited(@NotNull Document document) { - if(getSynchronizer().isInSynchronization(document)) return false; - return ((DocumentEx)document).isInEventsHandling() || myUncommittedDocuments.contains(document); + return !isCommitted(document); + } + + @Override + public boolean isCommitted(@NotNull Document document) { + if (getSynchronizer().isInSynchronization(document)) return true; + return !((DocumentEx)document).isInEventsHandling() && !myUncommittedDocuments.contains(document); } public boolean hasUncommitedDocuments() { @@ -559,6 +491,9 @@ public class PsiDocumentManagerImpl extends PsiDocumentManager implements Projec } private final Key TEMP_TREE_IN_DOCUMENT_KEY = Key.create("TEMP_TREE_IN_DOCUMENT_KEY"); + void clearTreeHardRef(@NotNull Document document) { + document.putUserData(TEMP_TREE_IN_DOCUMENT_KEY, null); + } public void beforeDocumentChange(DocumentEvent event) { final Document document = event.getDocument(); @@ -571,7 +506,7 @@ public class PsiDocumentManagerImpl extends PsiDocumentManager implements Projec if (virtualFile.getFileType().isBinary()) return; final List files = viewProvider.getAllFiles(); - boolean hasLockedBlocks = false; + boolean hasLockedBlocks = false; for (PsiFile file : files) { if (file == null) continue; @@ -579,7 +514,7 @@ public class PsiDocumentManagerImpl extends PsiDocumentManager implements Projec SmartPointerManagerImpl.fastenBelts(file, event.getOffset()); } - final TextBlock textBlock = getTextBlock(document, file); + final TextBlock textBlock = getTextBlock(file); if (textBlock.isLocked()) { hasLockedBlocks = true; continue; @@ -601,6 +536,8 @@ public class PsiDocumentManagerImpl extends PsiDocumentManager implements Projec if (!hasLockedBlocks) { ((SingleRootFileViewProvider)viewProvider).beforeDocumentChanged(); } + + DocumentCommitThread.getInstance(myProject).cancelCommit(document); } public void documentChanged(DocumentEvent event) { @@ -609,6 +546,7 @@ public class PsiDocumentManagerImpl extends PsiDocumentManager implements Projec if (viewProvider == null) return; if (!isRelevant(viewProvider)) return; + ApplicationManager.getApplication().assertWriteAccessAllowed(); final List files = viewProvider.getAllFiles(); boolean commitNecessary = false; for (PsiFile file : files) { @@ -616,23 +554,30 @@ public class PsiDocumentManagerImpl extends PsiDocumentManager implements Projec if (mySmartPointerManager != null) { // mock tests SmartPointerManagerImpl.unfastenBelts(file, event.getOffset()); } - final TextBlock textBlock = getTextBlock(document, file); + final TextBlock textBlock = getTextBlock(file); if (textBlock.isLocked()) continue; - textBlock.documentChanged(event); assert file instanceof PsiFileImpl || "mock.file".equals(file.getName()) && ApplicationManager.getApplication().isUnitTestMode() : event + "; file="+file+"; allFiles="+files+"; viewProvider="+viewProvider; - myUncommittedDocuments.add(document); + commitNecessary = true; } + if (commitNecessary) { + myUncommittedDocuments.add(document); + // irrespective of prev value + setCommitStage(document, CommitStage.DIRTY); + + DocumentCommitThread.getInstance(myProject).queueCommit(document); + } + // Consider that it's worth to perform complete re-parse instead of merge if the whole document text is replaced and // current document lines number is roughly above 5000. This makes sense in situations when external change is performed // for the huge file (that causes the whole document to be reloaded and 'merge' way takes a while to complete). if (event.isWholeTextReplaced() && document.getTextLength() > 100000) { document.putUserData(BlockSupport.DO_NOT_REPARSE_INCREMENTALLY, Boolean.TRUE); } - + if (commitNecessary && ApplicationManager.getApplication().hasWriteAction(ExternalChangeAction.class)){ commitDocument(document); } @@ -642,17 +587,16 @@ public class PsiDocumentManagerImpl extends PsiDocumentManager implements Projec } } - private boolean isRelevant(FileViewProvider viewProvider) { VirtualFile virtualFile = viewProvider.getVirtualFile(); return !virtualFile.getFileType().isBinary() && viewProvider.getManager() == myPsiManager && !myPsiManager.getProject().isDisposed(); } - public TextBlock getTextBlock(Document document, PsiFile file) { + @NotNull + public static TextBlock getTextBlock(@NotNull PsiFile file) { TextBlock textBlock = file.getUserData(KEY_TEXT_BLOCK); if (textBlock == null){ - textBlock = new TextBlock(); - file.putUserData(KEY_TEXT_BLOCK, textBlock); + textBlock = ((UserDataHolderEx)file).putUserDataIfAbsent(KEY_TEXT_BLOCK, new TextBlock()); } return textBlock; @@ -724,7 +668,7 @@ public class PsiDocumentManagerImpl extends PsiDocumentManager implements Projec public void contentsLoaded(PsiFileImpl file) { final Document document = getCachedDocument(file); - if (document != null) getTextBlock(document, file).clear(); + if (document != null) getTextBlock(file).clear(); } @TestOnly @@ -737,12 +681,27 @@ public class PsiDocumentManagerImpl extends PsiDocumentManager implements Projec return mySynchronizer; } - private static boolean isCommittingDocument(final Document doc) { - return doc.getUserData(KEY_COMMITTING) == Boolean.TRUE; + static CommitStage getCommitStage(@NotNull Document doc) { + CommitStage stage = doc.getUserData(COMMIT_STAGE); + if (stage == null) { + stage = ((UserDataHolderEx)doc).putUserDataIfAbsent(COMMIT_STAGE, CommitStage.DIRTY); + } + return stage; + } + static void setCommitStage(@NotNull Document document, @NotNull CommitStage stage) { + document.putUserData(COMMIT_STAGE, stage); + } + static boolean changeCommitStage(@NotNull Document document, CommitStage expected, @NotNull CommitStage stage) { + return ((UserDataHolderEx)document).replace(COMMIT_STAGE, expected, stage); } + public void save() { - // Ensure all documents are commited on save so file content dependent indicies, that use PSI to build have consistent content. + // Ensure all documents are committed on save so file content dependent indices, that use PSI to build have consistent content. commitAllDocuments(); } + + static enum CommitStage { + DIRTY, QUEUED_TO_COMMIT, COMMITTED + } } diff --git a/platform/lang-impl/src/com/intellij/psi/impl/PsiToDocumentSynchronizer.java b/platform/lang-impl/src/com/intellij/psi/impl/PsiToDocumentSynchronizer.java index 4f54d8e1ee98..3c9c24c32692 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/PsiToDocumentSynchronizer.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/PsiToDocumentSynchronizer.java @@ -23,10 +23,12 @@ import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.ex.DocumentEx; +import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; import com.intellij.util.messages.MessageBus; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; @@ -66,28 +68,27 @@ public class PsiToDocumentSynchronizer extends PsiTreeChangeAdapter { void syncDocument(Document document, PsiTreeChangeEventImpl event); } - private void doSync(PsiTreeChangeEvent event, DocSyncAction syncAction) { + private void doSync(final PsiTreeChangeEvent event, final DocSyncAction syncAction) { if (!toProcessPsiEvent()) return; PsiFile psiFile = event.getFile(); if (psiFile == null || psiFile.getNode() == null) return; - DocumentEx document = getCachedDocument(psiFile); + final DocumentEx document = (DocumentEx)myPsiDocumentManager.getCachedDocument(psiFile); if (document == null || document instanceof DocumentWindow) return; - TextBlock textBlock = getTextBlock(document, psiFile); + TextBlock textBlock = PsiDocumentManagerImpl.getTextBlock(psiFile); if (!textBlock.isEmpty()) { LOG.error("Attempt to modify PSI for non-committed Document!"); textBlock.clear(); } - textBlock.lock(); - try { - syncAction.syncDocument(document, (PsiTreeChangeEventImpl)event); - } - finally { - textBlock.unlock(); - } + textBlock.performAtomically(new Runnable() { + @Override + public void run() { + syncAction.syncDocument(document, (PsiTreeChangeEventImpl)event); + } + }); myPsiDocumentManager.commitOtherFilesAssociatedWithDocument(document, psiFile); @@ -160,17 +161,8 @@ public class PsiToDocumentSynchronizer extends PsiTreeChangeAdapter { } } - @Nullable - private DocumentEx getCachedDocument(PsiFile file) { - return (DocumentEx)myPsiDocumentManager.getCachedDocument(file); - } - - private TextBlock getTextBlock(Document document, PsiFile file) { - return myPsiDocumentManager.getTextBlock(document, file); - } - - public void startTransaction(Document doc, PsiElement scope) { - LOG.assertTrue(!myPsiDocumentManager.getProject().isDisposed()); + public void startTransaction(@NotNull Project project, Document doc, PsiElement scope) { + LOG.assertTrue(!project.isDisposed()); Pair pair = myTransactionsMap.get(doc); if (pair == null) { final PsiFile psiFile = scope != null ? scope.getContainingFile() : null; diff --git a/platform/lang-impl/src/com/intellij/psi/impl/TextBlock.java b/platform/lang-impl/src/com/intellij/psi/impl/TextBlock.java index 13f5e4eb1581..42d11720377a 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/TextBlock.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/TextBlock.java @@ -19,10 +19,11 @@ package com.intellij.psi.impl; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.event.DocumentAdapter; import com.intellij.openapi.editor.event.DocumentEvent; +import org.jetbrains.annotations.NotNull; public class TextBlock extends DocumentAdapter { @SuppressWarnings({"UnusedDeclaration"}) - private Document myDocument; // Will hold a document on a hard reference until there's uncommited PSI for this document. + private Document myDocument; // Will hold a document on a hard reference until there's uncommitted PSI for this document. private int myStartOffset = -1; private int myTextEndOffset = -1; @@ -46,11 +47,11 @@ public class TextBlock extends DocumentAdapter { return myTextEndOffset; } - public void lock() { + private void lock() { myIsLocked = true; } - public void unlock() { + private void unlock() { myIsLocked = false; } @@ -86,4 +87,19 @@ public class TextBlock extends DocumentAdapter { myStartOffset = Math.min(myStartOffset, offset); } } + + public void performAtomically(@NotNull Runnable runnable) { + boolean wasLocked = isLocked(); + if (!wasLocked) { + lock(); + } + try { + runnable.run(); + } + finally { + if (!wasLocked) { + unlock(); + } + } + } } diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/text/ASTDiffBuilder.java b/platform/lang-impl/src/com/intellij/psi/impl/source/text/ASTDiffBuilder.java index f7a42aff9002..70d342d6bb16 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/text/ASTDiffBuilder.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/text/ASTDiffBuilder.java @@ -26,111 +26,39 @@ import com.intellij.pom.tree.events.ChangeInfo; import com.intellij.pom.tree.events.impl.ChangeInfoImpl; import com.intellij.pom.tree.events.impl.ReplaceChangeInfoImpl; import com.intellij.pom.tree.events.impl.TreeChangeEventImpl; -import com.intellij.psi.PsiElement; -import com.intellij.psi.impl.DebugUtil; -import com.intellij.psi.impl.PsiManagerEx; -import com.intellij.psi.impl.PsiTreeChangeEventImpl; import com.intellij.psi.impl.source.PsiFileImpl; -import com.intellij.psi.impl.source.tree.CompositeElement; import com.intellij.psi.impl.source.tree.FileElement; -import com.intellij.psi.impl.source.tree.TreeElement; -import com.intellij.psi.impl.source.tree.TreeUtil; import com.intellij.util.diff.DiffTreeChangeBuilder; import org.jetbrains.annotations.NotNull; public class ASTDiffBuilder implements DiffTreeChangeBuilder { private final TreeChangeEventImpl myEvent; - private final PsiFileImpl myFile; - private final PsiManagerEx myPsiManager; - private final boolean myIsPhysicalScope; - - public ASTDiffBuilder(final PsiFileImpl fileImpl) { - myFile = fileImpl; - myIsPhysicalScope = fileImpl.isPhysical(); - myPsiManager = (PsiManagerEx)fileImpl.getManager(); - myEvent = new TreeChangeEventImpl(PomManager.getModel(fileImpl.getProject()).getModelAspect(TreeAspect.class), fileImpl.getTreeElement()); + public ASTDiffBuilder(@NotNull PsiFileImpl fileImpl) { + TreeAspect modelAspect = PomManager.getModel(fileImpl.getProject()).getModelAspect(TreeAspect.class); + myEvent = new TreeChangeEventImpl(modelAspect, fileImpl.getTreeElement()); } public void nodeReplaced(@NotNull ASTNode oldNode, @NotNull ASTNode newNode) { if (oldNode instanceof FileElement && newNode instanceof FileElement) { - BlockSupportImpl.replaceFileElement(myFile, (FileElement)oldNode, (FileElement)newNode, myPsiManager); } else { - final ASTNode parent = oldNode.getTreeParent(); - assert parent != null : "old:" + oldNode + " new:" + newNode; - - TreeUtil.ensureParsed(oldNode); - - final PsiElement psiParent = parent.getPsi(); - final PsiElement psiChild = myIsPhysicalScope ? oldNode.getPsi() : null; - if (psiParent != null && psiChild != null) { - final PsiTreeChangeEventImpl event = new PsiTreeChangeEventImpl(myPsiManager); - event.setParent(psiParent); - event.setChild(psiChild); - myPsiManager.beforeChildReplacement(event); - } - - ((TreeElement)newNode).rawRemove(); - ((TreeElement)oldNode).rawReplaceWithList((TreeElement)newNode); - - final ReplaceChangeInfoImpl change = (ReplaceChangeInfoImpl)ChangeInfoImpl.create(ChangeInfo.REPLACE, newNode); - + final ReplaceChangeInfoImpl change = new ReplaceChangeInfoImpl(newNode); change.setReplaced(oldNode); - myEvent.addElementaryChange(newNode, change); - ((TreeElement)newNode).clearCaches(); - if (!(newNode instanceof FileElement)) { - ((CompositeElement)newNode.getTreeParent()).subtreeChanged(); - } - DebugUtil.checkTreeStructureIfConfigured(parent); + myEvent.addElementaryChange(newNode, change); } } public void nodeDeleted(@NotNull ASTNode parent, @NotNull final ASTNode child) { - PsiElement psiParent = parent.getPsi(); - PsiElement psiChild = myIsPhysicalScope ? child.getPsi() : null; - - if (psiParent != null && psiChild != null) { - PsiTreeChangeEventImpl event = new PsiTreeChangeEventImpl(myPsiManager); - event.setParent(psiParent); - event.setChild(psiChild); - myPsiManager.beforeChildRemoval(event); - } - myEvent.addElementaryChange(child, ChangeInfoImpl.create(ChangeInfo.REMOVED, child)); - ((TreeElement)child).rawRemove(); - ((CompositeElement)parent).subtreeChanged(); - - DebugUtil.checkTreeStructureIfConfigured(parent); } - public void nodeInserted(@NotNull final ASTNode oldParent, @NotNull ASTNode node, final int pos) { - ASTNode anchor = null; - for (int i = 0; i < pos; i++) { - anchor = anchor == null ? oldParent.getFirstChildNode() : anchor.getTreeNext(); - } - - ((TreeElement)node).rawRemove(); - if (anchor != null) { - ((TreeElement)anchor).rawInsertAfterMe((TreeElement)node); - } - else { - if (oldParent.getFirstChildNode() != null) { - ((TreeElement)oldParent.getFirstChildNode()).rawInsertBeforeMe((TreeElement)node); - } - else { - ((CompositeElement)oldParent).rawAddChildren((TreeElement)node); - } - } - - myEvent.addElementaryChange(node, ChangeInfoImpl.create(ChangeInfo.ADD, node)); - ((TreeElement)node).clearCaches(); - ((CompositeElement)oldParent).subtreeChanged(); - - DebugUtil.checkTreeStructureIfConfigured(oldParent); + public void nodeInserted(@NotNull final ASTNode oldParent, @NotNull ASTNode newNode, final int pos) { + myEvent.addElementaryChange(newNode, ChangeInfoImpl.create(ChangeInfo.ADD, newNode)); } + @NotNull public TreeChangeEventImpl getEvent() { return myEvent; } diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/text/BlockSupportImpl.java b/platform/lang-impl/src/com/intellij/psi/impl/source/text/BlockSupportImpl.java index 553a6d56bf0b..de74bbc78ef9 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/text/BlockSupportImpl.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/text/BlockSupportImpl.java @@ -18,20 +18,15 @@ package com.intellij.psi.impl.source.text; import com.intellij.lang.ASTNode; import com.intellij.lang.Language; -import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ex.ApplicationManagerEx; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.ex.DocumentBulkUpdateListener; import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.TextRange; -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.psi.*; import com.intellij.psi.impl.PsiManagerEx; import com.intellij.psi.impl.PsiManagerImpl; @@ -48,7 +43,11 @@ import com.intellij.testFramework.LightVirtualFile; import com.intellij.util.CharTable; import com.intellij.util.IncorrectOperationException; 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 org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; public class BlockSupportImpl extends BlockSupport { private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.text.BlockSupportImpl"); @@ -70,20 +69,23 @@ public class BlockSupportImpl extends BlockSupport { PsiDocumentManager.getInstance(psiFile.getProject()).commitDocument(document); } - public void reparseRange(final PsiFile file, - final int startOffset, - final int endOffset, - final int lengthShift, - final CharSequence newFileText) { - // adjust editor offsets to damage area markers - file.getManager().performActionWithFormatterDisabled(new Runnable() { - public void run() { - reparseRangeInternal(file, startOffset > 0 ? startOffset - 1 : 0, endOffset, lengthShift, newFileText); - } - }); + @NotNull + public DiffLog reparseRange(@NotNull final PsiFile file, + final int startOffset, + final int endOffset, + final int lengthShift, + @NotNull final CharSequence newFileText, + @NotNull final ProgressIndicator indicator) { + return reparseRangeInternal(file, startOffset > 0 ? startOffset - 1 : 0, endOffset, lengthShift, newFileText, indicator); } - private static void reparseRangeInternal(PsiFile file, int startOffset, int endOffset, int lengthShift, CharSequence newFileText) { + @NotNull + private static DiffLog reparseRangeInternal(@NotNull PsiFile file, + int startOffset, + int endOffset, + int lengthShift, + @NotNull CharSequence newFileText, + @NotNull ProgressIndicator indicator) { file.getViewProvider().beforeContentsSynchronized(); final PsiFileImpl fileImpl = (PsiFileImpl)file; Project project = fileImpl.getProject(); @@ -94,8 +96,7 @@ public class BlockSupportImpl extends BlockSupport { if (treeFileElement.getElementType() instanceof ITemplateDataElementType || isTooDeep(file)) { // unable to perform incremental reparse for template data in JSP, or in exceptionally deep trees - makeFullParse(treeFileElement, newFileText, textLength, fileImpl); - return; + return makeFullParse(treeFileElement, newFileText, textLength, fileImpl, indicator); } final ASTNode leafAtStart = treeFileElement.findLeafElementAt(startOffset); @@ -123,15 +124,13 @@ public class BlockSupportImpl extends BlockSupport { holder.getTreeElement().rawAddChildren((TreeElement)chameleon); if (holder.getTextLength() != newTextStr.length()) { - if (ApplicationManagerEx.getApplicationEx().isInternal() && !ApplicationManager.getApplication().isUnitTestMode()) { - LOG.error("Inconsistent reparse: text=" + newTextStr + "; treeText=" + holder.getText() + "; type=" + elementType); - } else { - LOG.error("Inconsistent reparse: type=" + elementType); - } + String details = ApplicationManagerEx.getApplicationEx().isInternal() + ? "text=" + newTextStr + "; treeText=" + holder.getText() + ";" + : ""; + LOG.error("Inconsistent reparse: " + details + " type=" + elementType); } - mergeTrees(fileImpl, node, chameleon); - return; + return mergeTrees(fileImpl, node, chameleon, indicator); } } } @@ -139,7 +138,7 @@ public class BlockSupportImpl extends BlockSupport { node = node.getTreeParent(); } - makeFullParse(node, newFileText, textLength, fileImpl); + return makeFullParse(node, newFileText, textLength, fileImpl, indicator); } private static void assertFileLength(PsiFile file, CharSequence newFileText, ASTNode node, IElementType elementType, int start, int end) { @@ -160,26 +159,36 @@ public class BlockSupportImpl extends BlockSupport { } } - private static void makeFullParse(final ASTNode parent, final CharSequence newFileText, final int textLength, final PsiFileImpl fileImpl) { + @NotNull + private static DiffLog makeFullParse(ASTNode parent, + @NotNull CharSequence newFileText, + int textLength, + @NotNull PsiFileImpl fileImpl, + @NotNull ProgressIndicator indicator) { if (fileImpl instanceof PsiCodeFragment) { final FileElement holderElement = new DummyHolder(fileImpl.getManager(), null).getTreeElement(); holderElement.rawAddChildren(fileImpl.createContentLeafElement(holderElement.getCharTable().intern(newFileText, 0, textLength))); - replaceFileElement(fileImpl, (FileElement)parent, (FileElement)holderElement.getFirstChildNode(), (PsiManagerEx)fileImpl.getManager()); + DiffLog diffLog = new DiffLog(); + diffLog.appendReplaceFileElement((FileElement)parent, (FileElement)holderElement.getFirstChildNode()); + + return diffLog; } else { - final FileViewProvider viewProvider = fileImpl.getViewProvider(); + FileViewProvider viewProvider = fileImpl.getViewProvider(); + viewProvider.getLanguages(); FileType fileType = viewProvider.getVirtualFile().getFileType(); final LightVirtualFile lightFile = new LightVirtualFile(fileImpl.getName(), fileType, newFileText, viewProvider.getVirtualFile().getCharset(), fileImpl.getModificationStamp()); lightFile.setOriginalFile(viewProvider.getVirtualFile()); - final FileViewProvider copy = viewProvider.createCopy(lightFile); + FileViewProvider copy = viewProvider.createCopy(lightFile); + copy.getLanguages(); final PsiFileImpl newFile = (PsiFileImpl)copy.getPsi(fileImpl.getLanguage()); if (newFile == null) { LOG.error("View provider " + viewProvider + " refused to parse text with " + fileImpl.getLanguage() + "; base: " + viewProvider.getBaseLanguage() + "; copy: " + copy.getBaseLanguage() + "; fileType: " + fileType); - return; + return null; } newFile.setOriginalFile(fileImpl); @@ -187,108 +196,105 @@ public class BlockSupportImpl extends BlockSupport { final FileElement newFileElement = (FileElement)newFile.getNode(); final FileElement oldFileElement = (FileElement)fileImpl.getNode(); - final Boolean data = fileImpl.getUserData(DO_NOT_REPARSE_INCREMENTALLY); - if (data != null) fileImpl.putUserData(DO_NOT_REPARSE_INCREMENTALLY, null); + assert oldFileElement != null && newFileElement != null; + DiffLog diffLog = mergeTrees(fileImpl, oldFileElement, newFileElement, indicator); - if (Boolean.TRUE.equals(data) || isTooDeep(fileImpl)) { - // TODO: Just to switch off incremental tree patching for certain conditions (like languages) if necessary. - replaceElementWithEvents(fileImpl, oldFileElement, newFileElement); - } - else { - assert oldFileElement != null && newFileElement != null; - mergeTrees(fileImpl, oldFileElement, newFileElement); - } ((PsiManagerEx)fileImpl.getManager()).getFileManager().setViewProvider(lightFile, null); + return diffLog; } } - private static void replaceElementWithEvents(final PsiFileImpl file, final CompositeElement oldRoot, final CompositeElement newRoot) { + @NotNull + private static DiffLog replaceElementWithEvents(final CompositeElement oldRoot, + final CompositeElement newRoot) { + DiffLog diffLog = new DiffLog(); + diffLog.appendReplaceElementWithEvents(oldRoot, newRoot); + return diffLog; + } + + @NotNull + public static DiffLog mergeTrees(@NotNull final PsiFileImpl fileImpl, + @NotNull final ASTNode oldRoot, + @NotNull final ASTNode newRoot, + @NotNull ProgressIndicator indicator) { if (newRoot instanceof FileElement) { - file.getTreeElement().setCharTable(((FileElement)newRoot).getCharTable()); + ((FileElement)newRoot).setCharTable(fileImpl.getTreeElement().getCharTable()); } - oldRoot.replaceAllChildrenToChildrenOf(newRoot); - } - static void replaceFileElement(final PsiFileImpl fileImpl, - final FileElement fileElement, - final FileElement newFileElement, - final PsiManagerEx manager) { - final int oldLength = fileElement.getTextLength(); - sendPsiBeforeEvent(fileImpl); - if (fileElement.getFirstChildNode() != null) fileElement.rawRemoveAllChildren(); - final ASTNode firstChildNode = newFileElement.getFirstChildNode(); - if (firstChildNode != null) fileElement.rawAddChildren((TreeElement)firstChildNode); - fileImpl.getTreeElement().setCharTable(newFileElement.getCharTable()); - manager.invalidateFile(fileImpl); - fileElement.subtreeChanged(); - sendPsiAfterEvent(fileImpl, oldLength); - } - - public static void mergeTrees(@NotNull final PsiFileImpl file, @NotNull final ASTNode oldRoot, @NotNull final ASTNode newRoot) { - synchronized (PsiLock.LOCK) { - if (newRoot instanceof FileElement) { - ((FileElement)newRoot).setCharTable(file.getTreeElement().getCharTable()); - } - - try { - newRoot.putUserData(TREE_TO_BE_REPARSED, oldRoot); - - final ASTNode childNode; - try { - childNode = newRoot.getFirstChildNode(); // Ensure parsed - } - catch (ReparsedSuccessfullyException e) { - return; // Successfully merged in PsiBuilderImpl - } - - final boolean childTooDeep = isTooDeep(childNode); - if (isTooDeep(file) || childTooDeep) { - replaceElementWithEvents(file, (CompositeElement)oldRoot, (CompositeElement)newRoot); - - if (childTooDeep) { - childNode.putUserData(TREE_DEPTH_LIMIT_EXCEEDED, null); - file.putUserData(TREE_DEPTH_LIMIT_EXCEEDED, Boolean.TRUE); - } - return; - } - - TreeUtil.ensureParsedRecursively(oldRoot); - - final PomModel model = PomManager.getModel(file.getProject()); - model.runTransaction(new PomTransactionBase(file, model.getModelAspect(TreeAspect.class)) { - public PomModelEvent runInner() { - final ASTDiffBuilder builder = new ASTDiffBuilder(file); - DiffTree.diff(new ASTStructure(oldRoot), new ASTStructure(newRoot), new ASTShallowComparator(), builder); - file.subtreeChanged(); - - return new TreeAspectEvent(model, builder.getEvent()); - } - }); - } - catch (IncorrectOperationException e) { - LOG.error(e); - } - catch (Throwable th) { - LOG.error(th); - } - finally { - ((PsiManagerEx)file.getManager()).invalidateFile(file); + try { + if (isReplaceWholeNode(fileImpl, newRoot)) { + DiffLog treeChangeEvent = replaceElementWithEvents((CompositeElement)oldRoot, (CompositeElement)newRoot); + fileImpl.putUserData(TREE_DEPTH_LIMIT_EXCEEDED, Boolean.TRUE); + return treeChangeEvent; } + newRoot.putUserData(TREE_TO_BE_REPARSED, oldRoot); + newRoot.getFirstChildNode(); // maybe reparsed in PsiBuilderImpl and have thrown exception here } + catch (ReparsedSuccessfullyException e) { + // reparsed in PsiBuilderImpl + return e.getDiffLog(); + } + + final ASTShallowComparator comparator = new ASTShallowComparator(indicator); + final ASTStructure treeStructure = createInterruptibleASTStructure(newRoot, indicator); + + DiffLog diffLog = new DiffLog(); + diffTrees(oldRoot, diffLog, comparator, treeStructure, indicator); + return diffLog; } - private static void sendPsiBeforeEvent(final PsiFile scope) { - if (!scope.isPhysical()) return; + public static void diffTrees(@NotNull final ASTNode oldRoot, + @NotNull final DiffTreeChangeBuilder builder, + @NotNull final ShallowNodeComparator comparator, + @NotNull final FlyweightCapableTreeStructure newTreeStructure, + final ProgressIndicator indicator) { + TreeUtil.ensureParsedRecursivelyCheckingProgress(oldRoot, indicator); + DiffTree.diff(createInterruptibleASTStructure(oldRoot, indicator), newTreeStructure, comparator, builder); + } + + private static ASTStructure createInterruptibleASTStructure(@NotNull final ASTNode oldRoot, @Nullable final ProgressIndicator indicator) { + return new ASTStructure(oldRoot) { + @Override + public int getChildren(@NotNull ASTNode astNode, @NotNull Ref into) { + if (indicator != null) { + indicator.checkCanceled(); + } + return super.getChildren(astNode, into); + } + }; + } + + private static boolean isReplaceWholeNode(@NotNull PsiFileImpl fileImpl, @NotNull ASTNode newRoot) throws ReparsedSuccessfullyException{ + final Boolean data = fileImpl.getUserData(DO_NOT_REPARSE_INCREMENTALLY); + if (data != null) fileImpl.putUserData(DO_NOT_REPARSE_INCREMENTALLY, null); + + boolean explicitlyMarkedDeep = Boolean.TRUE.equals(data); + + if (explicitlyMarkedDeep || isTooDeep(fileImpl)) { + return true; + } + + final ASTNode childNode = newRoot.getFirstChildNode(); // maybe reparsed in PsiBuilderImpl and have thrown exception here + boolean childTooDeep = isTooDeep(childNode); + if (childTooDeep) { + childNode.putUserData(TREE_DEPTH_LIMIT_EXCEEDED, null); + fileImpl.putUserData(TREE_DEPTH_LIMIT_EXCEEDED, Boolean.TRUE); + } + return childTooDeep; + } + + public 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); - event.setOffset(0); + event.setFile(scope.getContainingFile()); + event.setOffset(scope.getTextRange().getStartOffset()); event.setOldLength(scope.getTextLength()); manager.beforeChildrenChange(event); } - private static void sendPsiAfterEvent(final PsiFileImpl scope, int oldLength) { + public static void sendPsiAfterFileEvent(final PsiFileImpl scope, int oldLength) { if (!scope.isPhysical()) return; final PsiManagerImpl manager = (PsiManagerImpl)scope.getManager(); PsiTreeChangeEventImpl event = new PsiTreeChangeEventImpl(manager); diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/text/DiffLog.java b/platform/lang-impl/src/com/intellij/psi/impl/source/text/DiffLog.java new file mode 100644 index 000000000000..dafa7a69fe7e --- /dev/null +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/text/DiffLog.java @@ -0,0 +1,287 @@ +/* + * 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.source.text; + +import com.intellij.lang.ASTNode; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.progress.ProgressManager; +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.psi.PsiDocumentManager; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.impl.*; +import com.intellij.psi.impl.source.PsiFileImpl; +import com.intellij.psi.impl.source.tree.CompositeElement; +import com.intellij.psi.impl.source.tree.FileElement; +import com.intellij.psi.impl.source.tree.TreeElement; +import com.intellij.util.IncorrectOperationException; +import com.intellij.util.diff.DiffTreeChangeBuilder; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +/** + * User: cdr + */ +public class DiffLog implements DiffTreeChangeBuilder { + private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.text.DiffLog"); + + public DiffLog() { + } + + private abstract static class LogEntry { + protected LogEntry() { + ProgressManager.checkCanceled(); + } + abstract void doActualPsiChange(@NotNull PsiFile file, @NotNull ASTDiffBuilder astDiffBuilder); + } + + private final List myEntries = new ArrayList(); + + public void doActualPsiChange(final PsiFile file){ + try { + + + final Document document = file.getViewProvider().getDocument(); + PsiToDocumentSynchronizer.DocumentChangeTransaction transaction = + ((PsiDocumentManagerImpl)PsiDocumentManager.getInstance(file.getProject())).getSynchronizer().getTransaction(document); + + final PsiFileImpl fileImpl = (PsiFileImpl)file; + + final ASTDiffBuilder astDiffBuilder = new ASTDiffBuilder(fileImpl); + + if (transaction == null) { + final PomModel model = PomManager.getModel(fileImpl.getProject()); + + model.runTransaction(new PomTransactionBase(fileImpl, model.getModelAspect(TreeAspect.class)) { + public PomModelEvent runInner() { + for (LogEntry entry : myEntries) { + entry.doActualPsiChange(file, astDiffBuilder); + } + fileImpl.subtreeChanged(); + + final PomModel model = PomManager.getModel(fileImpl.getProject()); + TreeAspectEvent treeAspectEvent = new TreeAspectEvent(model, astDiffBuilder.getEvent()); + return treeAspectEvent; + } + }); + } + else { + for (LogEntry entry : myEntries) { + entry.doActualPsiChange(file, astDiffBuilder); + } + fileImpl.subtreeChanged(); + } + } + catch (IncorrectOperationException e) { + LOG.error(e); + } + + + } + + @Override + public void nodeReplaced(@NotNull ASTNode oldNode, @NotNull ASTNode newNode) { + if (oldNode instanceof FileElement && newNode instanceof FileElement) { + appendReplaceFileElement((FileElement)oldNode, (FileElement)newNode); + } + else { + myEntries.add(new ReplaceEntry(oldNode, newNode)); + } + } + + public void appendReplaceElementWithEvents(CompositeElement oldRoot, CompositeElement newRoot) { + myEntries.add(new ReplaceElementWithEvents(oldRoot, newRoot)); + } + + public void appendReplaceFileElement(FileElement oldNode, FileElement newNode) { + myEntries.add(new ReplaceFileElement(oldNode, newNode)); + } + + @Override + public void nodeDeleted(@NotNull ASTNode oldParent, @NotNull ASTNode oldNode) { + myEntries.add(new DeleteEntry(oldParent, oldNode)); + + } + + @Override + public void nodeInserted(@NotNull ASTNode oldParent, @NotNull ASTNode newNode, int pos) { + myEntries.add(new InsertEntry(oldParent, newNode, pos)); + } + + private static class ReplaceEntry extends LogEntry { + private final ASTNode myOldChild; + private final ASTNode myNewChild; + + public ReplaceEntry(ASTNode oldChild, ASTNode newChild) { + myOldChild = oldChild; + myNewChild = newChild; + } + + @Override + void doActualPsiChange(@NotNull PsiFile file, @NotNull ASTDiffBuilder astDiffBuilder) { + ASTNode oldNode = myOldChild; + ASTNode parent = oldNode.getTreeParent(); + ASTNode newNode = myNewChild; + assert parent != null : "old:" + oldNode + " new:" + newNode; + + final PsiElement psiParent = parent.getPsi(); + final PsiElement psiChild = file.isPhysical() ? oldNode.getPsi() : null; + if (psiParent != null && psiChild != null) { + final PsiTreeChangeEventImpl event = new PsiTreeChangeEventImpl(file.getManager()); + event.setParent(psiParent); + event.setChild(psiChild); + ((PsiManagerEx)file.getManager()).beforeChildReplacement(event); + } + + ((TreeElement)newNode).rawRemove(); + ((TreeElement)oldNode).rawReplaceWithList((TreeElement)newNode); + + + astDiffBuilder.nodeReplaced(oldNode, newNode); + + ///////////////// + ((TreeElement)newNode).clearCaches(); + if (!(newNode instanceof FileElement)) { + ((CompositeElement)newNode.getTreeParent()).subtreeChanged(); + } + + DebugUtil.checkTreeStructure(parent); + + } + } + + private static class DeleteEntry extends LogEntry { + private final ASTNode myOldParent; + private final ASTNode myOldNode; + + public DeleteEntry(ASTNode oldParent, ASTNode oldNode) { + myOldParent = oldParent; + myOldNode = oldNode; + } + + @Override + void doActualPsiChange(@NotNull PsiFile file, @NotNull ASTDiffBuilder astDiffBuilder) { + ASTNode child = myOldNode; + ASTNode parent = myOldParent; + + PsiElement psiParent = parent.getPsi(); + PsiElement psiChild = file.isPhysical() ? child.getPsi() : null; + + if (psiParent != null && psiChild != null) { + PsiTreeChangeEventImpl event = new PsiTreeChangeEventImpl(file.getManager()); + event.setParent(psiParent); + event.setChild(psiChild); + ((PsiManagerEx)file.getManager()).beforeChildRemoval(event); + } + + astDiffBuilder.nodeDeleted(parent, child); + + ((TreeElement)child).rawRemove(); + ((CompositeElement)parent).subtreeChanged(); + + DebugUtil.checkTreeStructure(parent); + + } + } + + private static class InsertEntry extends LogEntry { + private final ASTNode myOldParent; + private final ASTNode myNewNode; + private final int myPos; + + public InsertEntry(ASTNode oldParent, ASTNode newNode, int pos) { + myOldParent = oldParent; + myNewNode = newNode; + myPos = pos; + } + + @Override + void doActualPsiChange(@NotNull PsiFile file, @NotNull ASTDiffBuilder astDiffBuilder) { + ASTNode anchor = null; + ASTNode firstChildNode = myOldParent.getFirstChildNode(); + for (int i = 0; i < myPos; i++) { + anchor = anchor == null ? firstChildNode : anchor.getTreeNext(); + } + + ((TreeElement)myNewNode).rawRemove(); + if (anchor != null) { + ((TreeElement)anchor).rawInsertAfterMe((TreeElement)myNewNode); + } + else { + if (firstChildNode != null) { + ((TreeElement)firstChildNode).rawInsertBeforeMe((TreeElement)myNewNode); + } + else { + ((CompositeElement)myOldParent).rawAddChildren((TreeElement)myNewNode); + } + } + + astDiffBuilder.nodeInserted(myOldParent, myNewNode, myPos); + + ((TreeElement)myNewNode).clearCaches(); + ((CompositeElement)myOldParent).subtreeChanged(); + + DebugUtil.checkTreeStructure(myOldParent); + } + } + + private static class ReplaceFileElement extends LogEntry { + private final FileElement myOldNode; + private final FileElement myNewNode; + + public ReplaceFileElement(FileElement oldNode, FileElement newNode) { + myOldNode = oldNode; + myNewNode = newNode; + } + + @Override + void doActualPsiChange(@NotNull PsiFile file, @NotNull ASTDiffBuilder astDiffBuilder) { + PsiFileImpl fileImpl = (PsiFileImpl)file; + final int oldLength = myOldNode.getTextLength(); + BlockSupportImpl.sendPsiBeforeEvent(fileImpl); + if (myOldNode.getFirstChildNode() != null) myOldNode.rawRemoveAllChildren(); + final ASTNode firstChildNode = myNewNode.getFirstChildNode(); + if (firstChildNode != null) myOldNode.rawAddChildren((TreeElement)firstChildNode); + fileImpl.getTreeElement().setCharTable(myNewNode.getCharTable()); + ((PsiManagerEx)file.getManager()).invalidateFile(fileImpl); + myOldNode.subtreeChanged(); + BlockSupportImpl.sendPsiAfterFileEvent(fileImpl, oldLength); + } + } + + private static class ReplaceElementWithEvents extends LogEntry { + private final CompositeElement myOldRoot; + private final CompositeElement myNewRoot; + + public ReplaceElementWithEvents(CompositeElement oldRoot, CompositeElement newRoot) { + myOldRoot = oldRoot; + myNewRoot = newRoot; + } + + @Override + void doActualPsiChange(@NotNull PsiFile file, @NotNull ASTDiffBuilder astDiffBuilder) { + myOldRoot.replaceAllChildrenToChildrenOf(myNewRoot); + } + } +} diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/tree/ASTShallowComparator.java b/platform/lang-impl/src/com/intellij/psi/impl/source/tree/ASTShallowComparator.java index f245c0a2cea3..ae9bd1a64b1b 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/tree/ASTShallowComparator.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/tree/ASTShallowComparator.java @@ -17,25 +17,38 @@ package com.intellij.psi.impl.source.tree; import com.intellij.lang.ASTNode; +import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.util.Comparing; import com.intellij.psi.PsiErrorElement; import com.intellij.util.ThreeState; import com.intellij.util.diff.ShallowNodeComparator; +import org.jetbrains.annotations.NotNull; /** * @author max */ public class ASTShallowComparator implements ShallowNodeComparator { + private final ProgressIndicator myIndicator; + + public ASTShallowComparator(@NotNull ProgressIndicator indicator) { + myIndicator = indicator; + } + public ThreeState deepEqual(final ASTNode oldNode, final ASTNode newNode) { return textMatches(oldNode, newNode); } - private static ThreeState textMatches(final ASTNode oldNode, final ASTNode newNode) { - if (TreeUtil.isCollapsedChameleon(oldNode)) { - return ((TreeElement)newNode).textMatches(oldNode.getText()) ? ThreeState.YES : ThreeState.UNSURE; + private ThreeState textMatches(ASTNode oldNode, ASTNode newNode) { + myIndicator.checkCanceled(); + String oldText = TreeUtil.isCollapsedChameleon(oldNode) ? oldNode.getText() : null; + String newText = TreeUtil.isCollapsedChameleon(newNode) ? newNode.getText() : null; + if (oldText != null && newText != null) return oldText.equals(newText) ? ThreeState.YES : ThreeState.UNSURE; + + if (oldText != null) { + return compareTreeToText((TreeElement)newNode, oldText) ? ThreeState.YES : ThreeState.UNSURE; } - if (TreeUtil.isCollapsedChameleon(newNode)) { - return ((TreeElement)oldNode).textMatches(newNode.getText()) ? ThreeState.YES : ThreeState.UNSURE; + if (newText != null) { + return compareTreeToText((TreeElement)oldNode, newText) ? ThreeState.YES : ThreeState.UNSURE; } if (oldNode instanceof LeafElement) { @@ -54,6 +67,36 @@ public class ASTShallowComparator implements ShallowNodeComparator= 0 : start; final CharSequence text = myText; return leafTextMatches(text, buffer, start); } public static int leafTextMatches(@NotNull CharSequence text, @NotNull CharSequence buffer, int start) { + assert start >= 0 : start; final int length = text.length(); if(buffer.length() - start < length) { return start == 0 ? Integer.MIN_VALUE : -start; @@ -124,7 +126,7 @@ public abstract class LeafElement extends TreeElement { } @SuppressWarnings({"MethodOverloadsMethodOfSuperclass"}) - public boolean textMatches(final CharSequence buf, int start, int end) { + public boolean textMatches(@NotNull final CharSequence buf, int start, int end) { final CharSequence text = getChars(); final int len = text.length(); diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/tree/TreeElement.java b/platform/lang-impl/src/com/intellij/psi/impl/source/tree/TreeElement.java index 8db543266b1d..347d1869bd3e 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/tree/TreeElement.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/tree/TreeElement.java @@ -143,11 +143,11 @@ public abstract class TreeElement extends ElementBase implements ASTNode, Clonea return getStartOffset(); } - public boolean textMatches(CharSequence buffer, int startOffset, int endOffset) { + public boolean textMatches(@NotNull CharSequence buffer, int startOffset, int endOffset) { return textMatches(buffer, startOffset) == endOffset; } - protected abstract int textMatches(CharSequence buffer, int start); + protected abstract int textMatches(@NotNull CharSequence buffer, int start); public boolean textMatches(@NotNull CharSequence seq) { return textMatches(seq, 0, seq.length()); @@ -236,9 +236,7 @@ public abstract class TreeElement extends ElementBase implements ASTNode, Clonea } else anchorPrev.rawInsertAfterMe(firstNew); - if (DebugUtil.CHECK){ - DebugUtil.checkTreeStructure(this); - } + DebugUtil.checkTreeStructure(this); } public void rawInsertAfterMe(@NotNull TreeElement firstNew) { @@ -265,41 +263,31 @@ public abstract class TreeElement extends ElementBase implements ASTNode, Clonea firstNew.setTreeNext(treeNext); treeNext.setTreePrev(firstNew); } - if (DebugUtil.CHECK){ - DebugUtil.checkTreeStructure(this); - } + DebugUtil.checkTreeStructure(this); } public void rawRemove() { - final TreeElement nxt = getTreeNext(); - final CompositeElement p = getTreeParent(); - final TreeElement prv = getTreePrev(); + final TreeElement next = getTreeNext(); + final CompositeElement parent = getTreeParent(); + final TreeElement prev = getTreePrev(); - if(prv != null){ - prv.setTreeNext(nxt); + if(prev != null){ + prev.setTreeNext(next); } - else if(p != null) { - p.setFirstChildNode(nxt); + else if(parent != null) { + parent.setFirstChildNode(next); } - if(nxt != null){ - nxt.setTreePrev(prv); + if(next != null){ + next.setTreePrev(prev); } - else if(p != null) { - p.setLastChildNode(prv); + else if(parent != null) { + parent.setLastChildNode(prev); } - if (DebugUtil.CHECK){ - if (getTreeParent() != null){ - DebugUtil.checkTreeStructure(getTreeParent()); - } - if (getTreePrev() != null){ - DebugUtil.checkTreeStructure(getTreePrev()); - } - if (getTreeNext() != null){ - DebugUtil.checkTreeStructure(getTreeNext()); - } - } + DebugUtil.checkTreeStructure(parent); + DebugUtil.checkTreeStructure(prev); + DebugUtil.checkTreeStructure(next); invalidate(); } @@ -360,12 +348,8 @@ public abstract class TreeElement extends ElementBase implements ASTNode, Clonea } } - if (DebugUtil.CHECK){ - if (parent != null){ - DebugUtil.checkTreeStructure(parent); - } - DebugUtil.checkTreeStructure(this); - } + DebugUtil.checkTreeStructure(parent); + DebugUtil.checkTreeStructure(this); } public IElementType getElementType() { diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/tree/TreeUtil.java b/platform/lang-impl/src/com/intellij/psi/impl/source/tree/TreeUtil.java index 4243155fb903..95768c9edacd 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/tree/TreeUtil.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/tree/TreeUtil.java @@ -19,6 +19,7 @@ package com.intellij.psi.impl.source.tree; import com.intellij.lang.ASTNode; import com.intellij.lexer.Lexer; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.Ref; @@ -48,6 +49,16 @@ public class TreeUtil { public static void ensureParsedRecursively(@NotNull ASTNode node) { ((TreeElement)node).acceptTree(new RecursiveTreeElementWalkingVisitor() { }); } + public static void ensureParsedRecursivelyCheckingProgress(@NotNull ASTNode node, final ProgressIndicator indicator) { + ((TreeElement)node).acceptTree(new RecursiveTreeElementWalkingVisitor() { + @Override + public void visitLeaf(LeafElement leaf) { + if (indicator != null) { + indicator.checkCanceled(); + } + } + }); + } public static boolean isCollapsedChameleon(ASTNode node) { return node instanceof LazyParseableElement && !((LazyParseableElement)node).isParsed(); diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/MultiHostRegistrarImpl.java b/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/MultiHostRegistrarImpl.java index e808c63d460b..087d1a84f8d0 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/MultiHostRegistrarImpl.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/MultiHostRegistrarImpl.java @@ -16,6 +16,7 @@ package com.intellij.psi.impl.source.tree.injected; +import com.intellij.codeInsight.daemon.impl.DaemonProgressIndicator; import com.intellij.injected.editor.DocumentWindow; import com.intellij.injected.editor.DocumentWindowImpl; import com.intellij.injected.editor.VirtualFileWindow; @@ -42,6 +43,7 @@ import com.intellij.psi.impl.PsiDocumentManagerImpl; import com.intellij.psi.impl.source.PsiFileImpl; import com.intellij.psi.impl.source.resolve.FileContextUtil; 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.tree.IElementType; import com.intellij.psi.util.PsiTreeUtil; @@ -397,7 +399,16 @@ public class MultiHostRegistrarImpl implements MultiHostRegistrar { assert shreds.isValid(); oldViewProvider.performNonPhysically(new Runnable() { public void run() { - BlockSupportImpl.mergeTrees(oldFile, oldFileNode, injectedNode); + //todo + final DiffLog diffLog = BlockSupportImpl.mergeTrees(oldFile, oldFileNode, injectedNode, new DaemonProgressIndicator()); + hostPsiFile.getManager().performActionWithFormatterDisabled(new Runnable() { + @Override + public void run() { + synchronized (PsiLock.LOCK) { + diffLog.doActualPsiChange(oldFile); + } + } + }); } }); assert shreds.isValid(); diff --git a/platform/lang-impl/src/com/intellij/psi/templateLanguages/TemplateDataElementType.java b/platform/lang-impl/src/com/intellij/psi/templateLanguages/TemplateDataElementType.java index e080c538b899..986150d61d9a 100644 --- a/platform/lang-impl/src/com/intellij/psi/templateLanguages/TemplateDataElementType.java +++ b/platform/lang-impl/src/com/intellij/psi/templateLanguages/TemplateDataElementType.java @@ -103,11 +103,11 @@ public class TemplateDataElementType extends IFileElementType implements ITempla treeElement.subtreeChanged(); TreeElement childNode = treeElement.getFirstChildNode(); - DebugUtil.checkTreeStructureIfConfigured(parsed); - DebugUtil.checkTreeStructureIfConfigured(treeElement); - DebugUtil.checkTreeStructureIfConfigured(chameleon); - DebugUtil.checkTreeStructureIfConfigured(file.getNode()); - DebugUtil.checkTreeStructureIfConfigured(originalFile.getNode()); + DebugUtil.checkTreeStructure(parsed); + DebugUtil.checkTreeStructure(treeElement); + DebugUtil.checkTreeStructure(chameleon); + DebugUtil.checkTreeStructure(file.getNode()); + DebugUtil.checkTreeStructure(originalFile.getNode()); return childNode; } diff --git a/platform/lang-impl/src/com/intellij/psi/text/BlockSupport.java b/platform/lang-impl/src/com/intellij/psi/text/BlockSupport.java index cfdd8e0c205f..2e69bf0523fb 100644 --- a/platform/lang-impl/src/com/intellij/psi/text/BlockSupport.java +++ b/platform/lang-impl/src/com/intellij/psi/text/BlockSupport.java @@ -18,13 +18,16 @@ package com.intellij.psi.text; import com.intellij.lang.ASTNode; import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.UserDataHolder; import com.intellij.openapi.util.registry.Registry; import com.intellij.psi.PsiFile; +import com.intellij.psi.impl.source.text.DiffLog; import com.intellij.util.IncorrectOperationException; import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; public abstract class BlockSupport { public static BlockSupport getInstance(Project project) { @@ -32,12 +35,30 @@ public abstract class BlockSupport { } public abstract void reparseRange(PsiFile file, int startOffset, int endOffset, @NonNls CharSequence newText) throws IncorrectOperationException; - public abstract void reparseRange(PsiFile file, int startOffset, int endOffset, int lengthShift, CharSequence newText) throws IncorrectOperationException; + + @NotNull + public abstract DiffLog reparseRange(@NotNull PsiFile file, + int startOffset, + int endOffset, + int lengthShift, + @NotNull CharSequence newText, + @NotNull ProgressIndicator progressIndicator) throws IncorrectOperationException; public static final Key DO_NOT_REPARSE_INCREMENTALLY = Key.create("SKIP_INCREMENTAL_REPARSE"); public static final Key TREE_TO_BE_REPARSED = new Key("TREE_TO_BE_REPARSED"); public static class ReparsedSuccessfullyException extends RuntimeException { + private final DiffLog myDiffLog; + + public ReparsedSuccessfullyException(@NotNull DiffLog diffLog) { + myDiffLog = diffLog; + } + + @NotNull + public DiffLog getDiffLog() { + return myDiffLog; + } + public synchronized Throwable fillInStackTrace() { return this; } diff --git a/platform/lang-impl/testSrc/com/intellij/lang/PsiBuilderQuickTest.java b/platform/lang-impl/testSrc/com/intellij/lang/PsiBuilderQuickTest.java index c60a76a73da0..ab3fb6a76552 100644 --- a/platform/lang-impl/testSrc/com/intellij/lang/PsiBuilderQuickTest.java +++ b/platform/lang-impl/testSrc/com/intellij/lang/PsiBuilderQuickTest.java @@ -16,14 +16,17 @@ package com.intellij.lang; import com.intellij.lang.impl.PsiBuilderImpl; +import com.intellij.lexer.Lexer; import com.intellij.lexer.LexerBase; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.ex.ApplicationEx; -import com.intellij.openapi.application.ex.ApplicationManagerEx; +import com.intellij.openapi.project.Project; +import com.intellij.psi.FileViewProvider; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; import com.intellij.psi.TokenType; import com.intellij.psi.impl.DebugUtil; import com.intellij.psi.impl.source.tree.ASTStructure; import com.intellij.psi.tree.*; +import com.intellij.testFramework.LightPlatformTestCase; import com.intellij.util.ThreeState; import com.intellij.util.diff.DiffTree; import com.intellij.util.diff.DiffTreeChangeBuilder; @@ -31,8 +34,6 @@ import com.intellij.util.diff.FlyweightCapableTreeStructure; import com.intellij.util.diff.ShallowNodeComparator; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; -import org.junit.AfterClass; -import org.junit.BeforeClass; import org.junit.Test; import java.io.IOException; @@ -40,12 +41,8 @@ import java.io.OutputStream; import java.io.PrintStream; import java.util.List; -import static org.easymock.EasyMock.*; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; - -public class PsiBuilderQuickTest { +public class PsiBuilderQuickTest extends LightPlatformTestCase { private static final IFileElementType ROOT = new IFileElementType("ROOT", Language.ANY); private static final IElementType LETTER = new IElementType("LETTER", Language.ANY); @@ -61,30 +58,6 @@ public class PsiBuilderQuickTest { private static final TokenSet WHITESPACE_SET = TokenSet.create(TokenType.WHITE_SPACE); private static final TokenSet COMMENT_SET = TokenSet.create(COMMENT); - private static ApplicationEx myMockApp = null; - - @BeforeClass - public static void setUp() { - if (ApplicationManager.getApplication() == null) { - if (myMockApp == null) { - myMockApp = createMock(ApplicationEx.class); - expect(myMockApp.isInternal()).andReturn(true); - expect(myMockApp.isUnitTestMode()).andReturn(true); - myMockApp.assertReadAccessAllowed(); - expectLastCall().asStub(); - replay(myMockApp); - } - ApplicationManagerEx.setApplication(myMockApp); - } - } - - @AfterClass - public static void tearDown() { - if (myMockApp != null) { - ApplicationManagerEx.setApplication(null); - } - } - @Test public void testPlain() { doTest("a< parseContents(LighterLazyParseableNode chameleon) { - final PsiBuilder builder = new PsiBuilderImpl(new MyTestLexer(), WHITESPACE_SET, COMMENT_SET, chameleon.getText()); + final PsiBuilder builder = createBuilder(chameleon.getText()); parse(builder); return builder.getLightTree(); } @Override public ASTNode parseContents(ASTNode chameleon) { - final PsiBuilder builder = new PsiBuilderImpl(new MyTestLexer(), WHITESPACE_SET, COMMENT_SET, chameleon.getText()); + final PsiBuilder builder = createBuilder(chameleon.getText()); parse(builder); return builder.getTreeBuilt().getFirstChildNode(); } @@ -509,14 +482,14 @@ public class PsiBuilderQuickTest { final IElementType CHAMELEON_1 = new MyLazyElementType("CHAMELEON_1") { @Override public FlyweightCapableTreeStructure parseContents(LighterLazyParseableNode chameleon) { - final PsiBuilder builder = new PsiBuilderImpl(new MyTestLexer(), WHITESPACE_SET, COMMENT_SET, chameleon.getText()); + final PsiBuilder builder = createBuilder(chameleon.getText()); parse(builder); return builder.getLightTree(); } @Override public ASTNode parseContents(ASTNode chameleon) { - final PsiBuilder builder = new PsiBuilderImpl(new MyTestLexer(), WHITESPACE_SET, COMMENT_SET, chameleon.getText()); + final PsiBuilder builder = createBuilder(chameleon.getText()); parse(builder); return builder.getTreeBuilt().getFirstChildNode(); } @@ -581,12 +554,67 @@ public class PsiBuilderQuickTest { " PsiElement(OTHER)('}')\n"); } + private static PsiBuilderImpl createBuilder(CharSequence text) { + ParserDefinition parserDefinition = new ParserDefinition() { + @NotNull + @Override + public Lexer createLexer(Project project) { + return new MyTestLexer(); + } + + @Override + public PsiParser createParser(Project project) { + return null; + } + + @Override + public IFileElementType getFileNodeType() { + return null; + } + + @NotNull + @Override + public TokenSet getWhitespaceTokens() { + return WHITESPACE_SET; + } + + @NotNull + @Override + public TokenSet getCommentTokens() { + return COMMENT_SET; + } + + @NotNull + @Override + public TokenSet getStringLiteralElements() { + return null; + } + + @NotNull + @Override + public PsiElement createElement(ASTNode node) { + return null; + } + + @Override + public PsiFile createFile(FileViewProvider viewProvider) { + return null; + } + + @Override + public SpaceRequirements spaceExistanceTypeBetweenTokens(ASTNode left, ASTNode right) { + return null; + } + }; + return new PsiBuilderImpl(getProject(), null, parserDefinition, parserDefinition.createLexer(getProject()), null, text, null, null); + } + private interface Parser { void parse(PsiBuilder builder); } - private static void doTest(final String text, final Parser parser, final String expected) { - final PsiBuilder builder = new PsiBuilderImpl(new MyTestLexer(), WHITESPACE_SET, COMMENT_SET, text); + private static void doTest(@NonNls final String text, final Parser parser, @NonNls final String expected) { + final PsiBuilder builder = createBuilder(text); final PsiBuilder.Marker rootMarker = builder.mark(); parser.parse(builder); rootMarker.done(ROOT); @@ -603,7 +631,7 @@ public class PsiBuilderQuickTest { assertEquals(expected, DebugUtil.nodeTreeToString(root, false)); // check heavy vs. light tree merging - final PsiBuilder builder2 = new PsiBuilderImpl(new MyTestLexer(), WHITESPACE_SET, COMMENT_SET, text); + final PsiBuilder builder2 = createBuilder(text); final PsiBuilder.Marker rootMarker2 = builder2.mark(); parser.parse(builder2); rootMarker2.done(ROOT); @@ -640,13 +668,64 @@ public class PsiBuilderQuickTest { ); } - private static void doFailTest(final String text, final Parser parser, final String expected) { + private static void doFailTest(@NonNls final String text, final Parser parser, @NonNls final String expected) { final PrintStream std = System.err; //noinspection IOResourceOpenedButNotSafelyClosed System.setErr(new PrintStream(new NullStream())); try { try { - final PsiBuilder builder = new PsiBuilderImpl(new MyTestLexer(), TokenSet.EMPTY, TokenSet.EMPTY, text); + ParserDefinition parserDefinition = new ParserDefinition() { + @NotNull + @Override + public Lexer createLexer(Project project) { + return null; + } + + @Override + public PsiParser createParser(Project project) { + return null; + } + + @Override + public IFileElementType getFileNodeType() { + return null; + } + + @NotNull + @Override + public TokenSet getWhitespaceTokens() { + return TokenSet.EMPTY; + } + + @NotNull + @Override + public TokenSet getCommentTokens() { + return TokenSet.EMPTY; + } + + @NotNull + @Override + public TokenSet getStringLiteralElements() { + return null; + } + + @NotNull + @Override + public PsiElement createElement(ASTNode node) { + return null; + } + + @Override + public PsiFile createFile(FileViewProvider viewProvider) { + return null; + } + + @Override + public SpaceRequirements spaceExistanceTypeBetweenTokens(ASTNode left, ASTNode right) { + return null; + } + }; + final PsiBuilder builder = PsiBuilderFactory.getInstance().createBuilder(parserDefinition, new MyTestLexer(),text); builder.setDebugMode(true); parser.parse(builder); builder.getLightTree(); diff --git a/platform/platform-impl/testSrc/com/intellij/openapi/editor/RangeMarkerTest.java b/platform/platform-impl/testSrc/com/intellij/openapi/editor/RangeMarkerTest.java index 55bc1806fe12..2b9c66697124 100644 --- a/platform/platform-impl/testSrc/com/intellij/openapi/editor/RangeMarkerTest.java +++ b/platform/platform-impl/testSrc/com/intellij/openapi/editor/RangeMarkerTest.java @@ -359,7 +359,7 @@ public class RangeMarkerTest extends LightPlatformTestCase { StringBuilder buffer = new StringBuilder("0123456789"); RangeMarker marker = createMarker("0123456789", 2, 5); PsiToDocumentSynchronizer synchronizer = ((PsiDocumentManagerImpl)PsiDocumentManager.getInstance(getProject())).getSynchronizer(); - synchronizer.startTransaction(marker.getDocument(), null); + synchronizer.startTransaction(getProject(), marker.getDocument(), null); synchronizer.insertString(marker.getDocument(), 3, "a"); buffer.insert(3, "a"); @@ -380,7 +380,7 @@ public class RangeMarkerTest extends LightPlatformTestCase { "import java.util.Map;"); PsiToDocumentSynchronizer synchronizer = ((PsiDocumentManagerImpl)PsiDocumentManager.getInstance(getProject())).getSynchronizer(); Document document = marker.getDocument(); - synchronizer.startTransaction(document, null); + synchronizer.startTransaction(getProject(), document, null); String newText = StringUtil.replaceSubstring(document.getText(), TextRange.create(marker), ""); synchronizer.replaceString(document, 0, document.getTextLength(), newText); @@ -403,7 +403,7 @@ public class RangeMarkerTest extends LightPlatformTestCase { StringBuilder buffer = new StringBuilder("0123456789"); RangeMarker marker = createMarker("0123456789", 2, 5); PsiToDocumentSynchronizer synchronizer = ((PsiDocumentManagerImpl)PsiDocumentManager.getInstance(getProject())).getSynchronizer(); - synchronizer.startTransaction(marker.getDocument(), null); + synchronizer.startTransaction(getProject(), marker.getDocument(), null); synchronizer.insertString(marker.getDocument(), 1, "a"); buffer.insert(1, "a"); @@ -424,7 +424,7 @@ public class RangeMarkerTest extends LightPlatformTestCase { StringBuilder buffer = new StringBuilder("0123456789"); RangeMarker marker = createMarker("0123456789", 2, 5); PsiToDocumentSynchronizer synchronizer = ((PsiDocumentManagerImpl)PsiDocumentManager.getInstance(getProject())).getSynchronizer(); - synchronizer.startTransaction(marker.getDocument(), null); + synchronizer.startTransaction(getProject(), marker.getDocument(), null); synchronizer.insertString(marker.getDocument(), 1, "a"); buffer.insert(1, "a"); @@ -451,7 +451,7 @@ public class RangeMarkerTest extends LightPlatformTestCase { StringBuilder buffer = new StringBuilder("0123456789"); RangeMarker marker = createMarker("0123456789", 2, 5); PsiToDocumentSynchronizer synchronizer = ((PsiDocumentManagerImpl)PsiDocumentManager.getInstance(getProject())).getSynchronizer(); - synchronizer.startTransaction(marker.getDocument(), null); + synchronizer.startTransaction(getProject(), marker.getDocument(), null); synchronizer.replaceString(marker.getDocument(), 0, 10, "0"); buffer.replace(0, 10, "0"); @@ -477,7 +477,7 @@ public class RangeMarkerTest extends LightPlatformTestCase { StringBuilder buffer = new StringBuilder("0123456789"); RangeMarker marker = createMarker("0123456789", 2, 5); PsiToDocumentSynchronizer synchronizer = ((PsiDocumentManagerImpl)PsiDocumentManager.getInstance(getProject())).getSynchronizer(); - synchronizer.startTransaction(marker.getDocument(), null); + synchronizer.startTransaction(getProject(), marker.getDocument(), null); final PsiToDocumentSynchronizer.DocumentChangeTransaction transaction = synchronizer.getTransaction(marker.getDocument()); final Set> affectedFragments = transaction.getAffectedFragments(); @@ -506,7 +506,7 @@ public class RangeMarkerTest extends LightPlatformTestCase { StringBuilder buffer = new StringBuilder("0123456789"); RangeMarker marker = createMarker("0123456789", 2, 5); PsiToDocumentSynchronizer synchronizer = ((PsiDocumentManagerImpl)PsiDocumentManager.getInstance(getProject())).getSynchronizer(); - synchronizer.startTransaction(marker.getDocument(), null); + synchronizer.startTransaction(getProject(), marker.getDocument(), null); synchronizer.replaceString(marker.getDocument(), 3, 5, "3a4"); buffer.replace(3, 5, "3a4"); @@ -535,7 +535,7 @@ public class RangeMarkerTest extends LightPlatformTestCase { RangeMarker marker = createMarker("0123456789", 2, 5); PsiToDocumentSynchronizer synchronizer = ((PsiDocumentManagerImpl)PsiDocumentManager.getInstance(getProject())).getSynchronizer(); Document document = marker.getDocument(); - synchronizer.startTransaction(document, null); + synchronizer.startTransaction(getProject(), document, null); synchronizer.replaceString(document, 4, 5, "3a4"); buffer.replace(4, 5, "3a4"); diff --git a/platform/platform-resources/src/componentSets/Lang.xml b/platform/platform-resources/src/componentSets/Lang.xml index 24f922d4b908..a457e48c3925 100644 --- a/platform/platform-resources/src/componentSets/Lang.xml +++ b/platform/platform-resources/src/componentSets/Lang.xml @@ -54,6 +54,9 @@ com.intellij.psi.PsiDocumentManager com.intellij.psi.impl.PsiDocumentManagerImpl + + com.intellij.psi.impl.DocumentCommitThread + com.intellij.openapi.module.ModuleManager diff --git a/platform/testFramework/src/com/intellij/testFramework/LeakHunter.java b/platform/testFramework/src/com/intellij/testFramework/LeakHunter.java index 241f58bda73a..3e02a6a6e800 100644 --- a/platform/testFramework/src/com/intellij/testFramework/LeakHunter.java +++ b/platform/testFramework/src/com/intellij/testFramework/LeakHunter.java @@ -20,7 +20,7 @@ import com.intellij.openapi.project.impl.ProjectImpl; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.UserDataHolder; import com.intellij.psi.PsiElement; -//import com.intellij.psi.impl.DocumentCommitThread; +import com.intellij.psi.impl.DocumentCommitThread; import com.intellij.psi.impl.source.tree.TreeElement; import com.intellij.util.Processor; import com.intellij.util.containers.Stack; @@ -140,9 +140,9 @@ public class LeakHunter { public static void markAsNotALeak(UserDataHolder object) { object.putUserData(IS_NOT_A_LEAK, Boolean.TRUE); } - //static { - // markAsNotALeak(DocumentCommitThread.THE_POISON_PILL); - //} + static { + markAsNotALeak(DocumentCommitThread.THE_POISON_PILL); + } private static boolean isReallyLeak(Field field, String fieldName, Object value, Class valueClass) { if (value instanceof UserDataHolder && ((UserDataHolder)value).getUserData(IS_NOT_A_LEAK) != null) return false; return true; diff --git a/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/CodeInsightTestFixtureImpl.java b/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/CodeInsightTestFixtureImpl.java index b59a9974e1c6..393ffad4828b 100644 --- a/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/CodeInsightTestFixtureImpl.java +++ b/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/CodeInsightTestFixtureImpl.java @@ -919,7 +919,7 @@ public class CodeInsightTestFixtureImpl extends BaseFixture implements CodeInsig }; Editor editor = getCompletionEditor(); handler.invokeCompletion(getProject(), editor, invocationCount, false); - + PsiDocumentManager.getInstance(getProject()).commitAllDocuments(); // to compare with file text } }, null, null); } diff --git a/platform/util/src/com/intellij/util/lang/CompoundRuntimeException.java b/platform/util/src/com/intellij/util/lang/CompoundRuntimeException.java index 139571436153..302c89fc1d15 100644 --- a/platform/util/src/com/intellij/util/lang/CompoundRuntimeException.java +++ b/platform/util/src/com/intellij/util/lang/CompoundRuntimeException.java @@ -16,9 +16,11 @@ package com.intellij.util.lang; -import java.util.List; +import org.jetbrains.annotations.NotNull; + import java.io.PrintStream; import java.io.PrintWriter; +import java.util.List; /** * @author mike @@ -46,4 +48,13 @@ public class CompoundRuntimeException extends RuntimeException { throwable.printStackTrace(s); } } + + public static void doThrow(@NotNull List throwables) { + if (throwables.size()== 1) { + Throwable throwable = throwables.get(0); + if (throwable instanceof RuntimeException) throw (RuntimeException)throwable; + if (throwable instanceof Error) throw (Error)throwable; + } + throw new CompoundRuntimeException(throwables); + } } diff --git a/xml/impl/src/com/intellij/psi/impl/source/parsing/xml/HtmlBuilderDriver.java b/xml/impl/src/com/intellij/psi/impl/source/parsing/xml/HtmlBuilderDriver.java index 6ee604cdbf56..571b1e42d633 100644 --- a/xml/impl/src/com/intellij/psi/impl/source/parsing/xml/HtmlBuilderDriver.java +++ b/xml/impl/src/com/intellij/psi/impl/source/parsing/xml/HtmlBuilderDriver.java @@ -35,8 +35,8 @@ public class HtmlBuilderDriver extends XmlBuilderDriver { final ParserDefinition htmlParserDef = LanguageParserDefinitions.INSTANCE.forLanguage(HTMLLanguage.INSTANCE); assert htmlParserDef != null; - PsiBuilder b = PsiBuilderFactory.getInstance().createBuilder(htmlParserDef.createLexer(null), HTMLLanguage.INSTANCE, getText()); + PsiBuilder b = PsiBuilderFactory.getInstance().createBuilder(htmlParserDef, htmlParserDef.createLexer(null), getText()); new HtmlParsing(b).parseDocument(); return b; } -} \ No newline at end of file +} diff --git a/xml/impl/src/com/intellij/psi/impl/source/parsing/xml/XmlBuilderDriver.java b/xml/impl/src/com/intellij/psi/impl/source/parsing/xml/XmlBuilderDriver.java index be9aa43e6abc..f1db1be33cbd 100644 --- a/xml/impl/src/com/intellij/psi/impl/source/parsing/xml/XmlBuilderDriver.java +++ b/xml/impl/src/com/intellij/psi/impl/source/parsing/xml/XmlBuilderDriver.java @@ -137,7 +137,7 @@ public class XmlBuilderDriver { final ParserDefinition xmlParserDefinition = LanguageParserDefinitions.INSTANCE.forLanguage(XMLLanguage.INSTANCE); assert xmlParserDefinition != null; - PsiBuilder b = PsiBuilderFactory.getInstance().createBuilder(xmlParserDefinition.createLexer(null), XMLLanguage.INSTANCE, myText); + PsiBuilder b = PsiBuilderFactory.getInstance().createBuilder(xmlParserDefinition, xmlParserDefinition.createLexer(null), myText); new XmlParsing(b).parseDocument(); return b; } @@ -322,4 +322,4 @@ public class XmlBuilderDriver { return name; } -} \ No newline at end of file +}