From a1d90e68d07272fd377e819e3a644952eb2903ae Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Sat, 21 Dec 2024 00:21:23 -0800 Subject: [PATCH] chore: clean up readline package --- readline/buffer.go | 307 ++++++++++++++++++++----------------------- readline/history.go | 148 +++++++++------------ readline/readline.go | 16 +-- 3 files changed, 204 insertions(+), 267 deletions(-) diff --git a/readline/buffer.go b/readline/buffer.go index 52dc70526..610150d18 100644 --- a/readline/buffer.go +++ b/readline/buffer.go @@ -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 diff --git a/readline/history.go b/readline/history.go index b3661e29b..5d1c21966 100644 --- a/readline/history.go +++ b/readline/history.go @@ -2,9 +2,7 @@ package readline import ( "bufio" - "errors" "fmt" - "io" "os" "path/filepath" "strings" @@ -13,115 +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) { - if latest, _ := h.Buf.Get(h.Size() - 1); latest != s { - h.Buf.Add(s) + if latest, _ := h.lines.Get(h.Size() - 1); latest != s { + h.lines.Add(s) h.Compact() - h.Pos = h.Size() - if h.Autosave { - _ = h.Save() - } + _ = 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 { @@ -129,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) } diff --git a/readline/readline.go b/readline/readline.go index f7b694eb2..20ccada0c 100644 --- a/readline/readline.go +++ b/readline/readline.go @@ -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: