Skip to content
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
221 lines (194 sloc) 5.33 KB
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 {
} else {
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() {
// Start is convenience for c.Place(0) so you don't have to know how I
// indexed.
func (c *Cursor) Start() {
// 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
// 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)
// Place moves the cursor to the absolute array index specified by position
func (c *Cursor) Place(position int) {
c.Position = position
// 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
// 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
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
// 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.
switch key {
case 0: // empty
case KeyEnter:
return []rune(c.Get()), c.Position, false
case KeyBackspace:
if c.erase {
c.erase = false
case KeyForward:
// the user wants to edit the default, despite how we set it up. Let
// them.
c.erase = false
case KeyBackward:
if c.erase {
c.erase = false
return []rune(c.Get()), c.Position, true
You can’t perform that action at this time.