1package ui23import (4 "strconv"5 "strings"67 tea "charm.land/bubbletea/v2"89 "src.rest/cat/012/internal/sheet"10)1112// Format > Wrapping, Borders and Merge cells, as in Sheets: each applies13// to the selection as one undo step and says what it did on the context14// line. Borders draw with the line style last chosen (thin at first),15// as Sheets' border style picker keeps its choice.1617var wrappings = []struct {18 id, title, desc string19 w sheet.Wrap20}{21 {"format.wrap_overflow", "Overflow", "Let text run on into the blank cells beside it", sheet.WrapOverflow},22 {"format.wrap", "Wrap", "Break text into lines that fit the column; the row grows to fit them", sheet.WrapOn},23 {"format.wrap_clip", "Clip", "Cut text off at the cell's edge", sheet.WrapClip},24}2526var borderKinds = []struct {27 id, title, desc string28 k sheet.BorderKind29}{30 {"format.borders_all", "All borders", "Draw every edge of the selected cells", sheet.BorderAll},31 {"format.borders_outer", "Outer borders", "Draw a line around the selection", sheet.BorderOuter},32 {"format.borders_inner", "Inner borders", "Draw the lines between the selected cells", sheet.BorderInner},33 {"format.border_top", "Top border", "Draw a line along the top of the selection", sheet.BorderTop},34 {"format.border_bottom", "Bottom border", "Draw a line along the bottom of the selection", sheet.BorderBottom},35 {"format.border_left", "Left border", "Draw a line down the left of the selection", sheet.BorderLeft},36 {"format.border_right", "Right border", "Draw a line down the right of the selection", sheet.BorderRight},37 {"format.borders_clear", "Clear borders", "Remove the lines of the selected cells", sheet.BorderNone},38}3940var borderLines = []struct {41 id, title, desc string42 l sheet.Line43}{44 {"format.border_thin", "Thin lines", "Draw borders with thin lines: ─", sheet.LineThin},45 {"format.border_thick", "Thick lines", "Draw borders with thick lines: ━", sheet.LineThick},46 {"format.border_double", "Double lines", "Draw borders with double lines: ═", sheet.LineDouble},47}4849var mergeKinds = []struct {50 id, title, desc string51 k sheet.MergeKind52}{53 {"format.merge_all", "Merge all", "Join the selection into one cell showing its top-left value", sheet.MergeAll},54 {"format.merge_horizontal", "Merge horizontally", "Join each row of the selection into one cell", sheet.MergeHorizontally},55 {"format.merge_vertical", "Merge vertically", "Join each column of the selection into one cell", sheet.MergeVertically},56}5758func init() {59 for _, wr := range wrappings {60 w, title := wr.w, wr.title61 register(&command{id: wr.id, title: title, desc: wr.desc, edits: (*Model).selection, keepsSpills: true,62 checked: func(m *Model) bool { return m.sheet.CellStyle(m.cur).Wrap == w },63 run: func(m *Model) tea.Cmd {64 r := m.selection()65 m.sheet.Batch(sheet.Change{Label: strings.ToLower(title) + " " + r.String(), Focus: r}, func() error {66 m.sheet.SetStyle(r, func(s *sheet.Style) { s.Wrap = w })67 return nil68 })69 m.formatted("Wrapping: " + strings.ToLower(title))70 return nil71 }})72 }73 for _, bk := range borderKinds {74 k, title := bk.k, bk.title75 register(&command{id: bk.id, title: title, desc: bk.desc, edits: (*Model).selection, keepsSpills: true,76 run: func(m *Model) tea.Cmd {77 m.sheet.SetBorderStroke(m.selection(), k, sheet.Stroke{Line: m.lineStyle(), Color: m.borderColor})78 m.formatted(title)79 return nil80 }})81 }82 for _, bl := range borderLines {83 l := bl.l84 register(&command{id: bl.id, title: bl.title, desc: bl.desc,85 checked: func(m *Model) bool { return m.lineStyle() == l },86 run: func(m *Model) tea.Cmd {87 m.borderLine = l88 m.note = "Borders draw " + strings.ToLower(bl.title) + " now"89 return nil90 }})91 }92 for _, mk := range mergeKinds {93 k := mk.k94 register(&command{id: mk.id, title: mk.title, desc: mk.desc, edits: (*Model).selection,95 enabled: (*Model).hasRange,96 run: func(m *Model) tea.Cmd { return m.merge(k) }})97 }98 register(&command{id: "format.unmerge", title: "Unmerge", desc: "Split the merged cells in the selection back into cells", edits: (*Model).selection, keepsSpills: true,99 enabled: func(m *Model) bool { return len(m.sheet.MergesIn(m.selection())) > 0 },100 run: func(m *Model) tea.Cmd {101 n := m.sheet.Unmerge(m.selection())102 m.changed = m.changed || n > 0103 m.note = "Unmerged " + strconv.Itoa(n) + plural(n, " merged cell", " merged cells")104 return nil105 }})106 // Sheets' border shortcuts; terminals without the kitty keyboard107 // protocol send Alt+Shift+1 as alt+!, which keyAliases maps.108 for key, id := range map[string]string{109 "alt+shift+1": "format.border_top",110 "alt+shift+2": "format.border_right",111 "alt+shift+3": "format.border_bottom",112 "alt+shift+4": "format.border_left",113 "alt+shift+6": "format.borders_clear",114 "alt+shift+7": "format.borders_outer",115 } {116 keymap[key] = id117 }118}119120// lineStyle is the line Format > Borders draws with.121func (m *Model) lineStyle() sheet.Line {122 if m.borderLine == sheet.LineNone {123 return sheet.LineThin124 }125 return m.borderLine126}127128// merge joins the selection as k says, asking first when that clears129// values other than the top-left ones, as Sheets warns.130func (m *Model) merge(k sheet.MergeKind) tea.Cmd {131 r := m.selection()132 do := func(m *Model) tea.Cmd {133 if err := m.sheet.Merge(r, k); err != nil {134 m.fail(err.Error())135 return nil136 }137 m.changed = true138 m.clearSelection()139 m.cur = r.From140 m.note = "Merged " + r.String()141 return nil142 }143 lost, loses := m.sheet.MergeLoses(r, k)144 if !loses {145 return do(m)146 }147 m.ask(question{148 msg: "Merging keeps only the top-left value.", warn: true,149 desc: "Merging clears " + lost.String() + " and the other values; undo brings them back",150 choices: []choice{151 {key: "enter", label: "Merge", run: do},152 {key: "esc", label: "Cancel", run: func(*Model) tea.Cmd { return nil }},153 }})154 return nil155}