mirror of
https://github.com/charmbracelet/glow
synced 2024-11-05 16:23:00 +00:00
Add functionality for setting document note in the pager
This commit is contained in:
parent
c2bb505182
commit
04f1ebfa23
5 changed files with 170 additions and 35 deletions
5
go.mod
5
go.mod
|
@ -3,7 +3,7 @@ module github.com/charmbracelet/glow
|
|||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/charmbracelet/boba v0.6.0
|
||||
github.com/charmbracelet/boba v0.6.1
|
||||
github.com/charmbracelet/charm v0.4.1
|
||||
github.com/charmbracelet/glamour v0.2.0
|
||||
github.com/charmbracelet/tea v0.3.0
|
||||
|
@ -12,8 +12,9 @@ require (
|
|||
github.com/dustin/go-humanize v1.0.1-0.20200219035652-afde56e7acac
|
||||
github.com/muesli/reflow v0.1.0
|
||||
github.com/muesli/termenv v0.6.0
|
||||
github.com/pkg/term v0.0.0-20200520122047-c3ffed290a03 // indirect
|
||||
github.com/spf13/cobra v0.0.7
|
||||
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 // indirect
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299
|
||||
)
|
||||
|
|
11
go.sum
11
go.sum
|
@ -16,8 +16,8 @@ github.com/calmh/randomart v1.1.0 h1:evl+iwc10LXtHdMZhzLxmsCQVmWnkXs44SbC6Uk0Il8
|
|||
github.com/calmh/randomart v1.1.0/go.mod h1:DQUbPVyP+7PAs21w/AnfMKG5NioxS3TbZ2F9MSK/jFM=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/charmbracelet/boba v0.5.0/go.mod h1:xqhji08CbPctzbvCi/GzmNvIFOhnWJnkl9N8T65gnK0=
|
||||
github.com/charmbracelet/boba v0.6.0 h1:ktCY611UE4ZJ4MS0FS6eCA/jqjJDyfNgFfdDKBgi07g=
|
||||
github.com/charmbracelet/boba v0.6.0/go.mod h1:xqhji08CbPctzbvCi/GzmNvIFOhnWJnkl9N8T65gnK0=
|
||||
github.com/charmbracelet/boba v0.6.1 h1:UwhxBYkYjnRTS8oJBoLxXzh+CpE12yt7utMpP0cjlYc=
|
||||
github.com/charmbracelet/boba v0.6.1/go.mod h1:xqhji08CbPctzbvCi/GzmNvIFOhnWJnkl9N8T65gnK0=
|
||||
github.com/charmbracelet/charm v0.4.1 h1:LJjQUDjeJkMTWmovyYiUzglbbbMBQGoDomqBn22qbpI=
|
||||
github.com/charmbracelet/charm v0.4.1/go.mod h1:bZQON+c4uV9mgbZBvKzH7wA1zGII9Zfg26cL70NYdH0=
|
||||
github.com/charmbracelet/glamour v0.2.0 h1:mTgaiNiumpqTZp3qVM6DH9UB0NlbY17wejoMf1kM8Pg=
|
||||
|
@ -106,8 +106,9 @@ github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/9
|
|||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/term v0.0.0-20190109203006-aa71e9d9e942 h1:A7GG7zcGjl3jqAqGPmcNjd/D9hzL95SuoOQAaFNdLU0=
|
||||
github.com/pkg/term v0.0.0-20190109203006-aa71e9d9e942/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ=
|
||||
github.com/pkg/term v0.0.0-20200520122047-c3ffed290a03 h1:pd4YKIqCB0U7O2I4gWHgEUA2mCEOENmco0l/bM957bU=
|
||||
github.com/pkg/term v0.0.0-20200520122047-c3ffed290a03/go.mod h1:Z9+Ul5bCbBKnbCvdOWbLqTHhJiYV414CURZJba6L8qA=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||
|
@ -178,8 +179,8 @@ golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200430202703-d923437fa56d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9 h1:YTzHMGlqJu67/uEo1lBv0n3wBXhXNeUbB1XfN2vmTm0=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4=
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
|
127
ui/pager.go
127
ui/pager.go
|
@ -1,48 +1,92 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/boba"
|
||||
"github.com/charmbracelet/boba/textinput"
|
||||
"github.com/charmbracelet/boba/viewport"
|
||||
"github.com/charmbracelet/charm"
|
||||
"github.com/charmbracelet/charm/ui/common"
|
||||
"github.com/charmbracelet/glamour"
|
||||
te "github.com/muesli/termenv"
|
||||
)
|
||||
|
||||
const (
|
||||
statusBarHeight = 1
|
||||
gray = "#333333"
|
||||
yellowGreen = "#ECFD65"
|
||||
fuschia = "#EE6FF8"
|
||||
noteHeadingText = " Set Memo "
|
||||
notePromptText = " > "
|
||||
)
|
||||
|
||||
var (
|
||||
noteHeading = te.String(noteHeadingText).
|
||||
Foreground(common.Cream.Color()).
|
||||
Background(common.Green.Color()).
|
||||
String()
|
||||
)
|
||||
|
||||
// MSG
|
||||
|
||||
type pagerErrMsg error
|
||||
type contentRenderedMsg string
|
||||
type noteSavedMsg *charm.Markdown
|
||||
|
||||
// MODEL
|
||||
|
||||
type pagerState int
|
||||
|
||||
const (
|
||||
pagerStateNormal pagerState = iota
|
||||
pagerStateBrowse pagerState = iota
|
||||
pagerStateSetNote
|
||||
)
|
||||
|
||||
type pagerModel struct {
|
||||
err error
|
||||
cc *charm.Client
|
||||
viewport viewport.Model
|
||||
state pagerState
|
||||
glamourStyle string
|
||||
width int
|
||||
height int
|
||||
textInput textinput.Model
|
||||
|
||||
// Current document being rendered, sans-glamour rendering. We cache
|
||||
// this here so we can re-render it on resize.
|
||||
currentDocument *charm.Markdown
|
||||
}
|
||||
|
||||
func newPagerModel(glamourStyle string) pagerModel {
|
||||
ti := textinput.NewModel()
|
||||
ti.Prompt = te.String(notePromptText).
|
||||
Foreground(te.ColorProfile().Color(gray)).
|
||||
Background(te.ColorProfile().Color(yellowGreen)).
|
||||
String()
|
||||
ti.TextColor = gray
|
||||
ti.BackgroundColor = yellowGreen
|
||||
ti.CursorColor = fuschia
|
||||
ti.CharLimit = 128 // totally arbitrary
|
||||
ti.Focus()
|
||||
|
||||
return pagerModel{
|
||||
state: pagerStateBrowse,
|
||||
glamourStyle: glamourStyle,
|
||||
textInput: ti,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *pagerModel) setSize(w, h int) {
|
||||
m.width = w
|
||||
m.height = h
|
||||
m.viewport.Width = w
|
||||
m.viewport.Height = h
|
||||
m.viewport.Height = h - statusBarHeight
|
||||
m.textInput.Width = w - len(noteHeadingText) - len(notePromptText) - 1
|
||||
}
|
||||
|
||||
func (m *pagerModel) setContent(s string) {
|
||||
|
@ -50,15 +94,55 @@ func (m *pagerModel) setContent(s string) {
|
|||
}
|
||||
|
||||
func (m *pagerModel) unload() {
|
||||
m.state = pagerStateBrowse
|
||||
m.viewport.SetContent("")
|
||||
m.viewport.Y = 0
|
||||
m.textInput.Reset()
|
||||
}
|
||||
|
||||
// UPDATE
|
||||
|
||||
func pagerUpdate(msg boba.Msg, m pagerModel) (pagerModel, boba.Cmd) {
|
||||
var (
|
||||
cmd boba.Cmd
|
||||
cmds []boba.Cmd
|
||||
)
|
||||
|
||||
switch msg := msg.(type) {
|
||||
|
||||
case boba.KeyMsg:
|
||||
switch m.state {
|
||||
case pagerStateSetNote:
|
||||
switch msg.String() {
|
||||
case "q":
|
||||
fallthrough
|
||||
case "esc":
|
||||
m.state = pagerStateBrowse
|
||||
return m, nil
|
||||
case "enter":
|
||||
m.currentDocument.Note = m.textInput.Value // set optimistically
|
||||
m.state = pagerStateBrowse
|
||||
m.textInput.Reset()
|
||||
return m, saveDocumentNote(m.cc, m.currentDocument.ID, m.currentDocument.Note)
|
||||
}
|
||||
default:
|
||||
switch msg.String() {
|
||||
case "q":
|
||||
fallthrough
|
||||
case "esc":
|
||||
if m.state != pagerStateBrowse {
|
||||
m.state = pagerStateBrowse
|
||||
return m, nil
|
||||
}
|
||||
case "n":
|
||||
m.state = pagerStateSetNote
|
||||
return m, textinput.Blink(m.textInput)
|
||||
}
|
||||
}
|
||||
|
||||
case pagerErrMsg:
|
||||
m.err = msg
|
||||
|
||||
// Glow has rendered the content
|
||||
case contentRenderedMsg:
|
||||
m.setContent(string(msg))
|
||||
|
@ -79,19 +163,32 @@ func pagerUpdate(msg boba.Msg, m pagerModel) (pagerModel, boba.Cmd) {
|
|||
return m, cmd
|
||||
}
|
||||
|
||||
var cmd boba.Cmd
|
||||
m.viewport, cmd = viewport.Update(msg, m.viewport)
|
||||
switch m.state {
|
||||
case pagerStateSetNote:
|
||||
m.textInput, cmd = textinput.Update(msg, m.textInput)
|
||||
cmds = append(cmds, cmd)
|
||||
default:
|
||||
m.viewport, cmd = viewport.Update(msg, m.viewport)
|
||||
cmds = append(cmds, cmd)
|
||||
}
|
||||
|
||||
return m, cmd
|
||||
return m, boba.Batch(cmds...)
|
||||
}
|
||||
|
||||
// VIEW
|
||||
|
||||
func pagerView(m pagerModel) string {
|
||||
var footer string
|
||||
if m.state == pagerStateSetNote {
|
||||
footer = pagerSetNoteView(m)
|
||||
} else {
|
||||
footer = pagerStatusBarView(m)
|
||||
}
|
||||
|
||||
return fmt.Sprintf(
|
||||
"\n%s\n%s",
|
||||
viewport.View(m.viewport),
|
||||
pagerStatusBarView(m),
|
||||
footer,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -126,6 +223,10 @@ func pagerStatusBarView(m pagerModel) string {
|
|||
return logo + note + emptySpace + percent
|
||||
}
|
||||
|
||||
func pagerSetNoteView(m pagerModel) string {
|
||||
return noteHeading + textinput.View(m.textInput)
|
||||
}
|
||||
|
||||
// CMD
|
||||
|
||||
func renderWithGlamour(m pagerModel, md string) boba.Cmd {
|
||||
|
@ -138,6 +239,20 @@ func renderWithGlamour(m pagerModel, md string) boba.Cmd {
|
|||
}
|
||||
}
|
||||
|
||||
func saveDocumentNote(cc *charm.Client, id int, note string) boba.Cmd {
|
||||
if cc == nil {
|
||||
return func() boba.Msg {
|
||||
return pagerErrMsg(errors.New("can't set note; no charm client"))
|
||||
}
|
||||
}
|
||||
return func() boba.Msg {
|
||||
if err := cc.SetMarkdownNote(id, note); err != nil {
|
||||
return pagerErrMsg(err)
|
||||
}
|
||||
return noteSavedMsg(&charm.Markdown{ID: id, Note: note})
|
||||
}
|
||||
}
|
||||
|
||||
// This is where the magic happens
|
||||
func glamourRender(m pagerModel, markdown string) (string, error) {
|
||||
|
||||
|
|
24
ui/stash.go
24
ui/stash.go
|
@ -39,7 +39,7 @@ type stashState int
|
|||
|
||||
const (
|
||||
stashStateInit stashState = iota
|
||||
stashStateStashLoaded
|
||||
stashStateReady
|
||||
stashStatePromptDelete
|
||||
stashStateLoadingDocument
|
||||
)
|
||||
|
@ -170,7 +170,7 @@ func stashUpdate(msg boba.Msg, m stashModel) (stashModel, boba.Cmd) {
|
|||
if m.state != stashStatePromptDelete {
|
||||
break
|
||||
}
|
||||
// Deletion confirmed. Delete the stashed item.
|
||||
// Deletion confirmed. Delete the stashed item...
|
||||
|
||||
// Index of the documents slice we'll be deleting
|
||||
i := m.paginator.Page*m.paginator.PerPage + m.index
|
||||
|
@ -187,9 +187,8 @@ func stashUpdate(msg boba.Msg, m stashModel) (stashModel, boba.Cmd) {
|
|||
m.paginator.Page = min(m.paginator.Page, m.paginator.TotalPages-1)
|
||||
|
||||
// Set state and delete
|
||||
m.state = stashStateStashLoaded
|
||||
m.state = stashStateReady
|
||||
return m, deleteStashedItem(m.cc, id)
|
||||
|
||||
}
|
||||
|
||||
case stashErrMsg:
|
||||
|
@ -207,7 +206,7 @@ func stashUpdate(msg boba.Msg, m stashModel) (stashModel, boba.Cmd) {
|
|||
|
||||
sort.Sort(charm.MarkdownsByCreatedAtDesc(msg)) // sort by date
|
||||
m.documents = append(m.documents, msg...)
|
||||
m.state = stashStateStashLoaded
|
||||
m.state = stashStateReady
|
||||
m.paginator.SetTotalPages(len(m.documents))
|
||||
|
||||
case stashSpinnerTickMsg:
|
||||
|
@ -215,9 +214,18 @@ func stashUpdate(msg boba.Msg, m stashModel) (stashModel, boba.Cmd) {
|
|||
m.spinner, cmd = spinner.Update(msg, m.spinner)
|
||||
cmds = append(cmds, cmd)
|
||||
}
|
||||
|
||||
case noteSavedMsg:
|
||||
// A note was set on a document. This happened in the pager, so we'll
|
||||
// find the corresponding document here and update accordingly.
|
||||
for i := range m.documents {
|
||||
if m.documents[i].ID == msg.ID {
|
||||
m.documents[i].Note = msg.Note
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if m.state == stashStateStashLoaded {
|
||||
if m.state == stashStateReady {
|
||||
|
||||
// Update paginator
|
||||
m.paginator, cmd = paginator.Update(msg, m.paginator)
|
||||
|
@ -243,7 +251,7 @@ func stashUpdate(msg boba.Msg, m stashModel) (stashModel, boba.Cmd) {
|
|||
// used for confirmation above) cancels the deletion
|
||||
k, ok := msg.(boba.KeyMsg)
|
||||
if ok && k.String() != "x" && m.state == stashStatePromptDelete {
|
||||
m.state = stashStateStashLoaded
|
||||
m.state = stashStateReady
|
||||
}
|
||||
|
||||
return m, boba.Batch(cmds...)
|
||||
|
@ -262,7 +270,7 @@ func stashView(m stashModel) string {
|
|||
s += spinner.View(m.spinner) + " Loading stash..."
|
||||
case stashStateLoadingDocument:
|
||||
s += spinner.View(m.spinner) + " Loading document..."
|
||||
case stashStateStashLoaded:
|
||||
case stashStateReady:
|
||||
fallthrough
|
||||
case stashStatePromptDelete:
|
||||
if len(m.documents) == 0 {
|
||||
|
|
38
ui/ui.go
38
ui/ui.go
|
@ -12,10 +12,6 @@ import (
|
|||
te "github.com/muesli/termenv"
|
||||
)
|
||||
|
||||
const (
|
||||
statusBarHeight = 1
|
||||
)
|
||||
|
||||
var (
|
||||
glowLogoTextColor = common.Color("#ECFD65")
|
||||
statusBarBg = common.NewColorPair("#242424", "#E6E6E6")
|
||||
|
@ -71,7 +67,7 @@ type model struct {
|
|||
|
||||
func (m *model) unloadDocument() {
|
||||
m.state = stateShowStash
|
||||
m.stash.state = stashStateStashLoaded
|
||||
m.stash.state = stashStateReady
|
||||
m.pager.unload()
|
||||
}
|
||||
|
||||
|
@ -94,10 +90,8 @@ func initialize(style string) func() (boba.Model, boba.Cmd) {
|
|||
|
||||
return model{
|
||||
spinner: s,
|
||||
pager: pagerModel{
|
||||
glamourStyle: style,
|
||||
},
|
||||
state: stateInitCharmClient,
|
||||
pager: newPagerModel(style),
|
||||
state: stateInitCharmClient,
|
||||
}, boba.Batch(
|
||||
newCharmClient,
|
||||
spinner.Tick(s),
|
||||
|
@ -124,20 +118,26 @@ func update(msg boba.Msg, mdl boba.Model) (boba.Model, boba.Cmd) {
|
|||
|
||||
case boba.KeyMsg:
|
||||
switch msg.String() {
|
||||
|
||||
case "q":
|
||||
fallthrough
|
||||
case "esc":
|
||||
if m.state == stateShowDocument {
|
||||
m.unloadDocument()
|
||||
return m, nil
|
||||
var cmd boba.Cmd
|
||||
if m.pager.state == pagerStateBrowse {
|
||||
// Exit pager
|
||||
m.unloadDocument()
|
||||
} else {
|
||||
// Pass message through to pager
|
||||
m.pager, cmd = pagerUpdate(msg, m.pager)
|
||||
}
|
||||
return m, cmd
|
||||
}
|
||||
return m, boba.Quit
|
||||
|
||||
case "ctrl+c":
|
||||
return m, boba.Quit
|
||||
|
||||
// Re-render
|
||||
// Repaint
|
||||
case "ctrl+l":
|
||||
return m, getTerminalSize()
|
||||
}
|
||||
|
@ -192,14 +192,24 @@ func update(msg boba.Msg, mdl boba.Model) (boba.Model, boba.Cmd) {
|
|||
m.state = stateKeygenFinished
|
||||
cmds = append(cmds, newCharmClient)
|
||||
|
||||
case noteSavedMsg:
|
||||
// A note was saved to a document. This will have be 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.
|
||||
m.stash, cmd = stashUpdate(msg, m.stash)
|
||||
cmds = append(cmds, cmd)
|
||||
|
||||
case newCharmClientMsg:
|
||||
m.cc = msg
|
||||
m.state = stateShowStash
|
||||
m.stash, cmd = stashInit(m.cc)
|
||||
m.stash, cmd = stashInit(msg)
|
||||
m.stash.setSize(m.terminalWidth, m.terminalHeight)
|
||||
m.pager.cc = msg
|
||||
cmds = append(cmds, cmd)
|
||||
|
||||
case gotStashedItemMsg:
|
||||
// Loaded markdown document from the server. We'll render it before
|
||||
// loading it into the pager.
|
||||
m.pager.currentDocument = msg
|
||||
cmds = append(cmds, renderWithGlamour(m.pager, msg.Body))
|
||||
|
||||
|
|
Loading…
Reference in a new issue