private void getInjectedPsiFiles( @NotNull final List<PsiElement> elements1, @NotNull final List<PsiElement> elements2, @NotNull final ProgressIndicator progress, @NotNull final Set<PsiFile> outInjected) { List<DocumentWindow> injected = InjectedLanguageUtil.getCachedInjectedDocuments(myFile); Collection<PsiElement> hosts = new THashSet<PsiElement>(elements1.size() + elements2.size() + injected.size()); // rehighlight all injected PSI regardless the range, // since change in one place can lead to invalidation of injected PSI in (completely) other // place. for (DocumentWindow documentRange : injected) { progress.checkCanceled(); if (!documentRange.isValid()) continue; PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(documentRange); if (file == null) continue; PsiElement context = InjectedLanguageManager.getInstance(file.getProject()).getInjectionHost(file); if (context != null && context.isValid() && !file.getProject().isDisposed() && (myUpdateAll || new ProperTextRange(myStartOffset, myEndOffset) .intersects(context.getTextRange()))) { hosts.add(context); } } hosts.addAll(elements1); hosts.addAll(elements2); final PsiLanguageInjectionHost.InjectedPsiVisitor visitor = new PsiLanguageInjectionHost.InjectedPsiVisitor() { @Override public void visit( @NotNull PsiFile injectedPsi, @NotNull List<PsiLanguageInjectionHost.Shred> places) { synchronized (outInjected) { outInjected.add(injectedPsi); } } }; if (!JobUtil.invokeConcurrentlyUnderProgress( new ArrayList<PsiElement>(hosts), progress, false, new Processor<PsiElement>() { @Override public boolean process(PsiElement element) { progress.checkCanceled(); InjectedLanguageUtil.enumerate(element, myFile, false, visitor); return true; } })) throw new ProcessCanceledException(); }
@NotNull private Set<PsiFile> getInjectedPsiFiles( @NotNull final List<PsiElement> elements1, @NotNull final List<PsiElement> elements2, @NotNull final ProgressIndicator progress) { ApplicationManager.getApplication().assertReadAccessAllowed(); final Set<PsiFile> outInjected = new THashSet<PsiFile>(); List<DocumentWindow> injected = InjectedLanguageUtil.getCachedInjectedDocuments(myFile); final Collection<PsiElement> hosts = new THashSet<PsiElement>(elements1.size() + elements2.size() + injected.size()); // rehighlight all injected PSI regardless the range, // since change in one place can lead to invalidation of injected PSI in (completely) other // place. for (DocumentWindow documentRange : injected) { progress.checkCanceled(); if (!documentRange.isValid()) continue; PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(documentRange); if (file == null) continue; PsiElement context = InjectedLanguageManager.getInstance(file.getProject()).getInjectionHost(file); if (context != null && context.isValid() && !file.getProject().isDisposed() && (myUpdateAll || myRestrictRange.intersects(context.getTextRange()))) { hosts.add(context); } } InjectedLanguageManagerImpl injectedLanguageManager = InjectedLanguageManagerImpl.getInstanceImpl(myProject); Processor<PsiElement> collectInjectableProcessor = new CommonProcessors.CollectProcessor<PsiElement>(hosts); injectedLanguageManager.processInjectableElements(elements1, collectInjectableProcessor); injectedLanguageManager.processInjectableElements(elements2, collectInjectableProcessor); final PsiLanguageInjectionHost.InjectedPsiVisitor visitor = new PsiLanguageInjectionHost.InjectedPsiVisitor() { @Override public void visit( @NotNull PsiFile injectedPsi, @NotNull List<PsiLanguageInjectionHost.Shred> places) { synchronized (outInjected) { outInjected.add(injectedPsi); } } }; if (!JobLauncher.getInstance() .invokeConcurrentlyUnderProgress( new ArrayList<PsiElement>(hosts), progress, true, new Processor<PsiElement>() { @Override public boolean process(PsiElement element) { ApplicationManager.getApplication().assertReadAccessAllowed(); progress.checkCanceled(); InjectedLanguageUtil.enumerate(element, myFile, false, visitor); return true; } })) { throw new ProcessCanceledException(); } synchronized (outInjected) { return outInjected; } }
private static TextRange preprocess(@NotNull final ASTNode node, @NotNull TextRange range) { TextRange result = range; PsiElement psi = node.getPsi(); if (!psi.isValid()) { for (PreFormatProcessor processor : Extensions.getExtensions(PreFormatProcessor.EP_NAME)) { result = processor.process(node, result); } return result; } PsiFile file = psi.getContainingFile(); // We use a set here because we encountered a situation when more than one PSI leaf points to // the same injected fragment // (at least for sql injected into sql). final LinkedHashSet<TextRange> injectedFileRangesSet = ContainerUtilRt.newLinkedHashSet(); if (!psi.getProject().isDefault()) { List<DocumentWindow> injectedDocuments = InjectedLanguageUtil.getCachedInjectedDocuments(file); if (!injectedDocuments.isEmpty()) { for (DocumentWindow injectedDocument : injectedDocuments) { injectedFileRangesSet.add( TextRange.from(injectedDocument.injectedToHost(0), injectedDocument.getTextLength())); } } else { Collection<PsiLanguageInjectionHost> injectionHosts = collectInjectionHosts(file, range); PsiLanguageInjectionHost.InjectedPsiVisitor visitor = new PsiLanguageInjectionHost.InjectedPsiVisitor() { @Override public void visit( @NotNull PsiFile injectedPsi, @NotNull List<PsiLanguageInjectionHost.Shred> places) { for (PsiLanguageInjectionHost.Shred place : places) { Segment rangeMarker = place.getHostRangeMarker(); injectedFileRangesSet.add( TextRange.create(rangeMarker.getStartOffset(), rangeMarker.getEndOffset())); } } }; for (PsiLanguageInjectionHost host : injectionHosts) { InjectedLanguageUtil.enumerate(host, visitor); } } } if (!injectedFileRangesSet.isEmpty()) { List<TextRange> ranges = ContainerUtilRt.newArrayList(injectedFileRangesSet); Collections.reverse(ranges); for (TextRange injectedFileRange : ranges) { int startHostOffset = injectedFileRange.getStartOffset(); int endHostOffset = injectedFileRange.getEndOffset(); if (startHostOffset >= range.getStartOffset() && endHostOffset <= range.getEndOffset()) { PsiFile injected = InjectedLanguageUtil.findInjectedPsiNoCommit(file, startHostOffset); if (injected != null) { int startInjectedOffset = range.getStartOffset() > startHostOffset ? startHostOffset - range.getStartOffset() : 0; int endInjectedOffset = injected.getTextLength(); if (range.getEndOffset() < endHostOffset) { endInjectedOffset -= endHostOffset - range.getEndOffset(); } final TextRange initialInjectedRange = TextRange.create(startInjectedOffset, endInjectedOffset); TextRange injectedRange = initialInjectedRange; for (PreFormatProcessor processor : Extensions.getExtensions(PreFormatProcessor.EP_NAME)) { injectedRange = processor.process(injected.getNode(), injectedRange); } // Allow only range expansion (not reduction) for injected context. if ((initialInjectedRange.getStartOffset() > injectedRange.getStartOffset() && initialInjectedRange.getStartOffset() > 0) || (initialInjectedRange.getEndOffset() < injectedRange.getEndOffset() && initialInjectedRange.getEndOffset() < injected.getTextLength())) { range = TextRange.create( range.getStartOffset() + injectedRange.getStartOffset() - initialInjectedRange.getStartOffset(), range.getEndOffset() + initialInjectedRange.getEndOffset() - injectedRange.getEndOffset()); } } } } } for (PreFormatProcessor processor : Extensions.getExtensions(PreFormatProcessor.EP_NAME)) { result = processor.process(node, result); } return result; }
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); } }); } }
@NotNull @Override public List<DocumentWindow> getCachedInjectedDocuments(@NotNull PsiFile hostPsiFile) { return InjectedLanguageUtil.getCachedInjectedDocuments(hostPsiFile); }
@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); } }