mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
commit outside EDT
This commit is contained in:
+13
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
+2
-6
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
+24
-11
@@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
+49
-3
@@ -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;
|
||||
|
||||
+6
-4
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+11
-10
@@ -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();
|
||||
|
||||
+12
-1
@@ -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();
|
||||
|
||||
+5
-5
@@ -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;
|
||||
|
||||
+1
-1
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user