2024-05-20 20:52:33 -07:00
|
|
|
import { Options, useHotkeys } from 'react-hotkeys-hook'
|
|
|
|
import { useEffect } from 'react'
|
|
|
|
import { codeManager } from './singletons'
|
2024-08-23 16:20:22 -04:00
|
|
|
import { Platform } from './utils'
|
2024-05-20 20:52:33 -07:00
|
|
|
|
|
|
|
// Hotkey wrapper wraps hotkeys for the app (outside of the editor)
|
2024-08-23 16:20:22 -04:00
|
|
|
// with hotkeys inside the editor.
|
2024-05-20 20:52:33 -07:00
|
|
|
// This way we can have hotkeys defined in one place and not have to worry about
|
|
|
|
// conflicting hotkeys, or them only being implemented for the app but not
|
|
|
|
// inside the editor.
|
|
|
|
// TODO: would be nice if this didn't have to be a react hook. It's not needed
|
|
|
|
// for the code mirror stuff but but it is needed for the useHotkeys hook.
|
|
|
|
export default function useHotkeyWrapper(
|
|
|
|
hotkey: string[],
|
|
|
|
callback: () => void,
|
|
|
|
additionalOptions?: Options
|
|
|
|
) {
|
2024-07-25 20:18:11 -04:00
|
|
|
const defaultOptions = { preventDefault: true }
|
|
|
|
const options = { ...defaultOptions, ...additionalOptions }
|
|
|
|
useHotkeys(hotkey, callback, options)
|
2024-05-20 20:52:33 -07:00
|
|
|
useEffect(() => {
|
|
|
|
for (const key of hotkey) {
|
|
|
|
const keybinding = mapHotkeyToCodeMirrorHotkey(key)
|
|
|
|
codeManager.registerHotkey(keybinding, callback)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Convert hotkey to code mirror hotkey
|
|
|
|
// See: https://codemirror.net/docs/ref/#view.KeyBinding
|
|
|
|
function mapHotkeyToCodeMirrorHotkey(hotkey: string): string {
|
|
|
|
return hotkey
|
|
|
|
.replaceAll('+', '-')
|
|
|
|
.replaceAll(' ', '')
|
2024-08-22 19:13:27 -04:00
|
|
|
.replaceAll('mod', 'Mod')
|
2024-05-20 20:52:33 -07:00
|
|
|
.replaceAll('meta', 'Meta')
|
|
|
|
.replaceAll('ctrl', 'Ctrl')
|
|
|
|
.replaceAll('shift', 'Shift')
|
|
|
|
.replaceAll('alt', 'Alt')
|
|
|
|
}
|
2024-08-23 16:20:22 -04:00
|
|
|
|
|
|
|
const LOWER_CASE_LETTER = /[a-z]/
|
|
|
|
const WHITESPACE = /\s+/g
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Convert hotkey to display text.
|
|
|
|
*
|
|
|
|
* TODO: We should handle capitalized single letter hotkeys like K as Shift+K,
|
|
|
|
* but we don't.
|
|
|
|
*/
|
|
|
|
export function hotkeyDisplay(hotkey: string, platform: Platform): string {
|
|
|
|
const isMac = platform === 'macos'
|
|
|
|
const isWindows = platform === 'windows'
|
|
|
|
// Browsers call it metaKey, but that's a misnomer.
|
|
|
|
const meta = isWindows ? 'Win' : 'Super'
|
|
|
|
const outputSeparator = isMac ? '' : '+'
|
|
|
|
const display = hotkey
|
|
|
|
// Capitalize letters. We want Ctrl+K, not Ctrl+k, since Shift should be
|
|
|
|
// shown as a separate modifier.
|
|
|
|
.split('+')
|
|
|
|
.map((word) => {
|
|
|
|
if (word.length === 1 && LOWER_CASE_LETTER.test(word)) {
|
|
|
|
return word.toUpperCase()
|
|
|
|
}
|
|
|
|
return word
|
|
|
|
})
|
|
|
|
.join(outputSeparator)
|
|
|
|
// Collapse multiple spaces into one.
|
|
|
|
.replaceAll(WHITESPACE, ' ')
|
|
|
|
.replaceAll('mod', isMac ? '⌘' : 'Ctrl')
|
|
|
|
.replaceAll('meta', isMac ? '⌘' : meta)
|
|
|
|
// This is technically the wrong arrow for control, but it's more visible
|
|
|
|
// and recognizable. May want to change this in the future.
|
|
|
|
//
|
|
|
|
// The correct arrow is ⌃ "UP ARROWHEAD" Unicode: U+2303
|
|
|
|
.replaceAll('ctrl', isMac ? '^' : 'Ctrl')
|
|
|
|
// This is technically the wrong arrow for shift, but it's more visible and
|
|
|
|
// recognizable. May want to change this in the future.
|
|
|
|
//
|
|
|
|
// The correct arrow is ⇧ "UPWARDS WHITE ARROW" Unicode: U+21E7
|
|
|
|
.replaceAll('shift', isMac ? '⬆' : 'Shift')
|
|
|
|
.replaceAll('alt', isMac ? '⌥' : 'Alt')
|
|
|
|
|
|
|
|
return display
|
|
|
|
}
|