cat / 012
clone

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

Vim p/P carry row formats; cutting a block leaves its cells plain

Dami committed ec1f34f
yy and dd copy whole rows, so pasting them back with p or P carries the
rows' line formats as a whole-row paste does.

A block cut and pasted leaves the cells it came from plain, as Sheets
clears cut cells: blank cells under a formatted column, row or sheet get
plain formatting of their own (bounded as other formatting changes are),
except where the block lands. Undo restores them.
commit
ec1f34fce029b61131c0e2a396ae9ce68fef8f3d
parent
aebff00858f611693da906c096142913e8f3dd3c
author
Dami <dami.etoile@gmail.com>
6 files changed, +102 -12
Mdocs/data.md5+++-
Mdocs/keys.md2+-
Minternal/sheet/clipfmt.go39+++++++++++++++++++++++++++++-
Minternal/sheet/linecopy_test.go33+++++++++++++++++++++++--
Minternal/ui/linecopy_test.go26++++++++++++++++++++
Minternal/ui/rows.go9++-----
M docs/data.md +4 -1
M docs/keys.md +1 -1
M internal/sheet/clipfmt.go +38 -1
@@ -12,7 +12,8 @@ import (
1212 // already show. Copying whole columns or rows and pasting them as whole1313 // lines (at the top of a column, the start of a row, or A1 for the whole1414 // sheet) carries the line formats themselves, and cutting them moves the15-// line formats, leaving the source lines plain.15+// line formats, leaving the source lines plain. Cutting a block leaves16+// its cells plain, over whatever lines cross it.1617 1718 // clipFormats is the formatting of a copied range, taken at copy time.1819 type clipFormats struct {
@@ -256,10 +257,46 @@ func (s *Sheet) moveFormats(from *Sheet, f *clipFormats, src, dst Rect) {
256257 for n := range f.rowFmt {257258 from.setLine(true, src.From.Row+n, lineFmt{})258259 }260+ case lineBlock:261+ from.plainSource(src, dst, from == s)259262 }260263 s.pasteFormats(f, p)261264 }262265 266+// plainSource leaves the cells a block was cut from showing plain, as267+// Sheets clears them: where a formatted row, column or the sheet crosses268+// src, its blank cells get plain formatting of their own, up to269+// materializeLimit cells looked at, except those in dst when the block270+// lands on the same sheet.271+func (s *Sheet) plainSource(src, dst Rect, same bool) {272+ var areas []Rect273+ if !s.lines.sheet.IsZero() {274+ areas = []Rect{src}275+ } else {276+ for n := range linesIn(s.lines.rows, src.From.Row, src.To.Row) {277+ row := src.From.Row + n278+ areas = append(areas, Rect{From: Addr{Col: src.From.Col, Row: row}, To: Addr{Col: src.To.Col, Row: row}})279+ }280+ for n := range linesIn(s.lines.cols, src.From.Col, src.To.Col) {281+ col := src.From.Col + n282+ areas = append(areas, Rect{From: Addr{Col: col, Row: src.From.Row}, To: Addr{Col: col, Row: src.To.Row}})283+ }284+ }285+ seen := 0286+ for _, r := range areas {287+ for row := r.From.Row; row <= r.To.Row; row++ {288+ for c := r.From.Col; c <= r.To.Col; c++ {289+ if seen++; seen > materializeLimit {290+ return291+ }292+ if a := (Addr{Col: c, Row: row}); !(same && dst.Contains(a)) && s.cells.get(a) == nil {293+ s.placeFormat(a, lineFmt{})294+ }295+ }296+ }297+ }298+}299+263300 // unionKeys are the keys of a and b, in order.264301 func unionKeys(a, b map[int]lineFmt) []int {265302 keys := slices.Collect(maps.Keys(a))
M internal/sheet/linecopy_test.go +30 -3
@@ -160,13 +160,40 @@ func TestMoveCarriesFormats(t *testing.T) {
160160 t.Errorf("Other!C1 shows %v %v; B1 %v", o.DisplayFormat(at("C1")).Kind, o.Value(at("C1")), s.DisplayFormat(at("B1")).Kind)161161 }162162 163- // A block keeps its formats; the source falls back on its lines.163+ // A block keeps its formats; the source is left plain, as Sheets164+ // clears cut cells, though its column stays currency.164165 o.Set(at("C2"), "7")165166 if _, err := o.MoveTo(s, Rect{From: at("C1"), To: at("C2")}, at("E4")); err != nil {166167 t.Fatal(err)167168 }168- if s.DisplayFormat(at("E5")).Kind != FmtCurrency || o.DisplayFormat(at("C1")).Kind != FmtCurrency {169- t.Errorf("E5 shows %v, Other!C1 %v", s.DisplayFormat(at("E5")).Kind, o.DisplayFormat(at("C1")).Kind)169+ if s.DisplayFormat(at("E5")).Kind != FmtCurrency || !o.DisplayFormat(at("C1")).IsZero() || o.DisplayFormat(at("C3")).Kind != FmtCurrency {170+ t.Errorf("E5 shows %v, Other!C1 %v, Other!C3 %v", s.DisplayFormat(at("E5")).Kind, o.DisplayFormat(at("C1")).Kind, o.DisplayFormat(at("C3")).Kind)171+ }172+}173+174+// Cutting a block on its sheet leaves the cells it left plain, over175+// formatted columns and rows, except where it lands; undo restores them.176+func TestMoveBlockLeavesSourcePlain(t *testing.T) {177+ s := New()178+ s.SetFormat(col(1), Preset(FmtCurrency))179+ s.SetStyle(rowRect(1, 1), bold) // row 2180+ s.Set(at("B1"), "5")181+ s.Set(at("B2"), "6")182+ s.Set(at("C3"), "7")183+ if _, err := s.Move(Rect{From: at("B1"), To: at("C3")}, at("C2")); err != nil {184+ t.Fatal(err)185+ }186+ for _, a := range []string{"B1", "B2", "B3", "C1"} {187+ if !s.DisplayFormat(at(a)).IsZero() || s.CellStyle(at(a)).Bold {188+ t.Errorf("%s shows %v, bold %v, want plain", a, s.DisplayFormat(at(a)).Kind, s.CellStyle(at(a)).Bold)189+ }190+ }191+ if s.DisplayFormat(at("C2")).Kind != FmtCurrency || s.DisplayFormat(at("B4")).Kind != FmtCurrency || !s.CellStyle(at("A2")).Bold {192+ t.Errorf("C2 %v (moved from B1), B4 %v, A2 bold %v", s.DisplayFormat(at("C2")).Kind, s.DisplayFormat(at("B4")).Kind, s.CellStyle(at("A2")).Bold)193+ }194+ s.Undo()195+ if s.DisplayFormat(at("B3")).Kind != FmtCurrency || !s.CellStyle(at("B2")).Bold || s.cells.get(at("B3")) != nil {196+ t.Errorf("undo: B3 %v, B2 bold %v", s.DisplayFormat(at("B3")).Kind, s.CellStyle(at("B2")).Bold)170197 }171198 }172199
M internal/ui/linecopy_test.go +26 -0
M internal/ui/rows.go +3 -6