Compare commits

...

5 commits

Author SHA1 Message Date
Ayman Bagabas
b541514436
chore: update deps 2024-05-14 13:25:02 -04:00
Ayman Bagabas
ab51fe2632
chore: use proposed api 2024-05-13 17:01:14 -04:00
Ayman Bagabas
3104f60786
fix: use lipgloss std defaults 2024-05-13 13:32:13 -04:00
Ayman Bagabas
bb6a1e5561
fix: conform to the new tea.Model interface 2024-05-13 13:18:08 -04:00
Ayman Bagabas
71704a1c0b
chore: use proposed api 2024-05-13 13:17:44 -04:00
8 changed files with 308 additions and 817 deletions

28
go.mod
View file

@ -1,14 +1,14 @@
module github.com/charmbracelet/glow
go 1.17
go 1.18
require (
github.com/atotto/clipboard v0.1.4
github.com/charmbracelet/bubbles v0.15.0
github.com/charmbracelet/bubbletea v0.25.0
github.com/charmbracelet/bubbles v0.18.1-0.20240514172444-4491293fd12c
github.com/charmbracelet/bubbletea v0.26.3-0.20240514172421-c69b27d7eeb8
github.com/charmbracelet/charm v0.8.7
github.com/charmbracelet/glamour v0.6.0
github.com/charmbracelet/lipgloss v0.6.0
github.com/charmbracelet/lipgloss v0.10.1-0.20240514172355-88ecbb398449
github.com/charmbracelet/x/editor v0.0.0-20231116172829-450eedbca1ab
github.com/dustin/go-humanize v1.0.1
github.com/mattn/go-runewidth v0.0.15
@ -17,12 +17,11 @@ require (
github.com/muesli/gitcha v0.2.0
github.com/muesli/go-app-paths v0.2.2
github.com/muesli/reflow v0.3.0
github.com/muesli/termenv v0.15.2
github.com/sahilm/fuzzy v0.1.0
github.com/sahilm/fuzzy v0.1.1
github.com/segmentio/ksuid v1.0.4
github.com/spf13/cobra v1.8.0
github.com/spf13/viper v1.14.0
golang.org/x/sys v0.17.0
golang.org/x/sys v0.20.0
golang.org/x/term v0.15.0
golang.org/x/text v0.14.0
)
@ -32,9 +31,13 @@ require (
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/calmh/randomart v1.1.0 // indirect
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
github.com/charmbracelet/x/ansi v0.1.1 // indirect
github.com/charmbracelet/x/input v0.1.0 // indirect
github.com/charmbracelet/x/term v0.1.1 // indirect
github.com/charmbracelet/x/windows v0.1.0 // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
github.com/dlclark/regexp2 v1.4.0 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/gorilla/css v1.0.0 // indirect
@ -43,28 +46,29 @@ require (
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/magiconair/properties v1.8.6 // indirect
github.com/mattn/go-isatty v0.0.18 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/microcosm-cc/bluemonday v1.0.21 // indirect
github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/sasquatch v0.0.0-20200811221207-66979d92330a // indirect
github.com/muesli/termenv v0.15.2 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/sabhiram/go-gitignore v0.0.0-20180611051255-d3107576ba94 // indirect
github.com/spf13/afero v1.9.2 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.4.1 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/yuin/goldmark v1.5.2 // indirect
github.com/yuin/goldmark-emoji v1.0.1 // indirect
golang.org/x/crypto v0.17.0 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sync v0.7.0 // indirect
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect

644
go.sum

File diff suppressed because it is too large Load diff

View file

@ -138,12 +138,12 @@ type pagerModel struct {
func newPagerModel(common *commonModel) pagerModel {
// Init viewport
vp := viewport.New(0, 0)
vp := viewport.New(common.ctx, 0, 0)
vp.YPosition = 0
vp.HighPerformanceRendering = config.HighPerformancePager
// Text input for notes/memos
ti := textinput.New()
ti := textinput.New(common.ctx)
ti.Prompt = " > "
ti.PromptStyle = pagerNoteInputPromptStyle
ti.TextStyle = pagerNoteInputStyle
@ -152,7 +152,7 @@ func newPagerModel(common *commonModel) pagerModel {
ti.Focus()
// Text input for search
sp := spinner.New()
sp := spinner.New(common.ctx)
sp.Style = spinnerStyle
return pagerModel{
@ -219,7 +219,7 @@ func (m *pagerModel) unload() {
m.textInput.Reset()
}
func (m pagerModel) update(msg tea.Msg) (pagerModel, tea.Cmd) {
func (m pagerModel) update(ctx tea.Context, msg tea.Msg) (pagerModel, tea.Cmd) {
var (
cmd tea.Cmd
cmds []tea.Cmd
@ -339,7 +339,7 @@ func (m pagerModel) update(msg tea.Msg) (pagerModel, tea.Cmd) {
// full lifetime. In either case we need to spin the spinner
// irrespective of it's more fine-grained visibility rules.
var cmd tea.Cmd
m.spinner, cmd = m.spinner.Update(msg)
m.spinner, cmd = m.spinner.Update(ctx, msg)
cmds = append(cmds, cmd)
} else if m.state == pagerStateStashSuccess {
// Successful stash. Stop spinning and update accordingly.
@ -396,10 +396,10 @@ func (m pagerModel) update(msg tea.Msg) (pagerModel, tea.Cmd) {
switch m.state {
case pagerStateSetNote:
m.textInput, cmd = m.textInput.Update(msg)
m.textInput, cmd = m.textInput.Update(ctx, msg)
cmds = append(cmds, cmd)
default:
m.viewport, cmd = m.viewport.Update(msg)
m.viewport, cmd = m.viewport.Update(ctx, msg)
cmds = append(cmds, cmd)
}
@ -414,16 +414,16 @@ func (m pagerModel) spinnerVisible() bool {
return now.After(windowStart) && now.Before(windowEnd)
}
func (m pagerModel) View() string {
func (m pagerModel) View(ctx tea.Context) string {
var b strings.Builder
fmt.Fprint(&b, m.viewport.View()+"\n")
fmt.Fprint(&b, m.viewport.View(ctx)+"\n")
// Footer
switch m.state {
case pagerStateSetNote:
m.setNoteView(&b)
default:
m.statusBarView(&b)
m.statusBarView(ctx, &b)
}
if m.showHelp {
@ -433,7 +433,7 @@ func (m pagerModel) View() string {
return b.String()
}
func (m pagerModel) statusBarView(b *strings.Builder) {
func (m pagerModel) statusBarView(ctx tea.Context, b *strings.Builder) {
const (
minPercent float64 = 0.0
maxPercent float64 = 1.0
@ -444,7 +444,7 @@ func (m pagerModel) statusBarView(b *strings.Builder) {
showStatusMessage := m.state == pagerStateStatusMessage
// Logo
logo := glowLogoView(" Glow ")
logo := m.common.styles.LogoStyle.Render(" Glow ")
// Scroll percent
percent := math.Max(minPercent, math.Min(maxPercent, m.viewport.ScrollPercent()))
@ -468,7 +468,7 @@ func (m pagerModel) statusBarView(b *strings.Builder) {
if m.state == pagerStateStashing || m.state == pagerStateStashSuccess {
var spinner string
if m.spinnerVisible() {
spinner = m.spinner.View()
spinner = m.spinner.View(ctx)
}
statusIndicator = statusBarNoteStyle(" ") + spinner
} else if isStashed && showStatusMessage {
@ -528,7 +528,7 @@ func (m pagerModel) statusBarView(b *strings.Builder) {
func (m pagerModel) setNoteView(b *strings.Builder) {
fmt.Fprint(b, noteHeading)
fmt.Fprint(b, m.textInput.View())
fmt.Fprint(b, m.textInput.View(m.common.ctx))
}
func (m pagerModel) helpView() (s string) {

View file

@ -14,7 +14,6 @@ import (
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/charm"
"github.com/charmbracelet/lipgloss"
"github.com/muesli/reflow/ansi"
"github.com/muesli/reflow/truncate"
"github.com/sahilm/fuzzy"
@ -33,26 +32,6 @@ var (
alreadyStashedStatusMessage = statusMessage{subtleStatusMessage, "Already stashed"}
)
var (
dividerDot = darkGrayFg.SetString(" • ")
dividerBar = darkGrayFg.SetString(" │ ")
offlineHeaderNote = darkGrayFg.SetString("(Offline)")
logoStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("#ECFD65")).
Background(fuchsia).
Bold(true)
stashSpinnerStyle = lipgloss.NewStyle().
Foreground(gray)
stashInputPromptStyle = lipgloss.NewStyle().
Foreground(yellowGreen).
MarginRight(1)
stashInputCursorStyle = lipgloss.NewStyle().
Foreground(fuchsia).
MarginRight(1)
)
// MSG
type (
@ -134,41 +113,41 @@ type statusMessage struct {
message string
}
func initSections() {
func initSections(c *commonModel) {
sections = map[sectionKey]section{
localSection: {
key: localSection,
docTypes: NewDocTypeSet(LocalDoc),
paginator: newStashPaginator(),
paginator: newStashPaginator(c),
},
stashedSection: {
key: stashedSection,
docTypes: NewDocTypeSet(StashedDoc, ConvertedDoc),
paginator: newStashPaginator(),
paginator: newStashPaginator(c),
},
newsSection: {
key: newsSection,
docTypes: NewDocTypeSet(NewsDoc),
paginator: newStashPaginator(),
paginator: newStashPaginator(c),
},
filterSection: {
key: filterSection,
docTypes: DocTypeSet{},
paginator: newStashPaginator(),
paginator: newStashPaginator(c),
},
}
}
// String returns a styled version of the status message appropriate for the
// Render returns a styled version of the status message appropriate for the
// given context.
func (s statusMessage) String() string {
func (s statusMessage) Render(st *styles) string {
switch s.status {
case subtleStatusMessage:
return dimGreenFg(s.message)
return st.DimGreenFg(s.message)
case errorStatusMessage:
return redFg(s.message)
return st.RedFg(s.message)
default:
return greenFg(s.message)
return st.GreenFg(s.message)
}
}
@ -514,21 +493,21 @@ func (m *stashModel) moveCursorDown() {
// INIT
func newStashModel(common *commonModel) stashModel {
sp := spinner.New()
sp := spinner.New(common.ctx)
sp.Spinner = spinner.Line
sp.Style = stashSpinnerStyle
sp.Style = common.styles.StashSpinnerStyle
ni := textinput.New()
ni := textinput.New(common.ctx)
ni.Prompt = "Memo:"
ni.PromptStyle = stashInputPromptStyle
ni.CursorStyle = stashInputCursorStyle
ni.PromptStyle = common.styles.StashInputPromptStyle
ni.CursorStyle = common.styles.StashInputCursorStyle
ni.CharLimit = noteCharacterLimit
ni.Focus()
si := textinput.New()
si := textinput.New(common.ctx)
si.Prompt = "Find:"
si.PromptStyle = stashInputPromptStyle
si.CursorStyle = stashInputCursorStyle
si.PromptStyle = common.styles.StashInputPromptStyle
si.CursorStyle = common.styles.StashInputCursorStyle
si.CharLimit = noteCharacterLimit
si.Focus()
@ -563,17 +542,17 @@ func newStashModel(common *commonModel) stashModel {
return m
}
func newStashPaginator() paginator.Model {
p := paginator.New()
func newStashPaginator(common *commonModel) paginator.Model {
p := paginator.New(common.ctx)
p.Type = paginator.Dots
p.ActiveDot = brightGrayFg("•")
p.InactiveDot = darkGrayFg.Render("•")
p.ActiveDot = common.styles.BrightGrayFg("•")
p.InactiveDot = common.styles.DarkGrayFg("•")
return p
}
// UPDATE
func (m stashModel) update(msg tea.Msg) (stashModel, tea.Cmd) {
func (m stashModel) update(ctx tea.Context, msg tea.Msg) (stashModel, tea.Cmd) {
var cmds []tea.Cmd
switch msg := msg.(type) {
@ -651,7 +630,7 @@ func (m stashModel) update(msg tea.Msg) (stashModel, tea.Cmd) {
case spinner.TickMsg:
if m.shouldSpin() {
var cmd tea.Cmd
m.spinner, cmd = m.spinner.Update(msg)
m.spinner, cmd = m.spinner.Update(ctx, msg)
cmds = append(cmds, cmd)
}
@ -919,7 +898,7 @@ func (m *stashModel) handleDocumentBrowsing(msg tea.Msg) tea.Cmd {
// Update paginator. Pagination key handling is done here, but it could
// also be moved up to this level, in which case we'd use model methods
// like model.PageUp().
newPaginatorModel, cmd := m.paginator().Update(msg)
newPaginatorModel, cmd := m.paginator().Update(m.common.ctx, msg)
m.setPaginator(newPaginatorModel)
cmds = append(cmds, cmd)
@ -1071,7 +1050,7 @@ func (m *stashModel) handleFiltering(msg tea.Msg) tea.Cmd {
}
// Update the filter text input component
newFilterInputModel, inputCmd := m.filterInput.Update(msg)
newFilterInputModel, inputCmd := m.filterInput.Update(m.common.ctx, msg)
currentFilterVal := m.filterInput.Value()
newFilterVal := newFilterInputModel.Value()
m.filterInput = newFilterInputModel
@ -1128,7 +1107,7 @@ func (m *stashModel) handleNoteInput(msg tea.Msg) tea.Cmd {
}
// Update the note text input component
newNoteInputModel, noteInputCmd := m.noteInput.Update(msg)
newNoteInputModel, noteInputCmd := m.noteInput.Update(m.common.ctx, msg)
m.noteInput = newNoteInputModel
cmds = append(cmds, noteInputCmd)
@ -1137,26 +1116,26 @@ func (m *stashModel) handleNoteInput(msg tea.Msg) tea.Cmd {
// VIEW
func (m stashModel) view() string {
func (m stashModel) view(ctx tea.Context) string {
var s string
switch m.viewState {
case stashStateShowingError:
return errorView(m.err, false)
return errorView(&m.common.styles, m.err, false)
case stashStateLoadingDocument:
s += " " + m.spinner.View() + " Loading document..."
s += " " + m.spinner.View(ctx) + " Loading document..."
case stashStateReady:
loadingIndicator := " "
if m.shouldSpin() {
loadingIndicator = m.spinner.View()
loadingIndicator = m.spinner.View(ctx)
}
var header string
switch m.selectionState {
case selectionPromptingDelete:
header = redFg("Delete this item from your stash? ") + faintRedFg("(y/N)")
header = m.common.styles.RedFg("Delete this item from your stash? ") + m.common.styles.FaintRedFg("(y/N)")
case selectionSettingNote:
header = yellowFg("Set the memo for this item?")
header = m.common.styles.YellowFg("Set the memo for this item?")
}
// Only draw the normal header if we're not using the header area for
@ -1168,13 +1147,13 @@ func (m stashModel) view() string {
// Rules for the logo, filter and status message.
logoOrFilter := " "
if m.showStatusMessage && m.filterState == filtering {
logoOrFilter += m.statusMessage.String()
logoOrFilter += m.statusMessage.Render(&m.common.styles)
} else if m.filterState == filtering {
logoOrFilter += m.filterInput.View()
logoOrFilter += m.filterInput.View(ctx)
} else {
logoOrFilter += glowLogoView(" Glow ")
logoOrFilter += m.common.styles.LogoStyle.Render(" Glow ")
if m.showStatusMessage {
logoOrFilter += " " + m.statusMessage.String()
logoOrFilter += " " + m.statusMessage.Render(&m.common.styles)
}
}
logoOrFilter = truncate.StringWithTail(logoOrFilter, uint(m.common.width-1), ellipsis)
@ -1195,7 +1174,7 @@ func (m stashModel) view() string {
var pagination string
if m.paginator().TotalPages > 1 {
pagination = m.paginator().View()
pagination = m.paginator().View(ctx)
// If the dot pagination is wider than the width of the window
// use the arabic paginator.
@ -1210,7 +1189,7 @@ func (m stashModel) view() string {
// pointers in our model should be refactored away.
var p paginator.Model = *(m.paginator())
p.Type = paginator.Arabic
pagination = paginationStyle.Render(p.View())
pagination = m.common.styles.PaginationStyle.Render(p.View(ctx))
}
// We could also look at m.stashFullyLoaded and add an indicator
@ -1232,10 +1211,6 @@ func (m stashModel) view() string {
return "\n" + indent(s, stashIndent)
}
func glowLogoView(text string) string {
return logoStyle.Render(text)
}
func (m stashModel) headerView() string {
localCount := m.countMarkdowns(LocalDoc)
stashedCount := m.countMarkdowns(StashedDoc) + m.countMarkdowns(ConvertedDoc)
@ -1246,7 +1221,7 @@ func (m stashModel) headerView() string {
// Filter results
if m.filterState == filtering {
if localCount+stashedCount+newsCount == 0 {
return grayFg("Nothing found.")
return m.common.styles.GrayFg("Nothing found.")
}
if localCount > 0 {
sections = append(sections, fmt.Sprintf("%d local", localCount))
@ -1256,22 +1231,22 @@ func (m stashModel) headerView() string {
}
for i := range sections {
sections[i] = grayFg(sections[i])
sections[i] = m.common.styles.GrayFg(sections[i])
}
return strings.Join(sections, dividerDot.String())
return strings.Join(sections, m.common.styles.DividerDot)
}
if m.loadingDone() && len(m.markdowns) == 0 {
var maybeOffline string
if m.common.authStatus == authFailed {
maybeOffline = " " + offlineHeaderNote.String()
maybeOffline = " " + m.common.styles.OfflineHeaderNote
}
if m.stashedOnly() {
return subtleStyle.Render("Cant load stash") + maybeOffline
return m.common.styles.SubtleStyle.Render("Cant load stash") + maybeOffline
}
return subtleStyle.Render("No markdown files found") + maybeOffline
return m.common.styles.SubtleStyle.Render("No markdown files found") + maybeOffline
}
// Tabs
@ -1299,16 +1274,16 @@ func (m stashModel) headerView() string {
}
if m.sectionIndex == i && len(m.sections) > 1 {
s = selectedTabStyle.Render(s)
s = m.common.styles.SelectedTabStyle.Render(s)
} else {
s = tabStyle.Render(s)
s = m.common.styles.TabStyle.Render(s)
}
sections = append(sections, s)
}
s := strings.Join(sections, dividerBar.String())
s := strings.Join(sections, m.common.styles.DividerBar)
if m.common.authStatus == authFailed {
s += dividerDot.String() + offlineHeaderNote.String()
s += m.common.styles.DividerDot + m.common.styles.OfflineHeaderNote
}
return s
@ -1322,7 +1297,7 @@ func (m stashModel) populatedView() string {
// Empty states
if len(mds) == 0 {
f := func(s string) {
b.WriteString(" " + grayFg(s))
b.WriteString(" " + m.common.styles.GrayFg(s))
}
switch m.sections[m.sectionIndex].key {

View file

@ -30,7 +30,7 @@ func newHelpColumn(pairs ...string) (h helpColumn) {
}
// render returns styled and formatted rows from keys and values.
func (h helpColumn) render(height int) (rows []string) {
func (h helpColumn) render(st *styles, height int) (rows []string) {
keyWidth, valWidth := h.maxWidths()
for i := 0; i < height; i++ {
@ -44,11 +44,11 @@ func (h helpColumn) render(height int) (rows []string) {
switch k {
case "s":
k = greenFg(k)
v = semiDimGreenFg(v)
k = st.GreenFg(k)
v = st.SemiDimGreenFg(v)
default:
k = grayFg(k)
v = midGrayFg(v)
k = st.GrayFg(k)
v = st.MidGrayFg(v)
}
}
b.WriteString(k)
@ -196,12 +196,13 @@ func (m stashModel) renderHelp(groups ...[]string) (string, int) {
// would otherwise wrap to two lines. Help view entries should come in as pairs,
// with the first being the key and the second being the help text.
func (m stashModel) miniHelpView(entries ...string) string {
st := &m.common.styles
if len(entries) == 0 {
return ""
}
var (
truncationChar = subtleStyle.Render("…")
truncationChar = st.SubtleStyle.Render("…")
truncationWidth = ansi.PrintableRuneWidth(truncationChar)
)
@ -221,17 +222,17 @@ func (m stashModel) miniHelpView(entries ...string) string {
switch k {
case "s":
k = greenFg(k)
v = semiDimGreenFg(v)
k = st.GreenFg(k)
v = st.SemiDimGreenFg(v)
default:
k = grayFg(k)
v = midGrayFg(v)
k = st.GrayFg(k)
v = st.MidGrayFg(v)
}
next = fmt.Sprintf("%s %s", k, v)
if i < len(entries)-2 {
next += dividerDot.String()
next += st.DividerDot
}
// Only this (and the following) help text items if we have the
@ -269,7 +270,7 @@ func (m stashModel) fullHelpView(groups ...[]string) string {
// Build columns
for _, c := range columns {
renderedCols = append(renderedCols, c.render(tallestCol))
renderedCols = append(renderedCols, c.render(&m.common.styles, tallestCol))
}
// Merge columns

View file

@ -46,6 +46,7 @@ func stashItemView(b *strings.Builder, m stashModel, index int, md *markdown) {
isSelected := index == m.cursor()
isFiltering := m.filterState == filtering
singleFilteredItem := isFiltering && len(m.getVisibleMarkdowns()) == 1
styles := &m.common.styles
// If there are multiple items being filtered don't highlight a selected
// item in the results. If we've filtered down to one item, however,
@ -55,33 +56,33 @@ func stashItemView(b *strings.Builder, m stashModel, index int, md *markdown) {
switch m.selectionState {
case selectionPromptingDelete:
gutter = faintRedFg(verticalLine)
icon = faintRedFg(icon)
title = redFg(title)
date = faintRedFg(date)
gutter = styles.FaintRedFg(verticalLine)
icon = styles.FaintRedFg(icon)
title = styles.RedFg(title)
date = styles.FaintRedFg(date)
case selectionSettingNote:
gutter = dullYellowFg(verticalLine)
gutter = styles.DullYellowFg(verticalLine)
icon = ""
title = m.noteInput.View()
date = dullYellowFg(date)
title = m.noteInput.View(m.common.ctx)
date = styles.DullYellowFg(date)
default:
if m.common.latestFileStashed == md.stashID &&
m.statusMessage == stashingStatusMessage {
gutter = greenFg(verticalLine)
icon = dimGreenFg(icon)
title = greenFg(title)
date = semiDimGreenFg(date)
gutter = styles.GreenFg(verticalLine)
icon = styles.DimGreenFg(icon)
title = styles.GreenFg(title)
date = styles.SemiDimGreenFg(date)
} else {
gutter = dullFuchsiaFg(verticalLine)
icon = dullFuchsiaFg(icon)
gutter = styles.DullFuchsiaFg(verticalLine)
icon = styles.DullFuchsiaFg(icon)
if m.currentSection().key == filterSection &&
m.filterState == filterApplied || singleFilteredItem {
s := lipgloss.NewStyle().Foreground(fuchsia)
title = styleFilteredText(title, m.filterInput.Value(), s, s.Copy().Underline(true))
} else {
title = fuchsiaFg(title)
title = styles.FuchsiaFg(title)
}
date = dullFuchsiaFg(date)
date = styles.DullFuchsiaFg(date)
}
}
} else {
@ -91,35 +92,35 @@ func stashItemView(b *strings.Builder, m stashModel, index int, md *markdown) {
if m.common.latestFileStashed == md.stashID &&
m.statusMessage == stashingStatusMessage {
icon = dimGreenFg(icon)
title = greenFg(title)
date = semiDimGreenFg(date)
icon = styles.DimGreenFg(icon)
title = styles.GreenFg(title)
date = styles.SemiDimGreenFg(date)
} else if md.docType == NewsDoc {
if isFiltering && m.filterInput.Value() == "" {
title = dimIndigoFg(title)
date = dimSubtleIndigoFg(date)
title = styles.DimIndigoFg(title)
date = styles.DimSubtleIndigoFg(date)
} else {
s := lipgloss.NewStyle().Foreground(indigo)
title = styleFilteredText(title, m.filterInput.Value(), s, s.Copy().Underline(true))
date = subtleIndigoFg(date)
date = styles.SubtleIndigoFg(date)
}
} else if isFiltering && m.filterInput.Value() == "" {
icon = dimGreenFg(icon)
icon = styles.DimGreenFg(icon)
if title == noMemoTitle {
title = dimBrightGrayFg(title)
title = styles.DimBrightGrayFg(title)
} else {
title = dimNormalFg(title)
title = styles.DimNormalFg(title)
}
date = dimBrightGrayFg(date)
date = styles.DimBrightGrayFg(date)
} else {
icon = greenFg(icon)
icon = styles.GreenFg(icon)
if title == noMemoTitle {
title = brightGrayFg(title)
title = styles.BrightGrayFg(title)
} else {
s := lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "#1a1a1a", Dark: "#dddddd"})
title = styleFilteredText(title, m.filterInput.Value(), s, s.Copy().Underline(true))
}
date = brightGrayFg(date)
date = styles.BrightGrayFg(date)
}
}

View file

@ -1,6 +1,10 @@
package ui
import . "github.com/charmbracelet/lipgloss" //nolint: revive
import (
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
. "github.com/charmbracelet/lipgloss"
) //nolint: revive
// Colors.
var (
@ -30,55 +34,125 @@ var (
dimGreen = AdaptiveColor{Light: "#72D2B0", Dark: "#0B5137"}
)
type styler func(...string) string
// Ulimately, we'll transition to named styles.
// nolint:deadcode,unused,varcheck
var (
normalFg = NewStyle().Foreground(normal).Render
dimNormalFg = NewStyle().Foreground(normalDim).Render
type styles struct {
NormalFg styler
DimNormalFg styler
brightGrayFg = NewStyle().Foreground(brightGray).Render
dimBrightGrayFg = NewStyle().Foreground(dimBrightGray).Render
BrightGrayFg styler
DimBrightGrayFg styler
grayFg = NewStyle().Foreground(gray).Render
midGrayFg = NewStyle().Foreground(midGray).Render
darkGrayFg = NewStyle().Foreground(darkGray)
GrayFg styler
MidGrayFg styler
DarkGrayFg styler
greenFg = NewStyle().Foreground(green).Render
semiDimGreenFg = NewStyle().Foreground(semiDimGreen).Render
dimGreenFg = NewStyle().Foreground(dimGreen).Render
GreenFg styler
SemiDimGreenFg styler
DimGreenFg styler
fuchsiaFg = NewStyle().Foreground(fuchsia).Render
dimFuchsiaFg = NewStyle().Foreground(dimFuchsia).Render
FuchsiaFg styler
DimFuchsiaFg styler
dullFuchsiaFg = NewStyle().Foreground(dullFuchsia).Render
dimDullFuchsiaFg = NewStyle().Foreground(dimDullFuchsia).Render
DullFuchsiaFg styler
DimDullFuchsiaFg styler
indigoFg = NewStyle().Foreground(fuchsia).Render
dimIndigoFg = NewStyle().Foreground(dimIndigo).Render
IndigoFg styler
DimIndigoFg styler
subtleIndigoFg = NewStyle().Foreground(subtleIndigo).Render
dimSubtleIndigoFg = NewStyle().Foreground(dimSubtleIndigo).Render
SubtleIndigoFg styler
DimSubtleIndigoFg styler
yellowFg = NewStyle().Foreground(yellowGreen).Render // renders light green on light backgrounds
dullYellowFg = NewStyle().Foreground(dullYellowGreen).Render // renders light green on light backgrounds
redFg = NewStyle().Foreground(red).Render
faintRedFg = NewStyle().Foreground(faintRed).Render
)
YellowFg styler
DullYellowFg styler
RedFg styler
FaintRedFg styler
var (
tabStyle = NewStyle().
Foreground(AdaptiveColor{Light: "#909090", Dark: "#626262"})
TabStyle Style
SelectedTabStyle Style
ErrorTitleStyle Style
SubtleStyle Style
PaginationStyle Style
selectedTabStyle = NewStyle().
Foreground(AdaptiveColor{Light: "#333333", Dark: "#979797"})
LogoStyle Style
StashSpinnerStyle Style
StashInputPromptStyle Style
StashInputCursorStyle Style
errorTitleStyle = NewStyle().
DividerDot string
DividerBar string
OfflineHeaderNote string
}
func defaultStyles(ctx tea.Context) styles {
darkGrayFg := ctx.NewStyle().Foreground(darkGray).Render
subtleStyle := ctx.NewStyle().
Foreground(AdaptiveColor{Light: "#9B9B9B", Dark: "#5C5C5C"})
return styles{
NormalFg: ctx.NewStyle().Foreground(normal).Render,
DimNormalFg: ctx.NewStyle().Foreground(normalDim).Render,
BrightGrayFg: ctx.NewStyle().Foreground(brightGray).Render,
DimBrightGrayFg: ctx.NewStyle().Foreground(dimBrightGray).Render,
GrayFg: ctx.NewStyle().Foreground(gray).Render,
MidGrayFg: ctx.NewStyle().Foreground(midGray).Render,
DarkGrayFg: darkGrayFg,
GreenFg: ctx.NewStyle().Foreground(green).Render,
SemiDimGreenFg: ctx.NewStyle().Foreground(semiDimGreen).Render,
DimGreenFg: ctx.NewStyle().Foreground(dimGreen).Render,
FuchsiaFg: ctx.NewStyle().Foreground(fuchsia).Render,
DimFuchsiaFg: ctx.NewStyle().Foreground(dimFuchsia).Render,
DullFuchsiaFg: ctx.NewStyle().Foreground(dullFuchsia).Render,
DimDullFuchsiaFg: ctx.NewStyle().Foreground(dimDullFuchsia).Render,
IndigoFg: ctx.NewStyle().Foreground(fuchsia).Render,
DimIndigoFg: ctx.NewStyle().Foreground(dimIndigo).Render,
SubtleIndigoFg: ctx.NewStyle().Foreground(subtleIndigo).Render,
DimSubtleIndigoFg: ctx.NewStyle().Foreground(dimSubtleIndigo).Render,
YellowFg: ctx.NewStyle().Foreground(yellowGreen).Render, // renders light green on light backgrounds
DullYellowFg: ctx.NewStyle().Foreground(dullYellowGreen).Render, // renders light green on light backgrounds
RedFg: ctx.NewStyle().Foreground(red).Render,
FaintRedFg: ctx.NewStyle().Foreground(faintRed).Render,
TabStyle: ctx.NewStyle().
Foreground(AdaptiveColor{Light: "#909090", Dark: "#626262"}),
SelectedTabStyle: ctx.NewStyle().
Foreground(AdaptiveColor{Light: "#333333", Dark: "#979797"}),
ErrorTitleStyle: ctx.NewStyle().
Foreground(cream).
Background(red).
Padding(0, 1)
Padding(0, 1),
subtleStyle = NewStyle().
Foreground(AdaptiveColor{Light: "#9B9B9B", Dark: "#5C5C5C"})
SubtleStyle: subtleStyle,
paginationStyle = subtleStyle.Copy()
)
PaginationStyle: subtleStyle.Copy(),
LogoStyle: ctx.NewStyle().
Foreground(lipgloss.Color("#ECFD65")).
Background(fuchsia).
Bold(true),
StashSpinnerStyle: ctx.NewStyle().
Foreground(gray),
StashInputPromptStyle: ctx.NewStyle().
Foreground(yellowGreen).
MarginRight(1),
StashInputCursorStyle: ctx.NewStyle().
Foreground(fuchsia).
MarginRight(1),
DividerDot: darkGrayFg(" • "),
DividerBar: darkGrayFg(" │ "),
OfflineHeaderNote: darkGrayFg("(Offline)"),
}
}

View file

@ -14,7 +14,6 @@ import (
"github.com/charmbracelet/charm/keygen"
"github.com/charmbracelet/glow/utils"
"github.com/muesli/gitcha"
te "github.com/muesli/termenv"
"github.com/segmentio/ksuid"
)
@ -141,6 +140,7 @@ const (
// Common stuff we'll need to access in all models.
type commonModel struct {
ctx tea.Context
cfg Config
cc *charm.Client
cwd string
@ -158,6 +158,8 @@ type commonModel struct {
// Files currently being stashed. We remove files from this set once
// a stash operation has either succeeded or failed.
filesStashing map[ksuid.KSUID]struct{}
styles styles
}
func (c commonModel) isStashing() bool {
@ -199,16 +201,6 @@ func (m *model) unloadDocument() []tea.Cmd {
}
func newModel(cfg Config) tea.Model {
initSections()
if cfg.GlamourStyle == "auto" {
if te.HasDarkBackground() {
cfg.GlamourStyle = "dark"
} else {
cfg.GlamourStyle = "light"
}
}
if len(cfg.DocumentTypes) == 0 {
cfg.DocumentTypes.Add(LocalDoc, StashedDoc, ConvertedDoc, NewsDoc)
}
@ -224,12 +216,28 @@ func newModel(cfg Config) tea.Model {
common: &common,
state: stateShowStash,
keygenState: keygenUnstarted,
pager: newPagerModel(&common),
stash: newStashModel(&common),
}
}
func (m model) Init() tea.Cmd {
func (m model) Init(ctx tea.Context) (tea.Model, tea.Cmd) {
if m.common.cfg.GlamourStyle == "auto" {
if ctx.HasLightBackground() {
m.common.cfg.GlamourStyle = "light"
} else {
m.common.cfg.GlamourStyle = "dark"
}
}
// Initialize styles & sub-models
m.common.ctx = ctx
m.common.styles = defaultStyles(ctx)
// Initialize sections after we have the styles
initSections(m.common)
m.pager = newPagerModel(m.common)
m.stash = newStashModel(m.common)
var cmds []tea.Cmd
d := m.common.cfg.DocumentTypes
@ -244,10 +252,10 @@ func (m model) Init() tea.Cmd {
cmds = append(cmds, findLocalFiles(m))
}
return tea.Batch(cmds...)
return m, tea.Batch(cmds...)
}
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
func (m model) Update(ctx tea.Context, msg tea.Msg) (tea.Model, tea.Cmd) {
// If there's been an error, any key exits
if m.fatalErr != nil {
if _, ok := msg.(tea.KeyMsg); ok {
@ -273,7 +281,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case stateShowStash:
// pass through all keys if we're editing the filter
if m.stash.filterState == filtering || m.stash.selectionState == selectionSettingNote {
m.stash, cmd = m.stash.update(msg)
m.stash, cmd = m.stash.update(ctx, msg)
return m, cmd
}
@ -283,7 +291,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// If setting a note send all keys straight through
case pagerStateSetNote:
var batch []tea.Cmd
newPagerModel, cmd := m.pager.update(msg)
newPagerModel, cmd := m.pager.update(ctx, msg)
m.pager = newPagerModel
batch = append(batch, cmd)
return m, tea.Batch(batch...)
@ -370,7 +378,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// A note was saved to a document. This will have been done in the
// pager, so we'll need to find the corresponding note in the stash.
// So, pass the message to the stash for processing.
stashModel, cmd := m.stash.update(msg)
stashModel, cmd := m.stash.update(ctx, msg)
m.stash = stashModel
return m, cmd
@ -378,7 +386,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// Always pass these messages to the stash so we can keep it updated
// about network activity, even if the user isn't currently viewing
// the stash.
stashModel, cmd := m.stash.update(msg)
stashModel, cmd := m.stash.update(ctx, msg)
m.stash = stashModel
return m, cmd
@ -424,7 +432,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case filteredMarkdownMsg:
if m.state == stateShowDocument {
newStashModel, cmd := m.stash.update(msg)
newStashModel, cmd := m.stash.update(ctx, msg)
m.stash = newStashModel
cmds = append(cmds, cmd)
}
@ -433,12 +441,12 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// Process children
switch m.state {
case stateShowStash:
newStashModel, cmd := m.stash.update(msg)
newStashModel, cmd := m.stash.update(ctx, msg)
m.stash = newStashModel
cmds = append(cmds, cmd)
case stateShowDocument:
newPagerModel, cmd := m.pager.update(msg)
newPagerModel, cmd := m.pager.update(ctx, msg)
m.pager = newPagerModel
cmds = append(cmds, cmd)
}
@ -446,20 +454,20 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, tea.Batch(cmds...)
}
func (m model) View() string {
func (m model) View(ctx tea.Context) string {
if m.fatalErr != nil {
return errorView(m.fatalErr, true)
return errorView(&m.common.styles, m.fatalErr, true)
}
switch m.state {
case stateShowDocument:
return m.pager.View()
return m.pager.View(ctx)
default:
return m.stash.view()
return m.stash.view(ctx)
}
}
func errorView(err error, fatal bool) string {
func errorView(st *styles, err error, fatal bool) string {
exitMsg := "press any key to "
if fatal {
exitMsg += "exit"
@ -467,9 +475,9 @@ func errorView(err error, fatal bool) string {
exitMsg += "return"
}
s := fmt.Sprintf("%s\n\n%v\n\n%s",
errorTitleStyle.Render("ERROR"),
st.ErrorTitleStyle.Render("ERROR"),
err,
subtleStyle.Render(exitMsg),
st.SubtleStyle.Render(exitMsg),
)
return "\n" + indent(s, 3)
}