1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
|
package ui
import (
"github.com/gdamore/tcell/v2"
"codeberg.org/turbo-editors/turbo-core/theme"
)
// StatusItem is one clickable hint on the status bar, such as "F2 Save".
type StatusItem struct {
Key string
Label string
Action func()
}
// width returns how many cells the item occupies, the separating space
// included.
func (s StatusItem) width() int {
return len([]rune(s.Key)) + 1 + len([]rune(s.Label)) + 2
}
// StatusBar is the row of key hints along the bottom of the screen.
//
// A message set with SetMessage temporarily replaces the hints, which is how
// the editor reports "File saved" or a language-server error without opening a
// dialog for it.
type StatusBar struct {
Box
items []StatusItem
message string
extra string
}
// NewStatusBar returns a status bar showing the given hints.
func NewStatusBar(items ...StatusItem) *StatusBar {
return &StatusBar{items: items}
}
// SetItems replaces the hints, as changing context does.
func (s *StatusBar) SetItems(items ...StatusItem) { s.items = items }
// Message returns the message currently shown instead of the hints.
func (s *StatusBar) Message() string { return s.message }
// SetMessage shows a message in place of the hints. Pass the empty string to
// bring the hints back.
func (s *StatusBar) SetMessage(message string) { s.message = message }
// SetExtra sets the text shown right-aligned, which the editor uses for the
// cursor position and the language-server state.
func (s *StatusBar) SetExtra(extra string) { s.extra = extra }
// Draw paints the bar.
func (s *StatusBar) Draw(p *Painter, th *theme.Theme) {
bar := p.Sub(s.Bounds())
normal := th.Style(theme.KeyStatusBar)
bar.Clear(normal)
// The right-aligned text is drawn first and its start becomes the limit
// for everything on the left, so the two can never overwrite each other.
limit := s.drawExtra(bar, th)
if s.message != "" {
bar.TextLimited(1, 0, limit-1, s.message, normal)
return
}
s.drawItems(bar, th, limit)
}
// drawItems paints the key hints from the left, stopping at limit.
func (s *StatusBar) drawItems(p *Painter, th *theme.Theme, limit int) {
normal, key := th.Style(theme.KeyStatusBar), th.Style(theme.KeyStatusBarKey)
x := 1
for _, item := range s.items {
if x+item.width() > limit {
return // the rest would run into the right-aligned text
}
x = p.Text(x, 0, item.Key, key)
x = p.Text(x, 0, " "+item.Label+" ", normal)
}
}
// drawExtra paints the right-aligned text and returns the column it starts at,
// which is where everything drawn from the left has to stop.
func (s *StatusBar) drawExtra(p *Painter, th *theme.Theme) int {
width := p.Size().W
if s.extra == "" {
return width
}
// The text is cut from the left when it is too long, so that the part
// that matters most — the cursor position — is the part that survives.
text := []rune(s.extra)
if len(text) > width-2 {
text = text[:max(width-2, 0)]
}
x := max(width-len(text)-1, 0)
p.Text(x, 0, string(text), th.Style(theme.KeyStatusBarHint))
return x
}
// HandleMouse runs the action of the hint that was clicked.
func (s *StatusBar) HandleMouse(ev *tcell.EventMouse) bool {
if !s.hit(ev) || ev.Buttons() != tcell.Button1 || s.message != "" {
return s.hit(ev)
}
x, _ := ev.Position()
cursor := s.Bounds().X + 1
for _, item := range s.items {
if x >= cursor && x < cursor+item.width() && item.Action != nil {
item.Action()
return true
}
cursor += item.width()
}
return true
}
|