From c7902c5b5499fe54ea314f21f4b05f1972a209a6 Mon Sep 17 00:00:00 2001 From: Sunny Chung Date: Mon, 23 Sep 2024 00:57:36 +0800 Subject: [PATCH] update BigMonospaceText transformation listener to be fired before deletion, so that it can determine how to transform --- .../hellohttp/extension/RangeExtension.kt | 4 + .../hellohttp/ux/bigtext/BigMonospaceText.kt | 47 +++++++++-- ...onmentVariableIncrementalTransformation.kt | 82 ++++++++++++++----- 3 files changed, 102 insertions(+), 31 deletions(-) diff --git a/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/extension/RangeExtension.kt b/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/extension/RangeExtension.kt index 6ba6a3a1..69d8ad9c 100644 --- a/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/extension/RangeExtension.kt +++ b/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/extension/RangeExtension.kt @@ -13,6 +13,10 @@ infix fun IntRange.intersect(other: IntRange): IntRange { return from .. to } +infix fun IntRange.hasIntersectWith(other: IntRange): Boolean { + return !intersect(other).isEmpty() +} + fun IntRange.toNonEmptyRange(): IntRange { if (length <= 0) { return start .. start diff --git a/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/ux/bigtext/BigMonospaceText.kt b/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/ux/bigtext/BigMonospaceText.kt index 8620b50b..b05fb4b7 100644 --- a/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/ux/bigtext/BigMonospaceText.kt +++ b/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/ux/bigtext/BigMonospaceText.kt @@ -430,18 +430,42 @@ private fun CoreBigMonospaceText( .sum() } - fun onValueChange(eventType: BigTextChangeEventType, changeStartIndex: Int, changeEndExclusiveIndex: Int) { - viewState.lastVisibleRow = minOf(viewState.lastVisibleRow, transformedText.lastRowIndex) - - viewState.version = Random.nextLong() - val event = BigTextChangeEvent( + fun generateChangeEvent(eventType: BigTextChangeEventType, changeStartIndex: Int, changeEndExclusiveIndex: Int) : BigTextChangeEvent { + return BigTextChangeEvent( changeId = viewState.version, bigText = text, eventType = eventType, changeStartIndex = changeStartIndex, changeEndExclusiveIndex = changeEndExclusiveIndex, ) - (textTransformation as? IncrementalTextTransformation)?.onTextChange(event, transformedText, transformedState) + } + + fun onValuePreChange(eventType: BigTextChangeEventType, changeStartIndex: Int, changeEndExclusiveIndex: Int) { + if (eventType == BigTextChangeEventType.Delete) { + viewState.version = Random.nextLong() + val event = generateChangeEvent(eventType, changeStartIndex, changeEndExclusiveIndex) + + // invoke textTransformation listener before deletion, so that it knows what will be deleted and transform accordingly + (textTransformation as? IncrementalTextTransformation)?.onTextChange( + event, + transformedText, + transformedState + ) + } + } + + fun onValuePostChange(eventType: BigTextChangeEventType, changeStartIndex: Int, changeEndExclusiveIndex: Int) { + viewState.lastVisibleRow = minOf(viewState.lastVisibleRow, transformedText.lastRowIndex) + + viewState.version = Random.nextLong() + val event = generateChangeEvent(eventType, changeStartIndex, changeEndExclusiveIndex) + if (eventType != BigTextChangeEventType.Delete) { + (textTransformation as? IncrementalTextTransformation)?.onTextChange( + event, + transformedText, + transformedState + ) + } onTextChange(event) } @@ -454,8 +478,10 @@ private fun CoreBigMonospaceText( viewState.transformedSelection = IntRange.EMPTY } val insertPos = viewState.cursorIndex + onValuePreChange(BigTextChangeEventType.Insert, insertPos, insertPos + textInput.length) text.insertAt(insertPos, textInput) - onValueChange(BigTextChangeEventType.Insert, insertPos, insertPos + textInput.length) + onValuePostChange(BigTextChangeEventType.Insert, insertPos, insertPos + textInput.length) + // update cursor after invoking listeners, because a transformation or change may take place viewState.cursorIndex = minOf(text.length, insertPos + textInput.length) viewState.updateTransformedCursorIndexByOriginal(transformedText) viewState.transformedSelectionStart = viewState.transformedCursorIndex @@ -467,15 +493,18 @@ private fun CoreBigMonospaceText( when (direction) { TextFBDirection.Forward -> { if (cursor + 1 <= text.length) { + onValuePreChange(BigTextChangeEventType.Delete, cursor, cursor + 1) text.delete(cursor, cursor + 1) - onValueChange(BigTextChangeEventType.Delete, cursor, cursor + 1) + onValuePostChange(BigTextChangeEventType.Delete, cursor, cursor + 1) return true } } TextFBDirection.Backward -> { if (cursor - 1 >= 0) { + onValuePreChange(BigTextChangeEventType.Delete, cursor - 1, cursor) text.delete(cursor - 1, cursor) - onValueChange(BigTextChangeEventType.Delete, cursor - 1, cursor) + onValuePostChange(BigTextChangeEventType.Delete, cursor - 1, cursor) + // update cursor after invoking listeners, because a transformation or change may take place viewState.cursorIndex = maxOf(0, cursor - 1) viewState.updateTransformedCursorIndexByOriginal(transformedText) viewState.transformedSelectionStart = viewState.transformedCursorIndex diff --git a/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/ux/transformation/incremental/EnvironmentVariableIncrementalTransformation.kt b/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/ux/transformation/incremental/EnvironmentVariableIncrementalTransformation.kt index 668b18da..6bf9f9a2 100644 --- a/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/ux/transformation/incremental/EnvironmentVariableIncrementalTransformation.kt +++ b/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/ux/transformation/incremental/EnvironmentVariableIncrementalTransformation.kt @@ -1,5 +1,7 @@ package com.sunnychung.application.multiplatform.hellohttp.ux.transformation.incremental +import com.sunnychung.application.multiplatform.hellohttp.extension.hasIntersectWith +import com.sunnychung.application.multiplatform.hellohttp.extension.intersect import com.sunnychung.application.multiplatform.hellohttp.util.log import com.sunnychung.application.multiplatform.hellohttp.ux.bigtext.BigText import com.sunnychung.application.multiplatform.hellohttp.ux.bigtext.BigTextChangeEvent @@ -10,7 +12,9 @@ import com.sunnychung.application.multiplatform.hellohttp.ux.bigtext.Incremental import com.sunnychung.application.multiplatform.hellohttp.ux.bigtext.TextFBDirection class EnvironmentVariableIncrementalTransformation : IncrementalTextTransformation { - private val variableRegex = "\\$\\{\\{([^{}]{1,20})\\}\\}".toRegex() + val processLengthLimit = 30 + + private val variableRegex = "\\$\\{\\{([^{}]{1,$processLengthLimit})\\}\\}".toRegex() override fun initialize(text: BigText, transformer: BigTextTransformer) { // if (true) return @@ -24,32 +28,66 @@ class EnvironmentVariableIncrementalTransformation : IncrementalTextTransformati } override fun onTextChange(change: BigTextChangeEvent, transformer: BigTextTransformer, context: Unit) { - // TODO handle delete - if (change.eventType != BigTextChangeEventType.Insert) return + // TODO handle multiple matches (e.g. triggered by pasting text) val originalText = change.bigText - originalText.findPositionByPattern(change.changeStartIndex, change.changeEndExclusiveIndex, "}}", TextFBDirection.Forward).also { - log.d { "EnvironmentVariableIncrementalTransformation search end end=$it" } - }?.let { - val anotherBracket = originalText.findPositionByPattern(it - 20, it - 1, "\${{", TextFBDirection.Backward) - log.d { "EnvironmentVariableIncrementalTransformation search end start=$it" } - if (anotherBracket != null) { - val variableName = originalText.substring(anotherBracket + "\${{".length, it) - log.d { "EnvironmentVariableIncrementalTransformation add '$variableName'" } - transformer.replace(anotherBracket until it + "}}".length, createSpan(variableName), BigTextTransformOffsetMapping.WholeBlock) + when (change.eventType) { + BigTextChangeEventType.Insert -> { + // Find if there is pattern match ("\${{" or "}}") in the inserted text. + // If yes, try to locate the pair within `processLengthLimit`, and make desired replacement. + + originalText.findPositionByPattern(change.changeStartIndex, change.changeEndExclusiveIndex, "}}", TextFBDirection.Forward).also { + log.d { "EnvironmentVariableIncrementalTransformation search end end=$it" } + }?.let { + val anotherBracket = originalText.findPositionByPattern(it - processLengthLimit, it - 1, "\${{", TextFBDirection.Backward) + log.d { "EnvironmentVariableIncrementalTransformation search end start=$it" } + if (anotherBracket != null) { + val variableName = originalText.substring(anotherBracket + "\${{".length, it) + log.d { "EnvironmentVariableIncrementalTransformation add '$variableName'" } + transformer.replace(anotherBracket until it + "}}".length, createSpan(variableName), BigTextTransformOffsetMapping.WholeBlock) + } + } + originalText.findPositionByPattern(change.changeStartIndex, change.changeEndExclusiveIndex, "\${{", TextFBDirection.Forward).also { + log.d { "EnvironmentVariableIncrementalTransformation search start start=$it" } + }?.let { + val anotherBracket = originalText.findPositionByPattern(it + "\${{".length, it + processLengthLimit, "}}", TextFBDirection.Forward) + log.d { "EnvironmentVariableIncrementalTransformation search start end=$it" } + if (anotherBracket != null) { + val variableName = originalText.substring(it + "\${{".length, anotherBracket) + log.d { "EnvironmentVariableIncrementalTransformation add '$variableName'" } + transformer.replace(it until anotherBracket + "}}".length, createSpan(variableName), BigTextTransformOffsetMapping.WholeBlock) + } + } } - } - originalText.findPositionByPattern(change.changeStartIndex, change.changeEndExclusiveIndex, "\${{", TextFBDirection.Forward).also { - log.d { "EnvironmentVariableIncrementalTransformation search start start=$it" } - }?.let { - val anotherBracket = originalText.findPositionByPattern(it + "\${{".length, it + 20, "}}", TextFBDirection.Forward) - log.d { "EnvironmentVariableIncrementalTransformation search start end=$it" } - if (anotherBracket != null) { - val variableName = originalText.substring(it + "\${{".length, anotherBracket) - log.d { "EnvironmentVariableIncrementalTransformation add '$variableName'" } - transformer.replace(it until anotherBracket + "}}".length, createSpan(variableName), BigTextTransformOffsetMapping.WholeBlock) + + BigTextChangeEventType.Delete -> { + // Find if there is pattern match ("\${{" or "}}") in the inserted text. + // If yes, try to locate the pair within `processLengthLimit`, and remove the transformation by restoring them to original. + + val changeRange = change.changeStartIndex until change.changeEndExclusiveIndex + + originalText.findPositionByPattern(change.changeStartIndex - processLengthLimit, change.changeEndExclusiveIndex, "}}", TextFBDirection.Backward) + ?.takeIf { (it until it + "}}".length) hasIntersectWith changeRange } + ?.let { + originalText.findPositionByPattern(it - processLengthLimit, it - 1, "\${{", TextFBDirection.Backward) + ?.let { anotherStart -> + log.d { "EnvironmentVariableIncrementalTransformation delete A" } + transformer.restoreToOriginal(anotherStart until it + "}}".length) + } + } + originalText.findPositionByPattern(change.changeStartIndex - processLengthLimit, change.changeEndExclusiveIndex, "\${{", TextFBDirection.Forward) + ?.takeIf { (it until it + "\${{".length) hasIntersectWith changeRange } + ?.let { + originalText.findPositionByPattern(it + "\${{".length, it + processLengthLimit, "}}", TextFBDirection.Forward) + ?.let { anotherStart -> + log.d { "EnvironmentVariableIncrementalTransformation delete B" } + transformer.restoreToOriginal(it until anotherStart + "}}".length) + } + } } } + + } fun createSpan(variableName: String): String { // TODO change to AnnotatedString