KeyMapping.kt

/*
 * Copyright 2021 The Android Open Source Project
 *
 * 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 androidx.compose.foundation.text

import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.KeyEvent
import androidx.compose.ui.input.key.isCtrlPressed
import androidx.compose.ui.input.key.isShiftPressed
import androidx.compose.ui.input.key.key

internal interface KeyMapping {
    fun map(event: KeyEvent): KeyCommand?
}

// each platform can define its own key mapping, on Android its just defaultKeyMapping, but on
// desktop, the value depends on the current OS
internal expect val platformDefaultKeyMapping: KeyMapping

// It's common for all platforms key mapping
internal fun commonKeyMapping(
    shortcutModifier: (KeyEvent) -> Boolean
): KeyMapping {
    return object : KeyMapping {
        override fun map(event: KeyEvent): KeyCommand? {
            return when {
                shortcutModifier(event) ->
                    when (event.key) {
                        Key.C, Key.Insert -> KeyCommand.COPY
                        Key.V -> KeyCommand.PASTE
                        Key.X -> KeyCommand.CUT
                        Key.A -> KeyCommand.SELECT_ALL
                        else -> null
                    }
                event.isCtrlPressed -> null
                event.isShiftPressed ->
                    when (event.key) {
                        Key.DirectionLeft -> KeyCommand.SELECT_LEFT_CHAR
                        Key.DirectionRight -> KeyCommand.SELECT_RIGHT_CHAR
                        Key.DirectionUp -> KeyCommand.SELECT_UP
                        Key.DirectionDown -> KeyCommand.SELECT_DOWN
                        Key.PageUp -> KeyCommand.SELECT_PAGE_UP
                        Key.PageDown -> KeyCommand.SELECT_PAGE_DOWN
                        Key.MoveHome -> KeyCommand.SELECT_LINE_START
                        Key.MoveEnd -> KeyCommand.SELECT_LINE_END
                        Key.Insert -> KeyCommand.PASTE
                        else -> null
                    }
                else ->
                    when (event.key) {
                        Key.DirectionLeft -> KeyCommand.LEFT_CHAR
                        Key.DirectionRight -> KeyCommand.RIGHT_CHAR
                        Key.DirectionUp -> KeyCommand.UP
                        Key.DirectionDown -> KeyCommand.DOWN
                        Key.PageUp -> KeyCommand.PAGE_UP
                        Key.PageDown -> KeyCommand.PAGE_DOWN
                        Key.MoveHome -> KeyCommand.LINE_START
                        Key.MoveEnd -> KeyCommand.LINE_END
                        Key.Enter -> KeyCommand.NEW_LINE
                        Key.Backspace -> KeyCommand.DELETE_PREV_CHAR
                        Key.Delete -> KeyCommand.DELETE_NEXT_CHAR
                        Key.Paste -> KeyCommand.PASTE
                        Key.Cut -> KeyCommand.CUT
                        Key.Tab -> KeyCommand.TAB
                        else -> null
                    }
            }
        }
    }
}

// It's "default" or actually "non macOS" key mapping
internal val defaultKeyMapping: KeyMapping =
    commonKeyMapping(KeyEvent::isCtrlPressed).let { common ->
        object : KeyMapping {
            override fun map(event: KeyEvent): KeyCommand? {
                return when {
                    event.isShiftPressed && event.isCtrlPressed ->
                        when (event.key) {
                            Key.DirectionLeft -> KeyCommand.SELECT_LEFT_WORD
                            Key.DirectionRight -> KeyCommand.SELECT_RIGHT_WORD
                            Key.DirectionUp -> KeyCommand.SELECT_PREV_PARAGRAPH
                            Key.DirectionDown -> KeyCommand.SELECT_NEXT_PARAGRAPH
                            else -> null
                        }
                    event.isCtrlPressed ->
                        when (event.key) {
                            Key.DirectionLeft -> KeyCommand.LEFT_WORD
                            Key.DirectionRight -> KeyCommand.RIGHT_WORD
                            Key.DirectionUp -> KeyCommand.PREV_PARAGRAPH
                            Key.DirectionDown -> KeyCommand.NEXT_PARAGRAPH
                            Key.H -> KeyCommand.DELETE_PREV_CHAR
                            Key.Delete -> KeyCommand.DELETE_NEXT_WORD
                            Key.Backspace -> KeyCommand.DELETE_PREV_WORD
                            Key.Backslash -> KeyCommand.DESELECT
                            else -> null
                        }
                    event.isShiftPressed ->
                        when (event.key) {
                            Key.MoveHome -> KeyCommand.SELECT_HOME
                            Key.MoveEnd -> KeyCommand.SELECT_END
                            else -> null
                        }
                    else -> null
                } ?: common.map(event)
            }
        }
    }