Compare commits
2 Commits
main
...
mxyng/cmd-
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a1d90e68d0 | ||
![]() |
7ccdc98a5f |
@ -10,15 +10,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Buffer struct {
|
type Buffer struct {
|
||||||
DisplayPos int
|
Prompt *Prompt
|
||||||
Pos int
|
LineWidth int
|
||||||
Buf *arraylist.List[rune]
|
Width int
|
||||||
// LineHasSpace is an arraylist of bools to keep track of whether a line has a space at the end
|
Height int
|
||||||
LineHasSpace *arraylist.List[bool]
|
|
||||||
Prompt *Prompt
|
line *arraylist.List[rune]
|
||||||
LineWidth int
|
spaceMask *arraylist.List[bool]
|
||||||
Width int
|
pos int
|
||||||
Height int
|
displayPos int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBuffer(prompt *Prompt) (*Buffer, error) {
|
func NewBuffer(prompt *Prompt) (*Buffer, error) {
|
||||||
@ -30,130 +30,113 @@ func NewBuffer(prompt *Prompt) (*Buffer, error) {
|
|||||||
|
|
||||||
lwidth := width - len(prompt.prompt())
|
lwidth := width - len(prompt.prompt())
|
||||||
|
|
||||||
b := &Buffer{
|
return &Buffer{
|
||||||
DisplayPos: 0,
|
displayPos: 0,
|
||||||
Pos: 0,
|
pos: 0,
|
||||||
Buf: arraylist.New[rune](),
|
line: arraylist.New[rune](),
|
||||||
LineHasSpace: arraylist.New[bool](),
|
spaceMask: arraylist.New[bool](),
|
||||||
Prompt: prompt,
|
Prompt: prompt,
|
||||||
Width: width,
|
Width: width,
|
||||||
Height: height,
|
Height: height,
|
||||||
LineWidth: lwidth,
|
LineWidth: lwidth,
|
||||||
}
|
}, nil
|
||||||
|
|
||||||
return b, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Buffer) GetLineSpacing(line int) bool {
|
func (b *Buffer) GetLineSpacing(line int) bool {
|
||||||
hasSpace, _ := b.LineHasSpace.Get(line)
|
hasSpace, _ := b.spaceMask.Get(line)
|
||||||
return hasSpace
|
return hasSpace
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Buffer) MoveLeft() {
|
func (b *Buffer) MoveLeft() {
|
||||||
if b.Pos > 0 {
|
if b.pos > 0 {
|
||||||
// asserts that we retrieve a rune
|
r, _ := b.line.Get(b.pos - 1)
|
||||||
if r, ok := b.Buf.Get(b.Pos - 1); ok {
|
rLength := runewidth.RuneWidth(r)
|
||||||
rLength := runewidth.RuneWidth(r)
|
|
||||||
|
|
||||||
if b.DisplayPos%b.LineWidth == 0 {
|
if b.displayPos%b.LineWidth == 0 {
|
||||||
fmt.Print(CursorUp + CursorBOL + CursorRightN(b.Width))
|
fmt.Print(CursorUp + CursorBOL + CursorRightN(b.Width))
|
||||||
if rLength == 2 {
|
if rLength == 2 {
|
||||||
fmt.Print(CursorLeft)
|
fmt.Print(CursorLeft)
|
||||||
}
|
|
||||||
|
|
||||||
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
|
line := b.displayPos/b.LineWidth - 1
|
||||||
b.DisplayPos -= rLength
|
hasSpace := b.GetLineSpacing(line)
|
||||||
|
if hasSpace {
|
||||||
|
b.displayPos -= 1
|
||||||
|
fmt.Print(CursorLeft)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Print(CursorLeftN(rLength))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
b.pos -= 1
|
||||||
|
b.displayPos -= rLength
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Buffer) MoveLeftWord() {
|
func (b *Buffer) MoveLeftWord() {
|
||||||
if b.Pos > 0 {
|
var foundNonspace bool
|
||||||
var foundNonspace bool
|
for b.pos > 0 {
|
||||||
for {
|
v, _ := b.line.Get(b.pos - 1)
|
||||||
v, _ := b.Buf.Get(b.Pos - 1)
|
if v == ' ' {
|
||||||
if v == ' ' {
|
if foundNonspace {
|
||||||
if foundNonspace {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
foundNonspace = true
|
|
||||||
}
|
|
||||||
b.MoveLeft()
|
|
||||||
|
|
||||||
if b.Pos == 0 {
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
foundNonspace = true
|
||||||
}
|
}
|
||||||
|
b.MoveLeft()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Buffer) MoveRight() {
|
func (b *Buffer) MoveRight() {
|
||||||
if b.Pos < b.Buf.Size() {
|
if b.pos < b.line.Size() {
|
||||||
if r, ok := b.Buf.Get(b.Pos); ok {
|
r, _ := b.line.Get(b.pos)
|
||||||
rLength := runewidth.RuneWidth(r)
|
rLength := runewidth.RuneWidth(r)
|
||||||
b.Pos += 1
|
b.pos += 1
|
||||||
hasSpace := b.GetLineSpacing(b.DisplayPos / b.LineWidth)
|
hasSpace := b.GetLineSpacing(b.displayPos / b.LineWidth)
|
||||||
b.DisplayPos += rLength
|
b.displayPos += rLength
|
||||||
|
|
||||||
if b.DisplayPos%b.LineWidth == 0 {
|
if b.displayPos%b.LineWidth == 0 {
|
||||||
fmt.Print(CursorDown + CursorBOL + CursorRightN(len(b.Prompt.prompt())))
|
fmt.Print(CursorDown + CursorBOL + CursorRightN(len(b.Prompt.prompt())))
|
||||||
} else if (b.DisplayPos-rLength)%b.LineWidth == b.LineWidth-1 && hasSpace {
|
} else if (b.displayPos-rLength)%b.LineWidth == b.LineWidth-1 && hasSpace {
|
||||||
fmt.Print(CursorDown + CursorBOL + CursorRightN(len(b.Prompt.prompt())+rLength))
|
fmt.Print(CursorDown + CursorBOL + CursorRightN(len(b.Prompt.prompt())+rLength))
|
||||||
b.DisplayPos += 1
|
b.displayPos += 1
|
||||||
} else if b.LineHasSpace.Size() > 0 && b.DisplayPos%b.LineWidth == b.LineWidth-1 && hasSpace {
|
} else if b.spaceMask.Size() > 0 && b.displayPos%b.LineWidth == b.LineWidth-1 && hasSpace {
|
||||||
fmt.Print(CursorDown + CursorBOL + CursorRightN(len(b.Prompt.prompt())))
|
fmt.Print(CursorDown + CursorBOL + CursorRightN(len(b.Prompt.prompt())))
|
||||||
b.DisplayPos += 1
|
b.displayPos += 1
|
||||||
} else {
|
} else {
|
||||||
fmt.Print(CursorRightN(rLength))
|
fmt.Print(CursorRightN(rLength))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Buffer) MoveRightWord() {
|
func (b *Buffer) MoveRightWord() {
|
||||||
if b.Pos < b.Buf.Size() {
|
for b.pos < b.line.Size() {
|
||||||
for {
|
b.MoveRight()
|
||||||
b.MoveRight()
|
v, _ := b.line.Get(b.pos)
|
||||||
v, _ := b.Buf.Get(b.Pos)
|
if v == ' ' {
|
||||||
if v == ' ' {
|
break
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.Pos == b.Buf.Size() {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Buffer) MoveToStart() {
|
func (b *Buffer) MoveToStart() {
|
||||||
if b.Pos > 0 {
|
if b.pos > 0 {
|
||||||
currLine := b.DisplayPos / b.LineWidth
|
currLine := b.displayPos / b.LineWidth
|
||||||
if currLine > 0 {
|
if currLine > 0 {
|
||||||
for range currLine {
|
for range currLine {
|
||||||
fmt.Print(CursorUp)
|
fmt.Print(CursorUp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fmt.Print(CursorBOL + CursorRightN(len(b.Prompt.prompt())))
|
fmt.Print(CursorBOL + CursorRightN(len(b.Prompt.prompt())))
|
||||||
b.Pos = 0
|
b.pos = 0
|
||||||
b.DisplayPos = 0
|
b.displayPos = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Buffer) MoveToEnd() {
|
func (b *Buffer) MoveToEnd() {
|
||||||
if b.Pos < b.Buf.Size() {
|
if b.pos < b.line.Size() {
|
||||||
currLine := b.DisplayPos / b.LineWidth
|
currLine := b.displayPos / b.LineWidth
|
||||||
totalLines := b.DisplaySize() / b.LineWidth
|
totalLines := b.DisplaySize() / b.LineWidth
|
||||||
if currLine < totalLines {
|
if currLine < totalLines {
|
||||||
for range totalLines - currLine {
|
for range totalLines - currLine {
|
||||||
@ -162,18 +145,18 @@ func (b *Buffer) MoveToEnd() {
|
|||||||
remainder := b.DisplaySize() % b.LineWidth
|
remainder := b.DisplaySize() % b.LineWidth
|
||||||
fmt.Print(CursorBOL + CursorRightN(len(b.Prompt.prompt())+remainder))
|
fmt.Print(CursorBOL + CursorRightN(len(b.Prompt.prompt())+remainder))
|
||||||
} else {
|
} else {
|
||||||
fmt.Print(CursorRightN(b.DisplaySize() - b.DisplayPos))
|
fmt.Print(CursorRightN(b.DisplaySize() - b.displayPos))
|
||||||
}
|
}
|
||||||
|
|
||||||
b.Pos = b.Buf.Size()
|
b.pos = b.line.Size()
|
||||||
b.DisplayPos = b.DisplaySize()
|
b.displayPos = b.DisplaySize()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Buffer) DisplaySize() int {
|
func (b *Buffer) DisplaySize() int {
|
||||||
sum := 0
|
sum := 0
|
||||||
for i := range b.Buf.Size() {
|
for i := range b.line.Size() {
|
||||||
if r, ok := b.Buf.Get(i); ok {
|
if r, ok := b.line.Get(i); ok {
|
||||||
sum += runewidth.RuneWidth(r)
|
sum += runewidth.RuneWidth(r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -182,7 +165,7 @@ func (b *Buffer) DisplaySize() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Buffer) Add(r rune) {
|
func (b *Buffer) Add(r rune) {
|
||||||
if b.Pos == b.Buf.Size() {
|
if b.pos == b.line.Size() {
|
||||||
b.AddChar(r, false)
|
b.AddChar(r, false)
|
||||||
} else {
|
} else {
|
||||||
b.AddChar(r, true)
|
b.AddChar(r, true)
|
||||||
@ -191,32 +174,32 @@ func (b *Buffer) Add(r rune) {
|
|||||||
|
|
||||||
func (b *Buffer) AddChar(r rune, insert bool) {
|
func (b *Buffer) AddChar(r rune, insert bool) {
|
||||||
rLength := runewidth.RuneWidth(r)
|
rLength := runewidth.RuneWidth(r)
|
||||||
b.DisplayPos += rLength
|
b.displayPos += rLength
|
||||||
|
|
||||||
if b.Pos > 0 {
|
if b.pos > 0 {
|
||||||
if b.DisplayPos%b.LineWidth == 0 {
|
if b.displayPos%b.LineWidth == 0 {
|
||||||
fmt.Printf("%c", r)
|
fmt.Printf("%c", r)
|
||||||
fmt.Printf("\n%s", b.Prompt.AltPrompt)
|
fmt.Printf("\n%s", b.Prompt.AltPrompt)
|
||||||
|
|
||||||
if insert {
|
if insert {
|
||||||
b.LineHasSpace.Set(b.DisplayPos/b.LineWidth-1, false)
|
b.spaceMask.Set(b.displayPos/b.LineWidth-1, false)
|
||||||
} else {
|
} else {
|
||||||
b.LineHasSpace.Add(false)
|
b.spaceMask.Add(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// this case occurs when a double-width rune crosses the line boundary
|
// this case occurs when a double-width rune crosses the line boundary
|
||||||
} else if b.DisplayPos%b.LineWidth < (b.DisplayPos-rLength)%b.LineWidth {
|
} else if b.displayPos%b.LineWidth < (b.displayPos-rLength)%b.LineWidth {
|
||||||
if insert {
|
if insert {
|
||||||
fmt.Print(ClearToEOL)
|
fmt.Print(ClearToEOL)
|
||||||
}
|
}
|
||||||
fmt.Printf("\n%s", b.Prompt.AltPrompt)
|
fmt.Printf("\n%s", b.Prompt.AltPrompt)
|
||||||
b.DisplayPos += 1
|
b.displayPos += 1
|
||||||
fmt.Printf("%c", r)
|
fmt.Printf("%c", r)
|
||||||
|
|
||||||
if insert {
|
if insert {
|
||||||
b.LineHasSpace.Set(b.DisplayPos/b.LineWidth-1, true)
|
b.spaceMask.Set(b.displayPos/b.LineWidth-1, true)
|
||||||
} else {
|
} else {
|
||||||
b.LineHasSpace.Add(true)
|
b.spaceMask.Add(true)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("%c", r)
|
fmt.Printf("%c", r)
|
||||||
@ -226,12 +209,12 @@ func (b *Buffer) AddChar(r rune, insert bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if insert {
|
if insert {
|
||||||
b.Buf.Insert(b.Pos, r)
|
b.line.Insert(b.pos, r)
|
||||||
} else {
|
} else {
|
||||||
b.Buf.Add(r)
|
b.line.Add(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
b.Pos += 1
|
b.pos += 1
|
||||||
|
|
||||||
if insert {
|
if insert {
|
||||||
b.drawRemaining()
|
b.drawRemaining()
|
||||||
@ -246,7 +229,7 @@ func (b *Buffer) countRemainingLineWidth(place int) int {
|
|||||||
for place <= b.LineWidth {
|
for place <= b.LineWidth {
|
||||||
counter += 1
|
counter += 1
|
||||||
sum += prevLen
|
sum += prevLen
|
||||||
if r, ok := b.Buf.Get(b.Pos + counter); ok {
|
if r, ok := b.line.Get(b.pos + counter); ok {
|
||||||
place += runewidth.RuneWidth(r)
|
place += runewidth.RuneWidth(r)
|
||||||
prevLen = len(string(r))
|
prevLen = len(string(r))
|
||||||
} else {
|
} else {
|
||||||
@ -259,9 +242,9 @@ func (b *Buffer) countRemainingLineWidth(place int) int {
|
|||||||
|
|
||||||
func (b *Buffer) drawRemaining() {
|
func (b *Buffer) drawRemaining() {
|
||||||
var place int
|
var place int
|
||||||
remainingText := b.StringN(b.Pos)
|
remainingText := b.StringN(b.pos)
|
||||||
if b.Pos > 0 {
|
if b.pos > 0 {
|
||||||
place = b.DisplayPos % b.LineWidth
|
place = b.displayPos % b.LineWidth
|
||||||
}
|
}
|
||||||
fmt.Print(CursorHide)
|
fmt.Print(CursorHide)
|
||||||
|
|
||||||
@ -279,14 +262,14 @@ func (b *Buffer) drawRemaining() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if currLineSpace != b.LineWidth-place && currLineSpace != remLength {
|
if currLineSpace != b.LineWidth-place && currLineSpace != remLength {
|
||||||
b.LineHasSpace.Set(b.DisplayPos/b.LineWidth, true)
|
b.spaceMask.Set(b.displayPos/b.LineWidth, true)
|
||||||
} else if currLineSpace != b.LineWidth-place {
|
} else if currLineSpace != b.LineWidth-place {
|
||||||
b.LineHasSpace.Remove(b.DisplayPos / b.LineWidth)
|
b.spaceMask.Remove(b.displayPos / b.LineWidth)
|
||||||
} else {
|
} else {
|
||||||
b.LineHasSpace.Set(b.DisplayPos/b.LineWidth, false)
|
b.spaceMask.Set(b.displayPos/b.LineWidth, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (b.DisplayPos+currLineSpace)%b.LineWidth == 0 && currLine == remainingText {
|
if (b.displayPos+currLineSpace)%b.LineWidth == 0 && currLine == remainingText {
|
||||||
fmt.Print(CursorRightN(currLineSpace))
|
fmt.Print(CursorRightN(currLineSpace))
|
||||||
fmt.Printf("\n%s", b.Prompt.AltPrompt)
|
fmt.Printf("\n%s", b.Prompt.AltPrompt)
|
||||||
fmt.Print(CursorUp + CursorBOL + CursorRightN(b.Width-currLineSpace))
|
fmt.Print(CursorUp + CursorBOL + CursorRightN(b.Width-currLineSpace))
|
||||||
@ -306,9 +289,9 @@ func (b *Buffer) drawRemaining() {
|
|||||||
|
|
||||||
if displayLength != 0 {
|
if displayLength != 0 {
|
||||||
if lineLength == b.LineWidth {
|
if lineLength == b.LineWidth {
|
||||||
b.LineHasSpace.Set(b.DisplayPos/b.LineWidth+totalLines-1, false)
|
b.spaceMask.Set(b.displayPos/b.LineWidth+totalLines-1, false)
|
||||||
} else {
|
} else {
|
||||||
b.LineHasSpace.Set(b.DisplayPos/b.LineWidth+totalLines-1, true)
|
b.spaceMask.Set(b.displayPos/b.LineWidth+totalLines-1, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -321,9 +304,9 @@ func (b *Buffer) drawRemaining() {
|
|||||||
}
|
}
|
||||||
fmt.Print(ClearToEOL + CursorUpN(totalLines) + CursorBOL + CursorRightN(b.Width-currLineSpace))
|
fmt.Print(ClearToEOL + CursorUpN(totalLines) + CursorBOL + CursorRightN(b.Width-currLineSpace))
|
||||||
|
|
||||||
hasSpace := b.GetLineSpacing(b.DisplayPos / b.LineWidth)
|
hasSpace := b.GetLineSpacing(b.displayPos / b.LineWidth)
|
||||||
|
|
||||||
if hasSpace && b.DisplayPos%b.LineWidth != b.LineWidth-1 {
|
if hasSpace && b.displayPos%b.LineWidth != b.LineWidth-1 {
|
||||||
fmt.Print(CursorLeft)
|
fmt.Print(CursorLeft)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -332,22 +315,22 @@ func (b *Buffer) drawRemaining() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Buffer) Remove() {
|
func (b *Buffer) Remove() {
|
||||||
if b.Buf.Size() > 0 && b.Pos > 0 {
|
if b.line.Size() > 0 && b.pos > 0 {
|
||||||
if r, ok := b.Buf.Get(b.Pos - 1); ok {
|
if r, ok := b.line.Get(b.pos - 1); ok {
|
||||||
rLength := runewidth.RuneWidth(r)
|
rLength := runewidth.RuneWidth(r)
|
||||||
hasSpace := b.GetLineSpacing(b.DisplayPos/b.LineWidth - 1)
|
hasSpace := b.GetLineSpacing(b.displayPos/b.LineWidth - 1)
|
||||||
|
|
||||||
if b.DisplayPos%b.LineWidth == 0 {
|
if b.displayPos%b.LineWidth == 0 {
|
||||||
// if the user backspaces over the word boundary, do this magic to clear the line
|
// if the user backspaces over the word boundary, do this magic to clear the line
|
||||||
// and move to the end of the previous line
|
// and move to the end of the previous line
|
||||||
fmt.Print(CursorBOL + ClearToEOL + CursorUp + CursorBOL + CursorRightN(b.Width))
|
fmt.Print(CursorBOL + ClearToEOL + CursorUp + CursorBOL + CursorRightN(b.Width))
|
||||||
|
|
||||||
if b.DisplaySize()%b.LineWidth < (b.DisplaySize()-rLength)%b.LineWidth {
|
if b.DisplaySize()%b.LineWidth < (b.DisplaySize()-rLength)%b.LineWidth {
|
||||||
b.LineHasSpace.Remove(b.DisplayPos/b.LineWidth - 1)
|
b.spaceMask.Remove(b.displayPos/b.LineWidth - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if hasSpace {
|
if hasSpace {
|
||||||
b.DisplayPos -= 1
|
b.displayPos -= 1
|
||||||
fmt.Print(CursorLeft)
|
fmt.Print(CursorLeft)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -356,13 +339,13 @@ func (b *Buffer) Remove() {
|
|||||||
} else {
|
} else {
|
||||||
fmt.Print(" " + CursorLeft)
|
fmt.Print(" " + CursorLeft)
|
||||||
}
|
}
|
||||||
} else if (b.DisplayPos-rLength)%b.LineWidth == 0 && hasSpace {
|
} else if (b.displayPos-rLength)%b.LineWidth == 0 && hasSpace {
|
||||||
fmt.Print(CursorBOL + ClearToEOL + CursorUp + CursorBOL + CursorRightN(b.Width))
|
fmt.Print(CursorBOL + ClearToEOL + CursorUp + CursorBOL + CursorRightN(b.Width))
|
||||||
|
|
||||||
if b.Pos == b.Buf.Size() {
|
if b.pos == b.line.Size() {
|
||||||
b.LineHasSpace.Remove(b.DisplayPos/b.LineWidth - 1)
|
b.spaceMask.Remove(b.displayPos/b.LineWidth - 1)
|
||||||
}
|
}
|
||||||
b.DisplayPos -= 1
|
b.displayPos -= 1
|
||||||
} else {
|
} else {
|
||||||
fmt.Print(CursorLeftN(rLength))
|
fmt.Print(CursorLeftN(rLength))
|
||||||
for range rLength {
|
for range rLength {
|
||||||
@ -376,18 +359,18 @@ func (b *Buffer) Remove() {
|
|||||||
eraseExtraLine = true
|
eraseExtraLine = true
|
||||||
}
|
}
|
||||||
|
|
||||||
b.Pos -= 1
|
b.pos -= 1
|
||||||
b.DisplayPos -= rLength
|
b.displayPos -= rLength
|
||||||
b.Buf.Remove(b.Pos)
|
b.line.Remove(b.pos)
|
||||||
|
|
||||||
if b.Pos < b.Buf.Size() {
|
if b.pos < b.line.Size() {
|
||||||
b.drawRemaining()
|
b.drawRemaining()
|
||||||
// this erases a line which is left over when backspacing in the middle of a line and there
|
// 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
|
// are trailing characters which go over the line width boundary
|
||||||
if eraseExtraLine {
|
if eraseExtraLine {
|
||||||
remainingLines := (b.DisplaySize() - b.DisplayPos) / b.LineWidth
|
remainingLines := (b.DisplaySize() - b.displayPos) / b.LineWidth
|
||||||
fmt.Print(CursorDownN(remainingLines+1) + CursorBOL + ClearToEOL)
|
fmt.Print(CursorDownN(remainingLines+1) + CursorBOL + ClearToEOL)
|
||||||
place := b.DisplayPos % b.LineWidth
|
place := b.displayPos % b.LineWidth
|
||||||
fmt.Print(CursorUpN(remainingLines+1) + CursorRightN(place+len(b.Prompt.prompt())))
|
fmt.Print(CursorUpN(remainingLines+1) + CursorRightN(place+len(b.Prompt.prompt())))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -396,14 +379,14 @@ func (b *Buffer) Remove() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Buffer) Delete() {
|
func (b *Buffer) Delete() {
|
||||||
if b.Buf.Size() > 0 && b.Pos < b.Buf.Size() {
|
if b.line.Size() > 0 && b.pos < b.line.Size() {
|
||||||
b.Buf.Remove(b.Pos)
|
b.line.Remove(b.pos)
|
||||||
b.drawRemaining()
|
b.drawRemaining()
|
||||||
if b.DisplaySize()%b.LineWidth == 0 {
|
if b.DisplaySize()%b.LineWidth == 0 {
|
||||||
if b.DisplayPos != b.DisplaySize() {
|
if b.displayPos != b.DisplaySize() {
|
||||||
remainingLines := (b.DisplaySize() - b.DisplayPos) / b.LineWidth
|
remainingLines := (b.DisplaySize() - b.displayPos) / b.LineWidth
|
||||||
fmt.Print(CursorDownN(remainingLines) + CursorBOL + ClearToEOL)
|
fmt.Print(CursorDownN(remainingLines) + CursorBOL + ClearToEOL)
|
||||||
place := b.DisplayPos % b.LineWidth
|
place := b.displayPos % b.LineWidth
|
||||||
fmt.Print(CursorUpN(remainingLines) + CursorRightN(place+len(b.Prompt.prompt())))
|
fmt.Print(CursorUpN(remainingLines) + CursorRightN(place+len(b.Prompt.prompt())))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -411,16 +394,16 @@ func (b *Buffer) Delete() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Buffer) DeleteBefore() {
|
func (b *Buffer) DeleteBefore() {
|
||||||
if b.Pos > 0 {
|
if b.pos > 0 {
|
||||||
for cnt := b.Pos - 1; cnt >= 0; cnt-- {
|
for cnt := b.pos - 1; cnt >= 0; cnt-- {
|
||||||
b.Remove()
|
b.Remove()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Buffer) DeleteRemaining() {
|
func (b *Buffer) DeleteRemaining() {
|
||||||
if b.DisplaySize() > 0 && b.Pos < b.DisplaySize() {
|
if b.DisplaySize() > 0 && b.pos < b.DisplaySize() {
|
||||||
charsToDel := b.Buf.Size() - b.Pos
|
charsToDel := b.line.Size() - b.pos
|
||||||
for range charsToDel {
|
for range charsToDel {
|
||||||
b.Delete()
|
b.Delete()
|
||||||
}
|
}
|
||||||
@ -428,10 +411,10 @@ func (b *Buffer) DeleteRemaining() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Buffer) DeleteWord() {
|
func (b *Buffer) DeleteWord() {
|
||||||
if b.Buf.Size() > 0 && b.Pos > 0 {
|
if b.line.Size() > 0 {
|
||||||
var foundNonspace bool
|
var foundNonspace bool
|
||||||
for {
|
for b.pos > 0 {
|
||||||
v, _ := b.Buf.Get(b.Pos - 1)
|
v, _ := b.line.Get(b.pos - 1)
|
||||||
if v == ' ' {
|
if v == ' ' {
|
||||||
if !foundNonspace {
|
if !foundNonspace {
|
||||||
b.Remove()
|
b.Remove()
|
||||||
@ -442,10 +425,6 @@ func (b *Buffer) DeleteWord() {
|
|||||||
foundNonspace = true
|
foundNonspace = true
|
||||||
b.Remove()
|
b.Remove()
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.Pos == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -456,10 +435,10 @@ func (b *Buffer) ClearScreen() {
|
|||||||
ph := b.Prompt.placeholder()
|
ph := b.Prompt.placeholder()
|
||||||
fmt.Print(ColorGrey + ph + CursorLeftN(len(ph)) + ColorDefault)
|
fmt.Print(ColorGrey + ph + CursorLeftN(len(ph)) + ColorDefault)
|
||||||
} else {
|
} else {
|
||||||
currPos := b.DisplayPos
|
currPos := b.displayPos
|
||||||
currIndex := b.Pos
|
currIndex := b.pos
|
||||||
b.Pos = 0
|
b.pos = 0
|
||||||
b.DisplayPos = 0
|
b.displayPos = 0
|
||||||
b.drawRemaining()
|
b.drawRemaining()
|
||||||
fmt.Print(CursorReset + CursorRightN(len(b.Prompt.prompt())))
|
fmt.Print(CursorReset + CursorRightN(len(b.Prompt.prompt())))
|
||||||
if currPos > 0 {
|
if currPos > 0 {
|
||||||
@ -477,21 +456,21 @@ func (b *Buffer) ClearScreen() {
|
|||||||
fmt.Print(CursorBOL + b.Prompt.AltPrompt)
|
fmt.Print(CursorBOL + b.Prompt.AltPrompt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
b.Pos = currIndex
|
b.pos = currIndex
|
||||||
b.DisplayPos = currPos
|
b.displayPos = currPos
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Buffer) IsEmpty() bool {
|
func (b *Buffer) IsEmpty() bool {
|
||||||
return b.Buf.Empty()
|
return b.line.Empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Buffer) Replace(r []rune) {
|
func (b *Buffer) Replace(r []rune) {
|
||||||
b.DisplayPos = 0
|
b.displayPos = 0
|
||||||
b.Pos = 0
|
b.pos = 0
|
||||||
lineNums := b.DisplaySize() / b.LineWidth
|
lineNums := b.DisplaySize() / b.LineWidth
|
||||||
|
|
||||||
b.Buf.Clear()
|
b.line.Clear()
|
||||||
|
|
||||||
fmt.Print(CursorBOL + ClearToEOL)
|
fmt.Print(CursorBOL + ClearToEOL)
|
||||||
|
|
||||||
@ -517,10 +496,10 @@ func (b *Buffer) StringN(n int) string {
|
|||||||
func (b *Buffer) StringNM(n, m int) string {
|
func (b *Buffer) StringNM(n, m int) string {
|
||||||
var s string
|
var s string
|
||||||
if m == 0 {
|
if m == 0 {
|
||||||
m = b.Buf.Size()
|
m = b.line.Size()
|
||||||
}
|
}
|
||||||
for cnt := n; cnt < m; cnt++ {
|
for cnt := n; cnt < m; cnt++ {
|
||||||
c, _ := b.Buf.Get(cnt)
|
c, _ := b.line.Get(cnt)
|
||||||
s += string(c)
|
s += string(c)
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
|
@ -2,9 +2,7 @@ package readline
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
@ -13,113 +11,91 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type History struct {
|
type History struct {
|
||||||
Buf *arraylist.List[string]
|
Enabled bool
|
||||||
Autosave bool
|
|
||||||
Pos int
|
lines *arraylist.List[string]
|
||||||
Limit int
|
limit int
|
||||||
Filename string
|
pos int
|
||||||
Enabled bool
|
filename string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHistory() (*History, error) {
|
func NewHistory() (*History, error) {
|
||||||
h := &History{
|
h := &History{
|
||||||
Buf: arraylist.New[string](),
|
Enabled: true,
|
||||||
Limit: 100, // resizeme
|
lines: arraylist.New[string](),
|
||||||
Autosave: true,
|
limit: 100, // resizeme
|
||||||
Enabled: true,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err := h.Init()
|
home, err := os.UserHomeDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
path := filepath.Join(home, ".ollama", "history")
|
||||||
|
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
h.filename = path
|
||||||
|
|
||||||
|
f, err := os.OpenFile(path, os.O_CREATE|os.O_RDONLY, 0o600)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(f)
|
||||||
|
for scanner.Scan() {
|
||||||
|
if line := strings.TrimSpace(scanner.Text()); len(line) > 0 {
|
||||||
|
h.Add(line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return h, nil
|
return h, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *History) Init() error {
|
|
||||||
home, err := os.UserHomeDir()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
path := filepath.Join(home, ".ollama", "history")
|
|
||||||
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
h.Filename = path
|
|
||||||
|
|
||||||
f, err := os.OpenFile(path, os.O_CREATE|os.O_RDONLY, 0o600)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
r := bufio.NewReader(f)
|
|
||||||
for {
|
|
||||||
line, err := r.ReadString('\n')
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, io.EOF) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
line = strings.TrimSpace(line)
|
|
||||||
if len(line) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
h.Add(line)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *History) Add(s string) {
|
func (h *History) Add(s string) {
|
||||||
h.Buf.Add(s)
|
if latest, _ := h.lines.Get(h.Size() - 1); latest != s {
|
||||||
h.Compact()
|
h.lines.Add(s)
|
||||||
h.Pos = h.Size()
|
h.Compact()
|
||||||
if h.Autosave {
|
|
||||||
_ = h.Save()
|
_ = h.Save()
|
||||||
}
|
}
|
||||||
|
// always set position to the end
|
||||||
|
h.pos = h.Size()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *History) Compact() {
|
func (h *History) Compact() {
|
||||||
s := h.Buf.Size()
|
if s := h.lines.Size(); s > h.limit {
|
||||||
if s > h.Limit {
|
for range s - h.limit {
|
||||||
for range s - h.Limit {
|
h.lines.Remove(0)
|
||||||
h.Buf.Remove(0)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *History) Clear() {
|
func (h *History) Clear() {
|
||||||
h.Buf.Clear()
|
h.lines.Clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *History) Prev() (line string) {
|
func (h *History) Prev() (line string) {
|
||||||
if h.Pos > 0 {
|
if h.pos > 0 {
|
||||||
h.Pos -= 1
|
h.pos -= 1
|
||||||
}
|
}
|
||||||
line, _ = h.Buf.Get(h.Pos)
|
// return first line if at the beginning
|
||||||
|
line, _ = h.lines.Get(h.pos)
|
||||||
return line
|
return line
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *History) Next() (line string) {
|
func (h *History) Next() (line string) {
|
||||||
if h.Pos < h.Buf.Size() {
|
if h.pos < h.lines.Size() {
|
||||||
h.Pos += 1
|
h.pos += 1
|
||||||
line, _ = h.Buf.Get(h.Pos)
|
line, _ = h.lines.Get(h.pos)
|
||||||
}
|
}
|
||||||
|
// return empty string if at the end
|
||||||
return line
|
return line
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *History) Size() int {
|
func (h *History) Size() int {
|
||||||
return h.Buf.Size()
|
return h.lines.Size()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *History) Save() error {
|
func (h *History) Save() error {
|
||||||
@ -127,25 +103,21 @@ func (h *History) Save() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpFile := h.Filename + ".tmp"
|
f, err := os.CreateTemp(filepath.Dir(h.filename), "")
|
||||||
|
|
||||||
f, err := os.OpenFile(tmpFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC|os.O_APPEND, 0o600)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
buf := bufio.NewWriter(f)
|
func() {
|
||||||
for cnt := range h.Size() {
|
defer f.Close()
|
||||||
line, _ := h.Buf.Get(cnt)
|
|
||||||
fmt.Fprintln(buf, line)
|
|
||||||
}
|
|
||||||
buf.Flush()
|
|
||||||
f.Close()
|
|
||||||
|
|
||||||
if err = os.Rename(tmpFile, h.Filename); err != nil {
|
w := bufio.NewWriter(f)
|
||||||
return err
|
defer w.Flush()
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
h.lines.Each(func(i int, line string) {
|
||||||
|
fmt.Fprintln(w, line)
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
|
||||||
|
return os.Rename(f.Name(), h.filename)
|
||||||
}
|
}
|
||||||
|
@ -91,8 +91,6 @@ func (i *Instance) Readline() (string, error) {
|
|||||||
var escex bool
|
var escex bool
|
||||||
var metaDel bool
|
var metaDel bool
|
||||||
|
|
||||||
var currentLineBuf []rune
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
// don't show placeholder when pasting unless we're in multiline mode
|
// don't show placeholder when pasting unless we're in multiline mode
|
||||||
showPlaceholder := !i.Pasting || i.Prompt.UseAlt
|
showPlaceholder := !i.Pasting || i.Prompt.UseAlt
|
||||||
@ -116,19 +114,9 @@ func (i *Instance) Readline() (string, error) {
|
|||||||
|
|
||||||
switch r {
|
switch r {
|
||||||
case KeyUp:
|
case KeyUp:
|
||||||
if i.History.Pos > 0 {
|
buf.Replace([]rune(i.History.Prev()))
|
||||||
if i.History.Pos == i.History.Size() {
|
|
||||||
currentLineBuf = []rune(buf.String())
|
|
||||||
}
|
|
||||||
buf.Replace([]rune(i.History.Prev()))
|
|
||||||
}
|
|
||||||
case KeyDown:
|
case KeyDown:
|
||||||
if i.History.Pos < i.History.Size() {
|
buf.Replace([]rune(i.History.Next()))
|
||||||
buf.Replace([]rune(i.History.Next()))
|
|
||||||
if i.History.Pos == i.History.Size() {
|
|
||||||
buf.Replace(currentLineBuf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case KeyLeft:
|
case KeyLeft:
|
||||||
buf.MoveLeft()
|
buf.MoveLeft()
|
||||||
case KeyRight:
|
case KeyRight:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user