200 lines
5.7 KiB
Go
200 lines
5.7 KiB
Go
package prompt
|
|
|
|
import (
|
|
"strings"
|
|
)
|
|
|
|
// Buffer emulates the console buffer.
|
|
type Buffer struct {
|
|
workingLines []string // The working lines. Similar to history
|
|
workingIndex int
|
|
CursorPosition int
|
|
cacheDocument *Document
|
|
preferredColumn int // Remember the original column for the next up/down movement.
|
|
}
|
|
|
|
// Text returns string of the current line.
|
|
func (b *Buffer) Text() string {
|
|
return b.workingLines[b.workingIndex]
|
|
}
|
|
|
|
// Document method to return document instance from the current text and cursor position.
|
|
func (b *Buffer) Document() (d *Document) {
|
|
if b.cacheDocument == nil ||
|
|
b.cacheDocument.Text != b.Text() ||
|
|
b.cacheDocument.CursorPosition != b.CursorPosition {
|
|
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) {
|
|
or := []rune(b.Text())
|
|
oc := b.CursorPosition
|
|
|
|
if overwrite {
|
|
overwritten := string(or[oc : oc+len(v)])
|
|
if strings.Contains(overwritten, "\n") {
|
|
i := strings.IndexAny(overwritten, "\n")
|
|
overwritten = overwritten[:i]
|
|
}
|
|
b.setText(string(or[:oc]) + v + string(or[oc+len(overwritten):]))
|
|
} else {
|
|
b.setText(string(or[:oc]) + v + string(or[oc:]))
|
|
}
|
|
|
|
if moveCursor {
|
|
b.CursorPosition += len([]rune(v))
|
|
}
|
|
}
|
|
|
|
// 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) {
|
|
if b.CursorPosition > len([]rune(v)) {
|
|
panic("The length of input value should be shorter than the position of cursor.")
|
|
}
|
|
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.
|
|
func (b *Buffer) CursorLeft(count int) {
|
|
l := b.Document().GetCursorLeftPosition(count)
|
|
b.CursorPosition += l
|
|
return
|
|
}
|
|
|
|
// CursorRight move to right on the current line.
|
|
func (b *Buffer) CursorRight(count int) {
|
|
l := b.Document().GetCursorRightPosition(count)
|
|
b.CursorPosition += l
|
|
return
|
|
}
|
|
|
|
// 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 {
|
|
panic("The count argument on DeleteBeforeCursor should grater than 0.")
|
|
}
|
|
r := []rune(b.Text())
|
|
|
|
if b.CursorPosition > 0 {
|
|
start := b.CursorPosition - count
|
|
if start < 0 {
|
|
start = 0
|
|
}
|
|
deleted = string(r[start:b.CursorPosition])
|
|
b.setDocument(&Document{
|
|
Text: string(r[:start]) + string(r[b.CursorPosition:]),
|
|
CursorPosition: b.CursorPosition - len([]rune(deleted)),
|
|
})
|
|
}
|
|
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) {
|
|
r := []rune(b.Text())
|
|
if b.CursorPosition < len(r) {
|
|
deleted = b.Document().TextAfterCursor()[:count]
|
|
b.setText(string(r[:b.CursorPosition]) + string(r[b.CursorPosition+len(deleted):]))
|
|
}
|
|
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
|
|
}
|