@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<Pair<PsiElement, TextRange>>(); InjectedLanguageUtil.enumerate( inTree, new PsiLanguageInjectionHost.InjectedPsiVisitor() { @Override public void visit( @NotNull PsiFile injectedPsi, @NotNull List<PsiLanguageInjectionHost.Shred> places) { for (PsiLanguageInjectionHost.Shred place : places) { if (place.getHost() == inTree) { result.add( new Pair<PsiElement, TextRange>(injectedPsi, place.getRangeInsideHost())); } } } }); return result.isEmpty() ? null : result; }
@Override public void enumerateEx( @NotNull PsiElement host, @NotNull PsiFile containingFile, boolean probeUp, @NotNull PsiLanguageInjectionHost.InjectedPsiVisitor visitor) { InjectedLanguageUtil.enumerate(host, containingFile, probeUp, visitor); }
@Override public void enumerate( @NotNull PsiElement host, @NotNull PsiLanguageInjectionHost.InjectedPsiVisitor visitor) { InjectedLanguageUtil.enumerate(host, visitor); }
@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); } }