cat / 012
clone

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

Plain text: an entry reads as it was typed until it's typed again, in the file too

Dami committed abb0201
A formula or number formatted as Plain text afterwards kept computing but
reopened as text, and text typed into a Plain text cell reopened as a
number or formula once the format changed (or failed to open when it
didn't parse). A cell's text field says how its input reads when its
format would say otherwise.
commit
abb0201c69eda4bb32ff08c4c21f7ffb1534f6ec
parent
cecd28e78723b3871966f2aca2ddc3e3289c4b79
author
Dami <dami.etoile@gmail.com>
kind
feat risk score 1.9 of 3, Jev's estimate
7 files changed, +109 -24
Mdocs/files/format.md1+
Minternal/sheet/cellfile.go58+++++++++++++++++++++++++-----
Minternal/sheet/cellformat_test.go27++++++++++++++
Minternal/sheet/fileread.go14+++----
Minternal/sheet/filestream_test.go6++--
Minternal/sheet/sheet.go25+++++++++++--
Minternal/sheet/slot.go2+-
M docs/files/format.md +1 -0
M internal/sheet/cellfile.go +49 -9
@@ -9,7 +9,13 @@ import (
99 // fileCell is a cell with formatting or a note as the file writes it; a1010 // cell with neither is just its input (see fileFormat).1111 type fileCell struct {12- Input string `json:"input,omitempty"`12+ Input string `json:"input,omitempty"`13+ // Text says how the input reads when that isn't what the format14+ // says: true for text typed into a Plain text cell whose format15+ // changed since, false for a number or formula typed before the cell16+ // was formatted as Plain text. It needs no version bump: earlier17+ // builds read the input as the format says.18+ Text *bool `json:"text,omitempty"`1319 Format string `json:"format,omitempty"`1420 Decimals *int `json:"decimals,omitempty"`1521 Pattern string `json:"pattern,omitempty"`
@@ -32,11 +38,13 @@ type fileCell struct {
3238 }3339 3440 func encodeCell(c *Cell) (json.RawMessage, error) {35- if c.Format.IsZero() && c.Style.IsZero() && c.Note == "" {41+ text := c.readsAgainstFormat()42+ if c.Format.IsZero() && c.Style.IsZero() && c.Note == "" && text == nil {3643 return json.Marshal(c.Input)3744 }3845 fc := fileCell{3946 Input: c.Input,47+ Text: text,4048 Pattern: c.Format.Pattern,4149 Bold: c.Style.Bold,4250 Italic: c.Style.Italic,
@@ -59,25 +67,57 @@ func encodeCell(c *Cell) (json.RawMessage, error) {
5967 return json.Marshal(fc)6068 }6169 70+// readsAgainstFormat is the file's text field for c: whether its entry71+// is text, when its format says otherwise (see fileCell), else nil.72+func (c *Cell) readsAgainstFormat() *bool {73+ text := c.Format.Kind == FmtText74+ switch {75+ case text && c.expr != nil, !text && c.typedText:76+ v := c.typedText77+ return &v78+ }79+ return nil80+}81+6282 // decodeCell reads a cell's entry and formatting, as line formats are6383 // stored too.6484 func decodeCell(raw json.RawMessage) (string, Format, Style, error) {65- input, f, st, _, err := decodeNoted(raw)66- return input, f, st, err85+ e, err := decodeNoted(raw)86+ return e.input, e.f, e.st, err87+}88+89+// fileEntry is a cell as the file has it.90+type fileEntry struct {91+ input string92+ f Format93+ st Style94+ note string95+ text *bool // see fileCell.Text96+}97+98+// cell builds the cell, as newCell does, reading its entry as text or99+// not as the file says.100+func (e fileEntry) cell(implied bool) (*Cell, error) {101+ asText := e.f.Kind == FmtText102+ if e.text != nil {103+ asText = *e.text104+ }105+ c, err := readEntry(e.input, e.f, e.st, implied, asText)106+ return c.withNote(CleanNote(e.note)), err67107 }68108 69-// decodeNoted is decodeCell with the cell's note.70-func decodeNoted(raw json.RawMessage) (string, Format, Style, string, error) {109+// decodeNoted is decodeCell with the cell's note and how it reads.110+func decodeNoted(raw json.RawMessage) (fileEntry, error) {71111 var input string72112 if err := json.Unmarshal(raw, &input); err == nil {73- return input, Format{}, Style{}, "", nil113+ return fileEntry{input: input}, nil74114 }75115 var fc fileCell76116 if err := json.Unmarshal(raw, &fc); err != nil {77- return "", Format{}, Style{}, "", err117+ return fileEntry{}, err78118 }79119 input, f, st, err := decodeFormatted(fc)80- return input, f, st, fc.Note, err120+ return fileEntry{input, f, st, fc.Note, fc.Text}, err81121 }82122 83123 // decodeFormatted reads the entry and formatting of a cell written as an
M internal/sheet/cellformat_test.go +27 -0
M internal/sheet/fileread.go +6 -8
M internal/sheet/filestream_test.go +3 -3
M internal/sheet/sheet.go +22 -3
@@ -47,6 +47,11 @@ type Cell struct {
4747 volatile bool // expr calls TODAY, NOW, RAND...4848 derived bool // a pivot table's result, owned by the engine: see pivot.go4949 spilled bool // part of an array a formula spills, owned by it: see spill.go50+ // typedText marks text typed into a Plain text cell that would read51+ // as a number or formula, or not at all, typed into a cell of another52+ // format: it stays text when the cell's format changes, as in Sheets,53+ // until it's typed again.54+ typedText bool5055 }5156 5257 // Spilled reports whether the cell shows part of an array another
@@ -274,6 +279,13 @@ func (s *Sheet) put(a Addr, input string) error {
274279 // is text. Control characters are dropped so a worksheet file can't275280 // smuggle escape sequences to the terminal.276281 func newCell(input string, f Format, st Style, implied bool) (*Cell, error) {282+ return readEntry(input, f, st, implied, f.Kind == FmtText)283+}284+285+// readEntry is newCell reading the entry as text when asText, and as286+// typed into a cell of another format than Plain text otherwise, as a287+// file says to of an entry typed before its cell's format changed.288+func readEntry(input string, f Format, st Style, implied, asText bool) (*Cell, error) {277289 input = strings.Map(func(r rune) rune {278290 if unicode.IsControl(r) {279291 return -1
@@ -284,21 +296,28 @@ func newCell(input string, f Format, st Style, implied bool) (*Cell, error) {
284296 return formattingOnly(f, st), nil285297 }286298 var n Node287- if f.Kind != FmtText {299+ if !asText {288300 var fi Format289301 var err error290302 if n, fi, err = classify(input); err != nil {291303 return nil, err292304 }293- if implied && !fi.IsZero() {305+ if implied && !fi.IsZero() && f.Kind != FmtText {294306 f = fi295307 }296308 }297- c := &Cell{Input: input, Format: f, Style: st}309+ c := &Cell{Input: input, Format: f, Style: st, typedText: asText && readsOtherwise(input)}298310 c.setExpr(n)299311 return c, nil300312 }301313 314+// readsOtherwise reports whether an entry typed as text reads as315+// something else, or not at all, typed into a cell of another format.316+func readsOtherwise(input string) bool {317+ n, _, err := classify(input)318+ return n != nil || err != nil319+}320+302321 // formattingOnly is a blank cell holding formatting, or nil when there is303322 // none.304323 func formattingOnly(f Format, st Style) *Cell {
M internal/sheet/slot.go +1 -1