diff --git a/composeApp/src/commonMain/kotlin/CorrectBracketSequence.kt b/composeApp/src/commonMain/kotlin/CorrectBracketSequence.kt index 245ca56..325d60b 100644 --- a/composeApp/src/commonMain/kotlin/CorrectBracketSequence.kt +++ b/composeApp/src/commonMain/kotlin/CorrectBracketSequence.kt @@ -197,95 +197,93 @@ fun CorrectBracketSequence() { ) ) }, - keyEventHandler = combineKeyEventHandlers( + keyboardEventHandler = combineKeyboardEventHandlers( handleMovingOffsets( state = codeTextFieldState, - ) - ), - charEventHandler = combineCharEventHandlers( + ), reusingCharsEventHandler( textFieldState = codeTextFieldState, chars = "])>}", ), - openingBracketCharEventHandler( + openingBracketEventHandler( textFieldState = codeTextFieldState, openingChar = '[', openingBracket = "[", closingBracket = "]", addNewLinesForSelection = { false }, ), - closingBracketCharEventHandler( + closingBracketEventHandler( textFieldState = codeTextFieldState, openingBracket = "[", closingBracket = "]", closingChar = ']', matchedBrackets = matchedBrackets, ), - openingBracketCharEventHandler( + openingBracketEventHandler( textFieldState = codeTextFieldState, openingChar = '(', openingBracket = "(", closingBracket = ")", addNewLinesForSelection = { false }, ), - closingBracketCharEventHandler( + closingBracketEventHandler( textFieldState = codeTextFieldState, openingBracket = "(", closingBracket = ")", closingChar = ')', matchedBrackets = matchedBrackets, ), - openingBracketCharEventHandler( + openingBracketEventHandler( textFieldState = codeTextFieldState, openingChar = '<', openingBracket = "<", closingBracket = ">", addNewLinesForSelection = { false }, ), - closingBracketCharEventHandler( + closingBracketEventHandler( textFieldState = codeTextFieldState, openingBracket = "<", closingBracket = ">", closingChar = '>', matchedBrackets = matchedBrackets, ), - openingBracketCharEventHandler( + openingBracketEventHandler( textFieldState = codeTextFieldState, openingChar = '{', openingBracket = "{", closingBracket = "}", addNewLinesForSelection = { true }, ), - closingBracketCharEventHandler( + closingBracketEventHandler( textFieldState = codeTextFieldState, openingBracket = "{", closingBracket = "}", closingChar = '}', matchedBrackets = matchedBrackets, ), - newLineCharEventHandler( + newLineEventHandler( textFieldState = codeTextFieldState, matchedBrackets = matchedBrackets, ), - removeIndentBackspaceCharEventHandler( + removeIndentBackspaceEventHandler( textFieldState = codeTextFieldState, ), - removeEmptyBracesBackspaceCharEventHandler( + removeEmptyBracesBackspaceEventHandler( textFieldState = codeTextFieldState, openingBracket = "[", closingBracket = "]", ), - removeEmptyBracesBackspaceCharEventHandler( + removeEmptyBracesBackspaceEventHandler( textFieldState = codeTextFieldState, openingBracket = "(", closingBracket = ")", ), - removeEmptyBracesBackspaceCharEventHandler( + removeEmptyBracesBackspaceEventHandler( textFieldState = codeTextFieldState, openingBracket = "<", closingBracket = ">", ), - removeEmptyBracesBackspaceCharEventHandler( + removeEmptyBracesBackspaceEventHandler( textFieldState = codeTextFieldState, openingBracket = "{", closingBracket = "}", diff --git a/editor/src/commonMain/kotlin/editor/basic/BasicSourceCodeTextField.kt b/editor/src/commonMain/kotlin/editor/basic/BasicSourceCodeTextField.kt index 1a37bae..a3a6ad4 100644 --- a/editor/src/commonMain/kotlin/editor/basic/BasicSourceCodeTextField.kt +++ b/editor/src/commonMain/kotlin/editor/basic/BasicSourceCodeTextField.kt @@ -141,22 +141,24 @@ public data class EditorOffsets( val end: Int = 0, ) -public typealias CharEventHandler = (CharEvent) -> TextFieldValue? +public typealias KeyboardEventHandler = (KeyboardEvent) -> TextFieldValue? +public typealias KeyboardEventFilter = (KeyboardEvent) -> Boolean -public fun combineCharEventHandlers(vararg handlers: CharEventHandler?): CharEventHandler = +public fun combineKeyboardEventHandlers(vararg handlers: KeyboardEventHandler?): KeyboardEventHandler = { event -> handlers.firstNotNullOfOrNull { it?.invoke(event) } } -public typealias KeyEventHandler = (KeyEvent) -> TextFieldValue? -public typealias KeyEventFilter = (KeyEvent) -> Boolean +public fun combineKeyboardEventFilters(vararg filters: KeyboardEventFilter?): KeyboardEventFilter = + { event -> filters.any { it?.invoke(event) == true } } -public fun combineKeyEventHandlers(vararg handlers: KeyEventHandler?): KeyEventHandler = - { event -> handlers.firstNotNullOfOrNull { it?.invoke(event) } } +public interface KeyboardEvent + +public data class PhysicalKeyboardEvent(val keyEvent: KeyEvent) : KeyboardEvent -public sealed class CharEvent { - public data object NonTextEvent : CharEvent() - public data class Insert(val char: Char) : CharEvent() - public data object Backspace : CharEvent() - public data object Misc : CharEvent() +public sealed class UniversalKeyboardEvent : KeyboardEvent { + public data object NonTextEvent : UniversalKeyboardEvent() + public data class Insert(val char: Char) : UniversalKeyboardEvent() + public data object Backspace : UniversalKeyboardEvent() + public data object Misc : UniversalKeyboardEvent() } private fun isBackSpace( @@ -238,13 +240,13 @@ public fun initializeBasicSourceCodeTextFieldState( textFieldState: TextFieldValue, preprocessors: List, tokenize: Tokenizer, - charEventHandler: CharEventHandler, + keyboardEventHandler: KeyboardEventHandler, ): BasicSourceCodeTextFieldState = translate( textFieldState = textFieldState, preprocessors = preprocessors, tokenize = tokenize, state = BasicSourceCodeTextFieldState(), - charEventHandler = charEventHandler, + keyboardEventHandler = keyboardEventHandler, ) private fun translate( @@ -252,19 +254,19 @@ private fun translate( preprocessors: List, tokenize: Tokenizer, state: BasicSourceCodeTextFieldState, - charEventHandler: CharEventHandler, + keyboardEventHandler: KeyboardEventHandler, ): BasicSourceCodeTextFieldState { val preprocessed = preprocessors.fold(textFieldState) { acc, preprocessor -> preprocessor(acc) } val charEvent = when { - state.text == textFieldState.text -> CharEvent.NonTextEvent - isBackSpace(state, textFieldState) -> CharEvent.Backspace + state.text == textFieldState.text -> UniversalKeyboardEvent.NonTextEvent + isBackSpace(state, textFieldState) -> UniversalKeyboardEvent.Backspace isCharInserted(state, textFieldState) -> - CharEvent.Insert(char = textFieldState.text[textFieldState.selection.start - 1]) + UniversalKeyboardEvent.Insert(char = textFieldState.text[textFieldState.selection.start - 1]) - else -> CharEvent.Misc + else -> UniversalKeyboardEvent.Misc } - val handledAsCharEvent = charEventHandler(charEvent) ?: preprocessed + val handledAsCharEvent = keyboardEventHandler(charEvent) ?: preprocessed return tokenize(handledAsCharEvent) } @@ -305,8 +307,7 @@ public fun BasicSourceCodeTextField( lineNumberModifier: Modifier = defaultLineNumberModifier, editorOffsetsForPosition: (sourceCodePosition: SourceCodePosition) -> EditorOffsets = { EditorOffsets() }, manualScrollToPosition: SharedFlow = remember { MutableSharedFlow() }, - charEventHandler: CharEventHandler = { null }, - keyEventHandler: KeyEventHandler = { null }, + keyboardEventHandler: KeyboardEventHandler = { null }, onHoveredSourceCodePositionChange: (position: SourceCodePosition) -> Unit = {}, horizontalThresholdEdgeChars: Int = 5, verticalThresholdEdgeLines: Int = 1, @@ -451,7 +452,7 @@ public fun BasicSourceCodeTextField( var textFieldSize: IntSize? by remember { mutableStateOf(null) } fun translate(textFieldState: TextFieldValue) = translate( - textFieldState, preprocessors, tokenize, state, charEventHandler + textFieldState, preprocessors, tokenize, state, keyboardEventHandler ) fun onValueChange(newTextFieldState: TextFieldValue) { @@ -496,7 +497,7 @@ public fun BasicSourceCodeTextField( ) .onSizeChanged { textFieldSize = it } .onPreviewKeyEvent { - when (val eventResult = keyEventHandler(it)) { + when (val eventResult = keyboardEventHandler(PhysicalKeyboardEvent(it))) { null -> false else -> { onValueChange(eventResult) diff --git a/editor/src/commonMain/kotlin/editor/basic/KeyEventHandlers.kt b/editor/src/commonMain/kotlin/editor/basic/KeyEventHandlers.kt index a38caf4..4d84fcd 100644 --- a/editor/src/commonMain/kotlin/editor/basic/KeyEventHandlers.kt +++ b/editor/src/commonMain/kotlin/editor/basic/KeyEventHandlers.kt @@ -3,21 +3,44 @@ package editor.basic import androidx.compose.ui.input.key.* import androidx.compose.ui.text.TextRange import androidx.compose.ui.text.input.TextFieldValue +import kotlin.jvm.JvmName + +public typealias PhysicalKeyboardEventHandler = (PhysicalKeyboardEvent) -> TextFieldValue? +public typealias PhysicalKeyboardEventFilter = (PhysicalKeyboardEvent) -> Boolean + +public fun PhysicalKeyboardEventHandler.asKeyboardEventHandler(): KeyboardEventHandler = { + if (it is PhysicalKeyboardEvent) invoke(it) else null +} + +public fun PhysicalKeyboardEventFilter.asKeyboardEventFilter(): KeyboardEventFilter = { + it is PhysicalKeyboardEvent && invoke(it) +} + +public typealias ComposeKeyEventHandler = (KeyEvent) -> TextFieldValue? +public typealias ComposeKeyEventFilter = (KeyEvent) -> Boolean + +@JvmName("composeKeyboardEventHandlerAsKeyboardEventHandler") +public fun ComposeKeyEventHandler.asKeyboardEventHandler(): KeyboardEventHandler = + { event: PhysicalKeyboardEvent -> invoke(event.keyEvent) }.asKeyboardEventHandler() + +@JvmName("composeKeyboardEventFilterAsKeyboardEventFilter") +public fun ComposeKeyEventFilter.asKeyboardEventFilter(): KeyboardEventFilter = + { event: PhysicalKeyboardEvent -> invoke(event.keyEvent) }.asKeyboardEventFilter() public fun handleMovingOffsets( state: BasicSourceCodeTextFieldState, indent: String = " ".repeat(4), - moveForwardFilter: KeyEventFilter = { keyEvent -> + moveForwardFilter: KeyboardEventFilter = { keyEvent: KeyEvent -> !keyEvent.isShiftPressed && keyEvent.key == Key.Tab && keyEvent.type == KeyEventType.KeyDown && !keyEvent.isAltPressed && !keyEvent.isCtrlPressed && !keyEvent.isMetaPressed - }, - moveBackwardFilter: KeyEventFilter = { keyEvent -> + }.asKeyboardEventFilter(), + moveBackwardFilter: KeyboardEventFilter = { keyEvent: KeyEvent -> keyEvent.isShiftPressed && keyEvent.key == Key.Tab && keyEvent.type == KeyEventType.KeyDown && !keyEvent.isAltPressed && !keyEvent.isCtrlPressed && !keyEvent.isMetaPressed - }, -): KeyEventHandler = f@{ keyEvent: KeyEvent -> - val moveForward = !state.selection.collapsed && moveForwardFilter(keyEvent) - val moveBackward = moveBackwardFilter(keyEvent) + }.asKeyboardEventFilter(), +): KeyboardEventHandler = f@{ keyboardEvent: KeyboardEvent -> + val moveForward = !state.selection.collapsed && moveForwardFilter(keyboardEvent) + val moveBackward = moveBackwardFilter(keyboardEvent) if (moveBackward == moveForward) return@f null val selectionLines = state.sourceCodePositions[state.selection.min].line..state.sourceCodePositions[state.selection.max].line diff --git a/editor/src/commonMain/kotlin/editor/basic/CharEventHandlers.kt b/editor/src/commonMain/kotlin/editor/basic/UniversalKeyboardEventHandlers.kt similarity index 87% rename from editor/src/commonMain/kotlin/editor/basic/CharEventHandlers.kt rename to editor/src/commonMain/kotlin/editor/basic/UniversalKeyboardEventHandlers.kt index 637f3a9..dd2673a 100644 --- a/editor/src/commonMain/kotlin/editor/basic/CharEventHandlers.kt +++ b/editor/src/commonMain/kotlin/editor/basic/UniversalKeyboardEventHandlers.kt @@ -3,14 +3,24 @@ package editor.basic import androidx.compose.ui.text.TextRange import androidx.compose.ui.text.input.TextFieldValue +public typealias UniversalKeyboardEventHandler = (UniversalKeyboardEvent) -> TextFieldValue? +public typealias UniversalKeyboardEventFilter = (UniversalKeyboardEvent) -> Boolean -public fun openingBracketCharEventHandler( +public fun UniversalKeyboardEventHandler.asKeyboardEventHandler(): KeyboardEventHandler = { + if (it is UniversalKeyboardEvent) invoke(it) else null +} + +public fun UniversalKeyboardEventFilter.asKeyboardEventFilter(): KeyboardEventFilter = { + it is UniversalKeyboardEvent && invoke(it) +} + +public fun openingBracketEventHandler( textFieldState: BasicSourceCodeTextFieldState, openingChar: Char, openingBracket: String, closingBracket: String, - addNewLinesForSelection: (CharEvent.Insert) -> Boolean = { false }, + addNewLinesForSelection: (UniversalKeyboardEvent.Insert) -> Boolean = { false }, indent: String? = " ".repeat(4), -): CharEventHandler = f@{ keyEvent -> - if (keyEvent !is CharEvent.Insert || keyEvent.char != openingChar) return@f null +): KeyboardEventHandler = f@{ keyEvent -> + if (keyEvent !is UniversalKeyboardEvent.Insert || keyEvent.char != openingChar) return@f null val oldSelection = textFieldState.selection val minLine = textFieldState.sourceCodePositions[oldSelection.min].line val maxLine = textFieldState.sourceCodePositions[oldSelection.max].line @@ -94,12 +104,12 @@ public fun openingBracketCharEventHandler( TextFieldValue(newString, newSelection, newComposition) } -public inline fun closingBracketCharEventHandler( +public inline fun closingBracketEventHandler( textFieldState: BasicSourceCodeTextFieldState, matchedBrackets: Map, openingBracket: String, closingChar: Char, closingBracket: String, -): CharEventHandler = f@{ keyEvent -> - if (keyEvent !is CharEvent.Insert || keyEvent.char != closingChar) return@f null +): KeyboardEventHandler = f@{ keyEvent -> + if (keyEvent !is UniversalKeyboardEvent.Insert || keyEvent.char != closingChar) return@f null val position = textFieldState.sourceCodePositions[textFieldState.selection.min] val finishLine = position.line @@ -152,9 +162,9 @@ public inline fun closingBrack public fun reusingCharsEventHandler( textFieldState: BasicSourceCodeTextFieldState, chars: String, -): CharEventHandler = f@{ keyEvent -> +): KeyboardEventHandler = f@{ keyEvent -> if ( - keyEvent !is CharEvent.Insert || keyEvent.char !in chars || !textFieldState.selection.collapsed || + keyEvent !is UniversalKeyboardEvent.Insert || keyEvent.char !in chars || !textFieldState.selection.collapsed || textFieldState.selection.start == textFieldState.text.length || textFieldState.text[textFieldState.selection.start] != keyEvent.char ) return@f null @@ -165,12 +175,12 @@ public fun reusingCharsEventHandler( ) } -public inline fun newLineCharEventHandler( +public inline fun newLineEventHandler( textFieldState: BasicSourceCodeTextFieldState, matchedBrackets: Map, indent: String = " ".repeat(4), -): CharEventHandler = f@{ keyEvent -> - if (keyEvent !is CharEvent.Insert || keyEvent.char != '\n') return@f null +): KeyboardEventHandler = f@{ keyEvent -> + if (keyEvent !is UniversalKeyboardEvent.Insert || keyEvent.char != '\n') return@f null val currentLine = textFieldState.sourceCodePositions[textFieldState.selection.min].line val newIndents = textFieldState.tokens.filter { if (it !is Bracket) return@filter false @@ -231,12 +241,12 @@ public inline fun newL ) } -public fun removeIndentBackspaceCharEventHandler( +public fun removeIndentBackspaceEventHandler( textFieldState: BasicSourceCodeTextFieldState, indent: String = " ".repeat(4), -): CharEventHandler = f@{ keyEvent -> +): KeyboardEventHandler = f@{ keyEvent -> val offset = textFieldState.selection.start - if (keyEvent !is CharEvent.Backspace || !textFieldState.selection.collapsed || offset < indent.length) return@f null + if (keyEvent !is UniversalKeyboardEvent.Backspace || !textFieldState.selection.collapsed || offset < indent.length) return@f null val position = textFieldState.sourceCodePositions[offset] val line = position.line val lineOffset = textFieldState.lineOffsets[line] @@ -257,14 +267,14 @@ public fun removeIndentBackspaceCharEventHandler( ) } -public fun removeEmptyBracesBackspaceCharEventHandler( +public fun removeEmptyBracesBackspaceEventHandler( textFieldState: BasicSourceCodeTextFieldState, openingBracket: String, closingBracket: String, -): CharEventHandler = f@{ keyEvent -> +): KeyboardEventHandler = f@{ keyEvent -> val offset = textFieldState.selection.start if ( - keyEvent !is CharEvent.Backspace || !textFieldState.selection.collapsed || offset < openingBracket.length || + keyEvent !is UniversalKeyboardEvent.Backspace || !textFieldState.selection.collapsed || offset < openingBracket.length || offset + closingBracket.length > textFieldState.text.length ) return@f null for ((i, c) in openingBracket.withIndex()) { diff --git a/gradle.properties b/gradle.properties index 77df0ee..5091fda 100644 --- a/gradle.properties +++ b/gradle.properties @@ -20,4 +20,4 @@ development=true #Publication group=com.zhelenskiy -version=0.0.12 +version=0.0.13