elvish/edit/reader.go
2013-11-17 00:36:24 +08:00

284 lines
5.9 KiB
Go

package edit
import (
"fmt"
"time"
"bufio"
"../util"
)
var EscTimeout = time.Millisecond * 10
// reader is the part of an Editor responsible for reading and decoding
// terminal key sequences.
type reader struct {
timed *util.TimedReader
buffed *bufio.Reader
unreadBuffer []rune
currentSeq string
}
func newReader(tr *util.TimedReader) *reader {
return &reader{
timed: tr,
buffed: bufio.NewReaderSize(tr, 0),
}
}
type BadEscSeq struct {
seq string
msg string
}
func newBadEscSeq(seq string, msg string) *BadEscSeq {
return &BadEscSeq{seq, msg}
}
func (bes *BadEscSeq) Error() string {
return fmt.Sprintf("Bad escape sequence %q: ", bes.seq, bes.msg)
}
func (rd *reader) badEscSeq(msg string) {
util.Panic(newBadEscSeq(rd.currentSeq, msg))
}
// G3 style function key sequences: ^[O followed by exactly one character.
var g3Seq = map[rune]rune{
// F1-F4: xterm, libvte and tmux
'P': F1, 'Q': F2,
'R': F3, 'S': F4,
// Home and End: libvte
'H': Home, 'F': End,
}
const (
RUNE_TIMEOUT rune = -1
)
func (rd *reader) readRune() (r rune) {
n := len(rd.unreadBuffer)
switch {
case n > 1:
r = rd.unreadBuffer[0]
rd.unreadBuffer = rd.unreadBuffer[1:]
fallthrough
case n == 1:
// Zero out unreadBuffer to avoid memory leak.
rd.unreadBuffer = nil
case n == 0:
var err error
r, _, err = rd.buffed.ReadRune()
if err == util.Timeout {
return RUNE_TIMEOUT
} else if err != nil {
util.Panic(err)
}
}
rd.currentSeq += string(r)
return
}
func (rd *reader) unreadRune(r... rune) {
// XXX Should back up rd.currentSeq too
rd.unreadBuffer = append(rd.unreadBuffer, r...)
}
func (rd *reader) readAssertedRune(r rune) {
if rd.readRune() != r {
rd.badEscSeq("Expect " + string(r))
}
}
// readCPR reads a cursor position report, in the form \033 [ y ; x R
func (rd *reader) readCPR() (x int, y int, err error) {
rd.readAssertedRune(0x1b)
rd.readAssertedRune('[')
yloop:
for {
switch r := rd.readRune(); {
case r == ';':
break yloop
case '0' <= r && r <= '9':
y = y * 10 + int(r - '0')
default:
rd.badEscSeq("Expect number or semicolon")
}
}
xloop:
for {
switch r := rd.readRune(); {
case r == 'R':
break xloop
case '0' <= r && r <= '9':
x = x * 10 + int(r - '0')
default:
rd.badEscSeq("Expect number or 'R'")
}
}
return
}
func (rd *reader) readKey() (k Key, err error) {
defer util.Recover(&err)
rd.currentSeq = ""
switch r := rd.readRune(); r {
case Tab, Enter, Backspace:
k = Key{r, 0}
case 0x0:
k = Key{'`', Ctrl} // ^@
case 0x1d:
k = Key{'6', Ctrl} // ^^
case 0x1f:
k = Key{'/', Ctrl} // ^_
case 0x1b: // ^[ Escape
rd.timed.Timeout = EscTimeout
defer func() { rd.timed.Timeout = -1 }()
r2 := rd.readRune()
if r2 == RUNE_TIMEOUT {
return Key{'[', Ctrl}, nil
}
switch r2 {
case '[':
// CSI style function key sequence, looks like [\d;]*[^\d;]
// Read numeric parameters (if any)
nums := make([]int, 0, 2)
seq := "\x1b["
for {
r = rd.readRune()
// Timeout can only happen at first readRune.
if r == RUNE_TIMEOUT {
return Key{'[', Alt}, nil
}
seq += string(r)
// After first rune read we turn off the timeout
rd.timed.Timeout = -1
if r != ';' && (r < '0' || r > '9') {
break
}
if len(nums) == 0 {
nums = append(nums, 0)
}
if r == ';' {
nums = append(nums, 0)
} else {
cur := len(nums) - 1
nums[cur] = nums[cur] * 10 + int(r - '0')
}
}
return parseCSI(nums, r, seq)
case 'O':
// G3 style function key sequence: read one rune.
r = rd.readRune()
if r == RUNE_TIMEOUT {
return Key{r2, Alt}, nil
}
r, ok := g3Seq[r]
if ok {
return Key{r, 0}, nil
} else {
rd.badEscSeq("")
}
}
return Key{r2, Alt}, nil
default:
// Sane Ctrl- sequences that agree with the keyboard...
if 0x1 <= r && r <= 0x1d {
k = Key{r+0x40, Ctrl}
} else {
k = Key{r, 0}
}
}
return
}
var keyByLast = map[rune]rune{
'A': Up, 'B': Down, 'C': Right, 'D': Left,
'H': Home, 'F': End,
}
// last == '~'
var keyByNum0 = map[int]rune{
1: Home, 2: Insert, 3: Delete, 4: End, 5: PageUp, 6: PageDown,
11: F1, 12: F2, 13: F3, 14: F4,
15: F5, 17: F6, 18: F7, 19: F8, 20: F9, 21: F10, 23: F11, 24: F12,
}
// last == '~', num[0] == 27
// The list is taken blindly from tmux source xterm-keys.c. I don't have a
// keyboard that can generate such sequences, but assumably some PC keyboard
// with a numpad can.
var keyByNum2 = map[int]rune{
9: '\t', 13: '\r',
33: '!', 35: '#', 39: '\'', 40: '(', 41: ')', 43: '+', 44: ',', 45: '-',
46: '.',
48: '0', 49: '1', 50: '2', 51: '3', 52: '4', 53: '5', 54: '6', 55: '7',
56: '8', 57: '9',
58: ':', 59: ';', 60: '<', 61: '=', 62: '>', 63: ';',
}
// Parse a CSI-style function key sequence.
func parseCSI(nums []int, last rune, seq string) (Key, error) {
if r, ok := keyByLast[last]; ok {
k := Key{r, 0}
if len(nums) == 0 {
// Unmodified: \e[A (Up)
return k, nil
} else if len(nums) == 2 && nums[0] == 1 {
// Modified: \e[1;5A (Ctrl-Up)
return xtermModify(k, nums[1], seq)
} else {
return ZeroKey, newBadEscSeq(seq, "")
}
}
if last == '~' {
if len(nums) == 1 || len(nums) == 2 {
if r, ok := keyByNum0[nums[0]]; ok {
k := Key{r, 0}
if len(nums) == 1 {
// Unmodified: \e[5~ (PageUp)
return k, nil
} else {
// Modified: \e[5;5~ (Ctrl-PageUp)
return xtermModify(k, nums[1], seq)
}
}
} else if len(nums) == 3 && nums[0] == 27 {
if r, ok := keyByNum2[nums[2]]; ok {
k := Key{r, 0}
return xtermModify(k, nums[1], seq)
}
}
}
return ZeroKey, newBadEscSeq(seq, "")
}
func xtermModify(k Key, mod int, seq string) (Key, error) {
switch mod {
case 0:
// do nothing
case 2:
k.Mod |= Shift
case 3:
k.Mod |= Alt
case 4:
k.Mod |= Shift | Alt
case 5:
k.Mod |= Ctrl
case 6:
k.Mod |= Shift | Ctrl
case 7:
k.Mod |= Alt | Ctrl
case 8:
k.Mod |= Shift | Alt | Ctrl
default:
return ZeroKey, newBadEscSeq(seq, "")
}
return k, nil
}