mirror of
https://github.com/rancher/os.git
synced 2025-09-04 16:21:07 +00:00
346 lines
7.4 KiB
Go
346 lines
7.4 KiB
Go
// Copyright 2010 Jonas mg
|
|
//
|
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
// +build !plan9,!windows
|
|
|
|
package readline
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/kless/term"
|
|
"github.com/kless/term/sys"
|
|
)
|
|
|
|
func init() {
|
|
if !term.SupportANSI() {
|
|
panic("Your terminal does not support ANSI")
|
|
}
|
|
}
|
|
|
|
// NewLine returns a line using both prompts ps1 and ps2, and setting the given
|
|
// terminal to raw mode, if were necessary.
|
|
// lenAnsi is the length of ANSI codes that the prompt ps1 could have.
|
|
// If the history is nil then it is not used.
|
|
func NewLine(ter *term.Terminal, ps1, ps2 string, lenAnsi int, hist *history) (*Line, error) {
|
|
if ter.Mode()&term.RawMode == 0 { // the raw mode is not set
|
|
if err := ter.RawMode(); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
lenPS1 := len(ps1) - lenAnsi
|
|
_, col, err := ter.GetSize()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
buf := newBuffer(lenPS1, col)
|
|
buf.insertRunes([]rune(ps1))
|
|
|
|
return &Line{
|
|
ter: ter,
|
|
buf: buf,
|
|
hist: hist,
|
|
|
|
ps1: ps1,
|
|
ps2: ps2,
|
|
lenPS1: lenPS1,
|
|
|
|
useHistory: hasHistory(hist),
|
|
}, nil
|
|
}
|
|
|
|
// Prompt prints the primary prompt.
|
|
func (ln *Line) Prompt() (err error) {
|
|
if _, err = term.Output.Write(DelLine_CR); err != nil {
|
|
return outputError(err.Error())
|
|
}
|
|
if _, err = fmt.Fprint(term.Output, ln.ps1); err != nil {
|
|
return outputError(err.Error())
|
|
}
|
|
|
|
ln.buf.pos, ln.buf.size = ln.lenPS1, ln.lenPS1
|
|
return
|
|
}
|
|
|
|
// Read reads charactes from input to write them to output, enabling line editing.
|
|
// The errors that could return are to indicate if Ctrl+D was pressed, and for
|
|
// both input/output errors.
|
|
func (ln *Line) Read() (line string, err error) {
|
|
var anotherLine []rune // For lines got from history.
|
|
var isHistoryUsed bool // If the history has been accessed.
|
|
var action keyAction
|
|
|
|
in := bufio.NewReader(term.Input) // Read input.
|
|
esc := make([]byte, 2) // For escape sequences.
|
|
extEsc := make([]byte, 3) // Extended escape sequences.
|
|
|
|
// Print the primary prompt.
|
|
if err = ln.Prompt(); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// == Detect change of window size.
|
|
winSize := term.DetectWinSize()
|
|
|
|
go func() {
|
|
for {
|
|
select {
|
|
case <-winSize.Change: // Wait for.
|
|
_, col, err := ln.ter.GetSize()
|
|
if err != nil {
|
|
ln.buf.columns = col
|
|
ln.buf.refresh()
|
|
}
|
|
}
|
|
}
|
|
}()
|
|
defer winSize.Close()
|
|
|
|
for ; ; action = 0 {
|
|
char, _, err := in.ReadRune()
|
|
if err != nil {
|
|
return "", inputError(err.Error())
|
|
}
|
|
|
|
_S:
|
|
switch char {
|
|
default:
|
|
if err = ln.buf.insertRune(char); err != nil {
|
|
return "", err
|
|
}
|
|
continue
|
|
|
|
case sys.K_RETURN:
|
|
line = ln.buf.toString()
|
|
|
|
if ln.useHistory {
|
|
ln.hist.Add(line)
|
|
}
|
|
if _, err = term.Output.Write(CRLF); err != nil {
|
|
return "", outputError(err.Error())
|
|
}
|
|
return strings.TrimSpace(line), nil
|
|
|
|
case sys.K_TAB:
|
|
// TODO: disabled by now
|
|
continue
|
|
|
|
case sys.K_BACK, sys.K_CTRL_H:
|
|
if err = ln.buf.deleteCharPrev(); err != nil {
|
|
return "", err
|
|
}
|
|
continue
|
|
|
|
case sys.K_CTRL_C:
|
|
if err = ln.buf.insertRunes(CtrlC); err != nil {
|
|
return "", err
|
|
}
|
|
if _, err = term.Output.Write(CRLF); err != nil {
|
|
return "", outputError(err.Error())
|
|
}
|
|
|
|
ChanCtrlC <- 1 //TODO: is really necessary?
|
|
|
|
if err = ln.Prompt(); err != nil {
|
|
return "", err
|
|
}
|
|
continue
|
|
case sys.K_CTRL_D:
|
|
if err = ln.buf.insertRunes(CtrlD); err != nil {
|
|
return "", err
|
|
}
|
|
if _, err = term.Output.Write(CRLF); err != nil {
|
|
return "", outputError(err.Error())
|
|
}
|
|
|
|
ln.Restore()
|
|
ChanCtrlD <- 1
|
|
return "", ErrCtrlD
|
|
|
|
// Escape sequence
|
|
case sys.K_ESCAPE: // Ctrl+[ ("\x1b" in hexadecimal, "033" in octal)
|
|
if _, err = in.Read(esc); err != nil {
|
|
return "", inputError(err.Error())
|
|
}
|
|
|
|
if esc[0] == 79 { // 'O'
|
|
switch esc[1] {
|
|
case 72: // Home: "\x1b O H"
|
|
action = _HOME
|
|
break _S
|
|
case 70: // End: "\x1b O F"
|
|
action = _END
|
|
break _S
|
|
}
|
|
}
|
|
|
|
if esc[0] == 91 { // '['
|
|
switch esc[1] {
|
|
case 65: // Up: "\x1b [ A"
|
|
if !ln.useHistory {
|
|
continue
|
|
}
|
|
action = _UP
|
|
break _S
|
|
case 66: // Down: "\x1b [ B"
|
|
if !ln.useHistory {
|
|
continue
|
|
}
|
|
action = _DOWN
|
|
break _S
|
|
case 68: // "\x1b [ D"
|
|
action = _LEFT
|
|
break _S
|
|
case 67: // "\x1b [ C"
|
|
action = _RIGHT
|
|
break _S
|
|
}
|
|
|
|
// Extended escape.
|
|
if esc[1] > 48 && esc[1] < 55 {
|
|
if _, err = in.Read(extEsc); err != nil {
|
|
return "", inputError(err.Error())
|
|
}
|
|
|
|
if extEsc[0] == 126 { // '~'
|
|
switch esc[1] {
|
|
//case 50: // Insert: "\x1b [ 2 ~"
|
|
|
|
case 51: // Delete: "\x1b [ 3 ~"
|
|
if err = ln.buf.deleteChar(); err != nil {
|
|
return "", err
|
|
}
|
|
continue
|
|
//case 53: // RePag: "\x1b [ 5 ~"
|
|
|
|
//case 54: // AvPag: "\x1b [ 6 ~"
|
|
|
|
}
|
|
}
|
|
if esc[1] == 49 && extEsc[0] == 59 && extEsc[1] == 53 { // "1;5"
|
|
switch extEsc[2] {
|
|
case 68: // Ctrl+left arrow: "\x1b [ 1 ; 5 D"
|
|
// move to last word
|
|
if err = ln.buf.wordBackward(); err != nil {
|
|
return "", err
|
|
}
|
|
continue
|
|
case 67: // Ctrl+right arrow: "\x1b [ 1 ; 5 C"
|
|
// move to next word
|
|
if err = ln.buf.wordForward(); err != nil {
|
|
return "", err
|
|
}
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
}
|
|
continue
|
|
|
|
case sys.K_CTRL_T: // Swap actual character by the previous one.
|
|
if err = ln.buf.swap(); err != nil {
|
|
return "", err
|
|
}
|
|
continue
|
|
|
|
case sys.K_CTRL_L: // Clear screen.
|
|
if _, err = term.Output.Write(DelScreenToUpper); err != nil {
|
|
return "", err
|
|
}
|
|
if err = ln.Prompt(); err != nil {
|
|
return "", err
|
|
}
|
|
continue
|
|
case sys.K_CTRL_U: // Delete the whole line.
|
|
if err = ln.buf.deleteLine(); err != nil {
|
|
return "", err
|
|
}
|
|
if err = ln.Prompt(); err != nil {
|
|
return "", err
|
|
}
|
|
continue
|
|
case sys.K_CTRL_K: // Delete from current to end of line.
|
|
if err = ln.buf.deleteToRight(); err != nil {
|
|
return "", err
|
|
}
|
|
continue
|
|
|
|
case sys.K_CTRL_P: // Up
|
|
if !ln.useHistory {
|
|
continue
|
|
}
|
|
action = _UP
|
|
case sys.K_CTRL_N: // Down
|
|
if !ln.useHistory {
|
|
continue
|
|
}
|
|
action = _DOWN
|
|
case sys.K_CTRL_B: // Left
|
|
action = _LEFT
|
|
case sys.K_CTRL_F: // Right
|
|
action = _RIGHT
|
|
|
|
case sys.K_CTRL_A: // Start of line.
|
|
action = _HOME
|
|
case sys.K_CTRL_E: // End of line.
|
|
action = _END
|
|
}
|
|
|
|
switch action {
|
|
case _UP, _DOWN: // Up and down arrow: history
|
|
if action == _UP {
|
|
anotherLine, err = ln.hist.Prev()
|
|
} else {
|
|
anotherLine, err = ln.hist.Next()
|
|
}
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
// Update the current history entry before to overwrite it with
|
|
// the next one.
|
|
// TODO: it has to be removed before of to be saved the history
|
|
if !isHistoryUsed {
|
|
ln.hist.Add(ln.buf.toString())
|
|
}
|
|
isHistoryUsed = true
|
|
|
|
ln.buf.grow(len(anotherLine))
|
|
ln.buf.size = len(anotherLine) + ln.buf.promptLen
|
|
copy(ln.buf.data[ln.lenPS1:], anotherLine)
|
|
|
|
if err = ln.buf.refresh(); err != nil {
|
|
return "", err
|
|
}
|
|
continue
|
|
case _LEFT:
|
|
if _, err = ln.buf.backward(); err != nil {
|
|
return "", err
|
|
}
|
|
continue
|
|
case _RIGHT:
|
|
if _, err = ln.buf.forward(); err != nil {
|
|
return "", err
|
|
}
|
|
continue
|
|
case _HOME:
|
|
if err = ln.buf.start(); err != nil {
|
|
return "", err
|
|
}
|
|
continue
|
|
case _END:
|
|
if _, err = ln.buf.end(); err != nil {
|
|
return "", err
|
|
}
|
|
continue
|
|
}
|
|
}
|
|
}
|