Make auto-completion work in Kotlin scripts with context


#1

I work on a Kotlin project that also uses Kotlin scripts (embedded as resources). The Kotlin script files are evaluated with some context, i.e. with variables bound. As a result, the plain script does not compile / run by itself (Ctrl+Shift+F10 on Windows), and either does auto-completion work.

Is there a way to tell the IDE about the context in which the script is run, i.e. which variables will be bound at runtime, so that the IDE will be able to perform auto-completion when working on the script inside the IDE?


#2

(Sorry for the late answer, seems we overlooked your question here).

I assume that you’re setting the context by using your own script definition, either by having the variables passed in the base class constructor, or passing it in the configuration as provided properties. If it is done differently, please explain how.

To make autocompletion work, you need to supply Intellij with your script definition. The easiest way is to put the jar with the script definition and discovery file to the module’s classpath, and put the script into sources. Or you can write an intellij plugin that supplies the jars to idea - in this case you can have your script in any location. And as a substitution (or workaround) for the latter, you can specify the FQN of your definition class along with the classpath needed to load it in the kotlin compiler settings -> “Kotlin scripting” in Intellij.
We are working on the other ways to configure scripting in the IDE too.


#3

I’m setting context (and I mean context in the literal sense, not javax.script.ScriptContext) by two means:

  • binding variables via engine.put()
  • adding preface / postface literal text to the main script to execute

As the project in question is Open Source, let me give you some pointers. This is an example for an embedded script that, if open in the IDE, lights up red like a Christmas tree due to undefined variables:

And this is the class that defines the context in which the script is run:

My question is: How can I make syntax checking for no_gpl_declared.kts work in the IDE, and ideally also run the script from the IDE?


#4

What you would need to do is to generate/create mock classes that provide the symbols you expect. You can then mark this mock project/jar as a “provided” or “compileOnly” dependency of the script so that it can work. It may require you to put the script in a proper module though (not in resource).


#5

It is quite unlikely that we’ll be able to support a variant with script text modifications in the IDE any time soon (although there is some functionality in the pipeline that can substitute it at least partially).

But at the current stage, you can achieve the same thing using the different approach. It might be a bit complicated though:

First, you need to create a script definition - a separate jar that describes your script “template”, e.g. similar to the https://github.com/JetBrains/kotlin/tree/master/libraries/tools/kotlin-main-kts
Your definition may look something like:

@KotlinScript(fileExtension = "custom.ext", compilationConfiguration = ScriptConfiguration::class)
abstract class MyScript(val bindings: Map<String, Any?>) {
    val ortResult = bindings["ortResult"] as OrtResult
    val evalErrors = mutableListOf<OrtIssue>()
}

object ScriptConfiguration : ScriptCompilationConfiguration(
    {
        defaultImports("com.here.ort.model.*", "java.util.*")
        ide {
            acceptedLocations(ScriptAcceptedLocation.Everywhere)
        }
    })

It is a good idea to have a dedicated extension for your scripts (“custom.ext” in the example above), since IDE distinguish scripts by the extension.

Then you’ll need to create your own JSR-223 factory the same way as here - https://github.com/JetBrains/kotlin/blob/master/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinJsr223ScriptEngineFactoryExamples.kt#L28, but use your script definition (MyScript) in place of KotlinStandardJsr223ScriptTemplate. You probably can do it in the same jar. And you need to register it in the services folder, of course.

You’ll still need a postface part in your evaluator though, but it seems not relevant to the IDE.

Then finally you need to supply Intellij with the definition. The simplest ad-hoc way to do it is to specify the FQN of your definition class along with the classpath needed to load it in the kotlin compiler settings -> “Kotlin scripting” in Intellij.


#6

I do have a similar problem but I do not have a real “template” to use. My code to invoke a script is:

val result = host.eval(
                    InputStreamReader(`is`).readText().toScriptSource(),
                    ScriptCompilationConfiguration {
                        baseClass(IntegrationConfiguration::class)
                        jvm {
                            //
                            // This is needed as workaround for:
                            //     https://youtrack.jetbrains.com/issue/KT-27497
                            //
                            javaHome(File(javaHome))

                            //
                            // The Kotlin script compiler does not inherit
                            // the classpath by default
                            //
                            dependenciesFromClassloader(wholeClasspath = true)
                        }
                    },
                    ScriptEvaluationConfiguration {
                        //
                        // Arguments used to initialize the script base class
                        //
                        constructorArgs(registry, builder)
                    }
                )

So when I edit my kotlin script, there’s no auto completion even if I add IntegrationConfiguration as script template class.

How can I enable auto completion ?

Full code is here: https://github.com/apache/camel-k/blob/master/runtime/camel-k-runtime-kotlin/src/main/kotlin/org/apache/camel/k/kotlin/KotlinRoutesLoader.kt


#7

This looks very interesting, but currently i’m not sure how to implement this. In your example you’re using:

ide {
acceptedLocations(ScriptAcceptedLocation.Everywhere)
}

But this seems not to be in official maven repositories but only in kontlin-dev repository. But i wasn’t able to use it (version 1.3.30-dev-576 from bintray) because of missing dependecies for thist version in the repository (despite the warning that the kotlin-gradle-plugin won’t fit the Intellij Idea version). Earlier versions i’ve tried led me to a 401-Not-Authorized.

But maybe i’m trying all this too naive. I do apologize for this.

Thanks!


#8

@lburgazzoli, you need to move your ScriptCompilationConfiguration into an object is in my sample above and reference it in the @KotlinScriptannotation, with which your IntegrationConfiguration is (should be) annotated. Then if IDEA will pick your template, it should offer appropriate highlighting/completion.
But it makes sense to replace your dependenciesFromClassloader in the configuration with specific dependencies, otherwise, you’ll likely get different classpath on the runtime and in the IDE.
(although taking into account the missing acceptedLocations configuration key in the public Kotlin releases, by default it will work only for the scripts in source roots; so maybe it is better to wait until 1.3.20)


#9

@solatis, this functionality should be part of the 1.3.20. We may publish some valid dev version before that, but I cannot guarantee it yet.


#10

how can I combine host.eval and annotation style set-up ?


#11

The best way is to use val cfg = createJvmCompilationConfigurationFromTemplate<IntegrationConfiguration> and then use it as a second param to eval.