1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2026-05-22 10:15:43 +02:00
Files
lazygit/vendor/github.com/gdamore/tcell/v3/vt/layout.go
T
2026-04-30 22:14:26 +02:00

714 lines
18 KiB
Go

// Copyright 2026 The TCell Authors
//
// 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 vt
import (
"sort"
"sync"
"time"
)
// This file defines the layout mechanism for keyboards.
// All keyboards are assumed to pass "codes" that are mapped to one of
// the Key values (this is easy for USB because the codes are mostly the
// same as USB already!), and then we look up values. This can be done
// using a simple map, and we allow inheritance so we don't have to redefine
// too many things. For complex scenarios you'll probably want to make use
// of operating system facilities or supply your own tables (for example for
// Japanese keyboards.)
// ModifierMap is a map that represents keys that generate runes in various
// shift states (modifier states). A layout may have many of these, and they
// are searched until a match is fine.
type ModifierMap struct {
When func(Modifier) bool // Map only applies if this returns true
Map map[Key]rune // Map is the mapping from Key to specific rune when this map matches.
}
// KeyboardState represents the current state of the keyboard.
// A zero initialized value is ready for use. Note that emulators that
// get the associated events from their operating system do not need to
// make use of this, but this structure makes it possible to build an emulator
// with a keyboard layout that is not known to the operating system.
//
// The keyboard state is assumed to be "single threaded", meaning only a single
// caller will operate on it at any given time. Typically there is just a single
// keyboard polling thread or goroutine.
type KeyboardState struct {
deadKey map[rune]DeadKey // current dead key state
mod Modifier
layout *Layout
lastKey Key // last key pressed
lastRune rune // last rune for last key
repeating bool // true if we are repeating
repeatStart time.Time // when we started repeating
repeatTime time.Time // last time we checked
repeatDelay time.Duration // delay before starting repeat
repeatInterval time.Duration // duration between repeats
pressed map[Key]bool
initialized bool
}
// initialize the keyboard, lazily.
func (ks *KeyboardState) initialize() {
if !ks.initialized {
ks.pressed = make(map[Key]bool)
if ks.layout == nil {
ks.layout = KeyboardANSI
}
ks.repeatDelay = time.Millisecond * 250
ks.repeatInterval = time.Millisecond * 30
ks.initialized = true
}
}
// reset the keyboard state.
func (ks *KeyboardState) reset() {
ks.clearRepeat()
ks.mod = 0
ks.deadKey = nil
ks.pressed = make(map[Key]bool)
}
// clear repeat clears any repeating key.
func (ks *KeyboardState) clearRepeat() {
ks.repeating = false
ks.repeatStart = time.Time{}
ks.repeatTime = time.Time{}
ks.lastRune = 0
ks.lastKey = 0
}
// SetRepeat sets the repeat parameters. Note that this will only have any meaningful
// impact if the caller calls the Pressed function repeatedly (periodically) while
// a key is depressed.
//
// The repeat starts after a key has been held for for delay, with a new repeat
// added every interval.
//
// The caller should usually call this before processing keyboard events. It must
// not be called concurrently with either of the Pressed or Release functions.
func (ks *KeyboardState) SetRepeat(delay time.Duration, interval time.Duration) {
ks.initialize()
ks.repeatDelay = delay
ks.repeatInterval = interval
ks.clearRepeat()
}
// SetLayout sets the layout this keyboard should use.
// This also resets the keyboard state.
func (ks *KeyboardState) SetLayout(km *Layout) {
ks.initialize()
ks.reset()
ks.layout = km
}
// Pressed should be called when a given key is depressed.
func (ks *KeyboardState) Pressed(k Key) *KeyEvent {
ks.initialize()
event := &KeyEvent{
Down: true,
Key: k,
SC: k.ScanCode(),
Base: k.KittyBase(),
Mod: ks.mod,
}
// if another key was pressed, then clear the repeat state
lastKey := ks.lastKey
if lastKey != k && ks.repeatInterval != 0 {
ks.clearRepeat()
ks.repeatStart = time.Now().Add(ks.repeatDelay)
ks.repeatTime = ks.repeatStart
}
wasPressed := ks.pressed[k]
ks.pressed[k] = true
ks.lastKey = k
l := ks.layout
event.VK = l.Virtual[k]
if mod, ok := l.Locking[k]; ok {
// locking modifiers never repeat
if wasPressed {
return nil
}
if ks.mod&mod == 0 {
ks.mod |= mod
} else {
ks.mod &^= mod
}
ks.pressed[k] = true
event.Mod = ks.mod
return event
}
if mod, ok := l.Modifiers[k]; ok {
if wasPressed {
return nil
}
ks.mod |= mod
event.Mod = ks.mod
return event
}
// attempt to look up the rune for this
r := l.KeyToUTF(k, ks.mod)
if ks.deadKey == nil && l.DeadKeys != nil && r != 0 {
if dk, ok := l.DeadKeys[r]; ok {
ks.deadKey = dk.Next
return event
}
}
if dk := ks.deadKey; dk != nil {
if n, ok := dk[r]; ok {
if n.U != 0 {
event.Utf = string(n.U)
ks.lastRune = n.U
ks.deadKey = nil
} else {
ks.deadKey = n.Next
}
return event
}
// failed lookup - ignore it
ks.deadKey = nil
}
if r != 0 {
event.Utf = string(r)
}
if lastKey == k && wasPressed && ks.repeatInterval > 0 {
ks.repeating = true
if time.Now().After(ks.repeatStart) {
deltaT := time.Since(ks.repeatTime).Truncate(ks.repeatInterval)
event.Repeat = int(deltaT / ks.repeatInterval)
if ks.repeatTime == ks.repeatStart {
// fence post - count the first one!
event.Repeat++
}
ks.repeatTime = ks.repeatTime.Add(deltaT)
// if we polled before the repeat interval then report nothing
if event.Repeat == 0 {
return nil
}
}
}
return event
}
// Released should be called when a given key is Released.
func (ks *KeyboardState) Released(k Key) *KeyEvent {
ks.initialize()
event := &KeyEvent{
Down: false,
Key: k,
SC: k.ScanCode(),
Base: k.KittyBase(),
Mod: ks.mod,
}
if ks.lastKey == k {
ks.clearRepeat()
}
wasPressed := ks.pressed[k]
delete(ks.pressed, k)
l := ks.layout
event.VK = l.Virtual[k]
if mod, ok := ks.layout.Modifiers[k]; ok {
if !wasPressed {
// something weird
return nil
}
ks.mod &^= mod
event.Mod = ks.mod
return event
}
if _, ok := ks.layout.Locking[k]; !ok {
if r := l.KeyToUTF(k, ks.mod); r != 0 {
event.Utf = string(r)
}
}
// no real point in looking up UTF for key release, so we don't
return event
}
// DeadKey is what happens when a dead key is pressed. Either it starts an unresolved sequence,
// (in which case Next will be non-nil), or it resolves to a final rune (in which case U will be non-zero)
// This is also sometimes called a composed key sequence.
type DeadKey struct {
Next map[rune]DeadKey // Next corresponds to the next key in the sequence for dead keys
U rune // U is the rune that should be emitted on completion of the sequence.
}
// DeadRune is used internally to create a rune to represent a dead key.
// While any numbers can be used from this point on, the expectation is
// that this will be added to a smaller rune (such as an ASCII character)
// This rune value a bit inside the supplementary private use area B,
// in an attempt to minimize the chance of any conflicting use (for
// example nerd fonts.)
const DeadRune = 0x101000
// Layout is a structure that represents a keyboard layout.
// Applications or users may implement their own layouts by
// registering an instance of this.
type Layout struct {
// Name is the name of the keyboard layout.
// We prefer to use the same names that Microsoft uses for keyboard layouts.
Name string
// Base is a base keyboard layout, so that we can simplify by only
// overriding keys we are are handling differently.
Base *Layout
// DeadKeys maps specific starting keys, to an emitted rune.
// The keys should a rune value starting with DeadRune.
DeadKeys map[rune]DeadKey
// Locking are modifiers that toggle a locked state like NumLock or CapsLock.
Locking map[Key]Modifier
// Modifiers toggle a state, but only while the key is depressed.
Modifiers map[Key]Modifier
// Virtual maps keys to virtual keys.
Virtual map[Key]WinVK
// Maps is the list of key maps by modifier and mask.
// Use more specific masks first - for example NumLock
// mask might contain only the masks for the number keypad,
// and that should be listed before the maps for the rest
// of the keyboard. The algorithm searches for the first
// match, not the best match.
Maps []ModifierMap
}
func (km *Layout) KeyToUTF(k Key, m Modifier) rune {
for _, mm := range km.Maps {
if mm.When != nil && !mm.When(m) {
continue
}
if u, ok := mm.Map[k]; ok {
return u
}
}
if km.Base != nil {
return km.Base.KeyToUTF(k, m)
}
return 0
}
// KeysUsLower is a list of lower case key maps.
var KeysUsLower = map[Key]rune{
KeyA: 'a',
KeyB: 'b',
KeyC: 'c',
KeyD: 'd',
KeyE: 'e',
KeyF: 'f',
KeyG: 'g',
KeyH: 'h',
KeyI: 'i',
KeyJ: 'j',
KeyK: 'k',
KeyL: 'l',
KeyM: 'm',
KeyN: 'n',
KeyO: 'o',
KeyP: 'p',
KeyQ: 'q',
KeyR: 'r',
KeyS: 's',
KeyT: 't',
KeyU: 'u',
KeyV: 'v',
KeyW: 'w',
KeyX: 'x',
KeyY: 'y',
KeyZ: 'z',
}
// KeysUsUpper is a list of upper case key maps.
var KeysUsUpper = map[Key]rune{
KeyA: 'A',
KeyB: 'B',
KeyC: 'C',
KeyD: 'D',
KeyE: 'E',
KeyF: 'F',
KeyG: 'G',
KeyH: 'H',
KeyI: 'I',
KeyJ: 'J',
KeyK: 'K',
KeyL: 'L',
KeyM: 'M',
KeyN: 'N',
KeyO: 'O',
KeyP: 'P',
KeyQ: 'Q',
KeyR: 'R',
KeyS: 'S',
KeyT: 'T',
KeyU: 'U',
KeyV: 'V',
KeyW: 'W',
KeyX: 'X',
KeyY: 'Y',
KeyZ: 'Z',
}
// KeysDigits is a map of number keys to corresponding digits.
var KeysDigits = map[Key]rune{
Key1: '1',
Key2: '2',
Key3: '3',
Key4: '4',
Key5: '5',
Key6: '6',
Key7: '7',
Key8: '8',
Key9: '9',
Key0: '0',
}
// KeysPadDigits is a map of the digits on the numeric keypad,
// when num lock is engaged.
var KeysPadDigits = map[Key]rune{
KeyPad0: '0',
KeyPad1: '1',
KeyPad2: '2',
KeyPad3: '3',
KeyPad4: '4',
KeyPad5: '5',
KeyPad6: '6',
KeyPad7: '7',
KeyPad8: '8',
KeyPad9: '9',
KeyPadDec: '.',
}
var KeysPadOps = map[Key]rune{
KeyPadMul: '*',
KeyPadAdd: '+',
KeyPadSub: '-',
KeyPadDiv: '/',
}
// KeyboardANSI is the base PC keyboard used in ANSI (US) systems.
var KeyboardANSI = &Layout{
Name: "US",
Base: nil,
// Virtual maps physical keys to virtual keys.
Virtual: map[Key]WinVK{
KeyEsc: VkEscape,
KeyF1: VkF1,
KeyF2: VkF2,
KeyF3: VkF3,
KeyF4: VkF4,
KeyF5: VkF5,
KeyF6: VkF6,
KeyF7: VkF7,
KeyF8: VkF8,
KeyF9: VkF9,
KeyF10: VkF10,
KeyF11: VkF11,
KeyF12: VkF12,
KeyF13: VkF13,
KeyF14: VkF14,
KeyF15: VkF15,
KeyF16: VkF16,
KeyF17: VkF17,
KeyF18: VkF18,
KeyF19: VkF19,
KeyF20: VkF20,
KeyF21: VkF21,
KeyF22: VkF22,
KeyF23: VkF23,
KeyF24: VkF24,
KeyPrtScr: VkSnapshot,
KeyScrLock: VkScroll,
KeyPause: VkPause,
KeyInsert: VkInsert,
KeyDelete: VkDelete,
KeyHome: VkHome,
KeyEnd: VkEnd,
KeyPgUp: VkPrior,
KeyPgDn: VkNext,
KeyLeft: VkLeft,
KeyRight: VkRight,
KeyUp: VkUp,
KeyDown: VkDown,
KeyNumLock: VkNumLock,
KeyCapsLock: VkCapital,
KeyLShift: VkLShift,
KeyLCtrl: VkLControl,
KeyLMeta: VkLWin,
KeyLAlt: VkLMenu,
KeyRShift: VkRShift,
KeyRCtrl: VkRControl,
KeyRMeta: VkRWin,
KeyRAlt: VkRMenu,
KeyMenu: VkApps,
KeyConvert: VkConvert,
KeyNonConvert: VkNonConvert,
KeyBackspace: VkBack,
KeyEnter: VkReturn,
KeySpace: VkSpace,
KeyTab: VkTab,
Key1: Vk1,
Key2: Vk2,
Key3: Vk3,
Key4: Vk4,
Key5: Vk5,
Key6: Vk6,
Key7: Vk7,
Key8: Vk8,
Key9: Vk9,
Key0: Vk0,
KeyA: VkA,
KeyB: VkB,
KeyC: VkC,
KeyD: VkD,
KeyE: VkE,
KeyF: VkF,
KeyG: VkG,
KeyH: VkH,
KeyI: VkI,
KeyJ: VkJ,
KeyK: VkK,
KeyL: VkL,
KeyM: VkM,
KeyN: VkN,
KeyO: VkO,
KeyP: VkP,
KeyQ: VkQ,
KeyR: VkR,
KeyS: VkS,
KeyT: VkT,
KeyU: VkU,
KeyV: VkV,
KeyW: VkW,
KeyX: VkX,
KeyY: VkY,
KeyZ: VkZ,
KeyPadMul: VkMultiply,
KeyPadAdd: VkAdd,
KeyPadSub: VkSubtract,
KeyPadDiv: VkDivide,
KeyEqual: VkOemPlus,
KeyComma: VkOemComma,
KeyMinus: VkOemMinus,
KeyPeriod: VkOemPeriod,
KeySlash: VkOem2,
KeyGrave: VkOem3,
KeyLBrace: VkOem4,
KeyBackslash: VkOem5,
KeyRBrace: VkOem6,
KeyQuote: VkOem7,
KeyIsoBackSlash: VkOem102,
KeyPad0: VkInsert,
KeyPad1: VkEnd,
KeyPad2: VkDown,
KeyPad3: VkNext,
KeyPad4: VkLeft,
KeyPad5: VkClear,
KeyPad6: VkRight,
KeyPad7: VkHome,
KeyPad8: VkUp,
KeyPad9: VkPrior,
KeyPadDec: VkDelete,
},
Maps: []ModifierMap{
// Specials - without control
{
When: func(m Modifier) bool { return !m.IsCtrl() },
Map: map[Key]rune{
KeyTab: '\t',
KeyEnter: '\r',
KeyBackspace: '\b',
KeyEsc: '\x1b',
KeySpace: ' ',
KeyPadEnter: '\r',
},
},
// Specials - with control (but without shift)
{
When: func(m Modifier) bool { return m.IsCtrl() && !m.IsShift() },
Map: map[Key]rune{
KeyTab: '\t',
KeyEnter: '\n',
KeyBackspace: '\x7f',
KeyEsc: '\x1b',
KeySpace: ' ',
KeyPadEnter: '\n',
},
},
// Key pad operators
{
When: func(m Modifier) bool { return !m.IsAlt() },
Map: KeysPadOps,
},
// Numeric keypad when num lock is engaged
{
When: func(m Modifier) bool { return m.IsNumLock() },
Map: KeysPadDigits,
},
// Numbers - without shift
{
When: func(m Modifier) bool { return !m.IsShift() && !m.IsCtrl() },
Map: KeysDigits,
},
// Numbers - with shift - this is locale sensitive usually
{
When: func(m Modifier) bool { return m.IsShift() && !m.IsCtrl() },
Map: map[Key]rune{
Key1: '!',
Key2: '@',
Key3: '#',
Key4: '$',
Key5: '%',
Key6: '^',
Key7: '&',
Key8: '*',
Key9: '(',
Key0: ')',
},
},
// Special shift-control cases
{
When: func(m Modifier) bool { return m.IsCtrl() && m.IsShift() },
Map: map[Key]rune{
Key2: 0,
Key6: '\x1e',
KeyMinus: '\x1f',
},
},
// Letters - base (lower case)
{
When: func(m Modifier) bool { return !m.IsCtrl() && !m.IsCapitals() },
Map: KeysUsLower,
},
// Letters - capitals (either caps lock or shift, but not both)
{
When: func(m Modifier) bool { return !m.IsCtrl() && m.IsCapitals() },
Map: KeysUsUpper,
},
// OEM keys - base
{
When: func(m Modifier) bool { return !m.IsShift() && !m.IsCtrl() },
Map: map[Key]rune{
KeySemi: ';',
KeyEqual: '=',
KeyComma: ',',
KeyMinus: '-',
KeyPeriod: '.',
KeySlash: '/',
KeyGrave: '`',
KeyLBrace: '[',
KeyBackslash: '\\',
KeyRBrace: ']',
KeyQuote: '\'',
KeyIsoBackSlash: '\\',
},
},
// OEM keys - shift
{
When: func(m Modifier) bool { return m.IsShift() && !m.IsCtrl() },
Map: map[Key]rune{
KeySemi: ':',
KeyEqual: '+',
KeyComma: '<',
KeyMinus: '_',
KeyPeriod: '>',
KeySlash: '?',
KeyGrave: '~',
KeyLBrace: '{',
KeyBackslash: '|',
KeyRBrace: '}',
KeyQuote: '"',
KeyIsoBackSlash: '|',
},
},
// OEM keys - control (odd balls)
{
When: func(m Modifier) bool { return m.IsCtrl() && !m.IsShift() },
Map: map[Key]rune{
KeyLBrace: '\x1b',
KeyBackslash: '\x1c',
KeyRBrace: '\x1d',
KeyIsoBackSlash: '\x1c',
},
},
},
Modifiers: map[Key]Modifier{
KeyLShift: ModLShift,
KeyRShift: ModRShift,
KeyLCtrl: ModLCtrl,
KeyRCtrl: ModRCtrl,
KeyLAlt: ModLAlt,
KeyRAlt: ModRAlt,
KeyRMeta: ModRMeta,
KeyLMeta: ModLMeta,
KeyRHyper: ModRHyper,
KeyLHyper: ModLHyper,
},
Locking: map[Key]Modifier{
KeyNumLock: ModNumLock,
KeyCapsLock: ModCapsLock,
},
}
var allLayouts = map[string]*Layout{
KeyboardANSI.Name: KeyboardANSI,
}
var layoutsLock sync.Mutex
// RegisterLayout registers the given layout.
func RegisterLayout(km *Layout) {
layoutsLock.Lock()
allLayouts[km.Name] = km
layoutsLock.Unlock()
}
// GetLayout returns a keyboard layout for the given name.
// The layout must have been previously registered with RegisterLayout.
// (Builtin layouts do this as a consequence of importing the layout.)
func GetLayout(name string) *Layout {
layoutsLock.Lock()
defer layoutsLock.Unlock()
return allLayouts[name]
}
// Layouts returns a list of all known layout names.
func Layouts() []string {
layoutsLock.Lock()
defer layoutsLock.Unlock()
res := make([]string, 0, len(allLayouts))
for k := range allLayouts {
res = append(res, k)
}
sort.Strings(res)
return res
}