Skip to content

Commit

Permalink
update prettifying in CodeEditorView to be undoable
Browse files Browse the repository at this point in the history
  • Loading branch information
sunny-chung committed Nov 12, 2024
1 parent ff1a9a3 commit cdc8982
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ fun CodeEditorView(
isEnableVariables: Boolean = false,
knownVariables: Map<String, String> = mutableMapOf(),
onSearchBarVisibilityChange: ((isVisible: Boolean) -> Unit)? = null,
onTextManipulatorReady: ((BigTextManipulator) -> Unit)? = null,
testTag: String? = null,
) {
log.d { "CodeEditorView start" }
Expand Down Expand Up @@ -533,6 +534,7 @@ fun CodeEditorView(
fontSize = LocalFont.current.codeEditorBodyFontSize,
scrollState = scrollState,
onTextLayout = { layoutResult = it },
onTextManipulatorReady = onTextManipulatorReady,
keyboardInputProcessor = object : BigTextKeyboardInputProcessor {
override fun beforeProcessInput(
it: KeyEvent,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ import com.sunnychung.application.multiplatform.hellohttp.util.copyWithRemovedIn
import com.sunnychung.application.multiplatform.hellohttp.util.emptyToNull
import com.sunnychung.application.multiplatform.hellohttp.util.log
import com.sunnychung.application.multiplatform.hellohttp.util.uuidString
import com.sunnychung.application.multiplatform.hellohttp.ux.bigtext.BigTextManipulator
import com.sunnychung.application.multiplatform.hellohttp.ux.compose.rememberLast
import com.sunnychung.application.multiplatform.hellohttp.ux.local.LocalColor
import com.sunnychung.application.multiplatform.hellohttp.ux.local.LocalFont
Expand Down Expand Up @@ -1458,6 +1459,7 @@ private fun RequestBodyTextEditor(
val colors = LocalColor.current
val baseExample = request.examples.first()

var textManipulator by remember(cacheKey) { mutableStateOf<BigTextManipulator?>(null) }
val changeText = { it: String ->
onRequestModified(
request.copy(
Expand All @@ -1466,13 +1468,15 @@ private fun RequestBodyTextEditor(
)
)
)
Unit
}

val prettifyHandler = when (contentType) {
ContentType.Json -> { code: String ->
try {
val prettified = JsonParser(code).prettify().prettyString
changeText(prettified)
textManipulator?.replace(0 until code.length, prettified)
} catch (e: Throwable) {
AppContext.ErrorMessagePromptViewModel.showErrorMessage(e.message ?: "Error while prettifying as JSON")
}
Expand All @@ -1497,6 +1501,7 @@ private fun RequestBodyTextEditor(
knownVariables = environmentVariables,
initialText = content,
onTextChange = changeText,
onTextManipulatorReady = { textManipulator = it },
syntaxHighlight = syntaxHighlight,
testTag = testTag ?: TestTag.RequestStringBodyTextField.name,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ fun BigMonospaceTextField(
keyboardInputProcessor: BigTextKeyboardInputProcessor? = null,
onPointerEvent: ((event: PointerEvent, tag: String?) -> Unit)? = null,
onTextLayout: ((BigTextSimpleLayoutResult) -> Unit)? = null,
onTextManipulatorReady: ((BigTextManipulator) -> Unit)? = null,
) {
BigMonospaceTextField(
modifier = modifier,
Expand All @@ -199,7 +200,8 @@ fun BigMonospaceTextField(
viewState = textFieldState.viewState,
keyboardInputProcessor = keyboardInputProcessor,
onPointerEvent = onPointerEvent,
onTextLayout = onTextLayout
onTextLayout = onTextLayout,
onTextManipulatorReady = onTextManipulatorReady,
)
}

Expand All @@ -219,6 +221,7 @@ fun BigMonospaceTextField(
keyboardInputProcessor: BigTextKeyboardInputProcessor? = null,
onPointerEvent: ((event: PointerEvent, tag: String?) -> Unit)? = null,
onTextLayout: ((BigTextSimpleLayoutResult) -> Unit)? = null,
onTextManipulatorReady: ((BigTextManipulator) -> Unit)? = null,
) = CoreBigMonospaceText(
modifier = modifier,
text = text as BigTextImpl,
Expand All @@ -236,6 +239,7 @@ fun BigMonospaceTextField(
keyboardInputProcessor = keyboardInputProcessor,
onPointerEvent = onPointerEvent,
onTextLayout = onTextLayout,
onTextManipulatorReady = onTextManipulatorReady,
)

@OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)
Expand All @@ -257,6 +261,7 @@ private fun CoreBigMonospaceText(
keyboardInputProcessor: BigTextKeyboardInputProcessor? = null,
onPointerEvent: ((event: PointerEvent, tag: String?) -> Unit)? = null,
onTextLayout: ((BigTextSimpleLayoutResult) -> Unit)? = null,
onTextManipulatorReady: ((BigTextManipulator) -> Unit)? = null,
onTransformInit: ((BigTextTransformed) -> Unit)? = null,
) {
log.d { "CoreBigMonospaceText recompose" }
Expand Down Expand Up @@ -534,6 +539,9 @@ private fun CoreBigMonospaceText(
}

fun delete(start: Int, endExclusive: Int) {
if (start >= endExclusive) {
return
}
onValuePreChange(BigTextChangeEventType.Delete, start, endExclusive)
text.delete(start, endExclusive)
onValuePostChange(BigTextChangeEventType.Delete, start, endExclusive)
Expand Down Expand Up @@ -972,51 +980,60 @@ private fun CoreBigMonospaceText(
}
}

fun onProcessKeyboardInput(keyEvent: KeyEvent): Boolean {
class BigTextManipulatorImpl(val onTextManipulated: (() -> Unit)? = null) : BigTextManipulator {
var hasManipulatedText = false
val textManipulator = object : BigTextManipulator {
override fun append(text: CharSequence) {
hasManipulatedText = true
insertAt(text.length, text)
}
private set

override fun insertAt(pos: Int, text: CharSequence) {
hasManipulatedText = true
insertAt(pos, text)
}
override fun append(text: CharSequence) {
hasManipulatedText = true
insertAt(text.length, text)
onTextManipulated?.invoke()
}

override fun replaceAtCursor(text: CharSequence) {
hasManipulatedText = true
onType(text, isSaveUndoSnapshot = false) // save undo snapshot at the end
}
override fun insertAt(pos: Int, text: CharSequence) {
hasManipulatedText = true
insertAt(pos, text)
onTextManipulated?.invoke()
}

override fun delete(range: IntRange) {
hasManipulatedText = true
delete(range.start, range.endInclusive + 1)
}
override fun replaceAtCursor(text: CharSequence) {
hasManipulatedText = true
onType(text, isSaveUndoSnapshot = false) // save undo snapshot at the end
onTextManipulated?.invoke()
}

override fun replace(range: IntRange, text: CharSequence) {
hasManipulatedText = true
delete(range.start, range.endInclusive + 1)
insertAt(range.start, text)
}
override fun delete(range: IntRange) {
hasManipulatedText = true
delete(range.start, range.endInclusive + 1)
onTextManipulated?.invoke()
}

override fun setCursorPosition(position: Int) {
require(position in 0 .. text.length) { "Cursor position $position is out of range. Text length: ${text.length}" }
viewState.cursorIndex = position
viewState.updateTransformedCursorIndexByOriginal(transformedText)
viewState.transformedSelectionStart = viewState.transformedCursorIndex
scrollToCursor()
}
override fun replace(range: IntRange, text: CharSequence) {
hasManipulatedText = true
delete(range.start, range.endInclusive + 1)
insertAt(range.start, text)
onTextManipulated?.invoke()
}

override fun setSelection(range: IntRange) {
require(range.start in 0 .. text.length) { "Range start ${range.start} is out of range. Text length: ${text.length}" }
require(range.endInclusive + 1 in 0 .. text.length) { "Range end ${range.endInclusive} is out of range. Text length: ${text.length}" }
override fun setCursorPosition(position: Int) {
require(position in 0 .. text.length) { "Cursor position $position is out of range. Text length: ${text.length}" }
viewState.cursorIndex = position
viewState.updateTransformedCursorIndexByOriginal(transformedText)
viewState.transformedSelectionStart = viewState.transformedCursorIndex
scrollToCursor()
}

viewState.selection = range
viewState.updateTransformedSelectionBySelection(transformedText)
}
override fun setSelection(range: IntRange) {
require(range.start in 0 .. text.length) { "Range start ${range.start} is out of range. Text length: ${text.length}" }
require(range.endInclusive + 1 in 0 .. text.length) { "Range end ${range.endInclusive} is out of range. Text length: ${text.length}" }

viewState.selection = range
viewState.updateTransformedSelectionBySelection(transformedText)
}
}

fun onProcessKeyboardInput(keyEvent: KeyEvent): Boolean {
val textManipulator = BigTextManipulatorImpl()

try {
if (keyboardInputProcessor?.beforeProcessInput(keyEvent, viewState, textManipulator) == true) {
Expand All @@ -1029,13 +1046,20 @@ private fun CoreBigMonospaceText(
return result

} finally {
if (hasManipulatedText) {
if (textManipulator.hasManipulatedText) {
updateViewState()
text.recordCurrentChangeSequenceIntoUndoHistory()
}
}
}

remember(text, onTextManipulatorReady) {
onTextManipulatorReady?.invoke(BigTextManipulatorImpl {
updateViewState()
text.recordCurrentChangeSequenceIntoUndoHistory()
})
}

val tv = remember { TextFieldValue() } // this value is not used

LaunchedEffect(transformedText) {
Expand Down

0 comments on commit cdc8982

Please sign in to comment.