turbo-editors/turbo-corepublic Fork 0
main
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-core.git
git clone ssh://git@rickub.com/turbo-editors/turbo-core.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

🛟 Updated. 28d5985 · on main · k33g · 4h ago
statusbar.go · 120 lines · 3.4 KBGo Blame HistoryRaw
  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
}