-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmarkdown.go
More file actions
99 lines (89 loc) Β· 2.65 KB
/
Copy pathmarkdown.go
File metadata and controls
99 lines (89 loc) Β· 2.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
package main
import (
"strings"
"time"
"github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/glamour"
)
func (m *model) openPreview() {
m.stash()
body := ""
if len(m.tabs) > 0 {
body = m.tabs[m.active].content
}
w := m.width - 2
if w < 20 {
w = 20
}
m.preview = viewport.New(m.width, m.previewHeight())
m.preview.SetContent(m.renderMarkdown(body, "*(empty note)*", w))
m.previewReady = true
m.mode = modePreview
}
func (m model) previewHeight() int {
h := m.height - 2
if h < 3 {
h = 3
}
return h
}
// editorWidth is the textarea width β half the screen when the split preview
// is on (leaving one column for the divider), full width otherwise.
func (m model) editorWidth() int {
if m.splitPreview && m.width > 30 {
return m.width / 2
}
return m.width
}
func (m model) splitPaneWidth() int {
w := m.width - m.editorWidth() - 1
if w < 10 {
w = 10
}
return w
}
// renderer returns a Glamour renderer for width w, cached across calls so the
// (expensive) construction happens once per width rather than per keystroke.
func (m *model) renderer(w int) *glamour.TermRenderer {
if m.splitRender == nil || m.splitRenderW != w {
// WithStandardStyle avoids WithAutoStyle's synchronous OSC background
// query, which can stall for hundreds of ms in some terminals.
if r, err := glamour.NewTermRenderer(glamour.WithStandardStyle(glamourStyle), glamour.WithWordWrap(w)); err == nil {
m.splitRender = r
m.splitRenderW = w
}
}
return m.splitRender
}
// refreshSplit re-renders the current buffer as markdown into the split pane.
// Heavy (Glamour) β call only on toggle/resize/tab-switch and debounced idle,
// never directly on a keystroke.
func (m *model) refreshSplit() {
if !m.splitPreview {
return
}
w := m.splitPaneWidth()
m.splitVP.Width = w
m.splitVP.Height = m.height - 4
m.splitVP.SetContent(m.renderMarkdown(m.ta.Value(), "*(empty β type markdown on the left)*", w))
m.splitDirty = false
}
// renderMarkdown renders body as styled markdown wrapped to w columns, falling
// back to emptyMsg when the body is blank and to raw text if rendering fails.
func (m *model) renderMarkdown(body, emptyMsg string, w int) string {
if strings.TrimSpace(body) == "" {
body = emptyMsg
}
if r := m.renderer(w); r != nil {
if out, err := r.Render(body); err == nil {
return out
}
}
return body
}
// scheduleSplitRender debounces: a render fires ~120ms after the last keystroke,
// coalescing bursts of typing into a single Glamour pass.
func scheduleSplitRender(seq int) tea.Cmd {
return tea.Tick(120*time.Millisecond, func(time.Time) tea.Msg { return splitRenderMsg(seq) })
}