Render with glamour asyncronously

This commit is contained in:
Christian Rocha 2020-05-15 15:08:45 -04:00 committed by Christian Muehlhaeuser
commit f5df688860
2 changed files with 90 additions and 15 deletions

View file

@ -34,8 +34,8 @@ type stashState int
const (
stashStateInit stashState = iota
stashStateLoaded
stashStateLoadingItem
stashStateStashLoaded
stashStateLoadingDocument
)
type stashModel struct {
@ -147,7 +147,9 @@ func stashUpdate(msg boba.Msg, m stashModel) (stashModel, boba.Cmd) {
return m, nil
case "enter":
m.state = stashStateLoadingItem
// Load the document from the server. We'll handle the message
// that comes back in the main update function.
m.state = stashStateLoadingDocument
indexToLoad := m.paginator.Page*m.paginator.PerPage + m.index
return m, boba.Batch(
loadStashedItem(m.cc, m.documents[indexToLoad].ID),
@ -162,17 +164,17 @@ func stashUpdate(msg boba.Msg, m stashModel) (stashModel, boba.Cmd) {
case gotStashMsg:
sort.Sort(charm.MarkdownsByCreatedAt(msg)) // sort by date
m.documents = append(m.documents, msg...)
m.state = stashStateLoaded
m.state = stashStateStashLoaded
m.paginator.SetTotalPages(len(m.documents))
case stashSpinnerTickMsg:
if m.state == stashStateInit || m.state == stashStateLoadingItem {
if m.state == stashStateInit || m.state == stashStateLoadingDocument {
m.spinner, cmd = spinner.Update(msg, m.spinner)
return m, cmd
}
}
if m.state == stashStateLoaded {
if m.state == stashStateStashLoaded {
m.paginator, cmd = paginator.Update(msg, m.paginator)
cmds = append(cmds, cmd)
}
@ -183,13 +185,17 @@ func stashUpdate(msg boba.Msg, m stashModel) (stashModel, boba.Cmd) {
// VIEW
func stashView(m stashModel) string {
if m.err != nil {
return "\n" + m.err.Error()
}
var s string
switch m.state {
case stashStateInit:
s += spinner.View(m.spinner) + " Loading stash..."
case stashStateLoadingItem:
case stashStateLoadingDocument:
s += spinner.View(m.spinner) + " Loading document..."
case stashStateLoaded:
case stashStateStashLoaded:
if len(m.documents) == 0 {
s += stashEmtpyView(m)
break

View file

@ -12,6 +12,7 @@ import (
"github.com/charmbracelet/charm"
"github.com/charmbracelet/charm/ui/common"
"github.com/charmbracelet/charm/ui/keygen"
"github.com/charmbracelet/glamour"
"github.com/muesli/reflow/indent"
te "github.com/muesli/termenv"
"golang.org/x/crypto/ssh/terminal"
@ -38,6 +39,7 @@ type fatalErrMsg error
type errMsg error
type newCharmClientMsg *charm.Client
type sshAuthErrMsg struct{}
type contentRenderedMsg string
type terminalSizeMsg struct {
width int
@ -78,7 +80,7 @@ type model struct {
func (m *model) unloadDocument() {
m.pager = pager.Model{}
m.state = stateShowStash
m.stash.state = stashStateLoaded
m.stash.state = stashStateStashLoaded
}
// INIT
@ -196,18 +198,34 @@ func update(msg boba.Msg, mdl boba.Model) (boba.Model, boba.Cmd) {
return m, cmd
case gotStashedItemMsg:
// TODO: there's a (unlikely) potential race condition that could
// happen here where we start the pager before we have received the
// terminal size. We could solve this in a few ways, including by
// getting the terminal size imperatively and synchronously
m.state = stateShowDocument
// We've received stashed item data. Render with Glamour and send to
// the pager.
//m.state = stateShowDocument
m.pager = pager.NewModel(
m.terminalWidth,
m.terminalHeight-statusBarHeight,
)
m.docNote = msg.Note
m.pager.Content(msg.Body)
// This could happen asyncronously with a Cmd since there's techincally
// IO happening, but since rendering is fast and Go is an imperative
// language I guess it's fine to just render synchronously here.
/*
md, err := glamourRender(m, msg.Body)
if err != nil {
m.err = err
return m, nil
}
*/
//m.pager.SetContent(md)
return m, renderWithGlamour(m, msg.Body)
case contentRenderedMsg:
m.state = stateShowDocument
m.pager.SetContent(string(msg))
return m, nil
}
switch m.state {
@ -227,6 +245,7 @@ func update(msg boba.Msg, mdl boba.Model) (boba.Model, boba.Cmd) {
return m, cmd
case stateShowDocument:
// Process keys (and eventually mouse) with pager.Update
newPagerModel, cmd := pager.Update(msg, boba.Model(m.pager))
newPagerModel_, ok := newPagerModel.(pager.Model)
if !ok {
@ -329,8 +348,58 @@ func newCharmClient() boba.Msg {
return newCharmClientMsg(cc)
}
func renderWithGlamour(m model, md string) boba.Cmd {
return func() boba.Msg {
s, err := glamourRender(m, md)
if err != nil {
return errMsg(err)
}
return contentRenderedMsg(s)
}
}
// ETC
// This is where the magic happens
func glamourRender(m model, markdown string) (string, error) {
// initialize glamour
var gs glamour.TermRendererOption
if m.style == "auto" {
gs = glamour.WithAutoStyle()
} else {
gs = glamour.WithStylePath(m.style)
}
r, err := glamour.NewTermRenderer(
gs,
glamour.WithWordWrap(m.terminalWidth),
)
if err != nil {
return "", err
}
out, err := r.Render(markdown)
if err != nil {
return "", err
}
// trim lines
lines := strings.Split(string(out), "\n")
var content string
for i, s := range lines {
content += strings.TrimSpace(s)
// don't add an artificial newline after the last split
if i+1 < len(lines) {
content += "\n"
}
}
return content, nil
}
func min(a, b int) int {
if a < b {
return a