cat / 012
clone

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

main
012 / internal / ui / pipe.go
pipe.go 6.4K
1package ui23import (4 "io"5 "strings"67 tea "charm.land/bubbletea/v2"89 "src.rest/cat/012/internal/fileio"10 "src.rest/cat/012/internal/sheet"11 "src.rest/cat/012/internal/ui/transfer"12)1314// 012 as a stage in a pipeline: 012 - reads a table from standard input15// into a new, unsaved sheet named stdin, and 012 --pipe also writes a16// table to standard output when it quits: the selection or the sheet,17// as the user chooses, in the input's format unless another was asked18// for. The screen is the terminal's own (cmd/012 opens it), so nothing19// else reaches standard output. Quitting asks what to send, or sends20// what --send chose without asking, and quitting without sending lets21// the pipeline know by the exit status.2223// StdinName is what a table read from standard input is called: its24// sheet's name.25const StdinName = "stdin"2627// pipeState is the pipeline 012 is a stage of, if any.28type pipeState struct {29 in io.Reader // standard input, read as the program starts; nil once read3031 on bool // --pipe: quitting writes a table to standard output32 to fileio.Kind // the format asked for (--to), or 0 for the input's33 send Send // what quitting sends (--send)3435 // Set by a quit that sends: what to write, and in which format.36 sent *fileio.Snapshot37 kind fileio.Kind38}3940// ReadStdin sets a table to read from r, standard input, as the program41// starts, into a new unsaved sheet named stdin.42func (m *Model) ReadStdin(r io.Reader) { m.pipe.in = r }4344// SetPipe makes quitting send a table to standard output, in format to45// (a text format; 0 for the input's, or NUON when the input isn't text).46func (m *Model) SetPipe(to fileio.Kind) { m.pipe.on, m.pipe.to = true, to }4748// Send is what quitting sends in a pipeline (012 --pipe --send).49type Send int5051const (52 SendAsk Send = iota // ask: the selection, the sheet, or nothing53 SendSelection // the selection, or the sheet when only one cell is selected54 SendSheet // the sheet, whatever is selected55)5657// SetSend makes quitting in a pipeline send s rather than ask.58func (m *Model) SetSend(s Send) { m.pipe.send = s }5960// Piped is what quitting sent: the table and its format, or false when61// the user quit without sending.62func (m *Model) Piped() (*fileio.Snapshot, fileio.Kind, bool) {63 return m.pipe.sent, m.pipe.kind, m.pipe.sent != nil64}6566func init() {67 register(68 &command{id: "pipe.send_selection", macro: macroNever, title: "Quit and send selection",69 desc: "Quit, writing the selection to standard output for the next command in the pipeline",70 enabled: func(m *Model) bool { return m.pipe.on && m.hasRange() },71 hidden: func(m *Model) bool { return !m.pipe.on },72 run: func(m *Model) tea.Cmd { return m.send(m.selection()) }},73 &command{id: "pipe.send_sheet", macro: macroNever, title: "Quit and send sheet",74 desc: "Quit, writing the sheet's data to standard output for the next command in the pipeline",75 enabled: func(m *Model) bool { return m.pipe.on },76 hidden: func(m *Model) bool { return !m.pipe.on },77 run: func(m *Model) tea.Cmd { return m.send(sheet.Rect{}) }},78 &command{id: "pipe.quit_unsent", macro: macroNever, title: "Quit without sending",79 desc: "Quit, writing nothing to standard output: the pipeline sees exit status 1",80 hidden: func(m *Model) bool { return !m.pipe.on },81 run: (*Model).exit},82 )83}8485// startStdin starts reading standard input, if it's to be read.86func (m *Model) startStdin() tea.Cmd {87 r := m.pipe.in88 if r == nil {89 return nil90 }91 m.pipe.in = nil92 return m.xfer.StartReader(StdinName, r, fileio.Options{Locale: m.locale()}, m.spans.Parent())93}9495// streamed opens a table read from standard input: a new spreadsheet96// with no file, unsaved.97func (m *Model) streamed(msg transfer.ImportedMsg) {98 m.reset(msg.Res.Sheet, "")99 m.xfer.Kind = msg.Res.Kind100 m.changed, m.saved = true, -1101 m.note = "Read " + transfer.Rows(msg.Res.Rows) + " of " + msg.Res.Kind.String() + " from standard input"102 if len(msg.Res.Notes) > 0 {103 m.note += "; " + strings.Join(msg.Res.Notes, "; ")104 }105}106107// pipeKind is the format sent: the one asked for, or the input's when108// it's text, or NUON.109func (m *Model) pipeKind() fileio.Kind {110 switch {111 case m.pipe.to != 0:112 return m.pipe.to113 case m.xfer.Kind.IsText():114 return m.xfer.Kind115 }116 return fileio.NUON117}118119// sendRange is what quitting would send: the selection, when a range120// is selected and --send doesn't ask for the sheet, or else the sheet121// (the zero Rect).122func (m *Model) sendRange() sheet.Rect {123 if m.pipe.send != SendSheet && m.hasRange() {124 return m.selection()125 }126 return sheet.Rect{}127}128129// sendWhat names what quitting would send: "B2:D9" or "the sheet".130func (m *Model) sendWhat() string {131 if r := m.sendRange(); r != (sheet.Rect{}) {132 return r.String()133 }134 return "the sheet"135}136137// quitPiped is Quit in a pipeline: send what --send chose, or ask.138func (m *Model) quitPiped() tea.Cmd {139 if m.pipe.send == SendAsk {140 return m.askSend()141 }142 return m.send(m.sendRange())143}144145// askSend asks what to send: the selection, if there is one, or the146// sheet, or nothing.147func (m *Model) askSend() tea.Cmd {148 var choices []choice149 if m.hasRange() {150 choices = append(choices,151 choice{key: "enter", label: "Send selection", run: func(m *Model) tea.Cmd { return m.send(m.selection()) }},152 choice{key: "s", label: "Send sheet", run: func(m *Model) tea.Cmd { return m.send(sheet.Rect{}) }})153 } else {154 choices = append(choices, choice{key: "enter", label: "Send sheet", run: func(m *Model) tea.Cmd { return m.send(sheet.Rect{}) }})155 }156 choices = append(choices,157 choice{key: "d", label: "Don't send", run: (*Model).exit},158 choice{key: "esc", label: "Cancel", run: func(*Model) tea.Cmd { return nil }})159 m.ask(question{160 msg: "Send " + m.sendWhat() + " as " + m.pipeKind().String() + "?",161 desc: "To standard output, for the next command; quitting without sending exits with status 1",162 choices: choices,163 })164 return nil165}166167// send quits, keeping r (the sheet's data when zero) to write to168// standard output once the terminal is given back.169func (m *Model) send(r sheet.Rect) tea.Cmd {170 m.pipe.sent = fileio.Snap(m.sheet, r, m.sheet.Name())171 m.pipe.kind = m.pipeKind()172 return m.exit()173}174175// pipeStatus is what the status line says in a pipeline: the key that176// sends, what and as what.177func (m *Model) pipeStatus() string {178 if !m.pipe.on {179 return ""180 }181 return " " + m.th.KeyHints(m.shortcut("quit"), "send "+m.sendWhat()+" as "+m.pipeKind().String())182}