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 := KeyPressEvent{Code: rune(b[1]), Mod: ModAlt}
 752	if len(b) == 2 && b[0] == ansi.ESC {
 753		// short cut if this is an alt+] key
 754		return 2, defaultKey
 755	}
 756
 757	var i int
 758	if b[i] == ansi.OSC || b[i] == ansi.ESC {
 759		i++
 760	}
 761	if i < len(b) && b[i-1] == ansi.ESC && b[i] == ']' {
 762		i++
 763	}
 764
 765	// Parse OSC command
 766	// An OSC sequence is terminated by a BEL, ESC, or ST character
 767	var start, end int
 768	cmd := -1
 769	for ; i < len(b) && b[i] >= '0' && b[i] <= '9'; i++ {
 770		if cmd == -1 {
 771			cmd = 0
 772		} else {
 773			cmd *= 10
 774		}
 775		cmd += int(b[i]) - '0'
 776	}
 777
 778	if i < len(b) && b[i] == ';' {
 779		// mark the start of the sequence data
 780		i++
 781		start = i
 782	}
 783
 784	for ; i < len(b); i++ {
 785		// advance to the end of the sequence
 786		if slices.Contains([]byte{ansi.BEL, ansi.ESC, ansi.ST, ansi.CAN, ansi.SUB}, b[i]) {
 787			break
 788		}
 789	}
 790
 791	if i >= len(b) {
 792		return i, UnknownEvent(b[:i])
 793	}
 794
 795	end = i // end of the sequence data
 796	i++
 797
 798	// Check 7-bit ST (string terminator) character
 799	switch b[i-1] {
 800	case ansi.CAN, ansi.SUB:
 801		return i, ignoredEvent(b[:i])
 802	case ansi.ESC:
 803		if i >= len(b) || b[i] != '\\' {
 804			if cmd == -1 || (start == 0 && end == 2) {
 805				return 2, defaultKey
 806			}
 807
 808			// If we don't have a valid ST terminator, then this is a
 809			// cancelled sequence and should be ignored.
 810			return i, ignoredEvent(b[:i])
 811		}
 812
 813		i++
 814	}
 815
 816	if end <= start {
 817		return i, UnknownEvent(b[:i])
 818	}
 819
 820	data := string(b[start:end])
 821	switch cmd {
 822	case 10:
 823		return i, ForegroundColorEvent{ansi.XParseColor(data)}
 824	case 11:
 825		return i, BackgroundColorEvent{ansi.XParseColor(data)}
 826	case 12:
 827		return i, CursorColorEvent{ansi.XParseColor(data)}
 828	case 52:
 829		parts := strings.Split(data, ";")
 830		if len(parts) == 0 {
 831			return i, ClipboardEvent{}
 832		}
 833		if len(parts) != 2 || len(parts[0]) < 1 {
 834			break
 835		}
 836
 837		b64 := parts[1]
 838		bts, err := base64.StdEncoding.DecodeString(b64)
 839		if err != nil {
 840			break
 841		}
 842
 843		sel := ClipboardSelection(parts[0][0]) //nolint:unconvert
 844		return i, ClipboardEvent{Selection: sel, Content: string(bts)}
 845	}
 846
 847	return i, UnknownOscEvent(b[:i])
 848}
 849
 850// parseStTerminated parses a control sequence that gets terminated by a ST character.
 851func (p *SequenceParser) parseStTerminated(intro8, intro7 byte, fn func([]byte) Event) func([]byte) (int, Event) {
 852	defaultKey := func(b []byte) (int, Event) {
 853		switch intro8 {
 854		case ansi.SOS:
 855			return 2, KeyPressEvent{Code: 'x', Mod: ModShift | ModAlt}
 856		case ansi.PM, ansi.APC:
 857			return 2, KeyPressEvent{Code: rune(b[1]), Mod: ModAlt}
 858		}
 859		return 0, nil
 860	}
 861	return func(b []byte) (int, Event) {
 862		if len(b) == 2 && b[0] == ansi.ESC {
 863			return defaultKey(b)
 864		}
 865
 866		var i int
 867		if b[i] == intro8 || b[i] == ansi.ESC {
 868			i++
 869		}
 870		if i < len(b) && b[i-1] == ansi.ESC && b[i] == intro7 {
 871			i++
 872		}
 873
 874		// Scan control sequence
 875		// Most common control sequence is terminated by a ST character
 876		// ST is a 7-bit string terminator character is (ESC \)
 877		start := i
 878		for ; i < len(b); i++ {
 879			if slices.Contains([]byte{ansi.ESC, ansi.ST, ansi.CAN, ansi.SUB}, b[i]) {
 880				break
 881			}
 882		}
 883
 884		if i >= len(b) {
 885			return i, UnknownEvent(b[:i])
 886		}
 887
 888		end := i // end of the sequence data
 889		i++
 890
 891		// Check 7-bit ST (string terminator) character
 892		switch b[i-1] {
 893		case ansi.CAN, ansi.SUB:
 894			return i, ignoredEvent(b[:i])
 895		case ansi.ESC:
 896			if i >= len(b) || b[i] != '\\' {
 897				if start == end {
 898					return defaultKey(b)
 899				}
 900
 901				// If we don't have a valid ST terminator, then this is a
 902				// cancelled sequence and should be ignored.
 903				return i, ignoredEvent(b[:i])
 904			}
 905
 906			i++
 907		}
 908
 909		// Call the function to parse the sequence and return the result
 910		if fn != nil {
 911			if e := fn(b[start:end]); e != nil {
 912				return i, e
 913			}
 914		}
 915
 916		switch intro8 {
 917		case ansi.PM:
 918			return i, UnknownPmEvent(b[:i])
 919		case ansi.SOS:
 920			return i, UnknownSosEvent(b[:i])
 921		case ansi.APC:
 922			return i, UnknownApcEvent(b[:i])
 923		default:
 924			return i, UnknownEvent(b[:i])
 925		}
 926	}
 927}
 928
 929func (p *SequenceParser) parseDcs(b []byte) (int, Event) {
 930	if len(b) == 2 && b[0] == ansi.ESC {
 931		// short cut if this is an alt+P key
 932		return 2, KeyPressEvent{Code: 'p', Mod: ModShift | ModAlt}
 933	}
 934
 935	var params [16]ansi.Param
 936	var paramsLen int
 937	var cmd ansi.Cmd
 938
 939	// DCS sequences are introduced by DCS (0x90) or ESC P (0x1b 0x50)
 940	var i int
 941	if b[i] == ansi.DCS || b[i] == ansi.ESC {
 942		i++
 943	}
 944	if i < len(b) && b[i-1] == ansi.ESC && b[i] == 'P' {
 945		i++
 946	}
 947
 948	// initial DCS byte
 949	if i < len(b) && b[i] >= '<' && b[i] <= '?' {
 950		cmd |= ansi.Cmd(b[i]) << parser.PrefixShift
 951	}
 952
 953	// Scan parameter bytes in the range 0x30-0x3F
 954	var j int
 955	for j = 0; i < len(b) && paramsLen < len(params) && b[i] >= 0x30 && b[i] <= 0x3F; i, j = i+1, j+1 {
 956		if b[i] >= '0' && b[i] <= '9' {
 957			if params[paramsLen] == parser.MissingParam {
 958				params[paramsLen] = 0
 959			}
 960			params[paramsLen] *= 10
 961			params[paramsLen] += ansi.Param(b[i]) - '0'
 962		}
 963		if b[i] == ':' {
 964			params[paramsLen] |= parser.HasMoreFlag
 965		}
 966		if b[i] == ';' || b[i] == ':' {
 967			paramsLen++
 968			if paramsLen < len(params) {
 969				// Don't overflow the params slice
 970				params[paramsLen] = parser.MissingParam
 971			}
 972		}
 973	}
 974
 975	if j > 0 && paramsLen < len(params) {
 976		// has parameters
 977		paramsLen++
 978	}
 979
 980	// Scan intermediate bytes in the range 0x20-0x2F
 981	var intermed byte
 982	for j := 0; i < len(b) && b[i] >= 0x20 && b[i] <= 0x2F; i, j = i+1, j+1 {
 983		intermed = b[i]
 984	}
 985
 986	// set intermediate byte
 987	cmd |= ansi.Cmd(intermed) << parser.IntermedShift
 988
 989	// Scan final byte in the range 0x40-0x7E
 990	if i >= len(b) || b[i] < 0x40 || b[i] > 0x7E {
 991		return i, UnknownEvent(b[:i])
 992	}
 993
 994	// Add the final byte
 995	cmd |= ansi.Cmd(b[i])
 996	i++
 997
 998	start := i // start of the sequence data
 999	for ; i < len(b); i++ {
1000		if b[i] == ansi.ST || b[i] == ansi.ESC {
1001			break
1002		}
1003	}
1004
1005	if i >= len(b) {
1006		return i, UnknownEvent(b[:i])
1007	}
1008
1009	end := i // end of the sequence data
1010	i++
1011
1012	// Check 7-bit ST (string terminator) character
1013	if i < len(b) && b[i-1] == ansi.ESC && b[i] == '\\' {
1014		i++
1015	}
1016
1017	pa := ansi.Params(params[:paramsLen])
1018	switch cmd {
1019	case 'r' | '+'<<parser.IntermedShift:
1020		// XTGETTCAP responses
1021		param, _, _ := pa.Param(0, 0)
1022		switch param {
1023		case 1: // 1 means valid response, 0 means invalid response
1024			tc := parseTermcap(b[start:end])
1025			// XXX: some terminals like KiTTY report invalid responses with
1026			// their queries i.e. sending a query for "Tc" using "\x1bP+q5463\x1b\\"
1027			// returns "\x1bP0+r5463\x1b\\".
1028			// The specs says that invalid responses should be in the form of
1029			// DCS 0 + r ST "\x1bP0+r\x1b\\"
1030			// We ignore invalid responses and only send valid ones to the program.
1031			//
1032			// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands
1033			return i, tc
1034		}
1035	case '|' | '>'<<parser.PrefixShift:
1036		// XTVersion response
1037		return i, TerminalVersionEvent(b[start:end])
1038	}
1039
1040	return i, UnknownDcsEvent(b[:i])
1041}
1042
1043func (p *SequenceParser) parseApc(b []byte) (int, Event) {
1044	if len(b) == 2 && b[0] == ansi.ESC {
1045		// short cut if this is an alt+_ key
1046		return 2, KeyPressEvent{Code: rune(b[1]), Mod: ModAlt}
1047	}
1048
1049	// APC sequences are introduced by APC (0x9f) or ESC _ (0x1b 0x5f)
1050	return p.parseStTerminated(ansi.APC, '_', func(b []byte) Event {
1051		if len(b) == 0 {
1052			return nil
1053		}
1054
1055		switch b[0] {
1056		case 'G': // Kitty Graphics Protocol
1057			var g KittyGraphicsEvent
1058			parts := bytes.Split(b[1:], []byte{';'})
1059			g.Options.UnmarshalText(parts[0]) //nolint:errcheck,gosec
1060			if len(parts) > 1 {
1061				g.Payload = parts[1]
1062			}
1063			return g
1064		}
1065
1066		return nil
1067	})(b)
1068}
1069
1070func (p *SequenceParser) parseUtf8(b []byte) (int, Event) {
1071	if len(b) == 0 {
1072		return 0, nil
1073	}
1074
1075	c := b[0]
1076	if c <= ansi.US || c == ansi.DEL {
1077		// Control codes get handled by parseControl
1078		return 1, p.parseControl(c)
1079	} else if c > ansi.US && c < ansi.DEL {
1080		// ASCII printable characters
1081		code := rune(c)
1082		k := KeyPressEvent{Code: code, Text: string(code)}
1083		if unicode.IsUpper(code) {
1084			// Convert upper case letters to lower case + shift modifier
1085			k.Code = unicode.ToLower(code)
1086			k.ShiftedCode = code
1087			k.Mod |= ModShift
1088		}
1089
1090		return 1, k
1091	}
1092
1093	code, _ := utf8.DecodeRune(b)
1094	if code == utf8.RuneError {
1095		return 1, UnknownEvent(b[0])
1096	}
1097
1098	cluster, _, _, _ := uniseg.FirstGraphemeCluster(b, -1)
1099	text := string(cluster)
1100	for i := range text {
1101		if i > 0 {
1102			// Use [KeyExtended] for multi-rune graphemes
1103			code = KeyExtended
1104			break
1105		}
1106	}
1107
1108	return len(cluster), KeyPressEvent{Code: code, Text: text}
1109}
1110
1111func (p *SequenceParser) parseControl(b byte) Event {
1112	switch b {
1113	case ansi.NUL:
1114		if p.Legacy&flagCtrlAt != 0 {
1115			return KeyPressEvent{Code: '@', Mod: ModCtrl}
1116		}
1117		return KeyPressEvent{Code: KeySpace, Mod: ModCtrl}
1118	case ansi.BS:
1119		return KeyPressEvent{Code: 'h', Mod: ModCtrl}
1120	case ansi.HT:
1121		if p.Legacy&flagCtrlI != 0 {
1122			return KeyPressEvent{Code: 'i', Mod: ModCtrl}
1123		}
1124		return KeyPressEvent{Code: KeyTab}
1125	case ansi.CR:
1126		if p.Legacy&flagCtrlM != 0 {
1127			return KeyPressEvent{Code: 'm', Mod: ModCtrl}
1128		}
1129		return KeyPressEvent{Code: KeyEnter}
1130	case ansi.ESC:
1131		if p.Legacy&flagCtrlOpenBracket != 0 {
1132			return KeyPressEvent{Code: '[', Mod: ModCtrl}
1133		}
1134		return KeyPressEvent{Code: KeyEscape}
1135	case ansi.DEL:
1136		if p.Legacy&flagBackspace != 0 {
1137			return KeyPressEvent{Code: KeyDelete}
1138		}
1139		return KeyPressEvent{Code: KeyBackspace}
1140	case ansi.SP:
1141		return KeyPressEvent{Code: KeySpace, Text: " "}
1142	default:
1143		if b >= ansi.SOH && b <= ansi.SUB {
1144			// Use lower case letters for control codes
1145			code := rune(b + 0x60)
1146			return KeyPressEvent{Code: code, Mod: ModCtrl}
1147		} else if b >= ansi.FS && b <= ansi.US {
1148			code := rune(b + 0x40)
1149			return KeyPressEvent{Code: code, Mod: ModCtrl}
1150		}
1151		return UnknownEvent(b)
1152	}
1153}
1154
1155func parseXTermModifyOtherKeys(params ansi.Params) Event {
1156	// XTerm modify other keys starts with ESC [ 27 ; <modifier> ; <code> ~
1157	xmod, _, _ := params.Param(1, 1)
1158	xrune, _, _ := params.Param(2, 1)
1159	mod := KeyMod(xmod - 1)
1160	r := rune(xrune)
1161
1162	switch r {
1163	case ansi.BS:
1164		return KeyPressEvent{Mod: mod, Code: KeyBackspace}
1165	case ansi.HT:
1166		return KeyPressEvent{Mod: mod, Code: KeyTab}
1167	case ansi.CR:
1168		return KeyPressEvent{Mod: mod, Code: KeyEnter}
1169	case ansi.ESC:
1170		return KeyPressEvent{Mod: mod, Code: KeyEscape}
1171	case ansi.DEL:
1172		return KeyPressEvent{Mod: mod, Code: KeyBackspace}
1173	}
1174
1175	// CSI 27 ; <modifier> ; <code> ~ keys defined in XTerm modifyOtherKeys
1176	k := KeyPressEvent{Code: r, Mod: mod}
1177	if k.Mod <= ModShift {
1178		k.Text = string(r)
1179	}
1180
1181	return k
1182}
1183
1184// Kitty Clipboard Control Sequences.
1185var kittyKeyMap = map[int]Key{
1186	ansi.BS:  {Code: KeyBackspace},
1187	ansi.HT:  {Code: KeyTab},
1188	ansi.CR:  {Code: KeyEnter},
1189	ansi.ESC: {Code: KeyEscape},
1190	ansi.DEL: {Code: KeyBackspace},
1191
1192	57344: {Code: KeyEscape},
1193	57345: {Code: KeyEnter},
1194	57346: {Code: KeyTab},
1195	57347: {Code: KeyBackspace},
1196	57348: {Code: KeyInsert},
1197	57349: {Code: KeyDelete},
1198	57350: {Code: KeyLeft},
1199	57351: {Code: KeyRight},
1200	57352: {Code: KeyUp},
1201	57353: {Code: KeyDown},
1202	57354: {Code: KeyPgUp},
1203	57355: {Code: KeyPgDown},
1204	57356: {Code: KeyHome},
1205	57357: {Code: KeyEnd},
1206	57358: {Code: KeyCapsLock},
1207	57359: {Code: KeyScrollLock},
1208	57360: {Code: KeyNumLock},
1209	57361: {Code: KeyPrintScreen},
1210	57362: {Code: KeyPause},
1211	57363: {Code: KeyMenu},
1212	57364: {Code: KeyF1},
1213	57365: {Code: KeyF2},
1214	57366: {Code: KeyF3},
1215	57367: {Code: KeyF4},
1216	57368: {Code: KeyF5},
1217	57369: {Code: KeyF6},
1218	57370: {Code: KeyF7},
1219	57371: {Code: KeyF8},
1220	57372: {Code: KeyF9},
1221	57373: {Code: KeyF10},
1222	57374: {Code: KeyF11},
1223	57375: {Code: KeyF12},
1224	57376: {Code: KeyF13},
1225	57377: {Code: KeyF14},
1226	57378: {Code: KeyF15},
1227	57379: {Code: KeyF16},
1228	57380: {Code: KeyF17},
1229	57381: {Code: KeyF18},
1230	57382: {Code: KeyF19},
1231	57383: {Code: KeyF20},
1232	57384: {Code: KeyF21},
1233	57385: {Code: KeyF22},
1234	57386: {Code: KeyF23},
1235	57387: {Code: KeyF24},
1236	57388: {Code: KeyF25},
1237	57389: {Code: KeyF26},
1238	57390: {Code: KeyF27},
1239	57391: {Code: KeyF28},
1240	57392: {Code: KeyF29},
1241	57393: {Code: KeyF30},
1242	57394: {Code: KeyF31},
1243	57395: {Code: KeyF32},
1244	57396: {Code: KeyF33},
1245	57397: {Code: KeyF34},
1246	57398: {Code: KeyF35},
1247	57399: {Code: KeyKp0},
1248	57400: {Code: KeyKp1},
1249	57401: {Code: KeyKp2},
1250	57402: {Code: KeyKp3},
1251	57403: {Code: KeyKp4},
1252	57404: {Code: KeyKp5},
1253	57405: {Code: KeyKp6},
1254	57406: {Code: KeyKp7},
1255	57407: {Code: KeyKp8},
1256	57408: {Code: KeyKp9},
1257	57409: {Code: KeyKpDecimal},
1258	57410: {Code: KeyKpDivide},
1259	57411: {Code: KeyKpMultiply},
1260	57412: {Code: KeyKpMinus},
1261	57413: {Code: KeyKpPlus},
1262	57414: {Code: KeyKpEnter},
1263	57415: {Code: KeyKpEqual},
1264	57416: {Code: KeyKpSep},
1265	57417: {Code: KeyKpLeft},
1266	57418: {Code: KeyKpRight},
1267	57419: {Code: KeyKpUp},
1268	57420: {Code: KeyKpDown},
1269	57421: {Code: KeyKpPgUp},
1270	57422: {Code: KeyKpPgDown},
1271	57423: {Code: KeyKpHome},
1272	57424: {Code: KeyKpEnd},
1273	57425: {Code: KeyKpInsert},
1274	57426: {Code: KeyKpDelete},
1275	57427: {Code: KeyKpBegin},
1276	57428: {Code: KeyMediaPlay},
1277	57429: {Code: KeyMediaPause},
1278	57430: {Code: KeyMediaPlayPause},
1279	57431: {Code: KeyMediaReverse},
1280	57432: {Code: KeyMediaStop},
1281	57433: {Code: KeyMediaFastForward},
1282	57434: {Code: KeyMediaRewind},
1283	57435: {Code: KeyMediaNext},
1284	57436: {Code: KeyMediaPrev},
1285	57437: {Code: KeyMediaRecord},
1286	57438: {Code: KeyLowerVol},
1287	57439: {Code: KeyRaiseVol},
1288	57440: {Code: KeyMute},
1289	57441: {Code: KeyLeftShift},
1290	57442: {Code: KeyLeftCtrl},
1291	57443: {Code: KeyLeftAlt},
1292	57444: {Code: KeyLeftSuper},
1293	57445: {Code: KeyLeftHyper},
1294	57446: {Code: KeyLeftMeta},
1295	57447: {Code: KeyRightShift},
1296	57448: {Code: KeyRightCtrl},
1297	57449: {Code: KeyRightAlt},
1298	57450: {Code: KeyRightSuper},
1299	57451: {Code: KeyRightHyper},
1300	57452: {Code: KeyRightMeta},
1301	57453: {Code: KeyIsoLevel3Shift},
1302	57454: {Code: KeyIsoLevel5Shift},
1303}
1304
1305func init() {
1306	// These are some faulty C0 mappings some terminals such as WezTerm have
1307	// and doesn't follow the specs.
1308	kittyKeyMap[ansi.NUL] = Key{Code: KeySpace, Mod: ModCtrl}
1309	for i := ansi.SOH; i <= ansi.SUB; i++ {
1310		if _, ok := kittyKeyMap[i]; !ok {
1311			kittyKeyMap[i] = Key{Code: rune(i + 0x60), Mod: ModCtrl}
1312		}
1313	}
1314	for i := ansi.FS; i <= ansi.US; i++ {
1315		if _, ok := kittyKeyMap[i]; !ok {
1316			kittyKeyMap[i] = Key{Code: rune(i + 0x40), Mod: ModCtrl}
1317		}
1318	}
1319}
1320
1321const (
1322	kittyShift = 1 << iota
1323	kittyAlt
1324	kittyCtrl
1325	kittySuper
1326	kittyHyper
1327	kittyMeta
1328	kittyCapsLock
1329	kittyNumLock
1330)
1331
1332func fromKittyMod(mod int) KeyMod {
1333	var m KeyMod
1334	if mod&kittyShift != 0 {
1335		m |= ModShift
1336	}
1337	if mod&kittyAlt != 0 {
1338		m |= ModAlt
1339	}
1340	if mod&kittyCtrl != 0 {
1341		m |= ModCtrl
1342	}
1343	if mod&kittySuper != 0 {
1344		m |= ModSuper
1345	}
1346	if mod&kittyHyper != 0 {
1347		m |= ModHyper
1348	}
1349	if mod&kittyMeta != 0 {
1350		m |= ModMeta
1351	}
1352	if mod&kittyCapsLock != 0 {
1353		m |= ModCapsLock
1354	}
1355	if mod&kittyNumLock != 0 {
1356		m |= ModNumLock
1357	}
1358	return m
1359}
1360
1361// parseKittyKeyboard parses a Kitty Keyboard Protocol sequence.
1362//
1363// In `CSI u`, this is parsed as:
1364//
1365//	CSI codepoint ; modifiers u
1366//	codepoint: ASCII Dec value
1367//
1368// The Kitty Keyboard Protocol extends this with optional components that can be
1369// enabled progressively. The full sequence is parsed as:
1370//
1371//	CSI unicode-key-code:alternate-key-codes ; modifiers:event-type ; text-as-codepoints u
1372//
1373// See https://sw.kovidgoyal.net/kitty/keyboard-protocol/
1374func parseKittyKeyboard(params ansi.Params) (Event Event) {
1375	var isRelease bool
1376	var key Key
1377
1378	// The index of parameters separated by semicolons ';'. Sub parameters are
1379	// separated by colons ':'.
1380	var paramIdx int
1381	var sudIdx int // The sub parameter index
1382	for _, p := range params {
1383		// Kitty Keyboard Protocol has 3 optional components.
1384		switch paramIdx {
1385		case 0:
1386			switch sudIdx {
1387			case 0:
1388				var foundKey bool
1389				code := p.Param(1) // CSI u has a default value of 1
1390				key, foundKey = kittyKeyMap[code]
1391				if !foundKey {
1392					r := rune(code)
1393					if !utf8.ValidRune(r) {
1394						r = utf8.RuneError
1395					}
1396
1397					key.Code = r
1398				}
1399
1400			case 2:
1401				// shifted key + base key
1402				if b := rune(p.Param(1)); unicode.IsPrint(b) {
1403					// XXX: When alternate key reporting is enabled, the protocol
1404					// can return 3 things, the unicode codepoint of the key,
1405					// the shifted codepoint of the key, and the standard
1406					// PC-101 key layout codepoint.
1407					// This is useful to create an unambiguous mapping of keys
1408					// when using a different language layout.
1409					key.BaseCode = b
1410				}
1411				fallthrough
1412
1413			case 1:
1414				// shifted key
1415				if s := rune(p.Param(1)); unicode.IsPrint(s) {
1416					// XXX: We swap keys here because we want the shifted key
1417					// to be the Rune that is returned by the event.
1418					// For example, shift+a should produce "A" not "a".
1419					// In such a case, we set AltRune to the original key "a"
1420					// and Rune to "A".
1421					key.ShiftedCode = s
1422				}
1423			}
1424		case 1:
1425			switch sudIdx {
1426			case 0:
1427				mod := p.Param(1)
1428				if mod > 1 {
1429					key.Mod = fromKittyMod(mod - 1)
1430					if key.Mod > ModShift {
1431						// XXX: We need to clear the text if we have a modifier key
1432						// other than a [ModShift] key.
1433						key.Text = ""
1434					}
1435				}
1436
1437			case 1:
1438				switch p.Param(1) {
1439				case 2:
1440					key.IsRepeat = true
1441				case 3:
1442					isRelease = true
1443				}
1444			case 2:
1445			}
1446		case 2:
1447			if code := p.Param(0); code != 0 {
1448				key.Text += string(rune(code))
1449			}
1450		}
1451
1452		sudIdx++
1453		if !p.HasMore() {
1454			paramIdx++
1455			sudIdx = 0
1456		}
1457	}
1458
1459	//nolint:nestif
1460	if len(key.Text) == 0 && unicode.IsPrint(key.Code) &&
1461		(key.Mod <= ModShift || key.Mod == ModCapsLock || key.Mod == ModShift|ModCapsLock) {
1462		if key.Mod == 0 {
1463			key.Text = string(key.Code)
1464		} else {
1465			desiredCase := unicode.ToLower
1466			if key.Mod.Contains(ModShift) || key.Mod.Contains(ModCapsLock) {
1467				desiredCase = unicode.ToUpper
1468			}
1469			if key.ShiftedCode != 0 {
1470				key.Text = string(key.ShiftedCode)
1471			} else {
1472				key.Text = string(desiredCase(key.Code))
1473			}
1474		}
1475	}
1476
1477	if isRelease {
1478		return KeyReleaseEvent(key)
1479	}
1480
1481	return KeyPressEvent(key)
1482}
1483
1484// parseKittyKeyboardExt parses a Kitty Keyboard Protocol sequence extensions
1485// for non CSI u sequences. This includes things like CSI A, SS3 A and others,
1486// and CSI ~.
1487func parseKittyKeyboardExt(params ansi.Params, k KeyPressEvent) Event {
1488	// Handle Kitty keyboard protocol
1489	if len(params) > 2 && // We have at least 3 parameters
1490		params[0].Param(1) == 1 && // The first parameter is 1 (defaults to 1)
1491		params[1].HasMore() { // The second parameter is a subparameter (separated by a ":")
1492		switch params[2].Param(1) { // The third parameter is the event type (defaults to 1)
1493		case 2:
1494			k.IsRepeat = true
1495		case 3:
1496			return KeyReleaseEvent(k)
1497		}
1498	}
1499	return k
1500}
1501
1502func parsePrimaryDevAttrs(params ansi.Params) Event {
1503	// Primary Device Attributes
1504	da1 := make(PrimaryDeviceAttributesEvent, len(params))
1505	for i, p := range params {
1506		if !p.HasMore() {
1507			da1[i] = p.Param(0)
1508		}
1509	}
1510	return da1
1511}
1512
1513// Parse SGR-encoded mouse events; SGR extended mouse events. SGR mouse events
1514// look like:
1515//
1516//	ESC [ < Cb ; Cx ; Cy (M or m)
1517//
1518// where:
1519//
1520//	Cb is the encoded button code
1521//	Cx is the x-coordinate of the mouse
1522//	Cy is the y-coordinate of the mouse
1523//	M is for button press, m is for button release
1524//
1525// https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Extended-coordinates
1526func parseSGRMouseEvent(cmd ansi.Cmd, params ansi.Params) Event {
1527	x, _, ok := params.Param(1, 1)
1528	if !ok {
1529		x = 1
1530	}
1531	y, _, ok := params.Param(2, 1)
1532	if !ok {
1533		y = 1
1534	}
1535	release := cmd.Final() == 'm'
1536	b, _, _ := params.Param(0, 0)
1537	mod, btn, _, isMotion := parseMouseButton(b)
1538
1539	// (1,1) is the upper left. We subtract 1 to normalize it to (0,0).
1540	x--
1541	y--
1542
1543	m := Mouse{X: x, Y: y, Button: btn, Mod: mod}
1544
1545	// Wheel buttons don't have release events
1546	// Motion can be reported as a release event in some terminals (Windows Terminal)
1547	if isWheel(m.Button) {
1548		return MouseWheelEvent(m)
1549	} else if !isMotion && release {
1550		return MouseReleaseEvent(m)
1551	} else if isMotion {
1552		return MouseMotionEvent(m)
1553	}
1554	return MouseClickEvent(m)
1555}
1556
1557const x10MouseByteOffset = 32
1558
1559// Parse X10-encoded mouse events; the simplest kind. The last release of X10
1560// was December 1986, by the way. The original X10 mouse protocol limits the Cx
1561// and Cy coordinates to 223 (=255-032).
1562//
1563// X10 mouse events look like:
1564//
1565//	ESC [M Cb Cx Cy
1566//
1567// See: http://www.xfree86.org/current/ctlseqs.html#Mouse%20Tracking
1568func parseX10MouseEvent(buf []byte) Event {
1569	v := buf[3:6]
1570	b := int(v[0])
1571	if b >= x10MouseByteOffset {
1572		// XXX: b < 32 should be impossible, but we're being defensive.
1573		b -= x10MouseByteOffset
1574	}
1575
1576	mod, btn, isRelease, isMotion := parseMouseButton(b)
1577
1578	// (1,1) is the upper left. We subtract 1 to normalize it to (0,0).
1579	x := int(v[1]) - x10MouseByteOffset - 1
1580	y := int(v[2]) - x10MouseByteOffset - 1
1581
1582	m := Mouse{X: x, Y: y, Button: btn, Mod: mod}
1583	if isWheel(m.Button) {
1584		return MouseWheelEvent(m)
1585	} else if isMotion {
1586		return MouseMotionEvent(m)
1587	} else if isRelease {
1588		return MouseReleaseEvent(m)
1589	}
1590	return MouseClickEvent(m)
1591}
1592
1593// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Extended-coordinates
1594func parseMouseButton(b int) (mod KeyMod, btn MouseButton, isRelease bool, isMotion bool) {
1595	// mouse bit shifts
1596	const (
1597		bitShift  = 0b0000_0100
1598		bitAlt    = 0b0000_1000
1599		bitCtrl   = 0b0001_0000
1600		bitMotion = 0b0010_0000
1601		bitWheel  = 0b0100_0000
1602		bitAdd    = 0b1000_0000 // additional buttons 8-11
1603
1604		bitsMask = 0b0000_0011
1605	)
1606
1607	// Modifiers
1608	if b&bitAlt != 0 {
1609		mod |= ModAlt
1610	}
1611	if b&bitCtrl != 0 {
1612		mod |= ModCtrl
1613	}
1614	if b&bitShift != 0 {
1615		mod |= ModShift
1616	}
1617
1618	if b&bitAdd != 0 {
1619		btn = MouseBackward + MouseButton(b&bitsMask)
1620	} else if b&bitWheel != 0 {
1621		btn = MouseWheelUp + MouseButton(b&bitsMask)
1622	} else {
1623		btn = MouseLeft + MouseButton(b&bitsMask)
1624		// X10 reports a button release as 0b0000_0011 (3)
1625		if b&bitsMask == bitsMask {
1626			btn = MouseNone
1627			isRelease = true
1628		}
1629	}
1630
1631	// Motion bit doesn't get reported for wheel events.
1632	if b&bitMotion != 0 && !isWheel(btn) {
1633		isMotion = true
1634	}
1635
1636	return //nolint:nakedret
1637}
1638
1639// isWheel returns true if the mouse event is a wheel event.
1640func isWheel(btn MouseButton) bool {
1641	return btn >= MouseWheelUp && btn <= MouseWheelRight
1642}
1643
1644type shiftable interface {
1645	~uint | ~uint16 | ~uint32 | ~uint64
1646}
1647
1648func shift[T shiftable](x T) T {
1649	if x > 0xff {
1650		x >>= 8
1651	}
1652	return x
1653}
1654
1655func colorToHex(c color.Color) string {
1656	if c == nil {
1657		return ""
1658	}
1659	r, g, b, _ := c.RGBA()
1660	return fmt.Sprintf("#%02x%02x%02x", shift(r), shift(g), shift(b))
1661}
1662
1663func getMaxMin(a, b, c float64) (ma, mi float64) {
1664	if a > b {
1665		ma = a
1666		mi = b
1667	} else {
1668		ma = b
1669		mi = a
1670	}
1671	if c > ma {
1672		ma = c
1673	} else if c < mi {
1674		mi = c
1675	}
1676	return ma, mi
1677}
1678
1679func round(x float64) float64 {
1680	return math.Round(x*1000) / 1000
1681}
1682
1683// rgbToHSL converts an RGB triple to an HSL triple.
1684func rgbToHSL(r, g, b uint8) (h, s, l float64) {
1685	// convert uint32 pre-multiplied value to uint8
1686	// The r,g,b values are divided by 255 to change the range from 0..255 to 0..1:
1687	Rnot := float64(r) / 255
1688	Gnot := float64(g) / 255
1689	Bnot := float64(b) / 255
1690	Cmax, Cmin := getMaxMin(Rnot, Gnot, Bnot)
1691	Δ := Cmax - Cmin
1692	// Lightness calculation:
1693	l = (Cmax + Cmin) / 2
1694	// Hue and Saturation Calculation:
1695	if Δ == 0 {
1696		h = 0
1697		s = 0
1698	} else {
1699		switch Cmax {
1700		case Rnot:
1701			h = 60 * (math.Mod((Gnot-Bnot)/Δ, 6))
1702		case Gnot:
1703			h = 60 * (((Bnot - Rnot) / Δ) + 2)
1704		case Bnot:
1705			h = 60 * (((Rnot - Gnot) / Δ) + 4)
1706		}
1707		if h < 0 {
1708			h += 360
1709		}
1710
1711		s = Δ / (1 - math.Abs((2*l)-1))
1712	}
1713
1714	return h, round(s), round(l)
1715}
1716
1717// isDarkColor returns whether the given color is dark.
1718func isDarkColor(c color.Color) bool {
1719	if c == nil {
1720		return true
1721	}
1722
1723	r, g, b, _ := c.RGBA()
1724	_, _, l := rgbToHSL(uint8(r>>8), uint8(g>>8), uint8(b>>8)) //nolint:gosec
1725	return l < 0.5
1726}
1727
1728func parseTermcap(data []byte) CapabilityEvent {
1729	// XTGETTCAP
1730	if len(data) == 0 {
1731		return CapabilityEvent("")
1732	}
1733
1734	var tc strings.Builder
1735	split := bytes.Split(data, []byte{';'})
1736	for _, s := range split {
1737		parts := bytes.SplitN(s, []byte{'='}, 2)
1738		if len(parts) == 0 {
1739			return CapabilityEvent("")
1740		}
1741
1742		name, err := hex.DecodeString(string(parts[0]))
1743		if err != nil || len(name) == 0 {
1744			continue
1745		}
1746
1747		var value []byte
1748		if len(parts) > 1 {
1749			value, err = hex.DecodeString(string(parts[1]))
1750			if err != nil {
1751				continue
1752			}
1753		}
1754
1755		if tc.Len() > 0 {
1756			tc.WriteByte(';')
1757		}
1758		tc.WriteString(string(name))
1759		if len(value) > 0 {
1760			tc.WriteByte('=')
1761			tc.WriteString(string(value))
1762		}
1763	}
1764
1765	return CapabilityEvent(tc.String())
1766}