cursive

MJ 2025-09-04T18:36:19.025199Z

I'm working on a plugin that would add an inlay hint (via InlayHintsCollector.collectFromElement) to some of the :import clauses in an ns form. collectFromElement basically receives just an individual PsiElement at a time. With help from https://clojurians.slack.com/archives/C0744GXCJ/p1638170040259600 I've been able to identify various Cursive elements, but trying to actually identify imports has eluded me. I've tried to look for a ClList whose getParent() is another ClList with first element :import (`ClKeyword`), and whose parent is another ClList with first element ns (`ClSymbol`). But not only is that very convoluted, but the results are maddeningly inconsistent. Is there a better way? If not, what am I doing wrong?

MJ 2025-09-05T21:30:50.844939Z

@cfleming can you offer any insight?

cfleming 2025-09-05T22:32:46.310229Z

Sorry, I've had a busy couple of days, and this is an answer that requires some explanation. The issue is that Cursive doesn't model many language features in the PSI. To be reflected in the PSI, the parser has to know all the possible things it could parse up-front, which is impossible with macros. So what's in the PSI in Cursive is more like what you'd get back from the reader rather than an AST - collections, symbols, strings, lists etc. Then there's a higher-level Cursive-specific layer on top of that which does the language analysis dynamically over that layer. So Cursive is quite different to other IntelliJ plugins. Unfortunately that layer is much more difficult to plug into, and on top of that I'm gradually rewriting it at the moment since it was one of the first things I wrote way back in the day, and the current design makes many many things harder than they should be. So I think that right now, the way you're doing things is probably your best option, but you're right that it sucks. You could look into the IntelliJ Pattern stuff (https://plugins.jetbrains.com/docs/intellij/element-patterns.html), but honestly I just end up doing it by hand when I can't use my parsing infrastructure for whatever reason.

MJ 2025-09-05T22:38:41.334229Z

thank you for the explanation! I'd be happy enough to go with my current approach, but the problem is it doesn't seem to work consistently. I think sometimes the nodes are wrapped in LeafPsiElement, but not always? It would help if there were something that could easily print a whole Psi subtree for debugging

MJ 2025-09-05T22:43:55.293299Z

this is what I think should work, but doesn't:

if (element instanceof ClList singleImport
        && singleImport.getParent() instanceof ClList allImports
        && allImports.getFirstChild() instanceof ClKeyword importKw
        && "import".equals(importKw.getQualifiedName())
        && allImports.getParent() instanceof ClList nsForm
        && nsForm.getFirstChild() instanceof ClKeyword nsKw
        && "ns".equals(nsKw.getQualifiedName())) {

MJ 2025-09-05T22:46:08.046529Z

oh derp, ns is a symbol not a keyword

cfleming 2025-09-05T22:52:40.319899Z

Right. There are also a bunch of other forms that you might see, depending on how general you want this to be: (:import java.io.File), [:import ( File)] etc.

cfleming 2025-09-05T22:52:56.380079Z

What are you annotating, BTW?

MJ 2025-09-05T23:08:12.836659Z

ohhh, ClKeyword.getQualifiedName() returns ":import" rather than "import"

MJ 2025-09-05T23:09:36.249999Z

something similar to deprecation warnings, but for imports that aren't actually deprecated. We just want to slowly remove our uses of them from a large codebase

cfleming 2025-09-05T23:12:32.355059Z

A simpler approach now that Cursive supports clj-kondo might be a custom kondo linter?

cfleming 2025-09-05T23:13:03.593029Z

That will be marked in the editor, not in an inlay but as an inspection annotation.

MJ 2025-09-05T23:14:04.952929Z

that's a good option to keep in mind, but it's a bit more complicated than what I've described. Sorry I can't be more specific

👍 1
MJ 2025-09-05T23:40:22.658629Z

oh my god, getFirstChild() on a ClList returns something different from getChildren()[0]. The former returns the opening paren, but the latter returns the "real" first child

cfleming 2025-09-06T00:35:24.794949Z

That's.... possible? Seems weird though.

cfleming 2025-09-06T01:28:54.679019Z

I just checked, that's all IntelliJ infrastructure at that level, not Cursive. I'd be surprised by that inconsistency, though.

MJ 2025-09-06T01:37:22.566499Z

I was also quite surprised, as it caused me several hours of headaches 😕

cfleming 2025-09-06T01:44:01.748049Z

Are you managing to debug your plugin ok? Sometimes the setup can be tricky.

cfleming 2025-09-06T01:45:26.755849Z

I actually build and manage deps with deps.edn and build.clj, just so I don't have to deal with Gradle. No-one has time for that.

MJ 2025-09-06T01:47:32.737819Z

eh, I've managed but it hasn't been pleasant. I have it working now anyway, at least this part of it