progress.go 1.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. package progress
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io"
  6. "sync"
  7. "time"
  8. )
  9. type State interface {
  10. String() string
  11. }
  12. type Progress struct {
  13. mu sync.Mutex
  14. w io.Writer
  15. buf bytes.Buffer
  16. pos int
  17. ticker *time.Ticker
  18. states []State
  19. }
  20. func NewProgress(w io.Writer) *Progress {
  21. p := &Progress{w: w}
  22. go p.start()
  23. return p
  24. }
  25. func (p *Progress) stop() bool {
  26. for _, state := range p.states {
  27. if spinner, ok := state.(*Spinner); ok {
  28. spinner.Stop()
  29. }
  30. }
  31. if p.ticker != nil {
  32. p.ticker.Stop()
  33. p.ticker = nil
  34. p.render()
  35. return true
  36. }
  37. return false
  38. }
  39. func (p *Progress) Stop() bool {
  40. stopped := p.stop()
  41. if stopped {
  42. fmt.Fprint(p.w, "\n")
  43. }
  44. return stopped
  45. }
  46. func (p *Progress) StopAndClear() bool {
  47. fmt.Fprint(p.w, "\033[?25l")
  48. defer fmt.Fprint(p.w, "\033[?25h")
  49. stopped := p.stop()
  50. if stopped {
  51. // clear all progress lines
  52. for i := range p.pos {
  53. if i > 0 {
  54. fmt.Fprint(p.w, "\033[A")
  55. }
  56. fmt.Fprint(p.w, "\033[2K\033[1G")
  57. }
  58. }
  59. return stopped
  60. }
  61. func (p *Progress) Add(key string, state State) {
  62. p.mu.Lock()
  63. defer p.mu.Unlock()
  64. p.states = append(p.states, state)
  65. }
  66. func (p *Progress) render() {
  67. p.mu.Lock()
  68. defer p.mu.Unlock()
  69. // buffer output to minimize flickering on all terminals
  70. p.buf.Reset()
  71. defer p.buf.WriteTo(p.w)
  72. // eliminate flickering on terminals that support synchronized output
  73. fmt.Fprint(&p.buf, "\033[?2026h")
  74. defer fmt.Fprint(&p.buf, "\033[?2026l")
  75. fmt.Fprint(&p.buf, "\033[?25l")
  76. defer fmt.Fprint(&p.buf, "\033[?25h")
  77. // move the cursor back to the beginning
  78. for range p.pos - 1 {
  79. fmt.Fprint(&p.buf, "\033[A")
  80. }
  81. fmt.Fprint(&p.buf, "\033[1G")
  82. // render progress lines
  83. for i, state := range p.states {
  84. fmt.Fprint(&p.buf, state.String())
  85. fmt.Fprintf(&p.buf, "\033[K")
  86. if i < len(p.states)-1 {
  87. fmt.Fprint(&p.buf, "\n")
  88. }
  89. }
  90. p.pos = len(p.states)
  91. }
  92. func (p *Progress) start() {
  93. p.ticker = time.NewTicker(100 * time.Millisecond)
  94. for range p.ticker.C {
  95. p.render()
  96. }
  97. }