cat / 012
clone

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

cat/module-path
012 / internal / ui / help.go
help.go 5.8K
1package ui23import (4 "cmp"5 "runtime/debug"6 "slices"7 "strings"89 tea "charm.land/bubbletea/v2"1011 "src.rest/cat/012/internal/sheet"12 "src.rest/cat/012/internal/ui/picker"13 "src.rest/cat/012/internal/ui/shortcuts"14)1516func init() {17 register(18 &command{id: "help", macro: macroNever, title: "Keyboard shortcuts", desc: "Show every key, grouped by what it does", run: func(m *Model) tea.Cmd {19 m.openOverlay(shortcuts.New(m.host()))20 return nil21 }},22 &command{id: "help.functions", macro: macroNever, title: "Function list", desc: "Search the functions formulas can use", run: func(m *Model) tea.Cmd {23 p := m.newPicker("Functions", "Type a function name, e.g. sum or if", 100, functionItems(m))24 p.Action = "insert"25 m.openOverlay(p)26 return nil27 }},28 &command{id: "help.about", macro: macroNever, title: "About 012", desc: "Show the version", run: func(m *Model) tea.Cmd {29 m.ask(question{30 msg: "012 " + version() + ": Lotus 1-2-3 looks, Google Sheets keys.",31 choices: []choice{32 {key: "esc", label: "Close", run: func(*Model) tea.Cmd { return nil }},33 },34 })35 return nil36 }},37 )38 keymap["ctrl+/"] = "help"39}4041// version is the release 012 was built from, or "devel" for a source42// build (including Go's pseudo-versions such as v0.0.0-2026...+dirty).43func version() string {44 if bi, ok := debug.ReadBuildInfo(); ok && strings.HasPrefix(bi.Main.Version, "v") && !strings.ContainsAny(bi.Main.Version, "-+") {45 return bi.Main.Version46 }47 return "devel"48}4950// functionItems lists the spreadsheet functions for the function list;51// picking one starts a formula with it.52func functionItems(m *Model) []picker.Item {53 fns := sheet.Funcs()54 items := make([]picker.Item, len(fns))55 for i, f := range fns {56 items[i] = picker.Item{57 Title: f.Name + "(" + f.Args + ")", Name: len(f.Name), Detail: f.Desc, Desc: f.Desc,58 Pick: func() tea.Cmd {59 m.closeOverlay()60 m.startEntry(modeEnter, "="+f.Name+"(")61 return nil62 },63 }64 }65 return items66}6768// helpRow is a line of the shortcuts view: a group heading, or keys and69// what they do.70type helpRow struct {71 heading string72 keys []string73 action string74}7576// helpRows lists the shortcuts, generated from the keymap and the command77// registry so help can't drift from behavior. Movement and typing, which78// aren't commands, come first; then commands grouped by the menu they're79// in. With vim keys on, the vim keys come first.80func helpRows(vim bool) []helpRow {81 if vim {82 return append(vimHelpRows(), keyHelpRows(true)...)83 }84 return keyHelpRows(false)85}8687// keyHelpRows are the Sheets keys and every command's shortcuts.88func keyHelpRows(vim bool) []helpRow {89 listed := map[string]bool{}90 var rows []helpRow91 group := func(title string, static []helpRow, ids ...string) {92 var cmds []helpRow93 for _, id := range ids {94 keys := keysFor(id, vim)95 if listed[id] || len(keys) == 0 || commands[id] == nil {96 continue97 }98 listed[id] = true99 for i, k := range keys {100 keys[i] = keyLabel(k)101 }102 cmds = append(cmds, helpRow{keys: keys, action: commands[id].title})103 }104 if len(static)+len(cmds) > 0 {105 rows = append(rows, helpRow{heading: title})106 rows = append(rows, static...)107 rows = append(rows, cmds...)108 }109 }110 byTitle := func(pred func(id string) bool) []string {111 var ids []string112 for id := range commands {113 if pred(id) {114 ids = append(ids, id)115 }116 }117 slices.SortFunc(ids, func(a, b string) int { return cmp.Compare(commands[a].title, commands[b].title) })118 return ids119 }120121 group("Moving around", []helpRow{122 {keys: []string{"Arrows"}, action: "Move"},123 {keys: []string{"Ctrl+arrows"}, action: "Jump to the edge of the data"},124 {keys: []string{"Tab", "Shift+Tab"}, action: "Right, left"},125 {keys: []string{"PgUp", "PgDn"}, action: "Screen up, down"},126 {keys: []string{"Alt+PgUp", "Alt+PgDn"}, action: "Screen left, right"},127 {keys: []string{"Home", "Ctrl+Home"}, action: "Column A, cell A1"},128 {keys: []string{"Ctrl+End"}, action: "Last used cell"},129 }, "goto")130 group("Sheets", []helpRow{131 {keys: []string{"Click a tab"}, action: "Show it; double-click renames"},132 }, "sheet.next", "sheet.prev", "sheet.new", "sheet.goto")133 group("Selecting", []helpRow{134 {keys: []string{"Shift+arrows"}, action: "Extend the selection"},135 {keys: []string{"Ctrl+Shift+arrows"}, action: "Extend to the edge of the data"},136 {keys: []string{"Mouse"}, action: "Select by dragging or Shift+click"},137 }, byTitle(func(id string) bool { return strings.HasPrefix(id, "select.") })...)138 group("Entering data", []helpRow{139 {keys: []string{"Type"}, action: "Replace the cell; = starts a formula"},140 {keys: []string{"Enter", "Tab"}, action: "Accept and move down, right"},141 {keys: []string{"Arrows"}, action: "Pick cells for a formula"},142 {keys: []string{"Esc"}, action: "Cancel the entry"},143 }, "edit")144 group("Macros", []helpRow{145 {keys: []string{"Ctrl+Alt+Shift+0-9"}, action: "Run the macro with that shortcut"},146 {keys: []string{"Esc"}, action: "Stop a macro while it runs"},147 })148 group("Notebooks", nbHelpRows(listed))149 group("Menus and search", []helpRow{150 {keys: []string{"Alt+letter"}, action: "Open a menu by its underlined letter"},151 }, "palette", "menu", "menu.context", "help")152 for _, d := range menuBar {153 var ids []string154 var walk func(items []menuItem)155 walk = func(items []menuItem) {156 for _, it := range items {157 if it.items != nil {158 walk(it.items)159 } else if !it.sep {160 ids = append(ids, it.cmd)161 }162 }163 }164 walk(visibleItems(d.items))165 group(d.title, nil, ids...)166 }167 group("Other", nil, byTitle(func(string) bool { return true })...)168 return rows169}170171// Shortcuts are the rows of the shortcuts view (package shortcuts),172// with the keys in use.173func (h host) Shortcuts() []shortcuts.Row {174 rows := helpRows(h.m.prefs.vim)175 out := make([]shortcuts.Row, len(rows))176 for i, r := range rows {177 out[i] = shortcuts.Row{Heading: r.heading, Keys: r.keys, Action: r.action}178 }179 return out180}