mirror of
https://github.com/rancher/os.git
synced 2025-09-04 16:21:07 +00:00
381 lines
8.3 KiB
Go
381 lines
8.3 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/.
|
|
|
|
package readline
|
|
|
|
import (
|
|
"fmt"
|
|
"unicode/utf8"
|
|
|
|
"github.com/kless/term"
|
|
)
|
|
|
|
// Buffer size
|
|
var (
|
|
BufferCap = 4096
|
|
BufferLen = 64 // Initial length
|
|
)
|
|
|
|
// == Init
|
|
|
|
/*var lines, columns int
|
|
|
|
func init() {
|
|
lines, columns = term.SizeInChar()
|
|
}*/
|
|
|
|
// == Type
|
|
|
|
// A buffer represents the line buffer.
|
|
type buffer struct {
|
|
columns int // Number of columns for actual window
|
|
promptLen int
|
|
pos int // Pointer position into buffer
|
|
size int // Amount of characters added
|
|
data []rune // Text buffer
|
|
}
|
|
|
|
func newBuffer(promptLen, columns int) *buffer {
|
|
b := new(buffer)
|
|
|
|
b.columns = columns
|
|
b.promptLen = promptLen
|
|
b.data = make([]rune, BufferLen, BufferCap)
|
|
|
|
return b
|
|
}
|
|
|
|
// == Output
|
|
|
|
// insertRune inserts a character in the cursor position.
|
|
func (b *buffer) insertRune(r rune) error {
|
|
var useRefresh bool
|
|
|
|
b.grow(b.size + 1) // Check if there is free space for one more character
|
|
|
|
// Avoid a full update of the line.
|
|
if b.pos == b.size {
|
|
char := make([]byte, utf8.UTFMax)
|
|
utf8.EncodeRune(char, r)
|
|
|
|
if _, err := term.Output.Write(char); err != nil {
|
|
return outputError(err.Error())
|
|
}
|
|
} else {
|
|
useRefresh = true
|
|
copy(b.data[b.pos+1:b.size+1], b.data[b.pos:b.size])
|
|
}
|
|
|
|
b.data[b.pos] = r
|
|
b.pos++
|
|
b.size++
|
|
|
|
if useRefresh {
|
|
return b.refresh()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// insertRunes inserts several characters.
|
|
func (b *buffer) insertRunes(runes []rune) error {
|
|
for _, r := range runes {
|
|
if err := b.insertRune(r); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// toBytes returns a slice of the contents of the buffer.
|
|
func (b *buffer) toBytes() []byte {
|
|
chars := make([]byte, b.size*utf8.UTFMax)
|
|
var end, runeLen int
|
|
|
|
// == Each character (as integer) is encoded to []byte
|
|
for i := 0; i < b.size; i++ {
|
|
if i != 0 {
|
|
runeLen = utf8.EncodeRune(chars[end:], b.data[i])
|
|
end += runeLen
|
|
} else {
|
|
runeLen = utf8.EncodeRune(chars, b.data[i])
|
|
end = runeLen
|
|
}
|
|
}
|
|
return chars[:end]
|
|
}
|
|
|
|
// toString returns the contents of the buffer as a string.
|
|
func (b *buffer) toString() string { return string(b.data[b.promptLen:b.size]) }
|
|
|
|
// refresh refreshes the line.
|
|
func (b *buffer) refresh() (err error) {
|
|
lastLine, _ := b.pos2xy(b.size)
|
|
posLine, posColumn := b.pos2xy(b.pos)
|
|
|
|
// To the first line.
|
|
for ln := posLine; ln > 0; ln-- {
|
|
if _, err = term.Output.Write(ToPreviousLine); err != nil {
|
|
return outputError(err.Error())
|
|
}
|
|
}
|
|
|
|
// == Write the line
|
|
if _, err = term.Output.Write(CR); err != nil {
|
|
return outputError(err.Error())
|
|
}
|
|
if _, err = term.Output.Write(b.toBytes()); err != nil {
|
|
return outputError(err.Error())
|
|
}
|
|
if _, err = term.Output.Write(DelToRight); err != nil {
|
|
return outputError(err.Error())
|
|
}
|
|
|
|
// == Move cursor to original position.
|
|
for ln := lastLine; ln > posLine; ln-- {
|
|
if _, err = term.Output.Write(ToPreviousLine); err != nil {
|
|
return outputError(err.Error())
|
|
}
|
|
}
|
|
if _, err = fmt.Fprintf(term.Output, "\r\033[%dC", posColumn); err != nil {
|
|
return outputError(err.Error())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// == Movement
|
|
|
|
// start moves the cursor at the start.
|
|
func (b *buffer) start() (err error) {
|
|
if b.pos == b.promptLen {
|
|
return
|
|
}
|
|
|
|
for ln, _ := b.pos2xy(b.pos); ln > 0; ln-- {
|
|
if _, err = term.Output.Write(CursorUp); err != nil {
|
|
return outputError(err.Error())
|
|
}
|
|
}
|
|
|
|
if _, err = fmt.Fprintf(term.Output, "\r\033[%dC", b.promptLen); err != nil {
|
|
return outputError(err.Error())
|
|
}
|
|
b.pos = b.promptLen
|
|
return
|
|
}
|
|
|
|
// end moves the cursor at the end.
|
|
// Returns the number of lines that fill in the data.
|
|
func (b *buffer) end() (lines int, err error) {
|
|
if b.pos == b.size {
|
|
return
|
|
}
|
|
|
|
lastLine, lastColumn := b.pos2xy(b.size)
|
|
|
|
for ln, _ := b.pos2xy(b.pos); ln < lastLine; ln++ {
|
|
if _, err = term.Output.Write(CursorDown); err != nil {
|
|
return 0, outputError(err.Error())
|
|
}
|
|
}
|
|
|
|
if _, err = fmt.Fprintf(term.Output, "\r\033[%dC", lastColumn); err != nil {
|
|
return 0, outputError(err.Error())
|
|
}
|
|
b.pos = b.size
|
|
return lastLine, nil
|
|
}
|
|
|
|
// backward moves the cursor one character backward.
|
|
// Returns a boolean to know if the cursor is at the beginning of the line.
|
|
func (b *buffer) backward() (start bool, err error) {
|
|
if b.pos == b.promptLen {
|
|
return true, nil
|
|
}
|
|
b.pos--
|
|
|
|
// If position is on the same line.
|
|
if _, col := b.pos2xy(b.pos); col != 0 {
|
|
if _, err = term.Output.Write(CursorBackward); err != nil {
|
|
return false, outputError(err.Error())
|
|
}
|
|
} else {
|
|
if _, err = term.Output.Write(CursorUp); err != nil {
|
|
return false, outputError(err.Error())
|
|
}
|
|
if _, err = fmt.Fprintf(term.Output, "\033[%dC", b.columns); err != nil {
|
|
return false, outputError(err.Error())
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// forward moves the cursor one character forward.
|
|
// Returns a boolean to know if the cursor is at the end of the line.
|
|
func (b *buffer) forward() (end bool, err error) {
|
|
if b.pos == b.size {
|
|
return true, nil
|
|
}
|
|
b.pos++
|
|
|
|
if _, col := b.pos2xy(b.pos); col != 0 {
|
|
if _, err = term.Output.Write(CursorForward); err != nil {
|
|
return false, outputError(err.Error())
|
|
}
|
|
} else {
|
|
if _, err = term.Output.Write(ToNextLine); err != nil {
|
|
return false, outputError(err.Error())
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// swap swaps the actual character by the previous one. If it is the end of the
|
|
// line then it is swapped the 2nd previous by the previous one.
|
|
func (b *buffer) swap() error {
|
|
if b.pos == b.promptLen {
|
|
return nil
|
|
}
|
|
|
|
if b.pos < b.size {
|
|
aux := b.data[b.pos-1]
|
|
b.data[b.pos-1] = b.data[b.pos]
|
|
b.data[b.pos] = aux
|
|
b.pos++
|
|
// End of line
|
|
} else {
|
|
aux := b.data[b.pos-2]
|
|
b.data[b.pos-2] = b.data[b.pos-1]
|
|
b.data[b.pos-1] = aux
|
|
}
|
|
return b.refresh()
|
|
}
|
|
|
|
// wordBackward moves the cursor one word backward.
|
|
func (b *buffer) wordBackward() (err error) {
|
|
for start := false; ; {
|
|
start, err = b.backward()
|
|
if start == true || err != nil || b.data[b.pos-1] == 32 {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// wordForward moves the cursor one word forward.
|
|
func (b *buffer) wordForward() (err error) {
|
|
for end := false; ; {
|
|
end, err = b.forward()
|
|
if end == true || err != nil || b.data[b.pos] == 32 {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// == Delete
|
|
|
|
// deleteChar deletes the character in cursor.
|
|
func (b *buffer) deleteChar() (err error) {
|
|
if b.pos == b.size {
|
|
return
|
|
}
|
|
|
|
copy(b.data[b.pos:], b.data[b.pos+1:b.size])
|
|
b.size--
|
|
|
|
if lastLine, _ := b.pos2xy(b.size); lastLine == 0 {
|
|
if _, err = term.Output.Write(DelChar); err != nil {
|
|
return outputError(err.Error())
|
|
}
|
|
return nil
|
|
}
|
|
return b.refresh()
|
|
}
|
|
|
|
// deleteCharPrev deletes the previous character from cursor.
|
|
func (b *buffer) deleteCharPrev() (err error) {
|
|
if b.pos == b.promptLen {
|
|
return
|
|
}
|
|
|
|
copy(b.data[b.pos-1:], b.data[b.pos:b.size])
|
|
b.pos--
|
|
b.size--
|
|
|
|
if lastLine, _ := b.pos2xy(b.size); lastLine == 0 {
|
|
if _, err = term.Output.Write(DelBackspace); err != nil {
|
|
return outputError(err.Error())
|
|
}
|
|
return nil
|
|
}
|
|
return b.refresh()
|
|
}
|
|
|
|
// deleteToRight deletes from current position until to end of line.
|
|
func (b *buffer) deleteToRight() (err error) {
|
|
if b.pos == b.size {
|
|
return
|
|
}
|
|
|
|
lastLine, _ := b.pos2xy(b.size)
|
|
posLine, _ := b.pos2xy(b.pos)
|
|
|
|
// To the last line.
|
|
for ln := posLine; ln < lastLine; ln++ {
|
|
if _, err = term.Output.Write(CursorDown); err != nil {
|
|
return outputError(err.Error())
|
|
}
|
|
}
|
|
// Delete all lines until the cursor position.
|
|
for ln := lastLine; ln > posLine; ln-- {
|
|
if _, err = term.Output.Write(DelLine_cursorUp); err != nil {
|
|
return outputError(err.Error())
|
|
}
|
|
}
|
|
|
|
if _, err = term.Output.Write(DelToRight); err != nil {
|
|
return outputError(err.Error())
|
|
}
|
|
b.size = b.pos
|
|
return nil
|
|
}
|
|
|
|
// deleteLine deletes full line.
|
|
func (b *buffer) deleteLine() error {
|
|
lines, err := b.end()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for lines > 0 {
|
|
if _, err = term.Output.Write(DelLine_cursorUp); err != nil {
|
|
return outputError(err.Error())
|
|
}
|
|
lines--
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// == Utility
|
|
|
|
// grow grows buffer to guarantee space for n more byte.
|
|
func (b *buffer) grow(n int) {
|
|
for n > len(b.data) {
|
|
b.data = b.data[:len(b.data)+BufferLen]
|
|
}
|
|
}
|
|
|
|
// pos2xy returns the coordinates of a position for a line of size given in
|
|
// columns.
|
|
func (b *buffer) pos2xy(pos int) (line, column int) {
|
|
if pos < b.columns {
|
|
return 0, pos
|
|
}
|
|
|
|
line = pos / b.columns
|
|
column = pos - (line * b.columns) //- 1
|
|
return
|
|
}
|