commit outside EDT

This commit is contained in:
Alexey Kudravtsev
2011-06-20 11:59:49 +04:00
parent 4d63800998
commit d55df65f57
42 changed files with 1544 additions and 720 deletions
@@ -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 {
@@ -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);
}
@@ -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.
*
+1
View File
@@ -23,6 +23,7 @@
<orderEntry type="module" module-name="xdebugger-api" />
<orderEntry type="library" name="xpp3-1.1.4-min" level="project" />
<orderEntry type="library" exported="" name="cli-parser" level="project" />
<orderEntry type="module" module-name="testFramework" />
</component>
</module>
@@ -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);
}
});
}
@@ -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<FileEditor> activeEditors = myDaemonListeners.getSelectedEditors();
if (activeEditors.isEmpty()) return;
Map<FileEditor, HighlightingPass[]> passes = new THashMap<FileEditor, HighlightingPass[]>(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<FileEditor, HighlightingPass[]> passes = new THashMap<FileEditor, HighlightingPass[]>(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);
}
};
}
@@ -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());
}
});
}
}
@@ -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);
}
}
@@ -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<ASTNode, LighterASTNode> {
private final ASTDiffBuilder myDelegate;
private static class ConvertFromTokensToASTBuilder implements DiffTreeChangeBuilder<ASTNode, LighterASTNode> {
private final DiffTreeChangeBuilder<ASTNode, ASTNode> myDelegate;
private final ASTConverter myConverter;
public MyBuilder(PsiFileImpl file, StartMarker rootNode) {
myDelegate = new ASTDiffBuilder(file);
public ConvertFromTokensToASTBuilder(StartMarker rootNode, DiffTreeChangeBuilder<ASTNode, ASTNode> 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<LazyParseableToken> 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<Token>(1000, new LimitedPool.ObjectFactory<Token>() {
public void cleanup(final Token token) {
@@ -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;
@@ -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();
}
@@ -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;
}
}
@@ -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);
}
}
@@ -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<Map.Entry<ASTNode,ChangeInfo>> entries = change.myChanges.entrySet();
for (final Map.Entry<ASTNode, ChangeInfo> 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<ASTNode, Integer> 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
@@ -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);
}
}
@@ -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<Document> documentsToCommit = new OrderedSet<Document>();
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<Document> 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<Processor<Document>> finishRunnables = new ArrayList<Processor<Document>>();
Runnable runnable = new Runnable() {
public void run() {
final FileViewProvider viewProvider = documentManager.getCachedViewProvider(document);
if (viewProvider == null) return;
final List<PsiFile> psiFiles = viewProvider.getAllFiles();
for (PsiFile file : psiFiles) {
if (file.isValid() && file != excludeFile) {
Processor<Document> 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<Document> 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<Document>() {
@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);
}
}
}
}
@@ -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<PsiFile> HARD_REF_TO_PSI = new Key<PsiFile>("HARD_REFERENCE_TO_PSI");
private static final Key<Boolean> KEY_COMMITTING = new Key<Boolean>("Committing");
private static final Key<CommitStage> COMMIT_STAGE = new Key<CommitStage>("Commit stage");
private static final Key<List<Runnable>> ACTION_AFTER_COMMIT = Key.create("ACTION_AFTER_COMMIT");
private final Project myProject;
private final PsiManager myPsiManager;
private final Key<TextBlock> KEY_TEXT_BLOCK = Key.create("KEY_TEXT_BLOCK");
private static final Key<TextBlock> KEY_TEXT_BLOCK = Key.create("KEY_TEXT_BLOCK");
private final Set<Document> myUncommittedDocuments = Collections.synchronizedSet(new HashSet<Document>());
private final BlockSupportImpl myBlockSupport;
private volatile boolean myIsCommitInProgress;
private final PsiToDocumentSynchronizer mySynchronizer;
private final List<Listener> 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<Object, Runnable> actionsWhenAllDocumentsAreCommitted = new LinkedHashMap<Object, Runnable>(); //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<Processor<Document>> 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<PsiFile> 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<Document> 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<Runnable> 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<Object> keys = new ArrayList<Object>(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> T commitAndRunReadAction(@NotNull final Computable<T> computation) {
final Ref<T> 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<ASTNode> 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<PsiFile> 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<PsiFile> 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
}
}
@@ -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<DocumentChangeTransaction, Integer> pair = myTransactionsMap.get(doc);
if (pair == null) {
final PsiFile psiFile = scope != null ? scope.getContainingFile() : null;
@@ -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();
}
}
}
}
@@ -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<ASTNode, ASTNode> {
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;
}
@@ -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 <T> void diffTrees(@NotNull final ASTNode oldRoot,
@NotNull final DiffTreeChangeBuilder<ASTNode, T> builder,
@NotNull final ShallowNodeComparator<ASTNode, T> comparator,
@NotNull final FlyweightCapableTreeStructure<T> 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<ASTNode[]> 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);
@@ -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<ASTNode,ASTNode> {
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<LogEntry> myEntries = new ArrayList<LogEntry>();
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);
}
}
}
@@ -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<ASTNode, ASTNode> {
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<ASTNode, ASTN
return ThreeState.UNSURE;
}
// have to perform tree walking by hand here to be able to interrupt ourselves
private boolean compareTreeToText(@NotNull TreeElement root, @NotNull final String text) {
final int[] curOffset = {0};
root.acceptTree(new RecursiveTreeElementWalkingVisitor() {
@Override
public void visitLeaf(LeafElement leaf) {
matchText(leaf);
}
private void matchText(TreeElement leaf) {
curOffset[0] = leaf.textMatches(text, curOffset[0]);
if (curOffset[0] < 0) {
stopWalking();
}
}
@Override
public void visitComposite(CompositeElement composite) {
myIndicator.checkCanceled();
if (composite instanceof LazyParseableElement && !((LazyParseableElement)composite).isParsed()) {
matchText(composite);
}
else {
super.visitComposite(composite);
}
}
});
return curOffset[0] == text.length();
}
public boolean typesEqual(final ASTNode n1, final ASTNode n2) {
return n1.getElementType() == n2.getElementType();
}
@@ -86,12 +86,14 @@ public abstract class LeafElement extends TreeElement {
return false;
}
protected int textMatches(CharSequence buffer, int start) {
protected int textMatches(@NotNull CharSequence buffer, int start) {
assert start >= 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();
@@ -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() {
@@ -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();
@@ -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();
@@ -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;
}
@@ -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<Boolean> DO_NOT_REPARSE_INCREMENTALLY = Key.create("SKIP_INCREMENTAL_REPARSE");
public static final Key<ASTNode> TREE_TO_BE_REPARSED = new Key<ASTNode>("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;
}
@@ -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<<b",
@@ -478,14 +451,14 @@ public class PsiBuilderQuickTest {
final IElementType CHAMELEON_2 = new MyLazyElementType("CHAMELEON_2") {
@Override
public FlyweightCapableTreeStructure<LighterASTNode> 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<LighterASTNode> 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();
@@ -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<Pair<PsiToDocumentSynchronizer.MutableTextRange, StringBuffer>> 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");
@@ -54,6 +54,9 @@
<interface-class>com.intellij.psi.PsiDocumentManager</interface-class>
<implementation-class>com.intellij.psi.impl.PsiDocumentManagerImpl</implementation-class>
</component>
<component>
<implementation-class>com.intellij.psi.impl.DocumentCommitThread</implementation-class>
</component>
<component>
<interface-class>com.intellij.openapi.module.ModuleManager</interface-class>
@@ -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;
@@ -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);
}
@@ -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<Throwable> 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);
}
}
@@ -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;
}
}
}
@@ -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;
}
}
}