public abstract class PsiDocumentManagerBase extends PsiDocumentManager implements ProjectComponent, DocumentListener { protected static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.PsiDocumentManagerImpl"); protected static final Key<PsiFile> HARD_REF_TO_PSI = new Key<PsiFile>("HARD_REFERENCE_TO_PSI"); protected static final Key<List<Runnable>> ACTION_AFTER_COMMIT = Key.create("ACTION_AFTER_COMMIT"); protected final Project myProject; protected final PsiManager myPsiManager; protected final DocumentCommitProcessor myDocumentCommitProcessor; protected final Set<Document> myUncommittedDocuments = Collections.synchronizedSet(new HashSet<Document>()); protected volatile boolean myIsCommitInProgress; protected final PsiToDocumentSynchronizer mySynchronizer; protected final List<Listener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList(); protected 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 public void projectOpened() {} @Override public void projectClosed() {} @Override @NotNull public String getComponentName() { return "PsiDocumentManager"; } @Override public void initComponent() {} @Override public void disposeComponent() {} @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, @NotNull 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 protected 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; } if (!file.getViewProvider().isEventSystemEnabled()) return null; document = FileDocumentManager.getInstance().getDocument(file.getViewProvider().getVirtualFile()); if (document != null) { if (document.getTextLength() != file.getTextLength()) { throw new AssertionError( "Modified PSI with no document: " + file + "; physical=" + file.getViewProvider().isPhysical()); } if (!file.getViewProvider().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(); 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, null); } } 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; } } viewProvider.contentsSynchronized(); } } 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 + "'"); } } }); } } protected void doCommit(@NotNull final Document document, final PsiFile excludeFile) { 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) && excludeFile == null) return; myIsCommitInProgress = true; try { myDocumentCommitProcessor.commitSynchronously(document, myProject, excludeFile); } finally { myIsCommitInProgress = false; } assert !myUncommittedDocuments.contains(document) : "Document :" + document; } }); } public void commitOtherFilesAssociatedWithDocument( final Document document, final PsiFile psiFile) {} @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 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) { Runnable action = actionsWhenAllDocumentsAreCommitted.remove(key); myDocumentCommitProcessor.log("Running after commit runnable: ", null, false, key, action); action.run(); } } } @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 Document[] getUncommittedDocuments() { ApplicationManager.getApplication().assertIsDispatchThread(); return myUncommittedDocuments.toArray(new Document[myUncommittedDocuments.size()]); } public Collection<Document> getUncommittedDocumentsUnsafe() { return myUncommittedDocuments; } @Override public boolean isUncommited(@NotNull Document document) { return !isCommitted(document); } @Override public boolean isCommitted(@NotNull Document document) { if (getSynchronizer().isInSynchronization(document)) return true; return !((DocumentEx) document).isInEventsHandling() && !myUncommittedDocuments.contains(document); } @Override public boolean hasUncommitedDocuments() { return !myIsCommitInProgress && !myUncommittedDocuments.isEmpty(); } @Override public void beforeDocumentChange(DocumentEvent event) { final Document document = event.getDocument(); 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) { mySmartPointerManager.fastenBelts(file, event.getOffset(), null); if (TextBlock.get(file).isLocked()) { 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) return; if (!isRelevant(viewProvider)) return; ApplicationManager.getApplication().assertWriteAccessAllowed(); final List<PsiFile> files = viewProvider.getAllFiles(); boolean commitNecessary = true; for (PsiFile file : files) { mySmartPointerManager.unfastenBelts(file, event.getOffset()); final TextBlock textBlock = TextBlock.get(file); if (textBlock.isLocked()) { commitNecessary = false; continue; } textBlock.documentChanged(event); 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) { myUncommittedDocuments.add(document); if (forceCommit) { commitDocument(document); } else if (!((DocumentEx) document).isInBulkUpdate()) { myDocumentCommitProcessor.commitAsynchronously(myProject, document, event); } } } 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() { myUncommittedDocuments.clear(); mySynchronizer.cleanupForNextTest(); } public PsiToDocumentSynchronizer getSynchronizer() { return mySynchronizer; } }
@Override protected void collectInformationWithProgress(@NotNull final ProgressIndicator progress) { if (!Registry.is("editor.injected.highlighting.enabled")) return; final Set<HighlightInfo> gotHighlights = new THashSet<HighlightInfo>(100); final List<PsiElement> inside = new ArrayList<PsiElement>(); final List<PsiElement> outside = new ArrayList<PsiElement>(); List<ProperTextRange> insideRanges = new ArrayList<ProperTextRange>(); List<ProperTextRange> outsideRanges = new ArrayList<ProperTextRange>(); // TODO: this thing is just called TWICE with same arguments eating CPU on huge files :( Divider.divideInsideAndOutside( myFile, myRestrictRange.getStartOffset(), myRestrictRange.getEndOffset(), myPriorityRange, inside, insideRanges, outside, outsideRanges, false, SHOULD_HIGHIGHT_FILTER); // all infos for the "injected fragment for the host which is inside" are indeed inside // but some of the infos for the "injected fragment for the host which is outside" can be still // inside Set<HighlightInfo> injectedResult = new THashSet<HighlightInfo>(); Set<PsiFile> injected = getInjectedPsiFiles(inside, outside, progress); setProgressLimit(injected.size()); if (!addInjectedPsiHighlights( injected, progress, Collections.synchronizedSet(injectedResult))) { throw new ProcessCanceledException(); } final List<HighlightInfo> injectionsOutside = new ArrayList<HighlightInfo>(gotHighlights.size()); Set<HighlightInfo> result; synchronized (injectedResult) { // sync here because all writes happened in another thread result = injectedResult; } for (HighlightInfo info : result) { if (myRestrictRange.contains(info)) { gotHighlights.add(info); } else { // nonconditionally apply injected results regardless whether they are in // myStartOffset,myEndOffset injectionsOutside.add(info); } } if (!injectionsOutside.isEmpty()) { final ProperTextRange priorityIntersection = myPriorityRange.intersection(myRestrictRange); if ((!inside.isEmpty() || !gotHighlights.isEmpty()) && priorityIntersection != null) { // do not apply when there were no elements to highlight // clear infos found in visible area to avoid applying them twice final List<HighlightInfo> toApplyInside = new ArrayList<HighlightInfo>(gotHighlights); myHighlights.addAll(toApplyInside); gotHighlights.clear(); myHighlightInfoProcessor.highlightsInsideVisiblePartAreProduced( myHighlightingSession, toApplyInside, myPriorityRange, myRestrictRange, getId()); } List<HighlightInfo> toApply = new ArrayList<HighlightInfo>(); for (HighlightInfo info : gotHighlights) { if (!myRestrictRange.containsRange(info.getStartOffset(), info.getEndOffset())) continue; if (!myPriorityRange.containsRange(info.getStartOffset(), info.getEndOffset())) { toApply.add(info); } } toApply.addAll(injectionsOutside); myHighlightInfoProcessor.highlightsOutsideVisiblePartAreProduced( myHighlightingSession, toApply, myRestrictRange, new ProperTextRange(0, myDocument.getTextLength()), getId()); } else { // else apply only result (by default apply command) and only within inside myHighlights.addAll(gotHighlights); myHighlightInfoProcessor.highlightsInsideVisiblePartAreProduced( myHighlightingSession, myHighlights, myRestrictRange, myRestrictRange, getId()); } }
private void processBatch( @NotNull final ProgressIndicator indicator, @NotNull Set<VirtualFile> files) { assert !myApplication.isDispatchThread(); final int resolvedInPreviousBatch = this.resolvedInPreviousBatch; final int totalSize = files.size() + resolvedInPreviousBatch; final ConcurrentIntObjectMap<int[]> fileToForwardIds = ContainerUtil.createConcurrentIntObjectMap(); final Set<VirtualFile> toProcess = Collections.synchronizedSet(files); indicator.setIndeterminate(false); ProgressIndicatorUtils.forceWriteActionPriority(indicator, (Disposable) indicator); long start = System.currentTimeMillis(); Processor<VirtualFile> processor = file -> { double fraction = 1 - toProcess.size() * 1.0 / totalSize; indicator.setFraction(fraction); try { if (!file.isDirectory() && toResolve(file, myProject)) { int fileId = getAbsId(file); int i = totalSize - toProcess.size(); indicator.setText(i + "/" + totalSize + ": Resolving " + file.getPresentableUrl()); int[] forwardIds = processFile(file, fileId, indicator); if (forwardIds == null) { // queueUpdate(file); return false; } fileToForwardIds.put(fileId, forwardIds); } toProcess.remove(file); return true; } catch (RuntimeException e) { indicator.checkCanceled(); } return true; }; boolean success = true; try { success = processFilesConcurrently(files, indicator, processor); } finally { this.resolvedInPreviousBatch = toProcess.isEmpty() ? 0 : totalSize - toProcess.size(); queue(toProcess, "re-added after fail. success=" + success); storeIds(fileToForwardIds); long end = System.currentTimeMillis(); log( "Resolved batch of " + (totalSize - toProcess.size()) + " from " + totalSize + " files in " + ((end - start) / 1000) + "sec. (Gap: " + storage.gap + ")"); synchronized (filesToResolve) { upToDate = filesToResolve.isEmpty(); log("upToDate = " + upToDate); if (upToDate) { for (Listener listener : myListeners) { listener.allFilesResolved(); } } } } }
public void inlineElement( final Project project, final Editor editor, final PsiElement psiElement) { final PsiParameter psiParameter = (PsiParameter) psiElement; final PsiParameterList parameterList = (PsiParameterList) psiParameter.getParent(); if (!(parameterList.getParent() instanceof PsiMethod)) { return; } final int index = parameterList.getParameterIndex(psiParameter); final PsiMethod method = (PsiMethod) parameterList.getParent(); String errorMessage = getCannotInlineMessage(psiParameter, method); if (errorMessage != null) { CommonRefactoringUtil.showErrorHint( project, editor, errorMessage, RefactoringBundle.message("inline.parameter.refactoring"), null); return; } final Ref<PsiExpression> refInitializer = new Ref<PsiExpression>(); final Ref<PsiExpression> refConstantInitializer = new Ref<PsiExpression>(); final Ref<PsiCallExpression> refMethodCall = new Ref<PsiCallExpression>(); final List<PsiReference> occurrences = Collections.synchronizedList(new ArrayList<PsiReference>()); final Collection<PsiFile> containingFiles = Collections.synchronizedSet(new HashSet<PsiFile>()); containingFiles.add(psiParameter.getContainingFile()); boolean result = ReferencesSearch.search(method) .forEach( new Processor<PsiReference>() { public boolean process(final PsiReference psiReference) { PsiElement element = psiReference.getElement(); final PsiElement parent = element.getParent(); if (parent instanceof PsiCallExpression) { final PsiCallExpression methodCall = (PsiCallExpression) parent; occurrences.add(psiReference); containingFiles.add(element.getContainingFile()); final PsiExpression[] expressions = methodCall.getArgumentList().getExpressions(); if (expressions.length <= index) return false; PsiExpression argument = expressions[index]; if (!refInitializer.isNull()) { return argument != null && PsiEquivalenceUtil.areElementsEquivalent( refInitializer.get(), argument) && PsiEquivalenceUtil.areElementsEquivalent( refMethodCall.get(), methodCall); } if (InlineToAnonymousConstructorProcessor.isConstant(argument) || getReferencedFinalField(argument) != null) { if (refConstantInitializer.isNull()) { refConstantInitializer.set(argument); } else if (!isSameConstant(argument, refConstantInitializer.get())) { return false; } } else if (!isRecursiveReferencedParameter(argument, psiParameter)) { if (!refConstantInitializer.isNull()) return false; refInitializer.set(argument); refMethodCall.set(methodCall); } } return true; } }); final int offset = editor.getCaretModel().getOffset(); final PsiElement refExpr = psiElement.getContainingFile().findElementAt(offset); final PsiCodeBlock codeBlock = PsiTreeUtil.getParentOfType(refExpr, PsiCodeBlock.class); if (codeBlock != null) { final PsiElement[] defs = DefUseUtil.getDefs(codeBlock, psiParameter, refExpr); if (defs.length == 1) { final PsiElement def = defs[0]; if (def instanceof PsiReferenceExpression && PsiUtil.isOnAssignmentLeftHand((PsiExpression) def)) { final PsiExpression rExpr = ((PsiAssignmentExpression) def.getParent()).getRExpression(); if (rExpr != null) { final PsiElement[] refs = DefUseUtil.getRefs(codeBlock, psiParameter, refExpr); if (InlineLocalHandler.checkRefsInAugmentedAssignmentOrUnaryModified(refs, def) == null) { new WriteCommandAction(project) { @Override protected void run(Result result) throws Throwable { for (final PsiElement ref : refs) { InlineUtil.inlineVariable( psiParameter, rExpr, (PsiJavaCodeReferenceElement) ref); } def.getParent().delete(); } }.execute(); return; } } } } } if (occurrences.isEmpty()) { CommonRefactoringUtil.showErrorHint( project, editor, "Method has no usages", RefactoringBundle.message("inline.parameter.refactoring"), null); return; } if (!result) { CommonRefactoringUtil.showErrorHint( project, editor, "Cannot find constant initializer for parameter", RefactoringBundle.message("inline.parameter.refactoring"), null); return; } if (!refInitializer.isNull()) { if (ApplicationManager.getApplication().isUnitTestMode()) { final InlineParameterExpressionProcessor processor = new InlineParameterExpressionProcessor( refMethodCall.get(), method, psiParameter, refInitializer.get(), method .getProject() .getUserData(InlineParameterExpressionProcessor.CREATE_LOCAL_FOR_TESTS)); processor.run(); } else { final boolean createLocal = ReferencesSearch.search(psiParameter).findAll().size() > 1; InlineParameterDialog dlg = new InlineParameterDialog( refMethodCall.get(), method, psiParameter, refInitializer.get(), createLocal); dlg.show(); } return; } if (refConstantInitializer.isNull()) { CommonRefactoringUtil.showErrorHint( project, editor, "Cannot find constant initializer for parameter", RefactoringBundle.message("inline.parameter.refactoring"), null); return; } final Ref<Boolean> isNotConstantAccessible = new Ref<Boolean>(); final PsiExpression constantExpression = refConstantInitializer.get(); constantExpression.accept( new JavaRecursiveElementVisitor() { @Override public void visitReferenceExpression(PsiReferenceExpression expression) { super.visitReferenceExpression(expression); final PsiElement resolved = expression.resolve(); if (resolved instanceof PsiMember && !PsiUtil.isAccessible((PsiMember) resolved, method, null)) { isNotConstantAccessible.set(Boolean.TRUE); } } }); if (!isNotConstantAccessible.isNull() && isNotConstantAccessible.get()) { CommonRefactoringUtil.showErrorHint( project, editor, "Constant initializer is not accessible in method body", RefactoringBundle.message("inline.parameter.refactoring"), null); return; } for (PsiReference psiReference : ReferencesSearch.search(psiParameter)) { final PsiElement element = psiReference.getElement(); if (element instanceof PsiExpression && PsiUtil.isAccessedForWriting((PsiExpression) element)) { CommonRefactoringUtil.showErrorHint( project, editor, "Inline parameter which has write usages is not supported", RefactoringBundle.message("inline.parameter.refactoring"), null); return; } } if (!ApplicationManager.getApplication().isUnitTestMode()) { String occurencesString = RefactoringBundle.message("occurences.string", occurrences.size()); String question = RefactoringBundle.message( "inline.parameter.confirmation", psiParameter.getName(), constantExpression.getText()) + " " + occurencesString; RefactoringMessageDialog dialog = new RefactoringMessageDialog( REFACTORING_NAME, question, HelpID.INLINE_VARIABLE, "OptionPane.questionIcon", true, project); dialog.show(); if (!dialog.isOK()) { return; } } final RefactoringEventData data = new RefactoringEventData(); data.addElement(psiElement.copy()); project .getMessageBus() .syncPublisher(RefactoringEventListener.REFACTORING_EVENT_TOPIC) .refactoringStarted(REFACTORING_ID, data); SameParameterValueInspection.InlineParameterValueFix.inlineSameParameterValue( method, psiParameter, constantExpression); project .getMessageBus() .syncPublisher(RefactoringEventListener.REFACTORING_EVENT_TOPIC) .refactoringDone(REFACTORING_ID, null); }
/** @author cdr */ public class InjectedLanguageManagerImpl extends InjectedLanguageManager implements Disposable { private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.tree.injected.InjectedLanguageManagerImpl"); static final Object ourInjectionPsiLock = new String("injectionPsiLock"); private final Project myProject; private final DumbService myDumbService; private volatile DaemonProgressIndicator myProgress; public static InjectedLanguageManagerImpl getInstanceImpl(Project project) { return (InjectedLanguageManagerImpl) InjectedLanguageManager.getInstance(project); } public InjectedLanguageManagerImpl(Project project, DumbService dumbService) { myProject = project; myDumbService = dumbService; final ExtensionPoint<MultiHostInjector> multiPoint = Extensions.getArea(project).getExtensionPoint(MultiHostInjector.MULTIHOST_INJECTOR_EP_NAME); ((ExtensionPointImpl<MultiHostInjector>) multiPoint) .addExtensionPointListener( new ExtensionPointListener<MultiHostInjector>() { @Override public void extensionAdded( @NotNull MultiHostInjector injector, @Nullable PluginDescriptor pluginDescriptor) { clearInjectorCache(); } @Override public void extensionRemoved( @NotNull MultiHostInjector injector, @Nullable PluginDescriptor pluginDescriptor) { clearInjectorCache(); } }, false, this); final ExtensionPointListener<LanguageInjector> myListener = new ExtensionPointListener<LanguageInjector>() { @Override public void extensionAdded( @NotNull LanguageInjector extension, @Nullable PluginDescriptor pluginDescriptor) { clearInjectorCache(); } @Override public void extensionRemoved( @NotNull LanguageInjector extension, @Nullable PluginDescriptor pluginDescriptor) { clearInjectorCache(); } }; final ExtensionPoint<LanguageInjector> psiManagerPoint = Extensions.getRootArea().getExtensionPoint(LanguageInjector.EXTENSION_POINT_NAME); ((ExtensionPointImpl<LanguageInjector>) psiManagerPoint) .addExtensionPointListener(myListener, false, this); myProgress = new DaemonProgressIndicator(); project .getMessageBus() .connect(this) .subscribe( DaemonCodeAnalyzer.DAEMON_EVENT_TOPIC, new DaemonCodeAnalyzer.DaemonListenerAdapter() { @Override public void daemonCancelEventOccurred(@NotNull String reason) { if (!myProgress.isCanceled()) myProgress.cancel(); } }); } @Override public void dispose() { myProgress.cancel(); EditorWindowImpl.disposeInvalidEditors(); } @Override public void startRunInjectors(@NotNull final Document hostDocument, final boolean synchronously) { if (myProject.isDisposed()) return; if (!synchronously && ApplicationManager.getApplication().isWriteAccessAllowed()) return; // use cached to avoid recreate PSI in alien project final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(myProject); final PsiFile hostPsiFile = documentManager.getCachedPsiFile(hostDocument); if (hostPsiFile == null) return; final ConcurrentList<DocumentWindow> injected = InjectedLanguageUtil.getCachedInjectedDocuments(hostPsiFile); if (injected.isEmpty()) return; if (myProgress.isCanceled()) { myProgress = new DaemonProgressIndicator(); } final Set<DocumentWindow> newDocuments = Collections.synchronizedSet(new THashSet<>()); final Processor<DocumentWindow> commitProcessor = documentWindow -> { if (myProject.isDisposed()) return false; ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); if (indicator != null && indicator.isCanceled()) return false; if (documentManager.isUncommited(hostDocument) || !hostPsiFile.isValid()) return false; // will be committed later // it is here where the reparse happens and old file contents replaced InjectedLanguageUtil.enumerate( documentWindow, hostPsiFile, (injectedPsi, places) -> { DocumentWindow newDocument = (DocumentWindow) injectedPsi.getViewProvider().getDocument(); if (newDocument != null) { PsiDocumentManagerBase.checkConsistency(injectedPsi, newDocument); newDocuments.add(newDocument); } }); return true; }; final Runnable commitInjectionsRunnable = () -> { if (myProgress.isCanceled()) return; JobLauncher.getInstance() .invokeConcurrentlyUnderProgress( new ArrayList<>(injected), myProgress, true, commitProcessor); synchronized (ourInjectionPsiLock) { injected.clear(); injected.addAll(newDocuments); } }; if (synchronously) { if (Thread.holdsLock(PsiLock.LOCK)) { // hack for the case when docCommit was called from within PSI modification, e.g. in // formatter. // we can't spawn threads to do injections there, otherwise a deadlock is imminent ContainerUtil.process(new ArrayList<>(injected), commitProcessor); } else { commitInjectionsRunnable.run(); } } else { JobLauncher.getInstance() .submitToJobThread( () -> ApplicationManagerEx.getApplicationEx() .tryRunReadAction(commitInjectionsRunnable), null); } } @Override public PsiLanguageInjectionHost getInjectionHost(@NotNull PsiElement element) { final PsiFile file = element.getContainingFile(); final VirtualFile virtualFile = file == null ? null : file.getVirtualFile(); if (virtualFile instanceof VirtualFileWindow) { PsiElement host = FileContextUtil.getFileContext( file); // use utility method in case the file's overridden getContext() if (host instanceof PsiLanguageInjectionHost) { return (PsiLanguageInjectionHost) host; } } return null; } @Override @NotNull public TextRange injectedToHost( @NotNull PsiElement injectedContext, @NotNull TextRange injectedTextRange) { PsiFile file = injectedContext.getContainingFile(); if (file == null) return injectedTextRange; Document document = PsiDocumentManager.getInstance(file.getProject()).getCachedDocument(file); if (!(document instanceof DocumentWindowImpl)) return injectedTextRange; DocumentWindowImpl documentWindow = (DocumentWindowImpl) document; return documentWindow.injectedToHost(injectedTextRange); } @Override public int injectedToHost(@NotNull PsiElement element, int offset) { PsiFile file = element.getContainingFile(); if (file == null) return offset; Document document = PsiDocumentManager.getInstance(file.getProject()).getCachedDocument(file); if (!(document instanceof DocumentWindowImpl)) return offset; DocumentWindowImpl documentWindow = (DocumentWindowImpl) document; return documentWindow.injectedToHost(offset); } // used only from tests => no need for complex synchronization private final Set<MultiHostInjector> myManualInjectors = Collections.synchronizedSet(new LinkedHashSet<MultiHostInjector>()); private volatile ClassMapCachingNulls<MultiHostInjector> cachedInjectors; public void processInjectableElements( Collection<PsiElement> in, Processor<PsiElement> processor) { ClassMapCachingNulls<MultiHostInjector> map = getInjectorMap(); for (PsiElement element : in) { if (map.get(element.getClass()) != null) processor.process(element); } } private ClassMapCachingNulls<MultiHostInjector> getInjectorMap() { ClassMapCachingNulls<MultiHostInjector> cached = cachedInjectors; if (cached != null) { return cached; } Map<Class, MultiHostInjector[]> injectors = ContainerUtil.newHashMap(); List<MultiHostInjector> allInjectors = ContainerUtil.newArrayList(); allInjectors.addAll(myManualInjectors); Collections.addAll( allInjectors, MultiHostInjector.MULTIHOST_INJECTOR_EP_NAME.getExtensions(myProject)); if (LanguageInjector.EXTENSION_POINT_NAME.getExtensions().length > 0) { allInjectors.add(PsiManagerRegisteredInjectorsAdapter.INSTANCE); } for (MultiHostInjector injector : allInjectors) { for (Class<? extends PsiElement> place : injector.elementsToInjectIn()) { LOG.assertTrue(place != null, injector); MultiHostInjector[] existing = injectors.get(place); injectors.put( place, existing == null ? new MultiHostInjector[] {injector} : ArrayUtil.append(existing, injector)); } } ClassMapCachingNulls<MultiHostInjector> result = new ClassMapCachingNulls<>(injectors, new MultiHostInjector[0], allInjectors); cachedInjectors = result; return result; } private void clearInjectorCache() { cachedInjectors = null; } @Override public void registerMultiHostInjector(@NotNull MultiHostInjector injector) { myManualInjectors.add(injector); clearInjectorCache(); } @Override public boolean unregisterMultiHostInjector(@NotNull MultiHostInjector injector) { try { return myManualInjectors.remove(injector); } finally { clearInjectorCache(); } } @Override public String getUnescapedText(@NotNull final PsiElement injectedNode) { final StringBuilder text = new StringBuilder(injectedNode.getTextLength()); // gather text from (patched) leaves injectedNode.accept( new PsiRecursiveElementWalkingVisitor() { @Override public void visitElement(PsiElement element) { String leafText = InjectedLanguageUtil.getUnescapedLeafText(element, false); if (leafText != null) { text.append(leafText); return; } super.visitElement(element); } }); return text.toString(); } /** * intersection may spread over several injected fragments * * @param rangeToEdit range in encoded(raw) PSI * @return list of ranges in encoded (raw) PSI */ @Override @SuppressWarnings({"ConstantConditions", "unchecked"}) @NotNull public List<TextRange> intersectWithAllEditableFragments( @NotNull PsiFile injectedPsi, @NotNull TextRange rangeToEdit) { Place shreds = InjectedLanguageUtil.getShreds(injectedPsi); if (shreds == null) return Collections.emptyList(); Object result = null; // optimization: TextRange or ArrayList int count = 0; int offset = 0; for (PsiLanguageInjectionHost.Shred shred : shreds) { TextRange encodedRange = TextRange.from( offset + shred.getPrefix().length(), shred.getRangeInsideHost().getLength()); TextRange intersection = encodedRange.intersection(rangeToEdit); if (intersection != null) { count++; if (count == 1) { result = intersection; } else if (count == 2) { TextRange range = (TextRange) result; if (range.isEmpty()) { result = intersection; count = 1; } else if (intersection.isEmpty()) { count = 1; } else { List<TextRange> list = new ArrayList<>(); list.add(range); list.add(intersection); result = list; } } else if (intersection.isEmpty()) { count--; } else { ((List<TextRange>) result).add(intersection); } } offset += shred.getPrefix().length() + shred.getRangeInsideHost().getLength() + shred.getSuffix().length(); } return count == 0 ? Collections.emptyList() : count == 1 ? Collections.singletonList((TextRange) result) : (List<TextRange>) result; } @Override public boolean isInjectedFragment(@NotNull final PsiFile file) { return file.getViewProvider() instanceof InjectedFileViewProvider; } @Override public PsiElement findInjectedElementAt(@NotNull PsiFile hostFile, int hostDocumentOffset) { return InjectedLanguageUtil.findInjectedElementNoCommit(hostFile, hostDocumentOffset); } @Override public void dropFileCaches(@NotNull PsiFile file) { InjectedLanguageUtil.clearCachedInjectedFragmentsForFile(file); } @Override public PsiFile getTopLevelFile(@NotNull PsiElement element) { return InjectedLanguageUtil.getTopLevelFile(element); } @NotNull @Override public List<DocumentWindow> getCachedInjectedDocuments(@NotNull PsiFile hostPsiFile) { return InjectedLanguageUtil.getCachedInjectedDocuments(hostPsiFile); } @Override public void enumerate( @NotNull PsiElement host, @NotNull PsiLanguageInjectionHost.InjectedPsiVisitor visitor) { InjectedLanguageUtil.enumerate(host, visitor); } @Override public void enumerateEx( @NotNull PsiElement host, @NotNull PsiFile containingFile, boolean probeUp, @NotNull PsiLanguageInjectionHost.InjectedPsiVisitor visitor) { InjectedLanguageUtil.enumerate(host, containingFile, probeUp, visitor); } @NotNull @Override public List<TextRange> getNonEditableFragments(@NotNull DocumentWindow window) { List<TextRange> result = ContainerUtil.newArrayList(); int offset = 0; for (PsiLanguageInjectionHost.Shred shred : ((DocumentWindowImpl) window).getShreds()) { Segment hostRange = shred.getHostRangeMarker(); if (hostRange == null) continue; offset = appendRange(result, offset, shred.getPrefix().length()); offset += hostRange.getEndOffset() - hostRange.getStartOffset(); offset = appendRange(result, offset, shred.getSuffix().length()); } return result; } private static int appendRange(List<TextRange> result, int start, int length) { if (length > 0) { int lastIndex = result.size() - 1; TextRange lastRange = lastIndex >= 0 ? result.get(lastIndex) : null; if (lastRange != null && lastRange.getEndOffset() == start) { result.set(lastIndex, lastRange.grown(length)); } else { result.add(TextRange.from(start, length)); } } return start + length; } private final Map<Class, MultiHostInjector[]> myInjectorsClone = new HashMap<>(); @TestOnly public static void pushInjectors(@NotNull Project project) { InjectedLanguageManagerImpl cachedManager = (InjectedLanguageManagerImpl) project.getUserData(INSTANCE_CACHE); if (cachedManager == null) return; try { assert cachedManager.myInjectorsClone.isEmpty() : cachedManager.myInjectorsClone; } finally { cachedManager.myInjectorsClone.clear(); } cachedManager.myInjectorsClone.putAll(cachedManager.getInjectorMap().getBackingMap()); } @TestOnly public static void checkInjectorsAreDisposed(@NotNull Project project) { InjectedLanguageManagerImpl cachedManager = (InjectedLanguageManagerImpl) project.getUserData(INSTANCE_CACHE); if (cachedManager == null) { return; } try { ClassMapCachingNulls<MultiHostInjector> cached = cachedManager.cachedInjectors; if (cached == null) return; for (Map.Entry<Class, MultiHostInjector[]> entry : cached.getBackingMap().entrySet()) { Class key = entry.getKey(); if (cachedManager.myInjectorsClone.isEmpty()) return; MultiHostInjector[] oldInjectors = cachedManager.myInjectorsClone.get(key); for (MultiHostInjector injector : entry.getValue()) { if (ArrayUtil.indexOf(oldInjectors, injector) == -1) { throw new AssertionError("Injector was not disposed: " + key + " -> " + injector); } } } } finally { cachedManager.myInjectorsClone.clear(); } } interface InjProcessor { boolean process(PsiElement element, MultiHostInjector injector); } void processInPlaceInjectorsFor(@NotNull PsiElement element, @NotNull InjProcessor processor) { MultiHostInjector[] infos = getInjectorMap().get(element.getClass()); if (infos != null) { final boolean dumb = myDumbService.isDumb(); for (MultiHostInjector injector : infos) { if (dumb && !DumbService.isDumbAware(injector)) { continue; } if (!processor.process(element, injector)) return; } } } @Override @Nullable public List<Pair<PsiElement, TextRange>> getInjectedPsiFiles(@NotNull final PsiElement host) { if (!(host instanceof PsiLanguageInjectionHost) || !((PsiLanguageInjectionHost) host).isValidHost()) { return null; } final PsiElement inTree = InjectedLanguageUtil.loadTree(host, host.getContainingFile()); final List<Pair<PsiElement, TextRange>> result = new SmartList<>(); InjectedLanguageUtil.enumerate( inTree, (injectedPsi, places) -> { for (PsiLanguageInjectionHost.Shred place : places) { if (place.getHost() == inTree) { result.add(new Pair<>(injectedPsi, place.getRangeInsideHost())); } } }); return result.isEmpty() ? null : result; } private static class PsiManagerRegisteredInjectorsAdapter implements MultiHostInjector { public static final PsiManagerRegisteredInjectorsAdapter INSTANCE = new PsiManagerRegisteredInjectorsAdapter(); @Override public void getLanguagesToInject( @NotNull final MultiHostRegistrar injectionPlacesRegistrar, @NotNull PsiElement context) { final PsiLanguageInjectionHost host = (PsiLanguageInjectionHost) context; InjectedLanguagePlaces placesRegistrar = (language, rangeInsideHost, prefix, suffix) -> injectionPlacesRegistrar .startInjecting(language) .addPlace(prefix, suffix, host, rangeInsideHost) .doneInjecting(); for (LanguageInjector injector : Extensions.getExtensions(LanguageInjector.EXTENSION_POINT_NAME)) { injector.getLanguagesToInject(host, placesRegistrar); } } @Override @NotNull public List<? extends Class<? extends PsiElement>> elementsToInjectIn() { return Collections.singletonList(PsiLanguageInjectionHost.class); } } }
@Override public void startRunInjectors(@NotNull final Document hostDocument, final boolean synchronously) { if (myProject.isDisposed()) return; if (!synchronously && ApplicationManager.getApplication().isWriteAccessAllowed()) return; // use cached to avoid recreate PSI in alien project final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(myProject); final PsiFile hostPsiFile = documentManager.getCachedPsiFile(hostDocument); if (hostPsiFile == null) return; final ConcurrentList<DocumentWindow> injected = InjectedLanguageUtil.getCachedInjectedDocuments(hostPsiFile); if (injected.isEmpty()) return; if (myProgress.isCanceled()) { myProgress = new DaemonProgressIndicator(); } final Set<DocumentWindow> newDocuments = Collections.synchronizedSet(new THashSet<>()); final Processor<DocumentWindow> commitProcessor = documentWindow -> { if (myProject.isDisposed()) return false; ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); if (indicator != null && indicator.isCanceled()) return false; if (documentManager.isUncommited(hostDocument) || !hostPsiFile.isValid()) return false; // will be committed later // it is here where the reparse happens and old file contents replaced InjectedLanguageUtil.enumerate( documentWindow, hostPsiFile, (injectedPsi, places) -> { DocumentWindow newDocument = (DocumentWindow) injectedPsi.getViewProvider().getDocument(); if (newDocument != null) { PsiDocumentManagerBase.checkConsistency(injectedPsi, newDocument); newDocuments.add(newDocument); } }); return true; }; final Runnable commitInjectionsRunnable = () -> { if (myProgress.isCanceled()) return; JobLauncher.getInstance() .invokeConcurrentlyUnderProgress( new ArrayList<>(injected), myProgress, true, commitProcessor); synchronized (ourInjectionPsiLock) { injected.clear(); injected.addAll(newDocuments); } }; if (synchronously) { if (Thread.holdsLock(PsiLock.LOCK)) { // hack for the case when docCommit was called from within PSI modification, e.g. in // formatter. // we can't spawn threads to do injections there, otherwise a deadlock is imminent ContainerUtil.process(new ArrayList<>(injected), commitProcessor); } else { commitInjectionsRunnable.run(); } } else { JobLauncher.getInstance() .submitToJobThread( () -> ApplicationManagerEx.getApplicationEx() .tryRunReadAction(commitInjectionsRunnable), null); } }