233 lines
5.6 KiB
Go
233 lines
5.6 KiB
Go
package promptui
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// Pointer is A specific type that translates a given set of runes into a given
|
|
// set of runes pointed at by the cursor.
|
|
type Pointer func(to []rune) []rune
|
|
|
|
func defaultCursor(ignored []rune) []rune {
|
|
return []rune("\u2588")
|
|
}
|
|
|
|
func blockCursor(input []rune) []rune {
|
|
return []rune(fmt.Sprintf("\\e[7m%s\\e[0m", string(input)))
|
|
}
|
|
|
|
func pipeCursor(input []rune) []rune {
|
|
marker := []rune("|")
|
|
out := []rune{}
|
|
out = append(out, marker...)
|
|
out = append(out, input...)
|
|
return out
|
|
}
|
|
|
|
var (
|
|
// DefaultCursor is a big square block character. Obscures whatever was
|
|
// input.
|
|
DefaultCursor Pointer = defaultCursor
|
|
// BlockCursor is a cursor which highlights a character by inverting colors
|
|
// on it.
|
|
BlockCursor Pointer = blockCursor
|
|
// PipeCursor is a pipe character "|" which appears before the input
|
|
// character.
|
|
PipeCursor Pointer = pipeCursor
|
|
)
|
|
|
|
// Cursor tracks the state associated with the movable cursor
|
|
// The strategy is to keep the prompt, input pristine except for requested
|
|
// modifications. The insertion of the cursor happens during a `format` call
|
|
// and we read in new input via an `Update` call
|
|
type Cursor struct {
|
|
// shows where the user inserts/updates text
|
|
Cursor Pointer
|
|
// what the user entered, and what we will echo back to them, after
|
|
// insertion of the cursor and prefixing with the prompt
|
|
input []rune
|
|
// Put the cursor before this slice
|
|
Position int
|
|
erase bool
|
|
}
|
|
|
|
// NewCursor create a new cursor, with the DefaultCursor, the specified input,
|
|
// and position at the end of the specified starting input.
|
|
func NewCursor(startinginput string, pointer Pointer, eraseDefault bool) Cursor {
|
|
if pointer == nil {
|
|
pointer = defaultCursor
|
|
}
|
|
cur := Cursor{Cursor: pointer, Position: len(startinginput), input: []rune(startinginput), erase: eraseDefault}
|
|
if eraseDefault {
|
|
cur.Start()
|
|
} else {
|
|
cur.End()
|
|
}
|
|
return cur
|
|
}
|
|
|
|
func (c *Cursor) String() string {
|
|
return fmt.Sprintf(
|
|
"Cursor: %s, input %s, Position %d",
|
|
string(c.Cursor([]rune(""))), string(c.input), c.Position)
|
|
}
|
|
|
|
// End is a convenience for c.Place(len(c.input)) so you don't have to know how I
|
|
// indexed.
|
|
func (c *Cursor) End() {
|
|
c.Place(len(c.input))
|
|
}
|
|
|
|
// Start is convenience for c.Place(0) so you don't have to know how I
|
|
// indexed.
|
|
func (c *Cursor) Start() {
|
|
c.Place(0)
|
|
}
|
|
|
|
// ensures we are in bounds.
|
|
func (c *Cursor) correctPosition() {
|
|
if c.Position > len(c.input) {
|
|
c.Position = len(c.input)
|
|
}
|
|
|
|
if c.Position < 0 {
|
|
c.Position = 0
|
|
}
|
|
}
|
|
|
|
// insert the cursor rune array into r before the provided index
|
|
func format(a []rune, c *Cursor) string {
|
|
i := c.Position
|
|
var b []rune
|
|
|
|
out := make([]rune, 0)
|
|
if i < len(a) {
|
|
b = c.Cursor(a[i : i+1])
|
|
out = append(out, a[:i]...) // does not include i
|
|
out = append(out, b...) // add the cursor
|
|
out = append(out, a[i+1:]...) // add the rest after i
|
|
} else {
|
|
b = c.Cursor([]rune{})
|
|
out = append(out, a...)
|
|
out = append(out, b...)
|
|
}
|
|
return string(out)
|
|
}
|
|
|
|
// Format renders the input with the Cursor appropriately positioned.
|
|
func (c *Cursor) Format() string {
|
|
r := c.input
|
|
// insert the cursor
|
|
return format(r, c)
|
|
}
|
|
|
|
// FormatMask replaces all input runes with the mask rune.
|
|
func (c *Cursor) FormatMask(mask rune) string {
|
|
if mask == ' ' {
|
|
return format([]rune{}, c)
|
|
}
|
|
|
|
r := make([]rune, len(c.input))
|
|
for i := range r {
|
|
r[i] = mask
|
|
}
|
|
return format(r, c)
|
|
}
|
|
|
|
// Update inserts newinput into the input []rune in the appropriate place.
|
|
// The cursor is moved to the end of the inputed sequence.
|
|
func (c *Cursor) Update(newinput string) {
|
|
a := c.input
|
|
b := []rune(newinput)
|
|
i := c.Position
|
|
a = append(a[:i], append(b, a[i:]...)...)
|
|
c.input = a
|
|
c.Move(len(b))
|
|
}
|
|
|
|
// Get returns a copy of the input
|
|
func (c *Cursor) Get() string {
|
|
return string(c.input)
|
|
}
|
|
|
|
// GetMask returns a mask string with length equal to the input
|
|
func (c *Cursor) GetMask(mask rune) string {
|
|
return strings.Repeat(string(mask), len(c.input))
|
|
}
|
|
|
|
// Replace replaces the previous input with whatever is specified, and moves the
|
|
// cursor to the end position
|
|
func (c *Cursor) Replace(input string) {
|
|
c.input = []rune(input)
|
|
c.End()
|
|
}
|
|
|
|
// Place moves the cursor to the absolute array index specified by position
|
|
func (c *Cursor) Place(position int) {
|
|
c.Position = position
|
|
c.correctPosition()
|
|
}
|
|
|
|
// Move moves the cursor over in relative terms, by shift indices.
|
|
func (c *Cursor) Move(shift int) {
|
|
// delete the current cursor
|
|
c.Position = c.Position + shift
|
|
c.correctPosition()
|
|
}
|
|
|
|
// Backspace removes the rune that precedes the cursor
|
|
//
|
|
// It handles being at the beginning or end of the row, and moves the cursor to
|
|
// the appropriate position.
|
|
func (c *Cursor) Backspace() {
|
|
a := c.input
|
|
i := c.Position
|
|
if i == 0 {
|
|
// Shrug
|
|
return
|
|
}
|
|
if i == len(a) {
|
|
c.input = a[:i-1]
|
|
} else {
|
|
c.input = append(a[:i-1], a[i:]...)
|
|
}
|
|
// now it's pointing to the i+1th element
|
|
c.Move(-1)
|
|
}
|
|
|
|
// Listen is a readline Listener that updates internal cursor state appropriately.
|
|
func (c *Cursor) Listen(line []rune, pos int, key rune) ([]rune, int, bool) {
|
|
if line != nil {
|
|
// no matter what, update our internal representation.
|
|
c.Update(string(line))
|
|
}
|
|
|
|
switch key {
|
|
case 0: // empty
|
|
case KeyEnter:
|
|
return []rune(c.Get()), c.Position, false
|
|
case KeyBackspace, KeyCtrlH:
|
|
if c.erase {
|
|
c.erase = false
|
|
c.Replace("")
|
|
}
|
|
c.Backspace()
|
|
case KeyForward:
|
|
// the user wants to edit the default, despite how we set it up. Let
|
|
// them.
|
|
c.erase = false
|
|
c.Move(1)
|
|
case KeyBackward:
|
|
c.Move(-1)
|
|
default:
|
|
if c.erase {
|
|
c.erase = false
|
|
c.Replace("")
|
|
c.Update(string(key))
|
|
}
|
|
}
|
|
|
|
return []rune(c.Get()), c.Position, true
|
|
}
|