mirror of
https://github.com/ollama/ollama.git
synced 2025-05-11 10:26:53 +02:00
The previous commit fixed flickering in the progress bar itself. Cursor flickering is harder to address. Cursor flickering could be fixed by hiding the cursor altogether while the progress bar is displayed. The downside of this is that if the program is killed in such a way that it can't clean up its state, it would leave the cursor invisible. Instead, this commit introduces an output buffer. All of the escape codes and content for a single progress update are written to a buffer, which is then flushed to the terminal all at once. This significantly decreases the time during which the terminal has seen the cursor-hiding code but has not yet seen the cursor-showing code, thus minimizing (but not 100% eliminating) cursor flickering. For more context, see: https://gitlab.gnome.org/GNOME/vte/-/issues/2837#note_2269501
117 lines
1.8 KiB
Go
117 lines
1.8 KiB
Go
package progress
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type State interface {
|
|
String() string
|
|
}
|
|
|
|
type Progress struct {
|
|
mu sync.Mutex
|
|
w io.Writer
|
|
buf bytes.Buffer
|
|
|
|
pos int
|
|
|
|
ticker *time.Ticker
|
|
states []State
|
|
}
|
|
|
|
func NewProgress(w io.Writer) *Progress {
|
|
p := &Progress{w: w}
|
|
go p.start()
|
|
return p
|
|
}
|
|
|
|
func (p *Progress) stop() bool {
|
|
for _, state := range p.states {
|
|
if spinner, ok := state.(*Spinner); ok {
|
|
spinner.Stop()
|
|
}
|
|
}
|
|
|
|
if p.ticker != nil {
|
|
p.ticker.Stop()
|
|
p.ticker = nil
|
|
p.render()
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (p *Progress) Stop() bool {
|
|
stopped := p.stop()
|
|
if stopped {
|
|
fmt.Fprint(p.w, "\n")
|
|
}
|
|
return stopped
|
|
}
|
|
|
|
func (p *Progress) StopAndClear() bool {
|
|
fmt.Fprint(p.w, "\033[?25l")
|
|
defer fmt.Fprint(p.w, "\033[?25h")
|
|
|
|
stopped := p.stop()
|
|
if stopped {
|
|
// clear all progress lines
|
|
for i := range p.pos {
|
|
if i > 0 {
|
|
fmt.Fprint(p.w, "\033[A")
|
|
}
|
|
fmt.Fprint(p.w, "\033[2K\033[1G")
|
|
}
|
|
}
|
|
|
|
return stopped
|
|
}
|
|
|
|
func (p *Progress) Add(key string, state State) {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
|
|
p.states = append(p.states, state)
|
|
}
|
|
|
|
func (p *Progress) render() {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
|
|
// buffer the terminal update to minimize cursor flickering
|
|
// https://gitlab.gnome.org/GNOME/vte/-/issues/2837#note_2269501
|
|
p.buf.Reset()
|
|
defer p.buf.WriteTo(p.w)
|
|
|
|
fmt.Fprint(&p.buf, "\033[?25l")
|
|
defer fmt.Fprint(&p.buf, "\033[?25h")
|
|
|
|
// move the cursor back to the beginning
|
|
for range p.pos - 1 {
|
|
fmt.Fprint(&p.buf, "\033[A")
|
|
}
|
|
fmt.Fprint(&p.buf, "\033[1G")
|
|
|
|
// render progress lines
|
|
for i, state := range p.states {
|
|
fmt.Fprint(&p.buf, state.String())
|
|
fmt.Fprintf(&p.buf, "\033[K")
|
|
if i < len(p.states)-1 {
|
|
fmt.Fprint(&p.buf, "\n")
|
|
}
|
|
}
|
|
|
|
p.pos = len(p.states)
|
|
}
|
|
|
|
func (p *Progress) start() {
|
|
p.ticker = time.NewTicker(100 * time.Millisecond)
|
|
for range p.ticker.C {
|
|
p.render()
|
|
}
|
|
}
|