Right-clicking a cell opens Sheets' cell menu (cut, copy, paste, insert and delete rows and columns, clear) under the mouse; right-clicking a column or row header selects it, as Sheets does, and opens the column or row subset, with Resize column for columns. Right-clicking outside the selection moves the active cell there first, right-clicking elsewhere with a menu open moves the menu, and Shift+F10 opens the cell menu at the active cell. The menus reuse the dropdown, so commands that aren't registered yet stay hidden. The e2e harness can now send mouse events through libghostty's mouse encoder.
- commit
- a5cb67ed4b7a9c3d4a5e38fbeb09edaef0052c36
- parent
- a893c005940270149034acf83c947bb3845652cd
- author
- Dami <dami.etoile@gmail.com>
| M | e2e/ | 58 | ++++++++++++++++- |
| M | e2e/ | 68 | +++---------------- |
| M | e2e/ | 14 | +++- |
| M | e2e/ | 24 | +++++++ |
| A | e2e/ | 30 | ++++++++ |
| A | e2e/ | 30 | ++++++++ |
| A | e2e/ | 30 | ++++++++ |
| M | e2e/ | 23 | +++--- |
| M | e2e/ | 23 | +++--- |
| M | e2e/ | 2 | +- |
| M | e2e/ | 10 | +- |
| M | e2e/ | 10 | +- |
| M | e2e/ | 2 | +- |
| A | internal/ | 87 | ++++++++++++++++++++++++ |
| A | internal/ | 107 | ++++++++++++++++++++++++++++++ |
| M | internal/ | 8 | ++- |
| M | internal/ | 11 | +--- |
| M | internal/ | 13 | ---- |
| M | internal/ | 7 | ++- |
| M | internal/ | 2 | +- |
@@ -53,6 +53,8 @@ type session struct {
5353 vt *ghostty.Terminal5454 enc *ghostty.KeyEncoder5555 ev *ghostty.KeyEvent56+ menc *ghostty.MouseEncoder57+ mev *ghostty.MouseEvent5658 cols uint165759 rows uint165860 @@ -90,6 +92,12 @@ func startWith(t *testing.T, o options, args ...string) *session {
9092 if s.enc, err = ghostty.NewKeyEncoder(); err != nil {9193 t.Fatal(err)9294 }95+ if s.menc, err = ghostty.NewMouseEncoder(); err != nil {96+ t.Fatal(err)97+ }98+ if s.mev, err = ghostty.NewMouseEvent(); err != nil {99+ t.Fatal(err)100+ }93101 if s.ev, err = ghostty.NewKeyEvent(); err != nil {94102 t.Fatal(err)95103 }@@ -108,7 +116,7 @@ func startWith(t *testing.T, o options, args ...string) *session {
108116 s.pty.Write(append([]byte(nil), data...))109117 })110118 s.vt.SetEffectSize(func(*ghostty.Terminal) (ghostty.SizeReportSize, bool) {111- return ghostty.SizeReportSize{Columns: s.cols, Rows: s.rows, CellWidth: 8, CellHeight: 16}, true119+ return ghostty.SizeReportSize{Columns: s.cols, Rows: s.rows, CellWidth: cellW, CellHeight: cellH}, true112120 })113121 114122 go func() {@@ -134,6 +142,8 @@ func startWith(t *testing.T, o options, args ...string) *session {
134142 s.pty.Close()135143 <-s.exited136144 s.ev.Close()145+ s.mev.Close()146+ s.menc.Close()137147 s.enc.Close()138148 s.vt.Close()139149 })@@ -290,6 +300,52 @@ func (s *session) press(key ghostty.Key, mods ghostty.Mods, text string, unshift
290300 time.Sleep(5 * time.Millisecond)291301 }292302 303+// Cell size in pixels, as reported to the program and used to place mouse304+// events.305+const cellW, cellH = 8, 16306+307+// mouse sends a mouse event at cell x, y, encoded by libghostty for the308+// tracking mode and format the program turned on. MouseButtonUnknown means309+// no button, e.g. plain motion.310+func (s *session) mouse(action ghostty.MouseAction, button ghostty.MouseButton, x, y int, mods ghostty.Mods) {311+ s.t.Helper()312+ s.mu.Lock()313+ s.menc.SetOptFromTerminal(s.vt)314+ s.menc.SetOptSize(ghostty.MouseEncoderSize{315+ ScreenWidth: uint32(s.cols) * cellW, ScreenHeight: uint32(s.rows) * cellH,316+ CellWidth: cellW, CellHeight: cellH,317+ })318+ s.mev.SetAction(action)319+ if button == ghostty.MouseButtonUnknown {320+ s.mev.ClearButton()321+ } else {322+ s.mev.SetButton(button)323+ }324+ s.mev.SetMods(mods)325+ // Motion with a button held is a drag.326+ s.menc.SetOptAnyButtonPressed(action == ghostty.MouseActionMotion && button != ghostty.MouseButtonUnknown)327+ s.mev.SetPosition(ghostty.MousePosition{X: float32(x*cellW + cellW/2), Y: float32(y*cellH + cellH/2)})328+ data, err := s.menc.Encode(s.mev)329+ s.mu.Unlock()330+ if err != nil {331+ s.t.Fatalf("encoding mouse event: %v", err)332+ }333+ if len(data) == 0 {334+ s.t.Fatalf("mouse event at %d,%d not reported; is mouse tracking on?", x, y)335+ }336+ if _, err := s.pty.Write(data); err != nil {337+ s.t.Fatal(err)338+ }339+ time.Sleep(5 * time.Millisecond)340+}341+342+// click presses and releases a mouse button over cell x, y.343+func (s *session) click(button ghostty.MouseButton, x, y int) {344+ s.t.Helper()345+ s.mouse(ghostty.MouseActionPress, button, x, y, 0)346+ s.mouse(ghostty.MouseActionRelease, button, x, y, 0)347+}348+293349 // resize changes both the virtual terminal and the pty, which sends the294350 // program SIGWINCH.295351 func (s *session) resize(cols, rows uint16) {@@ -3,61 +3,13 @@ package e2e
33 import (44 "strings"55 "testing"6- "time"76 87 ghostty "go.mitchellh.com/libghostty"98 )109 11-const cellW, cellH = 8, 1612-13-// mouse sends one mouse event at a cell, encoded by libghostty from the14-// terminal's current tracking mode, as Ghostty would send it. A zero15-// button means no button (plain motion).16-func (s *session) mouse(action ghostty.MouseAction, button ghostty.MouseButton, col, row int, mods ghostty.Mods) {17- s.t.Helper()18- s.mu.Lock()19- enc, err := ghostty.NewMouseEncoder()20- if err != nil {21- s.mu.Unlock()22- s.t.Fatal(err)23- }24- defer enc.Close()25- ev, err := ghostty.NewMouseEvent()26- if err != nil {27- s.mu.Unlock()28- s.t.Fatal(err)29- }30- defer ev.Close()31- enc.SetOptSize(ghostty.MouseEncoderSize{32- ScreenWidth: uint32(s.cols) * cellW, ScreenHeight: uint32(s.rows) * cellH,33- CellWidth: cellW, CellHeight: cellH,34- })35- enc.SetOptFromTerminal(s.vt)36- enc.SetOptAnyButtonPressed(button != 0 && action == ghostty.MouseActionMotion)37- ev.SetAction(action)38- if button != 0 {39- ev.SetButton(button)40- } else {41- ev.ClearButton()42- }43- ev.SetMods(mods)44- ev.SetPosition(ghostty.MousePosition{X: float32(col*cellW + cellW/2), Y: float32(row*cellH + cellH/2)})45- data, err := enc.Encode(ev)46- s.mu.Unlock()47- if err != nil {48- s.t.Fatal(err)49- }50- if len(data) == 0 {51- return52- }53- if _, err := s.pty.Write(data); err != nil {54- s.t.Fatal(err)55- }56- time.Sleep(5 * time.Millisecond)57-}58-59-// click presses and releases the left button at a screen cell.60-func (s *session) click(col, row int, mods ghostty.Mods) {10+// leftClick presses and releases the left button at a screen cell, with11+// modifiers.12+func (s *session) leftClick(col, row int, mods ghostty.Mods) {6113 s.mouse(ghostty.MouseActionPress, ghostty.MouseButtonLeft, col, row, mods)6214 s.mouse(ghostty.MouseActionRelease, ghostty.MouseButtonLeft, col, row, mods)6315 }@@ -88,11 +40,11 @@ func colX(c int) int { return 6 + c*10 + 2 }
8840 8941 func TestMouseClickDragAndShiftClick(t *testing.T) {9042 s := start(t, "")91- s.click(colX(1), gridRow1+2, 0)43+ s.leftClick(colX(1), gridRow1+2, 0)9244 s.waitForName("B3")9345 s.drag([2]int{colX(0), gridRow1}, [2]int{colX(1), gridRow1 + 1}, [2]int{colX(2), gridRow1 + 2})9446 s.eventually("drag selection", func() bool { return strings.Contains(s.line(29), "A1:C3") })95- s.click(colX(3), gridRow1+4, ghostty.ModShift)47+ s.leftClick(colX(3), gridRow1+4, ghostty.ModShift)9648 s.eventually("shift+click extends", func() bool { return strings.Contains(s.line(29), "A1:D5") })9749 9850 // The app asked the terminal to pass Shift+click through.@@ -107,17 +59,17 @@ func TestMouseClickDragAndShiftClick(t *testing.T) {
10759 func TestMouseClickWhileTypingAndFormulaReference(t *testing.T) {10860 s := start(t, "")10961 s.keys("40", "<enter>", "2", "<enter>", "=")110- s.click(colX(0), gridRow1, 0)62+ s.leftClick(colX(0), gridRow1, 0)11163 s.waitForEntry("=A1")11264 s.keys("+")113- s.click(colX(0), gridRow1+1, 0)65+ s.leftClick(colX(0), gridRow1+1, 0)11466 s.waitForEntry("=A1+A2")11567 s.keys("<enter>")11668 s.waitForLine(gridRow1+2, numRow(3, "42"))11769 11870 // Clicking another cell while typing plain text accepts it there.11971 s.keys("note")120- s.click(colX(2), gridRow1, 0)72+ s.leftClick(colX(2), gridRow1, 0)12173 s.waitForName("C1")12274 s.waitForLine(gridRow1+3, " 4 note")12375 }@@ -140,8 +92,8 @@ func TestMouseResizeAndWheel(t *testing.T) {
14092 func TestMouseDoubleClickEdits(t *testing.T) {14193 s := start(t, "")14294 s.keys("hello", "<enter>")143- s.click(colX(0), gridRow1, 0)144- s.click(colX(0), gridRow1, 0)95+ s.leftClick(colX(0), gridRow1, 0)96+ s.leftClick(colX(0), gridRow1, 0)14597 s.waitFor("EDIT")14698 s.waitForEntry("hello")14799 }@@ -84,6 +84,16 @@ var screens = []screen{
8484 s.keys("<ctrl+k>", "sa")8585 s.waitFor("│ › sa")8686 }},87+ {name: "context-menu", setup: func(s *session) {88+ budget(s)89+ s.click(ghostty.MouseButtonRight, 6+10+4, 4+3)90+ s.waitFor("│ Clear")91+ }},92+ {name: "context-menu-column", setup: func(s *session) {93+ budget(s)94+ s.click(ghostty.MouseButtonRight, 6+10+4, 3)95+ s.waitFor("│ Resize column")96+ }},8797 {name: "quit-confirm", setup: func(s *session) {8898 budget(s)8999 s.keys("<ctrl+q>")@@ -105,7 +115,7 @@ var screens = []screen{
105115 {name: "narrow", opts: options{cols: 60, rows: 16}, setup: budget},106116 {name: "hover-resize-handle", setup: func(s *session) {107117 budget(s)108- s.mouse(ghostty.MouseActionMotion, 0, 6+10-1, 3, 0)118+ s.mouse(ghostty.MouseActionMotion, ghostty.MouseButtonUnknown, 6+10-1, 3, 0)109119 s.waitFor("▐")110120 }},111121 {name: "resizing-column", setup: func(s *session) {@@ -139,7 +149,7 @@ var screens = []screen{
139149 // Key screens are also recorded on a light terminal, where the app picks140150 // its light theme from the reported background color.141151 func init() {142- for _, name := range []string{"budget", "point-range", "selection-stats", "menu", "palette-search", "quit-confirm", "help", "resizing-column", "copy-marker", "copy-marker-selected"} {152+ for _, name := range []string{"budget", "point-range", "selection-stats", "menu", "palette-search", "context-menu-column", "quit-confirm", "help", "resizing-column", "copy-marker", "copy-marker-selected"} {143153 for _, sc := range screens {144154 if sc.name == name {145155 sc.name += "-light"@@ -5,6 +5,8 @@ import (
55 "path/filepath"66 "strings"77 "testing"8+9+ ghostty "go.mitchellh.com/libghostty"810 )911 1012 // The app shell: menu bar, dropdowns, context menus, the command palette@@ -46,6 +48,28 @@ func TestPaletteSearchAndRun(t *testing.T) {
4648 s.waitForBar("C5", "")4749 }4850 51+func TestRightClickColumnMenu(t *testing.T) {52+ s := start(t, "")53+ // Right-click column C's header: it selects the column and opens the54+ // column menu under the mouse.55+ x := 6 + 2*10 + 456+ s.click(ghostty.MouseButtonRight, x, 3)57+ s.waitFor("│ Resize column")58+ s.waitFor("C1:C8192")59+ s.keys("<esc>")60+ s.waitFor("READY")61+62+ // Clicking an item runs it.63+ s.click(ghostty.MouseButtonRight, x, 3)64+ s.waitFor("│ Resize column")65+ for y, l := range strings.Split(s.screen(), "\n") {66+ if strings.Contains(l, "│ Resize column") {67+ s.click(ghostty.MouseButtonLeft, x+3, y)68+ }69+ }70+ s.waitFor("Column width (1-240): 10")71+}72+4973 func TestQuitAsksAboutUnsavedChanges(t *testing.T) {5074 s := start(t, "")5175 s.keys("1", "<enter>", "<ctrl+q>")@@ -0,0 +1,30 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">MENU</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">B1:B8192</span><span style="background:var(--vt-palette-7)"> </span>3+4+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">A</span><span style="background:var(--vt-palette-7)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">B</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">C</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">D</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">E</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">F</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">G</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">H</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">I</span><span style="background:var(--vt-palette-7)"> </span>5+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span> Household<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">bud</span><span style="color:var(--vt-palette-8)">┌────────────────────────────────┐</span>6+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">2</span><span style="background:var(--vt-palette-4)"> </span> <span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-8)">│</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Cut</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Ctrl+X</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-8)">│</span>7+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">3</span><span style="background:var(--vt-palette-4)"> </span> Rent <span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-8)">│</span> Copy <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+C</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span>8+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">4</span><span style="background:var(--vt-palette-4)"> </span> Groceries<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-8)">│</span> Paste <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+V</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span>9+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">5</span><span style="background:var(--vt-palette-4)"> </span> Transit <span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-8)">├────────────────────────────────┤</span>10+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">6</span><span style="background:var(--vt-palette-4)"> </span> Savings r<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">#DI</span><span style="color:var(--vt-palette-8)">│</span> Insert column left <span style="color:var(--vt-palette-8)">│</span>11+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">7</span><span style="background:var(--vt-palette-4)"> </span> Total <span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">2</span><span style="color:var(--vt-palette-8)">│</span> Insert column right <span style="color:var(--vt-palette-8)">│</span>12+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">8</span><span style="background:var(--vt-palette-4)"> </span> <span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-8)">├────────────────────────────────┤</span>13+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">9</span><span style="background:var(--vt-palette-4)"> </span> <span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-8)">│</span> Delete column <span style="color:var(--vt-palette-8)">│</span>14+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">10</span><span style="background:var(--vt-palette-4)"> </span> <span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-8)">│</span> Clear <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Del</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span>15+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">11</span><span style="background:var(--vt-palette-4)"> </span> <span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-8)">├────────────────────────────────┤</span>16+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">12</span><span style="background:var(--vt-palette-4)"> </span> <span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-8)">│</span> Resize column <span style="color:var(--vt-palette-8)">│</span>17+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">13</span><span style="background:var(--vt-palette-4)"> </span> <span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-8)">│</span> Reset column width <span style="color:var(--vt-palette-8)">│</span>18+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">14</span><span style="background:var(--vt-palette-4)"> </span> <span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-8)">└────────────────────────────────┘</span>19+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">15</span><span style="background:var(--vt-palette-4)"> </span> <span style="background:var(--vt-palette-4)"> </span>20+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">16</span><span style="background:var(--vt-palette-4)"> </span> <span style="background:var(--vt-palette-4)"> </span>21+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">17</span><span style="background:var(--vt-palette-4)"> </span> <span style="background:var(--vt-palette-4)"> </span>22+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">18</span><span style="background:var(--vt-palette-4)"> </span> <span style="background:var(--vt-palette-4)"> </span>23+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">19</span><span style="background:var(--vt-palette-4)"> </span> <span style="background:var(--vt-palette-4)"> </span>24+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">20</span><span style="background:var(--vt-palette-4)"> </span> <span style="background:var(--vt-palette-4)"> </span>25+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">21</span><span style="background:var(--vt-palette-4)"> </span> <span style="background:var(--vt-palette-4)"> </span>26+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">22</span><span style="background:var(--vt-palette-4)"> </span> <span style="background:var(--vt-palette-4)"> </span>27+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">23</span><span style="background:var(--vt-palette-4)"> </span> <span style="background:var(--vt-palette-4)"> </span>28+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">24</span><span style="background:var(--vt-palette-4)"> </span> <span style="background:var(--vt-palette-4)"> </span>29+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">25</span><span style="background:var(--vt-palette-4)"> </span> <span style="background:var(--vt-palette-4)"> </span>30+Cut the selection, to move it where you paste <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Up/Down</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">move</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Enter</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">choose</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Esc</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">close</span></pre>\ No newline at end of file@@ -0,0 +1,30 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">MENU</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">B1:B8192</span><span style="background:var(--vt-palette-8)"> </span>3+4+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">A</span><span style="background:var(--vt-palette-8)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">B</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">C</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">D</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">E</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">G</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">H</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">I</span><span style="background:var(--vt-palette-8)"> </span>5+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span> Household<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">bud</span><span style="color:var(--vt-palette-8)">┌────────────────────────────────┐</span>6+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">2</span><span style="background:var(--vt-palette-4)"> </span> <span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-8)">│</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Cut</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Ctrl+X</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-8)">│</span>7+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">3</span><span style="background:var(--vt-palette-4)"> </span> Rent <span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-8)">│</span> Copy <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+C</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>8+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">4</span><span style="background:var(--vt-palette-4)"> </span> Groceries<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-8)">│</span> Paste <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+V</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>9+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">5</span><span style="background:var(--vt-palette-4)"> </span> Transit <span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-8)">├────────────────────────────────┤</span>10+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">6</span><span style="background:var(--vt-palette-4)"> </span> Savings r<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">#DI</span><span style="color:var(--vt-palette-8)">│</span> Insert column left <span style="color:var(--vt-palette-8)">│</span>11+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">7</span><span style="background:var(--vt-palette-4)"> </span> Total <span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">2</span><span style="color:var(--vt-palette-8)">│</span> Insert column right <span style="color:var(--vt-palette-8)">│</span>12+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">8</span><span style="background:var(--vt-palette-4)"> </span> <span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-8)">├────────────────────────────────┤</span>13+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">9</span><span style="background:var(--vt-palette-4)"> </span> <span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-8)">│</span> Delete column <span style="color:var(--vt-palette-8)">│</span>14+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">10</span><span style="background:var(--vt-palette-4)"> </span> <span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-8)">│</span> Clear <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Del</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>15+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">11</span><span style="background:var(--vt-palette-4)"> </span> <span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-8)">├────────────────────────────────┤</span>16+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">12</span><span style="background:var(--vt-palette-4)"> </span> <span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-8)">│</span> Resize column <span style="color:var(--vt-palette-8)">│</span>17+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">13</span><span style="background:var(--vt-palette-4)"> </span> <span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-8)">│</span> Reset column width <span style="color:var(--vt-palette-8)">│</span>18+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">14</span><span style="background:var(--vt-palette-4)"> </span> <span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-8)">└────────────────────────────────┘</span>19+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">15</span><span style="background:var(--vt-palette-4)"> </span> <span style="background:var(--vt-palette-4)"> </span>20+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">16</span><span style="background:var(--vt-palette-4)"> </span> <span style="background:var(--vt-palette-4)"> </span>21+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">17</span><span style="background:var(--vt-palette-4)"> </span> <span style="background:var(--vt-palette-4)"> </span>22+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">18</span><span style="background:var(--vt-palette-4)"> </span> <span style="background:var(--vt-palette-4)"> </span>23+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">19</span><span style="background:var(--vt-palette-4)"> </span> <span style="background:var(--vt-palette-4)"> </span>24+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">20</span><span style="background:var(--vt-palette-4)"> </span> <span style="background:var(--vt-palette-4)"> </span>25+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">21</span><span style="background:var(--vt-palette-4)"> </span> <span style="background:var(--vt-palette-4)"> </span>26+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">22</span><span style="background:var(--vt-palette-4)"> </span> <span style="background:var(--vt-palette-4)"> </span>27+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">23</span><span style="background:var(--vt-palette-4)"> </span> <span style="background:var(--vt-palette-4)"> </span>28+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">24</span><span style="background:var(--vt-palette-4)"> </span> <span style="background:var(--vt-palette-4)"> </span>29+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">25</span><span style="background:var(--vt-palette-4)"> </span> <span style="background:var(--vt-palette-4)"> </span>30+Cut the selection, to move it where you paste <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Up/Down</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">move</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Enter</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">choose</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">close</span></pre>\ No newline at end of file@@ -0,0 +1,30 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">MENU</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">B4</span><span style="background:var(--vt-palette-8)"> </span> 612.43+4+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">A</span><span style="background:var(--vt-palette-8)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">B</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">C</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">D</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">E</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">G</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">H</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">I</span><span style="background:var(--vt-palette-8)"> </span>5+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">1</span><span style="background:var(--vt-palette-8)"> </span> Household budget 20266+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span>7+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span> Rent 14508+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">4</span><span style="background:var(--vt-palette-6)"> </span> Groceries<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">612.4</span><span style="background:var(--vt-palette-6)"> </span>9+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> Transit <span style="color:var(--vt-palette-8)">┌──────────────────────────────────────┐</span>10+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span> Savings r <span style="color:var(--vt-palette-1)">#DI</span><span style="color:var(--vt-palette-8)">│</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Cut</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Ctrl+X</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-8)">│</span>11+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span> Total 2<span style="color:var(--vt-palette-8)">│</span> Copy <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+C</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>12+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">8</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Paste <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+V</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>13+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Paste values only <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Shift+V</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>14+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">├──────────────────────────────────────┤</span>15+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Insert row above <span style="color:var(--vt-palette-8)">│</span>16+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Insert row below <span style="color:var(--vt-palette-8)">│</span>17+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Insert column left <span style="color:var(--vt-palette-8)">│</span>18+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">14</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Insert column right <span style="color:var(--vt-palette-8)">│</span>19+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">15</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">├──────────────────────────────────────┤</span>20+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">16</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Delete row <span style="color:var(--vt-palette-8)">│</span>21+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">17</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Delete column <span style="color:var(--vt-palette-8)">│</span>22+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">18</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">├──────────────────────────────────────┤</span>23+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">19</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Clear <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Del</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>24+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">20</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">└──────────────────────────────────────┘</span>25+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">21</span><span style="background:var(--vt-palette-8)"> </span>26+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">22</span><span style="background:var(--vt-palette-8)"> </span>27+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">23</span><span style="background:var(--vt-palette-8)"> </span>28+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">24</span><span style="background:var(--vt-palette-8)"> </span>29+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">25</span><span style="background:var(--vt-palette-8)"> </span>30+Cut the selection, to move it where you paste <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Up/Down</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">move</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Enter</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">choose</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">close</span></pre>\ No newline at end of file@@ -7,17 +7,18 @@
77 Mouse Click, drag or Shift+click to select; click headers88 for whole columns or rows; double-click to edit99 10- Del / Backspace Clear Ctrl+O Open11- Ctrl+C Copy Ctrl+V Paste12- Ctrl+X Cut Ctrl+Shift+V Paste values only13- Ctrl+Alt+- Delete rows or columns Ctrl+Q Quit14- Esc Deselect Ctrl+Shift+Z / Ctrl+Y Redo15- F2 / Enter Edit cell Ctrl+S Save16- Ctrl+D Fill down Ctrl+K / Ctrl+Shift+P / Alt+/Search the menus17- Ctrl+R Fill right Ctrl+A Select all18- Ctrl+G / F5 Go to Ctrl+Space Select columns19- Ctrl+Alt+= Insert rows or columns Shift+Space Select rows20- F10 Menu Ctrl+Z Undo10+ Shift+F10 Cell menu Ctrl+O Open11+ Del / Backspace Clear Ctrl+V Paste12+ Ctrl+C Copy Ctrl+Shift+V Paste values only13+ Ctrl+X Cut Ctrl+Q Quit14+ Ctrl+Alt+- Delete rows or columns Ctrl+Shift+Z / Ctrl+Y Redo15+ Esc Deselect Ctrl+S Save16+ F2 / Enter Edit cell Ctrl+K / Ctrl+Shift+P / Alt+/Search the menus17+ Ctrl+D Fill down Ctrl+A Select all18+ Ctrl+R Fill right Ctrl+Space Select columns19+ Ctrl+G / F5 Go to Shift+Space Select rows20+ Ctrl+Alt+= Insert rows or columns Ctrl+Z Undo21+ F10 Menu2122 2223 Typing replaces the cell. Start with = for a formula, ' to force text.2324 While typing a formula, arrows after an operator pick cells.@@ -7,17 +7,18 @@
77 Mouse Click, drag or Shift+click to select; click headers88 for whole columns or rows; double-click to edit99 10- Del / Backspace Clear Ctrl+O Open11- Ctrl+C Copy Ctrl+V Paste12- Ctrl+X Cut Ctrl+Shift+V Paste values only13- Ctrl+Alt+- Delete rows or columns Ctrl+Q Quit14- Esc Deselect Ctrl+Shift+Z / Ctrl+Y Redo15- F2 / Enter Edit cell Ctrl+S Save16- Ctrl+D Fill down Ctrl+K / Ctrl+Shift+P / Alt+/Search the menus17- Ctrl+R Fill right Ctrl+A Select all18- Ctrl+G / F5 Go to Ctrl+Space Select columns19- Ctrl+Alt+= Insert rows or columns Shift+Space Select rows20- F10 Menu Ctrl+Z Undo10+ Shift+F10 Cell menu Ctrl+O Open11+ Del / Backspace Clear Ctrl+V Paste12+ Ctrl+C Copy Ctrl+Shift+V Paste values only13+ Ctrl+X Cut Ctrl+Q Quit14+ Ctrl+Alt+- Delete rows or columns Ctrl+Shift+Z / Ctrl+Y Redo15+ Esc Deselect Ctrl+S Save16+ F2 / Enter Edit cell Ctrl+K / Ctrl+Shift+P / Alt+/Search the menus17+ Ctrl+D Fill down Ctrl+A Select all18+ Ctrl+R Fill right Ctrl+Space Select columns19+ Ctrl+G / F5 Go to Shift+Space Select rows20+ Ctrl+Alt+= Insert rows or columns Ctrl+Z Undo21+ F10 Menu2122 2223 Typing replaces the cell. Start with = for a formula, ' to force text.2324 While typing a formula, arrows after an operator pick cells.@@ -8,7 +8,7 @@
88 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-8)">│</span> In<span style="color:var(--vt-palette-3);font-weight:bold">s</span>ert rows <span style="color:var(--vt-palette-3);font-weight:bold">a</span>bove <span style="color:var(--vt-palette-8)">Insert</span> <span style="color:var(--vt-palette-8)">│</span>99 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-8)">│</span> Pa<span style="color:var(--vt-palette-3);font-weight:bold">s</span>te v<span style="color:var(--vt-palette-3);font-weight:bold">a</span>lues only <span style="color:var(--vt-palette-8)">Edit</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Shift+V</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>1010 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-8)">│</span> Re<span style="color:var(--vt-palette-3);font-weight:bold">s</span>et column width <span style="color:var(--vt-palette-8)">Form</span><span style="color:var(--vt-palette-3);font-weight:bold">a</span><span style="color:var(--vt-palette-8)">t</span> <span style="color:var(--vt-palette-8)">│</span>11-<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-8)">└────────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">6</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">32</span> <span style="color:var(--vt-palette-8)">─┘</span>11+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-8)">└────────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">6</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">33</span> <span style="color:var(--vt-palette-8)">─┘</span>1212 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">8</span><span style="background:var(--vt-palette-8)"> </span>1313 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span>1414 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span>@@ -6,11 +6,11 @@
66 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">2</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> Select <span style="color:var(--vt-palette-4);font-weight:bold">col</span>umns <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+Space</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span>77 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">3</span><span style="background:var(--vt-palette-7)"> </span> Rent 145<span style="color:var(--vt-palette-8)">│</span> Delete <span style="color:var(--vt-palette-4);font-weight:bold">col</span>umns <span style="color:var(--vt-palette-0)">Edit</span> <span style="color:var(--vt-palette-8)">│</span>88 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">4</span><span style="background:var(--vt-palette-7)"> </span> Groceries 612.<span style="color:var(--vt-palette-8)">│</span> Insert <span style="color:var(--vt-palette-4);font-weight:bold">co</span>lumns <span style="color:var(--vt-palette-4);font-weight:bold">l</span>eft <span style="color:var(--vt-palette-0)">Insert</span> <span style="color:var(--vt-palette-8)">│</span>9-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">5</span><span style="background:var(--vt-palette-7)"> </span> Transit 9<span style="color:var(--vt-palette-8)">│</span> Delete rows or <span style="color:var(--vt-palette-4);font-weight:bold">col</span>umns <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+Alt+-</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span>10-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">6</span><span style="background:var(--vt-palette-7)"> </span> Savings r <span style="color:var(--vt-palette-1)">#DIV/0!</span><span style="color:var(--vt-palette-8)">│</span> Insert rows or <span style="color:var(--vt-palette-4);font-weight:bold">col</span>umns <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+Alt+=</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span>11-<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">7</span><span style="background:var(--vt-palette-6)"> </span> Total <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">2158.</span><span style="color:var(--vt-palette-8)">│</span> Reset <span style="color:var(--vt-palette-4);font-weight:bold">col</span>umn width <span style="color:var(--vt-palette-0)">Format</span> <span style="color:var(--vt-palette-8)">│</span>12-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">8</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> Insert <span style="color:var(--vt-palette-4);font-weight:bold">col</span>umns right <span style="color:var(--vt-palette-0)">Insert</span> <span style="color:var(--vt-palette-8)">│</span>13-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">9</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">└────────────────────────────────────────</span> <span style="color:var(--vt-palette-0)">8</span> <span style="color:var(--vt-palette-0)">of</span> <span style="color:var(--vt-palette-0)">32</span> <span style="color:var(--vt-palette-8)">─┘</span>9+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">5</span><span style="background:var(--vt-palette-7)"> </span> Transit 9<span style="color:var(--vt-palette-8)">│</span> Reset <span style="color:var(--vt-palette-4);font-weight:bold">col</span>umn width <span style="color:var(--vt-palette-0)">Format</span> <span style="color:var(--vt-palette-8)">│</span>10+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">6</span><span style="background:var(--vt-palette-7)"> </span> Savings r <span style="color:var(--vt-palette-1)">#DIV/0!</span><span style="color:var(--vt-palette-8)">│</span> Delete rows or <span style="color:var(--vt-palette-4);font-weight:bold">col</span>umns <span style="color:var(--vt-palette-0)">Edit</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+Alt+-</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span>11+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">7</span><span style="background:var(--vt-palette-6)"> </span> Total <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">2158.</span><span style="color:var(--vt-palette-8)">│</span> Insert <span style="color:var(--vt-palette-4);font-weight:bold">col</span>umns right <span style="color:var(--vt-palette-0)">Insert</span> <span style="color:var(--vt-palette-8)">│</span>12+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">8</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> Insert rows or <span style="color:var(--vt-palette-4);font-weight:bold">col</span>umns <span style="color:var(--vt-palette-0)">Insert</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+Alt+=</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span>13+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">9</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">└────────────────────────────────────────</span> <span style="color:var(--vt-palette-0)">8</span> <span style="color:var(--vt-palette-0)">of</span> <span style="color:var(--vt-palette-0)">33</span> <span style="color:var(--vt-palette-8)">─┘</span>1414 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">10</span><span style="background:var(--vt-palette-7)"> </span>1515 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">11</span><span style="background:var(--vt-palette-7)"> </span>1616 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">12</span><span style="background:var(--vt-palette-7)"> </span>@@ -6,11 +6,11 @@
66 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Select <span style="color:var(--vt-palette-3);font-weight:bold">col</span>umns <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Space</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>77 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span> Rent 145<span style="color:var(--vt-palette-8)">│</span> Delete <span style="color:var(--vt-palette-3);font-weight:bold">col</span>umns <span style="color:var(--vt-palette-8)">Edit</span> <span style="color:var(--vt-palette-8)">│</span>88 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> Groceries 612.<span style="color:var(--vt-palette-8)">│</span> Insert <span style="color:var(--vt-palette-3);font-weight:bold">co</span>lumns <span style="color:var(--vt-palette-3);font-weight:bold">l</span>eft <span style="color:var(--vt-palette-8)">Insert</span> <span style="color:var(--vt-palette-8)">│</span>9-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> Transit 9<span style="color:var(--vt-palette-8)">│</span> Delete rows or <span style="color:var(--vt-palette-3);font-weight:bold">col</span>umns <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Alt+-</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>10-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span> Savings r <span style="color:var(--vt-palette-1)">#DIV/0!</span><span style="color:var(--vt-palette-8)">│</span> Insert rows or <span style="color:var(--vt-palette-3);font-weight:bold">col</span>umns <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Alt+=</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>11-<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">7</span><span style="background:var(--vt-palette-6)"> </span> Total <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">2158.</span><span style="color:var(--vt-palette-8)">│</span> Reset <span style="color:var(--vt-palette-3);font-weight:bold">col</span>umn width <span style="color:var(--vt-palette-8)">Format</span> <span style="color:var(--vt-palette-8)">│</span>12-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">8</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Insert <span style="color:var(--vt-palette-3);font-weight:bold">col</span>umns right <span style="color:var(--vt-palette-8)">Insert</span> <span style="color:var(--vt-palette-8)">│</span>13-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">└────────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">8</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">32</span> <span style="color:var(--vt-palette-8)">─┘</span>9+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> Transit 9<span style="color:var(--vt-palette-8)">│</span> Reset <span style="color:var(--vt-palette-3);font-weight:bold">col</span>umn width <span style="color:var(--vt-palette-8)">Format</span> <span style="color:var(--vt-palette-8)">│</span>10+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span> Savings r <span style="color:var(--vt-palette-1)">#DIV/0!</span><span style="color:var(--vt-palette-8)">│</span> Delete rows or <span style="color:var(--vt-palette-3);font-weight:bold">col</span>umns <span style="color:var(--vt-palette-8)">Edit</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Alt+-</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>11+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">7</span><span style="background:var(--vt-palette-6)"> </span> Total <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">2158.</span><span style="color:var(--vt-palette-8)">│</span> Insert <span style="color:var(--vt-palette-3);font-weight:bold">col</span>umns right <span style="color:var(--vt-palette-8)">Insert</span> <span style="color:var(--vt-palette-8)">│</span>12+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">8</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Insert rows or <span style="color:var(--vt-palette-3);font-weight:bold">col</span>umns <span style="color:var(--vt-palette-8)">Insert</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Alt+=</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>13+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">└────────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">8</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">33</span> <span style="color:var(--vt-palette-8)">─┘</span>1414 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span>1515 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span>1616 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span>@@ -14,7 +14,7 @@
1414 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Paste <span style="color:var(--vt-palette-8)">Edit</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+V</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>1515 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Paste values only <span style="color:var(--vt-palette-8)">Edit</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Shift+V</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>1616 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Fill down <span style="color:var(--vt-palette-8)">Edit</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+D</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>17-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">└───────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">32</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">32</span> <span style="color:var(--vt-palette-8)">─┘</span>17+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">└───────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">33</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">33</span> <span style="color:var(--vt-palette-8)">─┘</span>1818 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">14</span><span style="background:var(--vt-palette-8)"> </span>1919 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">15</span><span style="background:var(--vt-palette-8)"> </span>2020 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">16</span><span style="background:var(--vt-palette-8)"> </span>@@ -0,0 +1,87 @@
1+package ui2+3+import (4+ tea "charm.land/bubbletea/v2"5+6+ "one23/internal/sheet"7+)8+9+// Right-click menus follow Sheets: the cell menu offers clipboard, insert10+// and delete commands, and the header menus the subset for whole columns11+// or rows. Right-clicking outside the selection first moves there, and12+// Shift+F10 opens the cell menu from the keyboard. The menus reuse the13+// dropdown, so missing commands are hidden the same way.14+15+var (16+ clipboardItems = []menuItem{17+ {cmd: "edit.cut"}, {cmd: "edit.copy"}, {cmd: "edit.paste"},18+ }19+ cellMenu = concat(clipboardItems, []menuItem{20+ {cmd: "edit.paste_values", title: "Paste values only"}, sep,21+ {cmd: "insert.row_above", title: "Insert row above"}, {cmd: "insert.row_below", title: "Insert row below"},22+ {cmd: "insert.col_left", title: "Insert column left"}, {cmd: "insert.col_right", title: "Insert column right"}, sep,23+ {cmd: "delete.row", title: "Delete row"}, {cmd: "delete.col", title: "Delete column"}, sep,24+ {cmd: "clear"},25+ })26+ columnMenu = concat(clipboardItems, []menuItem{sep,27+ {cmd: "insert.col_left", title: "Insert column left"}, {cmd: "insert.col_right", title: "Insert column right"}, sep,28+ {cmd: "delete.col", title: "Delete column"}, {cmd: "clear"}, sep,29+ {cmd: "column.width", title: "Resize column"}, {cmd: "column.reset"},30+ })31+ rowMenu = concat(clipboardItems, []menuItem{sep,32+ {cmd: "insert.row_above", title: "Insert row above"}, {cmd: "insert.row_below", title: "Insert row below"}, sep,33+ {cmd: "delete.row", title: "Delete row"}, {cmd: "clear"},34+ })35+)36+37+func concat(lists ...[]menuItem) []menuItem {38+ var out []menuItem39+ for _, l := range lists {40+ out = append(out, l...)41+ }42+ return out43+}44+45+func init() {46+ register(&command{id: "menu.context", title: "Cell menu", desc: "Open the right-click menu for the selection", run: func(m *Model) tea.Cmd {47+ x, y := m.cellPos(m.cur)48+ m.showContextMenu(cellMenu, x, y+1)49+ return nil50+ }})51+ keymap["shift+f10"] = "menu.context"52+}53+54+// rightClick opens the menu for what's under the mouse at x, y.55+func (m *Model) rightClick(x, y int) {56+ h := m.hitTest(x, y)57+ sel := m.selection()58+ switch h.kind {59+ case hitCell:60+ if !m.hasRange() || !sel.Contains(h.addr) {61+ m.cur = h.addr62+ m.clearSelection()63+ }64+ m.showContextMenu(cellMenu, x, y+1)65+ case hitColHeader, hitColBorder:66+ if m.whole != wholeCols || h.addr.Col < sel.From.Col || h.addr.Col > sel.To.Col {67+ m.cur = h.addr68+ m.selecting, m.whole, m.ext = true, wholeCols, h.addr69+ }70+ m.showContextMenu(columnMenu, x, y+1)71+ case hitRowHeader:72+ if m.whole != wholeRows || h.addr.Row < sel.From.Row || h.addr.Row > sel.To.Row {73+ m.cur = h.addr74+ m.selecting, m.whole, m.ext = true, wholeRows, h.addr75+ }76+ m.showContextMenu(rowMenu, x, y+1)77+ }78+}79+80+// cellPos returns the screen position of a visible cell's left edge.81+func (m *Model) cellPos(a sheet.Addr) (x, y int) {82+ x = rowHdrW83+ for c := m.left; c < a.Col; c++ {84+ x += m.sheet.ColWidth(c)85+ }86+ return x, gridTop + a.Row - m.top87+}@@ -0,0 +1,107 @@
1+package ui2+3+import (4+ "strings"5+ "testing"6+7+ tea "charm.land/bubbletea/v2"8+9+ "one23/internal/sheet"10+)11+12+func rightClick(m *Model, x, y int) {13+ mouseAt(m, tea.MouseClickMsg{X: x, Y: y, Button: tea.MouseRight})14+ mouseAt(m, tea.MouseReleaseMsg{X: x, Y: y, Button: tea.MouseRight})15+}16+17+func menuLabels(t *testing.T, m *Model) string {18+ t.Helper()19+ var got []string20+ for _, it := range openMenu(t, m).top().items {21+ if it.sep {22+ got = append(got, "-")23+ } else {24+ got = append(got, it.label())25+ }26+ }27+ return strings.Join(got, ",")28+}29+30+// tallModel has room for full-height context menus below the click.31+func tallModel() *Model {32+ m := newModel()33+ m.Update(tea.WindowSizeMsg{Width: 80, Height: 40})34+ return m35+}36+37+func TestRightClickCellMenu(t *testing.T) {38+ copied := fakeCommand(t, "edit.copy", "Copy", nil)39+ m := tallModel()40+ press(t, m, "<shift+down>", "<shift+right>") // A1:B241+ // Inside the selection, the selection stays.42+ rightClick(m, cellX(1), gridTop+1)43+ if m.selection().String() != "A1:B2" {44+ t.Errorf("right-click inside moved the selection to %s", m.selection())45+ }46+ if got := menuLabels(t, m); got != "Cut,Copy,Paste,Paste values only,-,Insert row above,Insert row below,Insert column left,Insert column right,-,Delete row,Delete column,-,Clear" {47+ t.Errorf("cell menu %s", got)48+ }49+ o := openMenu(t, m)50+ if b := o.layout(m)[0]; b.x != cellX(1) || b.y != gridTop+2 {51+ t.Errorf("menu at %d,%d", b.x, b.y)52+ }53+ press(t, m, "<down>", "<enter>")54+ if *copied != 1 || m.overlay != nil {55+ t.Errorf("copy ran %d times", *copied)56+ }57+ // Outside the selection, the active cell moves there first.58+ rightClick(m, cellX(3), gridTop+5)59+ if m.cur != addr("D6") || m.hasRange() {60+ t.Errorf("right-click outside: cur %v range %v", m.cur, m.hasRange())61+ }62+ // Right-clicking elsewhere with a menu open moves the menu.63+ rightClick(m, cellX(0), gridTop)64+ if m.cur != addr("A1") || m.overlay == nil {65+ t.Errorf("second right-click: cur %v overlay %T", m.cur, m.overlay)66+ }67+ press(t, m, "<esc>")68+ if m.overlay != nil || m.mode != modeReady {69+ t.Error("Esc did not close the context menu")70+ }71+}72+73+func TestRightClickHeaders(t *testing.T) {74+ m := tallModel()75+ rightClick(m, cellX(2), headerLine)76+ if r := m.selection(); r.From != addr("C1") || r.To.Row != sheet.MaxRows-1 || r.To.Col != 2 {77+ t.Errorf("column header selected %s", r)78+ }79+ if got := menuLabels(t, m); got != "Cut,Copy,Paste,-,Insert column left,Insert column right,-,Delete column,Clear,-,Resize column,Reset column width" {80+ t.Errorf("column menu %s", got)81+ }82+ press(t, m, "r")83+ if highlighted(t, m) != "Resize column" {84+ t.Errorf("r highlighted %q", highlighted(t, m))85+ }86+ press(t, m, "<enter>")87+ if m.mode != modePrompt || m.prompt.kind != promptWidth {88+ t.Errorf("Resize column: mode %v", m.mode)89+ }90+ press(t, m, "<esc>")91+ rightClick(m, 1, gridTop+3)92+ if r := m.selection(); r.From != addr("A4") || r.To.Col != sheet.MaxCols-1 {93+ t.Errorf("row header selected %s", r)94+ }95+ if got := menuLabels(t, m); got != "Cut,Copy,Paste,-,Insert row above,Insert row below,-,Delete row,Clear" {96+ t.Errorf("row menu %s", got)97+ }98+}99+100+func TestShiftF10OpensCellMenu(t *testing.T) {101+ m := tallModel()102+ press(t, m, "<right>", "<down>", "<shift+f10>")103+ b := openMenu(t, m).layout(m)[0]104+ if b.x != cellX(1)-2 || b.y != gridTop+2 {105+ t.Errorf("menu at %d,%d", b.x, b.y)106+ }107+}@@ -42,14 +42,15 @@ var menuBar = []menuDef{
4242 {cmd: "edit.cut"}, {cmd: "edit.copy"}, {cmd: "edit.paste"}, {cmd: "edit.paste_values", title: "Paste values only"}, sep,4343 {cmd: "edit.fill_down", title: "Fill down"}, {cmd: "edit.fill_right", title: "Fill right"}, sep,4444 {cmd: "clear"}, {cmd: "select.all"}, {cmd: "goto"}, sep,45- {cmd: "delete.row", title: "Delete row"}, {cmd: "delete.col", title: "Delete column"},45+ {cmd: "delete.row", title: "Delete row"}, {cmd: "delete.col", title: "Delete column"}, {cmd: "delete.selection"},4646 }},4747 {title: "View", accel: 'v', items: []menuItem{4848 {cmd: "palette", title: "Command palette"}, {cmd: "help"},4949 }},5050 {title: "Insert", accel: 'i', items: []menuItem{5151 {cmd: "insert.row_above", title: "Row above"}, {cmd: "insert.row_below", title: "Row below"}, sep,52- {cmd: "insert.col_left", title: "Column left"}, {cmd: "insert.col_right", title: "Column right"},52+ {cmd: "insert.col_left", title: "Column left"}, {cmd: "insert.col_right", title: "Column right"}, sep,53+ {cmd: "insert.selection"},5354 }},5455 {title: "Format", accel: 'o', items: []menuItem{5556 {title: "Number", items: []menuItem{@@ -401,6 +402,9 @@ func (o *menuOverlay) mouse(m *Model, e mouseEvent) tea.Cmd {
401402 }402403 if e.kind == mousePress {403404 m.closeOverlay()405+ if e.button == tea.MouseRight {406+ m.rightClick(e.x, e.y) // right-clicking elsewhere opens a menu there407+ }404408 }405409 return nil406410 }@@ -151,16 +151,9 @@ func (m *Model) handlePress(mouse tea.Mouse) tea.Cmd {
151151 h := m.hitTest(mouse.X, mouse.Y)152152 double := h.kind == m.lastHit.kind && h.addr == m.lastHit.addr && time.Since(m.lastClick) < doubleClick153153 m.lastClick, m.lastHit = time.Now(), h154- switch mouse.Button {155- case tea.MouseLeft:154+ // Right clicks open context menus (context.go) before reaching here.155+ if mouse.Button == tea.MouseLeft {156156 return m.leftPress(h, mouse, double)157- case tea.MouseRight:158- // Right-clicking outside the selection moves there first, as in159- // Sheets, so a context menu acts on what was clicked.160- if h.kind == hitCell && m.mode == modeReady && !m.selection().Contains(h.addr) {161- m.cur = h.addr162- m.clearSelection()163- }164157 }165158 return nil166159 }@@ -137,19 +137,6 @@ func TestPointerShapes(t *testing.T) {
137137 }138138 }139139 140-func TestRightClickMovesOutsideSelection(t *testing.T) {141- m := newModel()142- press(t, m, "<shift+down>")143- send(m, tea.MouseClickMsg{X: cellX(0), Y: gridTop + 1, Button: tea.MouseRight})144- if !m.hasRange() {145- t.Error("right-click inside the selection dropped it")146- }147- send(m, tea.MouseClickMsg{X: cellX(3), Y: gridTop + 5, Button: tea.MouseRight})148- if m.cur != addr("D6") || m.hasRange() {149- t.Errorf("cur %v", m.cur)150- }151-}152-153140 func TestClickDismissesHelp(t *testing.T) {154141 m := newModel()155142 press(t, m, "<f1>")@@ -115,7 +115,12 @@ func (m *Model) shellMouse(msg tea.MouseMsg) (tea.Cmd, bool) {
115115 if !ok || m.mode != modeReady {116116 return nil, false117117 }118- if mouse := click.Mouse(); mouse.Y == menuLine && mouse.Button == tea.MouseLeft {118+ mouse := click.Mouse()119+ switch {120+ case mouse.Button == tea.MouseRight:121+ m.rightClick(mouse.X, mouse.Y)122+ return nil, true123+ case mouse.Y == menuLine && mouse.Button == tea.MouseLeft:119124 if i := barMenuAt(mouse.X); i >= 0 {120125 m.showBarMenu(i)121126 return nil, true@@ -85,7 +85,7 @@ func formulaBarTextX() int { return nameBoxW + 1 }
8585 // edited here with the terminal cursor, and shown in the cell too.8686 func (m *Model) formulaBar() string {8787 name := m.cur.String()88- if m.hasRange() && m.mode == modeReady {88+ if m.hasRange() && (m.mode == modeReady || m.mode == modeMenu) {8989 name = m.selection().String()9090 }9191 box := m.th.header.Render(padRight(" "+name, nameBoxW)) + " "