|
|
|
@ -4,7 +4,7 @@ import ( |
|
|
|
"fmt" |
|
|
|
"os" |
|
|
|
|
|
|
|
"github.com/emirpasic/gods/lists/arraylist" |
|
|
|
"github.com/emirpasic/gods/v2/lists/arraylist" |
|
|
|
"github.com/mattn/go-runewidth" |
|
|
|
"golang.org/x/term" |
|
|
|
) |
|
|
|
@ -12,9 +12,9 @@ import ( |
|
|
|
type Buffer struct { |
|
|
|
DisplayPos int |
|
|
|
Pos int |
|
|
|
Buf *arraylist.List |
|
|
|
Buf *arraylist.List[rune] |
|
|
|
// LineHasSpace is an arraylist of bools to keep track of whether a line has a space at the end
|
|
|
|
LineHasSpace *arraylist.List |
|
|
|
LineHasSpace *arraylist.List[bool] |
|
|
|
Prompt *Prompt |
|
|
|
LineWidth int |
|
|
|
Width int |
|
|
|
@ -33,8 +33,8 @@ func NewBuffer(prompt *Prompt) (*Buffer, error) { |
|
|
|
b := &Buffer{ |
|
|
|
DisplayPos: 0, |
|
|
|
Pos: 0, |
|
|
|
Buf: arraylist.New(), |
|
|
|
LineHasSpace: arraylist.New(), |
|
|
|
Buf: arraylist.New[rune](), |
|
|
|
LineHasSpace: arraylist.New[bool](), |
|
|
|
Prompt: prompt, |
|
|
|
Width: width, |
|
|
|
Height: height, |
|
|
|
@ -46,40 +46,33 @@ func NewBuffer(prompt *Prompt) (*Buffer, error) { |
|
|
|
|
|
|
|
func (b *Buffer) GetLineSpacing(line int) bool { |
|
|
|
hasSpace, _ := b.LineHasSpace.Get(line) |
|
|
|
|
|
|
|
if hasSpace == nil { |
|
|
|
return false |
|
|
|
} |
|
|
|
|
|
|
|
return hasSpace.(bool) |
|
|
|
return hasSpace |
|
|
|
} |
|
|
|
|
|
|
|
func (b *Buffer) MoveLeft() { |
|
|
|
if b.Pos > 0 { |
|
|
|
// asserts that we retrieve a rune
|
|
|
|
if e, ok := b.Buf.Get(b.Pos - 1); ok { |
|
|
|
if r, ok := e.(rune); ok { |
|
|
|
rLength := runewidth.RuneWidth(r) |
|
|
|
|
|
|
|
if b.DisplayPos%b.LineWidth == 0 { |
|
|
|
fmt.Print(CursorUp + CursorBOL + CursorRightN(b.Width)) |
|
|
|
if rLength == 2 { |
|
|
|
fmt.Print(CursorLeft) |
|
|
|
} |
|
|
|
if r, ok := b.Buf.Get(b.Pos - 1); ok { |
|
|
|
rLength := runewidth.RuneWidth(r) |
|
|
|
|
|
|
|
line := b.DisplayPos/b.LineWidth - 1 |
|
|
|
hasSpace := b.GetLineSpacing(line) |
|
|
|
if hasSpace { |
|
|
|
b.DisplayPos -= 1 |
|
|
|
fmt.Print(CursorLeft) |
|
|
|
} |
|
|
|
} else { |
|
|
|
fmt.Print(CursorLeftN(rLength)) |
|
|
|
if b.DisplayPos%b.LineWidth == 0 { |
|
|
|
fmt.Print(CursorUp + CursorBOL + CursorRightN(b.Width)) |
|
|
|
if rLength == 2 { |
|
|
|
fmt.Print(CursorLeft) |
|
|
|
} |
|
|
|
|
|
|
|
b.Pos -= 1 |
|
|
|
b.DisplayPos -= rLength |
|
|
|
line := b.DisplayPos/b.LineWidth - 1 |
|
|
|
hasSpace := b.GetLineSpacing(line) |
|
|
|
if hasSpace { |
|
|
|
b.DisplayPos -= 1 |
|
|
|
fmt.Print(CursorLeft) |
|
|
|
} |
|
|
|
} else { |
|
|
|
fmt.Print(CursorLeftN(rLength)) |
|
|
|
} |
|
|
|
|
|
|
|
b.Pos -= 1 |
|
|
|
b.DisplayPos -= rLength |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
@ -107,24 +100,22 @@ func (b *Buffer) MoveLeftWord() { |
|
|
|
|
|
|
|
func (b *Buffer) MoveRight() { |
|
|
|
if b.Pos < b.Buf.Size() { |
|
|
|
if e, ok := b.Buf.Get(b.Pos); ok { |
|
|
|
if r, ok := e.(rune); ok { |
|
|
|
rLength := runewidth.RuneWidth(r) |
|
|
|
b.Pos += 1 |
|
|
|
hasSpace := b.GetLineSpacing(b.DisplayPos / b.LineWidth) |
|
|
|
b.DisplayPos += rLength |
|
|
|
|
|
|
|
if b.DisplayPos%b.LineWidth == 0 { |
|
|
|
fmt.Print(CursorDown + CursorBOL + CursorRightN(len(b.Prompt.prompt()))) |
|
|
|
} else if (b.DisplayPos-rLength)%b.LineWidth == b.LineWidth-1 && hasSpace { |
|
|
|
fmt.Print(CursorDown + CursorBOL + CursorRightN(len(b.Prompt.prompt())+rLength)) |
|
|
|
b.DisplayPos += 1 |
|
|
|
} else if b.LineHasSpace.Size() > 0 && b.DisplayPos%b.LineWidth == b.LineWidth-1 && hasSpace { |
|
|
|
fmt.Print(CursorDown + CursorBOL + CursorRightN(len(b.Prompt.prompt()))) |
|
|
|
b.DisplayPos += 1 |
|
|
|
} else { |
|
|
|
fmt.Print(CursorRightN(rLength)) |
|
|
|
} |
|
|
|
if r, ok := b.Buf.Get(b.Pos); ok { |
|
|
|
rLength := runewidth.RuneWidth(r) |
|
|
|
b.Pos += 1 |
|
|
|
hasSpace := b.GetLineSpacing(b.DisplayPos / b.LineWidth) |
|
|
|
b.DisplayPos += rLength |
|
|
|
|
|
|
|
if b.DisplayPos%b.LineWidth == 0 { |
|
|
|
fmt.Print(CursorDown + CursorBOL + CursorRightN(len(b.Prompt.prompt()))) |
|
|
|
} else if (b.DisplayPos-rLength)%b.LineWidth == b.LineWidth-1 && hasSpace { |
|
|
|
fmt.Print(CursorDown + CursorBOL + CursorRightN(len(b.Prompt.prompt())+rLength)) |
|
|
|
b.DisplayPos += 1 |
|
|
|
} else if b.LineHasSpace.Size() > 0 && b.DisplayPos%b.LineWidth == b.LineWidth-1 && hasSpace { |
|
|
|
fmt.Print(CursorDown + CursorBOL + CursorRightN(len(b.Prompt.prompt()))) |
|
|
|
b.DisplayPos += 1 |
|
|
|
} else { |
|
|
|
fmt.Print(CursorRightN(rLength)) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
@ -182,10 +173,8 @@ func (b *Buffer) MoveToEnd() { |
|
|
|
func (b *Buffer) DisplaySize() int { |
|
|
|
sum := 0 |
|
|
|
for i := range b.Buf.Size() { |
|
|
|
if e, ok := b.Buf.Get(i); ok { |
|
|
|
if r, ok := e.(rune); ok { |
|
|
|
sum += runewidth.RuneWidth(r) |
|
|
|
} |
|
|
|
if r, ok := b.Buf.Get(i); ok { |
|
|
|
sum += runewidth.RuneWidth(r) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@ -257,11 +246,9 @@ func (b *Buffer) countRemainingLineWidth(place int) int { |
|
|
|
for place <= b.LineWidth { |
|
|
|
counter += 1 |
|
|
|
sum += prevLen |
|
|
|
if e, ok := b.Buf.Get(b.Pos + counter); ok { |
|
|
|
if r, ok := e.(rune); ok { |
|
|
|
place += runewidth.RuneWidth(r) |
|
|
|
prevLen = len(string(r)) |
|
|
|
} |
|
|
|
if r, ok := b.Buf.Get(b.Pos + counter); ok { |
|
|
|
place += runewidth.RuneWidth(r) |
|
|
|
prevLen = len(string(r)) |
|
|
|
} else { |
|
|
|
break |
|
|
|
} |
|
|
|
@ -346,64 +333,62 @@ func (b *Buffer) drawRemaining() { |
|
|
|
|
|
|
|
func (b *Buffer) Remove() { |
|
|
|
if b.Buf.Size() > 0 && b.Pos > 0 { |
|
|
|
if e, ok := b.Buf.Get(b.Pos - 1); ok { |
|
|
|
if r, ok := e.(rune); ok { |
|
|
|
rLength := runewidth.RuneWidth(r) |
|
|
|
hasSpace := b.GetLineSpacing(b.DisplayPos/b.LineWidth - 1) |
|
|
|
|
|
|
|
if b.DisplayPos%b.LineWidth == 0 { |
|
|
|
// if the user backspaces over the word boundary, do this magic to clear the line
|
|
|
|
// and move to the end of the previous line
|
|
|
|
fmt.Print(CursorBOL + ClearToEOL + CursorUp + CursorBOL + CursorRightN(b.Width)) |
|
|
|
|
|
|
|
if b.DisplaySize()%b.LineWidth < (b.DisplaySize()-rLength)%b.LineWidth { |
|
|
|
b.LineHasSpace.Remove(b.DisplayPos/b.LineWidth - 1) |
|
|
|
} |
|
|
|
if r, ok := b.Buf.Get(b.Pos - 1); ok { |
|
|
|
rLength := runewidth.RuneWidth(r) |
|
|
|
hasSpace := b.GetLineSpacing(b.DisplayPos/b.LineWidth - 1) |
|
|
|
|
|
|
|
if hasSpace { |
|
|
|
b.DisplayPos -= 1 |
|
|
|
fmt.Print(CursorLeft) |
|
|
|
} |
|
|
|
if b.DisplayPos%b.LineWidth == 0 { |
|
|
|
// if the user backspaces over the word boundary, do this magic to clear the line
|
|
|
|
// and move to the end of the previous line
|
|
|
|
fmt.Print(CursorBOL + ClearToEOL + CursorUp + CursorBOL + CursorRightN(b.Width)) |
|
|
|
|
|
|
|
if rLength == 2 { |
|
|
|
fmt.Print(CursorLeft + " " + CursorLeftN(2)) |
|
|
|
} else { |
|
|
|
fmt.Print(" " + CursorLeft) |
|
|
|
} |
|
|
|
} else if (b.DisplayPos-rLength)%b.LineWidth == 0 && hasSpace { |
|
|
|
fmt.Print(CursorBOL + ClearToEOL + CursorUp + CursorBOL + CursorRightN(b.Width)) |
|
|
|
if b.DisplaySize()%b.LineWidth < (b.DisplaySize()-rLength)%b.LineWidth { |
|
|
|
b.LineHasSpace.Remove(b.DisplayPos/b.LineWidth - 1) |
|
|
|
} |
|
|
|
|
|
|
|
if b.Pos == b.Buf.Size() { |
|
|
|
b.LineHasSpace.Remove(b.DisplayPos/b.LineWidth - 1) |
|
|
|
} |
|
|
|
if hasSpace { |
|
|
|
b.DisplayPos -= 1 |
|
|
|
fmt.Print(CursorLeft) |
|
|
|
} |
|
|
|
|
|
|
|
if rLength == 2 { |
|
|
|
fmt.Print(CursorLeft + " " + CursorLeftN(2)) |
|
|
|
} else { |
|
|
|
fmt.Print(CursorLeftN(rLength)) |
|
|
|
for range rLength { |
|
|
|
fmt.Print(" ") |
|
|
|
} |
|
|
|
fmt.Print(CursorLeftN(rLength)) |
|
|
|
fmt.Print(" " + CursorLeft) |
|
|
|
} |
|
|
|
} else if (b.DisplayPos-rLength)%b.LineWidth == 0 && hasSpace { |
|
|
|
fmt.Print(CursorBOL + ClearToEOL + CursorUp + CursorBOL + CursorRightN(b.Width)) |
|
|
|
|
|
|
|
var eraseExtraLine bool |
|
|
|
if (b.DisplaySize()-1)%b.LineWidth == 0 || (rLength == 2 && ((b.DisplaySize()-2)%b.LineWidth == 0)) || b.DisplaySize()%b.LineWidth == 0 { |
|
|
|
eraseExtraLine = true |
|
|
|
if b.Pos == b.Buf.Size() { |
|
|
|
b.LineHasSpace.Remove(b.DisplayPos/b.LineWidth - 1) |
|
|
|
} |
|
|
|
b.DisplayPos -= 1 |
|
|
|
} else { |
|
|
|
fmt.Print(CursorLeftN(rLength)) |
|
|
|
for range rLength { |
|
|
|
fmt.Print(" ") |
|
|
|
} |
|
|
|
fmt.Print(CursorLeftN(rLength)) |
|
|
|
} |
|
|
|
|
|
|
|
b.Pos -= 1 |
|
|
|
b.DisplayPos -= rLength |
|
|
|
b.Buf.Remove(b.Pos) |
|
|
|
|
|
|
|
if b.Pos < b.Buf.Size() { |
|
|
|
b.drawRemaining() |
|
|
|
// this erases a line which is left over when backspacing in the middle of a line and there
|
|
|
|
// are trailing characters which go over the line width boundary
|
|
|
|
if eraseExtraLine { |
|
|
|
remainingLines := (b.DisplaySize() - b.DisplayPos) / b.LineWidth |
|
|
|
fmt.Print(CursorDownN(remainingLines+1) + CursorBOL + ClearToEOL) |
|
|
|
place := b.DisplayPos % b.LineWidth |
|
|
|
fmt.Print(CursorUpN(remainingLines+1) + CursorRightN(place+len(b.Prompt.prompt()))) |
|
|
|
} |
|
|
|
var eraseExtraLine bool |
|
|
|
if (b.DisplaySize()-1)%b.LineWidth == 0 || (rLength == 2 && ((b.DisplaySize()-2)%b.LineWidth == 0)) || b.DisplaySize()%b.LineWidth == 0 { |
|
|
|
eraseExtraLine = true |
|
|
|
} |
|
|
|
|
|
|
|
b.Pos -= 1 |
|
|
|
b.DisplayPos -= rLength |
|
|
|
b.Buf.Remove(b.Pos) |
|
|
|
|
|
|
|
if b.Pos < b.Buf.Size() { |
|
|
|
b.drawRemaining() |
|
|
|
// this erases a line which is left over when backspacing in the middle of a line and there
|
|
|
|
// are trailing characters which go over the line width boundary
|
|
|
|
if eraseExtraLine { |
|
|
|
remainingLines := (b.DisplaySize() - b.DisplayPos) / b.LineWidth |
|
|
|
fmt.Print(CursorDownN(remainingLines+1) + CursorBOL + ClearToEOL) |
|
|
|
place := b.DisplayPos % b.LineWidth |
|
|
|
fmt.Print(CursorUpN(remainingLines+1) + CursorRightN(place+len(b.Prompt.prompt()))) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
@ -536,7 +521,7 @@ func (b *Buffer) StringNM(n, m int) string { |
|
|
|
} |
|
|
|
for cnt := n; cnt < m; cnt++ { |
|
|
|
c, _ := b.Buf.Get(cnt) |
|
|
|
s += string(c.(rune)) |
|
|
|
s += string(c) |
|
|
|
} |
|
|
|
return s |
|
|
|
} |
|
|
|
|