From 9494a70591b2fdd88d789d26a57c9426e45d8acb Mon Sep 17 00:00:00 2001 From: Daan Vanden Bosch Date: Sat, 24 Apr 2021 12:14:48 +0200 Subject: [PATCH] Added type definitions and documentation to completion suggestions. --- .../lib/fileFormats/quest/QstTests.kt | 2 +- .../web/assemblyWorker/AssemblyWorker.kt | 176 +++++++++--------- .../web/shared/messages/Messages.kt | 8 +- .../phantasmal/web/questEditor/QuestEditor.kt | 2 +- .../web/questEditor/asm/AsmAnalyser.kt | 15 +- .../asm/monaco/AsmCompletionItemProvider.kt | 2 + ...smController.kt => AsmEditorController.kt} | 2 +- .../questEditor/widgets/AsmEditorWidget.kt | 4 +- .../questEditor/widgets/AsmToolbarWidget.kt | 17 +- .../web/questEditor/widgets/AsmWidget.kt | 4 +- 10 files changed, 130 insertions(+), 102 deletions(-) rename web/src/main/kotlin/world/phantasmal/web/questEditor/controllers/{AsmController.kt => AsmEditorController.kt} (95%) diff --git a/lib/src/commonTest/kotlin/world/phantasmal/lib/fileFormats/quest/QstTests.kt b/lib/src/commonTest/kotlin/world/phantasmal/lib/fileFormats/quest/QstTests.kt index 02380b6a..93523937 100644 --- a/lib/src/commonTest/kotlin/world/phantasmal/lib/fileFormats/quest/QstTests.kt +++ b/lib/src/commonTest/kotlin/world/phantasmal/lib/fileFormats/quest/QstTests.kt @@ -54,7 +54,7 @@ class QstTests : LibTestSuite { "/ep2/shop/gallon.qst", "/princ/ep1/", "/princ/ep4/", - "/solo/ep1/04.qst", // Skip because it contains every chuck twice. + "/solo/ep1/04.qst", // Skip because it contains every chunk twice. "/fragmentofmemoryen.qst", "/lost havoc vulcan.qst", "/goodluck.qst", diff --git a/web/assembly-worker/src/main/kotlin/world/phantasmal/web/assemblyWorker/AssemblyWorker.kt b/web/assembly-worker/src/main/kotlin/world/phantasmal/web/assemblyWorker/AssemblyWorker.kt index 8b276226..c090e69d 100644 --- a/web/assembly-worker/src/main/kotlin/world/phantasmal/web/assemblyWorker/AssemblyWorker.kt +++ b/web/assembly-worker/src/main/kotlin/world/phantasmal/web/assemblyWorker/AssemblyWorker.kt @@ -316,88 +316,6 @@ class AssemblyWorker(private val sendMessage: (ServerMessage) -> Unit) { } } - private fun getSignature(opcode: Opcode): Signature { - val signature = StringBuilder(opcode.mnemonic).append(" ") - val params = mutableListOf() - var first = true - - for (param in opcode.params) { - if (first) { - first = false - } else { - signature.append(", ") - } - - val labelStart = signature.length - - signature.appendParam(param) - - params.add( - Parameter( - labelStart, - labelEnd = signature.length, - documentation = param.doc, - ) - ) - } - - return Signature( - label = signature.toString(), - documentation = opcode.doc, - parameters = params, - ) - } - - private fun StringBuilder.appendParam(param: Param) { - if (param.read || param.write) { - if (param.read) append("in") - if (param.write) append("out") - append(" ") - } - - when (val type = param.type) { - AnyType.Instance -> append("Any") - ByteType -> append("Byte") - ShortType -> append("Short") - IntType -> append("Int") - FloatType -> append("Float") - LabelType.Instance -> append("Label") - ILabelType -> append("ILabel") - DLabelType -> append("DLabel") - SLabelType -> append("SLabel") - ILabelVarType -> append("...ILabel") - StringType -> append("String") - is RegType -> { - append("Reg") - - type.registers?.let { registers -> - append("<") - - var first = true - - for (register in registers) { - if (first) { - first = false - } else { - append(", ") - } - - appendParam(register) - } - - append(">") - } - } - RegVarType -> append("...Reg") - PointerType -> append("Pointer") - } - - param.name?.let { - append(" ") - append(param.name) - } - } - private fun getHover(requestId: Int, lineNo: Int, col: Int) { val hover = signatureHelp(lineNo, col)?.let { help -> val sig = help.signature @@ -559,16 +477,22 @@ class AssemblyWorker(private val sendMessage: (ServerMessage) -> Unit) { CompletionItem( label = ".code", type = CompletionItemType.Keyword, + detail = null, + documentation = "Start of a code segment", insertText = "code", ), CompletionItem( label = ".data", type = CompletionItemType.Keyword, + detail = null, + documentation = "Start of a data segment", insertText = "data", ), CompletionItem( label = ".string", type = CompletionItemType.Keyword, + detail = null, + documentation = "Start of a string data segment", insertText = "string", ), ) @@ -578,14 +502,98 @@ class AssemblyWorker(private val sendMessage: (ServerMessage) -> Unit) { (OPCODES.asSequence() + OPCODES_F8.asSequence() + OPCODES_F9.asSequence()) .filterNotNull() .map { opcode -> + val sig = getSignature(opcode) CompletionItem( label = opcode.mnemonic, - // TODO: Add signature? type = CompletionItemType.Opcode, - insertText = opcode.mnemonic, + detail = sig.label, + documentation = sig.documentation, + insertText = "${opcode.mnemonic} ", ) } .sortedBy { it.label } .toList() + + private fun getSignature(opcode: Opcode): Signature { + val signature = StringBuilder(opcode.mnemonic).append(" ") + val params = mutableListOf() + var first = true + + for (param in opcode.params) { + if (first) { + first = false + } else { + signature.append(", ") + } + + val labelStart = signature.length + + signature.appendParam(param) + + params.add( + Parameter( + labelStart, + labelEnd = signature.length, + documentation = param.doc, + ) + ) + } + + return Signature( + label = signature.toString(), + documentation = opcode.doc, + parameters = params, + ) + } + + private fun StringBuilder.appendParam(param: Param) { + if (param.read || param.write) { + if (param.read) append("in") + if (param.write) append("out") + append(" ") + } + + when (val type = param.type) { + AnyType.Instance -> append("Any") + ByteType -> append("Byte") + ShortType -> append("Short") + IntType -> append("Int") + FloatType -> append("Float") + LabelType.Instance -> append("Label") + ILabelType -> append("ILabel") + DLabelType -> append("DLabel") + SLabelType -> append("SLabel") + ILabelVarType -> append("...ILabel") + StringType -> append("String") + is RegType -> { + append("Reg") + + type.registers?.let { registers -> + append("<") + + var first = true + + for (register in registers) { + if (first) { + first = false + } else { + append(", ") + } + + appendParam(register) + } + + append(">") + } + } + RegVarType -> append("...Reg") + PointerType -> append("Pointer") + } + + param.name?.let { + append(" ") + append(param.name) + } + } } } diff --git a/web/shared/src/commonMain/kotlin/world/phantasmal/web/shared/messages/Messages.kt b/web/shared/src/commonMain/kotlin/world/phantasmal/web/shared/messages/Messages.kt index 508ed299..fd8defc9 100644 --- a/web/shared/src/commonMain/kotlin/world/phantasmal/web/shared/messages/Messages.kt +++ b/web/shared/src/commonMain/kotlin/world/phantasmal/web/shared/messages/Messages.kt @@ -113,7 +113,13 @@ enum class CompletionItemType { } @Serializable -class CompletionItem(val label: String, val type: CompletionItemType, val insertText: String) +class CompletionItem( + val label: String, + val type: CompletionItemType, + val detail: String?, + val documentation: String?, + val insertText: String, +) @Serializable class SignatureHelp(val signature: Signature, val activeParameter: Int) diff --git a/web/src/main/kotlin/world/phantasmal/web/questEditor/QuestEditor.kt b/web/src/main/kotlin/world/phantasmal/web/questEditor/QuestEditor.kt index fece4e49..91d0d0c1 100644 --- a/web/src/main/kotlin/world/phantasmal/web/questEditor/QuestEditor.kt +++ b/web/src/main/kotlin/world/phantasmal/web/questEditor/QuestEditor.kt @@ -63,7 +63,7 @@ class QuestEditor( val questInfoController = addDisposable(QuestInfoController(questEditorStore)) val npcCountsController = addDisposable(NpcCountsController(questEditorStore)) val entityInfoController = addDisposable(EntityInfoController(areaStore, questEditorStore)) - val asmController = addDisposable(AsmController(asmStore)) + val asmController = addDisposable(AsmEditorController(asmStore)) val npcListController = addDisposable(EntityListController(questEditorStore, npcs = true)) val objectListController = addDisposable(EntityListController(questEditorStore, npcs = false)) diff --git a/web/src/main/kotlin/world/phantasmal/web/questEditor/asm/AsmAnalyser.kt b/web/src/main/kotlin/world/phantasmal/web/questEditor/asm/AsmAnalyser.kt index 758d4d73..cf08bc24 100644 --- a/web/src/main/kotlin/world/phantasmal/web/questEditor/asm/AsmAnalyser.kt +++ b/web/src/main/kotlin/world/phantasmal/web/questEditor/asm/AsmAnalyser.kt @@ -7,6 +7,7 @@ import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withTimeout import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString +import mu.KotlinLogging import org.w3c.dom.Worker import world.phantasmal.observable.ChangeEvent import world.phantasmal.observable.Observable @@ -18,6 +19,8 @@ import world.phantasmal.web.shared.messages.* import kotlin.coroutines.Continuation import kotlin.coroutines.resume +private val logger = KotlinLogging.logger {} + class AsmAnalyser { private var inlineStackArgs: Boolean = true private var _mapDesignations = emitter>() @@ -95,8 +98,16 @@ class AsmAnalyser { } is Response<*> -> { - val continuation = inFlightRequests[message.id].unsafeCast?>() - continuation?.resume(message.result.unsafeCast()) + val continuation = inFlightRequests.remove(message.id) + + if (continuation == null) { + logger.warn { + "No continuation for ${message::class.simpleName} ${message.id}, possibly due to timeout." + } + } else { + continuation.unsafeCast>() + .resume(message.result.unsafeCast()) + } } } } diff --git a/web/src/main/kotlin/world/phantasmal/web/questEditor/asm/monaco/AsmCompletionItemProvider.kt b/web/src/main/kotlin/world/phantasmal/web/questEditor/asm/monaco/AsmCompletionItemProvider.kt index 76985fb7..97422aa9 100644 --- a/web/src/main/kotlin/world/phantasmal/web/questEditor/asm/monaco/AsmCompletionItemProvider.kt +++ b/web/src/main/kotlin/world/phantasmal/web/questEditor/asm/monaco/AsmCompletionItemProvider.kt @@ -32,6 +32,8 @@ class AsmCompletionItemProvider(private val analyser: AsmAnalyser) : CompletionI CompletionItemType.Opcode -> CompletionItemKind.Function } insertText = completion.insertText + completion.detail?.let { detail = it } + completion.documentation?.let { documentation = it } } } incomplete = false diff --git a/web/src/main/kotlin/world/phantasmal/web/questEditor/controllers/AsmController.kt b/web/src/main/kotlin/world/phantasmal/web/questEditor/controllers/AsmEditorController.kt similarity index 95% rename from web/src/main/kotlin/world/phantasmal/web/questEditor/controllers/AsmController.kt rename to web/src/main/kotlin/world/phantasmal/web/questEditor/controllers/AsmEditorController.kt index c7f471d1..e2a92065 100644 --- a/web/src/main/kotlin/world/phantasmal/web/questEditor/controllers/AsmController.kt +++ b/web/src/main/kotlin/world/phantasmal/web/questEditor/controllers/AsmEditorController.kt @@ -10,7 +10,7 @@ import world.phantasmal.web.externals.monacoEditor.createModel import world.phantasmal.web.questEditor.stores.AsmStore import world.phantasmal.webui.controllers.Controller -class AsmController(private val store: AsmStore) : Controller() { +class AsmEditorController(private val store: AsmStore) : Controller() { val enabled: Val = store.editingEnabled val readOnly: Val = !enabled or store.textModel.isNull() diff --git a/web/src/main/kotlin/world/phantasmal/web/questEditor/widgets/AsmEditorWidget.kt b/web/src/main/kotlin/world/phantasmal/web/questEditor/widgets/AsmEditorWidget.kt index 8ad1bc09..32225563 100644 --- a/web/src/main/kotlin/world/phantasmal/web/questEditor/widgets/AsmEditorWidget.kt +++ b/web/src/main/kotlin/world/phantasmal/web/questEditor/widgets/AsmEditorWidget.kt @@ -4,12 +4,12 @@ import org.w3c.dom.Node import world.phantasmal.core.disposable.disposable import world.phantasmal.web.externals.monacoEditor.* import world.phantasmal.web.questEditor.asm.monaco.EditorHistory -import world.phantasmal.web.questEditor.controllers.AsmController +import world.phantasmal.web.questEditor.controllers.AsmEditorController import world.phantasmal.webui.dom.div import world.phantasmal.webui.obj import world.phantasmal.webui.widgets.Widget -class AsmEditorWidget(private val ctrl: AsmController) : Widget() { +class AsmEditorWidget(private val ctrl: AsmEditorController) : Widget() { private lateinit var editor: IStandaloneCodeEditor override fun Node.createElement() = diff --git a/web/src/main/kotlin/world/phantasmal/web/questEditor/widgets/AsmToolbarWidget.kt b/web/src/main/kotlin/world/phantasmal/web/questEditor/widgets/AsmToolbarWidget.kt index a01df3d5..d0551976 100644 --- a/web/src/main/kotlin/world/phantasmal/web/questEditor/widgets/AsmToolbarWidget.kt +++ b/web/src/main/kotlin/world/phantasmal/web/questEditor/widgets/AsmToolbarWidget.kt @@ -1,23 +1,24 @@ package world.phantasmal.web.questEditor.widgets import org.w3c.dom.Node -import world.phantasmal.web.questEditor.controllers.AsmController +import world.phantasmal.web.questEditor.controllers.AsmEditorController import world.phantasmal.webui.dom.div import world.phantasmal.webui.widgets.Checkbox import world.phantasmal.webui.widgets.Toolbar import world.phantasmal.webui.widgets.Widget -class AsmToolbarWidget(private val ctrl: AsmController) : Widget() { +class AsmToolbarWidget(private val ctrl: AsmEditorController) : Widget() { override fun Node.createElement() = div { className = "pw-quest-editor-asm-toolbar" - addChild(Toolbar( - enabled = ctrl.enabled, - children = listOf( - Checkbox( - enabled = ctrl.inlineStackArgsEnabled, - tooltip = ctrl.inlineStackArgsTooltip, + addChild( + Toolbar( + enabled = ctrl.enabled, + children = listOf( + Checkbox( + enabled = ctrl.inlineStackArgsEnabled, + tooltip = ctrl.inlineStackArgsTooltip, label = "Inline args", checked = ctrl.inlineStackArgs, onChange = ctrl::setInlineStackArgs, diff --git a/web/src/main/kotlin/world/phantasmal/web/questEditor/widgets/AsmWidget.kt b/web/src/main/kotlin/world/phantasmal/web/questEditor/widgets/AsmWidget.kt index f6bca558..8424baec 100644 --- a/web/src/main/kotlin/world/phantasmal/web/questEditor/widgets/AsmWidget.kt +++ b/web/src/main/kotlin/world/phantasmal/web/questEditor/widgets/AsmWidget.kt @@ -1,11 +1,11 @@ package world.phantasmal.web.questEditor.widgets import org.w3c.dom.Node -import world.phantasmal.web.questEditor.controllers.AsmController +import world.phantasmal.web.questEditor.controllers.AsmEditorController import world.phantasmal.webui.dom.div import world.phantasmal.webui.widgets.Widget -class AsmWidget(private val ctrl: AsmController) : Widget() { +class AsmWidget(private val ctrl: AsmEditorController) : Widget() { private lateinit var editorWidget: AsmEditorWidget override fun Node.createElement() =