Skip to content

DatalogEditorExtension.kt

src/jvmMain/kotlin/afield/data/query/datalog/DatalogEditorExtension.kt · 76 lines

package afield.data.query.datalog

import java.io.File

Serves datalog.js, the browser-side Datalog editor support, to the page and to the test that checks it.

The JavaScript is the implementation and this file is a reader for it. datalog.js lives in src/commonMain/resources, reaches the browser as the AMD module bithero/datalog (one of the modules bootstrapMonaco.js requires by name while the editor starts), and is read back verbatim here, so the smoke test datalog.editor-extension.workout runs the same bytes the editor does. A browser-side language that can only be tested in a browser does not stay correct.

Once installed, the extension brings syntax highlighting, lexical diagnostics, a context badge and completions to #datalog regions embedded in Kotlin triple-quoted strings, and to whole .datalog documents. Colouring is decoration-based rather than a Monarch grammar, so one tokenizer covers both contexts and there is no grammar here that can fail to compile and take Monaco's startup down with it. The same workout also compares the editor's vocabulary against DatalogParser's, since the editor is JavaScript in a browser and the parser is Kotlin on a server, and the two lists can only be kept in step at build time.

datalog.js installs itself either way it is loaded: as an AMD module when a Monaco loader is present, and immediately when it is evaluated bare, which is the path html() uses.

object DatalogEditorExtension

The Datalog editor extension, as bytes: the resource itself, and a page fragment that installs it.

object DatalogEditorExtension {

const val id: String = "datalog"

The extension's id, which is also the extensionId datalog.js registers itself under.

fun javascript(): String

The bytes of datalog.js.

Read off the classpath, and from src/commonMain/resources/datalog.js on disk when the classpath has no copy, so this works from a source tree as well as from a jar. It fails rather than returning an empty extension when neither is there.

    fun javascript(): String {
        DatalogEditorExtension::class.java.getResourceAsStream("/datalog.js")?.use { stream ->
            return stream.readBytes().toString(Charsets.UTF_8)
        }
        val onDisk = File("./src/commonMain/resources/datalog.js")
        require(onDisk.isFile) { "datalog.js not found on the classpath or at ${onDisk.absolutePath}" }
        return onDisk.readText(Charsets.UTF_8)
    }

fun html(): String

The extension as a drop-in HTML fragment: a titled line of prose saying what it does, with the whole of datalog.js inlined in a script tag.

Nothing in src/ calls this. datalogEditorExtensionHtml is the unqualified name for it.

    fun html(): String = """
        <div style="font-family:Iosevka Web, monospace; background:#050505; color:#d8d8d8; border:1px solid #303030; padding:10px; max-width:680px">
          <strong>Datalog editor extension</strong>
          <div style="margin-top:4px; color:#a8a8a8">
            Registers <code>#datalog</code> embedded Kotlin regions, and whole <code>.datalog</code> documents,
            with syntax highlighting, lexical diagnostics, a context badge and completions.
          </div>
          <script>
        ${javascript()}
          </script>
        </div>
    """.trimIndent()
}

fun datalogEditorExtensionJavascript(): String = DatalogEditorExtension.javascript()

DatalogEditorExtension.javascript, as a top-level function.

fun datalogEditorExtensionHtml(): String = DatalogEditorExtension.html()

DatalogEditorExtension.html, as a top-level function.