From d8aa54f4b62189f57e853ff75999be3539c40eb1 Mon Sep 17 00:00:00 2001 From: Sascha Lisson Date: Wed, 24 Apr 2024 15:57:42 +0200 Subject: [PATCH 01/10] build: ensure plugin is installed into correct MPS version --- buildSrc/build.gradle.kts | 4 ++++ buildSrc/src/main/kotlin/org/modelix/CopyMps.kt | 8 ++++++++ projectional-editor-ssr-mps/build.gradle.kts | 7 ++++--- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 876c922b..80bf5350 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -2,6 +2,10 @@ plugins { `kotlin-dsl` } +dependencies { + implementation(kotlin("stdlib")) +} + repositories { mavenCentral() } diff --git a/buildSrc/src/main/kotlin/org/modelix/CopyMps.kt b/buildSrc/src/main/kotlin/org/modelix/CopyMps.kt index 9610fbaf..d63be9c0 100644 --- a/buildSrc/src/main/kotlin/org/modelix/CopyMps.kt +++ b/buildSrc/src/main/kotlin/org/modelix/CopyMps.kt @@ -61,6 +61,14 @@ val Project.mpsHomeDir: Provider get() { return project.layout.buildDirectory.dir("mps-$mpsVersion") } +val Project.mpsPluginsDir: File? get() { + val candidates = listOfNotNull( + project.findProperty("mps$mpsPlatformVersion.plugins.dir")?.toString()?.let { file(it) }, + System.getProperty("user.home")?.let { file(it).resolve("/Library/Application Support/JetBrains/MPS2023.2/plugins/") }, + ) + return candidates.firstOrNull { it.isDirectory } +} + fun Project.copyMps(): File { if (project != rootProject) return rootProject.copyMps() diff --git a/projectional-editor-ssr-mps/build.gradle.kts b/projectional-editor-ssr-mps/build.gradle.kts index 28b776f3..cf86434f 100644 --- a/projectional-editor-ssr-mps/build.gradle.kts +++ b/projectional-editor-ssr-mps/build.gradle.kts @@ -2,6 +2,7 @@ import com.jetbrains.plugin.structure.intellij.utils.JDOMUtil import org.jdom2.Element import org.jetbrains.intellij.transformXml import org.modelix.mpsHomeDir +import org.modelix.mpsPluginsDir buildscript { dependencies { @@ -117,12 +118,12 @@ tasks { archiveFileName = "$stubsSolutionName.jar" } - val mpsPluginDir = project.findProperty("mps232.plugins.dir")?.toString()?.let { file(it) } - if (mpsPluginDir != null && mpsPluginDir.isDirectory) { + val pluginDir = mpsPluginsDir + if (pluginDir != null) { create("installMpsPlugin") { dependsOn(prepareSandbox) from(project.layout.buildDirectory.dir("idea-sandbox/plugins/${project.name}")) - into(mpsPluginDir.resolve(project.name)) + into(pluginDir.resolve(project.name)) } } From 69a7d6528733007d2f7768efc8cdee7b48538506 Mon Sep 17 00:00:00 2001 From: Sascha Lisson Date: Thu, 25 Apr 2024 13:02:45 +0200 Subject: [PATCH 02/10] fix: HTML elements were in wrong order after incremental change --- .../editor/IncrementalVirtualDOMBuilder.kt | 2 + .../kotlin/org/modelix/editor/VirtualDom.kt | 47 +++++++++++++++++-- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/projectional-editor/src/commonMain/kotlin/org/modelix/editor/IncrementalVirtualDOMBuilder.kt b/projectional-editor/src/commonMain/kotlin/org/modelix/editor/IncrementalVirtualDOMBuilder.kt index 6fd5b699..a0a7e161 100644 --- a/projectional-editor/src/commonMain/kotlin/org/modelix/editor/IncrementalVirtualDOMBuilder.kt +++ b/projectional-editor/src/commonMain/kotlin/org/modelix/editor/IncrementalVirtualDOMBuilder.kt @@ -44,9 +44,11 @@ class IncrementalVirtualDOMBuilder(val document: IVirtualDom, existingRootElemen if (index < parent.childNodes.size) { val actualChild = parent.childNodes.get(index) if (actualChild != expectedChild) { + expectedChild.parent?.removeChild(expectedChild) parent.insertBefore(expectedChild, actualChild) } } else { + expectedChild.parent?.removeChild(expectedChild) parent.appendChild(expectedChild) } } diff --git a/projectional-editor/src/commonMain/kotlin/org/modelix/editor/VirtualDom.kt b/projectional-editor/src/commonMain/kotlin/org/modelix/editor/VirtualDom.kt index e8da6cb4..8a90db2f 100644 --- a/projectional-editor/src/commonMain/kotlin/org/modelix/editor/VirtualDom.kt +++ b/projectional-editor/src/commonMain/kotlin/org/modelix/editor/VirtualDom.kt @@ -180,24 +180,42 @@ class VirtualDom(override val ui: IVirtualDomUI, val idPrefix: String = "") : IV if (referenceNode == null) return appendChild(newNode) val index = childNodes.indexOf(referenceNode) require(index >= 0) { "$referenceNode is not a child of $this" } - childNodes.add(index, newNode) + addChild(index, newNode) return newNode } + override fun appendChild(child: IVirtualDom.Node): IVirtualDom.Node { - childNodes += child + addChild(childNodes.size, child) return child } + override fun replaceChild(newChild: IVirtualDom.Node, oldChild: IVirtualDom.Node): IVirtualDom.Node { val index = childNodes.indexOf(oldChild) require(index >= 0) { "$oldChild is not a child of $this" } - childNodes[index] = newChild + + removeChildAt(index) + addChild(index, newChild) return oldChild } + override fun removeChild(child: IVirtualDom.Node): IVirtualDom.Node { - require(childNodes.remove(child)) { "$child is not a child of $this" } + val index = childNodes.indexOf(child) + require(index >= 0) { "$child is not a child of $this" } + removeChildAt(index) return child } + fun removeChildAt(index: Int) { + val child = childNodes.removeAt(index) + (child as Node).parent = null + } + + fun addChild(index: Int, child: IVirtualDom.Node) { + check(child.parent == null) { "Node is already attached to a parent node" } + childNodes.add(index, child) + (child as Node).parent = this + } + override fun remove() { parent?.removeChild(this) } @@ -218,11 +236,32 @@ class VirtualDom(override val ui: IVirtualDomUI, val idPrefix: String = "") : IV override fun getInnerBounds(): Bounds = ui.getInnerBounds(this) override fun getOuterBounds(): Bounds = ui.getOuterBounds(this) + + override fun toString(): String { + return buildString { + append("<$tagName>") + attributes.forEach { attribute -> + append(" ") + append(attribute.key) + append("=\"") + append(attribute.value) + append("\"") + } + append(">") + childNodes.forEach { child -> + append(child) + } + append("") + } + } } inner class HTMLElement(tagName: String) : Element(tagName), IVirtualDom.HTMLElement inner class Text : Node(), IVirtualDom.Text { override var textContent: String? = null + override fun toString(): String { + return textContent ?: "" + } } } From 7c077ce146056b735080f97a5dd1a0a7d70f4543 Mon Sep 17 00:00:00 2001 From: Sascha Lisson Date: Thu, 25 Apr 2024 16:00:57 +0200 Subject: [PATCH 03/10] fix: sort modules/models/nodes alphabetically --- .../editor/ssr/mps/ModelixSSRServerForMPS.kt | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/projectional-editor-ssr-mps/src/main/kotlin/org/modelix/editor/ssr/mps/ModelixSSRServerForMPS.kt b/projectional-editor-ssr-mps/src/main/kotlin/org/modelix/editor/ssr/mps/ModelixSSRServerForMPS.kt index d0f9313d..1ac04d0b 100644 --- a/projectional-editor-ssr-mps/src/main/kotlin/org/modelix/editor/ssr/mps/ModelixSSRServerForMPS.kt +++ b/projectional-editor-ssr-mps/src/main/kotlin/org/modelix/editor/ssr/mps/ModelixSSRServerForMPS.kt @@ -167,11 +167,11 @@ class ModelixSSRServerForMPS : Disposable { repository.getArea().executeRead { body { ul { - repository.getChildren(BuiltinLanguages.MPSRepositoryConcepts.Repository.modules).forEach { + repository.getChildren(BuiltinLanguages.MPSRepositoryConcepts.Repository.modules).sortedBy { it.name }.forEach { li { a { href = "module/${URLEncoder.encode(it.reference.serialize(), StandardCharsets.UTF_8)}/" - +(it.getPropertyValue(BuiltinLanguages.jetbrains_mps_lang_core.INamedConcept.name) ?: "") + +(it.name ?: "") } } } @@ -188,11 +188,11 @@ class ModelixSSRServerForMPS : Disposable { val module = repository.getArea().resolveNode(NodeReference(moduleRef))!! body { ul { - module.getChildren(BuiltinLanguages.MPSRepositoryConcepts.Module.models).forEach { + module.getChildren(BuiltinLanguages.MPSRepositoryConcepts.Module.models).sortedBy { it.name }.forEach { li { a { href = "../../model/${URLEncoder.encode(it.reference.serialize(), StandardCharsets.UTF_8)}/" - +(it.getPropertyValue(BuiltinLanguages.jetbrains_mps_lang_core.INamedConcept.name) ?: "") + +(it.name ?: "") } } } @@ -209,11 +209,11 @@ class ModelixSSRServerForMPS : Disposable { val model = repository.getArea().resolveNode(NodeReference(modelRef))!! body { ul { - model.getChildren(BuiltinLanguages.MPSRepositoryConcepts.Model.rootNodes).forEach { + model.getChildren(BuiltinLanguages.MPSRepositoryConcepts.Model.rootNodes).sortedBy { it.name }.forEach { li { a { href = "../../editor/${URLEncoder.encode(it.reference.serialize(), StandardCharsets.UTF_8)}/" - +(it.getPropertyValue(BuiltinLanguages.jetbrains_mps_lang_core.INamedConcept.name) ?: "") + +(it.name ?: "") } } } @@ -343,4 +343,6 @@ fun IReferenceLink?.toMPS(): SReferenceLink? = if (this is MPSReferenceLink) thi fun IProperty?.toMPS(): SProperty? = if (this is MPSProperty) this.property else null fun IConcept?.toMPS(): SAbstractConcept? = if (this is MPSConcept) this.concept else null +val INode.name get() = getPropertyValue(BuiltinLanguages.jetbrains_mps_lang_core.INamedConcept.name) + class MPSConstraintViolation(val rule: Rule<*>) : IConstraintViolation From 5ce9fe41e0c982fee19ba4cbcc1e470b4fe1c5fc Mon Sep 17 00:00:00 2001 From: Sascha Lisson Date: Thu, 25 Apr 2024 16:10:03 +0200 Subject: [PATCH 04/10] feat: a "new line concept" can be specified that is inserted when pressing ENTER --- ...notation.generator.templates@generator.mps | 277 ++++++++++++------ ...mps.notation.impl.baseLanguage.modelix.mps | 7 +- .../kotlin/org/modelix/editor/CellTemplate.kt | 28 +- .../org/modelix/editor/CellTemplateBuilder.kt | 11 +- .../org/modelix/editor/INonExistingNode.kt | 5 +- 5 files changed, 229 insertions(+), 99 deletions(-) diff --git a/mps/languages/org.modelix.mps.notation/generator/templates/org.modelix.mps.notation.generator.templates@generator.mps b/mps/languages/org.modelix.mps.notation/generator/templates/org.modelix.mps.notation.generator.templates@generator.mps index de539a75..1232833f 100644 --- a/mps/languages/org.modelix.mps.notation/generator/templates/org.modelix.mps.notation.generator.templates@generator.mps +++ b/mps/languages/org.modelix.mps.notation/generator/templates/org.modelix.mps.notation.generator.templates@generator.mps @@ -35,8 +35,8 @@ - + @@ -76,6 +76,9 @@ + + + @@ -90,6 +93,10 @@ + + + + @@ -99,6 +106,10 @@ + + + + @@ -636,7 +647,45 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -722,7 +771,49 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1253,94 +1344,20 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + - - - - - @@ -1463,4 +1480,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mps/solutions/org.modelix.mps.notation.impl.baseLanguage/models/org.modelix.mps.notation.impl.baseLanguage.modelix.mps b/mps/solutions/org.modelix.mps.notation.impl.baseLanguage/models/org.modelix.mps.notation.impl.baseLanguage.modelix.mps index 2c83def9..ae847ec8 100644 --- a/mps/solutions/org.modelix.mps.notation.impl.baseLanguage/models/org.modelix.mps.notation.impl.baseLanguage.modelix.mps +++ b/mps/solutions/org.modelix.mps.notation.impl.baseLanguage/models/org.modelix.mps.notation.impl.baseLanguage.modelix.mps @@ -34,6 +34,7 @@ + @@ -121,6 +122,7 @@ + @@ -1437,7 +1439,10 @@ - + + + + diff --git a/projectional-editor/src/commonMain/kotlin/org/modelix/editor/CellTemplate.kt b/projectional-editor/src/commonMain/kotlin/org/modelix/editor/CellTemplate.kt index e5de5dd1..774e61cc 100644 --- a/projectional-editor/src/commonMain/kotlin/org/modelix/editor/CellTemplate.kt +++ b/projectional-editor/src/commonMain/kotlin/org/modelix/editor/CellTemplate.kt @@ -384,6 +384,13 @@ class ChildCellTemplate( private var separatorCell: CellTemplate? = null + /** + * When pressing ENTER a new node of this concept is inserted. + * If this concept is null, then a special placeholder cell is added to the editor and a node can be created using + * the code completion menu. + */ + var newLineConcept: IConcept? = null + fun setSeparator(newSeparator: CellTemplate) { this.separatorCell = newSeparator reference?.let { newSeparator.setReference(SeparatorCellTemplateReference(it)) } @@ -398,7 +405,7 @@ class ChildCellTemplate( val childNodes = getChildNodes(node) val substitutionPlaceholder = context.editorState.substitutionPlaceholderPositions[createCellReference(node)] val placeholderIndex = substitutionPlaceholder?.index?.coerceIn(0..childNodes.size) ?: 0 - val addSubstitutionPlaceholder: (Int) -> Unit = { index -> + fun addSubstitutionPlaceholder(index: Int) { val isDefaultPlaceholder = childNodes.isEmpty() val placeholderText = if (isDefaultPlaceholder) "" else "" val placeholder = TextCellData("", placeholderText) @@ -413,10 +420,12 @@ class ChildCellTemplate( placeholder.properties[CommonCellProperties.tabTarget] = true cell.addChild(placeholder) } - val addInsertActionCell: (Int) -> Unit = { index -> + fun addInsertActionCell(index: Int) { if (link.isMultiple) { val actionCell = CellData() - val action = InsertSubstitutionPlaceholderAction(context.editorState, createCellReference(node), index) + val action = newLineConcept?.let { + InstantiateNodeAction(NonExistingChild(ExistingNode(node), link, index), it) + } ?: InsertSubstitutionPlaceholderAction(context.editorState, createCellReference(node), index) actionCell.properties[CellActionProperties.insert] = action cell.addChild(actionCell) } @@ -483,6 +492,19 @@ class InsertSubstitutionPlaceholderAction( } } +class InstantiateNodeAction(val location: INonExistingNode, val concept: IConcept) : ICellAction { + override fun isApplicable(): Boolean = true + + override fun execute(editor: EditorComponent) { + val newNode = location.getExistingAncestor()!!.getArea().executeWrite { + location.replaceNode(concept) + } + editor.selectAfterUpdate { + CaretPositionPolicy(newNode).getBestSelection(editor) + } + } +} + fun CellTemplate.firstLeaf(): CellTemplate = if (getChildren().isEmpty()) this else getChildren().first().firstLeaf() fun CellTemplate.descendants(includeSelf: Boolean = false): Sequence { return if (includeSelf) { diff --git a/projectional-editor/src/commonMain/kotlin/org/modelix/editor/CellTemplateBuilder.kt b/projectional-editor/src/commonMain/kotlin/org/modelix/editor/CellTemplateBuilder.kt index 036a896f..d9a4916d 100644 --- a/projectional-editor/src/commonMain/kotlin/org/modelix/editor/CellTemplateBuilder.kt +++ b/projectional-editor/src/commonMain/kotlin/org/modelix/editor/CellTemplateBuilder.kt @@ -265,19 +265,18 @@ open class CellTemplateBuilder(val template: CellTe ChildCellTemplate(template.concept, this).builder().also(body).template.also(template::addChild) } - fun ITypedChildListLink<*>.vertical(body: CellTemplateBuilder.() -> Unit = {}) { + fun ITypedChildListLink<*>.vertical(body: ChildCellTemplateBuilder.() -> Unit = {}) { this.untyped().vertical(body) } - fun IChildLink.vertical(body: CellTemplateBuilder.() -> Unit = {}) { - // TODO add layout information + fun IChildLink.vertical(body: ChildCellTemplateBuilder.() -> Unit = {}) { horizontal(separator = null) { template.properties[CommonCellProperties.layout] = ECellLayout.VERTICAL body() } } - fun ITypedChildListLink<*>.horizontal(separator: String? = ",", body: CellTemplateBuilder.() -> Unit = {}) { + fun ITypedChildListLink<*>.horizontal(separator: String? = ",", body: ChildCellTemplateBuilder.() -> Unit = {}) { this.untyped().horizontal(separator, body) } @@ -352,6 +351,10 @@ class ChildCellTemplateBuilder( CollectionCellTemplate(template.concept).also { body(it.builder()) }, ) } + + fun newLineConcept(newLineConcept: IConcept) { + (template as ChildCellTemplate).newLineConcept = newLineConcept + } } class ReferenceCellTemplateBuilder( diff --git a/projectional-editor/src/commonMain/kotlin/org/modelix/editor/INonExistingNode.kt b/projectional-editor/src/commonMain/kotlin/org/modelix/editor/INonExistingNode.kt index 72f7717b..98cb74df 100644 --- a/projectional-editor/src/commonMain/kotlin/org/modelix/editor/INonExistingNode.kt +++ b/projectional-editor/src/commonMain/kotlin/org/modelix/editor/INonExistingNode.kt @@ -100,8 +100,7 @@ data class ExistingNode(private val node: INode) : INonExistingNode { } override fun getOrCreateNode(subConcept: IConcept?): INode { - val outputConcept = coerceOutputConcept(subConcept) - return if (node.isInstanceOf(outputConcept)) { + return if (subConcept == null || node.isInstanceOf(coerceOutputConcept(subConcept))) { node } else { replaceNode(subConcept) @@ -130,9 +129,7 @@ data class NonExistingChild(private val parent: INonExistingNode, val link: IChi override fun replaceNode(subConcept: IConcept?): INode { val parentNode = parent.getOrCreateNode(null) - val existing = parentNode.getChildren(link).toList().getOrNull(index) val newNode = parentNode.addNewChild(link, index, coerceOutputConcept(subConcept)) - existing?.remove() return newNode } From 3441476054dff42a60f157dcd82fe37679aa1cd4 Mon Sep 17 00:00:00 2001 From: Sascha Lisson Date: Thu, 25 Apr 2024 17:09:53 +0200 Subject: [PATCH 05/10] fix: missing read action when trying to type the text for substituting a node --- .../src/commonMain/kotlin/org/modelix/editor/CaretSelection.kt | 2 +- .../src/commonMain/kotlin/org/modelix/editor/CellTemplate.kt | 2 +- .../kotlin/org/modelix/editor/ReplaceNodeActionProvider.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/projectional-editor/src/commonMain/kotlin/org/modelix/editor/CaretSelection.kt b/projectional-editor/src/commonMain/kotlin/org/modelix/editor/CaretSelection.kt index 0e9a81f8..9be739b7 100644 --- a/projectional-editor/src/commonMain/kotlin/org/modelix/editor/CaretSelection.kt +++ b/projectional-editor/src/commonMain/kotlin/org/modelix/editor/CaretSelection.kt @@ -209,7 +209,7 @@ class CaretSelection(val layoutable: LayoutableCell, val start: Int, val end: In // complete immediately if there is a single matching action val providers = layoutable.cell.getSubstituteActions() val params = CodeCompletionParameters(editor, newText) - val actions = providers.flatMap { it.flattenApplicableActions(params) }.toList() + val actions = editor.runRead { providers.flatMap { it.flattenApplicableActions(params) }.toList() } val matchingActions = actions .filter { it.getMatchingText() == newText } .applyShadowing() diff --git a/projectional-editor/src/commonMain/kotlin/org/modelix/editor/CellTemplate.kt b/projectional-editor/src/commonMain/kotlin/org/modelix/editor/CellTemplate.kt index 774e61cc..324691d8 100644 --- a/projectional-editor/src/commonMain/kotlin/org/modelix/editor/CellTemplate.kt +++ b/projectional-editor/src/commonMain/kotlin/org/modelix/editor/CellTemplate.kt @@ -333,7 +333,7 @@ class ReferenceCellTemplate( return sourceNode.getReferenceTarget(link) } override fun getInstantiationActions(location: INonExistingNode, parameters: CodeCompletionParameters): List { - val sourceNode = NonExistingChild(location.getParent()!!, location.getContainmentLink()!!, location.index()).ofSubConcept(concept) + val sourceNode = location.ofSubConcept(concept) val scope = ScopeAspect.getScope(sourceNode, link) val targets = scope.getVisibleElements(sourceNode, link) return targets.map { target -> diff --git a/projectional-editor/src/commonMain/kotlin/org/modelix/editor/ReplaceNodeActionProvider.kt b/projectional-editor/src/commonMain/kotlin/org/modelix/editor/ReplaceNodeActionProvider.kt index 81a26b29..21d22ae3 100644 --- a/projectional-editor/src/commonMain/kotlin/org/modelix/editor/ReplaceNodeActionProvider.kt +++ b/projectional-editor/src/commonMain/kotlin/org/modelix/editor/ReplaceNodeActionProvider.kt @@ -12,7 +12,7 @@ data class ReplaceNodeActionProvider(val location: INonExistingNode) : ICodeComp val allowedConcepts = expectedConcept.getAllSubConcepts(true) .filterNot { it.isAbstract() } .filter { concept -> - val newNode = SpecializedNonExistingNode(NonExistingChild(location.getParent()!!, location.getContainmentLink()!!, location.index()), concept) + val newNode = SpecializedNonExistingNode(location, concept) ConstraintsAspect.canCreate(newNode) } val cellModels = allowedConcepts.map { concept -> From 90a9caea17f93487807bde450824c820b933ff12 Mon Sep 17 00:00:00 2001 From: Sascha Lisson Date: Fri, 26 Apr 2024 09:51:12 +0200 Subject: [PATCH 06/10] perf: collect changed DOM elements efficiently Previously, the whole DOM was serialized and then just filtered by previously sent elements. Now the virtual DOM marks elements and subtrees as changed, which allows to efficiently iterate only over the elements that changed since the last update. The virtual DOM itself was already updated incrementally, just the synchronization to the client was a temporary inefficient implementation. --- .../editor/ssr/server/ModelixSSRServer.kt | 54 +++++++++---------- .../kotlin/org/modelix/editor/VirtualDom.kt | 50 +++++++++++++++-- 2 files changed, 71 insertions(+), 33 deletions(-) diff --git a/projectional-editor-ssr-server/src/main/kotlin/org/modelix/editor/ssr/server/ModelixSSRServer.kt b/projectional-editor-ssr-server/src/main/kotlin/org/modelix/editor/ssr/server/ModelixSSRServer.kt index 1e53c267..94c894e2 100644 --- a/projectional-editor-ssr-server/src/main/kotlin/org/modelix/editor/ssr/server/ModelixSSRServer.kt +++ b/projectional-editor-ssr-server/src/main/kotlin/org/modelix/editor/ssr/server/ModelixSSRServer.kt @@ -44,11 +44,8 @@ import java.util.Collections import kotlin.collections.HashMap import kotlin.collections.LinkedHashSet import kotlin.collections.List -import kotlin.collections.Map import kotlin.collections.MutableMap import kotlin.collections.MutableSet -import kotlin.collections.asSequence -import kotlin.collections.emptyMap import kotlin.collections.filter import kotlin.collections.forEach import kotlin.collections.get @@ -172,7 +169,6 @@ class ModelixSSRServer(private val nodeResolutionScope: INodeResolutionScope) { private inner class EditorSession(val editorId: String) { private var editorComponent: EditorComponent? = null private val commonElementPrefix = editorId + "-" - private var domStateOnClient: Map = emptyMap() private fun getEditor() = checkNotNull(editorComponent) { "Editor $editorId isn't initialized" } @@ -201,55 +197,57 @@ class ModelixSSRServer(private val nodeResolutionScope: INodeResolutionScope) { fun sendUpdate() { LOG.debug { "($editorId) sendUpdate" } editorComponent!!.update() - val dom = editorComponent!!.getHtmlElement()!! - LOG.debug { "($editorId) dom: $dom" } - val latestDomState = HashMap() - // TODO performance - var rootData = toUpdateData(dom, latestDomState) - if (rootData is ElementReference) rootData = latestDomState[rootData.id]!! - check(rootData is HTMLElementUpdateData) - if (rootData.id != editorId) { - latestDomState.remove(rootData.id) - rootData = rootData.copy(id = editorId) - latestDomState[editorId] = rootData + val dom = editorComponent!!.getHtmlElement()!! as VirtualDom.Node + val changedElements = HashMap() + var rootData: INodeUpdateData? = toUpdateData(dom, changedElements) + dom.resetModificationMarker() + if (rootData is ElementReference) rootData = changedElements[rootData.id] + if (rootData != null) { + check(rootData is HTMLElementUpdateData) + if (rootData.id != editorId) { + changedElements.remove(rootData.id) + rootData = rootData.copy(id = editorId) + changedElements[editorId] = rootData + } } - val changesOnly = latestDomState.entries.asSequence() - .filter { domStateOnClient[it.key] != it.value } - .map { it.value }.toList() - if (changesOnly.isEmpty()) return - - domStateOnClient = latestDomState + if (changedElements.isEmpty()) return ws.outgoing.trySend( Frame.Text( MessageFromServer( editorId = editorId, domUpdate = DomTreeUpdate( - elements = changesOnly, + elements = changedElements.values.toList(), ), ).toJson(), ), ) } - fun toUpdateData(node: IVirtualDom.Node, id2data: MutableMap): INodeUpdateData { + fun toUpdateData(node: VirtualDom.Node, id2data: MutableMap): INodeUpdateData { return when (node) { - is IVirtualDom.Text -> TextNodeUpdateData(node.textContent ?: "") - is IVirtualDom.Element -> { + is VirtualDom.Text -> TextNodeUpdateData(node.textContent ?: "") + is VirtualDom.Element -> { val id = node.id?.takeIf { it.isNotEmpty() }?.let { if (it.startsWith(commonElementPrefix)) it else commonElementPrefix + it } - val data = HTMLElementUpdateData( + fun createData() = HTMLElementUpdateData( id = id, tagName = node.tagName, attributes = node.getAttributes() - "id", children = node.childNodes.toList().map { toUpdateData(it, id2data) }, ) if (id == null) { - data + createData() } else { - id2data[id] = data + if (node.wasModified()) { + id2data[id] = createData() + } else { + if (node.wasAnyDescendantModified()) { + node.childNodes.forEach { toUpdateData(it, id2data) } + } + } ElementReference(id) } } diff --git a/projectional-editor/src/commonMain/kotlin/org/modelix/editor/VirtualDom.kt b/projectional-editor/src/commonMain/kotlin/org/modelix/editor/VirtualDom.kt index 8a90db2f..24780620 100644 --- a/projectional-editor/src/commonMain/kotlin/org/modelix/editor/VirtualDom.kt +++ b/projectional-editor/src/commonMain/kotlin/org/modelix/editor/VirtualDom.kt @@ -160,9 +160,33 @@ class VirtualDom(override val ui: IVirtualDomUI, val idPrefix: String = "") : IV } open inner class Node : IVirtualDom.Node { + private var wasModified: Boolean = true + private var wasAnyDescendantModified: Boolean = true private val userObjects: MutableMap = HashMap() - override var parent: IVirtualDom.Node? = null - override val childNodes: MutableList = ArrayList() + override var parent: Node? = null + override val childNodes: MutableList = ArrayList() + + fun resetModificationMarker() { + if (!wasModified && wasAnyDescendantModified) return + childNodes.forEach { it.resetModificationMarker() } + wasAnyDescendantModified = false + wasModified = false + } + + fun markModified() { + wasModified = true + parent?.markDescendantModified() + } + + fun markDescendantModified() { + if (wasAnyDescendantModified) return + wasAnyDescendantModified = true + parent?.markDescendantModified() + } + + fun wasAnyDescendantModified() = wasAnyDescendantModified + + fun wasModified(): Boolean = wasModified override fun getVDom(): IVirtualDom = this@VirtualDom @@ -207,13 +231,17 @@ class VirtualDom(override val ui: IVirtualDomUI, val idPrefix: String = "") : IV fun removeChildAt(index: Int) { val child = childNodes.removeAt(index) - (child as Node).parent = null + child.parent = null + markModified() } fun addChild(index: Int, child: IVirtualDom.Node) { + require(child is Node) check(child.parent == null) { "Node is already attached to a parent node" } childNodes.add(index, child) - (child as Node).parent = this + child.parent = this + markModified() + if (child.wasModified() || child.wasAnyDescendantModified()) markDescendantModified() } override fun remove() { @@ -226,12 +254,18 @@ class VirtualDom(override val ui: IVirtualDomUI, val idPrefix: String = "") : IV override fun getAttributeNames(): Array = attributes.keys.toTypedArray() override fun getAttribute(qualifiedName: String): String? = attributes[qualifiedName] override fun setAttribute(qualifiedName: String, value: String) { + if (attributes[qualifiedName] == value) return attributes[qualifiedName] = value if (qualifiedName == "id") { elementsMap[value] = this } + markModified() + } + override fun removeAttribute(qualifiedName: String) { + if (attributes.remove(qualifiedName) != null) { + markModified() + } } - override fun removeAttribute(qualifiedName: String) { attributes.remove(qualifiedName) } override fun getAttributes(): Map = attributes override fun getInnerBounds(): Bounds = ui.getInnerBounds(this) @@ -260,6 +294,12 @@ class VirtualDom(override val ui: IVirtualDomUI, val idPrefix: String = "") : IV inner class Text : Node(), IVirtualDom.Text { override var textContent: String? = null + set(value) { + if (field == value) return + field = value + markModified() + } + override fun toString(): String { return textContent ?: "" } From 40abfd1ee439e0a7b7da0fb55dd076ffd1fd7d24 Mon Sep 17 00:00:00 2001 From: Sascha Lisson Date: Fri, 26 Apr 2024 10:21:51 +0200 Subject: [PATCH 07/10] fix: missing read access when computing side transformations in MPS --- .../kotlin/org/modelix/editor/CaretSelection.kt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/projectional-editor/src/commonMain/kotlin/org/modelix/editor/CaretSelection.kt b/projectional-editor/src/commonMain/kotlin/org/modelix/editor/CaretSelection.kt index 9be739b7..bdcb9655 100644 --- a/projectional-editor/src/commonMain/kotlin/org/modelix/editor/CaretSelection.kt +++ b/projectional-editor/src/commonMain/kotlin/org/modelix/editor/CaretSelection.kt @@ -164,10 +164,12 @@ class CaretSelection(val layoutable: LayoutableCell, val start: Int, val end: In } ).toList() val params = CodeCompletionParameters(editor, typedText) - val actions = providers.flatMap { it.flattenApplicableActions(params) } - val matchingActions = actions - .filter { it.getMatchingText().startsWith(typedText) } - .applyShadowing() + val matchingActions = editor.runRead { + val actions = providers.flatMap { it.flattenApplicableActions(params) } + actions + .filter { it.getMatchingText().startsWith(typedText) } + .applyShadowing() + } if (matchingActions.isNotEmpty()) { if (matchingActions.size == 1 && matchingActions.first().getMatchingText() == typedText) { matchingActions.first().execute(editor) From 13245373ea8c08abe71f0b2bb53bb1d283cdf6a1 Mon Sep 17 00:00:00 2001 From: Sascha Lisson Date: Fri, 26 Apr 2024 15:17:01 +0200 Subject: [PATCH 08/10] fix: wrong concept was provided to MPS scopes --- .../editor/ssr/mps/ModelixSSRServerForMPS.kt | 2 +- .../kotlin/org/modelix/editor/CellTemplate.kt | 5 ++-- .../org/modelix/editor/INonExistingNode.kt | 26 +++++++------------ .../editor/ReplaceNodeActionProvider.kt | 2 +- 4 files changed, 14 insertions(+), 21 deletions(-) diff --git a/projectional-editor-ssr-mps/src/main/kotlin/org/modelix/editor/ssr/mps/ModelixSSRServerForMPS.kt b/projectional-editor-ssr-mps/src/main/kotlin/org/modelix/editor/ssr/mps/ModelixSSRServerForMPS.kt index 1ac04d0b..ea77a592 100644 --- a/projectional-editor-ssr-mps/src/main/kotlin/org/modelix/editor/ssr/mps/ModelixSSRServerForMPS.kt +++ b/projectional-editor-ssr-mps/src/main/kotlin/org/modelix/editor/ssr/mps/ModelixSSRServerForMPS.kt @@ -283,7 +283,7 @@ object MPSScopeProvider : IScopeProvider { val containmentLink: SContainmentLink = sourceNode.getContainmentLink().toMPS()!! val index = sourceNode.index() val association: SReferenceLink = link.toMPS()!! - val concept: SAbstractConcept = (sourceNode.getNode()?.concept ?: sourceNode.expectedConcept()).toMPS()!! + val concept: SAbstractConcept = sourceNode.expectedConcept().toMPS()!! ModelConstraints.getReferenceDescriptor( contextNode, containmentLink, diff --git a/projectional-editor/src/commonMain/kotlin/org/modelix/editor/CellTemplate.kt b/projectional-editor/src/commonMain/kotlin/org/modelix/editor/CellTemplate.kt index 324691d8..eea98ff9 100644 --- a/projectional-editor/src/commonMain/kotlin/org/modelix/editor/CellTemplate.kt +++ b/projectional-editor/src/commonMain/kotlin/org/modelix/editor/CellTemplate.kt @@ -333,7 +333,7 @@ class ReferenceCellTemplate( return sourceNode.getReferenceTarget(link) } override fun getInstantiationActions(location: INonExistingNode, parameters: CodeCompletionParameters): List { - val sourceNode = location.ofSubConcept(concept) + val sourceNode = location.replacement(concept) val scope = ScopeAspect.getScope(sourceNode, link) val targets = scope.getVisibleElements(sourceNode, link) return targets.map { target -> @@ -358,8 +358,7 @@ class ReferenceCellTemplate( val sourceNode = location.getOrCreateNode(concept) sourceNode.setReferenceTarget(link, target.getOrCreateNode()) editor.selectAfterUpdate { - CaretPositionPolicy(createCellReference(sourceNode)) - .getBestSelection(editor) + CaretPositionPolicy(createCellReference(sourceNode)).getBestSelection(editor) } } } diff --git a/projectional-editor/src/commonMain/kotlin/org/modelix/editor/INonExistingNode.kt b/projectional-editor/src/commonMain/kotlin/org/modelix/editor/INonExistingNode.kt index 98cb74df..c0ade64b 100644 --- a/projectional-editor/src/commonMain/kotlin/org/modelix/editor/INonExistingNode.kt +++ b/projectional-editor/src/commonMain/kotlin/org/modelix/editor/INonExistingNode.kt @@ -37,39 +37,33 @@ fun INonExistingNode.commonAncestor(otherNode: INonExistingNode): INonExistingNo return null } -data class SpecializedNonExistingNode(val node: INonExistingNode, val subConcept: IConcept) : INonExistingNode { - override fun getExistingAncestor(): INode? = node.getExistingAncestor() +data class NodeReplacement(val nodeToReplace: INonExistingNode, val replacementConcept: IConcept) : INonExistingNode { + override fun getExistingAncestor(): INode? = nodeToReplace.getExistingAncestor() - override fun getParent(): INonExistingNode? = node.getParent() + override fun getParent(): INonExistingNode? = nodeToReplace.getParent() - override fun getContainmentLink(): IChildLink? = node.getContainmentLink() + override fun getContainmentLink(): IChildLink? = nodeToReplace.getContainmentLink() - override fun index(): Int = node.index() + override fun index(): Int = nodeToReplace.index() override fun replaceNode(subConcept: IConcept?): INode { - return node.replaceNode(coerceOutputConcept(subConcept)) + return nodeToReplace.replaceNode(coerceOutputConcept(subConcept)) } override fun getOrCreateNode(subConcept: IConcept?): INode { - return node.getOrCreateNode(coerceOutputConcept(subConcept)) + return replaceNode(subConcept) } override fun getNode(): INode? { - return node.getNode() + return null } override fun expectedConcept(): IConcept { - return subConcept + return replacementConcept } } -fun INonExistingNode.ofSubConcept(subConcept: IConcept): INonExistingNode { - return if (expectedConcept().isSubConceptOf(subConcept)) { - this - } else { - SpecializedNonExistingNode(this, subConcept) - } -} +fun INonExistingNode.replacement(newConcept: IConcept): INonExistingNode = NodeReplacement(this, newConcept) fun INonExistingNode.coerceOutputConcept(subConcept: IConcept?): IConcept? { val expectedConcept = expectedConcept() diff --git a/projectional-editor/src/commonMain/kotlin/org/modelix/editor/ReplaceNodeActionProvider.kt b/projectional-editor/src/commonMain/kotlin/org/modelix/editor/ReplaceNodeActionProvider.kt index 21d22ae3..88b081da 100644 --- a/projectional-editor/src/commonMain/kotlin/org/modelix/editor/ReplaceNodeActionProvider.kt +++ b/projectional-editor/src/commonMain/kotlin/org/modelix/editor/ReplaceNodeActionProvider.kt @@ -12,7 +12,7 @@ data class ReplaceNodeActionProvider(val location: INonExistingNode) : ICodeComp val allowedConcepts = expectedConcept.getAllSubConcepts(true) .filterNot { it.isAbstract() } .filter { concept -> - val newNode = SpecializedNonExistingNode(location, concept) + val newNode = location.replacement(concept) ConstraintsAspect.canCreate(newNode) } val cellModels = allowedConcepts.map { concept -> From 90d3ea54beecc17e6dbca56df60aa861a0367ff3 Mon Sep 17 00:00:00 2001 From: Sascha Lisson Date: Fri, 26 Apr 2024 16:55:20 +0200 Subject: [PATCH 09/10] feat: wrapper actions derived from child cells --- .../editor/ssr/mps/ModelixSSRServerForMPS.kt | 8 ++++++-- .../kotlin/org/modelix/editor/CellTemplate.kt | 17 +++++++++++++++-- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/projectional-editor-ssr-mps/src/main/kotlin/org/modelix/editor/ssr/mps/ModelixSSRServerForMPS.kt b/projectional-editor-ssr-mps/src/main/kotlin/org/modelix/editor/ssr/mps/ModelixSSRServerForMPS.kt index ea77a592..c0f91507 100644 --- a/projectional-editor-ssr-mps/src/main/kotlin/org/modelix/editor/ssr/mps/ModelixSSRServerForMPS.kt +++ b/projectional-editor-ssr-mps/src/main/kotlin/org/modelix/editor/ssr/mps/ModelixSSRServerForMPS.kt @@ -309,10 +309,14 @@ object MPSConstraints : IConstraintsChecker { // Constraints only prevent creating a node. If it already exists, it's handled by the model checker. if (node.getNode() != null) return emptyList() + val parentNode = node.getParent()?.getNode().toMPS() + // MPS doesn't support constraint checking without a parent node + if (parentNode == null) return emptyList() + // ConstraintsCanBeFacade.checkCanBeRoot() val containmentContext = ContainmentContext.Builder() - .parentNode(node.getParent()?.getNode().toMPS()) + .parentNode(parentNode) .link(node.getContainmentLink().toMPS()) .childConcept(node.expectedConcept().toMPS()!!) .build() @@ -323,7 +327,7 @@ object MPSConstraints : IConstraintsChecker { ConstraintsCanBeFacade.checkCanBeAncestor( CanBeAncestorContext.Builder() .ancestorNode(ancestorNode) - .parentNode(node.getParent()?.getNode().toMPS()) + .parentNode(parentNode) .childConcept(node.expectedConcept().toMPS()!!) .descendantNode(node.getNode().toMPS()) .link(node.getContainmentLink().toMPS()) diff --git a/projectional-editor/src/commonMain/kotlin/org/modelix/editor/CellTemplate.kt b/projectional-editor/src/commonMain/kotlin/org/modelix/editor/CellTemplate.kt index eea98ff9..1e72da12 100644 --- a/projectional-editor/src/commonMain/kotlin/org/modelix/editor/CellTemplate.kt +++ b/projectional-editor/src/commonMain/kotlin/org/modelix/editor/CellTemplate.kt @@ -8,6 +8,7 @@ import org.modelix.model.api.INode import org.modelix.model.api.INodeReference import org.modelix.model.api.IProperty import org.modelix.model.api.IReferenceLink +import org.modelix.model.api.isSubConceptOf import org.modelix.scopes.ScopeAspect import kotlin.jvm.JvmName @@ -468,8 +469,20 @@ class ChildCellTemplate( fun getChildNodes(node: INode) = node.getChildren(link).toList() override fun getInstantiationActions(location: INonExistingNode, parameters: CodeCompletionParameters): List? { - // TODO - return listOf() + // This cell produces "wrappers". + // For example, in MPS baseLanguage you can type "int" (which is a Type) where a Statement is expected, + // and it is automatically wrapped with a LocalVariableDeclarationStatement. + // If the to-be-wrapped concept is already allowed without the wrapper, then it's not necessary to generate + // such an action. + if (link.targetConcept.isSubConceptOf(location.expectedConcept())) { + return emptyList() + } + + // prevent endless nesting + if (location.ancestors(true).takeWhile { it.getNode() == null }.count() > 10) return emptyList() + + val childNode = NonExistingChild(location.replacement(concept), link) + return listOf(ReplaceNodeActionProvider(childNode)) } } data class PlaceholderCellReference(val childCellRef: TemplateCellReference) : CellReference() From 3551916e3ae05880acafd62ed5ee364ebc086a5b Mon Sep 17 00:00:00 2001 From: Sascha Lisson Date: Sun, 28 Apr 2024 07:36:44 +0200 Subject: [PATCH 10/10] test: first unit test of the MPS plugin --- .github/workflows/mps-compatibility.yaml | 8 +- .pre-commit-config.yaml | 5 +- .../src/main/kotlin/org/modelix/CopyMps.kt | 2 +- gradle/libs.versions.toml | 5 +- kernelf-angular-demo/build.gradle.kts | 4 +- mps/.mps/modules.xml | 13 +- mps/build.gradle.kts | 11 +- ...e2kotlin.generator.templates@generator.mps | 0 ...delix.mps.baseLanguage2kotlin.behavior.mps | 0 ...ix.mps.baseLanguage2kotlin.constraints.mps | 0 ...modelix.mps.baseLanguage2kotlin.editor.mps | 0 ...elix.mps.baseLanguage2kotlin.structure.mps | 0 ...odelix.mps.baseLanguage2kotlin.textGen.mps | 0 ...lix.mps.baseLanguage2kotlin.typesystem.mps | 0 .../org.modelix.mps.baseLanguage2kotlin.mpl | 0 ...mps.notation.impl.baseLanguage.modelix.mps | 1 + ...modelix.mps.notation.impl.baseLanguage.msd | 0 ...notation.generator.templates@generator.mps | 0 .../org.modelix.mps.notation.behavior.mps | 0 .../org.modelix.mps.notation.constraints.mps | 0 .../org.modelix.mps.notation.editor.mps | 0 .../org.modelix.mps.notation.structure.mps | 0 .../org.modelix.mps.notation.typesystem.mps | 0 .../org.modelix.mps.notation.mpl | 0 .../org.modelix.mps.webaspect.devkit.devkit | 0 .../org.modelix.mps.webaspect.genplan.mps | 0 .../org.modelix.mps.webaspect.genplan.msd | 0 .../org.modelix.mps.editor.ssr.stubs.msd | 101 ---------- .../org.modelix.mps.notation.runtime.msd | 17 -- .../org.modelix.mps.editor.ssr.stubs.msd | 107 +++++++++++ .../build.gradle.kts | 149 ++++++++++++++ .../src/main/resources/META-INF/plugin.xml | 28 +++ .../main/resources/META-INF/pluginIcon.svg | 66 +++++++ .../editor/ssr/mps/BaseLanguageTests.kt | 73 +++++++ .../org/modelix/editor/ssr/mps/TestBase.kt | 123 ++++++++++++ .../editor/ssr/mps/TestFrameworkSetupTest.kt | 35 ++++ .../testdata/SimpleProject/.mps/migration.xml | 6 + .../testdata/SimpleProject/.mps/modules.xml | 8 + .../testdata/SimpleProject/.mps/vcs.xml | 6 + .../testdata/SimpleProject/.mps/workspace.xml | 181 ++++++++++++++++++ .../solutions/Solution1/Solution1.msd | 22 +++ .../Solution1/models/Solution1.model1.mps | 42 ++++ projectional-editor-ssr-mps/build.gradle.kts | 18 +- .../editor/ssr/mps/EditorIntegrationForMPS.kt | 34 ++++ .../editor/ssr/mps/ModelixSSRServerForMPS.kt | 28 +-- .../main/resources/META-INF/pluginIcon.svg | 85 +++++--- .../testdata/SimpleProject/.mps/modules.xml | 6 + .../testdata/SimpleProject/.mps/vcs.xml | 6 + .../kotlin/org/modelix/editor/EditorEngine.kt | 2 +- settings.gradle.kts | 1 + 50 files changed, 998 insertions(+), 195 deletions(-) rename mps/{ => modules}/org.modelix.mps.baseLanguage2kotlin/generator/templates/org.modelix.mps.baseLanguage2kotlin.generator.templates@generator.mps (100%) rename mps/{ => modules}/org.modelix.mps.baseLanguage2kotlin/models/org.modelix.mps.baseLanguage2kotlin.behavior.mps (100%) rename mps/{ => modules}/org.modelix.mps.baseLanguage2kotlin/models/org.modelix.mps.baseLanguage2kotlin.constraints.mps (100%) rename mps/{ => modules}/org.modelix.mps.baseLanguage2kotlin/models/org.modelix.mps.baseLanguage2kotlin.editor.mps (100%) rename mps/{ => modules}/org.modelix.mps.baseLanguage2kotlin/models/org.modelix.mps.baseLanguage2kotlin.structure.mps (100%) rename mps/{ => modules}/org.modelix.mps.baseLanguage2kotlin/models/org.modelix.mps.baseLanguage2kotlin.textGen.mps (100%) rename mps/{ => modules}/org.modelix.mps.baseLanguage2kotlin/models/org.modelix.mps.baseLanguage2kotlin.typesystem.mps (100%) rename mps/{ => modules}/org.modelix.mps.baseLanguage2kotlin/org.modelix.mps.baseLanguage2kotlin.mpl (100%) rename mps/{solutions => modules}/org.modelix.mps.notation.impl.baseLanguage/models/org.modelix.mps.notation.impl.baseLanguage.modelix.mps (99%) rename mps/{solutions => modules}/org.modelix.mps.notation.impl.baseLanguage/org.modelix.mps.notation.impl.baseLanguage.msd (100%) rename mps/{languages => modules}/org.modelix.mps.notation/generator/templates/org.modelix.mps.notation.generator.templates@generator.mps (100%) rename mps/{languages => modules}/org.modelix.mps.notation/models/org.modelix.mps.notation.behavior.mps (100%) rename mps/{languages => modules}/org.modelix.mps.notation/models/org.modelix.mps.notation.constraints.mps (100%) rename mps/{languages => modules}/org.modelix.mps.notation/models/org.modelix.mps.notation.editor.mps (100%) rename mps/{languages => modules}/org.modelix.mps.notation/models/org.modelix.mps.notation.structure.mps (100%) rename mps/{languages => modules}/org.modelix.mps.notation/models/org.modelix.mps.notation.typesystem.mps (100%) rename mps/{languages => modules}/org.modelix.mps.notation/org.modelix.mps.notation.mpl (100%) rename mps/{devkits => modules}/org.modelix.mps.webaspect.devkit/org.modelix.mps.webaspect.devkit.devkit (100%) rename mps/{solutions => modules}/org.modelix.mps.webaspect.genplan/models/org.modelix.mps.webaspect.genplan.mps (100%) rename mps/{solutions => modules}/org.modelix.mps.webaspect.genplan/org.modelix.mps.webaspect.genplan.msd (100%) delete mode 100644 mps/org.modelix.mps.editor.ssr.stubs/org.modelix.mps.editor.ssr.stubs.msd delete mode 100644 mps/org.modelix.mps.notation.runtime/org.modelix.mps.notation.runtime.msd create mode 100644 mps/stubs-template/org.modelix.mps.editor.ssr.stubs/org.modelix.mps.editor.ssr.stubs.msd create mode 100644 projectional-editor-ssr-mps-languages/build.gradle.kts create mode 100644 projectional-editor-ssr-mps-languages/src/main/resources/META-INF/plugin.xml create mode 100644 projectional-editor-ssr-mps-languages/src/main/resources/META-INF/pluginIcon.svg create mode 100644 projectional-editor-ssr-mps-languages/src/test/kotlin/org/modelix/editor/ssr/mps/BaseLanguageTests.kt create mode 100644 projectional-editor-ssr-mps-languages/src/test/kotlin/org/modelix/editor/ssr/mps/TestBase.kt create mode 100644 projectional-editor-ssr-mps-languages/src/test/kotlin/org/modelix/editor/ssr/mps/TestFrameworkSetupTest.kt create mode 100644 projectional-editor-ssr-mps-languages/testdata/SimpleProject/.mps/migration.xml create mode 100644 projectional-editor-ssr-mps-languages/testdata/SimpleProject/.mps/modules.xml create mode 100644 projectional-editor-ssr-mps-languages/testdata/SimpleProject/.mps/vcs.xml create mode 100644 projectional-editor-ssr-mps-languages/testdata/SimpleProject/.mps/workspace.xml create mode 100644 projectional-editor-ssr-mps-languages/testdata/SimpleProject/solutions/Solution1/Solution1.msd create mode 100644 projectional-editor-ssr-mps-languages/testdata/SimpleProject/solutions/Solution1/models/Solution1.model1.mps create mode 100644 projectional-editor-ssr-mps/src/main/kotlin/org/modelix/editor/ssr/mps/EditorIntegrationForMPS.kt create mode 100644 projectional-editor-ssr-mps/testdata/SimpleProject/.mps/modules.xml create mode 100644 projectional-editor-ssr-mps/testdata/SimpleProject/.mps/vcs.xml diff --git a/.github/workflows/mps-compatibility.yaml b/.github/workflows/mps-compatibility.yaml index 9a22a6bb..731aa3ed 100644 --- a/.github/workflows/mps-compatibility.yaml +++ b/.github/workflows/mps-compatibility.yaml @@ -32,7 +32,13 @@ jobs: - name: Build env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: ./gradlew :projectional-editor-ssr-mps:build -PciBuild=true -Pgpr.token=${{ secrets.GITHUB_TOKEN }} -Pmps.version.major=${{ matrix.version }} + run: >- + ./gradlew + :projectional-editor-ssr-mps:build + :projectional-editor-ssr-mps-languages:build + -PciBuild=true + -Pgpr.token=${{ secrets.GITHUB_TOKEN }} + -Pmps.version.major=${{ matrix.version }} - name: Archive test report uses: actions/upload-artifact@v4 if: always() diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0bd03c9a..bbc970c4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,8 +6,7 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.4.0 hooks: - - id: trailing-whitespace - - id: end-of-file-fixer +# - id: end-of-file-fixer # disabled, because it modifies MPS XML files - id: check-toml - id: check-yaml - id: check-added-large-files @@ -15,7 +14,7 @@ repos: - id: check-symlinks - id: fix-byte-order-marker - id: mixed-line-ending - - id: trailing-whitespace +# - id: trailing-whitespace # disabled, because it modifies multiline string literals to invalid values - repo: https://github.com/alessandrojcm/commitlint-pre-commit-hook rev: v9.4.0 hooks: diff --git a/buildSrc/src/main/kotlin/org/modelix/CopyMps.kt b/buildSrc/src/main/kotlin/org/modelix/CopyMps.kt index d63be9c0..b31286df 100644 --- a/buildSrc/src/main/kotlin/org/modelix/CopyMps.kt +++ b/buildSrc/src/main/kotlin/org/modelix/CopyMps.kt @@ -27,7 +27,7 @@ val Project.mpsMajorVersion: String get() { if (project != rootProject) return rootProject.mpsVersion return project.findProperty("mps.version.major")?.toString()?.takeIf { it.isNotEmpty() } ?: project.findProperty("mps.version")?.toString()?.takeIf { it.isNotEmpty() }?.replace(Regex("""(20\d\d\.\d+).*"""), "$1") - ?: "2023.3" + ?: "2023.2" } val Project.mpsVersion: String get() { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8a4ed8f7..7946e82f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,8 +6,8 @@ npm-publish = { id = "dev.petuska.npm.publish", version = "3.4.2" } modelix-mps-buildtools = { id = "org.modelix.mps.build-tools", version.ref = "modelixBuildtools" } [versions] -modelixCore = "7.2.0-pr707.7a5dda1" -modelixBuildtools="1.4.1" +modelixCore = "7.3.0" +modelixBuildtools="1.4.2-kotlin.support" [libraries] modelix-model-api = { group = "org.modelix", name = "model-api", version.ref = "modelixCore" } @@ -21,3 +21,4 @@ modelix-model-datastructure = { group = "org.modelix", name = "model-datastructu modelix-mps-model-adapters = { group = "org.modelix.mps", name = "model-adapters", version.ref = "modelixCore" } kotlin-logging = { group = "io.github.oshai", name = "kotlin-logging", version = "6.0.9" } kotlin-html = "org.jetbrains.kotlinx:kotlinx-html:0.11.0" +modelix-build-tools-lib = { group = "org.modelix.mps", name = "build-tools-lib", version.ref = "modelixBuildtools" } diff --git a/kernelf-angular-demo/build.gradle.kts b/kernelf-angular-demo/build.gradle.kts index 8c8f8c01..62325219 100644 --- a/kernelf-angular-demo/build.gradle.kts +++ b/kernelf-angular-demo/build.gradle.kts @@ -32,12 +32,12 @@ val updateTsModelApiVersion = tasks.create("updateTsModelApiVersion") { var text = packageJsonFile.readText() println("ts-model-api path: $localPath") val replacement = if (localPath.exists()) { - """"@modelix/ts-model-api": "file:${localPath.relativeTo(projectDir)}"""" + """"@modelix/ts-model-api": "file:${localPath.relativeTo(projectDir).toString().replace("\\", "\\\\")}"""" } else { """"@modelix/ts-model-api": "${rootProject.property("ts-model-api.version")}"""" } println("ts-model-api version: $replacement") - text = text.replace(Regex(""""@modelix/ts-model-api": ".*""""), replacement) + text = text.replace(Regex(""""@modelix/ts-model-api": ".*""""), { replacement }) packageJsonFile.writeText(text) } } diff --git a/mps/.mps/modules.xml b/mps/.mps/modules.xml index 09474367..882ff03c 100644 --- a/mps/.mps/modules.xml +++ b/mps/.mps/modules.xml @@ -2,13 +2,12 @@ - - - - - - - + + + + + + diff --git a/mps/build.gradle.kts b/mps/build.gradle.kts index 2de564de..64dedc2c 100644 --- a/mps/build.gradle.kts +++ b/mps/build.gradle.kts @@ -8,15 +8,20 @@ plugins { } mpsBuild { + dependsOn(":projectional-editor-ssr-mps:buildPlugin") mpsHome = mpsHomeDir.get().asFile.absolutePath javaHome = Jvm.current().javaHome - search(".") + search("../projectional-editor-ssr-mps/build/idea-sandbox/plugins/projectional-editor-ssr-mps") + search("modules") publication("editor-languages") { module("org.modelix.mps.webaspect.devkit") + module("org.modelix.mps.webaspect.genplan") + module("org.modelix.mps.baseLanguage2kotlin") + module("org.modelix.mps.notation") } - publication("already-packaged-modules") { - module("org.modelix.mps.editor.ssr.stubs") + publication("baseLanguage-notation") { + module("org.modelix.mps.notation.impl.baseLanguage") } } diff --git a/mps/org.modelix.mps.baseLanguage2kotlin/generator/templates/org.modelix.mps.baseLanguage2kotlin.generator.templates@generator.mps b/mps/modules/org.modelix.mps.baseLanguage2kotlin/generator/templates/org.modelix.mps.baseLanguage2kotlin.generator.templates@generator.mps similarity index 100% rename from mps/org.modelix.mps.baseLanguage2kotlin/generator/templates/org.modelix.mps.baseLanguage2kotlin.generator.templates@generator.mps rename to mps/modules/org.modelix.mps.baseLanguage2kotlin/generator/templates/org.modelix.mps.baseLanguage2kotlin.generator.templates@generator.mps diff --git a/mps/org.modelix.mps.baseLanguage2kotlin/models/org.modelix.mps.baseLanguage2kotlin.behavior.mps b/mps/modules/org.modelix.mps.baseLanguage2kotlin/models/org.modelix.mps.baseLanguage2kotlin.behavior.mps similarity index 100% rename from mps/org.modelix.mps.baseLanguage2kotlin/models/org.modelix.mps.baseLanguage2kotlin.behavior.mps rename to mps/modules/org.modelix.mps.baseLanguage2kotlin/models/org.modelix.mps.baseLanguage2kotlin.behavior.mps diff --git a/mps/org.modelix.mps.baseLanguage2kotlin/models/org.modelix.mps.baseLanguage2kotlin.constraints.mps b/mps/modules/org.modelix.mps.baseLanguage2kotlin/models/org.modelix.mps.baseLanguage2kotlin.constraints.mps similarity index 100% rename from mps/org.modelix.mps.baseLanguage2kotlin/models/org.modelix.mps.baseLanguage2kotlin.constraints.mps rename to mps/modules/org.modelix.mps.baseLanguage2kotlin/models/org.modelix.mps.baseLanguage2kotlin.constraints.mps diff --git a/mps/org.modelix.mps.baseLanguage2kotlin/models/org.modelix.mps.baseLanguage2kotlin.editor.mps b/mps/modules/org.modelix.mps.baseLanguage2kotlin/models/org.modelix.mps.baseLanguage2kotlin.editor.mps similarity index 100% rename from mps/org.modelix.mps.baseLanguage2kotlin/models/org.modelix.mps.baseLanguage2kotlin.editor.mps rename to mps/modules/org.modelix.mps.baseLanguage2kotlin/models/org.modelix.mps.baseLanguage2kotlin.editor.mps diff --git a/mps/org.modelix.mps.baseLanguage2kotlin/models/org.modelix.mps.baseLanguage2kotlin.structure.mps b/mps/modules/org.modelix.mps.baseLanguage2kotlin/models/org.modelix.mps.baseLanguage2kotlin.structure.mps similarity index 100% rename from mps/org.modelix.mps.baseLanguage2kotlin/models/org.modelix.mps.baseLanguage2kotlin.structure.mps rename to mps/modules/org.modelix.mps.baseLanguage2kotlin/models/org.modelix.mps.baseLanguage2kotlin.structure.mps diff --git a/mps/org.modelix.mps.baseLanguage2kotlin/models/org.modelix.mps.baseLanguage2kotlin.textGen.mps b/mps/modules/org.modelix.mps.baseLanguage2kotlin/models/org.modelix.mps.baseLanguage2kotlin.textGen.mps similarity index 100% rename from mps/org.modelix.mps.baseLanguage2kotlin/models/org.modelix.mps.baseLanguage2kotlin.textGen.mps rename to mps/modules/org.modelix.mps.baseLanguage2kotlin/models/org.modelix.mps.baseLanguage2kotlin.textGen.mps diff --git a/mps/org.modelix.mps.baseLanguage2kotlin/models/org.modelix.mps.baseLanguage2kotlin.typesystem.mps b/mps/modules/org.modelix.mps.baseLanguage2kotlin/models/org.modelix.mps.baseLanguage2kotlin.typesystem.mps similarity index 100% rename from mps/org.modelix.mps.baseLanguage2kotlin/models/org.modelix.mps.baseLanguage2kotlin.typesystem.mps rename to mps/modules/org.modelix.mps.baseLanguage2kotlin/models/org.modelix.mps.baseLanguage2kotlin.typesystem.mps diff --git a/mps/org.modelix.mps.baseLanguage2kotlin/org.modelix.mps.baseLanguage2kotlin.mpl b/mps/modules/org.modelix.mps.baseLanguage2kotlin/org.modelix.mps.baseLanguage2kotlin.mpl similarity index 100% rename from mps/org.modelix.mps.baseLanguage2kotlin/org.modelix.mps.baseLanguage2kotlin.mpl rename to mps/modules/org.modelix.mps.baseLanguage2kotlin/org.modelix.mps.baseLanguage2kotlin.mpl diff --git a/mps/solutions/org.modelix.mps.notation.impl.baseLanguage/models/org.modelix.mps.notation.impl.baseLanguage.modelix.mps b/mps/modules/org.modelix.mps.notation.impl.baseLanguage/models/org.modelix.mps.notation.impl.baseLanguage.modelix.mps similarity index 99% rename from mps/solutions/org.modelix.mps.notation.impl.baseLanguage/models/org.modelix.mps.notation.impl.baseLanguage.modelix.mps rename to mps/modules/org.modelix.mps.notation.impl.baseLanguage/models/org.modelix.mps.notation.impl.baseLanguage.modelix.mps index ae847ec8..362bf057 100644 --- a/mps/solutions/org.modelix.mps.notation.impl.baseLanguage/models/org.modelix.mps.notation.impl.baseLanguage.modelix.mps +++ b/mps/modules/org.modelix.mps.notation.impl.baseLanguage/models/org.modelix.mps.notation.impl.baseLanguage.modelix.mps @@ -240,6 +240,7 @@ + diff --git a/mps/solutions/org.modelix.mps.notation.impl.baseLanguage/org.modelix.mps.notation.impl.baseLanguage.msd b/mps/modules/org.modelix.mps.notation.impl.baseLanguage/org.modelix.mps.notation.impl.baseLanguage.msd similarity index 100% rename from mps/solutions/org.modelix.mps.notation.impl.baseLanguage/org.modelix.mps.notation.impl.baseLanguage.msd rename to mps/modules/org.modelix.mps.notation.impl.baseLanguage/org.modelix.mps.notation.impl.baseLanguage.msd diff --git a/mps/languages/org.modelix.mps.notation/generator/templates/org.modelix.mps.notation.generator.templates@generator.mps b/mps/modules/org.modelix.mps.notation/generator/templates/org.modelix.mps.notation.generator.templates@generator.mps similarity index 100% rename from mps/languages/org.modelix.mps.notation/generator/templates/org.modelix.mps.notation.generator.templates@generator.mps rename to mps/modules/org.modelix.mps.notation/generator/templates/org.modelix.mps.notation.generator.templates@generator.mps diff --git a/mps/languages/org.modelix.mps.notation/models/org.modelix.mps.notation.behavior.mps b/mps/modules/org.modelix.mps.notation/models/org.modelix.mps.notation.behavior.mps similarity index 100% rename from mps/languages/org.modelix.mps.notation/models/org.modelix.mps.notation.behavior.mps rename to mps/modules/org.modelix.mps.notation/models/org.modelix.mps.notation.behavior.mps diff --git a/mps/languages/org.modelix.mps.notation/models/org.modelix.mps.notation.constraints.mps b/mps/modules/org.modelix.mps.notation/models/org.modelix.mps.notation.constraints.mps similarity index 100% rename from mps/languages/org.modelix.mps.notation/models/org.modelix.mps.notation.constraints.mps rename to mps/modules/org.modelix.mps.notation/models/org.modelix.mps.notation.constraints.mps diff --git a/mps/languages/org.modelix.mps.notation/models/org.modelix.mps.notation.editor.mps b/mps/modules/org.modelix.mps.notation/models/org.modelix.mps.notation.editor.mps similarity index 100% rename from mps/languages/org.modelix.mps.notation/models/org.modelix.mps.notation.editor.mps rename to mps/modules/org.modelix.mps.notation/models/org.modelix.mps.notation.editor.mps diff --git a/mps/languages/org.modelix.mps.notation/models/org.modelix.mps.notation.structure.mps b/mps/modules/org.modelix.mps.notation/models/org.modelix.mps.notation.structure.mps similarity index 100% rename from mps/languages/org.modelix.mps.notation/models/org.modelix.mps.notation.structure.mps rename to mps/modules/org.modelix.mps.notation/models/org.modelix.mps.notation.structure.mps diff --git a/mps/languages/org.modelix.mps.notation/models/org.modelix.mps.notation.typesystem.mps b/mps/modules/org.modelix.mps.notation/models/org.modelix.mps.notation.typesystem.mps similarity index 100% rename from mps/languages/org.modelix.mps.notation/models/org.modelix.mps.notation.typesystem.mps rename to mps/modules/org.modelix.mps.notation/models/org.modelix.mps.notation.typesystem.mps diff --git a/mps/languages/org.modelix.mps.notation/org.modelix.mps.notation.mpl b/mps/modules/org.modelix.mps.notation/org.modelix.mps.notation.mpl similarity index 100% rename from mps/languages/org.modelix.mps.notation/org.modelix.mps.notation.mpl rename to mps/modules/org.modelix.mps.notation/org.modelix.mps.notation.mpl diff --git a/mps/devkits/org.modelix.mps.webaspect.devkit/org.modelix.mps.webaspect.devkit.devkit b/mps/modules/org.modelix.mps.webaspect.devkit/org.modelix.mps.webaspect.devkit.devkit similarity index 100% rename from mps/devkits/org.modelix.mps.webaspect.devkit/org.modelix.mps.webaspect.devkit.devkit rename to mps/modules/org.modelix.mps.webaspect.devkit/org.modelix.mps.webaspect.devkit.devkit diff --git a/mps/solutions/org.modelix.mps.webaspect.genplan/models/org.modelix.mps.webaspect.genplan.mps b/mps/modules/org.modelix.mps.webaspect.genplan/models/org.modelix.mps.webaspect.genplan.mps similarity index 100% rename from mps/solutions/org.modelix.mps.webaspect.genplan/models/org.modelix.mps.webaspect.genplan.mps rename to mps/modules/org.modelix.mps.webaspect.genplan/models/org.modelix.mps.webaspect.genplan.mps diff --git a/mps/solutions/org.modelix.mps.webaspect.genplan/org.modelix.mps.webaspect.genplan.msd b/mps/modules/org.modelix.mps.webaspect.genplan/org.modelix.mps.webaspect.genplan.msd similarity index 100% rename from mps/solutions/org.modelix.mps.webaspect.genplan/org.modelix.mps.webaspect.genplan.msd rename to mps/modules/org.modelix.mps.webaspect.genplan/org.modelix.mps.webaspect.genplan.msd diff --git a/mps/org.modelix.mps.editor.ssr.stubs/org.modelix.mps.editor.ssr.stubs.msd b/mps/org.modelix.mps.editor.ssr.stubs/org.modelix.mps.editor.ssr.stubs.msd deleted file mode 100644 index 0f197c95..00000000 --- a/mps/org.modelix.mps.editor.ssr.stubs/org.modelix.mps.editor.ssr.stubs.msd +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 6354ebe7-c22a-4a0f-ac54-50b52ab9b065(JDK) - b50d89c0-0fb9-4105-b652-222148c26a9b(jetbrains.mps.kotlin.stdlib) - d2c5f31a-2aac-440d-a15c-2d8cba42bf3d(jetbrains.mps.kotlin.stdlib.jvm) - 6944825c-ddc2-4099-8cc7-5e6dbbf7f0be(jetbrains.mps.kotlin.stubs) - 8865b7a8-5271-43d3-884c-6fd1d9cfdd34(MPS.OpenAPI) - - - - - - - - - - - - - - - - - - - - - diff --git a/mps/org.modelix.mps.notation.runtime/org.modelix.mps.notation.runtime.msd b/mps/org.modelix.mps.notation.runtime/org.modelix.mps.notation.runtime.msd deleted file mode 100644 index 79b6144d..00000000 --- a/mps/org.modelix.mps.notation.runtime/org.modelix.mps.notation.runtime.msd +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/mps/stubs-template/org.modelix.mps.editor.ssr.stubs/org.modelix.mps.editor.ssr.stubs.msd b/mps/stubs-template/org.modelix.mps.editor.ssr.stubs/org.modelix.mps.editor.ssr.stubs.msd new file mode 100644 index 00000000..3f83bd49 --- /dev/null +++ b/mps/stubs-template/org.modelix.mps.editor.ssr.stubs/org.modelix.mps.editor.ssr.stubs.msd @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 6354ebe7-c22a-4a0f-ac54-50b52ab9b065(JDK) + b50d89c0-0fb9-4105-b652-222148c26a9b(jetbrains.mps.kotlin.stdlib) + d2c5f31a-2aac-440d-a15c-2d8cba42bf3d(jetbrains.mps.kotlin.stdlib.jvm) + 6944825c-ddc2-4099-8cc7-5e6dbbf7f0be(jetbrains.mps.kotlin.stubs) + 8865b7a8-5271-43d3-884c-6fd1d9cfdd34(MPS.OpenAPI) + + + + + + + + + + + + + + + + + + + + + diff --git a/projectional-editor-ssr-mps-languages/build.gradle.kts b/projectional-editor-ssr-mps-languages/build.gradle.kts new file mode 100644 index 00000000..2399ebd7 --- /dev/null +++ b/projectional-editor-ssr-mps-languages/build.gradle.kts @@ -0,0 +1,149 @@ +import org.modelix.mpsHomeDir +import org.modelix.mpsPluginsDir + +buildscript { + dependencies { + classpath(coreLibs.semver) + classpath(libs.modelix.build.tools.lib) + } +} + +plugins { + kotlin("jvm") + id("org.jetbrains.intellij") version "1.17.3" +} + +kotlin { + jvmToolchain(11) +} + +dependencies { + testImplementation(project(":projectional-editor-ssr-mps")) + testImplementation(project(":projectional-editor")) + testImplementation(libs.modelix.mps.model.adapters) +} + +intellij { + localPath = mpsHomeDir.map { it.asFile.absolutePath } + instrumentCode = false + plugins = listOf(project(":projectional-editor-ssr-mps")) + listOf( +// "Git4Idea", +// "Subversion", +// "com.intellij.copyright", +// "com.intellij.laf.macos", +// "com.intellij.platform.images", +// "com.intellij.properties", +// "com.intellij.properties.bundle.editor", +// "com.intellij.tasks", +// "com.jetbrains.changeReminder", +// "jetbrains.jetpad", +// "jetbrains.mps.build", +// "jetbrains.mps.build.ui", +// "jetbrains.mps.console", + "jetbrains.mps.core", +// "jetbrains.mps.debugger.api", +// "jetbrains.mps.debugger.java", +// "jetbrains.mps.editor.contextActions", +// "jetbrains.mps.editor.diagram", +// "jetbrains.mps.editor.spellcheck", +// "jetbrains.mps.editor.tooltips", +// "jetbrains.mps.execution.api", +// "jetbrains.mps.execution.configurations", +// "jetbrains.mps.execution.languages", +// "jetbrains.mps.git4idea.stubs", +// "jetbrains.mps.ide", +// "jetbrains.mps.ide.devkit", +// "jetbrains.mps.ide.httpsupport", +// "jetbrains.mps.ide.java", +// "jetbrains.mps.ide.make", +// "jetbrains.mps.ide.memtool", +// "jetbrains.mps.ide.migration.workbench", +// "jetbrains.mps.ide.modelchecker", +// "jetbrains.mps.ide.mpsmigration", + "jetbrains.mps.kotlin", +// "jetbrains.mps.navbar", +// "jetbrains.mps.rcp", +// "jetbrains.mps.samples", +// "jetbrains.mps.testing", +// "jetbrains.mps.tool.make", +// "jetbrains.mps.trove", +// "jetbrains.mps.vcs", +// "org.intellij.plugins.markdown", +// "org.jetbrains.plugins.github", +// "org.jetbrains.settingsRepository", + +// "com.intellij", +// "com.jetbrains.sh", +// "org.jetbrains.plugins.terminal", + ) +} + +tasks { + compileKotlin { + kotlinOptions { + apiVersion = "1.6" + } + } + patchPluginXml { + sinceBuild.set("232") + untilBuild.set("233.*") + } + + buildSearchableOptions { + enabled = false + } + +// signPlugin { +// certificateChain.set(System.getenv("CERTIFICATE_CHAIN")) +// privateKey.set(System.getenv("PRIVATE_KEY")) +// password.set(System.getenv("PRIVATE_KEY_PASSWORD")) +// } +// +// publishPlugin { +// token.set(System.getenv("PUBLISH_TOKEN")) +// } + + runIde { + autoReloadPlugins.set(true) + } + + val pluginDir = mpsPluginsDir + if (pluginDir != null) { + create("installMpsPlugin") { + dependsOn(prepareSandbox) + from(project.layout.buildDirectory.dir("idea-sandbox/plugins/${project.name}")) + into(pluginDir.resolve(project.name)) + } + } + + withType().configureEach { + dependsOn(project(":mps").tasks.named("packageMpsPublications")) + + intoChild(pluginName.map { "$it/META-INF" }) + .from(project.layout.projectDirectory.file("src/main/resources/META-INF")) + .exclude("plugin.xml") + intoChild(pluginName.map { "$it/META-INF" }) + .from(patchPluginXml.flatMap { it.outputFiles }) + + listOf("editor-languages", "baseLanguage-notation").forEach { publicationName -> + intoChild(pluginName.map { "$it/languages" }) + .from(zipTree({ project(":mps").layout.buildDirectory.file("mpsbuild/publications/$publicationName.zip") })) + .eachFile { + path = path.replaceFirst("packaged-modules/", "") + } + } + } +} + +group = "org.modelix.mps" + +publishing { + publications { + create("maven") { + artifactId = "projectional-editor-languages-plugin" + artifact(tasks.buildPlugin) { + extension = "zip" + } + } + } +} diff --git a/projectional-editor-ssr-mps-languages/src/main/resources/META-INF/plugin.xml b/projectional-editor-ssr-mps-languages/src/main/resources/META-INF/plugin.xml new file mode 100644 index 00000000..a82cc8b8 --- /dev/null +++ b/projectional-editor-ssr-mps-languages/src/main/resources/META-INF/plugin.xml @@ -0,0 +1,28 @@ + + + + org.modelix.mps.editor.languages + + + Notation Language for the Modelix Web Editor + + + itemis AG + + + + + + com.intellij.modules.mps + jetbrains.mps.core + org.modelix.mps.editor + + + + diff --git a/projectional-editor-ssr-mps-languages/src/main/resources/META-INF/pluginIcon.svg b/projectional-editor-ssr-mps-languages/src/main/resources/META-INF/pluginIcon.svg new file mode 100644 index 00000000..ddaf89cb --- /dev/null +++ b/projectional-editor-ssr-mps-languages/src/main/resources/META-INF/pluginIcon.svg @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/projectional-editor-ssr-mps-languages/src/test/kotlin/org/modelix/editor/ssr/mps/BaseLanguageTests.kt b/projectional-editor-ssr-mps-languages/src/test/kotlin/org/modelix/editor/ssr/mps/BaseLanguageTests.kt new file mode 100644 index 00000000..df211fd3 --- /dev/null +++ b/projectional-editor-ssr-mps-languages/src/test/kotlin/org/modelix/editor/ssr/mps/BaseLanguageTests.kt @@ -0,0 +1,73 @@ +package org.modelix.editor.ssr.mps + +import org.modelix.editor.CaretSelection +import org.modelix.editor.EditorEngine +import org.modelix.editor.JSKeyboardEvent +import org.modelix.editor.JSKeyboardEventType +import org.modelix.editor.KnownKeys +import org.modelix.editor.NodeCellReference +import org.modelix.editor.getVisibleText +import org.modelix.editor.lastLeaf +import org.modelix.editor.layoutable +import org.modelix.incremental.IncrementalEngine +import org.modelix.model.api.INode +import org.modelix.model.mpsadapters.MPSNode + +/** + * Test editor for MPS baseLanguage ClassConcept + */ +class BaseLanguageTests : TestBase("SimpleProject") { + fun `test inserting new line into class`() { + lateinit var rootNode: MPSNode + lateinit var firstMethod: INode + readAction { + val solution = mpsProject.projectModules.first { it.moduleName == "Solution1" } + val model = solution.models.first() + rootNode = model.rootNodes.first().let { MPSNode(it) } + firstMethod = rootNode.allChildren.first { it.concept?.getShortName() == "InstanceMethodDeclaration" } + } + + fun memberConcepts() = readAction { + rootNode.allChildren.filter { it.getContainmentLink()?.getSimpleName() == "member" }.map { it.concept?.getShortName() } + } + assertEquals(listOf("InstanceMethodDeclaration"), memberConcepts()) + + val incrementalEngine = IncrementalEngine() + val editorEngine = EditorEngine(incrementalEngine) + val mpsIntegration = EditorIntegrationForMPS(editorEngine) + mpsIntegration.init(mpsProject.repository) + + val editor = editorEngine.editNode(rootNode) + fun assertEditorText(expected: String) { + assertEquals(expected, editor.getRootCell().layout.toString()) + } + + assertEditorText( + """ + class Class1 { + public void method1() { + + } + } + """.trimIndent(), + ) + + val firstMethodCell = editor.resolveCell(NodeCellReference(firstMethod.reference)).first() + val lastLeafCell = firstMethodCell.lastLeaf() + assertEquals("}", lastLeafCell.getVisibleText()) + + editor.changeSelection(CaretSelection(lastLeafCell.layoutable()!!, -1)) + editor.processKeyEvent(JSKeyboardEvent(JSKeyboardEventType.KEYDOWN, KnownKeys.Enter)) + + assertEditorText( + """ + class Class1 { + public void method1() { + + } + + } + """.trimIndent(), + ) + } +} diff --git a/projectional-editor-ssr-mps-languages/src/test/kotlin/org/modelix/editor/ssr/mps/TestBase.kt b/projectional-editor-ssr-mps-languages/src/test/kotlin/org/modelix/editor/ssr/mps/TestBase.kt new file mode 100644 index 00000000..7d6e9e6f --- /dev/null +++ b/projectional-editor-ssr-mps-languages/src/test/kotlin/org/modelix/editor/ssr/mps/TestBase.kt @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2023. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.modelix.editor.ssr.mps + +import com.intellij.ide.impl.OpenProjectTask +import com.intellij.openapi.Disposable +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.project.ProjectManager +import com.intellij.openapi.project.ex.ProjectManagerEx +import com.intellij.openapi.util.Disposer +import com.intellij.testFramework.TestApplicationManager +import com.intellij.testFramework.UsefulTestCase +import jetbrains.mps.ide.ThreadUtils +import jetbrains.mps.ide.project.ProjectHelper +import jetbrains.mps.project.MPSProject +import java.io.File +import java.nio.file.Files +import java.nio.file.Path +import kotlin.io.path.ExperimentalPathApi +import kotlin.io.path.absolute +import kotlin.io.path.deleteRecursively + +/** + * Based on org.jetbrains.uast.test.env.AbstractLargeProjectTest + */ +@Suppress("removal") +abstract class TestBase(val testDataName: String?) : UsefulTestCase() { + init { + // workaround for MPS 2023.3 failing to start in test mode + System.setProperty("intellij.platform.load.app.info.from.resources", "true") + } + + protected lateinit var project: Project + + override fun runInDispatchThread() = false + + override fun setUp() { + super.setUp() + TestApplicationManager.getInstance() + project = openTestProject() + } + + override fun tearDown() { + super.tearDown() + } + + @OptIn(ExperimentalPathApi::class) + private fun openTestProject(): Project { + val projectDirParent = Path.of("build", "test-projects").absolute() + projectDirParent.toFile().mkdirs() + val projectDir = Files.createTempDirectory(projectDirParent, "mps-project") + projectDir.deleteRecursively() + projectDir.toFile().mkdirs() + projectDir.toFile().deleteOnExit() + val project = if (testDataName != null) { + val sourceDir = File("testdata/$testDataName") + sourceDir.copyRecursively(projectDir.toFile(), overwrite = true) + ProjectManagerEx.getInstanceEx().openProject(projectDir, OpenProjectTask())!! + } else { + ProjectManagerEx.getInstanceEx().newProject(projectDir, OpenProjectTask())!! + } + + disposeOnTearDownInEdt { ProjectManager.getInstance().closeAndDispose(project) } + + ApplicationManager.getApplication().invokeAndWait { + // empty - openTestProject executed not in EDT, so, invokeAndWait just forces + // processing of all events that were queued during project opening + } + + return project + } + + private fun disposeOnTearDownInEdt(runnable: Runnable) { + Disposer.register( + testRootDisposable, + Disposable { + ApplicationManager.getApplication().invokeAndWait(runnable) + }, + ) + } + + protected val mpsProject: MPSProject get() { + return checkNotNull(ProjectHelper.fromIdeaProject(project)) { "MPS project not loaded" } + } + + protected fun writeAction(body: () -> R): R { + return mpsProject.modelAccess.computeWriteAction(body) + } + + protected fun writeActionOnEdt(body: () -> R): R { + return onEdt { writeAction { body() } } + } + + protected fun onEdt(body: () -> R): R { + var result: R? = null + ThreadUtils.runInUIThreadAndWait { + result = body() + } + return result as R + } + + protected fun readAction(body: () -> R): R { + var result: R? = null + mpsProject.modelAccess.runReadAction { + result = body() + } + return result as R + } +} diff --git a/projectional-editor-ssr-mps-languages/src/test/kotlin/org/modelix/editor/ssr/mps/TestFrameworkSetupTest.kt b/projectional-editor-ssr-mps-languages/src/test/kotlin/org/modelix/editor/ssr/mps/TestFrameworkSetupTest.kt new file mode 100644 index 00000000..05251c41 --- /dev/null +++ b/projectional-editor-ssr-mps-languages/src/test/kotlin/org/modelix/editor/ssr/mps/TestFrameworkSetupTest.kt @@ -0,0 +1,35 @@ +package org.modelix.editor.ssr.mps + +import com.intellij.ide.plugins.PluginManager +import jetbrains.mps.classloading.ModuleClassLoader +import jetbrains.mps.module.ReloadableModuleBase + +/** + * Check that the environment is initialized properly and all plugins and modules are loaded properly. + */ +class TestFrameworkSetupTest : TestBase("SimpleProject") { + fun `test plugins loaded`() { + // IDEA plugins + assertContainsElements( + PluginManager.getPlugins().map { it.pluginId.idString }.sorted(), + "org.modelix.mps.editor.languages", + "org.modelix.mps.editor", + ) + + // MPS modules inside those IDEA plugins + readAction { + assertContainsElements( + mpsProject.repository.modules.map { it.moduleName ?: "" }.sorted(), + "org.modelix.mps.editor.ssr.stubs", + "org.modelix.mps.notation.impl.baseLanguage", + ) + } + } + + fun `test module is valid for classloading`() { + readAction { + val module = mpsProject.repository.modules.filterIsInstance().first { it.moduleName == "org.modelix.mps.notation.impl.baseLanguage" } + assertInstanceOf(module.classLoader, ModuleClassLoader::class.java) + } + } +} diff --git a/projectional-editor-ssr-mps-languages/testdata/SimpleProject/.mps/migration.xml b/projectional-editor-ssr-mps-languages/testdata/SimpleProject/.mps/migration.xml new file mode 100644 index 00000000..d512ce45 --- /dev/null +++ b/projectional-editor-ssr-mps-languages/testdata/SimpleProject/.mps/migration.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/projectional-editor-ssr-mps-languages/testdata/SimpleProject/.mps/modules.xml b/projectional-editor-ssr-mps-languages/testdata/SimpleProject/.mps/modules.xml new file mode 100644 index 00000000..d24dfe06 --- /dev/null +++ b/projectional-editor-ssr-mps-languages/testdata/SimpleProject/.mps/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/projectional-editor-ssr-mps-languages/testdata/SimpleProject/.mps/vcs.xml b/projectional-editor-ssr-mps-languages/testdata/SimpleProject/.mps/vcs.xml new file mode 100644 index 00000000..fbbc5665 --- /dev/null +++ b/projectional-editor-ssr-mps-languages/testdata/SimpleProject/.mps/vcs.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/projectional-editor-ssr-mps-languages/testdata/SimpleProject/.mps/workspace.xml b/projectional-editor-ssr-mps-languages/testdata/SimpleProject/.mps/workspace.xml new file mode 100644 index 00000000..3a6ee076 --- /dev/null +++ b/projectional-editor-ssr-mps-languages/testdata/SimpleProject/.mps/workspace.xml @@ -0,0 +1,181 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + { + "associatedIndex": 1 +} + + + + + + + + + + + + + + + + + + + + + + + + + { + "keyToString": { + "RunOnceActivity.OpenProjectViewOnStart": "true", + "RunOnceActivity.ShowReadmeOnStart": "true", + "settings.editor.selected.configurable": "preferences.projectLibraryManager" + } +} + + + + + 1714286490472 + + + + + + + + + + + diff --git a/projectional-editor-ssr-mps-languages/testdata/SimpleProject/solutions/Solution1/Solution1.msd b/projectional-editor-ssr-mps-languages/testdata/SimpleProject/solutions/Solution1/Solution1.msd new file mode 100644 index 00000000..f015948f --- /dev/null +++ b/projectional-editor-ssr-mps-languages/testdata/SimpleProject/solutions/Solution1/Solution1.msd @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/projectional-editor-ssr-mps-languages/testdata/SimpleProject/solutions/Solution1/models/Solution1.model1.mps b/projectional-editor-ssr-mps-languages/testdata/SimpleProject/solutions/Solution1/models/Solution1.model1.mps new file mode 100644 index 00000000..64b0724b --- /dev/null +++ b/projectional-editor-ssr-mps-languages/testdata/SimpleProject/solutions/Solution1/models/Solution1.model1.mps @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/projectional-editor-ssr-mps/build.gradle.kts b/projectional-editor-ssr-mps/build.gradle.kts index cf86434f..cb5a30e1 100644 --- a/projectional-editor-ssr-mps/build.gradle.kts +++ b/projectional-editor-ssr-mps/build.gradle.kts @@ -7,6 +7,7 @@ import org.modelix.mpsPluginsDir buildscript { dependencies { classpath(coreLibs.semver) + classpath(libs.modelix.build.tools.lib) } } @@ -88,7 +89,7 @@ tasks { doLast { val ownJar: File = prepareSandbox.get().pluginJar.get().asFile - val originalFile = project(":mps").layout.projectDirectory.file("$stubsSolutionName/$stubsSolutionName.msd").asFile + val originalFile = project(":mps").layout.projectDirectory.file("stubs-template/$stubsSolutionName/$stubsSolutionName.msd").asFile val xml = originalFile.inputStream().use { JDOMUtil.loadDocument(it) } val modelRoot = xml.descendants.filterIsInstance().first { it.name == "modelRoot" } @@ -131,16 +132,11 @@ tasks { dependsOn(packageStubsSolution) intoChild(pluginName.map { "$it/languages" }) .from(packageStubsSolution.map { it.archiveFile }) - - if ("true" == project.findProperty("ciBuild")) { - // packaging the MPS modules during development would make them all read-only - dependsOn(project(":mps").tasks.named("packageMpsPublications")) - intoChild(pluginName.map { "$it/languages" }) - .from(zipTree({ project(":mps").layout.buildDirectory.file("mpsbuild/publications/editor-languages.zip") })) - .eachFile { - path = path.replaceFirst("packaged-modules/", "") - } - } + intoChild(pluginName.map { "$it/META-INF" }) + .from(project.layout.projectDirectory.file("src/main/resources/META-INF")) + .exclude("plugin.xml") + intoChild(pluginName.map { "$it/META-INF" }) + .from(patchPluginXml.flatMap { it.outputFiles }) } } diff --git a/projectional-editor-ssr-mps/src/main/kotlin/org/modelix/editor/ssr/mps/EditorIntegrationForMPS.kt b/projectional-editor-ssr-mps/src/main/kotlin/org/modelix/editor/ssr/mps/EditorIntegrationForMPS.kt new file mode 100644 index 00000000..9f79cf30 --- /dev/null +++ b/projectional-editor-ssr-mps/src/main/kotlin/org/modelix/editor/ssr/mps/EditorIntegrationForMPS.kt @@ -0,0 +1,34 @@ +package org.modelix.editor.ssr.mps + +import org.jetbrains.mps.openapi.module.SRepository +import org.modelix.constraints.ConstraintsAspect +import org.modelix.editor.EditorEngine +import org.modelix.model.api.ILanguageRepository +import org.modelix.model.mpsadapters.MPSChangeTranslator +import org.modelix.model.mpsadapters.MPSLanguageRepository +import org.modelix.scopes.ScopeAspect + +class EditorIntegrationForMPS(val editorEngine: EditorEngine) { + private var aspectsFromMPS: LanguageAspectsFromMPSModules? = null + private var mpsChangeTranslator: MPSChangeTranslator? = null + private var mpsLanguageRepository: MPSLanguageRepository? = null + + fun init(repository: SRepository) { + mpsLanguageRepository = MPSLanguageRepository(repository) + ILanguageRepository.register(mpsLanguageRepository!!) + mpsChangeTranslator = MPSChangeTranslator() + mpsChangeTranslator!!.start(repository) + aspectsFromMPS = LanguageAspectsFromMPSModules(repository) + ScopeAspect.registerScopeProvider(MPSScopeProvider) + ConstraintsAspect.checkers.add(MPSConstraints) + editorEngine.addRegistry(aspectsFromMPS!!) + } + + fun dispose() { + mpsLanguageRepository?.let { ILanguageRepository.unregister(it) } + mpsLanguageRepository = null + + ScopeAspect.unregisterScopeProvider(MPSScopeProvider) + ConstraintsAspect.checkers.remove(MPSConstraints) + } +} diff --git a/projectional-editor-ssr-mps/src/main/kotlin/org/modelix/editor/ssr/mps/ModelixSSRServerForMPS.kt b/projectional-editor-ssr-mps/src/main/kotlin/org/modelix/editor/ssr/mps/ModelixSSRServerForMPS.kt index c0f91507..c47d5d70 100644 --- a/projectional-editor-ssr-mps/src/main/kotlin/org/modelix/editor/ssr/mps/ModelixSSRServerForMPS.kt +++ b/projectional-editor-ssr-mps/src/main/kotlin/org/modelix/editor/ssr/mps/ModelixSSRServerForMPS.kt @@ -54,7 +54,6 @@ import org.jetbrains.mps.openapi.language.SContainmentLink import org.jetbrains.mps.openapi.language.SProperty import org.jetbrains.mps.openapi.language.SReferenceLink import org.jetbrains.mps.openapi.model.SNode -import org.modelix.constraints.ConstraintsAspect import org.modelix.constraints.IConstraintViolation import org.modelix.constraints.IConstraintsChecker import org.modelix.editor.ExistingNode @@ -64,23 +63,19 @@ import org.modelix.editor.ssr.server.ModelixSSRServer import org.modelix.model.api.BuiltinLanguages import org.modelix.model.api.IChildLink import org.modelix.model.api.IConcept -import org.modelix.model.api.ILanguageRepository import org.modelix.model.api.INode import org.modelix.model.api.IProperty import org.modelix.model.api.IReferenceLink import org.modelix.model.api.NodeReference import org.modelix.model.api.runSynchronized -import org.modelix.model.mpsadapters.MPSChangeTranslator import org.modelix.model.mpsadapters.MPSChildLink import org.modelix.model.mpsadapters.MPSConcept -import org.modelix.model.mpsadapters.MPSLanguageRepository import org.modelix.model.mpsadapters.MPSNode import org.modelix.model.mpsadapters.MPSProperty import org.modelix.model.mpsadapters.MPSReferenceLink import org.modelix.model.mpsadapters.MPSRepositoryAsNode import org.modelix.scopes.IScope import org.modelix.scopes.IScopeProvider -import org.modelix.scopes.ScopeAspect import java.net.URLEncoder import java.nio.charset.StandardCharsets import java.time.Duration @@ -103,10 +98,8 @@ class ModelixSSRServerForMPS : Disposable { private var ssrServer: ModelixSSRServer? = null private var ktorServer: ApplicationEngine? = null - private var aspectsFromMPS: LanguageAspectsFromMPSModules? = null - private var mpsChangeTranslator: MPSChangeTranslator? = null - private var mpsLanguageRepository: MPSLanguageRepository? = null private val projects: MutableSet = Collections.synchronizedSet(HashSet()) + private var mpsIntegration: EditorIntegrationForMPS? = null fun registerProject(project: Project) { projects.add(project) @@ -135,17 +128,10 @@ class ModelixSSRServerForMPS : Disposable { println("starting modelix SSR server") - val repository = getMPSProjects().first().repository - mpsLanguageRepository = MPSLanguageRepository(repository) - ILanguageRepository.register(mpsLanguageRepository!!) - mpsChangeTranslator = MPSChangeTranslator() - mpsChangeTranslator!!.start(repository) val ssrServer = ModelixSSRServer((getRootNode() ?: return).getArea()) this.ssrServer = ssrServer - aspectsFromMPS = LanguageAspectsFromMPSModules(repository) - ScopeAspect.registerScopeProvider(MPSScopeProvider) - ConstraintsAspect.checkers.add(MPSConstraints) - ssrServer.editorEngine.addRegistry(aspectsFromMPS!!) + mpsIntegration = EditorIntegrationForMPS(ssrServer.editorEngine) + mpsIntegration!!.init(getMPSProjects().first().repository) ktorServer = embeddedServer(Netty, port = 43593) { initKtorServer(ssrServer) } @@ -255,12 +241,8 @@ class ModelixSSRServerForMPS : Disposable { ktorServer = null ssrServer?.dispose() ssrServer = null - - mpsLanguageRepository?.let { ILanguageRepository.unregister(it) } - mpsLanguageRepository = null - - ScopeAspect.unregisterScopeProvider(MPSScopeProvider) - ConstraintsAspect.checkers.remove(MPSConstraints) + mpsIntegration?.dispose() + mpsIntegration = null } } diff --git a/projectional-editor-ssr-mps/src/main/resources/META-INF/pluginIcon.svg b/projectional-editor-ssr-mps/src/main/resources/META-INF/pluginIcon.svg index 1555925e..ddaf89cb 100644 --- a/projectional-editor-ssr-mps/src/main/resources/META-INF/pluginIcon.svg +++ b/projectional-editor-ssr-mps/src/main/resources/META-INF/pluginIcon.svg @@ -1,27 +1,66 @@ - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/projectional-editor-ssr-mps/testdata/SimpleProject/.mps/modules.xml b/projectional-editor-ssr-mps/testdata/SimpleProject/.mps/modules.xml new file mode 100644 index 00000000..47f90476 --- /dev/null +++ b/projectional-editor-ssr-mps/testdata/SimpleProject/.mps/modules.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/projectional-editor-ssr-mps/testdata/SimpleProject/.mps/vcs.xml b/projectional-editor-ssr-mps/testdata/SimpleProject/.mps/vcs.xml new file mode 100644 index 00000000..fbbc5665 --- /dev/null +++ b/projectional-editor-ssr-mps/testdata/SimpleProject/.mps/vcs.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/projectional-editor/src/commonMain/kotlin/org/modelix/editor/EditorEngine.kt b/projectional-editor/src/commonMain/kotlin/org/modelix/editor/EditorEngine.kt index c8c02263..a3b082c8 100644 --- a/projectional-editor/src/commonMain/kotlin/org/modelix/editor/EditorEngine.kt +++ b/projectional-editor/src/commonMain/kotlin/org/modelix/editor/EditorEngine.kt @@ -119,7 +119,7 @@ class EditorEngine(incrementalEngine: IncrementalEngine? = null) { } } - private fun resolveConceptEditor(concept: IConcept?): List { + fun resolveConceptEditor(concept: IConcept?): List { if (concept == null) return listOf(defaultConceptEditor) val editors = concept.getAllConcepts().firstNotNullOfOrNull { superConcept -> val conceptReference = superConcept.getReference() diff --git a/settings.gradle.kts b/settings.gradle.kts index 0ebdc8c4..c7e58dd3 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -35,4 +35,5 @@ include("mps") include("projectional-editor-ssr-client") include("projectional-editor-ssr-common") include("projectional-editor-ssr-mps") +include("projectional-editor-ssr-mps-languages") include("projectional-editor-ssr-server")