| /* |
| * Copyright 2000-2014 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.injected.editor.DocumentWindow; |
| import com.intellij.lang.injection.InjectedLanguageManager; |
| import com.intellij.openapi.application.Application; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.editor.Document; |
| import com.intellij.openapi.editor.event.DocumentEvent; |
| import com.intellij.openapi.editor.event.DocumentListener; |
| import com.intellij.openapi.editor.ex.DocumentEx; |
| import com.intellij.openapi.fileEditor.FileDocumentManager; |
| import com.intellij.openapi.progress.ProgressIndicator; |
| import com.intellij.openapi.progress.ProgressManager; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.roots.FileIndexFacade; |
| import com.intellij.openapi.util.Computable; |
| import com.intellij.openapi.util.Key; |
| import com.intellij.openapi.util.Ref; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.psi.*; |
| import com.intellij.psi.impl.smartPointers.SmartPointerManagerImpl; |
| import com.intellij.psi.impl.source.PsiFileImpl; |
| import com.intellij.psi.impl.source.text.BlockSupportImpl; |
| import com.intellij.psi.text.BlockSupport; |
| import com.intellij.util.FileContentUtilCore; |
| import com.intellij.util.Processor; |
| import com.intellij.util.SmartList; |
| import com.intellij.util.SystemProperties; |
| import com.intellij.util.concurrency.Semaphore; |
| import com.intellij.util.containers.ConcurrentHashSet; |
| import com.intellij.util.containers.ContainerUtil; |
| import com.intellij.util.messages.MessageBus; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.jetbrains.annotations.TestOnly; |
| |
| import javax.swing.*; |
| import java.util.*; |
| |
| public abstract class PsiDocumentManagerBase extends PsiDocumentManager implements DocumentListener { |
| protected 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<List<Runnable>> ACTION_AFTER_COMMIT = Key.create("ACTION_AFTER_COMMIT"); |
| |
| protected final Project myProject; |
| private final PsiManager myPsiManager; |
| private final DocumentCommitProcessor myDocumentCommitProcessor; |
| protected final Set<Document> myUncommittedDocuments = new ConcurrentHashSet<Document>(); |
| private final Map<Document, CharSequence> myLastCommittedTexts = ContainerUtil.newConcurrentMap(); |
| |
| private volatile boolean myIsCommitInProgress; |
| private final PsiToDocumentSynchronizer mySynchronizer; |
| |
| private final List<Listener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList(); |
| private final SmartPointerManagerImpl mySmartPointerManager; |
| |
| public PsiDocumentManagerBase(@NotNull final Project project, |
| @NotNull PsiManager psiManager, |
| @NotNull SmartPointerManager smartPointerManager, |
| @NotNull MessageBus bus, |
| @NonNls @NotNull final DocumentCommitProcessor documentCommitProcessor) { |
| myProject = project; |
| myPsiManager = psiManager; |
| myDocumentCommitProcessor = documentCommitProcessor; |
| mySmartPointerManager = (SmartPointerManagerImpl)smartPointerManager; |
| mySynchronizer = new PsiToDocumentSynchronizer(this, bus); |
| myPsiManager.addPsiTreeChangeListener(mySynchronizer); |
| } |
| |
| @Override |
| @Nullable |
| public PsiFile getPsiFile(@NotNull Document document) { |
| final PsiFile userData = document.getUserData(HARD_REF_TO_PSI); |
| if (userData != null) return userData; |
| |
| PsiFile psiFile = getCachedPsiFile(document); |
| if (psiFile != null) return psiFile; |
| |
| final VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document); |
| if (virtualFile == null || !virtualFile.isValid()) return null; |
| |
| psiFile = getPsiFile(virtualFile); |
| if (psiFile == null) return null; |
| |
| fireFileCreated(document, psiFile); |
| |
| return psiFile; |
| } |
| |
| public static void cachePsi(@NotNull Document document, @Nullable PsiFile file) { |
| document.putUserData(HARD_REF_TO_PSI, file); |
| } |
| |
| @Override |
| public PsiFile getCachedPsiFile(@NotNull Document document) { |
| final PsiFile userData = document.getUserData(HARD_REF_TO_PSI); |
| if (userData != null) return userData; |
| |
| final VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document); |
| if (virtualFile == null || !virtualFile.isValid()) return null; |
| return getCachedPsiFile(virtualFile); |
| } |
| |
| @Nullable |
| public FileViewProvider getCachedViewProvider(@NotNull Document document) { |
| final VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document); |
| if (virtualFile == null || !virtualFile.isValid()) return null; |
| return ((PsiManagerEx)myPsiManager).getFileManager().findCachedViewProvider(virtualFile); |
| } |
| |
| @Nullable |
| protected PsiFile getCachedPsiFile(VirtualFile virtualFile) { |
| return ((PsiManagerEx)myPsiManager).getFileManager().getCachedPsiFile(virtualFile); |
| } |
| |
| @Nullable |
| private PsiFile getPsiFile(VirtualFile virtualFile) { |
| return ((PsiManagerEx)myPsiManager).getFileManager().findFile(virtualFile); |
| } |
| |
| @Nullable |
| @Override |
| public Document getDocument(@NotNull PsiFile file) { |
| if (file instanceof PsiBinaryFile) return null; |
| |
| Document document = getCachedDocument(file); |
| if (document != null) { |
| if (!file.getViewProvider().isPhysical() && document.getUserData(HARD_REF_TO_PSI) == null) { |
| cachePsi(document, file); |
| } |
| return document; |
| } |
| |
| FileViewProvider viewProvider = file.getViewProvider(); |
| if (!viewProvider.isEventSystemEnabled()) return null; |
| |
| document = FileDocumentManager.getInstance().getDocument(viewProvider.getVirtualFile()); |
| if (document != null) { |
| if (document.getTextLength() != file.getTextLength()) { |
| String message = "Modified PSI with no document: " + file + "; physical=" + viewProvider.isPhysical(); |
| if (document.getTextLength() + file.getTextLength() < 8096) { |
| message += "\n=== document ===\n" + document.getText() + "\n=== PSI ===\n" + file.getText(); |
| } |
| throw new AssertionError(message); |
| } |
| |
| if (!viewProvider.isPhysical()) { |
| cachePsi(document, file); |
| } |
| } |
| |
| return document; |
| } |
| |
| @Override |
| public Document getCachedDocument(@NotNull PsiFile file) { |
| if (!file.isPhysical()) return null; |
| VirtualFile vFile = file.getViewProvider().getVirtualFile(); |
| return FileDocumentManager.getInstance().getCachedDocument(vFile); |
| } |
| |
| @Override |
| public void commitAllDocuments() { |
| ApplicationManager.getApplication().assertIsDispatchThread(); |
| if (myUncommittedDocuments.isEmpty()) return; |
| |
| final Document[] documents = getUncommittedDocuments(); |
| for (Document document : documents) { |
| commitDocument(document); |
| } |
| |
| 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 (isCommitted(document)) { |
| action.run(); |
| } |
| else { |
| addRunOnCommit(document, action); |
| } |
| } |
| |
| private final Map<Object, Runnable> actionsWhenAllDocumentsAreCommitted = new LinkedHashMap<Object, Runnable>(); //accessed from EDT only |
| private static final Object PERFORM_ALWAYS_KEY = new Object() { |
| @Override |
| @NonNls |
| public String toString() { |
| return "PERFORM_ALWAYS"; |
| } |
| }; |
| |
| /** |
| * Cancel previously registered action and schedules (new) action to be executed when all documents are committed. |
| * |
| * @param key the (unique) id of the action. |
| * @param action The action to be executed after automatic commit. |
| * This action will overwrite any action which was registered under this key earlier. |
| * The action will be executed in EDT. |
| * @return true if action has been run immediately, or false if action was scheduled for execution later. |
| */ |
| public boolean cancelAndRunWhenAllCommitted(@NonNls @NotNull Object key, @NotNull final Runnable action) { |
| ApplicationManager.getApplication().assertIsDispatchThread(); |
| if (myProject.isDisposed()) { |
| action.run(); |
| return true; |
| } |
| if (myUncommittedDocuments.isEmpty()) { |
| action.run(); |
| if (!hasUncommitedDocuments()) { |
| assert actionsWhenAllDocumentsAreCommitted.isEmpty() : actionsWhenAllDocumentsAreCommitted; |
| } |
| return true; |
| } |
| actionsWhenAllDocumentsAreCommitted.put(key, action); |
| return false; |
| } |
| |
| public static void addRunOnCommit(@NotNull Document document, @NotNull Runnable action) { |
| synchronized (ACTION_AFTER_COMMIT) { |
| List<Runnable> list = document.getUserData(ACTION_AFTER_COMMIT); |
| if (list == null) { |
| document.putUserData(ACTION_AFTER_COMMIT, list = new SmartList<Runnable>()); |
| } |
| list.add(action); |
| } |
| } |
| |
| @Override |
| public void commitDocument(@NotNull final Document doc) { |
| final Document document = doc instanceof DocumentWindow ? ((DocumentWindow)doc).getDelegate() : doc; |
| if (!isCommitted(document)) { |
| doCommit(document); |
| } |
| } |
| |
| public boolean finishCommit(@NotNull final Document document, |
| @NotNull final List<Processor<Document>> finishProcessors, |
| final boolean synchronously, |
| @NotNull final Object reason) { |
| assert !myProject.isDisposed() : "Already disposed"; |
| final boolean[] ok = {true}; |
| ApplicationManager.getApplication().runWriteAction(new CommitToPsiFileAction(document, myProject) { |
| @Override |
| public void run() { |
| ok[0] = finishCommitInWriteAction(document, finishProcessors, synchronously); |
| } |
| }); |
| |
| if (ok[0]) { |
| // otherwise changes maybe not synced to the document yet, and injectors will crash |
| if (!mySynchronizer.isDocumentAffectedByTransactions(document)) { |
| final InjectedLanguageManager injectedLanguageManager = InjectedLanguageManager.getInstance(myProject); |
| if (injectedLanguageManager != null) injectedLanguageManager.startRunInjectors(document, synchronously); |
| } |
| // run after commit actions outside write action |
| runAfterCommitActions(document); |
| if (DebugUtil.DO_EXPENSIVE_CHECKS) { |
| checkAllElementsValid(document, reason); |
| } |
| } |
| return ok[0]; |
| } |
| |
| protected boolean finishCommitInWriteAction(@NotNull final Document document, |
| @NotNull final List<Processor<Document>> finishProcessors, |
| final boolean synchronously) { |
| if (myProject.isDisposed()) |
| return false; |
| |
| assert !(document instanceof DocumentWindow); |
| myIsCommitInProgress = true; |
| boolean success = true; |
| try { |
| final FileViewProvider viewProvider = getCachedViewProvider(document); |
| if (viewProvider != null) { |
| for (Processor<Document> finishRunnable : finishProcessors) { |
| success = finishRunnable.process(document); |
| if (synchronously) { |
| assert success; |
| } |
| if (!success) { |
| break; |
| } |
| } |
| myLastCommittedTexts.remove(document); |
| viewProvider.contentsSynchronized(); |
| } else { |
| handleCommitWithoutPsi(document); |
| } |
| } |
| finally { |
| myDocumentCommitProcessor.log("in PDI.finishDoc: ", null, synchronously, success, myUncommittedDocuments); |
| if (success) { |
| myUncommittedDocuments.remove(document); |
| myDocumentCommitProcessor.log("in PDI.finishDoc: removed doc", null, synchronously, success, myUncommittedDocuments); |
| } |
| myIsCommitInProgress = false; |
| myDocumentCommitProcessor.log("in PDI.finishDoc: exit", null, synchronously, success, myUncommittedDocuments); |
| } |
| |
| return success; |
| } |
| |
| private void checkAllElementsValid(@NotNull Document document, @NotNull final Object reason) { |
| final PsiFile psiFile = getCachedPsiFile(document); |
| if (psiFile != null) { |
| psiFile.accept(new PsiRecursiveElementWalkingVisitor() { |
| @Override |
| public void visitElement(PsiElement element) { |
| if (!element.isValid()) { |
| throw new AssertionError("Commit to '" + psiFile.getVirtualFile() + "' has led to invalid element: " + element + "; Reason: '" + reason + "'"); |
| } |
| } |
| }); |
| } |
| } |
| |
| private void doCommit(@NotNull final Document document) { |
| assert !myIsCommitInProgress : "Do not call commitDocument() from inside PSI change listener"; |
| ApplicationManager.getApplication().runWriteAction(new Runnable() { |
| @Override |
| public void run() { |
| // otherwise there are many clients calling commitAllDocs() on PSI childrenChanged() |
| if (getSynchronizer().isDocumentAffectedByTransactions(document)) return; |
| |
| myIsCommitInProgress = true; |
| try { |
| myDocumentCommitProcessor.commitSynchronously(document, myProject); |
| } |
| finally { |
| myIsCommitInProgress = false; |
| } |
| assert !isInUncommittedSet(document) : "Document :" + document; |
| } |
| }); |
| } |
| |
| @Override |
| public <T> T commitAndRunReadAction(@NotNull final Computable<T> computation) { |
| final Ref<T> ref = Ref.create(null); |
| commitAndRunReadAction(new Runnable() { |
| @Override |
| public void run() { |
| ref.set(computation.compute()); |
| } |
| }); |
| return ref.get(); |
| } |
| |
| @Override |
| public void reparseFiles(@NotNull Collection<VirtualFile> files, boolean includeOpenFiles) { |
| FileContentUtilCore.reparseFiles(files); |
| } |
| |
| @Override |
| public void commitAndRunReadAction(@NotNull final Runnable runnable) { |
| final Application application = ApplicationManager.getApplication(); |
| if (SwingUtilities.isEventDispatchThread()) { |
| commitAllDocuments(); |
| runnable.run(); |
| } |
| else { |
| 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(); |
| final boolean[] committed = {false}; |
| |
| application.runReadAction( |
| new Runnable() { |
| @Override |
| public void run() { |
| if (myUncommittedDocuments.isEmpty()) { |
| runnable.run(); |
| committed[0] = true; |
| } |
| else { |
| s1.down(); |
| s2.down(); |
| final Runnable commitRunnable = new Runnable() { |
| @Override |
| public void run() { |
| commitAllDocuments(); |
| s1.up(); |
| s2.waitFor(); |
| } |
| }; |
| final ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator(); |
| if (progressIndicator == null) { |
| ApplicationManager.getApplication().invokeLater(commitRunnable); |
| } |
| else { |
| ApplicationManager.getApplication().invokeLater(commitRunnable, progressIndicator.getModalityState()); |
| } |
| } |
| } |
| } |
| ); |
| |
| if (!committed[0]) { |
| s1.waitFor(); |
| application.runReadAction( |
| new Runnable() { |
| @Override |
| public void run() { |
| s2.up(); |
| runnable.run(); |
| } |
| } |
| ); |
| } |
| } |
| } |
| |
| /** |
| * Schedules action to be executed when all documents are committed. |
| * |
| * @return true if action has been run immediately, or false if action was scheduled for execution later. |
| */ |
| @Override |
| public boolean performWhenAllCommitted(@NotNull final Runnable action) { |
| ApplicationManager.getApplication().assertIsDispatchThread(); |
| assert !myProject.isDisposed() : "Already disposed: " + myProject; |
| if (myUncommittedDocuments.isEmpty()) { |
| action.run(); |
| return true; |
| } |
| CompositeRunnable actions = (CompositeRunnable)actionsWhenAllDocumentsAreCommitted.get(PERFORM_ALWAYS_KEY); |
| if (actions == null) { |
| actions = new CompositeRunnable(); |
| actionsWhenAllDocumentsAreCommitted.put(PERFORM_ALWAYS_KEY, actions); |
| } |
| actions.add(action); |
| myDocumentCommitProcessor.log("PDI: added performWhenAllCommitted", null, false, action, myUncommittedDocuments); |
| return false; |
| } |
| |
| private static class CompositeRunnable extends ArrayList<Runnable> implements Runnable { |
| @Override |
| public void run() { |
| for (Runnable runnable : this) { |
| runnable.run(); |
| } |
| } |
| } |
| |
| private void runAfterCommitActions(@NotNull Document document) { |
| ApplicationManager.getApplication().assertIsDispatchThread(); |
| List<Runnable> list; |
| synchronized (ACTION_AFTER_COMMIT) { |
| list = document.getUserData(ACTION_AFTER_COMMIT); |
| if (list != null) { |
| list = new ArrayList<Runnable>(list); |
| document.putUserData(ACTION_AFTER_COMMIT, null); |
| } |
| } |
| if (list != null) { |
| for (final Runnable runnable : list) { |
| runnable.run(); |
| } |
| } |
| |
| if (!hasUncommitedDocuments() && !actionsWhenAllDocumentsAreCommitted.isEmpty()) { |
| List<Object> keys = new ArrayList<Object>(actionsWhenAllDocumentsAreCommitted.keySet()); |
| for (Object key : keys) { |
| try { |
| Runnable action = actionsWhenAllDocumentsAreCommitted.remove(key); |
| myDocumentCommitProcessor.log("Running after commit runnable: ", null, false, key, action); |
| action.run(); |
| } |
| catch (Throwable e) { |
| LOG.error(e); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void addListener(@NotNull Listener listener) { |
| myListeners.add(listener); |
| } |
| |
| @Override |
| public void removeListener(@NotNull Listener listener) { |
| myListeners.remove(listener); |
| } |
| |
| @Override |
| public boolean isDocumentBlockedByPsi(@NotNull Document doc) { |
| return false; |
| } |
| |
| @Override |
| public void doPostponedOperationsAndUnblockDocument(@NotNull Document doc) { |
| } |
| |
| protected void fireDocumentCreated(@NotNull Document document, PsiFile file) { |
| for (Listener listener : myListeners) { |
| listener.documentCreated(document, file); |
| } |
| } |
| |
| private void fireFileCreated(Document document, PsiFile file) { |
| for (Listener listener : myListeners) { |
| listener.fileCreated(file, document); |
| } |
| } |
| |
| @Override |
| @NotNull |
| public CharSequence getLastCommittedText(@NotNull Document document) { |
| CharSequence text = myLastCommittedTexts.get(document); |
| return text != null ? text : document.getImmutableCharSequence(); |
| } |
| |
| @Override |
| @NotNull |
| public Document[] getUncommittedDocuments() { |
| ApplicationManager.getApplication().assertIsDispatchThread(); |
| return myUncommittedDocuments.toArray(new Document[myUncommittedDocuments.size()]); |
| } |
| |
| boolean isInUncommittedSet(@NotNull Document document) { |
| if (document instanceof DocumentWindow) return isInUncommittedSet(((DocumentWindow)document).getDelegate()); |
| return myUncommittedDocuments.contains(document); |
| } |
| |
| @Override |
| public boolean isUncommited(@NotNull Document document) { |
| return !isCommitted(document); |
| } |
| |
| @Override |
| public boolean isCommitted(@NotNull Document document) { |
| if (document instanceof DocumentWindow) return isCommitted(((DocumentWindow)document).getDelegate()); |
| if (getSynchronizer().isInSynchronization(document)) return true; |
| return !((DocumentEx)document).isInEventsHandling() && !isInUncommittedSet(document); |
| } |
| |
| @Override |
| public boolean hasUncommitedDocuments() { |
| return !myIsCommitInProgress && !myUncommittedDocuments.isEmpty(); |
| } |
| |
| @Override |
| public void beforeDocumentChange(DocumentEvent event) { |
| final Document document = event.getDocument(); |
| if (!(document instanceof DocumentWindow) && !myLastCommittedTexts.containsKey(document)) { |
| myLastCommittedTexts.put(document, document.getImmutableCharSequence()); |
| } |
| |
| final FileViewProvider viewProvider = getCachedViewProvider(document); |
| if (viewProvider == null) return; |
| if (!isRelevant(viewProvider)) return; |
| |
| VirtualFile virtualFile = viewProvider.getVirtualFile(); |
| if (virtualFile.getFileType().isBinary()) return; |
| |
| final List<PsiFile> files = viewProvider.getAllFiles(); |
| PsiFile psiCause = null; |
| for (PsiFile file : files) { |
| if (file == null) { |
| throw new AssertionError("View provider "+viewProvider+" ("+viewProvider.getClass()+") returned null in its files array: "+files+" for file "+viewProvider.getVirtualFile()); |
| } |
| mySmartPointerManager.fastenBelts(file, event.getOffset(), null); |
| |
| if (mySynchronizer.isInsideAtomicChange(file)) { |
| psiCause = file; |
| } |
| } |
| |
| if (psiCause == null) { |
| beforeDocumentChangeOnUnlockedDocument(viewProvider); |
| } |
| |
| ((SingleRootFileViewProvider)viewProvider).beforeDocumentChanged(psiCause); |
| } |
| |
| protected void beforeDocumentChangeOnUnlockedDocument(@NotNull final FileViewProvider viewProvider) { |
| } |
| |
| @Override |
| public void documentChanged(DocumentEvent event) { |
| final Document document = event.getDocument(); |
| final FileViewProvider viewProvider = getCachedViewProvider(document); |
| if (viewProvider == null) { |
| handleCommitWithoutPsi(document); |
| return; |
| } |
| if (!isRelevant(viewProvider)) { |
| myLastCommittedTexts.remove(document); |
| return; |
| } |
| |
| ApplicationManager.getApplication().assertWriteAccessAllowed(); |
| final List<PsiFile> files = viewProvider.getAllFiles(); |
| boolean commitNecessary = true; |
| for (PsiFile file : files) { |
| mySmartPointerManager.unfastenBelts(file, event.getOffset()); |
| |
| if (mySynchronizer.isInsideAtomicChange(file)) { |
| commitNecessary = false; |
| continue; |
| } |
| |
| assert file instanceof PsiFileImpl || "mock.file".equals(file.getName()) && ApplicationManager.getApplication().isUnitTestMode() : |
| event + |
| "; file=" + |
| file + |
| "; allFiles=" + |
| files + |
| "; viewProvider=" + |
| viewProvider; |
| } |
| |
| boolean forceCommit = ApplicationManager.getApplication().hasWriteAction(ExternalChangeAction.class) && |
| (SystemProperties.getBooleanProperty("idea.force.commit.on.external.change", false) || |
| ApplicationManager.getApplication().isHeadlessEnvironment() |
| ); |
| |
| // 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) { |
| assert !(document instanceof DocumentWindow); |
| myUncommittedDocuments.add(document); |
| myDocumentCommitProcessor.log("added uncommitted doc", null, false, myProject, document, ((DocumentEx)document).isInBulkUpdate()); |
| if (forceCommit) { |
| commitDocument(document); |
| } |
| else if (!((DocumentEx)document).isInBulkUpdate()) { |
| myDocumentCommitProcessor.commitAsynchronously(myProject, document, event); |
| } |
| } else { |
| myLastCommittedTexts.remove(document); |
| } |
| } |
| |
| public void handleCommitWithoutPsi(final Document document) { |
| final CharSequence prevText = myLastCommittedTexts.remove(document); |
| if (prevText == null) { |
| return; |
| } |
| |
| if (!myProject.isInitialized() || myProject.isDisposed()) { |
| return; |
| } |
| |
| VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document); |
| if (virtualFile == null || !FileIndexFacade.getInstance(myProject).isInContent(virtualFile)) { |
| return; |
| } |
| |
| final PsiFileImpl psiFile = (PsiFileImpl)getPsiFile(document); |
| if (psiFile == null) { |
| return; |
| } |
| |
| ApplicationManager.getApplication().runWriteAction(new Runnable() { |
| public void run() { |
| psiFile.getViewProvider().beforeContentsSynchronized(); |
| synchronized (PsiLock.LOCK) { |
| final int oldLength = prevText.length(); |
| PsiManagerImpl manager = (PsiManagerImpl)psiFile.getManager(); |
| BlockSupportImpl.sendBeforeChildrenChangeEvent(manager, psiFile, true); |
| BlockSupportImpl.sendBeforeChildrenChangeEvent(manager, psiFile, false); |
| psiFile.onContentReload(); |
| BlockSupportImpl.sendAfterChildrenChangedEvent(manager, psiFile, oldLength, false); |
| BlockSupportImpl.sendAfterChildrenChangedEvent(manager, psiFile, oldLength, true); |
| } |
| psiFile.getViewProvider().contentsSynchronized(); |
| } |
| }); |
| |
| } |
| |
| private boolean isRelevant(@NotNull FileViewProvider viewProvider) { |
| VirtualFile virtualFile = viewProvider.getVirtualFile(); |
| return !virtualFile.getFileType().isBinary() && |
| viewProvider.getManager() == myPsiManager && |
| !myPsiManager.getProject().isDisposed(); |
| } |
| |
| public static boolean checkConsistency(PsiFile psiFile, Document document) { |
| //todo hack |
| if (psiFile.getVirtualFile() == null) return true; |
| |
| CharSequence editorText = document.getCharsSequence(); |
| int documentLength = document.getTextLength(); |
| if (psiFile.textMatches(editorText)) { |
| LOG.assertTrue(psiFile.getTextLength() == documentLength); |
| return true; |
| } |
| |
| char[] fileText = psiFile.textToCharArray(); |
| @SuppressWarnings({"NonConstantStringShouldBeStringBuffer"}) |
| @NonNls String error = "File '" + psiFile.getName() + "' text mismatch after reparse. " + |
| "File length=" + fileText.length + "; Doc length=" + documentLength + "\n"; |
| int i = 0; |
| for (; i < documentLength; i++) { |
| if (i >= fileText.length) { |
| error += "editorText.length > psiText.length i=" + i + "\n"; |
| break; |
| } |
| if (i >= editorText.length()) { |
| error += "editorText.length > psiText.length i=" + i + "\n"; |
| break; |
| } |
| if (editorText.charAt(i) != fileText[i]) { |
| error += "first unequal char i=" + i + "\n"; |
| break; |
| } |
| } |
| //error += "*********************************************" + "\n"; |
| //if (i <= 500){ |
| // error += "Equal part:" + editorText.subSequence(0, i) + "\n"; |
| //} |
| //else{ |
| // error += "Equal part start:\n" + editorText.subSequence(0, 200) + "\n"; |
| // error += "................................................" + "\n"; |
| // error += "................................................" + "\n"; |
| // error += "................................................" + "\n"; |
| // error += "Equal part end:\n" + editorText.subSequence(i - 200, i) + "\n"; |
| //} |
| error += "*********************************************" + "\n"; |
| error += "Editor Text tail:(" + (documentLength - i) + ")\n";// + editorText.subSequence(i, Math.min(i + 300, documentLength)) + "\n"; |
| error += "*********************************************" + "\n"; |
| error += "Psi Text tail:(" + (fileText.length - i) + ")\n"; |
| error += "*********************************************" + "\n"; |
| |
| if (document instanceof DocumentWindow) { |
| error += "doc: '" + document.getText() + "'\n"; |
| error += "psi: '" + psiFile.getText() + "'\n"; |
| error += "ast: '" + psiFile.getNode().getText() + "'\n"; |
| error += psiFile.getLanguage() + "\n"; |
| PsiElement context = InjectedLanguageManager.getInstance(psiFile.getProject()).getInjectionHost(psiFile); |
| if (context != null) { |
| error += "context: " + context + "; text: '" + context.getText() + "'\n"; |
| error += "context file: " + context.getContainingFile() + "\n"; |
| } |
| error += "document window ranges: " + Arrays.asList(((DocumentWindow)document).getHostRanges()) + "\n"; |
| } |
| LOG.error(error); |
| //document.replaceString(0, documentLength, psiFile.getText()); |
| return false; |
| } |
| |
| @TestOnly |
| public void clearUncommittedDocuments() { |
| myLastCommittedTexts.clear(); |
| myUncommittedDocuments.clear(); |
| mySynchronizer.cleanupForNextTest(); |
| } |
| |
| public PsiToDocumentSynchronizer getSynchronizer() { |
| return mySynchronizer; |
| } |
| } |