forked from Mirrors/bubbletea
120 lines
3.1 KiB
Go
120 lines
3.1 KiB
Go
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)
|
|
}
|