Compare commits

...

2 Commits

Author SHA1 Message Date
Michael Yang
a1d90e68d0 chore: clean up readline package 2024-12-21 01:12:17 -08:00
Michael Yang
7ccdc98a5f fix: only add to history of different
if the last item in history is the same as the one being added, skip it.
this reduces the number of history entries. the behaviour is similar to
how most shells maintain history
2024-12-21 00:08:14 -08:00
3 changed files with 204 additions and 265 deletions

View File

@ -10,15 +10,15 @@ import (
)
type Buffer struct {
DisplayPos int
Pos int
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[bool]
Prompt *Prompt
LineWidth int
Width int
Height int
Prompt *Prompt
LineWidth int
Width int
Height int
line *arraylist.List[rune]
spaceMask *arraylist.List[bool]
pos int
displayPos int
}
func NewBuffer(prompt *Prompt) (*Buffer, error) {
@ -30,130 +30,113 @@ func NewBuffer(prompt *Prompt) (*Buffer, error) {
lwidth := width - len(prompt.prompt())
b := &Buffer{
DisplayPos: 0,
Pos: 0,
Buf: arraylist.New[rune](),
LineHasSpace: arraylist.New[bool](),
Prompt: prompt,
Width: width,
Height: height,
LineWidth: lwidth,
}
return b, nil
return &Buffer{
displayPos: 0,
pos: 0,
line: arraylist.New[rune](),
spaceMask: arraylist.New[bool](),
Prompt: prompt,
Width: width,
Height: height,
LineWidth: lwidth,
}, nil
}
func (b *Buffer) GetLineSpacing(line int) bool {
hasSpace, _ := b.LineHasSpace.Get(line)
hasSpace, _ := b.spaceMask.Get(line)
return hasSpace
}
func (b *Buffer) MoveLeft() {
if b.Pos > 0 {
// asserts that we retrieve a rune
if r, ok := b.Buf.Get(b.Pos - 1); ok {
rLength := runewidth.RuneWidth(r)
if b.pos > 0 {
r, _ := b.line.Get(b.pos - 1)
rLength := runewidth.RuneWidth(r)
if b.DisplayPos%b.LineWidth == 0 {
fmt.Print(CursorUp + CursorBOL + CursorRightN(b.Width))
if rLength == 2 {
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))
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
}
}
func (b *Buffer) MoveLeftWord() {
if b.Pos > 0 {
var foundNonspace bool
for {
v, _ := b.Buf.Get(b.Pos - 1)
if v == ' ' {
if foundNonspace {
break
}
} else {
foundNonspace = true
}
b.MoveLeft()
if b.Pos == 0 {
var foundNonspace bool
for b.pos > 0 {
v, _ := b.line.Get(b.pos - 1)
if v == ' ' {
if foundNonspace {
break
}
} else {
foundNonspace = true
}
b.MoveLeft()
}
}
func (b *Buffer) MoveRight() {
if b.Pos < b.Buf.Size() {
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.pos < b.line.Size() {
r, _ := b.line.Get(b.pos)
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 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.spaceMask.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))
}
}
}
func (b *Buffer) MoveRightWord() {
if b.Pos < b.Buf.Size() {
for {
b.MoveRight()
v, _ := b.Buf.Get(b.Pos)
if v == ' ' {
break
}
if b.Pos == b.Buf.Size() {
break
}
for b.pos < b.line.Size() {
b.MoveRight()
v, _ := b.line.Get(b.pos)
if v == ' ' {
break
}
}
}
func (b *Buffer) MoveToStart() {
if b.Pos > 0 {
currLine := b.DisplayPos / b.LineWidth
if b.pos > 0 {
currLine := b.displayPos / b.LineWidth
if currLine > 0 {
for range currLine {
fmt.Print(CursorUp)
}
}
fmt.Print(CursorBOL + CursorRightN(len(b.Prompt.prompt())))
b.Pos = 0
b.DisplayPos = 0
b.pos = 0
b.displayPos = 0
}
}
func (b *Buffer) MoveToEnd() {
if b.Pos < b.Buf.Size() {
currLine := b.DisplayPos / b.LineWidth
if b.pos < b.line.Size() {
currLine := b.displayPos / b.LineWidth
totalLines := b.DisplaySize() / b.LineWidth
if currLine < totalLines {
for range totalLines - currLine {
@ -162,18 +145,18 @@ func (b *Buffer) MoveToEnd() {
remainder := b.DisplaySize() % b.LineWidth
fmt.Print(CursorBOL + CursorRightN(len(b.Prompt.prompt())+remainder))
} else {
fmt.Print(CursorRightN(b.DisplaySize() - b.DisplayPos))
fmt.Print(CursorRightN(b.DisplaySize() - b.displayPos))
}
b.Pos = b.Buf.Size()
b.DisplayPos = b.DisplaySize()
b.pos = b.line.Size()
b.displayPos = b.DisplaySize()
}
}
func (b *Buffer) DisplaySize() int {
sum := 0
for i := range b.Buf.Size() {
if r, ok := b.Buf.Get(i); ok {
for i := range b.line.Size() {
if r, ok := b.line.Get(i); ok {
sum += runewidth.RuneWidth(r)
}
}
@ -182,7 +165,7 @@ func (b *Buffer) DisplaySize() int {
}
func (b *Buffer) Add(r rune) {
if b.Pos == b.Buf.Size() {
if b.pos == b.line.Size() {
b.AddChar(r, false)
} else {
b.AddChar(r, true)
@ -191,32 +174,32 @@ func (b *Buffer) Add(r rune) {
func (b *Buffer) AddChar(r rune, insert bool) {
rLength := runewidth.RuneWidth(r)
b.DisplayPos += rLength
b.displayPos += rLength
if b.Pos > 0 {
if b.DisplayPos%b.LineWidth == 0 {
if b.pos > 0 {
if b.displayPos%b.LineWidth == 0 {
fmt.Printf("%c", r)
fmt.Printf("\n%s", b.Prompt.AltPrompt)
if insert {
b.LineHasSpace.Set(b.DisplayPos/b.LineWidth-1, false)
b.spaceMask.Set(b.displayPos/b.LineWidth-1, false)
} else {
b.LineHasSpace.Add(false)
b.spaceMask.Add(false)
}
// 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 {
fmt.Print(ClearToEOL)
}
fmt.Printf("\n%s", b.Prompt.AltPrompt)
b.DisplayPos += 1
b.displayPos += 1
fmt.Printf("%c", r)
if insert {
b.LineHasSpace.Set(b.DisplayPos/b.LineWidth-1, true)
b.spaceMask.Set(b.displayPos/b.LineWidth-1, true)
} else {
b.LineHasSpace.Add(true)
b.spaceMask.Add(true)
}
} else {
fmt.Printf("%c", r)
@ -226,12 +209,12 @@ func (b *Buffer) AddChar(r rune, insert bool) {
}
if insert {
b.Buf.Insert(b.Pos, r)
b.line.Insert(b.pos, r)
} else {
b.Buf.Add(r)
b.line.Add(r)
}
b.Pos += 1
b.pos += 1
if insert {
b.drawRemaining()
@ -246,7 +229,7 @@ func (b *Buffer) countRemainingLineWidth(place int) int {
for place <= b.LineWidth {
counter += 1
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)
prevLen = len(string(r))
} else {
@ -259,9 +242,9 @@ func (b *Buffer) countRemainingLineWidth(place int) int {
func (b *Buffer) drawRemaining() {
var place int
remainingText := b.StringN(b.Pos)
if b.Pos > 0 {
place = b.DisplayPos % b.LineWidth
remainingText := b.StringN(b.pos)
if b.pos > 0 {
place = b.displayPos % b.LineWidth
}
fmt.Print(CursorHide)
@ -279,14 +262,14 @@ func (b *Buffer) drawRemaining() {
}
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 {
b.LineHasSpace.Remove(b.DisplayPos / b.LineWidth)
b.spaceMask.Remove(b.displayPos / b.LineWidth)
} 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.Printf("\n%s", b.Prompt.AltPrompt)
fmt.Print(CursorUp + CursorBOL + CursorRightN(b.Width-currLineSpace))
@ -306,9 +289,9 @@ func (b *Buffer) drawRemaining() {
if displayLength != 0 {
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 {
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))
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)
}
}
@ -332,22 +315,22 @@ func (b *Buffer) drawRemaining() {
}
func (b *Buffer) Remove() {
if b.Buf.Size() > 0 && b.Pos > 0 {
if r, ok := b.Buf.Get(b.Pos - 1); ok {
if b.line.Size() > 0 && b.pos > 0 {
if r, ok := b.line.Get(b.pos - 1); ok {
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
// 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)
b.spaceMask.Remove(b.displayPos/b.LineWidth - 1)
}
if hasSpace {
b.DisplayPos -= 1
b.displayPos -= 1
fmt.Print(CursorLeft)
}
@ -356,13 +339,13 @@ func (b *Buffer) Remove() {
} else {
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))
if b.Pos == b.Buf.Size() {
b.LineHasSpace.Remove(b.DisplayPos/b.LineWidth - 1)
if b.pos == b.line.Size() {
b.spaceMask.Remove(b.displayPos/b.LineWidth - 1)
}
b.DisplayPos -= 1
b.displayPos -= 1
} else {
fmt.Print(CursorLeftN(rLength))
for range rLength {
@ -376,18 +359,18 @@ func (b *Buffer) Remove() {
eraseExtraLine = true
}
b.Pos -= 1
b.DisplayPos -= rLength
b.Buf.Remove(b.Pos)
b.pos -= 1
b.displayPos -= rLength
b.line.Remove(b.pos)
if b.Pos < b.Buf.Size() {
if b.pos < b.line.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
remainingLines := (b.DisplaySize() - b.displayPos) / b.LineWidth
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())))
}
}
@ -396,14 +379,14 @@ func (b *Buffer) Remove() {
}
func (b *Buffer) Delete() {
if b.Buf.Size() > 0 && b.Pos < b.Buf.Size() {
b.Buf.Remove(b.Pos)
if b.line.Size() > 0 && b.pos < b.line.Size() {
b.line.Remove(b.pos)
b.drawRemaining()
if b.DisplaySize()%b.LineWidth == 0 {
if b.DisplayPos != b.DisplaySize() {
remainingLines := (b.DisplaySize() - b.DisplayPos) / b.LineWidth
if b.displayPos != b.DisplaySize() {
remainingLines := (b.DisplaySize() - b.displayPos) / b.LineWidth
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())))
}
}
@ -411,16 +394,16 @@ func (b *Buffer) Delete() {
}
func (b *Buffer) DeleteBefore() {
if b.Pos > 0 {
for cnt := b.Pos - 1; cnt >= 0; cnt-- {
if b.pos > 0 {
for cnt := b.pos - 1; cnt >= 0; cnt-- {
b.Remove()
}
}
}
func (b *Buffer) DeleteRemaining() {
if b.DisplaySize() > 0 && b.Pos < b.DisplaySize() {
charsToDel := b.Buf.Size() - b.Pos
if b.DisplaySize() > 0 && b.pos < b.DisplaySize() {
charsToDel := b.line.Size() - b.pos
for range charsToDel {
b.Delete()
}
@ -428,10 +411,10 @@ func (b *Buffer) DeleteRemaining() {
}
func (b *Buffer) DeleteWord() {
if b.Buf.Size() > 0 && b.Pos > 0 {
if b.line.Size() > 0 {
var foundNonspace bool
for {
v, _ := b.Buf.Get(b.Pos - 1)
for b.pos > 0 {
v, _ := b.line.Get(b.pos - 1)
if v == ' ' {
if !foundNonspace {
b.Remove()
@ -442,10 +425,6 @@ func (b *Buffer) DeleteWord() {
foundNonspace = true
b.Remove()
}
if b.Pos == 0 {
break
}
}
}
}
@ -456,10 +435,10 @@ func (b *Buffer) ClearScreen() {
ph := b.Prompt.placeholder()
fmt.Print(ColorGrey + ph + CursorLeftN(len(ph)) + ColorDefault)
} else {
currPos := b.DisplayPos
currIndex := b.Pos
b.Pos = 0
b.DisplayPos = 0
currPos := b.displayPos
currIndex := b.pos
b.pos = 0
b.displayPos = 0
b.drawRemaining()
fmt.Print(CursorReset + CursorRightN(len(b.Prompt.prompt())))
if currPos > 0 {
@ -477,21 +456,21 @@ func (b *Buffer) ClearScreen() {
fmt.Print(CursorBOL + b.Prompt.AltPrompt)
}
}
b.Pos = currIndex
b.DisplayPos = currPos
b.pos = currIndex
b.displayPos = currPos
}
}
func (b *Buffer) IsEmpty() bool {
return b.Buf.Empty()
return b.line.Empty()
}
func (b *Buffer) Replace(r []rune) {
b.DisplayPos = 0
b.Pos = 0
b.displayPos = 0
b.pos = 0
lineNums := b.DisplaySize() / b.LineWidth
b.Buf.Clear()
b.line.Clear()
fmt.Print(CursorBOL + ClearToEOL)
@ -517,10 +496,10 @@ func (b *Buffer) StringN(n int) string {
func (b *Buffer) StringNM(n, m int) string {
var s string
if m == 0 {
m = b.Buf.Size()
m = b.line.Size()
}
for cnt := n; cnt < m; cnt++ {
c, _ := b.Buf.Get(cnt)
c, _ := b.line.Get(cnt)
s += string(c)
}
return s

View File

@ -2,9 +2,7 @@ package readline
import (
"bufio"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"strings"
@ -13,113 +11,91 @@ import (
)
type History struct {
Buf *arraylist.List[string]
Autosave bool
Pos int
Limit int
Filename string
Enabled bool
Enabled bool
lines *arraylist.List[string]
limit int
pos int
filename string
}
func NewHistory() (*History, error) {
h := &History{
Buf: arraylist.New[string](),
Limit: 100, // resizeme
Autosave: true,
Enabled: true,
Enabled: true,
lines: arraylist.New[string](),
limit: 100, // resizeme
}
err := h.Init()
home, err := os.UserHomeDir()
if err != nil {
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
}
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) {
h.Buf.Add(s)
h.Compact()
h.Pos = h.Size()
if h.Autosave {
if latest, _ := h.lines.Get(h.Size() - 1); latest != s {
h.lines.Add(s)
h.Compact()
_ = h.Save()
}
// always set position to the end
h.pos = h.Size()
}
func (h *History) Compact() {
s := h.Buf.Size()
if s > h.Limit {
for range s - h.Limit {
h.Buf.Remove(0)
if s := h.lines.Size(); s > h.limit {
for range s - h.limit {
h.lines.Remove(0)
}
}
}
func (h *History) Clear() {
h.Buf.Clear()
h.lines.Clear()
}
func (h *History) Prev() (line string) {
if h.Pos > 0 {
h.Pos -= 1
if h.pos > 0 {
h.pos -= 1
}
line, _ = h.Buf.Get(h.Pos)
// return first line if at the beginning
line, _ = h.lines.Get(h.pos)
return line
}
func (h *History) Next() (line string) {
if h.Pos < h.Buf.Size() {
h.Pos += 1
line, _ = h.Buf.Get(h.Pos)
if h.pos < h.lines.Size() {
h.pos += 1
line, _ = h.lines.Get(h.pos)
}
// return empty string if at the end
return line
}
func (h *History) Size() int {
return h.Buf.Size()
return h.lines.Size()
}
func (h *History) Save() error {
@ -127,25 +103,21 @@ func (h *History) Save() error {
return nil
}
tmpFile := h.Filename + ".tmp"
f, err := os.OpenFile(tmpFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC|os.O_APPEND, 0o600)
f, err := os.CreateTemp(filepath.Dir(h.filename), "")
if err != nil {
return err
}
defer f.Close()
buf := bufio.NewWriter(f)
for cnt := range h.Size() {
line, _ := h.Buf.Get(cnt)
fmt.Fprintln(buf, line)
}
buf.Flush()
f.Close()
func() {
defer f.Close()
if err = os.Rename(tmpFile, h.Filename); err != nil {
return err
}
w := bufio.NewWriter(f)
defer w.Flush()
return nil
h.lines.Each(func(i int, line string) {
fmt.Fprintln(w, line)
})
}()
return os.Rename(f.Name(), h.filename)
}

View File

@ -91,8 +91,6 @@ func (i *Instance) Readline() (string, error) {
var escex bool
var metaDel bool
var currentLineBuf []rune
for {
// don't show placeholder when pasting unless we're in multiline mode
showPlaceholder := !i.Pasting || i.Prompt.UseAlt
@ -116,19 +114,9 @@ func (i *Instance) Readline() (string, error) {
switch r {
case KeyUp:
if i.History.Pos > 0 {
if i.History.Pos == i.History.Size() {
currentLineBuf = []rune(buf.String())
}
buf.Replace([]rune(i.History.Prev()))
}
buf.Replace([]rune(i.History.Prev()))
case KeyDown:
if i.History.Pos < i.History.Size() {
buf.Replace([]rune(i.History.Next()))
if i.History.Pos == i.History.Size() {
buf.Replace(currentLineBuf)
}
}
buf.Replace([]rune(i.History.Next()))
case KeyLeft:
buf.MoveLeft()
case KeyRight: