cat / 012
clone

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

main
012 / internal / sheet / notebook.go
notebook.go 6.5K
1package sheet23import (4 "slices"5 "strconv"6 "strings"78 "src.rest/cat/012/internal/notebook"9)1011// Notebook tabs: a sheet of the workbook that holds a notebook's cells12// rather than a grid (package notebook). The tab sits among the sheets,13// is named, moved, hidden and deleted as a sheet is, and its cells are14// part of the undo state (regionState, region.go), so adding, editing,15// moving and deleting cells undo as any change does. What running a cell16// left, its output, isn't: a run can't be taken back, as a linked file's17// rows can't. Outputs are kept by the cell's ID in the workbook, and18// saved with it up to the caps (notebookfile.go).1920// notebookState is the workbook's side of its notebooks: the outputs,21// and the counters that make IDs and Seqs.22type notebookState struct {23 outputs map[int]*notebook.Output24 lastID int25 lastSeq int26 // changed counts changes to outputs, which the modified flag27 // follows as it follows the undo history.28 changed int29 caps notebook.Caps30 // notes say what opening the file did that the user should know:31 // the notebook sheets it converted.32 notes []string33}3435// IsNotebook reports whether the sheet is a notebook tab.36func (s *Sheet) IsNotebook() bool { return s.regions.notebook }3738// MakeNotebook turns an empty sheet into a notebook tab, without an undo39// step, as 012 nu does with a new file's sheet.40func (s *Sheet) MakeNotebook() { s.regions.notebook = true }4142// AddNotebook adds a notebook tab named name ("" for Notebook, then43// Notebook 2 and so on) after the sheet after, as one undo step.44func (w *Workbook) AddNotebook(name string, after *Sheet) (*Sheet, error) {45 if name == "" {46 name = w.freeName("Notebook")47 }48 if err := w.checkName(nil, name); err != nil {49 return nil, err50 }51 s := w.newSheet(name)52 s.regions.notebook = true53 at := len(w.sheets)54 if i := w.Index(after); i >= 0 {55 at = i + 156 }57 w.change(s, "insert notebook "+name, Rect{}, func() {58 w.recordSheets()59 w.insert(s, at)60 })61 return s, nil62}6364// Notebook is the workbook's first notebook tab, or nil.65func (w *Workbook) Notebook() *Sheet {66 for _, s := range w.sheets {67 if s.IsNotebook() {68 return s69 }70 }71 return nil72}7374// NotebookCells returns a notebook tab's cells.75func (s *Sheet) NotebookCells() []notebook.Cell { return slices.Clone(s.regions.cells) }7677// SetNotebookCells replaces a notebook tab's cells as one undo step78// described by label ("add cell"). Cells without an ID are given one.79func (s *Sheet) SetNotebookCells(label string, cells []notebook.Cell) {80 cells = slices.Clone(cells)81 for i := range cells {82 if cells[i].ID == 0 {83 cells[i].ID = s.wb.NewCellID()84 }85 }86 if slices.Equal(cells, s.regions.cells) {87 return88 }89 s.change(label, Rect{}, func() {90 st := s.regionsCopy()91 st.cells = cells92 s.putRegions(st)93 })94}9596// NewCellID is an ID no cell of the workbook has had.97func (w *Workbook) NewCellID() int {98 w.nb.lastID++99 return w.nb.lastID100}101102// Reactive reports whether the notebook runs the cells reading a cell103// again whenever it runs.104func (s *Sheet) Reactive() bool { return s.regions.reactive }105106// SetReactive turns the notebook's reactive setting on or off, as one107// undo step.108func (s *Sheet) SetReactive(on bool) {109 if s.regions.reactive == on {110 return111 }112 s.change("reactive notebook", Rect{}, func() {113 st := s.regionsCopy()114 st.reactive = on115 s.putRegions(st)116 })117}118119// Output is what the cell with the ID id left when it last ran, or nil.120func (w *Workbook) Output(id int) *notebook.Output { return w.nb.outputs[id] }121122// SetOutput keeps o as what the cell with the ID id left, nil for123// nothing, giving it a Seq of its own. It isn't an undo step.124func (w *Workbook) SetOutput(id int, o *notebook.Output) {125 w.nb.changed++126 if o == nil {127 delete(w.nb.outputs, id)128 return129 }130 cp := *o131 w.nb.lastSeq++132 cp.Seq = w.nb.lastSeq133 if w.nb.outputs == nil {134 w.nb.outputs = map[int]*notebook.Output{}135 }136 w.nb.outputs[id] = &cp137}138139// OutputsChanged counts changes to outputs, for the modified flag.140func (w *Workbook) OutputsChanged() int { return w.nb.changed }141142// SetOutputCaps sets how much of the outputs a file keeps.143func (w *Workbook) SetOutputCaps(c notebook.Caps) { w.nb.caps = c }144145// OutputCaps are how much of the outputs a file keeps.146func (w *Workbook) OutputCaps() notebook.Caps {147 if w.nb.caps == (notebook.Caps{}) {148 return notebook.DefaultCaps149 }150 return w.nb.caps151}152153// LoadNotes say what opening the file changed that the user should154// know, such as notebook sheets converted.155func (w *Workbook) LoadNotes() []string { return slices.Clone(w.nb.notes) }156157// cellNamed reports whether a notebook's cell is named name, ignoring158// case.159func (w *Workbook) cellNamed(name string) bool {160 for _, s := range w.sheets {161 for _, c := range s.regions.cells {162 if strings.EqualFold(c.Name(), name) {163 return true164 }165 }166 }167 return false168}169170// hasNotebook reports whether the workbook has a notebook tab.171func (w *Workbook) hasNotebook() bool { return w.Notebook() != nil }172173// nextNotebookCellName is a name for a cell no cell and no region has:174// cell1, cell2 and so on.175func (w *Workbook) nextNotebookCellName() string {176 for n := 1; ; n++ {177 name := "cell" + strconv.Itoa(n)178 if w.checkRegionName(name, false) == nil {179 return name180 }181 }182}183184// FreeCellName is a name like base that no cell and no region of the185// workbook has: base, then base_2 and so on; cell1 and on for "".186func (w *Workbook) FreeCellName(base string) string {187 if base == "" || ValidRegionName(base) != nil {188 return w.nextNotebookCellName()189 }190 name := base191 for n := 2; w.checkRegionName(name, false) != nil; n++ {192 name = base + "_" + strconv.Itoa(n)193 }194 return name195}196197// StaleOutputs names the outputs sent to sheets that are to be sent198// again: new, put back by undo, moved, or no longer blocked.199func (w *Workbook) StaleOutputs() []string {200 var out []string201 for _, s := range w.sheets {202 for _, r := range s.regions.list {203 if me := s.regionMeta[nameKey(r.Name)]; r.Output && (me == nil || me.stale || me.reread) {204 out = append(out, r.Name)205 }206 }207 }208 return out209}210211// RenameRegion gives a sent output its cell's new name, as one undo212// step; it is sent again.213func (w *Workbook) RenameRegion(old, name string) error {214 s, r, ok := w.Region(old)215 if !ok {216 return ErrNoRegion217 }218 if err := w.checkRegionName(name, r.Output); err != nil && nameKey(old) != nameKey(name) {219 return err220 }221 k := nameKey(r.Name)222 s.change("rename "+r.Name, s.covered(r), func() {223 st := s.regionsCopy()224 st.list[s.regionIndex(k)].Name = name225 s.putRegions(st)226 })227 s.meta(nameKey(name)).stale = true228 return nil229}