cat / 012
clone

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

cat/module-path
012 / internal / ui / nbsend.go
nbsend.go 5.8K
1package ui23import (4 "context"5 "strconv"67 tea "charm.land/bubbletea/v2"89 "src.rest/cat/012/internal/fileio"10 "src.rest/cat/012/internal/notebook"11 "src.rest/cat/012/internal/sheet"12 "src.rest/cat/012/internal/ui/picker"13)1415// Sending a cell's output to a sheet: the output becomes a region of the16// sheet named after the cell (sheet.Region.Output), which formulas read17// as nu.name, and whose rows the cell sends as live operations18// (Workbook.ApplyLive) whenever it runs. The region's rows aren't part19// of the undo history: when undo puts it back, or it moves, it is stale20// and the cell's output is sent again (syncOutputs).2122// sendCell asks where the selected cell's output goes: a new sheet named23// after the cell, or a cell of a sheet there is.24func (m *Model) sendCell() tea.Cmd {25 nb := m.sheet26 c, _ := m.nbCell()27 name := c.Name()28 if name == "" {29 i, _ := m.nbView().Selected()30 name = m.book().FreeCellName("cell" + strconv.Itoa(i+1))31 m.renameCell(nb, c, name)32 }33 if t, r, ok := m.book().Region(name); ok {34 m.note = name + " is on " + t.Name() + " at " + r.At.String() + ": nu." + name + " reads it"35 return nil36 }37 items := []picker.Item{{Title: "New sheet " + name, Name: len("New sheet " + name), Desc: "Add a sheet named " + name + " with the output at A1",38 Pick: func() tea.Cmd {39 m.closeOverlay()40 return m.sendToNewSheet(nb, name)41 }}}42 for _, t := range m.visibleSheets() {43 if t.IsNotebook() {44 continue45 }46 items = append(items, picker.Item{Title: t.Name(), Name: len(t.Name()), Detail: "pick a cell", Desc: "Put the output on " + t.Name(),47 Pick: func() tea.Cmd {48 m.closeOverlay()49 m.askSendCell(nb, name, t)50 return nil51 }})52 }53 p := m.newPicker("Send "+name+" to", "Type a sheet name", 60, items)54 p.Action = "send"55 m.openOverlay(p)56 return nil57}5859// sendToNewSheet sends name's output to a new sheet named after it.60func (m *Model) sendToNewSheet(nb *sheet.Sheet, name string) tea.Cmd {61 w := m.book()62 t, err := w.AddSheet(freeSheetName(w, name), w.Index(nb)+1)63 if err != nil {64 m.fail(err.Error())65 return nil66 }67 return m.sendTo(nb, name, t, sheet.Addr{})68}6970// askSendCell asks for the cell of t the output starts at.71func (m *Model) askSendCell(nb *sheet.Sheet, name string, t *sheet.Sheet) {72 at := m.tabs.Places[t].Cur73 m.openText("Send "+name+" to "+t.Name()+" at:", at.String(), func(m *Model, text string) tea.Cmd {74 a, ok := sheet.ParseAddr(text)75 if !ok {76 m.fail(text + " isn't a cell")77 return nil78 }79 return m.sendTo(nb, name, t, a)80 })81}8283// sendTo makes name's output a region of t at a, and sends it there.84func (m *Model) sendTo(nb *sheet.Sheet, name string, t *sheet.Sheet, a sheet.Addr) tea.Cmd {85 if err := t.AddRegion(sheet.Region{Name: name, At: a, Output: true}); err != nil {86 m.fail(err.Error())87 return nil88 }89 m.feedOutput(name)90 m.note = "Sent " + name + " to " + sheet.Qualified(t.Name(), sheet.Rect{From: a, To: a}) + ": =SUM(nu." + name + ") reads it"91 if m.sheet != nb {92 m.showSheet(nb)93 }94 return nil95}9697// cellNamed finds the cell named name among the notebooks' cells.98func (m *Model) cellNamed(name string) (notebook.Cell, bool) {99 for _, s := range m.book().Sheets() {100 cells := s.NotebookCells()101 if i, ok := notebook.Names(cells)[name]; ok {102 return cells[i], true103 }104 }105 return notebook.Cell{}, false106}107108// feedOutput sends the output of the cell named name to the region it109// was sent to, if it was: its rows, none when it hasn't run, or why110// when it failed or is gone.111func (m *Model) feedOutput(name string) {112 w := m.book()113 if name == "" {114 return115 }116 _, r, ok := w.Region(name)117 if !ok || !r.Output {118 return119 }120 op := sheet.LiveOp{Region: r.Name, Reset: true}121 c, ok := m.cellNamed(r.Name)122 switch o := w.Output(c.ID); {123 case !ok:124 op = sheet.LiveOp{Region: r.Name, Err: "its cell is gone"}125 case o == nil || o.Unsaved:126 case o.Failed():127 op = sheet.LiveOp{Region: r.Name, Err: o.Err}128 default:129 rows, note, err := fileio.NUONRows(context.Background(), o.NUON, 0)130 if err != nil {131 op = sheet.LiveOp{Region: r.Name, Err: err.Error()}132 break133 }134 fileio.KeepFormats(&rows, fileio.OutputFormats(w, o, m.rangeRef))135 op.Header, op.Rows, op.Note = rows.Header, rows.Rows, note136 }137 if err := w.ApplyLive(op); err != nil {138 m.warn = err.Error()139 }140}141142// syncOutputs sends again the outputs whose regions are stale.143func (m *Model) syncOutputs() {144 for _, name := range m.book().StaleOutputs() {145 m.feedOutput(name)146 }147}148149// moveOutput gives a sent output its cell's new name.150func (m *Model) moveOutput(r sheet.Region, name string) {151 if err := m.book().RenameRegion(r.Name, name); err != nil {152 m.warn = err.Error()153 }154}155156// onOutputRegion reports whether the active cell is in an output sent157// to the sheet.158func onOutputRegion(m *Model) bool {159 r, ok := m.sheet.RegionAt(m.cur)160 return ok && r.Output161}162163// freezeOutput turns the output sent here into plain values.164func (m *Model) freezeOutput() tea.Cmd {165 r, _ := m.sheet.RegionAt(m.cur)166 if err := m.sheet.FreezeRegion(r.Name); err != nil {167 m.fail(err.Error())168 return nil169 }170 m.note = "Froze " + r.Name + " into values: the cell no longer updates them"171 return nil172}173174// deleteOutput removes the output sent here.175func (m *Model) deleteOutput() tea.Cmd {176 r, _ := m.sheet.RegionAt(m.cur)177 if err := m.sheet.DeleteRegion(r.Name); err != nil {178 m.fail(err.Error())179 return nil180 }181 m.note = "Removed " + r.Name + " from " + m.sheet.Name()182 return nil183}184185// regionLine is the context line on a region: a linked file's state, or186// the cell an output comes from.187func (m *Model) regionLine() string {188 r, ok := m.sheet.RegionAt(m.cur)189 if !ok {190 return ""191 }192 if r.Linked() {193 return m.linkedLine() // linked.go194 }195 from := "a notebook"196 for _, s := range m.book().Sheets() {197 if _, ok := notebook.Names(s.NotebookCells())[r.Name]; ok {198 from = s.Name()199 }200 }201 return m.th.Muted.Render("Output of " + r.Name + " from " + from + ", " + r.Name + "[column] in formulas")202}