@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); }
/** * 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<TextRange>(); 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.<TextRange>emptyList() : count == 1 ? Collections.singletonList((TextRange) result) : (List<TextRange>) result; }
@Override public void dropFileCaches(@NotNull PsiFile file) { InjectedLanguageUtil.clearCachedInjectedFragmentsForFile(file); }
@Override public PsiElement findInjectedElementAt(@NotNull PsiFile hostFile, int hostDocumentOffset) { return InjectedLanguageUtil.findInjectedElementNoCommit(hostFile, hostDocumentOffset); }
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 CopyOnWriteArrayList<DocumentWindow> injected = (CopyOnWriteArrayList<DocumentWindow>) InjectedLanguageUtil.getCachedInjectedDocuments(hostPsiFile); if (injected.isEmpty()) return; if (myProgress.isCanceled()) { myProgress = new DaemonProgressIndicator(); } final Processor<DocumentWindow> commitProcessor = new Processor<DocumentWindow>() { @Override public boolean process(DocumentWindow 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 Segment[] ranges = documentWindow.getHostRanges(); Segment rangeMarker = ranges.length > 0 ? ranges[0] : null; PsiElement element = rangeMarker == null ? null : hostPsiFile.findElementAt(rangeMarker.getStartOffset()); if (element == null) { synchronized (PsiLock.LOCK) { injected.remove(documentWindow); } return true; } final DocumentWindow[] stillInjectedDocument = {null}; // it is here where the reparse happens and old file contents replaced InjectedLanguageUtil.enumerate( element, hostPsiFile, true, new PsiLanguageInjectionHost.InjectedPsiVisitor() { @Override public void visit( @NotNull PsiFile injectedPsi, @NotNull List<PsiLanguageInjectionHost.Shred> places) { stillInjectedDocument[0] = (DocumentWindow) injectedPsi.getViewProvider().getDocument(); PsiDocumentManagerImpl.checkConsistency(injectedPsi, stillInjectedDocument[0]); } }); synchronized (PsiLock.LOCK) { if (stillInjectedDocument[0] == null) { injected.remove(documentWindow); } else if (stillInjectedDocument[0] != documentWindow) { injected.remove(documentWindow); injected.addIfAbsent(stillInjectedDocument[0]); } } return true; } }; final Runnable commitInjectionsRunnable = new Runnable() { @Override public void run() { if (myProgress.isCanceled()) return; JobLauncher.getInstance() .invokeConcurrentlyUnderProgress( new ArrayList<DocumentWindow>(injected), myProgress, !synchronously, commitProcessor); } }; 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<DocumentWindow>(injected), commitProcessor); } else { commitInjectionsRunnable.run(); } } else { JobLauncher.getInstance() .submitToJobThread( Job.DEFAULT_PRIORITY, new Runnable() { @Override public void run() { ApplicationManagerEx.getApplicationEx() .tryRunReadAction(commitInjectionsRunnable); } }); } }
@Override public void enumerate( @NotNull PsiElement host, @NotNull PsiLanguageInjectionHost.InjectedPsiVisitor visitor) { InjectedLanguageUtil.enumerate(host, visitor); }
@NotNull @Override public List<DocumentWindow> getCachedInjectedDocuments(@NotNull PsiFile hostPsiFile) { return InjectedLanguageUtil.getCachedInjectedDocuments(hostPsiFile); }
@Override public PsiFile getTopLevelFile(@NotNull PsiElement element) { return InjectedLanguageUtil.getTopLevelFile(element); }
@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); } }