I am looking into language injection support for the Nix plugin (current state). An important part of the complexity is that the Nix language supports string interpolations and removing common indentation.
rec {
recipient = "World";
message = ''
Hello ${recipient}!
'';
}
The plugin is currently targeting IntelliJ 2025.2.6.1. In general, I have to say that this topic turns out to be quite complex, partially due to seemingly overlapping responsibilities of different SPIs, without clear documentation about the relations between them. Anyway, I think I have figured out the basics and settled on the LanguageInjectionPerformer extension point (my current implementation). I am currently wondering about two specific questions regarding best practices related to interpolations:
-
Which PSI element should implement the
PsiLanguageInjectionHostinterface? The element representing the string (NixString), or the element representing the text components between interpolations (NixStringText)? Semantically, I would assume it makes more sense to implement it on the level of the entire string. However, the API becomes rather cumbersome when I try to do that. Most notably,MultiHostRegistrar.addPlacedoesn’t accept any element not implementingPsiLanguageInjectionHost. My only way to preserve the mapping from the “place” to its text component is by encoding it into theTextRange, which hasn’t worked reliable so far. I therefore tried to implement thePsiLanguageInjectionHoston the individual text component, but that makes the action to open the fragment editor (QuickEditAction) unavailable at various places inside the string (i.e. next to the quotation marks, and just before string interpolations). There seems to be no good solution. The documentation seems to avoid this topic by only talking about string concatenations, ignoring the existence of string interpolations in many languages. -
Which placeholder text should I inject for interpolations? When there is a string interpolation between two text components, I inject a placeholder by providing it either as suffix to the previous “shred/place”, or as prefix to the next “shred/place” (source code). This seems to be handled nicely by IntelliJ. The value of the placeholder is never shown in the UI. But this leaves a question: what should be the value of the placeholder? Ideally, the placeholder should be a valid expression or identifier in the guest language, to avoid breaking its parser. However, I am not in control of the guest language. Otherwise, the text of the placeholder is irrelevant to me. Is there some convention about the value of the placeholder, or some why to query a good placeholder from the guest language?