go-prompt/buffer.go

201 lines
5.7 KiB
Go
Raw Normal View History

2017-07-03 15:54:43 +00:00
package prompt
import (
2017-08-06 06:21:14 +00:00
"log"
2017-07-03 15:54:43 +00:00
"strings"
)
// Buffer emulates the console buffer.
type Buffer struct {
workingLines []string // The working lines. Similar to history
workingIndex int
CursorPosition int
2017-07-16 17:21:14 +00:00
cacheDocument *Document
2017-07-17 17:01:24 +00:00
preferredColumn int // Remember the original column for the next up/down movement.
2017-07-03 15:54:43 +00:00
}
// Text returns string of the current line.
func (b *Buffer) Text() string {
return b.workingLines[b.workingIndex]
}
2017-07-16 07:21:19 +00:00
// Document method to return document instance from the current text and cursor position.
2017-07-03 15:54:43 +00:00
func (b *Buffer) Document() (d *Document) {
if b.cacheDocument == nil ||
b.cacheDocument.Text != b.Text() ||
2017-07-16 07:21:19 +00:00
b.cacheDocument.CursorPosition != b.CursorPosition {
2017-07-03 15:54:43 +00:00
b.cacheDocument = &Document{
Text: b.Text(),
CursorPosition: b.CursorPosition,
}
}
return b.cacheDocument
}
// InsertText insert string from current line.
func (b *Buffer) InsertText(v string, overwrite bool, moveCursor bool) {
2017-07-15 07:44:10 +00:00
or := []rune(b.Text())
2017-07-03 15:54:43 +00:00
oc := b.CursorPosition
if overwrite {
2017-07-15 07:44:10 +00:00
overwritten := string(or[oc : oc+len(v)])
2017-07-03 15:54:43 +00:00
if strings.Contains(overwritten, "\n") {
i := strings.IndexAny(overwritten, "\n")
overwritten = overwritten[:i]
}
2017-07-15 07:44:10 +00:00
b.setText(string(or[:oc]) + v + string(or[oc+len(overwritten):]))
2017-07-03 15:54:43 +00:00
} else {
2017-07-15 07:44:10 +00:00
b.setText(string(or[:oc]) + v + string(or[oc:]))
2017-07-03 15:54:43 +00:00
}
if moveCursor {
2017-07-15 07:44:10 +00:00
b.CursorPosition += len([]rune(v))
2017-07-03 15:54:43 +00:00
}
}
// SetText method to set text and update CursorPosition.
// (When doing this, make sure that the cursor_position is valid for this text.
// text/cursor_position should be consistent at any time, otherwise set a Document instead.)
func (b *Buffer) setText(v string) {
2017-07-15 07:44:10 +00:00
if b.CursorPosition > len([]rune(v)) {
2017-08-06 06:21:14 +00:00
log.Print("[ERROR] The length of input value should be shorter than the position of cursor.")
2017-07-03 15:54:43 +00:00
}
o := b.workingLines[b.workingIndex]
b.workingLines[b.workingIndex] = v
if o != v {
// Text is changed.
// TODO: Call callback function triggered by text changed. And also history search text should reset.
// https://github.com/jonathanslenders/python-prompt-toolkit/blob/master/prompt_toolkit/buffer.py#L380-L384
}
}
// Set cursor position. Return whether it changed.
func (b *Buffer) setCursorPosition(p int) {
o := b.CursorPosition
if p > 0 {
b.CursorPosition = p
} else {
b.CursorPosition = 0
}
if p != o {
// Cursor position is changed.
// TODO: Call a onCursorPositionChanged function.
}
}
func (b *Buffer) setDocument(d *Document) {
b.cacheDocument = d
b.setCursorPosition(d.CursorPosition) // Call before setText because setText check the relation between cursorPosition and line length.
b.setText(d.Text)
}
// CursorLeft move to left on the current line.
2017-07-15 11:22:56 +00:00
func (b *Buffer) CursorLeft(count int) {
2017-07-09 13:55:59 +00:00
l := b.Document().GetCursorLeftPosition(count)
b.CursorPosition += l
2017-07-15 11:22:56 +00:00
return
2017-07-03 15:54:43 +00:00
}
// CursorRight move to right on the current line.
2017-07-15 11:22:56 +00:00
func (b *Buffer) CursorRight(count int) {
2017-07-09 13:55:59 +00:00
l := b.Document().GetCursorRightPosition(count)
b.CursorPosition += l
2017-07-15 11:22:56 +00:00
return
2017-07-03 15:54:43 +00:00
}
// CursorUp move cursor to the previous line.
// (for multi-line edit).
func (b *Buffer) CursorUp(count int) {
orig := b.preferredColumn
if b.preferredColumn == -1 { // -1 means nil
orig = b.Document().CursorPositionCol()
}
b.CursorPosition += b.Document().GetCursorUpPosition(count, orig)
// Remember the original column for the next up/down movement.
b.preferredColumn = orig
}
// CursorDown move cursor to the next line.
// (for multi-line edit).
func (b *Buffer) CursorDown(count int) {
orig := b.preferredColumn
if b.preferredColumn == -1 { // -1 means nil
orig = b.Document().CursorPositionCol()
}
b.CursorPosition += b.Document().GetCursorDownPosition(count, orig)
// Remember the original column for the next up/down movement.
b.preferredColumn = orig
}
// DeleteBeforeCursor delete specified number of characters before cursor and return the deleted text.
func (b *Buffer) DeleteBeforeCursor(count int) (deleted string) {
if count <= 0 {
2017-08-06 06:21:14 +00:00
log.Print("[ERROR] The count argument on DeleteBeforeCursor should grater than 0.")
2017-07-03 15:54:43 +00:00
}
2017-07-15 07:44:10 +00:00
r := []rune(b.Text())
2017-07-03 15:54:43 +00:00
if b.CursorPosition > 0 {
start := b.CursorPosition - count
if start < 0 {
start = 0
}
2017-07-15 07:44:10 +00:00
deleted = string(r[start:b.CursorPosition])
2017-07-03 15:54:43 +00:00
b.setDocument(&Document{
2017-07-15 07:44:10 +00:00
Text: string(r[:start]) + string(r[b.CursorPosition:]),
CursorPosition: b.CursorPosition - len([]rune(deleted)),
2017-07-03 15:54:43 +00:00
})
}
return
}
// NewLine means CR.
func (b *Buffer) NewLine(copyMargin bool) {
if copyMargin {
b.InsertText("\n"+b.Document().leadingWhitespaceInCurrentLine(), false, true)
} else {
b.InsertText("\n", false, true)
}
}
// Delete specified number of characters and Return the deleted text.
func (b *Buffer) Delete(count int) (deleted string) {
2017-07-15 07:44:10 +00:00
r := []rune(b.Text())
if b.CursorPosition < len(r) {
2017-07-03 15:54:43 +00:00
deleted = b.Document().TextAfterCursor()[:count]
2017-07-15 07:44:10 +00:00
b.setText(string(r[:b.CursorPosition]) + string(r[b.CursorPosition+len(deleted):]))
2017-07-03 15:54:43 +00:00
}
return
}
// JoinNextLine joins the next line to the current one by deleting the line ending after the current line.
func (b *Buffer) JoinNextLine(separator string) {
if !b.Document().OnLastLine() {
b.CursorPosition += b.Document().GetEndOfLinePosition()
b.Delete(1)
// Remove spaces
b.setText(b.Document().TextBeforeCursor() + separator + strings.TrimLeft(b.Document().TextAfterCursor(), " "))
}
}
// SwapCharactersBeforeCursor swaps the last two characters before the cursor.
func (b *Buffer) SwapCharactersBeforeCursor() {
if b.CursorPosition >= 2 {
x := b.Text()[b.CursorPosition-2 : b.CursorPosition-1]
y := b.Text()[b.CursorPosition-1 : b.CursorPosition]
b.setText(b.Text()[:b.CursorPosition-2] + y + x + b.Text()[b.CursorPosition:])
}
}
// NewBuffer is constructor of Buffer struct.
func NewBuffer() (b *Buffer) {
b = &Buffer{
workingLines: []string{""},
workingIndex: 0,
preferredColumn: -1, // -1 means nil
}
return
}