IDEA Plugin: Editor on Light VirtualFile


#1

In my plugin I’m opening an Editor on .kts content delivered by a light virtual file:

    val document = FileDocumentManager.getInstance().getDocument(virtualFile)
    document ?: throw IOException("No document: " + virtualFile.path)
    EditorFactory.getInstance().createEditor(document, project, virtualFile, false)

Code completion and syntax highlighting in the resulting editor only provide bare-bones functionality (no knowledge of project’s dependencies or local source code). Opening the same editor from the file system works fine:

    val virtualFile = LocalFileSystem.getInstance().findFileByIoFile(File("/path/to/script.kts"))
    val document = FileDocumentManager.getInstance().getDocument(virtualFile)

Java and Groovy editors opened from similar light virtual files do provide dependency/context awareness. Is there a way for me to get the same capability when editing in-memory Kotlin scripts?


#2

Or maybe does this question belong in a different forum like IDEA support or YouTrack? I can provide a simple plugin project that reproduces the behavior if that helps.


#3

Hello,
To have similar functionality for kotlin files, kotlin plugin should know the information about the module from which your in-memory Kotlin script should take the dependencies.
Could you provide more details about how your plugin creates those light virtual files, so I can help you to find the correct way to pass this info to kotlin plugin? Maybe you can share some code with us?


#4

Thanks so much for your response. I’ve created a trivial plugin with a single action in plugin.xml that opens a light VirtualFile:

<action id="com.myplugin.ktlv.ScriptAction" class="com.myplugin.ktlv.ScriptAction"
        text="Open Script" description="Open kts light VirtualFile">
  <add-to-group group-id="ToolsMenu" anchor="first"/>
</action>
package com.myplugin.ktlv

import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.editor.EditorFactory
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.fileTypes.FileTypeManager
import com.intellij.openapi.ui.DialogBuilder
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.testFramework.LightVirtualFileBase
import java.awt.Dimension
import java.io.*

class ScriptAction : AnAction() {

    override fun actionPerformed(e: AnActionEvent) {
        val virtualFile = createVirtualFile()
        val document = FileDocumentManager.getInstance().getDocument(virtualFile)
        document ?: throw IOException("No document: " + virtualFile.path)

        val editor = EditorFactory.getInstance().createEditor(document, e.project, virtualFile, false)
        editor.component.preferredSize = Dimension(800, 600)

        val dialogBuilder = DialogBuilder(e.project)
        dialogBuilder.setTitle("ascript.kts")
        dialogBuilder.setActionDescriptors(DialogBuilder.CloseDialogAction())
        dialogBuilder.setCenterPanel(editor.component)
        dialogBuilder.setDimensionServiceKey("mdw.AttributeSourceDialog")
        dialogBuilder.showNotModal()
    }

    private fun createVirtualFile(): VirtualFile {
        val fileType = FileTypeManager.getInstance().getFileTypeByExtension("kts")
        return object : LightVirtualFileBase("ascript.kts", fileType, System.currentTimeMillis()) {
            override fun contentsToByteArray(): ByteArray {
                return scriptContent.toByteArray()
            }
            override fun getInputStream(): InputStream {
                return ByteArrayInputStream(contentsToByteArray())
            }

            override fun getOutputStream(requestor: Any?, newModificationStamp: Long, newTimeStamp: Long): OutputStream {
                return ByteArrayOutputStream()
            }
        }
    }

    companion object {
        const val scriptContent = """import com.example.useful.Helper

val helper = Helper()
helper.doSomething("hello")"""
    }
}

When I debug IDEA with this plugin, I open a client project that has a simple kotlin helper class:

package com.example.useful

class Helper {

    fun doSomething(input: String): String {
        return "something: $input"
    }

    fun doSomethingElse(input: String): String {
        return "something else: $input"
    }
}

Then when I perform the plugin’s Open Script action, I get a dialog with syntax highlighting, but the com.example… import is not understood. Only entities declared in the script itself are understood by the editor. The same content in a VirtualFile opened from the file system works perfectly.

Any guidance is greatly appreciated. I can share both the plugin and client projects as attachments if it helps.


#5

Can you elaborate on how I can pass module information to the Kotlin plugin? Our use case involves workflow artifacts inside a client module where a step may contain kotlin script. We are attempting to migrate from Groovy/Eclipse to Kotlin/IDEA. At runtime the script executes perfectly. At design time it would be great to have context-aware editing.

Is there a different way I should be approaching this? If you can point me to an example or plugin source code that has the hooks for me to pass in module info, I’ll do my best to figure it out. Also I want to confirm whether this is even possible.