Commit 7a13be41 authored by Eric's avatar Eric

stash

parent 0bcfdc7a
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package utils
import (
"bytes"
"fmt"
"io"
"strconv"
"unicode/utf8"
)
func ParseTerminalData(p []byte) (lines []string) {
c := bytes.NewReader(p)
pasteActive := false
var line []rune
var pos int
var remainder []byte
var inBuf [256]byte
for {
rest := remainder
lineOk := false
for !lineOk {
var key rune
key, rest = bytesToKey(rest, pasteActive)
if key == utf8.RuneError {
type TerminalParser struct {
// line is the current line being entered.
line []rune
// pos is the logical position of the cursor in line
pos int
// pasteActive is true iff there is a bracketed paste operation in
// progress.
pasteActive bool
// maxLine is the greatest value of cursorY so far.
maxLine int
// remainder contains the remainder of any partial key sequences after
// a read. It aliases into inBuf.
remainder []byte
inBuf [256]byte
// history contains previously entered commands so that they can be
// accessed with the up and down keys.
history stRingBuffer
// historyIndex stores the currently accessed history entry, where zero
// means the immediately previous entry.
historyIndex int
// When navigating up and down the history it's possible to return to
// the incomplete, initial line. That value is stored in
// historyPending.
historyPending string
}
// NewTerminal runs a VT100 terminal on the given ReadWriter. If the ReadWriter is
// a local terminal, that terminal must first have been put into raw mode.
// prompt is a string that is written at the start of each input line (i.e.
// "> ").
func NewTerminalParser(prompt string) *TerminalParser {
return &TerminalParser{
historyIndex: -1,
}
}
func (t *TerminalParser) move(up, down, left, right int) {
m := []rune{}
// 1 unit up can be expressed as ^[[A or ^[A
// 5 units up can be expressed as ^[[5A
if up == 1 {
m = append(m, keyEscape, '[', 'A')
} else if up > 1 {
m = append(m, keyEscape, '[')
m = append(m, []rune(strconv.Itoa(up))...)
m = append(m, 'A')
}
if down == 1 {
m = append(m, keyEscape, '[', 'B')
} else if down > 1 {
m = append(m, keyEscape, '[')
m = append(m, []rune(strconv.Itoa(down))...)
m = append(m, 'B')
}
if right == 1 {
m = append(m, keyEscape, '[', 'C')
} else if right > 1 {
m = append(m, keyEscape, '[')
m = append(m, []rune(strconv.Itoa(right))...)
m = append(m, 'C')
}
if left == 1 {
m = append(m, keyEscape, '[', 'D')
} else if left > 1 {
m = append(m, keyEscape, '[')
m = append(m, []rune(strconv.Itoa(left))...)
m = append(m, 'D')
}
}
func (t *TerminalParser) setLine(newLine []rune, newPos int) {
t.line = newLine
t.pos = newPos
}
func (t *TerminalParser) eraseNPreviousChars(n int) {
if n == 0 {
return
}
if t.pos < n {
n = t.pos
}
t.pos -= n
copy(t.line[t.pos:], t.line[n+t.pos:])
t.line = t.line[:len(t.line)-n]
}
// countToLeftWord returns then number of characters from the cursor to the
// start of the previous word.
func (t *TerminalParser) countToLeftWord() int {
if t.pos == 0 {
return 0
}
pos := t.pos - 1
for pos > 0 {
if t.line[pos] != ' ' {
break
}
if !pasteActive {
if key == keyPasteStart {
pasteActive = true
if len(line) == 0 {
pos--
}
continue
for pos > 0 {
if t.line[pos] == ' ' {
pos++
break
}
} else if key == keyPasteEnd {
pasteActive = false
continue
pos--
}
return t.pos - pos
}
// countToRightWord returns then number of characters from the cursor to the
// start of the next word.
func (t *TerminalParser) countToRightWord() int {
pos := t.pos
for pos < len(t.line) {
if t.line[pos] == ' ' {
break
}
pos++
}
for pos < len(t.line) {
if t.line[pos] != ' ' {
break
}
pos++
}
return pos - t.pos
}
// handleKey processes the given key and, optionally, returns a line of text
// that the user has entered.
func (t *TerminalParser) handleKey(key rune) (line string, ok bool) {
if t.pasteActive && key != keyEnter {
t.addKeyToLine(key)
return
}
switch key {
case keyBackspace:
if pos == 0 {
continue
if t.pos == 0 {
return
}
line, pos = EraseNPreviousChars(1, pos, line)
t.eraseNPreviousChars(1)
case keyAltLeft:
// move left by a word.
pos -= CountToLeftWord(pos, line)
t.pos -= t.countToLeftWord()
case keyAltRight:
// move right by a word.
pos += CountToRightWord(pos, line)
t.pos += t.countToRightWord()
case keyLeft:
if pos == 0 {
continue
if t.pos == 0 {
return
}
pos--
t.pos--
case keyRight:
if pos == len(line) {
continue
if t.pos == len(t.line) {
return
}
pos++
t.pos++
case keyHome:
if pos == 0 {
continue
if t.pos == 0 {
return
}
pos = 0
t.pos = 0
case keyEnd:
if pos == len(line) {
continue
if t.pos == len(t.line) {
return
}
pos = len(line)
t.pos = len(t.line)
case keyUp:
line = []rune{}
pos = 0
entry, ok := t.history.NthPreviousEntry(t.historyIndex + 1)
if !ok {
return "", false
}
if t.historyIndex == -1 {
t.historyPending = string(t.line)
}
t.historyIndex++
runes := []rune(entry)
t.setLine(runes, len(runes))
case keyDown:
line = []rune{}
pos = 0
switch t.historyIndex {
case -1:
return
case 0:
runes := []rune(t.historyPending)
t.setLine(runes, len(runes))
t.historyIndex--
default:
entry, ok := t.history.NthPreviousEntry(t.historyIndex - 1)
if ok {
t.historyIndex--
runes := []rune(entry)
t.setLine(runes, len(runes))
}
}
case keyEnter:
lines = append(lines, string(line))
line = line[:0]
pos = 0
lineOk = true
line = string(t.line)
ok = true
t.line = t.line[:0]
t.pos = 0
t.maxLine = 0
case keyDeleteWord:
// Delete zero or more spaces and then one or more characters.
line, pos = EraseNPreviousChars(CountToLeftWord(pos, line), pos, line)
t.eraseNPreviousChars(t.countToLeftWord())
case keyDeleteLine:
line = line[:pos]
t.line = t.line[:t.pos]
case keyCtrlD:
// Erase the character under the current position.
// The EOF case when the line is empty is handled in
// readLine().
if pos < len(line) {
pos++
line, pos = EraseNPreviousChars(1, pos, line)
if t.pos < len(t.line) {
t.pos++
t.eraseNPreviousChars(1)
}
case keyCtrlU:
line = line[:0]
t.eraseNPreviousChars(t.pos)
case keyClearScreen:
// Erases the screen and moves the cursor to the home position.
t.setLine(t.line, t.pos)
default:
if !isPrintable(key) {
fmt.Println("could not printable: ", []byte(string(key)), " ", key)
continue
}
line, pos = AddKeyToLine(key, pos, line)
}
}
if len(rest) > 0 {
n := copy(inBuf[:], rest)
remainder = inBuf[:n]
} else {
remainder = nil
}
// remainder is a slice at the beginning of t.inBuf
// containing a partial key sequence
readBuf := inBuf[len(remainder):]
var n int
n, err := c.Read(readBuf)
if err != nil {
if len(line) > 0 {
lines = append(lines, string(line))
} else if len(rest) > 0 {
lines = append(lines, string(rest))
return
}
if len(t.line) == maxLineLength {
return
}
remainder = inBuf[:n+len(remainder)]
t.addKeyToLine(key)
}
return
}
func EraseNPreviousChars(n, cPos int, line []rune) ([]rune, int) {
if n == 0 {
return line, cPos
// addKeyToLine inserts the given key at the current position in the current
// line.
func (t *TerminalParser) addKeyToLine(key rune) {
if len(t.line) == cap(t.line) {
newLine := make([]rune, len(t.line), 2*(1+len(t.line)))
copy(newLine, t.line)
t.line = newLine
}
if cPos < n {
n = cPos
}
cPos -= n
copy(line[cPos:], line[n+cPos:])
return line[:len(line)-n], cPos
t.line = t.line[:len(t.line)+1]
copy(t.line[t.pos+1:], t.line[t.pos:])
t.line[t.pos] = key
t.pos++
}
func CountToLeftWord(currentPos int, line []rune) int {
if currentPos == 0 {
return 0
}
pos := currentPos - 1
for pos > 0 {
if line[pos] != ' ' {
func (t *TerminalParser) ParseLines(p []byte) (lines []string, err error) {
lines = make([]string, 0, 3)
lineIsPasted := t.pasteActive
reader := bytes.NewBuffer(p)
for {
rest := t.remainder
line := ""
lineOk := false
for !lineOk {
var key rune
key, rest = bytesToKey(rest, t.pasteActive)
if key == utf8.RuneError {
break
}
pos--
if !t.pasteActive {
if key == keyCtrlD {
if len(t.line) == 0 {
// as key has already handled, we need update remainder data,
t.remainder = rest
return "", io.EOF
}
for pos > 0 {
if line[pos] == ' ' {
pos++
break
}
pos--
if key == keyPasteStart {
t.pasteActive = true
if len(t.line) == 0 {
lineIsPasted = true
}
return currentPos - pos
}
func CountToRightWord(currentPos int, line []rune) int {
pos := currentPos
for pos < len(line) {
if line[pos] == ' ' {
break
continue
}
pos++
} else if key == keyPasteEnd {
t.pasteActive = false
continue
}
for pos < len(line) {
if line[pos] != ' ' {
break
if !t.pasteActive {
lineIsPasted = false
}
pos++
line, lineOk = t.handleKey(key)
}
if len(rest) > 0 {
n := copy(t.inBuf[:], rest)
t.remainder = t.inBuf[:n]
} else {
t.remainder = nil
}
if lineOk {
if lineIsPasted {
err = ErrPasteIndicator
}
return
}
return pos - currentPos
}
func AddKeyToLine(key rune, pos int, line []rune) ([]rune, int) {
if len(line) == cap(line) {
newLine := make([]rune, len(line), 2*(1+len(line)))
copy(newLine, line)
line = newLine
// t.remainder is a slice at the beginning of t.inBuf
// containing a partial key sequence
readBuf := t.inBuf[len(t.remainder):]
var n int
n, err = reader.Read(readBuf)
if err != nil {
return
}
t.remainder = t.inBuf[:n+len(t.remainder)]
}
line = line[:len(line)+1]
copy(line[pos+1:], line[pos:])
line[pos] = key
pos++
return line, pos
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment