Load webSymbols.webTypes JSON file from URL

Answered

Hi all,

I am creating a plugin that needs to load a web-types JSON file from a URL and use the file to assist with type hinting and autocompletion.

From looking at the documentation (here and here), I need to use

<webSymbols.webTypes source="[SOURCE_HERE]"/>

which works fine, but I seem to be getting stuck, considering the following use cases;

  • The framework which I am trying to support does not follow versioning, so it can change, meaning the plugin will need to re-fetch the web types file once in a while to keep up to date, since the framework releases periodically
  • The plugin has an environment switch to toggle between a production version and a test version of the web-types file, so the plugin will need to switch environments & re-fetch the web types file

That means that I will need to support fetching the web types dynamically, making me believe that I cannot do this in the XML file, because that is static and won't change.

I might be mistaken, but I think it is not possible to write to the resources folder of my plugin. So my question is as follows.

How can I programmatically supply the web-types symbol parser with a web-types JSON file, and make it reload the file afterwards?  

 

0
5 comments
Official comment

Hi!

If you need a more advanced use case, you need to create a  WebSymbolsQueryConfigurator, which will provide the loaded Web Types depending on the context. You would need to use some of the internal APIs, because I haven't yet exposed these to the public, but it is definitely doable. If you succeed we can consider making these APIs available of course.

So, to load WebTypes from an InputStream you can use internal com.intellij.webSymbols.webTypes#InputStream.readWebTypes() function. Next you need to create a scope, which will be returned by the query configurator. For that extend internal WebTypesScopeBase and call addWebTypes/removeWebTypes to update the set of available WebTypes within the scope.

WebSymbolsQueryConfigurator can return scopes depending on the current location. Configurator is called very often, so the scope calculations should be as optimized as possible. Scopes should be cached, i.e. they should not be rebuilt on every call.

Please note that there were some API changes between 2023.2 and 2023.3, as the framework is still under heavy development and is considered experimental for now.

Let me know if you have any more questions!

 

 

Hi Piotr,

Thanks for your response. Trying to create the plugin on Friday mornings, so excuse the slow progress.

I've been at work trying to grasp my head around the way to inject the web types to the scope.
As far as I understand, the WebSymbolsQueryConfigurator.getScope() is called for each line of the open file in the editor. In the getScope of an implementation of WebSymbolsQueryConfigurator, I need to add the webtypes to the scope, but how does one accomplish that? I've also extended the WebTypesScopeBase() and exposed the addWebTypes, but besides a WebTypes object it also requires a WebTypesJsonOrigin for context. I can't figure out where I get that context from..

First time creating an IntelliJ plugin & working with Kotlin so please excuse me if I overlooked something obvious.

class AuroraLanguageService : WebSymbolsQueryConfigurator {
    private var webTypes: String = ""
    private var parsedTypes: WebTypes = WebTypes()
    private val auroraWebTypesScopeBase = AuroraWebTypesScope()
    override fun getScope(
        project: Project,
        location: PsiElement?,
        context: WebSymbolsContext,
        allowResolve: Boolean
    ): List<WebSymbolsScope> {

        if (webTypes === "") {
            webTypes = project.service<MyProjectService>().webTypes
            parsedTypes = webTypes.byteInputStream().readWebTypes()
        }

        val scope = super.getScope(project, location, context, allowResolve)
        thisLogger().warn("Context: $context")
        thisLogger().warn("Language: ${location?.language}")
        thisLogger().warn("Project: $project")

        if (location?.language === Language.findLanguageByID("HTML")) {
            thisLogger().warn("Is HTML!")
            // Where do I get the WebTypesJsonOrigin from?
            auroraWebTypesScopeBase.addWebTypes(parsedTypes, context)

        }
        return scope
    }
}

class AuroraWebTypesScope : WebTypesScopeBase() {
    override fun createPointer(): Pointer<out WebTypesScopeBase> {
        // Cannot find a way to create a pointer
        throw NotImplementedError("Not yet implemented")
    }

    public override fun addWebTypes(webTypes: WebTypes, context: WebTypesJsonOrigin) {
        super.addWebTypes(webTypes, context)
    }

}
0

Hi!

The code looks more or less OK. I think it would be better to enclose all the stuff related to reading the WebTypes JSON into the AuroraWebTypesScope, it can be a Project service. As far as the WebTypesJsonOrigin is concerned, You need to implement the interface yourself. The interface allows to provide some basic things related to the symbols, like resolution to the source symbol, description rendering or context matching, as well as some info on the library, version or default icon. All these things are kind of optional, so you can gradually add the code.

The most impactful part is the framework and matchContext/WebSymbolsContext, failing to match the context disables symbols from a particular origin. The framework is special kind of WebSymbolsContext named “framework”. There can be only one context of a particular kind, which means that there can be only one framework in the context. So far this type of context has been used to differentiate between web frameworks, like angular, vue, svelte, astro, etc. I think that you can skip this part for now, and later on introduce some context limitations, so that your feature doesn't show in every file in unrelated projects.

I am sorry, that it's so complicated, but again, this part of framework is still under @Internal API status, but I hope that your use case can help us better understand needs for such kinds of APIs. It would be great if afterwards, you could share the whole code and we could do some adjustments in the APIs if needed and improve documentation.

0

Hi Piotr,

I'm back with another attempt at trying to get this working. I'm struggling quite a lot because of the unavailability of proper documentation. Understandable of course since the API is really new, but because of that please excuse me if I am missing something obvious. I've tried coding against decompiled function signatures, so without knowing what stuff does, making it fairly difficult.

I've tried to implement my own interface on top of the WebTypesJsonOrigin like below.

class AuroraWebTypesJsonOrigin : WebTypesJsonOrigin {
    override fun matchContext(context: WebSymbolsContext): Boolean {
        thisLogger().warn("Match context called")
        return true
    }

    override fun renderDescription(description: String): String {
        return description
    }

    override fun resolveSourceLocation(source: SourceBase): WebTypesSymbol.Location? {
        thisLogger().warn(source.toString())
        TODO("Not yet implemented")
    }

    override fun resolveSourceSymbol(source: SourceBase, cacheHolder: UserDataHolderEx): PsiElement? {
        TODO("Not yet implemented")
    }
}

The rest of my implementation hasn't changed much, besides using auroraWebTypesScopeBase.addWebTypes(parsedTypes, AuroraWebTypesJsonOrigin()). The matchContext method of the AuroraWebTypesJsonOrigin is never called, leading me to believe that I should do that somewhere. I think I am really close to getting it working (at least somewhat). Do you mind pointing me in the right direction? Or maybe modifying the code a bit to get it in a working state?

Thanks for bearing with me.

0

Basvanrooten I think it might be easier if you share a github repo with me (piotr.tomiak at jetbrains.com) :) I might be able to experiment with this locally and see what's wrong.

0

Please sign in to leave a comment.