Auto-popup completion has stopped working consistently in a MultiplePsiFilesPerDocumentFileViewProvider
Starting with one of the recent builds of IntelliJ IDEA (I haven't isolated exactly which), auto-popup completion has stopped working in my custom language plugin's MultiplePsiFilesPerDocumentFileViewProvider which extends the base HTML file type. After bit of debugging, I've found that this is happening in the following stack:
at com.intellij.codeInsight.completion.ActionTracker.hasAnythingHappened(ActionTracker.java:79)
at com.intellij.codeInsight.completion.CompletionPhase$CommittingDocuments.checkExpired(CompletionPhase.java:82)
at com.intellij.codeInsight.AutoPopupController.lambda$scheduleAutoPopup$0(AutoPopupController.java:141)
in particular in the following:
return myActionsHappened || DumbService.getInstance(myProject).isDumb() ||
myEditor.isDisposed() ||
(myEditor instanceof EditorWindow && !((EditorWindow)myEditor).isValid());
myEditor.isDisposed() sometimes returns true.
Any idea what might have changed to cause this and why sometimes-not-always the editor will seem to be disposed and short-circuit this? It seems to depend on how deeply nested the element is in the HTML document, but that's more an observation than a proven fact.
Thanks in advance for any thoughts!
Scott
请先登录再写评论。
I'd suggest to step inside isDisposed in debugger and see why exactly it's so. Probably some field contains "true". Then I'd suggest to put a breakpoint where this field is modified to true (after completion starts), and look at the stack trace. You can also post it here, so we can help with analysis.
Thanks, Peter. Here's how the editor is being disposed immediately upon typing a letter that would trigger a the completion popup:
Any thoughts on why it's happening now but hasn't in the (relatively recent) past? If there's something I need to do with my composite document type, no problem, but I'm just perplexed because it was all working and now it's not.
Thanks much!
Scott
There were some performance optimizations with respect to injected PSI reparsing, they might have such unfortunate side effect for you. So apparently InjectionRegistrarImpl.reparse returns null. It might be that the injector says that now there's no injected PSI at that location. But in general, I'd suggest to debug inside that method to see where exactly it returned null and why.
Thanks again, Peter. Just going to provide a bit of a coarse-grained play-by-play, but InjectionRegistrarImpl.reparse() is returning null because the following is returning null:
Going to continue drilling down until I either understand the root cause and any potential changes I need to make or I hit a wall in which case hopefully you guys can help out.
More to come shortly...
Okay. I can see why the null is being returned but don't have enough context to know why it's happening now and wasn't before. First off, here's the text before I try to start auto-popup completion:
<apex:inputField value="{!opp.StageName}"/><apex:outputText value="{!<caret-is-here>}"/>
and the caret is inside of the second embedded expression {!...}. When I type a character that should produce one or more valid completions (in this case "o"), it ends up here:
and the following line ends up being the origin of the null that's returned:
In this case the text range of the anchor element corresponds to the following which is a PSI element of type HTML_TEXT in the composite doc:
and the provided startOffset and endOffset text range corresponds to the following which is a PSI element of type FORMULA in the composite doc:
{!o}I hope that helps drive some insights into this behavioral change and what either I can do to remedy it now or, if necessary, what you guys might need to do to return the original behavior.
Regards,
Scott
Which of these two elements (HTML_TEXT or FORMULA) is really an injection host? I'd say that the second one, judging by its look (the first one is too unusual), but your PSI might differ from my expectations.
The Identikit seen in your stack trace is created in the previous line in findNewInjectionHost, it's supposed to be based on the injection host in the previous version of the tree. Is this oldHost is incorrect, can you trace how it appears in this trace at all?
Sorry for the delayed response. It's been a busy week! So in my MultiplePsiFilesPerDocumentFileViewProvider, the template data language is HTML and the base language is my expression language that is seen in the original examples as {!...}. The root grammar rule for my expression language is basically:
where an expression is defined as {!...} and HTML_TEXT is defined as everything around that.
If you had a simple document like:
the PSI tree for the expression language document would look like (simplified slightly):
and the PSI tree for the HTML document would look like:
I don't know if that helps at all. Please let me know if there's other info that would be helpful here.
That helps, thanks, but not completely. Which of these elements is the injection host?
Does your plugin work with Community Edition? I have a suspicion about a possible cause for your issue, but I can't create a scenario when that would fail. I could give you a patch against intellij-community to try, or maybe you know a scenario when autopopup doesn't work in a similar way with bundled languages?
Peter, I apologize but I'm not sure I follow. If by "injection" you mean language injection such as how one language can be injected into string literals of another language, I'm not using it here. This is just a MultiplePsiFilesPerDocumentFileViewProvider. Perhaps that automatically implies an injection host?
Yes, the plugin works with Community Edition as well as Ultimate Edition and WebStorm. I believe the same behavior occurs in all three but am not 100% sure about that. I can verify one way or the other if necessary.
I'm also happy to do a screenshare or similar where I can show you more details on how this is implemented in case that helps to accelerate the discussion. Just let me know if you'd like to do that.
The stack trace you posted (with InjectionRegistrarImpl) implies there's some language injected (yes, like with string literals) around the area you invoke completion in. You can find out more about it by clicking Alt+Enter around that location, and you'll probably be offered related intentions: uninject language, edit injected fragment, etc. During completion that injected fragment disappears, that leads to autopopup not working.
So, I'd start with investigating why you have injection that you don't know about :)
Doh! I'm just being dumb. Of course I have a language injector. The injection host is XML/HTML and I'm injecting my language into XmlAttributeValues. So sorry!
So you're injecting into an attribute value which already contains text in another (template) language? This looks like too many languages in one place, probably your injector should skip those (or they shouldn't be template language parts).
Anyway, here's a patch that you can try if you wish to build intellij-community from source :)
Index: platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/ShredImpl.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/ShredImpl.java (revision 5c24fccb8f80eb62d567bd50e95a9ee31c678fd3)
+++ platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/ShredImpl.java (date 1523544930000)
@@ -67,12 +67,13 @@
@NotNull PsiLanguageInjectionHost newHost) {
SmartPsiFileRange rangeMarker = relevantRangeInHost;
Segment oldRangeInHostElementPSI = calcRangeInsideHostElement(false);
- SmartPointerManager pointerManager = SmartPointerManager.getInstance(rangeMarker.getProject());
- SmartPsiElementPointer<PsiLanguageInjectionHost> newHostPointer = pointerManager.createSmartPsiElementPointer(newHost);
+ SmartPointerManagerImpl pointerManager = (SmartPointerManagerImpl)SmartPointerManager.getInstance(rangeMarker.getProject());
+ SmartPsiElementPointer<PsiLanguageInjectionHost> newHostPointer = pointerManager.createSmartPsiElementPointer(newHost, newHost.getContainingFile(), true);
if (!rangeInHostElementPSI.equals(TextRange.create(oldRangeInHostElementPSI))) {
Segment hostElementRange = newHostPointer.getRange();
- rangeMarker = ((SmartPointerManagerImpl)pointerManager).createSmartPsiFileRangePointer(rangeMarker.getContainingFile(), rangeInHostElementPSI.shiftRight(hostElementRange.getStartOffset()), true);
+ rangeMarker = pointerManager
+ .createSmartPsiFileRangePointer(rangeMarker.getContainingFile(), rangeInHostElementPSI.shiftRight(hostElementRange.getStartOffset()), true);
}
return new ShredImpl(rangeMarker, newHostPointer, prefix, suffix, rangeInDecodedPSI, usePsiRange, isOneLine);
}