cat / 012
clone

a spreadsheet for the terminal: lotus 1-2-3 looks, google sheets keys and formulas, one go binary

main
012 / internal / ui / hosts.go
hosts.go 6.1K
1package ui23import (4 tea "charm.land/bubbletea/v2"56 "src.rest/cat/012/internal/locale"7 "src.rest/cat/012/internal/sheet"8 "src.rest/cat/012/internal/telemetry"9 "src.rest/cat/012/internal/ui/choicebar"10 "src.rest/cat/012/internal/ui/cmdhelp"11 "src.rest/cat/012/internal/ui/cmdline"12 "src.rest/cat/012/internal/ui/filterpick"13 "src.rest/cat/012/internal/ui/findbar"14 "src.rest/cat/012/internal/ui/lineedit"15 "src.rest/cat/012/internal/ui/picker"16 "src.rest/cat/012/internal/ui/review"17 "src.rest/cat/012/internal/ui/rules"18 "src.rest/cat/012/internal/ui/shortcuts"19 "src.rest/cat/012/internal/ui/sortbar"20 "src.rest/cat/012/internal/ui/suggest"21 "src.rest/cat/012/internal/ui/theme"22 "src.rest/cat/012/internal/ui/themepicker"23)2425// host is how components in packages of their own reach the model. It26// implements each one's Host interface, the little it acts on, so the27// model's exported methods stay what cmd/012 and serve use, and a28// component sees only its own interface.29type host struct{ m *Model }3031func (m *Model) host() host { return host{m} }3233var (34 _ picker.Host = host{}35 _ cmdline.Host = host{}36 _ findbar.Host = host{}37 _ themepicker.Host = host{}38 _ rules.Host = host{}39 _ choicebar.Host = host{}40 _ sortbar.Host = host{}41 _ filterpick.Host = host{}42 _ shortcuts.Host = host{}43 _ cmdhelp.Host = host{}44 _ suggest.Host = host{}45 _ review.Host = host{}46)4748// The in-package hosts the model implements itself.49var (50 _ menuHost = (*Model)(nil)51 _ pivotHost = (*Model)(nil)52 _ macrosHost = (*Model)(nil)53 _ namesHost = (*Model)(nil)54 _ tablesHost = (*Model)(nil)55 _ promptHost = (*Model)(nil)56 _ chartHost = (*Model)(nil)57)5859// Components that stay in package ui are handed interfaces of their own60// (menuHost and the like), which the model implements with unexported61// methods, these among them.6263func (m *Model) styles() *theme.Theme { return &m.th }64func (m *Model) size() (width, height int) { return m.width, m.height }65func (m *Model) available(id string) bool { return commands[id].available(m) }66func (m *Model) sheetShown() *sheet.Sheet { return m.sheet }67func (m *Model) canEditScripts() bool { return m.macros.editor }6869// syncChanged makes the modified flag follow the undo history.70func (m *Model) syncChanged() { m.changed = m.sheet.StateID() != m.saved }7172// pickValues opens a filter's values list titled title at screen column73// x; apply gets the criteria chosen, and cancel runs after Esc.74func (m *Model) pickValues(title string, x int, values []sheet.FilterValue, cond sheet.Condition, apply func(sheet.Criteria), cancel func()) {75 fp := m.openValuesPicker(title, x, values, cond, apply)76 fp.OnCancel = cancel77}7879// pointRange asks for a range on the context line; done gets it, and80// cancel runs after Esc.81func (m *Model) pointRange(label string, done func(sheet.Rect), cancel func()) {82 m.openRange(label, func(_ *Model, r sheet.Rect) tea.Cmd {83 done(r)84 return nil85 })86 m.prompt.onCancel = cancel87}8889// askText asks for text on the context line, starting from initial; done90// gets the answer, and cancel runs after Esc.91func (m *Model) askText(label, initial string, done func(string), cancel func()) {92 m.openText(label, initial, func(_ *Model, text string) tea.Cmd {93 done(text)94 return nil95 })96 m.prompt.onCancel = cancel97}9899// What every component with a text field needs.100101func (h host) Theme() *theme.Theme { return &h.m.th }102func (h host) Size() (width, height int) { return h.m.width, h.m.height }103func (h host) Line() *lineedit.Line { return &h.m.line }104func (h host) Close() { h.m.closeOverlay() }105func (h host) Locale() *locale.Locale { return h.m.locale() }106107// The picker.108109func (h host) RecordAnswer(answer string, cancelled bool) { h.m.recordAnswer(answer, cancelled) }110111// newPicker returns a picker of items over the model; the caller opens it.112func (m *Model) newPicker(title, placeholder string, maxW int, items []picker.Item) *picker.Picker {113 return picker.New(m.host(), title, placeholder, maxW, items)114}115116// The command line.117118// Commands are the command line's completions: every registered command119// but the command line itself, by title.120func (h host) Commands() []cmdline.Item {121 ids := commandIDs()122 items := make([]cmdline.Item, 0, len(ids))123 for _, id := range ids {124 if id == "vim.command" {125 continue126 }127 c := commands[id]128 items = append(items, cmdline.Item{Word: id, Title: c.title, Desc: c.desc, Key: h.m.shortcut(id), Off: !c.available(h.m)})129 }130 return items131}132133func (h host) Run(text string) (tea.Cmd, bool) { return h.m.runCmdLine(text) }134func (h host) Fail(msg string) { h.m.fail(msg) }135func (h host) History() *cmdline.History { return &h.m.session.history }136137// The theme picker.138139func (h host) Current() string { return h.m.prefs.Config.Theme().Pick(!h.m.prefs.light) }140func (h host) ThemesDir() string { return h.m.prefs.ThemesDir }141func (h host) Keep(name string) { h.m.keepTheme(name) }142func (h host) KeyReleases() bool { return h.m.session.releases }143144func (h host) Preview(name string) {145 h.m.prefs.preview = name146 h.m.applyTheme()147}148149// The find bar.150151func (h host) Book() *sheet.Workbook { return h.m.book() }152func (h host) Trace() *telemetry.Trace { return h.m.spans }153func (h host) At() (*sheet.Sheet, sheet.Addr) { return h.m.sheet, h.m.cur }154func (h host) Note(msg string) { h.m.note = msg }155func (h host) Show(s *sheet.Sheet, a sheet.Addr) { h.m.showSheet(s); h.m.cur = a }156157// Replace runs a replacement of the find bar, recorded as Find and158// replace answered with what it did, after the selection it started159// from.160func (h host) Replace(do func() string) {161 h.m.recordFlush()162 if answer := do(); answer != "" {163 h.m.changed = true164 h.m.recordDialog("edit.replace", answer)165 }166}167168// Leave closes the find bar, keeping it for Ctrl+F and find next.169func (h host) Leave() {170 if f, ok := h.m.overlay.(*findbar.Bar); ok {171 h.m.find = f172 }173 h.m.closeOverlay()174}175176// Press hands a click outside an overlay to the grid, as if none were177// open.178func (h host) Press(x, y int, button tea.MouseButton) tea.Cmd {179 return h.m.handlePress(tea.Mouse{X: x, Y: y, Button: button})180}