main
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}