File: key_sequences.go

package info (click to toggle)
golang-github-charmbracelet-bubbletea 0.27.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 3,352 kB
  • sloc: makefile: 3
file content (119 lines) | stat: -rw-r--r-- 3,212 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
package tea

import (
	"bytes"
	"sort"
	"unicode/utf8"
)

// extSequences is used by the map-based algorithm below. It contains
// the sequences plus their alternatives with an escape character
// prefixed, plus the control chars, plus the space.
// It does not contain the NUL character, which is handled specially
// by detectOneMsg.
var extSequences = func() map[string]Key {
	s := map[string]Key{}
	for seq, key := range sequences {
		key := key
		s[seq] = key
		if !key.Alt {
			key.Alt = true
			s["\x1b"+seq] = key
		}
	}
	for i := keyNUL + 1; i <= keyDEL; i++ {
		if i == keyESC {
			continue
		}
		s[string([]byte{byte(i)})] = Key{Type: i}
		s[string([]byte{'\x1b', byte(i)})] = Key{Type: i, Alt: true}
		if i == keyUS {
			i = keyDEL - 1
		}
	}
	s[" "] = Key{Type: KeySpace, Runes: spaceRunes}
	s["\x1b "] = Key{Type: KeySpace, Alt: true, Runes: spaceRunes}
	s["\x1b\x1b"] = Key{Type: KeyEscape, Alt: true}
	return s
}()

// seqLengths is the sizes of valid sequences, starting with the
// largest size.
var seqLengths = func() []int {
	sizes := map[int]struct{}{}
	for seq := range extSequences {
		sizes[len(seq)] = struct{}{}
	}
	lsizes := make([]int, 0, len(sizes))
	for sz := range sizes {
		lsizes = append(lsizes, sz)
	}
	sort.Slice(lsizes, func(i, j int) bool { return lsizes[i] > lsizes[j] })
	return lsizes
}()

// detectSequence uses a longest prefix match over the input
// sequence and a hash map.
func detectSequence(input []byte) (hasSeq bool, width int, msg Msg) {
	seqs := extSequences
	for _, sz := range seqLengths {
		if sz > len(input) {
			continue
		}
		prefix := input[:sz]
		key, ok := seqs[string(prefix)]
		if ok {
			return true, sz, KeyMsg(key)
		}
	}
	// Is this an unknown CSI sequence?
	if loc := unknownCSIRe.FindIndex(input); loc != nil {
		return true, loc[1], unknownCSISequenceMsg(input[:loc[1]])
	}

	return false, 0, nil
}

// detectBracketedPaste detects an input pasted while bracketed
// paste mode was enabled.
//
// Note: this function is a no-op if bracketed paste was not enabled
// on the terminal, since in that case we'd never see this
// particular escape sequence.
func detectBracketedPaste(input []byte) (hasBp bool, width int, msg Msg) {
	// Detect the start sequence.
	const bpStart = "\x1b[200~"
	if len(input) < len(bpStart) || string(input[:len(bpStart)]) != bpStart {
		return false, 0, nil
	}

	// Skip over the start sequence.
	input = input[len(bpStart):]

	// If we saw the start sequence, then we must have an end sequence
	// as well. Find it.
	const bpEnd = "\x1b[201~"
	idx := bytes.Index(input, []byte(bpEnd))
	inputLen := len(bpStart) + idx + len(bpEnd)
	if idx == -1 {
		// We have encountered the end of the input buffer without seeing
		// the marker for the end of the bracketed paste.
		// Tell the outer loop we have done a short read and we want more.
		return true, 0, nil
	}

	// The paste is everything in-between.
	paste := input[:idx]

	// All there is in-between is runes, not to be interpreted further.
	k := Key{Type: KeyRunes, Paste: true}
	for len(paste) > 0 {
		r, w := utf8.DecodeRune(paste)
		if r != utf8.RuneError {
			k.Runes = append(k.Runes, r)
		}
		paste = paste[w:]
	}

	return true, inputLen, KeyMsg(k)
}