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 / nbview / outline.go
outline.go 1.8K
1package nbview23import (4 "strings"56 "github.com/charmbracelet/x/ansi"78 "src.rest/cat/012/internal/notebook"9)1011// Entry is a place in the notebook to jump to: a heading of a note, or12// a cell.13type Entry struct {14 Cell int // the cell's index15 Level int // a heading's level, 1 for #; 0 for a cell16 Title string // the heading's text, or the cell's first line17}1819// Outline is the notebook's headings in order, from its notes'20// Markdown: its table of contents. With cells, every cell is there too,21// a code cell by its first line and a note by its first heading or line.22func (v *View) Outline(cells bool) []Entry {23 var out []Entry24 for i, c := range v.h.Cells() {25 heads := headings(c)26 for _, h := range heads {27 out = append(out, Entry{Cell: i, Level: h.Level, Title: h.Title})28 }29 if cells && (c.Kind == notebook.Code || len(heads) == 0) {30 out = append(out, Entry{Cell: i, Title: firstLine(c.Source)})31 }32 }33 return out34}3536// headings are a note's Markdown headings, outside code fences.37func headings(c notebook.Cell) []Entry {38 if c.Kind != notebook.Note {39 return nil40 }41 var out []Entry42 fenced := false43 for line := range strings.SplitSeq(c.Source, "\n") {44 t := strings.TrimSpace(line)45 if strings.HasPrefix(t, "```") {46 fenced = !fenced47 }48 level := len(t) - len(strings.TrimLeft(t, "#"))49 if fenced || level == 0 || level > 6 || len(t) > level && t[level] != ' ' {50 continue51 }52 if title := strings.TrimSpace(t[level:]); title != "" {53 out = append(out, Entry{Level: level, Title: title})54 }55 }56 return out57}5859// firstLine is a source's first line that isn't blank, shortened.60func firstLine(src string) string {61 for line := range strings.SplitSeq(src, "\n") {62 if t := strings.TrimSpace(line); t != "" {63 return ansi.Truncate(t, 56, "…")64 }65 }66 return "(empty)"67}