Skip to content

Commit e212a82

Browse files
committed
New len calculations
1 parent 8964e2e commit e212a82

File tree

4 files changed

+148
-107
lines changed

4 files changed

+148
-107
lines changed

internal/commands/which.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ func Which(cfg *config.Config) error {
5151

5252
terminal.SetNoColor(cfg.NoColor)
5353
desc := "Looking for " + cfg.Command
54-
terminal.Action(terminal.InfoLevel, desc, true)
55-
terminal.DashedLine(len(desc)+4, 6)
54+
_ = terminal.Action(terminal.InfoLevel, desc, true)
55+
terminal.DashedLine()
5656
terminal.Result(ok)
5757

5858
if !ok {

internal/terminal/print.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const (
1010
Fast bool = false
1111
slowPrintDelay = 10 * time.Millisecond
1212
cursorChar string = "█"
13+
ellipsis = "…"
1314
)
1415

1516
func Print(colorName colorStyle, slow bool, text string) {
@@ -59,3 +60,27 @@ func caret(level Level) {
5960

6061
print(" ")
6162
}
63+
64+
func Line(level Level, msg string, slow bool) {
65+
caret(level)
66+
Print(PrimaryColor, slow, msg+"\n")
67+
}
68+
69+
func Action(level Level, msg string, slow bool) int {
70+
caret(level)
71+
72+
Print(PrimaryColor, slow, msg+":")
73+
74+
return len(msg) + 3
75+
}
76+
77+
func Error(err error) {
78+
if err == nil {
79+
return
80+
}
81+
82+
caret(ErrorLevel)
83+
Print(ErrorColor, false, "ERROR")
84+
PrintF(PrimaryColor, false, ": %s", err.Error())
85+
fmt.Println()
86+
}

internal/terminal/progress.go

Lines changed: 47 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,16 @@ import (
1313
)
1414

1515
type Progress struct {
16-
title string
17-
spinner spinner
18-
spin int
19-
start time.Time
20-
printedLen int
21-
ctx context.Context
22-
cancel context.CancelFunc
23-
lock *sync.Mutex
24-
OutBuffer *bytes.Buffer
25-
ErrBuffer *bytes.Buffer
16+
title string
17+
spinner spinner
18+
lastPrintedLen int
19+
spin int
20+
start time.Time
21+
ctx context.Context
22+
cancel context.CancelFunc
23+
lock *sync.Mutex
24+
OutBuffer *bytes.Buffer
25+
ErrBuffer *bytes.Buffer
2626
}
2727

2828
func NewProgress(title, style string) *Progress {
@@ -34,16 +34,14 @@ func NewProgress(title, style string) *Progress {
3434
}
3535

3636
return &Progress{
37-
title: title,
38-
spinner: s,
39-
spin: 0,
40-
start: time.Now(),
41-
printedLen: 0,
42-
ctx: ctx,
43-
cancel: cancel,
44-
lock: &sync.Mutex{},
45-
OutBuffer: new(bytes.Buffer),
46-
ErrBuffer: new(bytes.Buffer),
37+
title: title,
38+
spinner: s,
39+
start: time.Now(),
40+
ctx: ctx,
41+
cancel: cancel,
42+
lock: &sync.Mutex{},
43+
OutBuffer: new(bytes.Buffer),
44+
ErrBuffer: new(bytes.Buffer),
4745
}
4846
}
4947

@@ -79,80 +77,65 @@ func (p *Progress) bufLen() string {
7977
return ""
8078
}
8179

82-
func (p *Progress) print() {
83-
p.lock.Lock()
84-
defer p.lock.Unlock()
85-
fmt.Print("\r")
86-
Action(InfoLevel, p.title, false)
87-
88-
et := p.elapsed()
89-
bl := p.bufLen()
90-
printedLen := len(p.title) + 4 + len(et)
91-
92-
if p.ctx.Err() == nil {
93-
Print(ClockColor, Fast, et)
94-
printedLen++
95-
if len(bl) > 0 {
96-
printedLen += 1 + len(bl)
97-
Print(SizeColor, Fast, bl)
98-
}
99-
fmt.Print(" ")
100-
}
101-
102-
if p.ctx.Err() == nil {
103-
Print(SpinnerColor, Fast, p.spinner.chars[p.spin])
104-
printedLen += len(p.spinner.chars[p.spin])
105-
106-
// remaining spinner characters
107-
if p.printedLen > printedLen {
108-
diff := p.printedLen - printedLen
109-
fmt.Print(strings.Repeat(" ", diff))
110-
fmt.Print(strings.Repeat("\b", diff))
111-
}
112-
}
113-
114-
p.printedLen = printedLen
115-
}
116-
11780
func (p *Progress) Start() {
11881
p.lock.Lock()
82+
11983
Action(InfoLevel, p.title, true)
12084
time.Sleep(slowPrintDelay)
121-
fmt.Print("\r")
122-
p.lock.Unlock()
12385

86+
p.lock.Unlock()
12487
p.start = time.Now()
12588

12689
if !IsInteractive() {
12790
return
12891
}
12992

13093
p.spin = 0
131-
13294
go func() {
13395
for {
96+
p.lock.Lock()
97+
13498
if p.ctx.Err() != nil {
99+
p.lock.Unlock()
135100
return
136101
}
137102

103+
print(" ")
104+
Print(ClockColor, Fast, p.elapsed())
105+
printedLen := len(p.elapsed()) + 1
106+
if len(p.bufLen()) > 0 {
107+
Print(SizeColor, Fast, p.bufLen())
108+
printedLen += len(p.bufLen())
109+
}
110+
print(" ")
111+
printedLen++
112+
138113
p.spin++
139114
if p.spin > len(p.spinner.chars)-1 {
140115
p.spin = 0
141116
}
142-
p.print()
117+
118+
Print(SpinnerColor, Fast, p.spinner.chars[p.spin])
119+
printedLen++
120+
if p.lastPrintedLen > printedLen {
121+
print(strings.Repeat(" ", p.lastPrintedLen-printedLen))
122+
print(strings.Repeat("\b", p.lastPrintedLen-printedLen))
123+
}
124+
p.lastPrintedLen = printedLen
125+
126+
print(strings.Repeat("\b", printedLen))
127+
128+
p.lock.Unlock()
143129
time.Sleep(p.spinner.delay)
144130
}
145131
}()
146132
}
147133

148134
func (p *Progress) Stop(result bool) {
149135
p.cancel()
150-
p.print()
151-
152-
if IsInteractive() {
153-
DashedLine(p.printedLen, len(p.elapsed())-1)
154-
}
155136

137+
DashedLine()
138+
print(strings.Repeat("\b", len(p.elapsed())+5))
156139
Print(ClockColor, Fast, p.elapsed())
157140
Result(result)
158141
ShowCursor()

internal/terminal/terminal.go

Lines changed: 74 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ package terminal
33
import (
44
"fmt"
55
"os"
6+
"os/signal"
67
"strings"
8+
"syscall"
79

810
"github.com/mattn/go-isatty"
911
"golang.org/x/term"
@@ -16,33 +18,39 @@ const (
1618
InfoLevel
1719
WarnLevel
1820
ErrorLevel
19-
defaultCols = 80
20-
defaultLines = 24
21+
defaultCols = 80
22+
defaultLines = 24
23+
minInteractiveRemainingCols = 15
2124
)
2225

2326
var (
2427
interactive bool
2528
nocolor bool
26-
cols int
29+
width int
2730
)
2831

2932
func init() {
3033
interactive = isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd())
3134

3235
// terminal columns
33-
cols = defaultCols
36+
width = defaultCols
3437
if !IsInteractive() {
3538
SetNoColor(true)
39+
3640
return
3741
}
3842

3943
HideCursor()
4044
c, _, err := term.GetSize(int(os.Stdout.Fd()))
4145
if err != nil {
46+
interactive = false
47+
4248
return
4349
}
4450

45-
cols = c
51+
width = c
52+
53+
watchTerminalResize()
4654
}
4755

4856
func CleanUp() {
@@ -53,27 +61,6 @@ func IsInteractive() bool {
5361
return interactive
5462
}
5563

56-
func Line(level Level, msg string, slow bool) {
57-
caret(level)
58-
Print(PrimaryColor, slow, msg+"\n")
59-
}
60-
61-
func Action(level Level, msg string, slow bool) {
62-
caret(level)
63-
Print(PrimaryColor, slow, msg+": ")
64-
}
65-
66-
func Error(err error) {
67-
if err == nil {
68-
return
69-
}
70-
71-
caret(ErrorLevel)
72-
Print(ErrorColor, false, "ERROR")
73-
PrintF(PrimaryColor, false, ": %s", err.Error())
74-
fmt.Println()
75-
}
76-
7764
func Result(ok bool) {
7865
if !IsInteractive() {
7966
if ok {
@@ -107,43 +94,89 @@ func TableTile(title string) {
10794
fmt.Println()
10895
}
10996

110-
func SavePos() {
97+
func HideCursor() {
11198
if !IsInteractive() {
11299
return
113100
}
114101

115-
fmt.Print("\033[s")
102+
fmt.Print("\033[?25l")
116103
}
117104

118-
func RestorePos() {
105+
func ShowCursor() {
119106
if !IsInteractive() {
120107
return
121108
}
122109

123-
fmt.Print("\033[u")
110+
fmt.Print("\033[?25h")
124111
}
125112

126-
func HideCursor() {
113+
func DashedLine() {
127114
if !IsInteractive() {
115+
Print(SecondaryColor, false, ellipsis)
116+
128117
return
129118
}
130119

131-
fmt.Print("\033[?25l")
132-
}
120+
_, col, err := GetCursorPosition()
121+
if err != nil {
122+
Print(SecondaryColor, false, ellipsis)
133123

134-
func ShowCursor() {
135-
if !IsInteractive() {
136124
return
137125
}
138126

139-
fmt.Print("\033[?25h")
127+
Print(SecondaryColor, false, strings.Repeat(ellipsis, width-col))
140128
}
141129

142-
func DashedLine(fromCol int, rightMargin int) {
143-
repeat := cols - fromCol - rightMargin
144-
if repeat < 0 {
145-
repeat = 0
130+
func watchTerminalResize() {
131+
sigwinch := make(chan os.Signal, 1)
132+
signal.Notify(sigwinch, syscall.SIGWINCH)
133+
134+
go func() {
135+
for range sigwinch {
136+
newWidth, _, err := term.GetSize(int(os.Stdout.Fd()))
137+
if err == nil {
138+
width = newWidth
139+
}
140+
}
141+
}()
142+
}
143+
144+
func GetCursorPosition() (int, int, error) {
145+
if !IsInteractive() {
146+
return 0, 0, fmt.Errorf("not an interactive terminal")
147+
}
148+
149+
// save current terminal state
150+
oldState, err := term.GetState(int(os.Stdin.Fd()))
151+
if err != nil {
152+
return 0, 0, fmt.Errorf("failed to get terminal state: %w", err)
153+
}
154+
defer func() {
155+
_ = term.Restore(int(os.Stdin.Fd()), oldState)
156+
}()
157+
158+
// set terminal to raw mode
159+
_, err = term.MakeRaw(int(os.Stdin.Fd()))
160+
if err != nil {
161+
return 0, 0, fmt.Errorf("failed to set raw mode: %w", err)
162+
}
163+
164+
// request cursor position
165+
fmt.Print("\033[6n")
166+
167+
// read response with timeout
168+
buf := make([]byte, 32)
169+
n, err := os.Stdin.Read(buf)
170+
if err != nil {
171+
return 0, 0, fmt.Errorf("failed to read cursor position: %w", err)
172+
}
173+
174+
// Parse response: \033[{row};{col}R
175+
var row, col int
176+
_, err = fmt.Sscanf(string(buf[:n]), "\033[%d;%dR", &row, &col)
177+
if err != nil {
178+
return 0, 0, fmt.Errorf("failed to parse cursor position: %w", err)
146179
}
147180

148-
Print(SecondaryColor, false, strings.Repeat("…", repeat))
181+
return row, col, nil
149182
}

0 commit comments

Comments
 (0)