PsiFile and PsiFile.findElementAt() only providing useful info for a few languages.

Answered

I'm trying to create an Intellij plugin that does the same thing as a VSCode extension I recently created, Ligatures Limited, which is designed prevent font coding ligatures from fonts like Fira Code from being rendered out-of-context, so that they work for things like operators and punctuation, but don't show up in comments and strings.

I've tried to follow the code in this example: https://github.com/JetBrains/intellij-sdk-docs/tree/master/code_samples/psi_demo

...but when I create code that looks like this

public void actionPerformed(AnActionEvent anActionEvent) {
Editor editor = anActionEvent.getData(CommonDataKeys.EDITOR);
PsiFile psiFile = anActionEvent.getData(CommonDataKeys.PSI_FILE);
if (editor == null || psiFile == null) return;
int offset = editor.getCaretModel().getOffset();

final StringBuilder infoBuilder = new StringBuilder();
PsiElement element = psiFile.findElementAt(offset);

...I find that only JSON files, Java, XML, etc. (languages supported by the Community Edition) are being recognized by language, and being tokenized (at least as far as my extension is allowed to see via PsiFile). Syntax coloring and other language-specific support are clearly functioning at some level, but not in a way my extension seems to be able to access.

psiFile.language is JSON for JSON, but it's only TEXT for JavaScript, and textmate for TypeScript.

I can see all of the different token types for JSON (JsonObject, JsonStringLiteral, JsonProperty, etc), but JavaScript files come back as one big TEXT token with all of the text of the JavaScript file inside of it, and TypeScript only yields PsiElement(empty token).

Here's my current plugin.xml file:

<idea-plugin>
  <id>com.shetline.ligatures-limited</id>
  <name>Ligatures Limited</name>
  <vendor email="kerry@shetline.com" url="http://github.com/kshetline/ligatures-limited-idea">Kerry Shetline</vendor>

  <description><![CDATA[
Code ligatures <i>only where you want them</i>, not where you don't
  ]]></description>

  <depends>com.intellij.modules.lang</depends>
  <depends>com.intellij.modules.platform</depends>
  <depends optional="true">com.intellij.modules.ultimate</depends>

  <applicationListeners>
    <listener class="com.shetline.lligatures.LigaturesLimited" topic="com.intellij.ide.AppLifecycleListener"/>
  </applicationListeners>

  <extensions defaultExtensionNs="com.intellij">
    <applicationService serviceImplementation="com.shetline.lligatures.LigaturesLimited"/>
    <highlightVisitor implementation="com.shetline.lligatures.LigaturesLimited"/>
    <highlightingPassFactory implementation="com.shetline.lligatures.LigaturesLimited"/>
  </extensions>

  <actions>
  </actions>
</idea-plugin>


I'm a bit worried that I might need to explicitly add dependencies for every language I want to parse. But then again, adding <depends optional="true">JavaScript</depends> didn't help me getting any further parsing JavaScript, so maybe that wouldn't help anyway.

Is it just that PsiFile only fully supports non-Community Edition languages, or there are extra hoops to jump through to get other languages supported?

0
6 comments

JavaScript and a number of other languages are not supported in Community Edition, so there is no PSI for it.

 

https://www.jetbrains.com/idea/features/#choose-your-edition

0

Even when you're using the Ultimate Edition?

I've been looking at the source code for Rainbow Brackets, and I put this test code into DefaultRainbowVisitor.kt at line 18:


override fun visit(element: PsiElement) {
var text = element.text.replace(Regex("""\r\n|\r|\n"""), "↵ ")
if (text.length > 40)
text = text.substring(0, 40);
println("${element.language}, $text")
val type = (element as? LeafPsiElement)?.elementType ?: return

...and I'm getting this output:

Language: TypeScript, updateSvgFlowItems()
Language: JavaScript, ;
Language: TypeScript, updateSvgFlowItems();
Language: TypeScript, ↵
Language: JavaScript, }
Language: TypeScript, {↵ this.outdoorMeter[0].setAttribu
Language: TypeScript, if (newFlowSpec !== flowSpec) {↵ t
Language: TypeScript, ↵
Language: JavaScript, }
Language: TypeScript, {↵ const flowSpec = this.outdoorMete
Language: TypeScript, private configureDisplay(indoorOption: s
Language: TypeScript, ↵
Language: JavaScript, }
Language: TypeScript, export class Sensors {↵ private readon

...which tells me that somehow Rainbow Brackets CAN get this info about non-Community languages from a PsiElement instance. What I can't figure out is how Rainbow Brackets manages this, and why I can't so far. I've disabled and commented out a good portion of the Rainbow Brackets code, and still can't find how it pulls this info out of PsiElement when I can't.

0

Sorry, I can't follow "Even when you're using the Ultimate Edition"?

Please install PsiViewer plugin https://plugins.jetbrains.com/plugin/227-psiviewer and check whether PSI is correctly provided for the chosen languages in your target IDE.

0

Yes, PsiViewer is working for TypeScript and JavaScript.

The reason I said "Even when you're using the Ultimate Edition" is that I wanted to make sure you didn't think I was using the Community Edition myself, or that I expected users of the Community Edition to magically get information about languages not supported by their version of IDEA. That is not the case.

So it does seem possible to extract specific info about languages like TypeScript from PsiFile and PsiElement objects. Why would my code be in a state where it's not getting that information, acting more like those languages are unknown to my Ultimate Edition copy of IDEA?

0

Your comment inspired me to check something... For some reason, when I launch my plugin from within IDEA, it's launching the Community Edition. That was not a deliberate choice, just what apparently happened when I used the built-in wizard for creating a new plugin project.

When I launch Rainbow Brackets (from source code, not using the extension itself), it's running with the Ultimate Edition.

It took me a while to figure out how the version launched for testing is determined, but it seems to come down to the intellij section of build.gradle (or, in my project's case, build.gradle.kts).

I hard-coded this in my build.gradle.kts:

intellij {
version = "IU-2019.3.3"
}

...and that finally got PsiElement working as I wanted it to work.

I'd like to use the more flexible syntax I see used by PsiViewer (version "${platformVersion}${platformBranch}${platformBuild}") or in Rainbow Brackets (version ideaVersion) in their build.gradle files, but even when adding an `=` sign to make the syntax more friendly for a .kts file, those variables like ideaVersion and platformVersion don't seem to be defined.

How would I do a similar thing in a build.gradle.kts file?

0

Ah ok, now that makes sense.

 

https://github.com/minecraft-dev/MinecraftDev/blob/dev/build.gradle.kts#L122 + https://github.com/minecraft-dev/MinecraftDev/blob/dev/gradle.properties seems like what you want.

Note the homepage of the gradle-intellij-plugin has a number of reference plugins as inspiration for solving different setups https://github.com/JetBrains/gradle-intellij-plugin/

0

Please sign in to leave a comment.