cat / 012
clone

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

Import location: new sheets go after the current tab; Replace current sheet keeps its charts

Dami committed ed04383
Charts that drew the old sheet's whole table are re-pointed to the new
table when it has as many series; the others keep their range. The note
says which.
commit
ed043839dc34e4e4919630485d3ce9205518a7ea
parent
20aee77255e9c7f6e3cd873f169bb5028e3fb185
author
Dami <dami.etoile@gmail.com>
8 files changed, +192 -55
Me2e/import_test.go10++--
Me2e/testdata/screens/import-location-light.html14+++---
Me2e/testdata/screens/import-location-narrow.html14+++---
Me2e/testdata/screens/import-location.html14+++---
Minternal/sheet/insertbook.go79+++++++++++++++++++++++++-----
Minternal/sheet/insertbook_test.go55+++++++++++++++++++--
Minternal/ui/importplace.go41+++++++++++++---
Minternal/ui/importplace_test.go20++++++--
M e2e/import_test.go +5 -5
M e2e/testdata/screens/import-location-light.html +7 -7
@@ -1,11 +1,11 @@
11 <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">D</span>ata <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)">A2</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">┌─</span> <span style="font-weight:bold">Import</span> <span style="font-weight:bold">budget.tsv</span> <span style="color:var(--vt-palette-8)">───────────────────────────┐</span>3- <span style="color:var(--vt-palette-8)">│</span> <span style="font-weight:bold">›</span> <span style="color:var(--vt-palette-0)">Import</span> <span style="color:var(--vt-palette-0)">location</span> <span style="color:var(--vt-palette-8)">│</span>4-<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">A</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)">B</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-8)">├───────────────────────────────────────────────┤</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-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">1</span><span style="background:var(--vt-palette-7)"> </span> Notes <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)">Insert</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">new</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">sheet</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">after</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">the</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">others</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-8)">│</span>6-<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">2</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-8)">│</span> Replace current sheet <span style="color:var(--vt-palette-0)">Sheet1</span> <span style="color:var(--vt-palette-8)">│</span>7-<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> <span style="color:var(--vt-palette-8)">│</span> Replace spreadsheet <span style="color:var(--vt-palette-0)">unsaved</span> <span style="color:var(--vt-palette-0)">changes</span> <span style="color:var(--vt-palette-8)">│</span>8-<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> <span style="color:var(--vt-palette-8)">└──────────────────────────────────────</span> <span style="color:var(--vt-palette-0)">3</span> <span style="color:var(--vt-palette-0)">of</span> <span style="color:var(--vt-palette-0)">3</span> <span style="color:var(--vt-palette-8)">─┘</span>2+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">A2</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">┌─</span> <span style="font-weight:bold">Import</span> <span style="font-weight:bold">budget.tsv</span> <span style="color:var(--vt-palette-8)">──────────────────────────┐</span>3+ <span style="color:var(--vt-palette-8)">│</span> <span style="font-weight:bold">›</span> <span style="color:var(--vt-palette-0)">Import</span> <span style="color:var(--vt-palette-0)">location</span> <span style="color:var(--vt-palette-8)">│</span>4+<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">A</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)">B</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-8)">├──────────────────────────────────────────────┤</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-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">1</span><span style="background:var(--vt-palette-7)"> </span> Notes <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)">Insert</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">new</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">sheet</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">after</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Sheet1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-8)">│</span>6+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">2</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-8)">│</span> Replace current sheet <span style="color:var(--vt-palette-0)">Sheet1</span> <span style="color:var(--vt-palette-8)">│</span>7+<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> <span style="color:var(--vt-palette-8)">│</span> Replace spreadsheet <span style="color:var(--vt-palette-0)">unsaved</span> <span style="color:var(--vt-palette-0)">changes</span> <span style="color:var(--vt-palette-8)">│</span>8+<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> <span style="color:var(--vt-palette-8)">└─────────────────────────────────────</span> <span style="color:var(--vt-palette-0)">3</span> <span style="color:var(--vt-palette-0)">of</span> <span style="color:var(--vt-palette-0)">3</span> <span style="color:var(--vt-palette-8)">─┘</span>99 <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>1010 <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>1111 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">7</span><span style="background:var(--vt-palette-7)"> </span>
M e2e/testdata/screens/import-location-narrow.html +7 -7
@@ -1,11 +1,11 @@
11 <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">D</span>ata <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)">A2</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-8)">┌─</span> <span style="font-weight:bold">Import</span> <span style="font-weight:bold">budget.tsv</span> <span style="color:var(--vt-palette-8)">───────────────────────────┐</span>3- <span style="color:var(--vt-palette-8)">│</span> <span style="font-weight:bold">›</span> <span style="color:var(--vt-palette-8)">Import</span> <span style="color:var(--vt-palette-8)">location</span> <span style="color:var(--vt-palette-8)">│</span>4-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-8)">├───────────────────────────────────────────────┤</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="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)">Insert</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">new</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">sheet</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">after</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">the</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">others</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-8)">│</span>6-<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">2</span><span style="color:var(--vt-palette-8)">│</span> Replace current sheet <span style="color:var(--vt-palette-8)">Sheet1</span> <span style="color: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="color:var(--vt-palette-8)">│</span> Replace spreadsheet <span style="color:var(--vt-palette-8)">unsaved</span> <span style="color:var(--vt-palette-8)">changes</span> <span style="color:var(--vt-palette-8)">│</span>8-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="color:var(--vt-palette-8)">└──────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">3</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">3</span> <span style="color:var(--vt-palette-8)">─┘</span>2+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">A2</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-8)">┌─</span> <span style="font-weight:bold">Import</span> <span style="font-weight:bold">budget.tsv</span> <span style="color:var(--vt-palette-8)">──────────────────────────┐</span>3+ <span style="color:var(--vt-palette-8)">│</span> <span style="font-weight:bold">›</span> <span style="color:var(--vt-palette-8)">Import</span> <span style="color:var(--vt-palette-8)">location</span> <span style="color:var(--vt-palette-8)">│</span>4+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-8)">├──────────────────────────────────────────────┤</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><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)">Insert</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">new</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">sheet</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">after</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Sheet1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-8)">│</span>6+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">2</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-8)">│</span> Replace current sheet <span style="color:var(--vt-palette-8)">Sheet1</span> <span style="color: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><span style="color:var(--vt-palette-8)">│</span> Replace spreadsheet <span style="color:var(--vt-palette-8)">unsaved</span> <span style="color:var(--vt-palette-8)">changes</span> <span style="color:var(--vt-palette-8)">│</span>8+<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><span style="color:var(--vt-palette-8)">└─────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">3</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">3</span> <span style="color:var(--vt-palette-8)">─┘</span>99 <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>1010 <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>1111 <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>
M e2e/testdata/screens/import-location.html +7 -7
@@ -1,11 +1,11 @@
11 <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">D</span>ata <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)">A2</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">┌─</span> <span style="font-weight:bold">Import</span> <span style="font-weight:bold">budget.tsv</span> <span style="color:var(--vt-palette-8)">───────────────────────────┐</span>3- <span style="color:var(--vt-palette-8)">│</span> <span style="font-weight:bold">›</span> <span style="color:var(--vt-palette-8)">Import</span> <span style="color:var(--vt-palette-8)">location</span> <span style="color:var(--vt-palette-8)">│</span>4-<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">A</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)">B</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-8)">├───────────────────────────────────────────────┤</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> Notes <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)">Insert</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">new</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">sheet</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">after</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">the</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">others</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-8)">│</span>6-<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">2</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-8)">│</span> Replace current sheet <span style="color:var(--vt-palette-8)">Sheet1</span> <span style="color: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> <span style="color:var(--vt-palette-8)">│</span> Replace spreadsheet <span style="color:var(--vt-palette-8)">unsaved</span> <span style="color:var(--vt-palette-8)">changes</span> <span style="color:var(--vt-palette-8)">│</span>8-<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> <span style="color:var(--vt-palette-8)">└──────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">3</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">3</span> <span style="color:var(--vt-palette-8)">─┘</span>2+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">A2</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">┌─</span> <span style="font-weight:bold">Import</span> <span style="font-weight:bold">budget.tsv</span> <span style="color:var(--vt-palette-8)">──────────────────────────┐</span>3+ <span style="color:var(--vt-palette-8)">│</span> <span style="font-weight:bold">›</span> <span style="color:var(--vt-palette-8)">Import</span> <span style="color:var(--vt-palette-8)">location</span> <span style="color:var(--vt-palette-8)">│</span>4+<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">A</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)">B</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-8)">├──────────────────────────────────────────────┤</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> Notes <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)">Insert</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">new</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">sheet</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">after</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Sheet1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-8)">│</span>6+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">2</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-8)">│</span> Replace current sheet <span style="color:var(--vt-palette-8)">Sheet1</span> <span style="color: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> <span style="color:var(--vt-palette-8)">│</span> Replace spreadsheet <span style="color:var(--vt-palette-8)">unsaved</span> <span style="color:var(--vt-palette-8)">changes</span> <span style="color:var(--vt-palette-8)">│</span>8+<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> <span style="color:var(--vt-palette-8)">└─────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">3</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">3</span> <span style="color:var(--vt-palette-8)">─┘</span>99 <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>1010 <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>1111 <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>
M internal/sheet/insertbook.go +67 -12
@@ -19,12 +19,12 @@ type ImportResult struct {
1919 Names int // named ranges left out, their names taken2020 }2121 22-// InsertBook moves the sheets of src, an imported workbook, into w after23-// its last sheet, with src's named ranges, as one undo step labelled24-// label. A sheet whose name w already has gets a number ("Sales 2"), and22+// InsertBook moves the sheets of src, an imported workbook, into w at23+// index at (clamped to the ends; the UI puts them after the sheet shown),24+// with src's named ranges, as one undo step labelled label. A sheet whose name w already has gets a number ("Sales 2"), and2525 // src's formulas follow the new name; a named range whose name w already2626 // has is left out. src is used up.27-func (w *Workbook) InsertBook(src *Workbook, label string) (ImportResult, error) {27+func (w *Workbook) InsertBook(src *Workbook, at int, label string) (ImportResult, error) {2828 var res ImportResult2929 if src == w || len(src.sheets) == 0 {3030 return res, errors.New("Nothing to import")
@@ -49,10 +49,16 @@ func (w *Workbook) InsertBook(src *Workbook, label string) (ImportResult, error)
4949 s.wb = w5050 }5151 src.sheets = nil52- w.change(sheets[0], label, Rect{}, func() {52+ // Undo shows the sheet the new ones follow, where the import started.53+ at = clampInt(at, 0, len(w.sheets))54+ shown := sheets[0]55+ if at > 0 {56+ shown = w.sheets[at-1]57+ }58+ w.change(shown, label, Rect{}, func() {5359 w.recordSheets()54- for _, s := range sheets {55- w.insert(s, len(w.sheets))60+ for i, s := range sheets {61+ w.insert(s, at+i)5662 }5763 for _, n := range names {5864 if _, taken := w.LookupName(n.Name); taken {
@@ -69,14 +75,16 @@ func (w *Workbook) InsertBook(src *Workbook, label string) (ImportResult, error)
6975 // ReplaceSheet puts s, the sheet of an imported workbook, in the place of7076 // dst, as one undo step labelled label: it takes dst's name and position,7177 // so formulas reading dst read it, and named ranges on dst move to it.72-// dst's cells, charts and the rest go with it; undo brings them back.73-func (w *Workbook) ReplaceSheet(dst, s *Sheet, label string) error {78+// dst's charts stay, drawing s's data (see keepCharts), and it returns79+// what became of each. dst's cells and the rest go; undo brings them back.80+func (w *Workbook) ReplaceSheet(dst, s *Sheet, label string) ([]ChartFate, error) {7481 switch {7582 case !dst.live || dst.wb != w:76- return errors.New("That sheet was deleted")83+ return nil, errors.New("That sheet was deleted")7784 case s.wb == w:78- return errors.New("That sheet is already in the spreadsheet")85+ return nil, errors.New("That sheet is already in the spreadsheet")7986 }87+ fates := s.keepCharts(dst)8088 s.wb.detach(s)8189 s.wb.sheets = slices.DeleteFunc(s.wb.sheets, func(t *Sheet) bool { return t == s })8290 s.wb, s.tabHidden = w, dst.tabHidden
@@ -93,7 +101,54 @@ func (w *Workbook) ReplaceSheet(dst, s *Sheet, label string) error {
93101 }94102 }95103 })96- return nil104+ return fates, nil105+}106+107+// ChartFate says what replacing a sheet did to one of its charts.108+type ChartFate struct {109+ Name string // the chart's title, or Chart 1, Chart 2 by position110+ Was, Now Rect // the range it drew, and draws111+ Empty bool // the new data has nothing in Now112+}113+114+// keepCharts gives s, replacing old, old's charts. A chart that drew a115+// whole block of old's data is re-pointed to the block s has at the same116+// corner when it has as many series (columns, or rows for a chart by117+// row), so a chart of last month's table draws this month's, however118+// many rows it has. Any other chart keeps its range, which may now hold119+// something else or nothing; ChartFate says which.120+func (s *Sheet) keepCharts(old *Sheet) []ChartFate {121+ fates := make([]ChartFate, 0, len(old.charts))122+ for i, c := range old.charts {123+ name := c.Title124+ if name == "" {125+ name = fmt.Sprintf("Chart %d", i+1)126+ }127+ was := c.Data128+ c.Data = s.repoint(old, was, c.ByRow)129+ _, filled := s.FilledBounds(c.Data)130+ fates = append(fates, ChartFate{Name: name, Was: was, Now: c.Data, Empty: !filled})131+ s.charts = append(s.charts, c)132+ }133+ return fates134+}135+136+// repoint is the range on s a chart drawing d on old draws: s's block of137+// data at d's corner when d was old's whole block there and the two have138+// as many series, else d. Whole columns or rows read any data as is.139+func (s *Sheet) repoint(old *Sheet, d Rect, byRow bool) Rect {140+ if d.AllRows() || d.AllCols() || old.Region(d.From) != d {141+ return d142+ }143+ now := s.Region(d.From)144+ if now.From != d.From || s.cells.get(now.From).Blank() && now.From == now.To {145+ return d146+ }147+ if byRow && now.To.Row-now.From.Row != d.To.Row-d.From.Row ||148+ !byRow && now.To.Col-now.From.Col != d.To.Col-d.From.Col {149+ return d150+ }151+ return now97152 }98153 99154 // freeIn returns base, or base with a number added ("Sales 2"), so that
M internal/sheet/insertbook_test.go +50 -5
@@ -4,7 +4,8 @@ import (
44 "testing"55 )66 7-// Inserting an imported workbook adds its sheets after the others,7+// Inserting an imported workbook adds its sheets where asked, here after8+// the first,89 // renaming the ones whose names are taken (and the formulas that use910 // them), brings its free named ranges, and undoes as one step.1011 func TestInsertBook(t *testing.T) {
@@ -22,11 +23,11 @@ func TestInsertBook(t *testing.T) {
2223 src.DefineName("Fresh", src.Sheet(1), NewRect(at("A1"), at("A1")))2324 src.hidePlain(src.Sheet(1))2425 25- res, err := w.InsertBook(src, "import book.xlsx")26+ res, err := w.InsertBook(src, 1, "import book.xlsx")2627 if err != nil {2728 t.Fatal(err)2829 }29- if got := namesOf(w); got != "Sales,Notes,Sales 2,Data" {30+ if got := namesOf(w); got != "Sales,Sales 2,Data,Notes" {3031 t.Fatalf("sheets %s", got)3132 }3233 if len(res.Sheets) != 2 || res.Renamed["Sales"] != "Sales 2" || res.Names != 1 {
@@ -60,7 +61,7 @@ func TestInsertBook(t *testing.T) {
6061 t.Errorf("undo left Fresh %v, Notes!A1 %s", ok, show(t, w, "Notes!A1"))6162 }6263 w.Redo()63- if namesOf(w) != "Sales,Notes,Sales 2,Data" || show(t, w, "Notes!A1") != "6" {64+ if namesOf(w) != "Sales,Sales 2,Data,Notes" || show(t, w, "Notes!A1") != "6" {6465 t.Errorf("redo: %s", namesOf(w))6566 }6667 }
@@ -78,7 +79,7 @@ func TestReplaceSheet(t *testing.T) {
7879 src := bookOf(t, page{"sales", map[string]string{"A1": "21"}})7980 8081 old := w.Sheet(0)81- if err := w.ReplaceSheet(old, src.Sheet(0), "import sales.csv"); err != nil {82+ if _, err := w.ReplaceSheet(old, src.Sheet(0), "import sales.csv"); err != nil {8283 t.Fatal(err)8384 }8485 if namesOf(w) != "Sales,Notes" || old.Live() || show(t, w, "Sales!B1") != "" {
@@ -110,3 +111,47 @@ func namesOf(w *Workbook) string {
110111 111112 // hidePlain hides s outside the history, as a loader would.112113 func (w *Workbook) hidePlain(s *Sheet) { s.tabHidden = true }114+115+// Replacing a sheet keeps its charts: one that drew the whole table is116+// re-pointed to the new table when it has as many columns, and the rest117+// keep their ranges, whatever they now hold.118+func TestReplaceSheetKeepsCharts(t *testing.T) {119+ w := bookOf(t, page{"Sales", map[string]string{120+ "A1": "Month", "B1": "Total", "A2": "Jan", "B2": "1", "A3": "Feb", "B3": "2",121+ "E1": "x", "E2": "3", "H1": "9",122+ }})123+ old := w.Sheet(0)124+ old.AddChart(Chart{Data: NewRect(at("A1"), at("B3")), Title: "Totals"})125+ old.AddChart(Chart{Data: NewRect(at("E1"), at("E2"))})126+ old.AddChart(Chart{Data: NewRect(at("H1"), at("H1"))})127+ w.ClearHistory()128+ src := bookOf(t, page{"sales", map[string]string{129+ "A1": "Month", "B1": "Total", "A2": "Jan", "B2": "4", "A3": "Feb", "B3": "5", "A4": "Mar", "B4": "6",130+ "E1": "a", "F1": "b", "E2": "1", "F2": "2",131+ }})132+ fates, err := w.ReplaceSheet(old, src.Sheet(0), "import sales.csv")133+ if err != nil {134+ t.Fatal(err)135+ }136+ want := []ChartFate{137+ {Name: "Totals", Was: NewRect(at("A1"), at("B3")), Now: NewRect(at("A1"), at("B4"))},138+ {Name: "Chart 2", Was: NewRect(at("E1"), at("E2")), Now: NewRect(at("E1"), at("E2"))}, // a column more: kept139+ {Name: "Chart 3", Was: NewRect(at("H1"), at("H1")), Now: NewRect(at("H1"), at("H1")), Empty: true},140+ }141+ if len(fates) != len(want) {142+ t.Fatalf("fates %+v", fates)143+ }144+ for i := range want {145+ if fates[i] != want[i] {146+ t.Errorf("chart %d: %+v, want %+v", i+1, fates[i], want[i])147+ }148+ }149+ charts := w.Sheet(0).Charts()150+ if len(charts) != 3 || charts[0].Data != want[0].Now || charts[0].Title != "Totals" {151+ t.Errorf("charts on the new sheet: %+v", charts)152+ }153+ w.Undo()154+ if w.Sheet(0) != old || len(old.Charts()) != 3 || old.Charts()[0].Data != want[0].Was {155+ t.Errorf("undo: charts %+v", old.Charts())156+ }157+}
M internal/ui/importplace.go +33 -8
@@ -13,9 +13,9 @@ import (
1313 )1414 1515 // Where File > Import puts what it reads, as Sheets' Import location:16-// into new sheets after the others, in place of the sheet shown, or in17-// place of the whole spreadsheet (what File > Open and the command line18-// do). A new, empty spreadsheet is just replaced. Inserting or replacing16+// into new sheets after the sheet shown, in place of it (keeping its17+// charts), or in place of the whole spreadsheet (what File > Open and18+// the command line do). A new, empty spreadsheet is just replaced. Inserting or replacing1919 // a sheet is one undo step, and the file stays the one being edited.2020 2121 // The places are transfer.Book, transfer.NewSheets and transfer.Sheet.
@@ -45,10 +45,14 @@ func (m *Model) askImportPlace(name string) tea.Cmd {
4545 return run(m)4646 }}4747 }48- items := []picker.Item{item(insert, "after the others", "Add "+base+as,48+ items := []picker.Item{item(insert, "after "+m.sheet.Name(), "Add "+base+as,4949 func(m *Model) tea.Cmd { return m.startImport(name, fileio.Options{}, transfer.NewSheets) })}5050 if !k.HoldsSheets() {51- items = append(items, item("Replace current sheet", m.sheet.Name(), "Put "+base+" in place of "+m.sheet.Name()+", keeping its name",51+ keeping := ", keeping its name"52+ if len(m.sheet.Charts()) > 0 {53+ keeping = ", keeping its name and charts"54+ }55+ items = append(items, item("Replace current sheet", m.sheet.Name(), "Put "+base+" in place of "+m.sheet.Name()+keeping,5256 func(m *Model) tea.Cmd { return m.startImport(name, fileio.Options{}, transfer.Sheet) }))5357 }5458 detail := "open it instead"
@@ -70,7 +74,7 @@ func (m *Model) placeImport(msg transfer.ImportedMsg, what string) bool {
7074 label := "import " + filepath.Base(msg.Name)7175 switch msg.Place {7276 case transfer.NewSheets:73- ins, err := m.book().InsertBook(res.Sheet.Book(), label)77+ ins, err := m.book().InsertBook(res.Sheet.Book(), m.book().Index(m.sheet)+1, label)7478 if err != nil {7579 m.fail(err.Error())7680 return true
@@ -86,12 +90,13 @@ func (m *Model) placeImport(msg transfer.ImportedMsg, what string) bool {
8690 }8791 case transfer.Sheet:8892 i := m.book().Index(m.sheet)89- if err := m.book().ReplaceSheet(m.sheet, res.Sheet, label); err != nil {93+ fates, err := m.book().ReplaceSheet(m.sheet, res.Sheet, label)94+ if err != nil {9095 m.fail(err.Error())9196 return true9297 }9398 m.afterSheetsChange(res.Sheet, i)94- m.note = "Imported " + what + " into " + res.Sheet.Name() + " (" + transfer.Rows(res.Rows) + ")"99+ m.note = "Imported " + what + " into " + res.Sheet.Name() + " (" + transfer.Rows(res.Rows) + ")" + chartFates(fates)95100 default:96101 return false97102 }
@@ -114,3 +119,23 @@ func sheetList(sheets []*sheet.Sheet) string {
114119 return transfer.Thousands(n) + " sheets, " + sheets[0].Name() + " to " + sheets[n-1].Name()115120 }116121 }122+123+// chartFates tells what replacing a sheet did to its charts, for the124+// note: re-pointed to the new data, or kept on their range.125+func chartFates(fates []sheet.ChartFate) string {126+ if len(fates) == 0 {127+ return ""128+ }129+ parts := make([]string, len(fates))130+ for i, f := range fates {131+ switch {132+ case f.Now != f.Was:133+ parts[i] = f.Name + " re-pointed to " + f.Now.String()134+ case f.Empty:135+ parts[i] = f.Name + " kept on " + f.Now.String() + ", empty now"136+ default:137+ parts[i] = f.Name + " kept on " + f.Now.String()138+ }139+ }140+ return "; charts: " + strings.Join(parts, ", ")141+}
M internal/ui/importplace_test.go +16 -4
@@ -36,7 +36,7 @@ func TestImportLocation(t *testing.T) {
3636 m.runCommand("file.import")3737 press(t, m, "sales.csv", "<enter>")3838 got := strings.Join(pickerTitles(t, m), "\n")39- want := "Insert new sheet | after the others\nReplace current sheet | Sheet1\nReplace spreadsheet | open it instead"39+ want := "Insert new sheet | after Sheet1\nReplace current sheet | Sheet1\nReplace spreadsheet | open it instead"4040 if got != want {4141 t.Errorf("locations:\n%s\nwant\n%s", got, want)4242 }
@@ -68,17 +68,29 @@ func TestImportLocation(t *testing.T) {
6868 if sheetNames(m) != "Sheet1,sales" || !strings.Contains(line(m, contextLine), "Undid: import sales.csv") {6969 t.Errorf("undo: %s, %q", sheetNames(m), line(m, contextLine))7070 }71+ // New sheets go after the sheet shown, not at the end.72+ press(t, m, "<ctrl+pgup>")73+ importInto(t, m, "sales.csv", "Insert new sheet")74+ if sheetNames(m) != "Sheet1,sales 2,sales" || m.sheet.Name() != "sales 2" {75+ t.Errorf("insert after Sheet1: %s on %s", sheetNames(m), m.sheet.Name())76+ }77+ press(t, m, "<ctrl+z>")7178 72- // Replace current sheet keeps the sheet's name and place.79+ // Replace current sheet keeps the sheet's name, place and charts,80+ // and says what became of the charts.7381 press(t, m, "<ctrl+pgup>")82+ m.sheet.AddChart(sheet.Chart{Data: sheet.NewRect(addr("C1"), addr("C2")), Title: "Totals"})7483 writeFile(t, "sales.csv", "Region,Total\nSouth,50\n")7584 importInto(t, m, "sales.csv", "Replace current sheet")7685 if sheetNames(m) != "Sheet1,sales" || m.sheet.Name() != "Sheet1" || input(m, "A2") != "South" || input(m, "A1") != "Region" {7786 t.Fatalf("replaced: %s on %s, A2 %q", sheetNames(m), m.sheet.Name(), input(m, "A2"))7887 }79- if line(m, contextLine) != "Imported sales.csv into Sheet1 (2 rows)" {88+ if line(m, contextLine) != "Imported sales.csv into Sheet1 (2 rows); charts: Totals kept on C1:C2, empty now" {8089 t.Errorf("context %q", line(m, contextLine))8190 }91+ if len(m.sheet.Charts()) != 1 {92+ t.Errorf("charts after replacing: %+v", m.sheet.Charts())93+ }8294 press(t, m, "<ctrl+z>")8395 if m.sheet.Name() != "Sheet1" || input(m, "A1") != "=sales!B2*2" {8496 t.Errorf("undo replace: on %s, A1 %q", m.sheet.Name(), input(m, "A1"))
@@ -102,7 +114,7 @@ func TestImportLocationWorkbook(t *testing.T) {
102114 press(t, m, "x", "<enter>")103115 m.runCommand("file.import")104116 press(t, m, "book", "<enter>")105- if got := strings.Join(pickerTitles(t, m), "\n"); got != "Insert new sheets | after the others\nReplace spreadsheet | unsaved changes" {117+ if got := strings.Join(pickerTitles(t, m), "\n"); got != "Insert new sheets | after Sheet1\nReplace spreadsheet | unsaved changes" {106118 t.Errorf("locations:\n%s", got)107119 }108120 press(t, m, "insert", "<enter>")