main
Raw Download raw file
  1package tea
  2
  3import (
  4	"bytes"
  5	"sort"
  6	"unicode/utf8"
  7)
  8
  9// extSequences is used by the map-based algorithm below. It contains
 10// the sequences plus their alternatives with an escape character
 11// prefixed, plus the control chars, plus the space.
 12// It does not contain the NUL character, which is handled specially
 13// by detectOneMsg.
 14var extSequences = func() map[string]Key {
 15	s := map[string]Key{}
 16	for seq, key := range sequences {
 17		key := key
 18		s[seq] = key
 19		if !key.Alt {
 20			key.Alt = true
 21			s["\x1b"+seq] = key
 22		}
 23	}
 24	for i := keyNUL + 1; i <= keyDEL; i++ {
 25		if i == keyESC {
 26			continue
 27		}
 28		s[string([]byte{byte(i)})] = Key{Type: i}
 29		s[string([]byte{'\x1b', byte(i)})] = Key{Type: i, Alt: true}
 30		if i == keyUS {
 31			i = keyDEL - 1
 32		}
 33	}
 34	s[" "] = Key{Type: KeySpace, Runes: spaceRunes}
 35	s["\x1b "] = Key{Type: KeySpace, Alt: true, Runes: spaceRunes}
 36	s["\x1b\x1b"] = Key{Type: KeyEscape, Alt: true}
 37	return s
 38}()
 39
 40// seqLengths is the sizes of valid sequences, starting with the
 41// largest size.
 42var seqLengths = func() []int {
 43	sizes := map[int]struct{}{}
 44	for seq := range extSequences {
 45		sizes[len(seq)] = struct{}{}
 46	}
 47	lsizes := make([]int, 0, len(sizes))
 48	for sz := range sizes {
 49		lsizes = append(lsizes, sz)
 50	}
 51	sort.Slice(lsizes, func(i, j int) bool { return lsizes[i] > lsizes[j] })
 52	return lsizes
 53}()
 54
 55// detectSequence uses a longest prefix match over the input
 56// sequence and a hash map.
 57func detectSequence(input []byte) (hasSeq bool, width int, msg Msg) {
 58	seqs := extSequences
 59	for _, sz := range seqLengths {
 60		if sz > len(input) {
 61			continue
 62		}
 63		prefix := input[:sz]
 64		key, ok := seqs[string(prefix)]
 65		if ok {
 66			return true, sz, KeyMsg(key)
 67		}
 68	}
 69	// Is this an unknown CSI sequence?
 70	if loc := unknownCSIRe.FindIndex(input); loc != nil {
 71		return true, loc[1], unknownCSISequenceMsg(input[:loc[1]])
 72	}
 73
 74	return false, 0, nil
 75}
 76
 77// detectBracketedPaste detects an input pasted while bracketed
 78// paste mode was enabled.
 79//
 80// Note: this function is a no-op if bracketed paste was not enabled
 81// on the terminal, since in that case we'd never see this
 82// particular escape sequence.
 83func detectBracketedPaste(input []byte) (hasBp bool, width int, msg Msg) {
 84	// Detect the start sequence.
 85	const bpStart = "\x1b[200~"
 86	if len(input) < len(bpStart) || string(input[:len(bpStart)]) != bpStart {
 87		return false, 0, nil
 88	}
 89
 90	// Skip over the start sequence.
 91	input = input[len(bpStart):]
 92
 93	// If we saw the start sequence, then we must have an end sequence
 94	// as well. Find it.
 95	const bpEnd = "\x1b[201~"
 96	idx := bytes.Index(input, []byte(bpEnd))
 97	inputLen := len(bpStart) + idx + len(bpEnd)
 98	if idx == -1 {
 99		// We have encountered the end of the input buffer without seeing
100		// the marker for the end of the bracketed paste.
101		// Tell the outer loop we have done a short read and we want more.
102		return true, 0, nil
103	}
104
105	// The paste is everything in-between.
106	paste := input[:idx]
107
108	// All there is in-between is runes, not to be interpreted further.
109	k := Key{Type: KeyRunes, Paste: true}
110	for len(paste) > 0 {
111		r, w := utf8.DecodeRune(paste)
112		if r != utf8.RuneError {
113			k.Runes = append(k.Runes, r)
114		}
115		paste = paste[w:]
116	}
117
118	return true, inputLen, KeyMsg(k)
119}
120
121// detectReportFocus detects a focus report sequence.
122// nolint: gomnd
123func detectReportFocus(input []byte) (hasRF bool, width int, msg Msg) {
124	switch {
125	case bytes.Equal(input, []byte("\x1b[I")):
126		return true, 3, FocusMsg{}
127	case bytes.Equal(input, []byte("\x1b[O")):
128		return true, 3, BlurMsg{}
129	}
130	return false, 0, nil
131}