mirror of
https://github.com/boolean-maybe/tiki
synced 2026-04-21 13:37:20 +00:00
119 lines
3.3 KiB
Go
119 lines
3.3 KiB
Go
package plugin
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/gdamore/tcell/v2"
|
|
)
|
|
|
|
// keyName returns a human-readable string for the activation key
|
|
func keyName(key tcell.Key, r rune) string {
|
|
if key == tcell.KeyRune {
|
|
return string(r)
|
|
}
|
|
return tcell.KeyNames[key]
|
|
}
|
|
|
|
// parseKey parses a key string into a tcell.Key, a rune, and a modifier mask.
|
|
// Supported formats:
|
|
// - single rune: "B" (case-preserving)
|
|
// - function keys: "F1", "F2", ..., "F12" (case-insensitive)
|
|
// - ctrl combos: "Ctrl-U", "Ctrl-F1" (case-insensitive)
|
|
// - alt combos: "Alt-M", "Alt-F2" (case-insensitive)
|
|
// - shift combos: "Shift-F", "Shift-F3" (case-insensitive)
|
|
func parseKey(s string) (tcell.Key, rune, tcell.ModMask, error) {
|
|
s = strings.TrimSpace(s)
|
|
if s == "" {
|
|
return 0, 0, 0, nil
|
|
}
|
|
|
|
upper := strings.ToUpper(s)
|
|
|
|
// Handle Ctrl-X notation
|
|
if strings.HasPrefix(upper, "CTRL-") {
|
|
rest := upper[5:]
|
|
// Check for F-keys first (before treating F as a letter)
|
|
if key, ok := parseFunctionKey(rest); ok {
|
|
return key, 0, tcell.ModCtrl, nil
|
|
}
|
|
char := []rune(rest)
|
|
if len(char) == 1 && char[0] >= 'A' && char[0] <= 'Z' {
|
|
// tcell.KeyCtrlA is 65 ('A'), KeyCtrlZ is 90 ('Z')
|
|
return tcell.Key(char[0]), 0, tcell.ModCtrl, nil
|
|
}
|
|
return 0, 0, 0, fmt.Errorf("invalid ctrl key: %q (expected Ctrl-A..Ctrl-Z or Ctrl-F1..Ctrl-F12)", s)
|
|
}
|
|
|
|
// Handle Alt-X notation
|
|
if strings.HasPrefix(upper, "ALT-") {
|
|
rest := upper[4:]
|
|
// Check for F-keys first (before treating F as a letter)
|
|
if key, ok := parseFunctionKey(rest); ok {
|
|
return key, 0, tcell.ModAlt, nil
|
|
}
|
|
char := []rune(rest)
|
|
if len(char) == 1 && char[0] >= 'A' && char[0] <= 'Z' {
|
|
return tcell.KeyRune, char[0], tcell.ModAlt, nil
|
|
}
|
|
return 0, 0, 0, fmt.Errorf("invalid alt key: %q (expected Alt-A..Alt-Z or Alt-F1..Alt-F12)", s)
|
|
}
|
|
|
|
// Handle Shift-X notation
|
|
if strings.HasPrefix(upper, "SHIFT-") {
|
|
rest := upper[6:]
|
|
// Check for F-keys first (before treating F as a letter)
|
|
if key, ok := parseFunctionKey(rest); ok {
|
|
return key, 0, tcell.ModShift, nil
|
|
}
|
|
char := []rune(rest)
|
|
if len(char) == 1 && char[0] >= 'A' && char[0] <= 'Z' {
|
|
return tcell.KeyRune, char[0], tcell.ModShift, nil
|
|
}
|
|
return 0, 0, 0, fmt.Errorf("invalid shift key: %q (expected Shift-A..Shift-Z or Shift-F1..Shift-F12)", s)
|
|
}
|
|
|
|
// Check for standalone F-keys
|
|
if key, ok := parseFunctionKey(upper); ok {
|
|
return key, 0, 0, nil
|
|
}
|
|
|
|
// Otherwise require exactly one rune.
|
|
runes := []rune(s)
|
|
if len(runes) != 1 {
|
|
return 0, 0, 0, fmt.Errorf("invalid key: %q (expected single character, F1..F12, Ctrl-X, Alt-X, or Shift-X)", s)
|
|
}
|
|
return tcell.KeyRune, runes[0], 0, nil
|
|
}
|
|
|
|
// parseFunctionKey parses function key notation (F1, F2, ..., F12)
|
|
// Returns the tcell.Key constant and true if valid, 0 and false otherwise
|
|
func parseFunctionKey(s string) (tcell.Key, bool) {
|
|
if !strings.HasPrefix(s, "F") {
|
|
return 0, false
|
|
}
|
|
|
|
// Parse the number after 'F'
|
|
numStr := s[1:]
|
|
if numStr == "" {
|
|
return 0, false
|
|
}
|
|
|
|
// Simple integer parsing for 1-12
|
|
var num int
|
|
for _, ch := range numStr {
|
|
if ch < '0' || ch > '9' {
|
|
return 0, false
|
|
}
|
|
num = num*10 + int(ch-'0')
|
|
}
|
|
|
|
// Map to tcell key constants (F1 = KeyF1, F2 = KeyF2, etc.)
|
|
// tcell.KeyF1 = 279, KeyF2 = 280, ..., KeyF12 = 290
|
|
if num >= 1 && num <= 12 {
|
|
//nolint:gosec // G115: num is bounded 1-12, safe to convert to int16
|
|
return tcell.Key(278 + num), true
|
|
}
|
|
|
|
return 0, false
|
|
}
|