parse.go

   1package uv
   2
   3import (
   4	"bytes"
   5	"encoding/base64"
   6	"encoding/hex"
   7	"fmt"
   8	"image/color"
   9	"math"
  10	"slices"
  11	"strings"
  12	"unicode"
  13	"unicode/utf8"
  14
  15	"github.com/charmbracelet/x/ansi"
  16	"github.com/charmbracelet/x/ansi/parser"
  17	"github.com/rivo/uniseg"
  18)
  19
  20// Flags to control the behavior of the parser.
  21const (
  22	// When this flag is set, the driver will treat both Ctrl+Space and Ctrl+@
  23	// as the same key sequence.
  24	//
  25	// Historically, the ANSI specs generate NUL (0x00) on both the Ctrl+Space
  26	// and Ctrl+@ key sequences. This flag allows the driver to treat both as
  27	// the same key sequence.
  28	flagCtrlAt = 1 << iota
  29
  30	// When this flag is set, the driver will treat the Tab key and Ctrl+I as
  31	// the same key sequence.
  32	//
  33	// Historically, the ANSI specs generate HT (0x09) on both the Tab key and
  34	// Ctrl+I. This flag allows the driver to treat both as the same key
  35	// sequence.
  36	flagCtrlI
  37
  38	// When this flag is set, the driver will treat the Enter key and Ctrl+M as
  39	// the same key sequence.
  40	//
  41	// Historically, the ANSI specs generate CR (0x0D) on both the Enter key
  42	// and Ctrl+M. This flag allows the driver to treat both as the same key.
  43	flagCtrlM
  44
  45	// When this flag is set, the driver will treat Escape and Ctrl+[ as
  46	// the same key sequence.
  47	//
  48	// Historically, the ANSI specs generate ESC (0x1B) on both the Escape key
  49	// and Ctrl+[. This flag allows the driver to treat both as the same key
  50	// sequence.
  51	flagCtrlOpenBracket
  52
  53	// When this flag is set, the driver will send a BS (0x08 byte) character
  54	// instead of a DEL (0x7F byte) character when the Backspace key is
  55	// pressed.
  56	//
  57	// The VT100 terminal has both a Backspace and a Delete key. The VT220
  58	// terminal dropped the Backspace key and replaced it with the Delete key.
  59	// Both terminals send a DEL character when the Delete key is pressed.
  60	// Modern terminals and PCs later readded the Delete key but used a
  61	// different key sequence, and the Backspace key was standardized to send a
  62	// DEL character.
  63	flagBackspace
  64
  65	// When this flag is set, the driver will recognize the Find key instead of
  66	// treating it as a Home key.
  67	//
  68	// The Find key was part of the VT220 keyboard, and is no longer used in
  69	// modern day PCs.
  70	flagFind
  71
  72	// When this flag is set, the driver will recognize the Select key instead
  73	// of treating it as a End key.
  74	//
  75	// The Symbol key was part of the VT220 keyboard, and is no longer used in
  76	// modern day PCs.
  77	flagSelect
  78
  79	// When this flag is set, the driver will preserve function keys (F13-F63)
  80	// as symbols.
  81	//
  82	// Since these keys are not part of today's standard 20th century keyboard,
  83	// we treat them as F1-F12 modifier keys i.e. ctrl/shift/alt + Fn combos.
  84	// Key definitions come from Terminfo, this flag is only useful when
  85	// FlagTerminfo is not set.
  86	flagFKeys
  87)
  88
  89// LegacyKeyEncoding is a set of flags that control the behavior of legacy
  90// terminal key encodings. Historically, ctrl+<key> input events produce
  91// control characters (0x00-0x1F) that collide with some special keys like Tab,
  92// Enter, and Escape. This controls the expected behavior of encoding these
  93// keys.
  94//
  95// This type has the following default values:
  96//   - CtrlAt maps 0x00 [ansi.NUL] to ctrl+space instead of ctrl+@.
  97//   - CtrlI maps 0x09 [ansi.HT] to the tab key instead of ctrl+i.
  98//   - CtrlM maps 0x0D [ansi.CR] to the enter key instead of ctrl+m.
  99//   - CtrlOpenBracket maps 0x1B [ansi.ESC] to the escape key instead of ctrl+[.
 100//   - Backspace maps the backspace key to 0x08 [ansi.BS] instead of 0x7F [ansi.DEL].
 101//   - Find maps the legacy find key to the home key.
 102//   - Select maps the legacy select key to the end key.
 103//   - FKeys maps high function keys instead of treating them as Function+<modifiers>.
 104type LegacyKeyEncoding uint32
 105
 106// CtrlAt returns a [LegacyKeyEncoding] with whether [ansi.NUL] (0x00)
 107// is mapped to ctrl+at instead of ctrl+space.
 108func (l LegacyKeyEncoding) CtrlAt(v bool) LegacyKeyEncoding {
 109	if v {
 110		l |= flagCtrlAt
 111	} else {
 112		l &^= flagCtrlAt
 113	}
 114	return l
 115}
 116
 117// CtrlI returns a [LegacyKeyEncoding] with whether [ansi.HT] (0x09)
 118// is mapped to ctrl+i instead of the tab key.
 119func (l LegacyKeyEncoding) CtrlI(v bool) LegacyKeyEncoding {
 120	if v {
 121		l |= flagCtrlI
 122	} else {
 123		l &^= flagCtrlI
 124	}
 125	return l
 126}
 127
 128// CtrlM returns a [LegacyKeyEncoding] with whether [ansi.CR] (0x0D)
 129// is mapped to ctrl+m instead of the enter key.
 130func (l LegacyKeyEncoding) CtrlM(v bool) LegacyKeyEncoding {
 131	if v {
 132		l |= flagCtrlM
 133	} else {
 134		l &^= flagCtrlM
 135	}
 136	return l
 137}
 138
 139// CtrlOpenBracket returns a [LegacyKeyEncoding] with whether [ansi.ESC] (0x1B)
 140// is mapped to ctrl+[ instead of the escape key.
 141func (l LegacyKeyEncoding) CtrlOpenBracket(v bool) LegacyKeyEncoding {
 142	if v {
 143		l |= flagCtrlOpenBracket
 144	} else {
 145		l &^= flagCtrlOpenBracket
 146	}
 147	return l
 148}
 149
 150// Backspace returns a [LegacyKeyEncoding] with whether the backspace key is
 151// mapped to [ansi.BS] (0x08) instead of [ansi.DEL] (0x7F).
 152func (l LegacyKeyEncoding) Backspace(v bool) LegacyKeyEncoding {
 153	if v {
 154		l |= flagBackspace
 155	} else {
 156		l &^= flagBackspace
 157	}
 158	return l
 159}
 160
 161// Find returns a [LegacyKeyEncoding] with whether the legacy find key
 162// is mapped to the home key.
 163func (l LegacyKeyEncoding) Find(v bool) LegacyKeyEncoding {
 164	if v {
 165		l |= flagFind
 166	} else {
 167		l &^= flagFind
 168	}
 169	return l
 170}
 171
 172// Select returns a [LegacyKeyEncoding] with whether the legacy select key
 173// is mapped to the end key.
 174func (l LegacyKeyEncoding) Select(v bool) LegacyKeyEncoding {
 175	if v {
 176		l |= flagSelect
 177	} else {
 178		l &^= flagSelect
 179	}
 180	return l
 181}
 182
 183// FKeys returns a [LegacyKeyEncoding] with whether high function keys are
 184// mapped to high function keys (beyond F20) instead of treating them as
 185// Function+<modifiers> keys.
 186func (l LegacyKeyEncoding) FKeys(v bool) LegacyKeyEncoding {
 187	if v {
 188		l |= flagFKeys
 189	} else {
 190		l &^= flagFKeys
 191	}
 192	return l
 193}
 194
 195// SequenceParser parses unicode, control, and escape sequences into events. It
 196// is used to parse input events from the terminal input buffer and generate
 197// events that can be used by the application.
 198type SequenceParser struct {
 199	// Legacy is the legacy key encoding flags. These flags control the
 200	// behavior of legacy terminal key encodings. See [LegacyKeyEncoding] for
 201	// more details.
 202	Legacy LegacyKeyEncoding
 203	// UseTerminfo is a flag that controls whether to use the terminal type
 204	// Terminfo database to map escape sequences to key events. This will
 205	// override the default key sequences handled by the parser.
 206	UseTerminfo bool
 207}
 208
 209// parseSequence finds the first recognized event sequence and returns it along
 210// with its length.
 211//
 212// It will return zero and nil no sequence is recognized or when the buffer is
 213// empty. If a sequence is not supported, an UnknownEvent is returned.
 214func (p *SequenceParser) parseSequence(buf []byte) (n int, Event Event) {
 215	if len(buf) == 0 {
 216		return 0, nil
 217	}
 218
 219	switch b := buf[0]; b {
 220	case ansi.ESC:
 221		if len(buf) == 1 {
 222			// Escape key
 223			return 1, KeyPressEvent{Code: KeyEscape}
 224		}
 225
 226		switch bPrime := buf[1]; bPrime {
 227		case 'O': // Esc-prefixed SS3
 228			return p.parseSs3(buf)
 229		case 'P': // Esc-prefixed DCS
 230			return p.parseDcs(buf)
 231		case '[': // Esc-prefixed CSI
 232			return p.parseCsi(buf)
 233		case ']': // Esc-prefixed OSC
 234			return p.parseOsc(buf)
 235		case '_': // Esc-prefixed APC
 236			return p.parseApc(buf)
 237		case '^': // Esc-prefixed PM
 238			return p.parseStTerminated(ansi.PM, '^', nil)(buf)
 239		case 'X': // Esc-prefixed SOS
 240			return p.parseStTerminated(ansi.SOS, 'X', nil)(buf)
 241		default:
 242			n, e := p.parseSequence(buf[1:])
 243			if k, ok := e.(KeyPressEvent); ok {
 244				k.Text = ""
 245				k.Mod |= ModAlt
 246				return n + 1, k
 247			}
 248
 249			// Not a key sequence, nor an alt modified key sequence. In that
 250			// case, just report a single escape key.
 251			return 1, KeyPressEvent{Code: KeyEscape}
 252		}
 253	case ansi.SS3:
 254		return p.parseSs3(buf)
 255	case ansi.DCS:
 256		return p.parseDcs(buf)
 257	case ansi.CSI:
 258		return p.parseCsi(buf)
 259	case ansi.OSC:
 260		return p.parseOsc(buf)
 261	case ansi.APC:
 262		return p.parseApc(buf)
 263	case ansi.PM:
 264		return p.parseStTerminated(ansi.PM, '^', nil)(buf)
 265	case ansi.SOS:
 266		return p.parseStTerminated(ansi.SOS, 'X', nil)(buf)
 267	default:
 268		if b <= ansi.US || b == ansi.DEL || b == ansi.SP {
 269			return 1, p.parseControl(b)
 270		} else if b >= ansi.PAD && b <= ansi.APC {
 271			// C1 control code
 272			// UTF-8 never starts with a C1 control code
 273			// Encode these as Ctrl+Alt+<code - 0x40>
 274			code := rune(b) - 0x40
 275			return 1, KeyPressEvent{Code: code, Mod: ModCtrl | ModAlt}
 276		}
 277		return p.parseUtf8(buf)
 278	}
 279}
 280
 281func (p *SequenceParser) parseCsi(b []byte) (int, Event) {
 282	if len(b) == 2 && b[0] == ansi.ESC {
 283		// short cut if this is an alt+[ key
 284		return 2, KeyPressEvent{Code: rune(b[1]), Mod: ModAlt}
 285	}
 286
 287	var cmd ansi.Cmd
 288	var params [parser.MaxParamsSize]ansi.Param
 289	var paramsLen int
 290
 291	var i int
 292	if b[i] == ansi.CSI || b[i] == ansi.ESC {
 293		i++
 294	}
 295	if i < len(b) && b[i-1] == ansi.ESC && b[i] == '[' {
 296		i++
 297	}
 298
 299	// Initial CSI byte
 300	if i < len(b) && b[i] >= '<' && b[i] <= '?' {
 301		cmd |= ansi.Cmd(b[i]) << parser.PrefixShift
 302	}
 303
 304	// Scan parameter bytes in the range 0x30-0x3F
 305	var j int
 306	for j = 0; i < len(b) && paramsLen < len(params) && b[i] >= 0x30 && b[i] <= 0x3F; i, j = i+1, j+1 {
 307		if b[i] >= '0' && b[i] <= '9' {
 308			if params[paramsLen] == parser.MissingParam {
 309				params[paramsLen] = 0
 310			}
 311			params[paramsLen] *= 10
 312			params[paramsLen] += ansi.Param(b[i]) - '0'
 313		}
 314		if b[i] == ':' {
 315			params[paramsLen] |= parser.HasMoreFlag
 316		}
 317		if b[i] == ';' || b[i] == ':' {
 318			paramsLen++
 319			if paramsLen < len(params) {
 320				// Don't overflow the params slice
 321				params[paramsLen] = parser.MissingParam
 322			}
 323		}
 324	}
 325
 326	if j > 0 && paramsLen < len(params) {
 327		// has parameters
 328		paramsLen++
 329	}
 330
 331	// Scan intermediate bytes in the range 0x20-0x2F
 332	var intermed byte
 333	for ; i < len(b) && b[i] >= 0x20 && b[i] <= 0x2F; i++ {
 334		intermed = b[i]
 335	}
 336
 337	// Set the intermediate byte
 338	cmd |= ansi.Cmd(intermed) << parser.IntermedShift
 339
 340	// Scan final byte in the range 0x40-0x7E
 341	if i >= len(b) || b[i] < 0x40 || b[i] > 0x7E {
 342		// Special case for URxvt keys
 343		// CSI <number> $ is an invalid sequence, but URxvt uses it for
 344		// shift modified keys.
 345		if b[i-1] == '$' {
 346			n, ev := p.parseCsi(append(b[:i-1], '~'))
 347			if k, ok := ev.(KeyPressEvent); ok {
 348				k.Mod |= ModShift
 349				return n, k
 350			}
 351		}
 352		return i, UnknownEvent(b[:i-1])
 353	}
 354
 355	// Add the final byte
 356	cmd |= ansi.Cmd(b[i])
 357	i++
 358
 359	pa := ansi.Params(params[:paramsLen])
 360	switch cmd {
 361	case 'y' | '?'<<parser.PrefixShift | '$'<<parser.IntermedShift:
 362		// Report Mode (DECRPM)
 363		mode, _, ok := pa.Param(0, -1)
 364		if !ok || mode == -1 {
 365			break
 366		}
 367		value, _, ok := pa.Param(1, -1)
 368		if !ok || value == -1 {
 369			break
 370		}
 371		return i, ModeReportEvent{Mode: ansi.DECMode(mode), Value: ansi.ModeSetting(value)}
 372	case 'c' | '?'<<parser.PrefixShift:
 373		// Primary Device Attributes
 374		return i, parsePrimaryDevAttrs(pa)
 375	case 'u' | '?'<<parser.PrefixShift:
 376		// Kitty keyboard flags
 377		flags, _, ok := pa.Param(0, -1)
 378		if !ok || flags == -1 {
 379			break
 380		}
 381		return i, KittyEnhancementsEvent(flags)
 382	case 'R' | '?'<<parser.PrefixShift:
 383		// This report may return a third parameter representing the page
 384		// number, but we don't really need it.
 385		row, _, ok := pa.Param(0, 1)
 386		if !ok {
 387			break
 388		}
 389		col, _, ok := pa.Param(1, 1)
 390		if !ok {
 391			break
 392		}
 393		return i, CursorPositionEvent{Y: row - 1, X: col - 1}
 394	case 'm' | '<'<<parser.PrefixShift, 'M' | '<'<<parser.PrefixShift:
 395		// Handle SGR mouse
 396		if paramsLen == 3 {
 397			return i, parseSGRMouseEvent(cmd, pa)
 398		}
 399	case 'm' | '>'<<parser.PrefixShift:
 400		// XTerm modifyOtherKeys
 401		mok, _, ok := pa.Param(0, 0)
 402		if !ok || mok != 4 {
 403			break
 404		}
 405		val, _, ok := pa.Param(1, -1)
 406		if !ok || val == -1 {
 407			break
 408		}
 409		return i, ModifyOtherKeysEvent(val) //nolint:gosec
 410	case 'n' | '?'<<parser.PrefixShift:
 411		report, _, _ := pa.Param(0, -1)
 412		darkLight, _, _ := pa.Param(1, -1)
 413		switch report {
 414		case 997: // [ansi.LightDarkReport]
 415			switch darkLight {
 416			case 1:
 417				return i, DarkColorSchemeEvent{}
 418			case 2:
 419				return i, LightColorSchemeEvent{}
 420			}
 421		}
 422	case 'I':
 423		return i, FocusEvent{}
 424	case 'O':
 425		return i, BlurEvent{}
 426	case 'R':
 427		// Cursor position report OR modified F3
 428		row, _, rok := pa.Param(0, 1)
 429		col, _, cok := pa.Param(1, 1)
 430		if paramsLen == 2 && rok && cok {
 431			m := CursorPositionEvent{Y: row - 1, X: col - 1}
 432			if row == 1 && col-1 <= int(ModMeta|ModShift|ModAlt|ModCtrl) {
 433				// XXX: We cannot differentiate between cursor position report and
 434				// CSI 1 ; <mod> R (which is modified F3) when the cursor is at the
 435				// row 1. In this case, we report both messages.
 436				//
 437				// For a non ambiguous cursor position report, use
 438				// [ansi.RequestExtendedCursorPosition] (DECXCPR) instead.
 439				return i, MultiEvent{KeyPressEvent{Code: KeyF3, Mod: KeyMod(col - 1)}, m}
 440			}
 441
 442			return i, m
 443		}
 444
 445		if paramsLen != 0 {
 446			break
 447		}
 448
 449		// Unmodified key F3 (CSI R)
 450		fallthrough
 451	case 'a', 'b', 'c', 'd', 'A', 'B', 'C', 'D', 'E', 'F', 'H', 'P', 'Q', 'S', 'Z':
 452		var k KeyPressEvent
 453		switch cmd {
 454		case 'a', 'b', 'c', 'd':
 455			k = KeyPressEvent{Code: KeyUp + rune(cmd-'a'), Mod: ModShift}
 456		case 'A', 'B', 'C', 'D':
 457			k = KeyPressEvent{Code: KeyUp + rune(cmd-'A')}
 458		case 'E':
 459			k = KeyPressEvent{Code: KeyBegin}
 460		case 'F':
 461			k = KeyPressEvent{Code: KeyEnd}
 462		case 'H':
 463			k = KeyPressEvent{Code: KeyHome}
 464		case 'P', 'Q', 'R', 'S':
 465			k = KeyPressEvent{Code: KeyF1 + rune(cmd-'P')}
 466		case 'Z':
 467			k = KeyPressEvent{Code: KeyTab, Mod: ModShift}
 468		}
 469		id, _, _ := pa.Param(0, 1)
 470		if id == 0 {
 471			id = 1
 472		}
 473		mod, _, _ := pa.Param(1, 1)
 474		if mod == 0 {
 475			mod = 1
 476		}
 477		if paramsLen > 1 && id == 1 && mod != -1 {
 478			// CSI 1 ; <modifiers> A
 479			k.Mod |= KeyMod(mod - 1)
 480		}
 481		// Don't forget to handle Kitty keyboard protocol
 482		return i, parseKittyKeyboardExt(pa, k)
 483	case 'M':
 484		// Handle X10 mouse
 485		if i+3 > len(b) {
 486			return i, UnknownCsiEvent(b[:i])
 487		}
 488		return i + 3, parseX10MouseEvent(append(b[:i], b[i:i+3]...))
 489	case 'y' | '$'<<parser.IntermedShift:
 490		// Report Mode (DECRPM)
 491		mode, _, ok := pa.Param(0, -1)
 492		if !ok || mode == -1 {
 493			break
 494		}
 495		val, _, ok := pa.Param(1, -1)
 496		if !ok || val == -1 {
 497			break
 498		}
 499		return i, ModeReportEvent{Mode: ansi.ANSIMode(mode), Value: ansi.ModeSetting(val)}
 500	case 'u':
 501		// Kitty keyboard protocol & CSI u (fixterms)
 502		if paramsLen == 0 {
 503			return i, UnknownCsiEvent(b[:i])
 504		}
 505		return i, parseKittyKeyboard(pa)
 506	case '_':
 507		// Win32 Input Mode
 508		if paramsLen != 6 {
 509			return i, UnknownCsiEvent(b[:i])
 510		}
 511
 512		vrc, _, _ := pa.Param(5, 0)
 513		rc := uint16(vrc) //nolint:gosec
 514		if rc == 0 {
 515			rc = 1
 516		}
 517
 518		vk, _, _ := pa.Param(0, 0)
 519		sc, _, _ := pa.Param(1, 0)
 520		uc, _, _ := pa.Param(2, 0)
 521		kd, _, _ := pa.Param(3, 0)
 522		cs, _, _ := pa.Param(4, 0)
 523		event := p.parseWin32InputKeyEvent(
 524			nil,
 525			uint16(vk), //nolint:gosec // Vk wVirtualKeyCode
 526			uint16(sc), //nolint:gosec // Sc wVirtualScanCode
 527			rune(uc),   // Uc UnicodeChar
 528			kd == 1,    // Kd bKeyDown
 529			uint32(cs), //nolint:gosec // Cs dwControlKeyState
 530			rc,         // Rc wRepeatCount
 531			nil,
 532		)
 533
 534		if event == nil {
 535			return i, UnknownCsiEvent(b[:])
 536		}
 537
 538		return i, event
 539	case '@', '^', '~':
 540		if paramsLen == 0 {
 541			return i, UnknownCsiEvent(b[:i])
 542		}
 543
 544		param, _, _ := pa.Param(0, 0)
 545		switch cmd {
 546		case '~':
 547			switch param {
 548			case 27:
 549				// XTerm modifyOtherKeys 2
 550				if paramsLen != 3 {
 551					return i, UnknownCsiEvent(b[:i])
 552				}
 553				return i, parseXTermModifyOtherKeys(pa)
 554			case 200:
 555				// bracketed-paste start
 556				return i, PasteStartEvent{}
 557			case 201:
 558				// bracketed-paste end
 559				return i, PasteEndEvent{}
 560			}
 561		}
 562
 563		switch param {
 564		case 1, 2, 3, 4, 5, 6, 7, 8,
 565			11, 12, 13, 14, 15,
 566			17, 18, 19, 20, 21,
 567			23, 24, 25, 26,
 568			28, 29, 31, 32, 33, 34:
 569			var k KeyPressEvent
 570			switch param {
 571			case 1:
 572				if p.Legacy&flagFind != 0 {
 573					k = KeyPressEvent{Code: KeyFind}
 574				} else {
 575					k = KeyPressEvent{Code: KeyHome}
 576				}
 577			case 2:
 578				k = KeyPressEvent{Code: KeyInsert}
 579			case 3:
 580				k = KeyPressEvent{Code: KeyDelete}
 581			case 4:
 582				if p.Legacy&flagSelect != 0 {
 583					k = KeyPressEvent{Code: KeySelect}
 584				} else {
 585					k = KeyPressEvent{Code: KeyEnd}
 586				}
 587			case 5:
 588				k = KeyPressEvent{Code: KeyPgUp}
 589			case 6:
 590				k = KeyPressEvent{Code: KeyPgDown}
 591			case 7:
 592				k = KeyPressEvent{Code: KeyHome}
 593			case 8:
 594				k = KeyPressEvent{Code: KeyEnd}
 595			case 11, 12, 13, 14, 15:
 596				k = KeyPressEvent{Code: KeyF1 + rune(param-11)}
 597			case 17, 18, 19, 20, 21:
 598				k = KeyPressEvent{Code: KeyF6 + rune(param-17)}
 599			case 23, 24, 25, 26:
 600				k = KeyPressEvent{Code: KeyF11 + rune(param-23)}
 601			case 28, 29:
 602				k = KeyPressEvent{Code: KeyF15 + rune(param-28)}
 603			case 31, 32, 33, 34:
 604				k = KeyPressEvent{Code: KeyF17 + rune(param-31)}
 605			}
 606
 607			// modifiers
 608			mod, _, _ := pa.Param(1, -1)
 609			if paramsLen > 1 && mod != -1 {
 610				k.Mod |= KeyMod(mod - 1)
 611			}
 612
 613			// Handle URxvt weird keys
 614			switch cmd {
 615			case '~':
 616				// Don't forget to handle Kitty keyboard protocol
 617				return i, parseKittyKeyboardExt(pa, k)
 618			case '^':
 619				k.Mod |= ModCtrl
 620			case '@':
 621				k.Mod |= ModCtrl | ModShift
 622			}
 623
 624			return i, k
 625		}
 626
 627	case 't':
 628		param, _, ok := pa.Param(0, 0)
 629		if !ok {
 630			break
 631		}
 632
 633		switch param {
 634		case 4: // Report Terminal pixel size.
 635			if paramsLen == 3 {
 636				height, _, hOk := pa.Param(1, 0)
 637				width, _, wOk := pa.Param(2, 0)
 638				if !hOk || !wOk {
 639					break
 640				}
 641				return i, WindowSizeEvent{Width: width, Height: height}
 642			}
 643		case 6: // Report Terminal cell size.
 644			if paramsLen == 3 {
 645				height, _, hOk := pa.Param(1, 0)
 646				width, _, wOk := pa.Param(2, 0)
 647				if !hOk || !wOk {
 648					break
 649				}
 650				return i, WindowPixelSizeEvent{Width: width, Height: height}
 651			}
 652		case 48: // In band terminal size report.
 653			if paramsLen == 5 {
 654				cellHeight, _, chOk := pa.Param(1, 0)
 655				cellWidth, _, cwOk := pa.Param(2, 0)
 656				pixelHeight, _, phOk := pa.Param(3, 0)
 657				pixelWidth, _, pwOk := pa.Param(4, 0)
 658				if !chOk || !cwOk || !phOk || !pwOk {
 659					break
 660				}
 661				return i, MultiEvent{
 662					WindowSizeEvent{Width: cellWidth, Height: cellHeight},
 663					WindowPixelSizeEvent{Width: pixelWidth, Height: pixelHeight},
 664				}
 665			}
 666		}
 667
 668		// Any other window operation event.
 669
 670		var winop WindowOpEvent
 671		winop.Op = param
 672		for j := 1; j < paramsLen; j++ {
 673			val, _, ok := pa.Param(j, 0)
 674			if ok {
 675				winop.Args = append(winop.Args, val)
 676			}
 677		}
 678
 679		return i, winop
 680	}
 681	return i, UnknownCsiEvent(b[:i])
 682}
 683
 684// parseSs3 parses a SS3 sequence.
 685// See https://vt100.net/docs/vt220-rm/chapter4.html#S4.4.4.2
 686func (p *SequenceParser) parseSs3(b []byte) (int, Event) {
 687	if len(b) == 2 && b[0] == ansi.ESC {
 688		// short cut if this is an alt+O key
 689		return 2, KeyPressEvent{Code: rune(b[1]), Mod: ModAlt}
 690	}
 691
 692	var i int
 693	if b[i] == ansi.SS3 || b[i] == ansi.ESC {
 694		i++
 695	}
 696	if i < len(b) && b[i-1] == ansi.ESC && b[i] == 'O' {
 697		i++
 698	}
 699
 700	// Scan numbers from 0-9
 701	var mod int
 702	for ; i < len(b) && b[i] >= '0' && b[i] <= '9'; i++ {
 703		mod *= 10
 704		mod += int(b[i]) - '0'
 705	}
 706
 707	// Scan a GL character
 708	// A GL character is a single byte in the range 0x21-0x7E
 709	// See https://vt100.net/docs/vt220-rm/chapter2.html#S2.3.2
 710	if i >= len(b) || b[i] < 0x21 || b[i] > 0x7E {
 711		return i, UnknownEvent(b[:i])
 712	}
 713
 714	// GL character(s)
 715	gl := b[i]
 716	i++
 717
 718	var k KeyPressEvent
 719	switch gl {
 720	case 'a', 'b', 'c', 'd':
 721		k = KeyPressEvent{Code: KeyUp + rune(gl-'a'), Mod: ModCtrl}
 722	case 'A', 'B', 'C', 'D':
 723		k = KeyPressEvent{Code: KeyUp + rune(gl-'A')}
 724	case 'E':
 725		k = KeyPressEvent{Code: KeyBegin}
 726	case 'F':
 727		k = KeyPressEvent{Code: KeyEnd}
 728	case 'H':
 729		k = KeyPressEvent{Code: KeyHome}
 730	case 'P', 'Q', 'R', 'S':
 731		k = KeyPressEvent{Code: KeyF1 + rune(gl-'P')}
 732	case 'M':
 733		k = KeyPressEvent{Code: KeyKpEnter}
 734	case 'X':
 735		k = KeyPressEvent{Code: KeyKpEqual}
 736	case 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y':
 737		k = KeyPressEvent{Code: KeyKpMultiply + rune(gl-'j')}
 738	default:
 739		return i, UnknownSs3Event(b[:i])
 740	}
 741
 742	// Handle weird SS3 <modifier> Func
 743	if mod > 0 {
 744		k.Mod |= KeyMod(mod - 1)
 745	}
 746
 747	return i, k
 748}
 749
 750func (p *SequenceParser) parseOsc(b []byte) (int, Event) {
 751	defaultKey := func() KeyPressEvent {
 752		return KeyPressEvent{Code: rune(b[1]), Mod: ModAlt}
 753	}
 754	if len(b) == 2 && b[0] == ansi.ESC {
 755		// short cut if this is an alt+] key
 756		return 2, defaultKey()
 757	}
 758
 759	var i int
 760	if b[i] == ansi.OSC || b[i] == ansi.ESC {
 761		i++
 762	}
 763	if i < len(b) && b[i-1] == ansi.ESC && b[i] == ']' {
 764		i++
 765	}
 766
 767	// Parse OSC command
 768	// An OSC sequence is terminated by a BEL, ESC, or ST character
 769	var start, end int
 770	cmd := -1
 771	for ; i < len(b) && b[i] >= '0' && b[i] <= '9'; i++ {
 772		if cmd == -1 {
 773			cmd = 0
 774		} else {
 775			cmd *= 10
 776		}
 777		cmd += int(b[i]) - '0'
 778	}
 779
 780	if i < len(b) && b[i] == ';' {
 781		// mark the start of the sequence data
 782		i++
 783		start = i
 784	}
 785
 786	for ; i < len(b); i++ {
 787		// advance to the end of the sequence
 788		if slices.Contains([]byte{ansi.BEL, ansi.ESC, ansi.ST, ansi.CAN, ansi.SUB}, b[i]) {
 789			break
 790		}
 791	}
 792
 793	if i >= len(b) {
 794		return i, UnknownEvent(b[:i])
 795	}
 796
 797	end = i // end of the sequence data
 798	i++
 799
 800	// Check 7-bit ST (string terminator) character
 801	switch b[i-1] {
 802	case ansi.CAN, ansi.SUB:
 803		return i, ignoredEvent(b[:i])
 804	case ansi.ESC:
 805		if i >= len(b) || b[i] != '\\' {
 806			if cmd == -1 || (start == 0 && end == 2) {
 807				return 2, defaultKey()
 808			}
 809
 810			// If we don't have a valid ST terminator, then this is a
 811			// cancelled sequence and should be ignored.
 812			return i, ignoredEvent(b[:i])
 813		}
 814
 815		i++
 816	}
 817
 818	if end <= start {
 819		return i, UnknownEvent(b[:i])
 820	}
 821
 822	data := string(b[start:end])
 823	switch cmd {
 824	case 10:
 825		return i, ForegroundColorEvent{ansi.XParseColor(data)}
 826	case 11:
 827		return i, BackgroundColorEvent{ansi.XParseColor(data)}
 828	case 12:
 829		return i, CursorColorEvent{ansi.XParseColor(data)}
 830	case 52:
 831		parts := strings.Split(data, ";")
 832		if len(parts) == 0 {
 833			return i, ClipboardEvent{}
 834		}
 835		if len(parts) != 2 || len(parts[0]) < 1 {
 836			break
 837		}
 838
 839		b64 := parts[1]
 840		bts, err := base64.StdEncoding.DecodeString(b64)
 841		if err != nil {
 842			break
 843		}
 844
 845		sel := ClipboardSelection(parts[0][0]) //nolint:unconvert
 846		return i, ClipboardEvent{Selection: sel, Content: string(bts)}
 847	}
 848
 849	return i, UnknownOscEvent(b[:i])
 850}
 851
 852// parseStTerminated parses a control sequence that gets terminated by a ST character.
 853func (p *SequenceParser) parseStTerminated(intro8, intro7 byte, fn func([]byte) Event) func([]byte) (int, Event) {
 854	defaultKey := func(b []byte) (int, Event) {
 855		switch intro8 {
 856		case ansi.SOS:
 857			return 2, KeyPressEvent{Code: 'x', Mod: ModShift | ModAlt}
 858		case ansi.PM, ansi.APC:
 859			return 2, KeyPressEvent{Code: rune(b[1]), Mod: ModAlt}
 860		}
 861		return 0, nil
 862	}
 863	return func(b []byte) (int, Event) {
 864		if len(b) == 2 && b[0] == ansi.ESC {
 865			return defaultKey(b)
 866		}
 867
 868		var i int
 869		if b[i] == intro8 || b[i] == ansi.ESC {
 870			i++
 871		}
 872		if i < len(b) && b[i-1] == ansi.ESC && b[i] == intro7 {
 873			i++
 874		}
 875
 876		// Scan control sequence
 877		// Most common control sequence is terminated by a ST character
 878		// ST is a 7-bit string terminator character is (ESC \)
 879		start := i
 880		for ; i < len(b); i++ {
 881			if slices.Contains([]byte{ansi.ESC, ansi.ST, ansi.CAN, ansi.SUB}, b[i]) {
 882				break
 883			}
 884		}
 885
 886		if i >= len(b) {
 887			return i, UnknownEvent(b[:i])
 888		}
 889
 890		end := i // end of the sequence data
 891		i++
 892
 893		// Check 7-bit ST (string terminator) character
 894		switch b[i-1] {
 895		case ansi.CAN, ansi.SUB:
 896			return i, ignoredEvent(b[:i])
 897		case ansi.ESC:
 898			if i >= len(b) || b[i] != '\\' {
 899				if start == end {
 900					return defaultKey(b)
 901				}
 902
 903				// If we don't have a valid ST terminator, then this is a
 904				// cancelled sequence and should be ignored.
 905				return i, ignoredEvent(b[:i])
 906			}
 907
 908			i++
 909		}
 910
 911		// Call the function to parse the sequence and return the result
 912		if fn != nil {
 913			if e := fn(b[start:end]); e != nil {
 914				return i, e
 915			}
 916		}
 917
 918		switch intro8 {
 919		case ansi.PM:
 920			return i, UnknownPmEvent(b[:i])
 921		case ansi.SOS:
 922			return i, UnknownSosEvent(b[:i])
 923		case ansi.APC:
 924			return i, UnknownApcEvent(b[:i])
 925		default:
 926			return i, UnknownEvent(b[:i])
 927		}
 928	}
 929}
 930
 931func (p *SequenceParser) parseDcs(b []byte) (int, Event) {
 932	if len(b) == 2 && b[0] == ansi.ESC {
 933		// short cut if this is an alt+P key
 934		return 2, KeyPressEvent{Code: 'p', Mod: ModShift | ModAlt}
 935	}
 936
 937	var params [16]ansi.Param
 938	var paramsLen int
 939	var cmd ansi.Cmd
 940
 941	// DCS sequences are introduced by DCS (0x90) or ESC P (0x1b 0x50)
 942	var i int
 943	if b[i] == ansi.DCS || b[i] == ansi.ESC {
 944		i++
 945	}
 946	if i < len(b) && b[i-1] == ansi.ESC && b[i] == 'P' {
 947		i++
 948	}
 949
 950	// initial DCS byte
 951	if i < len(b) && b[i] >= '<' && b[i] <= '?' {
 952		cmd |= ansi.Cmd(b[i]) << parser.PrefixShift
 953	}
 954
 955	// Scan parameter bytes in the range 0x30-0x3F
 956	var j int
 957	for j = 0; i < len(b) && paramsLen < len(params) && b[i] >= 0x30 && b[i] <= 0x3F; i, j = i+1, j+1 {
 958		if b[i] >= '0' && b[i] <= '9' {
 959			if params[paramsLen] == parser.MissingParam {
 960				params[paramsLen] = 0
 961			}
 962			params[paramsLen] *= 10
 963			params[paramsLen] += ansi.Param(b[i]) - '0'
 964		}
 965		if b[i] == ':' {
 966			params[paramsLen] |= parser.HasMoreFlag
 967		}
 968		if b[i] == ';' || b[i] == ':' {
 969			paramsLen++
 970			if paramsLen < len(params) {
 971				// Don't overflow the params slice
 972				params[paramsLen] = parser.MissingParam
 973			}
 974		}
 975	}
 976
 977	if j > 0 && paramsLen < len(params) {
 978		// has parameters
 979		paramsLen++
 980	}
 981
 982	// Scan intermediate bytes in the range 0x20-0x2F
 983	var intermed byte
 984	for j := 0; i < len(b) && b[i] >= 0x20 && b[i] <= 0x2F; i, j = i+1, j+1 {
 985		intermed = b[i]
 986	}
 987
 988	// set intermediate byte
 989	cmd |= ansi.Cmd(intermed) << parser.IntermedShift
 990
 991	// Scan final byte in the range 0x40-0x7E
 992	if i >= len(b) || b[i] < 0x40 || b[i] > 0x7E {
 993		return i, UnknownEvent(b[:i])
 994	}
 995
 996	// Add the final byte
 997	cmd |= ansi.Cmd(b[i])
 998	i++
 999
1000	start := i // start of the sequence data
1001	for ; i < len(b); i++ {
1002		if b[i] == ansi.ST || b[i] == ansi.ESC {
1003			break
1004		}
1005	}
1006
1007	if i >= len(b) {
1008		return i, UnknownEvent(b[:i])
1009	}
1010
1011	end := i // end of the sequence data
1012	i++
1013
1014	// Check 7-bit ST (string terminator) character
1015	if i < len(b) && b[i-1] == ansi.ESC && b[i] == '\\' {
1016		i++
1017	}
1018
1019	pa := ansi.Params(params[:paramsLen])
1020	switch cmd {
1021	case 'r' | '+'<<parser.IntermedShift:
1022		// XTGETTCAP responses
1023		param, _, _ := pa.Param(0, 0)
1024		switch param {
1025		case 1: // 1 means valid response, 0 means invalid response
1026			tc := parseTermcap(b[start:end])
1027			// XXX: some terminals like KiTTY report invalid responses with
1028			// their queries i.e. sending a query for "Tc" using "\x1bP+q5463\x1b\\"
1029			// returns "\x1bP0+r5463\x1b\\".
1030			// The specs says that invalid responses should be in the form of
1031			// DCS 0 + r ST "\x1bP0+r\x1b\\"
1032			// We ignore invalid responses and only send valid ones to the program.
1033			//
1034			// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands
1035			return i, tc
1036		}
1037	case '|' | '>'<<parser.PrefixShift:
1038		// XTVersion response
1039		return i, TerminalVersionEvent(b[start:end])
1040	}
1041
1042	return i, UnknownDcsEvent(b[:i])
1043}
1044
1045func (p *SequenceParser) parseApc(b []byte) (int, Event) {
1046	if len(b) == 2 && b[0] == ansi.ESC {
1047		// short cut if this is an alt+_ key
1048		return 2, KeyPressEvent{Code: rune(b[1]), Mod: ModAlt}
1049	}
1050
1051	// APC sequences are introduced by APC (0x9f) or ESC _ (0x1b 0x5f)
1052	return p.parseStTerminated(ansi.APC, '_', func(b []byte) Event {
1053		if len(b) == 0 {
1054			return nil
1055		}
1056
1057		switch b[0] {
1058		case 'G': // Kitty Graphics Protocol
1059			var g KittyGraphicsEvent
1060			parts := bytes.Split(b[1:], []byte{';'})
1061			g.Options.UnmarshalText(parts[0]) //nolint:errcheck,gosec
1062			if len(parts) > 1 {
1063				g.Payload = parts[1]
1064			}
1065			return g
1066		}
1067
1068		return nil
1069	})(b)
1070}
1071
1072func (p *SequenceParser) parseUtf8(b []byte) (int, Event) {
1073	if len(b) == 0 {
1074		return 0, nil
1075	}
1076
1077	c := b[0]
1078	if c <= ansi.US || c == ansi.DEL {
1079		// Control codes get handled by parseControl
1080		return 1, p.parseControl(c)
1081	} else if c > ansi.US && c < ansi.DEL {
1082		// ASCII printable characters
1083		code := rune(c)
1084		k := KeyPressEvent{Code: code, Text: string(code)}
1085		if unicode.IsUpper(code) {
1086			// Convert upper case letters to lower case + shift modifier
1087			k.Code = unicode.ToLower(code)
1088			k.ShiftedCode = code
1089			k.Mod |= ModShift
1090		}
1091
1092		return 1, k
1093	}
1094
1095	code, _ := utf8.DecodeRune(b)
1096	if code == utf8.RuneError {
1097		return 1, UnknownEvent(b[0])
1098	}
1099
1100	cluster, _, _, _ := uniseg.FirstGraphemeCluster(b, -1)
1101	text := string(cluster)
1102	for i := range text {
1103		if i > 0 {
1104			// Use [KeyExtended] for multi-rune graphemes
1105			code = KeyExtended
1106			break
1107		}
1108	}
1109
1110	return len(cluster), KeyPressEvent{Code: code, Text: text}
1111}
1112
1113func (p *SequenceParser) parseControl(b byte) Event {
1114	switch b {
1115	case ansi.NUL:
1116		if p.Legacy&flagCtrlAt != 0 {
1117			return KeyPressEvent{Code: '@', Mod: ModCtrl}
1118		}
1119		return KeyPressEvent{Code: KeySpace, Mod: ModCtrl}
1120	case ansi.BS:
1121		return KeyPressEvent{Code: 'h', Mod: ModCtrl}
1122	case ansi.HT:
1123		if p.Legacy&flagCtrlI != 0 {
1124			return KeyPressEvent{Code: 'i', Mod: ModCtrl}
1125		}
1126		return KeyPressEvent{Code: KeyTab}
1127	case ansi.CR:
1128		if p.Legacy&flagCtrlM != 0 {
1129			return KeyPressEvent{Code: 'm', Mod: ModCtrl}
1130		}
1131		return KeyPressEvent{Code: KeyEnter}
1132	case ansi.ESC:
1133		if p.Legacy&flagCtrlOpenBracket != 0 {
1134			return KeyPressEvent{Code: '[', Mod: ModCtrl}
1135		}
1136		return KeyPressEvent{Code: KeyEscape}
1137	case ansi.DEL:
1138		if p.Legacy&flagBackspace != 0 {
1139			return KeyPressEvent{Code: KeyDelete}
1140		}
1141		return KeyPressEvent{Code: KeyBackspace}
1142	case ansi.SP:
1143		return KeyPressEvent{Code: KeySpace, Text: " "}
1144	default:
1145		if b >= ansi.SOH && b <= ansi.SUB {
1146			// Use lower case letters for control codes
1147			code := rune(b + 0x60)
1148			return KeyPressEvent{Code: code, Mod: ModCtrl}
1149		} else if b >= ansi.FS && b <= ansi.US {
1150			code := rune(b + 0x40)
1151			return KeyPressEvent{Code: code, Mod: ModCtrl}
1152		}
1153		return UnknownEvent(b)
1154	}
1155}
1156
1157func parseXTermModifyOtherKeys(params ansi.Params) Event {
1158	// XTerm modify other keys starts with ESC [ 27 ; <modifier> ; <code> ~
1159	xmod, _, _ := params.Param(1, 1)
1160	xrune, _, _ := params.Param(2, 1)
1161	mod := KeyMod(xmod - 1)
1162	r := rune(xrune)
1163
1164	switch r {
1165	case ansi.BS:
1166		return KeyPressEvent{Mod: mod, Code: KeyBackspace}
1167	case ansi.HT:
1168		return KeyPressEvent{Mod: mod, Code: KeyTab}
1169	case ansi.CR:
1170		return KeyPressEvent{Mod: mod, Code: KeyEnter}
1171	case ansi.ESC:
1172		return KeyPressEvent{Mod: mod, Code: KeyEscape}
1173	case ansi.DEL:
1174		return KeyPressEvent{Mod: mod, Code: KeyBackspace}
1175	}
1176
1177	// CSI 27 ; <modifier> ; <code> ~ keys defined in XTerm modifyOtherKeys
1178	k := KeyPressEvent{Code: r, Mod: mod}
1179	if k.Mod <= ModShift {
1180		k.Text = string(r)
1181	}
1182
1183	return k
1184}
1185
1186// Kitty Clipboard Control Sequences.
1187var kittyKeyMap = map[int]Key{
1188	ansi.BS:  {Code: KeyBackspace},
1189	ansi.HT:  {Code: KeyTab},
1190	ansi.CR:  {Code: KeyEnter},
1191	ansi.ESC: {Code: KeyEscape},
1192	ansi.DEL: {Code: KeyBackspace},
1193
1194	57344: {Code: KeyEscape},
1195	57345: {Code: KeyEnter},
1196	57346: {Code: KeyTab},
1197	57347: {Code: KeyBackspace},
1198	57348: {Code: KeyInsert},
1199	57349: {Code: KeyDelete},
1200	57350: {Code: KeyLeft},
1201	57351: {Code: KeyRight},
1202	57352: {Code: KeyUp},
1203	57353: {Code: KeyDown},
1204	57354: {Code: KeyPgUp},
1205	57355: {Code: KeyPgDown},
1206	57356: {Code: KeyHome},
1207	57357: {Code: KeyEnd},
1208	57358: {Code: KeyCapsLock},
1209	57359: {Code: KeyScrollLock},
1210	57360: {Code: KeyNumLock},
1211	57361: {Code: KeyPrintScreen},
1212	57362: {Code: KeyPause},
1213	57363: {Code: KeyMenu},
1214	57364: {Code: KeyF1},
1215	57365: {Code: KeyF2},
1216	57366: {Code: KeyF3},
1217	57367: {Code: KeyF4},
1218	57368: {Code: KeyF5},
1219	57369: {Code: KeyF6},
1220	57370: {Code: KeyF7},
1221	57371: {Code: KeyF8},
1222	57372: {Code: KeyF9},
1223	57373: {Code: KeyF10},
1224	57374: {Code: KeyF11},
1225	57375: {Code: KeyF12},
1226	57376: {Code: KeyF13},
1227	57377: {Code: KeyF14},
1228	57378: {Code: KeyF15},
1229	57379: {Code: KeyF16},
1230	57380: {Code: KeyF17},
1231	57381: {Code: KeyF18},
1232	57382: {Code: KeyF19},
1233	57383: {Code: KeyF20},
1234	57384: {Code: KeyF21},
1235	57385: {Code: KeyF22},
1236	57386: {Code: KeyF23},
1237	57387: {Code: KeyF24},
1238	57388: {Code: KeyF25},
1239	57389: {Code: KeyF26},
1240	57390: {Code: KeyF27},
1241	57391: {Code: KeyF28},
1242	57392: {Code: KeyF29},
1243	57393: {Code: KeyF30},
1244	57394: {Code: KeyF31},
1245	57395: {Code: KeyF32},
1246	57396: {Code: KeyF33},
1247	57397: {Code: KeyF34},
1248	57398: {Code: KeyF35},
1249	57399: {Code: KeyKp0},
1250	57400: {Code: KeyKp1},
1251	57401: {Code: KeyKp2},
1252	57402: {Code: KeyKp3},
1253	57403: {Code: KeyKp4},
1254	57404: {Code: KeyKp5},
1255	57405: {Code: KeyKp6},
1256	57406: {Code: KeyKp7},
1257	57407: {Code: KeyKp8},
1258	57408: {Code: KeyKp9},
1259	57409: {Code: KeyKpDecimal},
1260	57410: {Code: KeyKpDivide},
1261	57411: {Code: KeyKpMultiply},
1262	57412: {Code: KeyKpMinus},
1263	57413: {Code: KeyKpPlus},
1264	57414: {Code: KeyKpEnter},
1265	57415: {Code: KeyKpEqual},
1266	57416: {Code: KeyKpSep},
1267	57417: {Code: KeyKpLeft},
1268	57418: {Code: KeyKpRight},
1269	57419: {Code: KeyKpUp},
1270	57420: {Code: KeyKpDown},
1271	57421: {Code: KeyKpPgUp},
1272	57422: {Code: KeyKpPgDown},
1273	57423: {Code: KeyKpHome},
1274	57424: {Code: KeyKpEnd},
1275	57425: {Code: KeyKpInsert},
1276	57426: {Code: KeyKpDelete},
1277	57427: {Code: KeyKpBegin},
1278	57428: {Code: KeyMediaPlay},
1279	57429: {Code: KeyMediaPause},
1280	57430: {Code: KeyMediaPlayPause},
1281	57431: {Code: KeyMediaReverse},
1282	57432: {Code: KeyMediaStop},
1283	57433: {Code: KeyMediaFastForward},
1284	57434: {Code: KeyMediaRewind},
1285	57435: {Code: KeyMediaNext},
1286	57436: {Code: KeyMediaPrev},
1287	57437: {Code: KeyMediaRecord},
1288	57438: {Code: KeyLowerVol},
1289	57439: {Code: KeyRaiseVol},
1290	57440: {Code: KeyMute},
1291	57441: {Code: KeyLeftShift},
1292	57442: {Code: KeyLeftCtrl},
1293	57443: {Code: KeyLeftAlt},
1294	57444: {Code: KeyLeftSuper},
1295	57445: {Code: KeyLeftHyper},
1296	57446: {Code: KeyLeftMeta},
1297	57447: {Code: KeyRightShift},
1298	57448: {Code: KeyRightCtrl},
1299	57449: {Code: KeyRightAlt},
1300	57450: {Code: KeyRightSuper},
1301	57451: {Code: KeyRightHyper},
1302	57452: {Code: KeyRightMeta},
1303	57453: {Code: KeyIsoLevel3Shift},
1304	57454: {Code: KeyIsoLevel5Shift},
1305}
1306
1307func init() {
1308	// These are some faulty C0 mappings some terminals such as WezTerm have
1309	// and doesn't follow the specs.
1310	kittyKeyMap[ansi.NUL] = Key{Code: KeySpace, Mod: ModCtrl}
1311	for i := ansi.SOH; i <= ansi.SUB; i++ {
1312		if _, ok := kittyKeyMap[i]; !ok {
1313			kittyKeyMap[i] = Key{Code: rune(i + 0x60), Mod: ModCtrl}
1314		}
1315	}
1316	for i := ansi.FS; i <= ansi.US; i++ {
1317		if _, ok := kittyKeyMap[i]; !ok {
1318			kittyKeyMap[i] = Key{Code: rune(i + 0x40), Mod: ModCtrl}
1319		}
1320	}
1321}
1322
1323const (
1324	kittyShift = 1 << iota
1325	kittyAlt
1326	kittyCtrl
1327	kittySuper
1328	kittyHyper
1329	kittyMeta
1330	kittyCapsLock
1331	kittyNumLock
1332)
1333
1334func fromKittyMod(mod int) KeyMod {
1335	var m KeyMod
1336	if mod&kittyShift != 0 {
1337		m |= ModShift
1338	}
1339	if mod&kittyAlt != 0 {
1340		m |= ModAlt
1341	}
1342	if mod&kittyCtrl != 0 {
1343		m |= ModCtrl
1344	}
1345	if mod&kittySuper != 0 {
1346		m |= ModSuper
1347	}
1348	if mod&kittyHyper != 0 {
1349		m |= ModHyper
1350	}
1351	if mod&kittyMeta != 0 {
1352		m |= ModMeta
1353	}
1354	if mod&kittyCapsLock != 0 {
1355		m |= ModCapsLock
1356	}
1357	if mod&kittyNumLock != 0 {
1358		m |= ModNumLock
1359	}
1360	return m
1361}
1362
1363// parseKittyKeyboard parses a Kitty Keyboard Protocol sequence.
1364//
1365// In `CSI u`, this is parsed as:
1366//
1367//	CSI codepoint ; modifiers u
1368//	codepoint: ASCII Dec value
1369//
1370// The Kitty Keyboard Protocol extends this with optional components that can be
1371// enabled progressively. The full sequence is parsed as:
1372//
1373//	CSI unicode-key-code:alternate-key-codes ; modifiers:event-type ; text-as-codepoints u
1374//
1375// See https://sw.kovidgoyal.net/kitty/keyboard-protocol/
1376func parseKittyKeyboard(params ansi.Params) (Event Event) {
1377	var isRelease bool
1378	var key Key
1379
1380	// The index of parameters separated by semicolons ';'. Sub parameters are
1381	// separated by colons ':'.
1382	var paramIdx int
1383	var sudIdx int // The sub parameter index
1384	for _, p := range params {
1385		// Kitty Keyboard Protocol has 3 optional components.
1386		switch paramIdx {
1387		case 0:
1388			switch sudIdx {
1389			case 0:
1390				var foundKey bool
1391				code := p.Param(1) // CSI u has a default value of 1
1392				key, foundKey = kittyKeyMap[code]
1393				if !foundKey {
1394					r := rune(code)
1395					if !utf8.ValidRune(r) {
1396						r = utf8.RuneError
1397					}
1398
1399					key.Code = r
1400				}
1401
1402			case 2:
1403				// shifted key + base key
1404				if b := rune(p.Param(1)); unicode.IsPrint(b) {
1405					// XXX: When alternate key reporting is enabled, the protocol
1406					// can return 3 things, the unicode codepoint of the key,
1407					// the shifted codepoint of the key, and the standard
1408					// PC-101 key layout codepoint.
1409					// This is useful to create an unambiguous mapping of keys
1410					// when using a different language layout.
1411					key.BaseCode = b
1412				}
1413				fallthrough
1414
1415			case 1:
1416				// shifted key
1417				if s := rune(p.Param(1)); unicode.IsPrint(s) {
1418					// XXX: We swap keys here because we want the shifted key
1419					// to be the Rune that is returned by the event.
1420					// For example, shift+a should produce "A" not "a".
1421					// In such a case, we set AltRune to the original key "a"
1422					// and Rune to "A".
1423					key.ShiftedCode = s
1424				}
1425			}
1426		case 1:
1427			switch sudIdx {
1428			case 0:
1429				mod := p.Param(1)
1430				if mod > 1 {
1431					key.Mod = fromKittyMod(mod - 1)
1432					if key.Mod > ModShift {
1433						// XXX: We need to clear the text if we have a modifier key
1434						// other than a [ModShift] key.
1435						key.Text = ""
1436					}
1437				}
1438
1439			case 1:
1440				switch p.Param(1) {
1441				case 2:
1442					key.IsRepeat = true
1443				case 3:
1444					isRelease = true
1445				}
1446			case 2:
1447			}
1448		case 2:
1449			if code := p.Param(0); code != 0 {
1450				key.Text += string(rune(code))
1451			}
1452		}
1453
1454		sudIdx++
1455		if !p.HasMore() {
1456			paramIdx++
1457			sudIdx = 0
1458		}
1459	}
1460
1461	//nolint:nestif
1462	if len(key.Text) == 0 && unicode.IsPrint(key.Code) &&
1463		(key.Mod <= ModShift || key.Mod == ModCapsLock || key.Mod == ModShift|ModCapsLock) {
1464		if key.Mod == 0 {
1465			key.Text = string(key.Code)
1466		} else {
1467			desiredCase := unicode.ToLower
1468			if key.Mod.Contains(ModShift) || key.Mod.Contains(ModCapsLock) {
1469				desiredCase = unicode.ToUpper
1470			}
1471			if key.ShiftedCode != 0 {
1472				key.Text = string(key.ShiftedCode)
1473			} else {
1474				key.Text = string(desiredCase(key.Code))
1475			}
1476		}
1477	}
1478
1479	if isRelease {
1480		return KeyReleaseEvent(key)
1481	}
1482
1483	return KeyPressEvent(key)
1484}
1485
1486// parseKittyKeyboardExt parses a Kitty Keyboard Protocol sequence extensions
1487// for non CSI u sequences. This includes things like CSI A, SS3 A and others,
1488// and CSI ~.
1489func parseKittyKeyboardExt(params ansi.Params, k KeyPressEvent) Event {
1490	// Handle Kitty keyboard protocol
1491	if len(params) > 2 && // We have at least 3 parameters
1492		params[0].Param(1) == 1 && // The first parameter is 1 (defaults to 1)
1493		params[1].HasMore() { // The second parameter is a subparameter (separated by a ":")
1494		switch params[2].Param(1) { // The third parameter is the event type (defaults to 1)
1495		case 2:
1496			k.IsRepeat = true
1497		case 3:
1498			return KeyReleaseEvent(k)
1499		}
1500	}
1501	return k
1502}
1503
1504func parsePrimaryDevAttrs(params ansi.Params) Event {
1505	// Primary Device Attributes
1506	da1 := make(PrimaryDeviceAttributesEvent, len(params))
1507	for i, p := range params {
1508		if !p.HasMore() {
1509			da1[i] = p.Param(0)
1510		}
1511	}
1512	return da1
1513}
1514
1515// Parse SGR-encoded mouse events; SGR extended mouse events. SGR mouse events
1516// look like:
1517//
1518//	ESC [ < Cb ; Cx ; Cy (M or m)
1519//
1520// where:
1521//
1522//	Cb is the encoded button code
1523//	Cx is the x-coordinate of the mouse
1524//	Cy is the y-coordinate of the mouse
1525//	M is for button press, m is for button release
1526//
1527// https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Extended-coordinates
1528func parseSGRMouseEvent(cmd ansi.Cmd, params ansi.Params) Event {
1529	x, _, ok := params.Param(1, 1)
1530	if !ok {
1531		x = 1
1532	}
1533	y, _, ok := params.Param(2, 1)
1534	if !ok {
1535		y = 1
1536	}
1537	release := cmd.Final() == 'm'
1538	b, _, _ := params.Param(0, 0)
1539	mod, btn, _, isMotion := parseMouseButton(b)
1540
1541	// (1,1) is the upper left. We subtract 1 to normalize it to (0,0).
1542	x--
1543	y--
1544
1545	m := Mouse{X: x, Y: y, Button: btn, Mod: mod}
1546
1547	// Wheel buttons don't have release events
1548	// Motion can be reported as a release event in some terminals (Windows Terminal)
1549	if isWheel(m.Button) {
1550		return MouseWheelEvent(m)
1551	} else if !isMotion && release {
1552		return MouseReleaseEvent(m)
1553	} else if isMotion {
1554		return MouseMotionEvent(m)
1555	}
1556	return MouseClickEvent(m)
1557}
1558
1559const x10MouseByteOffset = 32
1560
1561// Parse X10-encoded mouse events; the simplest kind. The last release of X10
1562// was December 1986, by the way. The original X10 mouse protocol limits the Cx
1563// and Cy coordinates to 223 (=255-032).
1564//
1565// X10 mouse events look like:
1566//
1567//	ESC [M Cb Cx Cy
1568//
1569// See: http://www.xfree86.org/current/ctlseqs.html#Mouse%20Tracking
1570func parseX10MouseEvent(buf []byte) Event {
1571	v := buf[3:6]
1572	b := int(v[0])
1573	if b >= x10MouseByteOffset {
1574		// XXX: b < 32 should be impossible, but we're being defensive.
1575		b -= x10MouseByteOffset
1576	}
1577
1578	mod, btn, isRelease, isMotion := parseMouseButton(b)
1579
1580	// (1,1) is the upper left. We subtract 1 to normalize it to (0,0).
1581	x := int(v[1]) - x10MouseByteOffset - 1
1582	y := int(v[2]) - x10MouseByteOffset - 1
1583
1584	m := Mouse{X: x, Y: y, Button: btn, Mod: mod}
1585	if isWheel(m.Button) {
1586		return MouseWheelEvent(m)
1587	} else if isMotion {
1588		return MouseMotionEvent(m)
1589	} else if isRelease {
1590		return MouseReleaseEvent(m)
1591	}
1592	return MouseClickEvent(m)
1593}
1594
1595// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Extended-coordinates
1596func parseMouseButton(b int) (mod KeyMod, btn MouseButton, isRelease bool, isMotion bool) {
1597	// mouse bit shifts
1598	const (
1599		bitShift  = 0b0000_0100
1600		bitAlt    = 0b0000_1000
1601		bitCtrl   = 0b0001_0000
1602		bitMotion = 0b0010_0000
1603		bitWheel  = 0b0100_0000
1604		bitAdd    = 0b1000_0000 // additional buttons 8-11
1605
1606		bitsMask = 0b0000_0011
1607	)
1608
1609	// Modifiers
1610	if b&bitAlt != 0 {
1611		mod |= ModAlt
1612	}
1613	if b&bitCtrl != 0 {
1614		mod |= ModCtrl
1615	}
1616	if b&bitShift != 0 {
1617		mod |= ModShift
1618	}
1619
1620	if b&bitAdd != 0 {
1621		btn = MouseBackward + MouseButton(b&bitsMask)
1622	} else if b&bitWheel != 0 {
1623		btn = MouseWheelUp + MouseButton(b&bitsMask)
1624	} else {
1625		btn = MouseLeft + MouseButton(b&bitsMask)
1626		// X10 reports a button release as 0b0000_0011 (3)
1627		if b&bitsMask == bitsMask {
1628			btn = MouseNone
1629			isRelease = true
1630		}
1631	}
1632
1633	// Motion bit doesn't get reported for wheel events.
1634	if b&bitMotion != 0 && !isWheel(btn) {
1635		isMotion = true
1636	}
1637
1638	return //nolint:nakedret
1639}
1640
1641// isWheel returns true if the mouse event is a wheel event.
1642func isWheel(btn MouseButton) bool {
1643	return btn >= MouseWheelUp && btn <= MouseWheelRight
1644}
1645
1646type shiftable interface {
1647	~uint | ~uint16 | ~uint32 | ~uint64
1648}
1649
1650func shift[T shiftable](x T) T {
1651	if x > 0xff {
1652		x >>= 8
1653	}
1654	return x
1655}
1656
1657func colorToHex(c color.Color) string {
1658	if c == nil {
1659		return ""
1660	}
1661	r, g, b, _ := c.RGBA()
1662	return fmt.Sprintf("#%02x%02x%02x", shift(r), shift(g), shift(b))
1663}
1664
1665func getMaxMin(a, b, c float64) (ma, mi float64) {
1666	if a > b {
1667		ma = a
1668		mi = b
1669	} else {
1670		ma = b
1671		mi = a
1672	}
1673	if c > ma {
1674		ma = c
1675	} else if c < mi {
1676		mi = c
1677	}
1678	return ma, mi
1679}
1680
1681func round(x float64) float64 {
1682	return math.Round(x*1000) / 1000
1683}
1684
1685// rgbToHSL converts an RGB triple to an HSL triple.
1686func rgbToHSL(r, g, b uint8) (h, s, l float64) {
1687	// convert uint32 pre-multiplied value to uint8
1688	// The r,g,b values are divided by 255 to change the range from 0..255 to 0..1:
1689	Rnot := float64(r) / 255
1690	Gnot := float64(g) / 255
1691	Bnot := float64(b) / 255
1692	Cmax, Cmin := getMaxMin(Rnot, Gnot, Bnot)
1693	Δ := Cmax - Cmin
1694	// Lightness calculation:
1695	l = (Cmax + Cmin) / 2
1696	// Hue and Saturation Calculation:
1697	if Δ == 0 {
1698		h = 0
1699		s = 0
1700	} else {
1701		switch Cmax {
1702		case Rnot:
1703			h = 60 * (math.Mod((Gnot-Bnot)/Δ, 6))
1704		case Gnot:
1705			h = 60 * (((Bnot - Rnot) / Δ) + 2)
1706		case Bnot:
1707			h = 60 * (((Rnot - Gnot) / Δ) + 4)
1708		}
1709		if h < 0 {
1710			h += 360
1711		}
1712
1713		s = Δ / (1 - math.Abs((2*l)-1))
1714	}
1715
1716	return h, round(s), round(l)
1717}
1718
1719// isDarkColor returns whether the given color is dark.
1720func isDarkColor(c color.Color) bool {
1721	if c == nil {
1722		return true
1723	}
1724
1725	r, g, b, _ := c.RGBA()
1726	_, _, l := rgbToHSL(uint8(r>>8), uint8(g>>8), uint8(b>>8)) //nolint:gosec
1727	return l < 0.5
1728}
1729
1730func parseTermcap(data []byte) CapabilityEvent {
1731	// XTGETTCAP
1732	if len(data) == 0 {
1733		return CapabilityEvent("")
1734	}
1735
1736	var tc strings.Builder
1737	split := bytes.Split(data, []byte{';'})
1738	for _, s := range split {
1739		parts := bytes.SplitN(s, []byte{'='}, 2)
1740		if len(parts) == 0 {
1741			return CapabilityEvent("")
1742		}
1743
1744		name, err := hex.DecodeString(string(parts[0]))
1745		if err != nil || len(name) == 0 {
1746			continue
1747		}
1748
1749		var value []byte
1750		if len(parts) > 1 {
1751			value, err = hex.DecodeString(string(parts[1]))
1752			if err != nil {
1753				continue
1754			}
1755		}
1756
1757		if tc.Len() > 0 {
1758			tc.WriteByte(';')
1759		}
1760		tc.WriteString(string(name))
1761		if len(value) > 0 {
1762			tc.WriteByte('=')
1763			tc.WriteString(string(value))
1764		}
1765	}
1766
1767	return CapabilityEvent(tc.String())
1768}