mirror of
https://github.com/manifoldco/promptui.git
synced 2026-03-14 22:35:53 +01:00
This time I actually ran them all. I also opted to have get return the internal slice rather than make multiple copies of the input on each invocation. It just sort of bothered me to leave it like that.
220 lines
5.3 KiB
Go
220 lines
5.3 KiB
Go
package promptui
|
|
|
|
import "fmt"
|
|
|
|
// 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([]rune(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 {
|
|
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)
|
|
}
|
|
|
|
// 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:
|
|
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
|
|
}
|