cat / 012
clone

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

Add right-click menus for cells and headers

Dami committed a5cb67e
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>
20 files changed, +438 -121
Me2e/harness_test.go58++++++++++++++++-
Me2e/mouse_test.go68+++----------------
Me2e/screens_test.go14+++-
Me2e/shell_test.go24+++++++
Ae2e/testdata/screens/context-menu-column-light.html30++++++++
Ae2e/testdata/screens/context-menu-column.html30++++++++
Ae2e/testdata/screens/context-menu.html30++++++++
Me2e/testdata/screens/help-light.html23+++---
Me2e/testdata/screens/help.html23+++---
Me2e/testdata/screens/palette-narrow.html2+-
Me2e/testdata/screens/palette-search-light.html10+-
Me2e/testdata/screens/palette-search.html10+-
Me2e/testdata/screens/palette.html2+-
Ainternal/ui/context.go87++++++++++++++++++++++++
Ainternal/ui/context_test.go107++++++++++++++++++++++++++++++
Minternal/ui/menubar.go8++-
Minternal/ui/mouse.go11+---
Minternal/ui/mouse_test.go13----
Minternal/ui/overlay.go7++-
Minternal/ui/panel.go2+-
M e2e/harness_test.go +57 -1
@@ -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) {
M e2e/mouse_test.go +10 -58
@@ -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 }
M e2e/screens_test.go +12 -2
@@ -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"
M e2e/shell_test.go +24 -0
A e2e/testdata/screens/context-menu-column-light.html +30 -0
@@ -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
A e2e/testdata/screens/context-menu-column.html +30 -0
@@ -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
A e2e/testdata/screens/context-menu.html +30 -0
@@ -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
M e2e/testdata/screens/help-light.html +12 -11
M e2e/testdata/screens/help.html +12 -11
M e2e/testdata/screens/palette-narrow.html +1 -1
@@ -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>
M e2e/testdata/screens/palette-search-light.html +5 -5
@@ -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>
M e2e/testdata/screens/palette-search.html +5 -5
@@ -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>
M e2e/testdata/screens/palette.html +1 -1
@@ -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>
A internal/ui/context.go +87 -0
@@ -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+}
A internal/ui/context_test.go +107 -0
@@ -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+}
M internal/ui/menubar.go +6 -2
M internal/ui/mouse.go +2 -9
M internal/ui/mouse_test.go +0 -13
M internal/ui/overlay.go +6 -1
M internal/ui/panel.go +1 -1