Support CJK and Cyrillic characters
This commit is contained in:
parent
2b80a3f52c
commit
82330a197a
14
Gopkg.lock
generated
14
Gopkg.lock
generated
@ -13,27 +13,33 @@
|
||||
revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39"
|
||||
version = "v0.0.3"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/mattn/go-runewidth"
|
||||
packages = ["."]
|
||||
revision = "ce7b0b5c7b45a81508558cd1dba6bb1e4ddb51bb"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/mattn/go-tty"
|
||||
packages = ["."]
|
||||
revision = "061c12e2dc3ef933c21c2249823e6f42e6935c40"
|
||||
revision = "931426f7535ac39720c8909d70ece5a41a2502a6"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/pkg/term"
|
||||
packages = ["termios"]
|
||||
revision = "b1f72af2d63057363398bec5873d16a98b453312"
|
||||
revision = "cda20d4ac917ad418d86e151eff439648b06185b"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/sys"
|
||||
packages = ["unix"]
|
||||
revision = "37707fdb30a5b38865cfb95e5aab41707daec7fd"
|
||||
revision = "ad87a3a340fa7f3bed189293fbfa7a9b7e021ae1"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "6c442f55617e93df75aa1ee2fd2b36cfab23003fded4723be56c6b6fd0545c56"
|
||||
inputs-digest = "d6a0ea9e49092cfd8cb3d6077c97a938de7c39195b83828dae2a0befdd207ffd"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
@ -32,3 +32,7 @@
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/pkg/term"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/mattn/go-runewidth"
|
||||
|
31
_example/hello-cjk-cyrillic/main.go
Normal file
31
_example/hello-cjk-cyrillic/main.go
Normal file
@ -0,0 +1,31 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/c-bata/go-prompt"
|
||||
)
|
||||
|
||||
func executor(in string) {
|
||||
fmt.Println("Your input: " + in)
|
||||
}
|
||||
|
||||
func completer(in prompt.Document) []prompt.Suggest {
|
||||
s := []prompt.Suggest{
|
||||
{Text: "こんにちは", Description: "'こんにちは' means 'Hello' in Japanese"},
|
||||
{Text: "감사합니다", Description: "'안녕하세요' means 'Hello' in Korean."},
|
||||
{Text: "您好", Description: "'您好' means 'Hello' in Chinese."},
|
||||
{Text: "Добрый день", Description: "'Добрый день' means 'Hello' in Russian."},
|
||||
}
|
||||
return prompt.FilterHasPrefix(s, in.GetWordBeforeCursor(), true)
|
||||
}
|
||||
|
||||
func main() {
|
||||
p := prompt.New(
|
||||
executor,
|
||||
completer,
|
||||
prompt.OptionPrefix(">>> "),
|
||||
prompt.OptionTitle("sql-prompt for multi width characters"),
|
||||
)
|
||||
p.Run()
|
||||
}
|
60
buffer.go
60
buffer.go
@ -9,7 +9,7 @@ import (
|
||||
type Buffer struct {
|
||||
workingLines []string // The working lines. Similar to history
|
||||
workingIndex int
|
||||
CursorPosition int
|
||||
cursorPosition int
|
||||
cacheDocument *Document
|
||||
preferredColumn int // Remember the original column for the next up/down movement.
|
||||
}
|
||||
@ -23,19 +23,25 @@ func (b *Buffer) Text() string {
|
||||
func (b *Buffer) Document() (d *Document) {
|
||||
if b.cacheDocument == nil ||
|
||||
b.cacheDocument.Text != b.Text() ||
|
||||
b.cacheDocument.CursorPosition != b.CursorPosition {
|
||||
b.cacheDocument.cursorPosition != b.cursorPosition {
|
||||
b.cacheDocument = &Document{
|
||||
Text: b.Text(),
|
||||
CursorPosition: b.CursorPosition,
|
||||
cursorPosition: b.cursorPosition,
|
||||
}
|
||||
}
|
||||
return b.cacheDocument
|
||||
}
|
||||
|
||||
// DisplayCursorPosition returns the cursor position on rendered text on terminal emulators.
|
||||
// So if Document is "日本(cursor)語", DisplayedCursorPosition returns 4 because '日' and '本' are double width characters.
|
||||
func (b *Buffer) DisplayCursorPosition() int {
|
||||
return b.Document().DisplayCursorPosition()
|
||||
}
|
||||
|
||||
// InsertText insert string from current line.
|
||||
func (b *Buffer) InsertText(v string, overwrite bool, moveCursor bool) {
|
||||
or := []rune(b.Text())
|
||||
oc := b.CursorPosition
|
||||
oc := b.cursorPosition
|
||||
|
||||
if overwrite {
|
||||
overwritten := string(or[oc : oc+len(v)])
|
||||
@ -49,15 +55,15 @@ func (b *Buffer) InsertText(v string, overwrite bool, moveCursor bool) {
|
||||
}
|
||||
|
||||
if moveCursor {
|
||||
b.CursorPosition += len([]rune(v))
|
||||
b.cursorPosition += len([]rune(v))
|
||||
}
|
||||
}
|
||||
|
||||
// SetText method to set text and update CursorPosition.
|
||||
// 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)) {
|
||||
if b.cursorPosition > len([]rune(v)) {
|
||||
log.Print("[ERROR] The length of input value should be shorter than the position of cursor.")
|
||||
}
|
||||
o := b.workingLines[b.workingIndex]
|
||||
@ -72,11 +78,11 @@ func (b *Buffer) setText(v string) {
|
||||
|
||||
// Set cursor position. Return whether it changed.
|
||||
func (b *Buffer) setCursorPosition(p int) {
|
||||
o := b.CursorPosition
|
||||
o := b.cursorPosition
|
||||
if p > 0 {
|
||||
b.CursorPosition = p
|
||||
b.cursorPosition = p
|
||||
} else {
|
||||
b.CursorPosition = 0
|
||||
b.cursorPosition = 0
|
||||
}
|
||||
if p != o {
|
||||
// Cursor position is changed.
|
||||
@ -86,21 +92,21 @@ func (b *Buffer) setCursorPosition(p int) {
|
||||
|
||||
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.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
|
||||
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
|
||||
b.cursorPosition += l
|
||||
return
|
||||
}
|
||||
|
||||
@ -111,7 +117,7 @@ func (b *Buffer) CursorUp(count int) {
|
||||
if b.preferredColumn == -1 { // -1 means nil
|
||||
orig = b.Document().CursorPositionCol()
|
||||
}
|
||||
b.CursorPosition += b.Document().GetCursorUpPosition(count, orig)
|
||||
b.cursorPosition += b.Document().GetCursorUpPosition(count, orig)
|
||||
|
||||
// Remember the original column for the next up/down movement.
|
||||
b.preferredColumn = orig
|
||||
@ -124,7 +130,7 @@ func (b *Buffer) CursorDown(count int) {
|
||||
if b.preferredColumn == -1 { // -1 means nil
|
||||
orig = b.Document().CursorPositionCol()
|
||||
}
|
||||
b.CursorPosition += b.Document().GetCursorDownPosition(count, orig)
|
||||
b.cursorPosition += b.Document().GetCursorDownPosition(count, orig)
|
||||
|
||||
// Remember the original column for the next up/down movement.
|
||||
b.preferredColumn = orig
|
||||
@ -137,15 +143,15 @@ func (b *Buffer) DeleteBeforeCursor(count int) (deleted string) {
|
||||
}
|
||||
r := []rune(b.Text())
|
||||
|
||||
if b.CursorPosition > 0 {
|
||||
start := b.CursorPosition - count
|
||||
if b.cursorPosition > 0 {
|
||||
start := b.cursorPosition - count
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
deleted = string(r[start:b.CursorPosition])
|
||||
deleted = string(r[start:b.cursorPosition])
|
||||
b.setDocument(&Document{
|
||||
Text: string(r[:start]) + string(r[b.CursorPosition:]),
|
||||
CursorPosition: b.CursorPosition - len([]rune(deleted)),
|
||||
Text: string(r[:start]) + string(r[b.cursorPosition:]),
|
||||
cursorPosition: b.cursorPosition - len([]rune(deleted)),
|
||||
})
|
||||
}
|
||||
return
|
||||
@ -163,9 +169,9 @@ func (b *Buffer) NewLine(copyMargin bool) {
|
||||
// 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) {
|
||||
if b.cursorPosition < len(r) {
|
||||
deleted = b.Document().TextAfterCursor()[:count]
|
||||
b.setText(string(r[:b.CursorPosition]) + string(r[b.CursorPosition+len(deleted):]))
|
||||
b.setText(string(r[:b.cursorPosition]) + string(r[b.cursorPosition+len(deleted):]))
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -173,7 +179,7 @@ func (b *Buffer) Delete(count int) (deleted string) {
|
||||
// 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.cursorPosition += b.Document().GetEndOfLinePosition()
|
||||
b.Delete(1)
|
||||
// Remove spaces
|
||||
b.setText(b.Document().TextBeforeCursor() + separator + strings.TrimLeft(b.Document().TextAfterCursor(), " "))
|
||||
@ -182,10 +188,10 @@ func (b *Buffer) JoinNextLine(separator string) {
|
||||
|
||||
// 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:])
|
||||
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:])
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,8 +23,8 @@ func TestBuffer_InsertText(t *testing.T) {
|
||||
t.Errorf("Text should be %#v, got %#v", "some_text", b.Text())
|
||||
}
|
||||
|
||||
if b.CursorPosition != len("some_text") {
|
||||
t.Errorf("CursorPosition should be %#v, got %#v", len("some_text"), b.CursorPosition)
|
||||
if b.cursorPosition != len("some_text") {
|
||||
t.Errorf("cursorPosition should be %#v, got %#v", len("some_text"), b.cursorPosition)
|
||||
}
|
||||
}
|
||||
|
||||
@ -39,8 +39,8 @@ func TestBuffer_CursorMovement(t *testing.T) {
|
||||
if b.Text() != "some_teAxt" {
|
||||
t.Errorf("Text should be %#v, got %#v", "some_teAxt", b.Text())
|
||||
}
|
||||
if b.CursorPosition != len("some_teA") {
|
||||
t.Errorf("Text should be %#v, got %#v", len("some_teA"), b.CursorPosition)
|
||||
if b.cursorPosition != len("some_teA") {
|
||||
t.Errorf("Text should be %#v, got %#v", len("some_teA"), b.cursorPosition)
|
||||
}
|
||||
|
||||
// Moving over left character counts.
|
||||
@ -49,8 +49,8 @@ func TestBuffer_CursorMovement(t *testing.T) {
|
||||
if b.Text() != "Asome_teAxt" {
|
||||
t.Errorf("Text should be %#v, got %#v", "some_teAxt", b.Text())
|
||||
}
|
||||
if b.CursorPosition != len("A") {
|
||||
t.Errorf("Text should be %#v, got %#v", len("some_teA"), b.CursorPosition)
|
||||
if b.cursorPosition != len("A") {
|
||||
t.Errorf("Text should be %#v, got %#v", len("some_teA"), b.cursorPosition)
|
||||
}
|
||||
|
||||
// TODO: Going right already at right end.
|
||||
@ -69,43 +69,43 @@ func TestBuffer_CursorUp(t *testing.T) {
|
||||
b := NewBuffer()
|
||||
b.InsertText("long line1\nline2", false, true)
|
||||
b.CursorUp(1)
|
||||
if b.Document().CursorPosition != 5 {
|
||||
t.Errorf("Should be %#v, got %#v", 5, b.Document().CursorPosition)
|
||||
if b.Document().cursorPosition != 5 {
|
||||
t.Errorf("Should be %#v, got %#v", 5, b.Document().cursorPosition)
|
||||
}
|
||||
|
||||
// Going up when already at the top.
|
||||
b.CursorUp(1)
|
||||
if b.Document().CursorPosition != 5 {
|
||||
t.Errorf("Should be %#v, got %#v", 5, b.Document().CursorPosition)
|
||||
if b.Document().cursorPosition != 5 {
|
||||
t.Errorf("Should be %#v, got %#v", 5, b.Document().cursorPosition)
|
||||
}
|
||||
|
||||
// Going up to a line that's shorter.
|
||||
b.setDocument(&Document{})
|
||||
b.InsertText("line1\nlong line2", false, true)
|
||||
b.CursorUp(1)
|
||||
if b.Document().CursorPosition != 5 {
|
||||
t.Errorf("Should be %#v, got %#v", 5, b.Document().CursorPosition)
|
||||
if b.Document().cursorPosition != 5 {
|
||||
t.Errorf("Should be %#v, got %#v", 5, b.Document().cursorPosition)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuffer_CursorDown(t *testing.T) {
|
||||
b := NewBuffer()
|
||||
b.InsertText("line1\nline2", false, true)
|
||||
b.CursorPosition = 3
|
||||
b.cursorPosition = 3
|
||||
|
||||
// Normally going down
|
||||
b.CursorDown(1)
|
||||
if b.Document().CursorPosition != len("line1\nlin") {
|
||||
t.Errorf("Should be %#v, got %#v", len("line1\nlin"), b.Document().CursorPosition)
|
||||
if b.Document().cursorPosition != len("line1\nlin") {
|
||||
t.Errorf("Should be %#v, got %#v", len("line1\nlin"), b.Document().cursorPosition)
|
||||
}
|
||||
|
||||
// Going down to a line that's storter.
|
||||
b = NewBuffer()
|
||||
b.InsertText("long line1\na\nb", false, true)
|
||||
b.CursorPosition = 3
|
||||
b.cursorPosition = 3
|
||||
b.CursorDown(1)
|
||||
if b.Document().CursorPosition != len("long line1\na") {
|
||||
t.Errorf("Should be %#v, got %#v", len("long line1\na"), b.Document().CursorPosition)
|
||||
if b.Document().cursorPosition != len("long line1\na") {
|
||||
t.Errorf("Should be %#v, got %#v", len("long line1\na"), b.Document().cursorPosition)
|
||||
}
|
||||
}
|
||||
|
||||
@ -121,8 +121,8 @@ func TestBuffer_DeleteBeforeCursor(t *testing.T) {
|
||||
if deleted != "e" {
|
||||
t.Errorf("Should be %#v, got %#v", deleted, "e")
|
||||
}
|
||||
if b.CursorPosition != len("some_t") {
|
||||
t.Errorf("Should be %#v, got %#v", len("some_t"), b.CursorPosition)
|
||||
if b.cursorPosition != len("some_t") {
|
||||
t.Errorf("Should be %#v, got %#v", len("some_t"), b.cursorPosition)
|
||||
}
|
||||
|
||||
// Delete over the characters length before cursor.
|
||||
@ -176,7 +176,7 @@ func TestBuffer_JoinNextLine(t *testing.T) {
|
||||
// Test when there is no '\n' in the text
|
||||
b = NewBuffer()
|
||||
b.InsertText("line1", false, true)
|
||||
b.CursorPosition = 0
|
||||
b.cursorPosition = 0
|
||||
b.JoinNextLine(" ")
|
||||
ac = b.Text()
|
||||
ex = "line1"
|
||||
|
@ -3,16 +3,21 @@ package prompt
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/mattn/go-runewidth"
|
||||
)
|
||||
|
||||
const (
|
||||
shortenSuffix = "..."
|
||||
leftPrefix = " "
|
||||
leftSuffix = " "
|
||||
rightPrefix = " "
|
||||
rightSuffix = " "
|
||||
leftMargin = len(leftPrefix + leftSuffix)
|
||||
rightMargin = len(rightPrefix + rightSuffix)
|
||||
shortenSuffix = "..."
|
||||
leftPrefix = " "
|
||||
leftSuffix = " "
|
||||
rightPrefix = " "
|
||||
rightSuffix = " "
|
||||
)
|
||||
|
||||
var (
|
||||
leftMargin = runewidth.StringWidth(leftPrefix + leftSuffix)
|
||||
rightMargin = runewidth.StringWidth(rightPrefix + rightSuffix)
|
||||
completionMargin = leftMargin + rightMargin
|
||||
)
|
||||
|
||||
@ -106,13 +111,14 @@ func formatTexts(o []string, max int, prefix, suffix string) (new []string, widt
|
||||
l := len(o)
|
||||
n := make([]string, l)
|
||||
|
||||
lenPrefix := len([]rune(prefix))
|
||||
lenSuffix := len([]rune(suffix))
|
||||
lenShorten := len(shortenSuffix)
|
||||
lenPrefix := runewidth.StringWidth(prefix)
|
||||
lenSuffix := runewidth.StringWidth(suffix)
|
||||
lenShorten := runewidth.StringWidth(shortenSuffix)
|
||||
min := lenPrefix + lenSuffix + lenShorten
|
||||
for i := 0; i < l; i++ {
|
||||
if width < len([]rune(o[i])) {
|
||||
width = len([]rune(o[i]))
|
||||
w := runewidth.StringWidth(o[i])
|
||||
if width < w {
|
||||
width = w
|
||||
}
|
||||
}
|
||||
|
||||
@ -128,13 +134,12 @@ func formatTexts(o []string, max int, prefix, suffix string) (new []string, widt
|
||||
}
|
||||
|
||||
for i := 0; i < l; i++ {
|
||||
r := []rune(o[i])
|
||||
x := len(r)
|
||||
x := runewidth.StringWidth(o[i])
|
||||
if x <= width {
|
||||
spaces := strings.Repeat(" ", width-x)
|
||||
n[i] = prefix + o[i] + spaces + suffix
|
||||
} else if x > width {
|
||||
n[i] = prefix + string(r[:width-lenShorten]) + shortenSuffix + suffix
|
||||
n[i] = prefix + runewidth.Truncate(o[i], width, "...") + suffix
|
||||
}
|
||||
}
|
||||
return n, lenPrefix + width + lenSuffix
|
||||
|
40
document.go
40
document.go
@ -4,22 +4,38 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/mattn/go-runewidth"
|
||||
)
|
||||
|
||||
// Document has text displayed in terminal and cursor position.
|
||||
type Document struct {
|
||||
Text string
|
||||
CursorPosition int
|
||||
Text string
|
||||
// This represents a index in a rune array of Document.Text.
|
||||
// So if Document is "日本(cursor)語", cursorPosition is 2.
|
||||
// But DisplayedCursorPosition returns 4 because '日' and '本' are double width characters.
|
||||
cursorPosition int
|
||||
}
|
||||
|
||||
// NewDocument return the new empty document.
|
||||
func NewDocument() *Document {
|
||||
return &Document{
|
||||
Text: "",
|
||||
CursorPosition: 0,
|
||||
cursorPosition: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// DisplayCursorPosition returns the cursor position on rendered text on terminal emulators.
|
||||
// So if Document is "日本(cursor)語", DisplayedCursorPosition returns 4 because '日' and '本' are double width characters.
|
||||
func (d *Document) DisplayCursorPosition() int {
|
||||
var position int
|
||||
runes := []rune(d.Text)[:d.cursorPosition]
|
||||
for i := range runes {
|
||||
position += runewidth.RuneWidth(runes[i])
|
||||
}
|
||||
return position
|
||||
}
|
||||
|
||||
// GetCharRelativeToCursor return character relative to cursor position, or empty string
|
||||
func (d *Document) GetCharRelativeToCursor(offset int) (r rune) {
|
||||
s := d.Text
|
||||
@ -28,7 +44,7 @@ func (d *Document) GetCharRelativeToCursor(offset int) (r rune) {
|
||||
for len(s) > 0 {
|
||||
cnt++
|
||||
r, size := utf8.DecodeRuneInString(s)
|
||||
if cnt == d.CursorPosition+offset {
|
||||
if cnt == d.cursorPosition+offset {
|
||||
return r
|
||||
}
|
||||
s = s[size:]
|
||||
@ -39,13 +55,13 @@ func (d *Document) GetCharRelativeToCursor(offset int) (r rune) {
|
||||
// TextBeforeCursor returns the text before the cursor.
|
||||
func (d *Document) TextBeforeCursor() string {
|
||||
r := []rune(d.Text)
|
||||
return string(r[:d.CursorPosition])
|
||||
return string(r[:d.cursorPosition])
|
||||
}
|
||||
|
||||
// TextAfterCursor returns the text after the cursor.
|
||||
func (d *Document) TextAfterCursor() string {
|
||||
r := []rune(d.Text)
|
||||
return string(r[d.CursorPosition:])
|
||||
return string(r[d.cursorPosition:])
|
||||
}
|
||||
|
||||
// GetWordBeforeCursor returns the word before the cursor.
|
||||
@ -95,7 +111,7 @@ func (d *Document) FindEndOfCurrentWord() int {
|
||||
if i := strings.IndexByte(x, ' '); i != -1 {
|
||||
return i
|
||||
} else {
|
||||
return len(x)
|
||||
return len([]rune(x))
|
||||
}
|
||||
}
|
||||
|
||||
@ -189,7 +205,7 @@ func (d *Document) findLineStartIndex(index int) (pos int, lineStartIndex int) {
|
||||
|
||||
// CursorPositionRow returns the current row. (0-based.)
|
||||
func (d *Document) CursorPositionRow() (row int) {
|
||||
row, _ = d.findLineStartIndex(d.CursorPosition)
|
||||
row, _ = d.findLineStartIndex(d.cursorPosition)
|
||||
return
|
||||
}
|
||||
|
||||
@ -197,8 +213,8 @@ func (d *Document) CursorPositionRow() (row int) {
|
||||
func (d *Document) CursorPositionCol() (col int) {
|
||||
// Don't use self.text_before_cursor to calculate this. Creating substrings
|
||||
// and splitting is too expensive for getting the cursor position.
|
||||
_, index := d.findLineStartIndex(d.CursorPosition)
|
||||
col = d.CursorPosition - index
|
||||
_, index := d.findLineStartIndex(d.cursorPosition)
|
||||
col = d.cursorPosition - index
|
||||
return
|
||||
}
|
||||
|
||||
@ -238,7 +254,7 @@ func (d *Document) GetCursorUpPosition(count int, preferredColumn int) int {
|
||||
if row < 0 {
|
||||
row = 0
|
||||
}
|
||||
return d.TranslateRowColToIndex(row, col) - d.CursorPosition
|
||||
return d.TranslateRowColToIndex(row, col) - d.cursorPosition
|
||||
}
|
||||
|
||||
// GetCursorDownPosition return the relative cursor position (character index) where we would be if the
|
||||
@ -251,7 +267,7 @@ func (d *Document) GetCursorDownPosition(count int, preferredColumn int) int {
|
||||
col = preferredColumn
|
||||
}
|
||||
row := d.CursorPositionRow() + count
|
||||
return d.TranslateRowColToIndex(row, col) - d.CursorPosition
|
||||
return d.TranslateRowColToIndex(row, col) - d.cursorPosition
|
||||
}
|
||||
|
||||
// Lines returns the array of all the lines.
|
||||
|
546
document_test.go
546
document_test.go
@ -6,27 +6,111 @@ import (
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
func TestDocument_GetCharRelativeToCursor(t *testing.T) {
|
||||
d := &Document{
|
||||
Text: "line 1\nline 2\nline 3\nline 4\n",
|
||||
CursorPosition: len([]rune("line 1\n" + "lin")),
|
||||
func TestDocument_DisplayCursorPosition(t *testing.T) {
|
||||
patterns := []struct {
|
||||
document *Document
|
||||
expected int
|
||||
}{
|
||||
{
|
||||
document: &Document{
|
||||
Text: "hello",
|
||||
cursorPosition: 2,
|
||||
},
|
||||
expected: 2,
|
||||
},
|
||||
{
|
||||
document: &Document{
|
||||
Text: "こんにちは",
|
||||
cursorPosition: 2,
|
||||
},
|
||||
expected: 4,
|
||||
},
|
||||
{
|
||||
document: &Document{
|
||||
Text: "Добрый день",
|
||||
cursorPosition: 3,
|
||||
},
|
||||
expected: 3,
|
||||
},
|
||||
}
|
||||
ac := d.GetCharRelativeToCursor(1)
|
||||
ex, _ := utf8.DecodeRuneInString("e")
|
||||
if ac != ex {
|
||||
t.Errorf("Should be %#v, got %#v", ex, ac)
|
||||
|
||||
for _, p := range patterns {
|
||||
ac := p.document.DisplayCursorPosition()
|
||||
if ac != p.expected {
|
||||
t.Errorf("Should be %#v, got %#v", p.expected, ac)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDocument_GetCharRelativeToCursor(t *testing.T) {
|
||||
patterns := []struct {
|
||||
document *Document
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
document: &Document{
|
||||
Text: "line 1\nline 2\nline 3\nline 4\n",
|
||||
cursorPosition: len([]rune("line 1\n" + "lin")),
|
||||
},
|
||||
expected: "e",
|
||||
},
|
||||
{
|
||||
document: &Document{
|
||||
Text: "あいうえお\nかきくけこ\nさしすせそ\nたちつてと\n",
|
||||
cursorPosition: 8,
|
||||
},
|
||||
expected: "く",
|
||||
},
|
||||
{
|
||||
document: &Document{
|
||||
Text: "Добрый\nдень\nДобрый день",
|
||||
cursorPosition: 9,
|
||||
},
|
||||
expected: "н",
|
||||
},
|
||||
}
|
||||
|
||||
for i, p := range patterns {
|
||||
ac := p.document.GetCharRelativeToCursor(1)
|
||||
ex, _ := utf8.DecodeRuneInString(p.expected)
|
||||
if ac != ex {
|
||||
t.Errorf("[%d] Should be %s, got %s", i, string(ex), string(ac))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDocument_TextBeforeCursor(t *testing.T) {
|
||||
d := &Document{
|
||||
Text: "line 1\nline 2\nline 3\nline 4\n",
|
||||
CursorPosition: len("line 1\n" + "lin"),
|
||||
patterns := []struct {
|
||||
document *Document
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
document: &Document{
|
||||
Text: "line 1\nline 2\nline 3\nline 4\n",
|
||||
cursorPosition: len("line 1\n" + "lin"),
|
||||
},
|
||||
expected: "line 1\nlin",
|
||||
},
|
||||
{
|
||||
document: &Document{
|
||||
Text: "あいうえお\nかきくけこ\nさしすせそ\nたちつてと\n",
|
||||
cursorPosition: 8,
|
||||
},
|
||||
expected: "あいうえお\nかき",
|
||||
},
|
||||
{
|
||||
document: &Document{
|
||||
Text: "Добрый\nдень\nДобрый день",
|
||||
cursorPosition: 9,
|
||||
},
|
||||
expected: "Добрый\nде",
|
||||
},
|
||||
}
|
||||
ac := d.TextBeforeCursor()
|
||||
ex := "line 1\nlin"
|
||||
if ac != ex {
|
||||
t.Errorf("Should be %#v, got %#v", ex, ac)
|
||||
for i, p := range patterns {
|
||||
ac := p.document.TextBeforeCursor()
|
||||
if ac != p.expected {
|
||||
t.Errorf("[%d] Should be %s, got %s", i, p.expected, ac)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -38,23 +122,37 @@ func TestDocument_TextAfterCursor(t *testing.T) {
|
||||
{
|
||||
document: &Document{
|
||||
Text: "line 1\nline 2\nline 3\nline 4\n",
|
||||
CursorPosition: len("line 1\n" + "lin"),
|
||||
cursorPosition: len("line 1\n" + "lin"),
|
||||
},
|
||||
expected: "e 2\nline 3\nline 4\n",
|
||||
},
|
||||
{
|
||||
document: &Document{
|
||||
Text: "",
|
||||
CursorPosition: 0,
|
||||
cursorPosition: 0,
|
||||
},
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
document: &Document{
|
||||
Text: "あいうえお\nかきくけこ\nさしすせそ\nたちつてと\n",
|
||||
cursorPosition: 8,
|
||||
},
|
||||
expected: "くけこ\nさしすせそ\nたちつてと\n",
|
||||
},
|
||||
{
|
||||
document: &Document{
|
||||
Text: "Добрый\nдень\nДобрый день",
|
||||
cursorPosition: 9,
|
||||
},
|
||||
expected: "нь\nДобрый день",
|
||||
},
|
||||
}
|
||||
|
||||
for _, p := range pattern {
|
||||
for i, p := range pattern {
|
||||
ac := p.document.TextAfterCursor()
|
||||
if ac != p.expected {
|
||||
t.Errorf("Should be %#v, got %#v", p.expected, ac)
|
||||
t.Errorf("[%d] Should be %#v, got %#v", i, p.expected, ac)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -67,21 +165,164 @@ func TestDocument_GetWordBeforeCursor(t *testing.T) {
|
||||
{
|
||||
document: &Document{
|
||||
Text: "apple bana",
|
||||
CursorPosition: len("apple bana"),
|
||||
cursorPosition: len("apple bana"),
|
||||
},
|
||||
expected: "bana",
|
||||
},
|
||||
{
|
||||
document: &Document{
|
||||
Text: "apple ",
|
||||
CursorPosition: len("apple "),
|
||||
cursorPosition: len("apple "),
|
||||
},
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
document: &Document{
|
||||
Text: "あいうえお かきくけこ さしすせそ",
|
||||
cursorPosition: 8,
|
||||
},
|
||||
expected: "かき",
|
||||
},
|
||||
{
|
||||
document: &Document{
|
||||
Text: "Добрый день Добрый день",
|
||||
cursorPosition: 9,
|
||||
},
|
||||
expected: "де",
|
||||
},
|
||||
}
|
||||
|
||||
for i, p := range pattern {
|
||||
ac := p.document.GetWordBeforeCursor()
|
||||
if ac != p.expected {
|
||||
t.Errorf("[%d] Should be %#v, got %#v", i, p.expected, ac)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDocument_GetWordBeforeCursorWithSpace(t *testing.T) {
|
||||
pattern := []struct {
|
||||
document *Document
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
document: &Document{
|
||||
Text: "apple bana ",
|
||||
cursorPosition: len("apple bana "),
|
||||
},
|
||||
expected: "bana ",
|
||||
},
|
||||
{
|
||||
document: &Document{
|
||||
Text: "apple ",
|
||||
cursorPosition: len("apple "),
|
||||
},
|
||||
expected: "apple ",
|
||||
},
|
||||
{
|
||||
document: &Document{
|
||||
Text: "あいうえお かきくけこ ",
|
||||
cursorPosition: 12,
|
||||
},
|
||||
expected: "かきくけこ ",
|
||||
},
|
||||
{
|
||||
document: &Document{
|
||||
Text: "Добрый день ",
|
||||
cursorPosition: 12,
|
||||
},
|
||||
expected: "день ",
|
||||
},
|
||||
}
|
||||
|
||||
for _, p := range pattern {
|
||||
ac := p.document.GetWordBeforeCursor()
|
||||
ac := p.document.GetWordBeforeCursorWithSpace()
|
||||
if ac != p.expected {
|
||||
t.Errorf("Should be %#v, got %#v", p.expected, ac)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDocument_FindStartOfPreviousWord(t *testing.T) {
|
||||
pattern := []struct {
|
||||
document *Document
|
||||
expected int
|
||||
}{
|
||||
{
|
||||
document: &Document{
|
||||
Text: "apple bana",
|
||||
cursorPosition: len("apple bana"),
|
||||
},
|
||||
expected: len("apple "),
|
||||
},
|
||||
{
|
||||
document: &Document{
|
||||
Text: "apple ",
|
||||
cursorPosition: len("apple "),
|
||||
},
|
||||
expected: len("apple "),
|
||||
},
|
||||
{
|
||||
document: &Document{
|
||||
Text: "あいうえお かきくけこ さしすせそ",
|
||||
cursorPosition: 8, // between 'き' and 'く'
|
||||
},
|
||||
expected: len("あいうえお "), // this function returns index byte in string
|
||||
},
|
||||
{
|
||||
document: &Document{
|
||||
Text: "Добрый день Добрый день",
|
||||
cursorPosition: 9,
|
||||
},
|
||||
expected: len("Добрый "), // this function returns index byte in string
|
||||
},
|
||||
}
|
||||
|
||||
for _, p := range pattern {
|
||||
ac := p.document.FindStartOfPreviousWord()
|
||||
if ac != p.expected {
|
||||
t.Errorf("Should be %#v, got %#v", p.expected, ac)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDocument_FindStartOfPreviousWordWithSpace(t *testing.T) {
|
||||
pattern := []struct {
|
||||
document *Document
|
||||
expected int
|
||||
}{
|
||||
{
|
||||
document: &Document{
|
||||
Text: "apple bana ",
|
||||
cursorPosition: len("apple bana "),
|
||||
},
|
||||
expected: len("apple "),
|
||||
},
|
||||
{
|
||||
document: &Document{
|
||||
Text: "apple ",
|
||||
cursorPosition: len("apple "),
|
||||
},
|
||||
expected: len(""),
|
||||
},
|
||||
{
|
||||
document: &Document{
|
||||
Text: "あいうえお かきくけこ ",
|
||||
cursorPosition: 12, // cursor points to last
|
||||
},
|
||||
expected: len("あいうえお "), // this function returns index byte in string
|
||||
},
|
||||
{
|
||||
document: &Document{
|
||||
Text: "Добрый день ",
|
||||
cursorPosition: 12,
|
||||
},
|
||||
expected: len("Добрый "), // this function returns index byte in string
|
||||
},
|
||||
}
|
||||
|
||||
for _, p := range pattern {
|
||||
ac := p.document.FindStartOfPreviousWordWithSpace()
|
||||
if ac != p.expected {
|
||||
t.Errorf("Should be %#v, got %#v", p.expected, ac)
|
||||
}
|
||||
@ -96,66 +337,51 @@ func TestDocument_GetWordAfterCursor(t *testing.T) {
|
||||
{
|
||||
document: &Document{
|
||||
Text: "apple bana",
|
||||
CursorPosition: len("apple bana"),
|
||||
cursorPosition: len("apple bana"),
|
||||
},
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
document: &Document{
|
||||
Text: "apple bana",
|
||||
CursorPosition: len("apple "),
|
||||
cursorPosition: len("apple "),
|
||||
},
|
||||
expected: "bana",
|
||||
},
|
||||
{
|
||||
document: &Document{
|
||||
Text: "apple bana",
|
||||
CursorPosition: len("apple"),
|
||||
cursorPosition: len("apple"),
|
||||
},
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
document: &Document{
|
||||
Text: "apple bana",
|
||||
CursorPosition: len("ap"),
|
||||
cursorPosition: len("ap"),
|
||||
},
|
||||
expected: "ple",
|
||||
},
|
||||
{
|
||||
document: &Document{
|
||||
Text: "あいうえお かきくけこ さしすせそ",
|
||||
cursorPosition: 8,
|
||||
},
|
||||
expected: "くけこ",
|
||||
},
|
||||
{
|
||||
document: &Document{
|
||||
Text: "Добрый день Добрый день",
|
||||
cursorPosition: 9,
|
||||
},
|
||||
expected: "нь",
|
||||
},
|
||||
}
|
||||
|
||||
for k, p := range pattern {
|
||||
ac := p.document.GetWordAfterCursor()
|
||||
if ac != p.expected {
|
||||
t.Errorf("[%d]Should be %#v, got %#v", k, p.expected, ac)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDocument_GetWordBeforeCursorWithSpace(t *testing.T) {
|
||||
pattern := []struct {
|
||||
document *Document
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
document: &Document{
|
||||
Text: "apple bana ",
|
||||
CursorPosition: len("apple bana "),
|
||||
},
|
||||
expected: "bana ",
|
||||
},
|
||||
{
|
||||
document: &Document{
|
||||
Text: "apple ",
|
||||
CursorPosition: len("apple "),
|
||||
},
|
||||
expected: "apple ",
|
||||
},
|
||||
}
|
||||
|
||||
for _, p := range pattern {
|
||||
ac := p.document.GetWordBeforeCursorWithSpace()
|
||||
if ac != p.expected {
|
||||
t.Errorf("Should be %#v, got %#v", p.expected, ac)
|
||||
t.Errorf("[%d] Should be %#v, got %#v", k, p.expected, ac)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -168,31 +394,45 @@ func TestDocument_GetWordAfterCursorWithSpace(t *testing.T) {
|
||||
{
|
||||
document: &Document{
|
||||
Text: "apple bana",
|
||||
CursorPosition: len("apple bana"),
|
||||
cursorPosition: len("apple bana"),
|
||||
},
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
document: &Document{
|
||||
Text: "apple bana",
|
||||
CursorPosition: len("apple "),
|
||||
cursorPosition: len("apple "),
|
||||
},
|
||||
expected: "bana",
|
||||
},
|
||||
{
|
||||
document: &Document{
|
||||
Text: "apple bana",
|
||||
CursorPosition: len("apple"),
|
||||
cursorPosition: len("apple"),
|
||||
},
|
||||
expected: " bana",
|
||||
},
|
||||
{
|
||||
document: &Document{
|
||||
Text: "apple bana",
|
||||
CursorPosition: len("ap"),
|
||||
cursorPosition: len("ap"),
|
||||
},
|
||||
expected: "ple",
|
||||
},
|
||||
{
|
||||
document: &Document{
|
||||
Text: "あいうえお かきくけこ さしすせそ",
|
||||
cursorPosition: 5,
|
||||
},
|
||||
expected: " かきくけこ",
|
||||
},
|
||||
{
|
||||
document: &Document{
|
||||
Text: "Добрый день Добрый день",
|
||||
cursorPosition: 6,
|
||||
},
|
||||
expected: " день",
|
||||
},
|
||||
}
|
||||
|
||||
for k, p := range pattern {
|
||||
@ -203,35 +443,6 @@ func TestDocument_GetWordAfterCursorWithSpace(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDocument_FindStartOfPreviousWord(t *testing.T) {
|
||||
pattern := []struct {
|
||||
document *Document
|
||||
expected int
|
||||
}{
|
||||
{
|
||||
document: &Document{
|
||||
Text: "apple bana",
|
||||
CursorPosition: len("apple bana"),
|
||||
},
|
||||
expected: len("apple "),
|
||||
},
|
||||
{
|
||||
document: &Document{
|
||||
Text: "apple ",
|
||||
CursorPosition: len("apple "),
|
||||
},
|
||||
expected: len("apple "),
|
||||
},
|
||||
}
|
||||
|
||||
for _, p := range pattern {
|
||||
ac := p.document.FindStartOfPreviousWord()
|
||||
if ac != p.expected {
|
||||
t.Errorf("Should be %#v, got %#v", p.expected, ac)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDocument_FindEndOfCurrentWord(t *testing.T) {
|
||||
pattern := []struct {
|
||||
document *Document
|
||||
@ -240,66 +451,60 @@ func TestDocument_FindEndOfCurrentWord(t *testing.T) {
|
||||
{
|
||||
document: &Document{
|
||||
Text: "apple bana",
|
||||
CursorPosition: len("apple bana"),
|
||||
cursorPosition: len("apple bana"),
|
||||
},
|
||||
expected: len(""),
|
||||
},
|
||||
{
|
||||
document: &Document{
|
||||
Text: "apple bana",
|
||||
CursorPosition: len("apple "),
|
||||
cursorPosition: len("apple "),
|
||||
},
|
||||
expected: len("bana"),
|
||||
},
|
||||
{
|
||||
document: &Document{
|
||||
Text: "apple bana",
|
||||
CursorPosition: len("apple"),
|
||||
cursorPosition: len("apple"),
|
||||
},
|
||||
expected: len(""),
|
||||
},
|
||||
{
|
||||
document: &Document{
|
||||
Text: "apple bana",
|
||||
CursorPosition: len("ap"),
|
||||
cursorPosition: len("ap"),
|
||||
},
|
||||
expected: len("ple"),
|
||||
},
|
||||
{
|
||||
// りん(cursor)ご ばなな
|
||||
document: &Document{
|
||||
Text: "りんご ばなな",
|
||||
cursorPosition: 2,
|
||||
},
|
||||
expected: len("ご"),
|
||||
},
|
||||
{
|
||||
document: &Document{
|
||||
Text: "りんご ばなな",
|
||||
cursorPosition: 3,
|
||||
},
|
||||
expected: 0,
|
||||
},
|
||||
{
|
||||
// Доб(cursor)рый день
|
||||
document: &Document{
|
||||
Text: "Добрый день",
|
||||
cursorPosition: 3,
|
||||
},
|
||||
expected: len("рый"),
|
||||
},
|
||||
}
|
||||
|
||||
for k, p := range pattern {
|
||||
ac := p.document.FindEndOfCurrentWord()
|
||||
if ac != p.expected {
|
||||
t.Errorf("[%d]Should be %#v, got %#v", k, p.expected, ac)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDocument_FindStartOfPreviousWordWithSpace(t *testing.T) {
|
||||
pattern := []struct {
|
||||
document *Document
|
||||
expected int
|
||||
}{
|
||||
{
|
||||
document: &Document{
|
||||
Text: "apple bana ",
|
||||
CursorPosition: len("apple bana "),
|
||||
},
|
||||
expected: len("apple "),
|
||||
},
|
||||
{
|
||||
document: &Document{
|
||||
Text: "apple ",
|
||||
CursorPosition: len("apple "),
|
||||
},
|
||||
expected: len(""),
|
||||
},
|
||||
}
|
||||
|
||||
for _, p := range pattern {
|
||||
ac := p.document.FindStartOfPreviousWordWithSpace()
|
||||
if ac != p.expected {
|
||||
t.Errorf("Should be %#v, got %#v", p.expected, ac)
|
||||
t.Errorf("[%d] Should be %#v, got %#v", k, p.expected, ac)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -312,37 +517,58 @@ func TestDocument_FindEndOfCurrentWordWithSpace(t *testing.T) {
|
||||
{
|
||||
document: &Document{
|
||||
Text: "apple bana",
|
||||
CursorPosition: len("apple bana"),
|
||||
cursorPosition: len("apple bana"),
|
||||
},
|
||||
expected: len(""),
|
||||
},
|
||||
{
|
||||
document: &Document{
|
||||
Text: "apple bana",
|
||||
CursorPosition: len("apple "),
|
||||
cursorPosition: len("apple "),
|
||||
},
|
||||
expected: len("bana"),
|
||||
},
|
||||
{
|
||||
document: &Document{
|
||||
Text: "apple bana",
|
||||
CursorPosition: len("apple"),
|
||||
cursorPosition: len("apple"),
|
||||
},
|
||||
expected: len(" bana"),
|
||||
},
|
||||
{
|
||||
document: &Document{
|
||||
Text: "apple bana",
|
||||
CursorPosition: len("ap"),
|
||||
cursorPosition: len("ap"),
|
||||
},
|
||||
expected: len("ple"),
|
||||
},
|
||||
{
|
||||
document: &Document{
|
||||
Text: "あいうえお かきくけこ",
|
||||
cursorPosition: 6,
|
||||
},
|
||||
expected: len("かきくけこ"),
|
||||
},
|
||||
{
|
||||
document: &Document{
|
||||
Text: "あいうえお かきくけこ",
|
||||
cursorPosition: 5,
|
||||
},
|
||||
expected: len(" かきくけこ"),
|
||||
},
|
||||
{
|
||||
document: &Document{
|
||||
Text: "Добрый день",
|
||||
cursorPosition: 6,
|
||||
},
|
||||
expected: len(" день"),
|
||||
},
|
||||
}
|
||||
|
||||
for k, p := range pattern {
|
||||
ac := p.document.FindEndOfCurrentWordWithSpace()
|
||||
if ac != p.expected {
|
||||
t.Errorf("[%d]Should be %#v, got %#v", k, p.expected, ac)
|
||||
t.Errorf("[%d] Should be %#v, got %#v", k, p.expected, ac)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -350,7 +576,7 @@ func TestDocument_FindEndOfCurrentWordWithSpace(t *testing.T) {
|
||||
func TestDocument_CurrentLineBeforeCursor(t *testing.T) {
|
||||
d := &Document{
|
||||
Text: "line 1\nline 2\nline 3\nline 4\n",
|
||||
CursorPosition: len("line 1\n" + "lin"),
|
||||
cursorPosition: len("line 1\n" + "lin"),
|
||||
}
|
||||
ac := d.CurrentLineBeforeCursor()
|
||||
ex := "lin"
|
||||
@ -362,7 +588,7 @@ func TestDocument_CurrentLineBeforeCursor(t *testing.T) {
|
||||
func TestDocument_CurrentLineAfterCursor(t *testing.T) {
|
||||
d := &Document{
|
||||
Text: "line 1\nline 2\nline 3\nline 4\n",
|
||||
CursorPosition: len("line 1\n" + "lin"),
|
||||
cursorPosition: len("line 1\n" + "lin"),
|
||||
}
|
||||
ac := d.CurrentLineAfterCursor()
|
||||
ex := "e 2"
|
||||
@ -374,7 +600,7 @@ func TestDocument_CurrentLineAfterCursor(t *testing.T) {
|
||||
func TestDocument_CurrentLine(t *testing.T) {
|
||||
d := &Document{
|
||||
Text: "line 1\nline 2\nline 3\nline 4\n",
|
||||
CursorPosition: len("line 1\n" + "lin"),
|
||||
cursorPosition: len("line 1\n" + "lin"),
|
||||
}
|
||||
ac := d.CurrentLine()
|
||||
ex := "line 2"
|
||||
@ -383,27 +609,23 @@ func TestDocument_CurrentLine(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Table Driven Tests for CursorPositionRow and CursorPositionCol
|
||||
type cursorPositionTest struct {
|
||||
document *Document
|
||||
expectedRow int
|
||||
expectedCol int
|
||||
}
|
||||
|
||||
var cursorPositionTests = []cursorPositionTest{
|
||||
{
|
||||
document: &Document{Text: "line 1\nline 2\nline 3\n", CursorPosition: len("line 1\n" + "lin")},
|
||||
expectedRow: 1,
|
||||
expectedCol: 3,
|
||||
},
|
||||
{
|
||||
document: &Document{Text: "", CursorPosition: 0},
|
||||
expectedRow: 0,
|
||||
expectedCol: 0,
|
||||
},
|
||||
}
|
||||
|
||||
func TestDocument_CursorPositionRowAndCol(t *testing.T) {
|
||||
var cursorPositionTests = []struct {
|
||||
document *Document
|
||||
expectedRow int
|
||||
expectedCol int
|
||||
}{
|
||||
{
|
||||
document: &Document{Text: "line 1\nline 2\nline 3\n", cursorPosition: len("line 1\n" + "lin")},
|
||||
expectedRow: 1,
|
||||
expectedCol: 3,
|
||||
},
|
||||
{
|
||||
document: &Document{Text: "", cursorPosition: 0},
|
||||
expectedRow: 0,
|
||||
expectedCol: 0,
|
||||
},
|
||||
}
|
||||
for _, test := range cursorPositionTests {
|
||||
ac := test.document.CursorPositionRow()
|
||||
if ac != test.expectedRow {
|
||||
@ -419,7 +641,7 @@ func TestDocument_CursorPositionRowAndCol(t *testing.T) {
|
||||
func TestDocument_GetCursorLeftPosition(t *testing.T) {
|
||||
d := &Document{
|
||||
Text: "line 1\nline 2\nline 3\nline 4\n",
|
||||
CursorPosition: len("line 1\n" + "line 2\n" + "lin"),
|
||||
cursorPosition: len("line 1\n" + "line 2\n" + "lin"),
|
||||
}
|
||||
ac := d.GetCursorLeftPosition(2)
|
||||
ex := -2
|
||||
@ -436,7 +658,7 @@ func TestDocument_GetCursorLeftPosition(t *testing.T) {
|
||||
func TestDocument_GetCursorUpPosition(t *testing.T) {
|
||||
d := &Document{
|
||||
Text: "line 1\nline 2\nline 3\nline 4\n",
|
||||
CursorPosition: len("line 1\n" + "line 2\n" + "lin"),
|
||||
cursorPosition: len("line 1\n" + "line 2\n" + "lin"),
|
||||
}
|
||||
ac := d.GetCursorUpPosition(2, -1)
|
||||
ex := len("lin") - len("line 1\n"+"line 2\n"+"lin")
|
||||
@ -454,7 +676,7 @@ func TestDocument_GetCursorUpPosition(t *testing.T) {
|
||||
func TestDocument_GetCursorDownPosition(t *testing.T) {
|
||||
d := &Document{
|
||||
Text: "line 1\nline 2\nline 3\nline 4\n",
|
||||
CursorPosition: len("lin"),
|
||||
cursorPosition: len("lin"),
|
||||
}
|
||||
ac := d.GetCursorDownPosition(2, -1)
|
||||
ex := len("line 1\n"+"line 2\n"+"lin") - len("lin")
|
||||
@ -472,7 +694,7 @@ func TestDocument_GetCursorDownPosition(t *testing.T) {
|
||||
func TestDocument_GetCursorRightPosition(t *testing.T) {
|
||||
d := &Document{
|
||||
Text: "line 1\nline 2\nline 3\nline 4\n",
|
||||
CursorPosition: len("line 1\n" + "line 2\n" + "lin"),
|
||||
cursorPosition: len("line 1\n" + "line 2\n" + "lin"),
|
||||
}
|
||||
ac := d.GetCursorRightPosition(2)
|
||||
ex := 2
|
||||
@ -489,7 +711,7 @@ func TestDocument_GetCursorRightPosition(t *testing.T) {
|
||||
func TestDocument_Lines(t *testing.T) {
|
||||
d := &Document{
|
||||
Text: "line 1\nline 2\nline 3\nline 4\n",
|
||||
CursorPosition: len("line 1\n" + "lin"),
|
||||
cursorPosition: len("line 1\n" + "lin"),
|
||||
}
|
||||
ac := d.Lines()
|
||||
ex := []string{"line 1", "line 2", "line 3", "line 4", ""}
|
||||
@ -501,7 +723,7 @@ func TestDocument_Lines(t *testing.T) {
|
||||
func TestDocument_LineCount(t *testing.T) {
|
||||
d := &Document{
|
||||
Text: "line 1\nline 2\nline 3\nline 4\n",
|
||||
CursorPosition: len("line 1\n" + "lin"),
|
||||
cursorPosition: len("line 1\n" + "lin"),
|
||||
}
|
||||
ac := d.LineCount()
|
||||
ex := 5
|
||||
@ -513,7 +735,7 @@ func TestDocument_LineCount(t *testing.T) {
|
||||
func TestDocument_TranslateIndexToPosition(t *testing.T) {
|
||||
d := &Document{
|
||||
Text: "line 1\nline 2\nline 3\nline 4\n",
|
||||
CursorPosition: len("line 1\n" + "lin"),
|
||||
cursorPosition: len("line 1\n" + "lin"),
|
||||
}
|
||||
row, col := d.TranslateIndexToPosition(len("line 1\nline 2\nlin"))
|
||||
if row != 2 {
|
||||
@ -534,7 +756,7 @@ func TestDocument_TranslateIndexToPosition(t *testing.T) {
|
||||
func TestDocument_TranslateRowColToIndex(t *testing.T) {
|
||||
d := &Document{
|
||||
Text: "line 1\nline 2\nline 3\nline 4\n",
|
||||
CursorPosition: len("line 1\n" + "lin"),
|
||||
cursorPosition: len("line 1\n" + "lin"),
|
||||
}
|
||||
ac := d.TranslateRowColToIndex(2, 3)
|
||||
ex := len("line 1\nline 2\nlin")
|
||||
@ -551,13 +773,13 @@ func TestDocument_TranslateRowColToIndex(t *testing.T) {
|
||||
func TestDocument_OnLastLine(t *testing.T) {
|
||||
d := &Document{
|
||||
Text: "line 1\nline 2\nline 3",
|
||||
CursorPosition: len("line 1\nline"),
|
||||
cursorPosition: len("line 1\nline"),
|
||||
}
|
||||
ac := d.OnLastLine()
|
||||
if ac {
|
||||
t.Errorf("Should be %#v, got %#v", false, ac)
|
||||
}
|
||||
d.CursorPosition = len("line 1\nline 2\nline")
|
||||
d.cursorPosition = len("line 1\nline 2\nline")
|
||||
ac = d.OnLastLine()
|
||||
if !ac {
|
||||
t.Errorf("Should be %#v, got %#v", true, ac)
|
||||
@ -567,7 +789,7 @@ func TestDocument_OnLastLine(t *testing.T) {
|
||||
func TestDocument_GetEndOfLinePosition(t *testing.T) {
|
||||
d := &Document{
|
||||
Text: "line 1\nline 2\nline 3",
|
||||
CursorPosition: len("line 1\nli"),
|
||||
cursorPosition: len("line 1\nli"),
|
||||
}
|
||||
ac := d.GetEndOfLinePosition()
|
||||
ex := len("ne 2")
|
||||
|
@ -5,20 +5,20 @@ import "testing"
|
||||
func TestEmacsKeyBindings(t *testing.T) {
|
||||
buf := NewBuffer()
|
||||
buf.InsertText("abcde", false, true)
|
||||
if buf.CursorPosition != len("abcde") {
|
||||
t.Errorf("Want %d, but got %d", len("abcde"), buf.CursorPosition)
|
||||
if buf.cursorPosition != len("abcde") {
|
||||
t.Errorf("Want %d, but got %d", len("abcde"), buf.cursorPosition)
|
||||
}
|
||||
|
||||
// Go to the beginning of the line
|
||||
applyEmacsKeyBind(buf, ControlA)
|
||||
if buf.CursorPosition != 0 {
|
||||
t.Errorf("Want %d, but got %d", 0, buf.CursorPosition)
|
||||
if buf.cursorPosition != 0 {
|
||||
t.Errorf("Want %d, but got %d", 0, buf.cursorPosition)
|
||||
}
|
||||
|
||||
// Go to the end of the line
|
||||
applyEmacsKeyBind(buf, ControlE)
|
||||
if buf.CursorPosition != len("abcde") {
|
||||
t.Errorf("Want %d, but got %d", len("abcde"), buf.CursorPosition)
|
||||
if buf.cursorPosition != len("abcde") {
|
||||
t.Errorf("Want %d, but got %d", len("abcde"), buf.cursorPosition)
|
||||
}
|
||||
}
|
||||
|
||||
|
20
render.go
20
render.go
@ -2,6 +2,8 @@ package prompt
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
|
||||
"github.com/mattn/go-runewidth"
|
||||
)
|
||||
|
||||
// Render to render prompt information from state of Buffer.
|
||||
@ -97,7 +99,7 @@ func (r *Render) renderCompletion(buf *Buffer, completions *CompletionManager) {
|
||||
prefix := r.getCurrentPrefix()
|
||||
formatted, width := formatSuggestions(
|
||||
suggestions,
|
||||
int(r.col)-len(prefix)-1, // -1 means a width of scrollbar
|
||||
int(r.col)-runewidth.StringWidth(prefix)-1, // -1 means a width of scrollbar
|
||||
)
|
||||
// +1 means a width of scrollbar.
|
||||
width++
|
||||
@ -109,7 +111,7 @@ func (r *Render) renderCompletion(buf *Buffer, completions *CompletionManager) {
|
||||
formatted = formatted[completions.verticalScroll : completions.verticalScroll+windowHeight]
|
||||
r.prepareArea(windowHeight)
|
||||
|
||||
cursor := len(prefix) + len(buf.Document().TextBeforeCursor())
|
||||
cursor := runewidth.StringWidth(prefix) + runewidth.StringWidth(buf.Document().TextBeforeCursor())
|
||||
x, _ := r.toPos(cursor)
|
||||
if x+width >= int(r.col) {
|
||||
cursor = r.backward(cursor, x+width-int(r.col))
|
||||
@ -178,7 +180,7 @@ func (r *Render) Render(buffer *Buffer, completion *CompletionManager) {
|
||||
|
||||
line := buffer.Text()
|
||||
prefix := r.getCurrentPrefix()
|
||||
cursor := len(prefix) + len(line)
|
||||
cursor := runewidth.StringWidth(prefix) + runewidth.StringWidth(line)
|
||||
|
||||
// prepare area
|
||||
_, y := r.toPos(cursor)
|
||||
@ -201,23 +203,23 @@ func (r *Render) Render(buffer *Buffer, completion *CompletionManager) {
|
||||
|
||||
r.out.EraseDown()
|
||||
|
||||
cursor = r.backward(cursor, len(line)-buffer.CursorPosition)
|
||||
cursor = r.backward(cursor, runewidth.StringWidth(line)-buffer.DisplayCursorPosition())
|
||||
|
||||
r.renderCompletion(buffer, completion)
|
||||
if suggest, ok := completion.GetSelectedSuggestion(); ok {
|
||||
cursor = r.backward(cursor, len(buffer.Document().GetWordBeforeCursor()))
|
||||
cursor = r.backward(cursor, runewidth.StringWidth(buffer.Document().GetWordBeforeCursor()))
|
||||
|
||||
r.out.SetColor(r.previewSuggestionTextColor, r.previewSuggestionBGColor, false)
|
||||
r.out.WriteStr(suggest.Text)
|
||||
r.out.SetColor(DefaultColor, DefaultColor, false)
|
||||
cursor += len(suggest.Text)
|
||||
cursor += runewidth.StringWidth(suggest.Text)
|
||||
|
||||
rest := buffer.Document().TextAfterCursor()
|
||||
r.out.WriteStr(rest)
|
||||
cursor += len(rest)
|
||||
cursor += runewidth.StringWidth(rest)
|
||||
r.lineWrap(cursor)
|
||||
|
||||
cursor = r.backward(cursor, len(rest))
|
||||
cursor = r.backward(cursor, runewidth.StringWidth(rest))
|
||||
}
|
||||
r.previousCursor = cursor
|
||||
}
|
||||
@ -225,7 +227,7 @@ func (r *Render) Render(buffer *Buffer, completion *CompletionManager) {
|
||||
// BreakLine to break line.
|
||||
func (r *Render) BreakLine(buffer *Buffer) {
|
||||
// Erasing and Render
|
||||
cursor := len(buffer.Document().TextBeforeCursor()) + len(r.getCurrentPrefix())
|
||||
cursor := runewidth.StringWidth(buffer.Document().TextBeforeCursor()) + runewidth.StringWidth(r.getCurrentPrefix())
|
||||
r.clear(cursor)
|
||||
r.renderPrefix()
|
||||
r.out.SetColor(r.inputTextColor, r.inputBGColor, false)
|
||||
|
Loading…
Reference in New Issue
Block a user