Skip to content

Commit d8a2348

Browse files
committed
improved caret positioning
1 parent a1085fe commit d8a2348

File tree

3 files changed

+83
-12
lines changed

3 files changed

+83
-12
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
package org.modelix.editor
15+
16+
import org.modelix.model.api.INode
17+
18+
data class CaretPositionPolicy(
19+
private val avoidedCellRefs: Set<CellReference>,
20+
private val preferredCellRefs: Set<CellReference>
21+
) {
22+
constructor(preferredCellRef: CellReference) : this(emptySet(), setOf(preferredCellRef))
23+
constructor(preferredNode: INode) : this(NodeCellReference(preferredNode.reference))
24+
25+
fun prefer(cellReference: CellReference) = copy(preferredCellRefs = preferredCellRefs + cellReference)
26+
fun avoid(cellReference: CellReference) = copy(avoidedCellRefs = avoidedCellRefs + cellReference)
27+
28+
fun merge(other: CaretPositionPolicy) = CaretPositionPolicy(
29+
avoidedCellRefs + other.avoidedCellRefs,
30+
preferredCellRefs + other.preferredCellRefs
31+
)
32+
33+
fun getBestSelection(editor: EditorComponent): CaretSelection? {
34+
val candidates = preferredCellRefs
35+
.flatMap { editor.resolveCell(it) }
36+
.flatMap { it.descendantsAndSelf() }
37+
.mapNotNull { editor.resolveLayoutable(it) }
38+
39+
val best = candidates
40+
.sortedByDescending { it.cell.ancestors(true).any { isPropertyCell(it) } }
41+
.sortedBy { it.cell.ancestors(true).filter { isAvoided(it) }.count() }
42+
.firstOrNull() ?: return null
43+
44+
return CaretSelection(best, (best.cell.getSelectableText() ?: "").length)
45+
}
46+
47+
private fun isAvoided(cell: Cell) = cell.data.cellReferences.intersect(avoidedCellRefs).isNotEmpty()
48+
private fun isPropertyCell(cell: Cell) = cell.data.cellReferences.any { it is PropertyCellReference }
49+
}
50+
51+
enum class CaretPositionType {
52+
START,
53+
END
54+
}

editor-runtime/src/commonMain/kotlin/org/modelix/editor/CellReference.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ package org.modelix.editor
33
import org.modelix.metamodel.ITypedNode
44
import org.modelix.metamodel.untyped
55
import org.modelix.metamodel.untypedReference
6+
import org.modelix.model.api.IChildLink
67
import org.modelix.model.api.INode
78
import org.modelix.model.api.INodeReference
89
import org.modelix.model.api.IProperty
10+
import org.modelix.model.api.IReferenceLink
911

1012
/**
1113
* A cell can have multiple CellReferences. Multiple CellReferences can resolve to the same cell.
@@ -35,3 +37,6 @@ fun EditorComponent.resolveNodeCell(node: INode): Cell? =
3537

3638
fun EditorComponent.resolveNodeCell(node: ITypedNode): Cell? =
3739
resolveNodeCell(node.untypedReference())
40+
41+
data class ChildNodeCellReference(val parentNodeRef: INodeReference, val link: IChildLink, val index: Int = 0) : CellReference()
42+
data class ReferencedNodeCellReference(val sourceNodeRef: INodeReference, val link: IReferenceLink) : CellReference()

editor-runtime/src/commonMain/kotlin/org/modelix/editor/CellTemplate.kt

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -144,9 +144,10 @@ class ConstantCellTemplate<NodeT : ITypedNode, ConceptT : ITypedConcept>(concept
144144
val wrapper = nodeToWrap.getParent()!!.getOrCreateNode(null).addNewChild(nodeToWrap.getContainmentLink()!!, nodeToWrap.index(), concept)
145145
wrapper.moveChild(wrappingLink, 0, nodeToWrap.getOrCreateNode(null))
146146
editor.selectAfterUpdate {
147-
val cell = editor.resolveCell(createCellReference(wrapper)).firstOrNull() ?: return@selectAfterUpdate null
148-
val layoutable = cell.layoutable() ?: return@selectAfterUpdate null
149-
CaretSelection(layoutable, layoutable.getLength())
147+
CaretPositionPolicy(wrapper)
148+
.avoid(ChildNodeCellReference(wrapper.reference, wrappingLink))
149+
.avoid(createCellReference(wrapper))
150+
.getBestSelection(editor)
150151
}
151152
}
152153

@@ -173,7 +174,11 @@ class ConstantCellTemplate<NodeT : ITypedNode, ConceptT : ITypedConcept>(concept
173174
}
174175

175176
override fun execute(editor: EditorComponent) {
176-
location.replaceNode(concept)
177+
val newNode = location.replaceNode(concept)
178+
editor.selectAfterUpdate {
179+
CaretPositionPolicy(newNode)
180+
.getBestSelection(editor)
181+
}
177182
}
178183
}
179184
}
@@ -273,9 +278,8 @@ open class PropertyCellTemplate<NodeT : ITypedNode, ConceptT : ITypedConcept>(co
273278
val node = location.getOrCreateNode(concept)
274279
node.setPropertyValue(property, value)
275280
editor.selectAfterUpdate {
276-
val cell = editor.resolveCell(createCellReference(node)).firstOrNull() ?: return@selectAfterUpdate null
277-
val layoutable = cell.layoutable() ?: return@selectAfterUpdate null
278-
CaretSelection(layoutable, layoutable.getLength())
281+
CaretPositionPolicy(createCellReference(node))
282+
.getBestSelection(editor)
279283
}
280284
}
281285
}
@@ -298,7 +302,11 @@ class ReferenceCellTemplate<NodeT : ITypedNode, ConceptT : ITypedConcept, Target
298302
val link: GeneratedReferenceLink<TargetNodeT, TargetConceptT>,
299303
val presentation: TargetNodeT.() -> String?
300304
) : CellTemplate<NodeT, ConceptT>(concept), IGrammarSymbol {
301-
override fun createCell(context: CellCreationContext, node: NodeT): CellData = TextCellData(getText(node), "<no ${link.name}>")
305+
override fun createCell(context: CellCreationContext, node: NodeT): CellData {
306+
val cell = TextCellData(getText(node), "<no ${link.name}>")
307+
cell.cellReferences += ReferencedNodeCellReference(node.untypedReference(), link)
308+
return cell
309+
}
302310
private fun getText(node: NodeT): String = getTargetNode(node)?.let(presentation) ?: ""
303311
private fun getTargetNode(sourceNode: NodeT): TargetNodeT? {
304312
return sourceNode.unwrap().getReferenceTarget(link)?.typedUnsafe()
@@ -322,9 +330,8 @@ class ReferenceCellTemplate<NodeT : ITypedNode, ConceptT : ITypedConcept, Target
322330
val sourceNode = location.getOrCreateNode(concept)
323331
sourceNode.setReferenceTarget(link, target)
324332
editor.selectAfterUpdate {
325-
val cell = editor.resolveCell(createCellReference(sourceNode)).firstOrNull() ?: return@selectAfterUpdate null
326-
val layoutable = cell.layoutable() ?: return@selectAfterUpdate null
327-
CaretSelection(layoutable, layoutable.getLength())
333+
CaretPositionPolicy(createCellReference(sourceNode))
334+
.getBestSelection(editor)
328335
}
329336
}
330337
}
@@ -351,13 +358,17 @@ class ChildCellTemplate<NodeT : ITypedNode, ConceptT : ITypedConcept>(
351358
val substitutionPlaceholder = context.editorState.substitutionPlaceholderPositions[createCellReference(node)]
352359
val placeholderIndex = substitutionPlaceholder?.index?.coerceIn(0..childNodes.size) ?: 0
353360
val addSubstitutionPlaceholder: (Int) -> Unit = { index ->
354-
val placeholderText = if (childNodes.isEmpty()) "<no ${link.name}>" else "<choose ${link.name}>"
361+
val isDefaultPlaceholder = childNodes.isEmpty()
362+
val placeholderText = if (isDefaultPlaceholder) "<no ${link.name}>" else "<choose ${link.name}>"
355363
val placeholder = TextCellData("", placeholderText)
356364
placeholder.properties[CellActionProperties.substitute] =
357365
ReplaceNodeActionProvider(NonExistingChild(node.untyped().toNonExisting(), link, index)).after {
358366
context.editorState.substitutionPlaceholderPositions.remove(createCellReference(node))
359367
}
360368
placeholder.cellReferences.add(PlaceholderCellReference(createCellReference(node)))
369+
if (isDefaultPlaceholder) {
370+
placeholder.cellReferences += ChildNodeCellReference(node.untypedReference(), link, index)
371+
}
361372
cell.addChild(placeholder)
362373
}
363374
val addInsertActionCell: (Int) -> Unit = { index ->
@@ -382,6 +393,7 @@ class ChildCellTemplate<NodeT : ITypedNode, ConceptT : ITypedConcept>(
382393
//child.parent?.removeChild(child) // child may be cached and is still attached to the old parent
383394
val wrapper = CellData() // allow setting properties by the parent, because the cell is already frozen
384395
wrapper.addChild(child)
396+
wrapper.cellReferences += ChildNodeCellReference(node.untypedReference(), link, index)
385397
cell.addChild(wrapper)
386398
}
387399
if (substitutionPlaceholder != null && placeholderIndex == childNodes.size) {

0 commit comments

Comments
 (0)