// 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 tests
import (
"testing"
"time"
"github.com/gdamore/tcell/v3/vt"
)
// TestScanCodes tests that the keys all have correct scan codes.
func TestScanCodes(t *testing.T) {
cases := []struct {
name string
key vt.Key
code vt.ScanCode
}{
{"A", vt.KeyA, 0x1e},
{"B", vt.KeyB, 0x30},
{"C", vt.KeyC, 0x2e},
{"D", vt.KeyD, 0x20},
{"E", vt.KeyE, 0x12},
{"F", vt.KeyF, 0x21},
{"G", vt.KeyG, 0x22},
{"H", vt.KeyH, 0x23},
{"I", vt.KeyI, 0x17},
{"J", vt.KeyJ, 0x24},
{"K", vt.KeyK, 0x25},
{"L", vt.KeyL, 0x26},
{"M", vt.KeyM, 0x32},
{"N", vt.KeyN, 0x31},
{"O", vt.KeyO, 0x18},
{"P", vt.KeyP, 0x19},
{"Q", vt.KeyQ, 0x10},
{"R", vt.KeyR, 0x13},
{"S", vt.KeyS, 0x1f},
{"T", vt.KeyT, 0x14},
{"U", vt.KeyU, 0x16},
{"V", vt.KeyV, 0x2f},
{"W", vt.KeyW, 0x11},
{"X", vt.KeyX, 0x2d},
{"Y", vt.KeyY, 0x15},
{"Z", vt.KeyZ, 0x2c},
{"1", vt.Key1, 0x02},
{"2", vt.Key2, 0x03},
{"3", vt.Key3, 0x04},
{"4", vt.Key4, 0x05},
{"5", vt.Key5, 0x06},
{"6", vt.Key6, 0x07},
{"7", vt.Key7, 0x08},
{"8", vt.Key8, 0x09},
{"9", vt.Key9, 0x0a},
{"0", vt.Key0, 0x0b},
{"enter", vt.KeyEnter, 0x1c},
{"escape", vt.KeyEsc, 0x01},
{"bs", vt.KeyBackspace, 0x0e},
{"tab", vt.KeyTab, 0x0f},
{"space", vt.KeySpace, 0x39},
{"minus", vt.KeyMinus, 0x0c},
{"equals", vt.KeyEqual, 0x0d},
{"lbrace", vt.KeyLBrace, 0x1a},
{"rbrace", vt.KeyRBrace, 0x1b},
{"backslash", vt.KeyBackslash, 0x2b},
{"semicolon", vt.KeySemi, 0x27},
{"quote", vt.KeyQuote, 0x28},
{"grave", vt.KeyGrave, 0x29},
{"comma", vt.KeyComma, 0x33},
{"period", vt.KeyPeriod, 0x34},
{"slash", vt.KeySlash, 0x35},
{"capsLock", vt.KeyCapsLock, 0x3a},
{"f1", vt.KeyF1, 0x3b},
{"f2", vt.KeyF2, 0x3c},
{"f3", vt.KeyF3, 0x3d},
{"f4", vt.KeyF4, 0x3e},
{"f5", vt.KeyF5, 0x3f},
{"f6", vt.KeyF6, 0x40},
{"f7", vt.KeyF7, 0x41},
{"f8", vt.KeyF8, 0x42},
{"f9", vt.KeyF9, 0x43},
{"f10", vt.KeyF10, 0x44},
{"f11", vt.KeyF11, 0x57},
{"f12", vt.KeyF12, 0x58},
{"prtScr", vt.KeyPrtScr, 0x54},
{"scrLock", vt.KeyScrLock, 0x46},
{"pause", vt.KeyPause, 0xe046},
{"ins", vt.KeyInsert, 0xe052},
{"home", vt.KeyHome, 0xe047},
{"pgUp", vt.KeyPgUp, 0xe049},
{"del", vt.KeyDelete, 0xe053},
{"end", vt.KeyEnd, 0xe04f},
{"pgDn", vt.KeyPgDn, 0xe051},
{"right", vt.KeyRight, 0xe04d},
{"left", vt.KeyLeft, 0xe04b},
{"down", vt.KeyDown, 0xe050},
{"up", vt.KeyUp, 0xe048},
{"numLock", vt.KeyNumLock, 0x45},
{"div", vt.KeyPadDiv, 0xe035},
{"mul", vt.KeyPadMul, 0x37},
{"sub", vt.KeyPadSub, 0x4a},
{"add", vt.KeyPadAdd, 0x4e},
{"enter", vt.KeyPadEnter, 0xe01c},
{"pad1", vt.KeyPad1, 0x4f},
{"pad2", vt.KeyPad2, 0x50},
{"pad3", vt.KeyPad3, 0x51},
{"pad4", vt.KeyPad4, 0x4b},
{"pad5", vt.KeyPad5, 0x4c},
{"pad6", vt.KeyPad6, 0x4d},
{"pad7", vt.KeyPad7, 0x47},
{"pad8", vt.KeyPad8, 0x48},
{"pad9", vt.KeyPad9, 0x49},
{"pad0", vt.KeyPad0, 0x52},
{"padDecimal", vt.KeyPadDec, 0x53},
{"isoBackSlash", vt.KeyIsoBackSlash, 0x56},
{"padEqual", vt.KeyPadEqual, 0x59},
{"f13", vt.KeyF13, 0x64},
{"f14", vt.KeyF14, 0x65},
{"f15", vt.KeyF15, 0x66},
{"f16", vt.KeyF16, 0x67},
{"f17", vt.KeyF17, 0x68},
{"f18", vt.KeyF18, 0x69},
{"f19", vt.KeyF19, 0x6a},
{"f20", vt.KeyF20, 0x6b},
{"f21", vt.KeyF21, 0x6c},
{"f22", vt.KeyF22, 0x6d},
{"f23", vt.KeyF23, 0x6e},
{"f24", vt.KeyF24, 0x76},
{"padComma", vt.KeyPadComma, 0x7e},
{"padIsoSlash", vt.KeyIsoSlash, 0x73},
{"hiragana", vt.KeyHiragana, 0x70},
{"yen", vt.KeyYen, 0x7d},
{"convert", vt.KeyConvert, 0x79},
{"noConvert", vt.KeyNonConvert, 0x7b},
{"leftCtrl", vt.KeyLCtrl, 0x1d},
{"leftShift", vt.KeyLShift, 0x2a},
{"leftAlt", vt.KeyLAlt, 0x38},
{"leftMeta", vt.KeyLMeta, 0xe05b},
{"rightCtrl", vt.KeyRCtrl, 0xe01d},
{"rightShift", vt.KeyRShift, 0x36},
{"rightAlt", vt.KeyRAlt, 0xe038},
{"rightMeta", vt.KeyRMeta, 0xe05c},
{"menu", vt.KeyMenu, 0xe05d},
}
for i := range cases {
t.Run(cases[i].name, func(t *testing.T) {
VerifyF(t, cases[i].key.ScanCode() == cases[i].code, "Scan code %x did not match %x", cases[i].key.ScanCode(), cases[i].code)
})
}
// Let's also make sure that we have no duplicate scan codes.
seen := make(map[vt.ScanCode]vt.Key)
for i := range cases {
sc := cases[i].key.ScanCode()
if other, ok := seen[sc]; ok {
t.Errorf("Duplicate scan %x code for key %x and %x", sc, cases[i].key, other)
}
seen[sc] = cases[i].key
}
}
// TestBaseKeys tests that all the "base" keys have correct values and correct shifted values.
// These are the values used for the Kitty protocol.
func TestBaseKeys(t *testing.T) {
cases := []struct {
name string
key vt.Key
base vt.BaseKey
shifted rune
}{
{"A", vt.KeyA, 'a', 'A'},
{"B", vt.KeyB, 'b', 'B'},
{"C", vt.KeyC, 'c', 'C'},
{"D", vt.KeyD, 'd', 'D'},
{"E", vt.KeyE, 'e', 'E'},
{"F", vt.KeyF, 'f', 'F'},
{"G", vt.KeyG, 'g', 'G'},
{"H", vt.KeyH, 'h', 'H'},
{"I", vt.KeyI, 'i', 'I'},
{"J", vt.KeyJ, 'j', 'J'},
{"K", vt.KeyK, 'k', 'K'},
{"L", vt.KeyL, 'l', 'L'},
{"M", vt.KeyM, 'm', 'M'},
{"N", vt.KeyN, 'n', 'N'},
{"O", vt.KeyO, 'o', 'O'},
{"P", vt.KeyP, 'p', 'P'},
{"Q", vt.KeyQ, 'q', 'Q'},
{"R", vt.KeyR, 'r', 'R'},
{"S", vt.KeyS, 's', 'S'},
{"T", vt.KeyT, 't', 'T'},
{"U", vt.KeyU, 'u', 'U'},
{"V", vt.KeyV, 'v', 'V'},
{"W", vt.KeyW, 'w', 'W'},
{"X", vt.KeyX, 'x', 'X'},
{"Y", vt.KeyY, 'y', 'Y'},
{"Z", vt.KeyZ, 'z', 'Z'},
{"1", vt.Key1, '1', '!'},
{"2", vt.Key2, '2', '@'},
{"3", vt.Key3, '3', '#'},
{"4", vt.Key4, '4', '$'},
{"5", vt.Key5, '5', '%'},
{"6", vt.Key6, '6', '^'},
{"7", vt.Key7, '7', '&'},
{"8", vt.Key8, '8', '*'},
{"9", vt.Key9, '9', '('},
{"0", vt.Key0, '0', ')'},
{"minus", vt.KeyMinus, '-', '_'},
{"equal", vt.KeyEqual, '=', '+'},
{"lbrace", vt.KeyLBrace, '[', '{'},
{"backslash", vt.KeyBackslash, '\\', '|'},
{"rbrace", vt.KeyRBrace, ']', '}'},
{"semi", vt.KeySemi, ';', ':'},
{"backslash-iso", vt.KeyIsoBackSlash, '\\', '|'},
{"comma", vt.KeyComma, ',', '<'},
{"period", vt.KeyPeriod, '.', '>'},
{"slash", vt.KeySlash, '/', '?'},
{"yen", vt.KeyYen, '¥', '|'},
}
for i := range cases {
t.Run(cases[i].name, func(t *testing.T) {
base := cases[i].key.KittyBase()
shift := base.Shifted()
VerifyF(t, base == cases[i].base, "Base code %x did not match %x", base, cases[i].base)
VerifyF(t, shift == cases[i].shifted, "Base code %q %x did not match %q %x", string(shift), shift, string(cases[i].shifted), cases[i].shifted)
})
}
}
// TestKeyRepeat tests simple key repeat
func TestKeyRepeat(t *testing.T) {
term := vt.NewMockTerm()
defer MustClose(t, term)
MustStart(t, term)
// Keep the initial repeat quick, but make later repeats very slow so the test
// does not depend on sub-100ms timer precision.
term.SetRepeat(time.Millisecond*50, time.Second)
term.KeyPress(vt.KeyX)
time.Sleep(100 * time.Millisecond)
term.KeyPress(vt.KeyX)
term.KeyRelease(vt.KeyX)
term.KeyRelease(vt.KeyCapsLock)
CheckRead(t, term, "xx")
}
// TestKeyRepeatDisabled tests simple key repeat
func TestKeyRepeatDisabled(t *testing.T) {
term := vt.NewMockTerm()
defer MustClose(t, term)
MustStart(t, term)
// Keep the initial repeat quick, but make later repeats very slow so the test
// only needs to distinguish repeated from non-repeated input.
term.SetRepeat(time.Millisecond*50, time.Second)
WriteF(t, term, "\x1b[?8l")
term.KeyPress(vt.KeyX)
time.Sleep(100 * time.Millisecond)
term.KeyPress(vt.KeyX)
term.KeyRelease(vt.KeyX)
CheckRead(t, term, "x")
}
// TestKeyRepeatCapsLock ensures that caps lock does not repeat
func TestKeyRepeatCapsLock(t *testing.T) {
term := vt.NewMockTerm()
defer MustClose(t, term)
MustStart(t, term)
// Use a large repeat interval so the test is not sitting on a timing boundary.
// We only need to prove that caps lock does not emit its own repeat.
term.SetRepeat(time.Millisecond*50, time.Millisecond*250)
term.KeyPress(vt.KeyCapsLock)
term.KeyPress(vt.KeyZ)
time.Sleep(90 * time.Millisecond)
term.KeyPress(vt.KeyZ)
term.KeyRelease(vt.KeyZ)
term.KeyRelease(vt.KeyCapsLock)
CheckRead(t, term, "ZZ") // 0 ms, 50 ms
}
// TestKeyRepeatNoAlt ensures that alt keys do not repeat
func TestKeyRepeatNoAlt(t *testing.T) {
term := vt.NewMockTerm()
defer MustClose(t, term)
MustStart(t, term)
// Keep the initial repeat quick, but make later repeats very slow so the test
// does not sit on a timer boundary.
term.SetRepeat(time.Millisecond*50, time.Second)
term.KeyPress(vt.KeyLAlt)
term.KeyPress(vt.KeyZ)
time.Sleep(100 * time.Millisecond)
term.KeyPress(vt.KeyZ)
term.KeyRelease(vt.KeyZ)
term.KeyRelease(vt.KeyLAlt)
CheckRead(t, term, "\x1bz")
}
// TestKeyRepeatShift ensures that shifted keys still work as long as repeat is held down.
func TestKeyRepeatShift(t *testing.T) {
term := vt.NewMockTerm()
defer MustClose(t, term)
MustStart(t, term)
// Use a large repeat interval so the test is not sitting on a timing boundary.
// We only need to prove that shifted keys keep their shifted value while repeating.
term.SetRepeat(time.Millisecond*50, time.Millisecond*250)
term.KeyPress(vt.KeyLShift)
term.KeyPress(vt.Key1)
time.Sleep(90 * time.Millisecond)
term.KeyPress(vt.Key1)
term.KeyRelease(vt.Key1)
term.KeyRelease(vt.KeyLShift)
CheckRead(t, term, "!!") // 0 ms, 50 ms
}
// TestKeyRepeatShiftRelease ensures that releasing shift breaks repeat.
func TestKeyRepeatShiftRelease(t *testing.T) {
term := vt.NewMockTerm()
defer MustClose(t, term)
MustStart(t, term)
// Use a large repeat interval so the test is not sitting on a timing boundary.
// We only need to prove that releasing shift changes the repeated value.
term.SetRepeat(time.Millisecond*50, time.Millisecond*250)
term.KeyPress(vt.KeyLShift)
term.KeyPress(vt.Key2)
term.KeyRelease(vt.KeyLShift)
time.Sleep(90 * time.Millisecond)
term.KeyPress(vt.Key2)
term.KeyRelease(vt.Key2)
CheckRead(t, term, "@2") // 0 ms, 50 ms
}
func TestKeyRepeatCursor(t *testing.T) {
term := vt.NewMockTerm()
defer MustClose(t, term)
MustStart(t, term)
// Keep the initial repeat quick, but make later repeats very slow so the test
// does not depend on coarse timer behavior.
term.SetRepeat(time.Millisecond*50, time.Second)
term.KeyPress(vt.KeyRight)
time.Sleep(100 * time.Millisecond)
term.KeyPress(vt.KeyRight)
term.KeyRelease(vt.KeyRight)
term.KeyRelease(vt.KeyRight)
CheckRead(t, term, "\x1b[C\x1b[C")
}
func TestKeyWin32(t *testing.T) {
// Win32 input mode is ESC [ Vk ; Sc ; Uc ; Kd ; Cs ; Rc _
term := vt.NewMockTerm()
defer MustClose(t, term)
MustStart(t, term)
WriteF(t, term, "%s", vt.PmWin32Input.Enable())
var want string
term.KeyTap(vt.KeySpace)
want = "\x1b[32;57;32;1;0;1_" + "\x1b[32;57;32;0;0;1_"
CheckRead(t, term, want)
term.KeyTap(vt.KeyLAlt, vt.KeyG)
want = "\x1b[164;56;0;1;2;1_" + // LAlt down
"\x1b[71;34;103;1;2;1_" + // G down
"\x1b[71;34;103;0;2;1_" + // G up
"\x1b[164;56;0;0;0;1_" // LAlt up
CheckRead(t, term, want)
term.KeyTap(vt.KeyRAlt, vt.Key1)
want = "\x1b[165;57400;0;1;1;1_" + // RAlt down
"\x1b[49;2;49;1;1;1_" + // 1 down
"\x1b[49;2;49;0;1;1_" + // 1 up
"\x1b[165;57400;0;0;0;1_" // RAlt up
CheckRead(t, term, want)
term.KeyTap(vt.KeyRCtrl, vt.KeyF1)
want = "\x1b[163;57373;0;1;4;1_" + // RCtrl down
"\x1b[112;59;0;1;4;1_" + // F1 down
"\x1b[112;59;0;0;4;1_" + // F1 up
"\x1b[163;57373;0;0;0;1_" // RCtrl up
CheckRead(t, term, want)
// NB: Windows has no "back tab" notion
term.KeyTap(vt.KeyLShift, vt.KeyTab)
want = "\x1b[160;42;0;1;16;1_\x1b[9;15;9;1;16;1_\x1b[9;15;9;0;16;1_\x1b[160;42;0;0;0;1_"
CheckRead(t, term, want)
term.KeyTap(vt.KeyNumLock)
term.KeyTap(vt.KeyPad0)
term.KeyTap(vt.KeyNumLock)
want = "\x1b[144;69;0;1;32;1_" + // num lock down
"\x1b[144;69;0;0;32;1_" + // num lock up
"\x1b[45;82;48;1;32;1_" + // key pad 0 down
"\x1b[45;82;48;0;32;1_" + // keypad 0 up
"\x1b[144;69;0;1;0;1_" + // num lock down
"\x1b[144;69;0;0;0;1_" // num lock up
CheckRead(t, term, want)
term.KeyTap(vt.KeyCapsLock)
term.KeyTap(vt.KeyJ)
term.KeyTap(vt.KeyCapsLock)
want = "\x1b[20;58;0;1;128;1_" + // caps lock down
"\x1b[20;58;0;0;128;1_" + // caps lock up
"\x1b[74;36;74;1;128;1_" + // J down
"\x1b[74;36;74;0;128;1_" + // J up
"\x1b[20;58;0;1;0;1_" + // caps lock down
"\x1b[20;58;0;0;0;1_" // caps lock up
CheckRead(t, term, want)
term.KeyTap(vt.KeyPadEnter)
want = "\x1b[0;57372;13;1;0;1_\x1b[0;57372;13;0;0;1_"
CheckRead(t, term, want)
term.KeyTap(vt.KeyPadDiv)
want = "\x1b[111;57397;47;1;0;1_\x1b[111;57397;47;0;0;1_"
CheckRead(t, term, want)
term.KeyTap(vt.KeyInsert)
want = "\x1b[45;57426;0;1;0;1_\x1b[45;57426;0;0;0;1_"
CheckRead(t, term, want)
term.KeyTap(vt.KeyDelete)
want = "\x1b[46;57427;0;1;0;1_\x1b[46;57427;0;0;0;1_"
CheckRead(t, term, want)
term.KeyTap(vt.KeyPgUp)
want = "\x1b[33;57417;0;1;0;1_\x1b[33;57417;0;0;0;1_"
CheckRead(t, term, want)
term.KeyTap(vt.KeyPgDn)
want = "\x1b[34;57425;0;1;256;1_\x1b[34;57425;0;0;256;1_"
CheckRead(t, term, want)
term.KeyTap(vt.KeyHome)
want = "\x1b[36;57415;0;1;0;1_\x1b[36;57415;0;0;0;1_"
CheckRead(t, term, want)
term.KeyTap(vt.KeyEnd)
want = "\x1b[35;57423;0;1;0;1_\x1b[35;57423;0;0;0;1_"
CheckRead(t, term, want)
term.KeyTap(vt.KeyEnter)
want = "\x1b[13;28;13;1;0;1_\x1b[13;28;13;0;0;1_"
CheckRead(t, term, want)
term.KeyTap(vt.KeyLMeta)
want = "\x1b[91;57435;0;1;0;1_\x1b[91;57435;0;0;0;1_"
CheckRead(t, term, want)
}
func TestKeyWin32NoRepeat(t *testing.T) {
term := vt.NewMockTerm()
defer MustClose(t, term)
// Keep the first repeat available, but avoid asserting an exact number of
// repeats from short sleeps on platforms with coarse timers.
term.SetRepeat(time.Millisecond*50, time.Second)
MustStart(t, term)
WriteF(t, term, "%s", vt.PmWin32Input.Enable())
WriteF(t, term, "%s", vt.PmAutoRepeat.Enable())
term.KeyPress(vt.KeyPause)
time.Sleep(time.Millisecond * 100)
term.KeyPress(vt.KeyPause)
term.KeyRelease(vt.KeyPause)
want := "\x1b[19;57414;0;1;0;1_\x1b[19;57414;0;0;0;1_"
CheckRead(t, term, want)
}
func TestKeyWin32Repeat(t *testing.T) {
term := vt.NewMockTerm()
defer MustClose(t, term)
// Keep the first repeat available, but avoid asserting an exact number of
// repeats from short sleeps on platforms with coarse timers.
term.SetRepeat(time.Millisecond*50, time.Second)
MustStart(t, term)
WriteF(t, term, "%s", vt.PmWin32Input.Enable())
WriteF(t, term, "%s", vt.PmAutoRepeat.Enable())
term.KeyPress(vt.KeyA)
time.Sleep(time.Millisecond * 100)
term.KeyPress(vt.KeyA)
term.KeyRelease(vt.KeyA)
want := "\x1b[65;30;97;1;0;1_\x1b[65;30;97;1;0;1_\x1b[65;30;97;0;0;1_"
CheckRead(t, term, want)
}
func TestKeyWin32NoRepeatMode(t *testing.T) {
term := vt.NewMockTerm()
defer MustClose(t, term)
// Keep the first repeat available, but make later repeats slow so this only
// verifies that auto-repeat mode suppresses repeat events.
term.SetRepeat(time.Millisecond*50, time.Second)
MustStart(t, term)
WriteF(t, term, "%s", vt.PmWin32Input.Enable())
WriteF(t, term, "%s", vt.PmAutoRepeat.Disable())
term.KeyPress(vt.KeyB)
time.Sleep(time.Millisecond * 100)
term.KeyPress(vt.KeyB)
term.KeyRelease(vt.KeyB)
want := "\x1b[66;48;98;1;0;1_\x1b[66;48;98;0;0;1_"
CheckRead(t, term, want)
}