Skip to content

Commit 76c9c7d

Browse files
committed
feat!: support scrolling inside slides
1 parent fd25ec2 commit 76c9c7d

4 files changed

Lines changed: 127 additions & 21 deletions

File tree

internal/model/model.go

Lines changed: 116 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"io"
88
"os"
9+
"strconv"
910
"strings"
1011
"time"
1112

@@ -61,6 +62,7 @@ type Model struct {
6162
Preprocessor *preprocessor.Config
6263
// TODO: move into some proper config struct
6364
HideInternalErrors HideInternalError
65+
ready bool
6466
}
6567

6668
type fileWatchMsg struct{}
@@ -119,15 +121,30 @@ func (m *Model) Load() error {
119121
m.Theme = styles.SelectTheme(metaData.Theme)
120122
}
121123

124+
m.updateViewportContent()
125+
122126
return nil
123127
}
124128

125129
// Update updates the presentation model.
126130
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
131+
var (
132+
cmd tea.Cmd
133+
cmds []tea.Cmd
134+
)
135+
127136
switch msg := msg.(type) {
128137
case tea.WindowSizeMsg:
129-
m.viewport.Width = msg.Width
130-
m.viewport.Height = msg.Height
138+
footerHeight := 3
139+
if !m.ready {
140+
m.viewport = viewport.New(msg.Width, msg.Height-footerHeight)
141+
m.viewport.YPosition = 0
142+
m.ready = true
143+
m.updateViewportContent()
144+
} else {
145+
m.viewport.Width = msg.Width
146+
m.viewport.Height = msg.Height - footerHeight
147+
}
131148
return m, nil
132149

133150
case tea.KeyMsg:
@@ -171,6 +188,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
171188
if err != nil {
172189
// We couldn't parse the code block on the screen
173190
m.VirtualText = "\n" + err.Error()
191+
m.updateViewportContent()
174192
return m, nil
175193
}
176194
var outs []string
@@ -187,6 +205,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
187205
outs = append(outs, res.Out)
188206
}
189207
m.VirtualText = strings.Join(outs, "\n")
208+
m.updateViewportContent()
190209
case "y":
191210
blocks, err := code.Parse(m.Slides[m.Page])
192211
if err != nil {
@@ -199,13 +218,39 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
199218
case "ctrl+c", "q":
200219
return m, tea.Quit
201220
default:
221+
if m.shouldHandleViewportNavigation(keyPress) {
222+
if keyPress == "j" || keyPress == "k" {
223+
repeat := 1
224+
if m.bufferIsNumeric() {
225+
if r, err := strconv.Atoi(m.buffer); err == nil && r > 0 {
226+
repeat = r
227+
}
228+
m.buffer = ""
229+
}
230+
231+
for i := 0; i < repeat; i++ {
232+
m.viewport, cmd = m.viewport.Update(msg)
233+
cmds = append(cmds, cmd)
234+
}
235+
return m, tea.Batch(cmds...)
236+
} else {
237+
m.viewport, cmd = m.viewport.Update(msg)
238+
cmds = append(cmds, cmd)
239+
return m, tea.Batch(cmds...)
240+
}
241+
}
242+
202243
newState := navigation.Navigate(navigation.State{
203244
Buffer: m.buffer,
204245
Page: m.Page,
205246
TotalSlides: len(m.Slides),
206247
}, keyPress)
207248
m.buffer = newState.Buffer
208-
m.SetPage(newState.Page)
249+
if newState.Page != m.Page {
250+
m.SetPage(newState.Page)
251+
m.updateViewportContent()
252+
m.viewport.GotoTop()
253+
}
209254
}
210255

211256
case fileWatchMsg:
@@ -216,25 +261,27 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
216261
if m.Page >= len(m.Slides) {
217262
m.Page = len(m.Slides) - 1
218263
}
264+
m.updateViewportContent()
219265
}
220266
return m, fileWatchCmd()
221267
}
222-
return m, nil
268+
269+
if !m.Search.Active {
270+
m.viewport, cmd = m.viewport.Update(msg)
271+
cmds = append(cmds, cmd)
272+
}
273+
274+
return m, tea.Batch(cmds...)
223275
}
224276

225277
// View renders the current slide in the presentation and the status bar which
226278
// contains the author, date, and pagination information.
227279
func (m Model) View() string {
228-
r, _ := glamour.NewTermRenderer(m.Theme, glamour.WithWordWrap(m.viewport.Width))
229-
slide := m.Slides[m.Page]
230-
slide = code.HideComments(slide)
231-
slide, err := r.Render(slide)
232-
slide = strings.ReplaceAll(slide, "\t", tabSpaces)
233-
slide += m.VirtualText
234-
if err != nil {
235-
slide = fmt.Sprintf("Error: Could not render markdown! (%v)", err)
280+
if !m.ready {
281+
return "\n Initializing..."
236282
}
237-
slide = styles.Slide.Render(slide)
283+
284+
slide := styles.Slide.Render(m.viewport.View())
238285

239286
var left string
240287
if m.Search.Active {
@@ -247,7 +294,61 @@ func (m Model) View() string {
247294

248295
right := styles.Page.Render(m.paging())
249296
status := styles.Status.Render(styles.JoinHorizontal(left, right, m.viewport.Width))
250-
return styles.JoinVertical(slide, status, m.viewport.Height)
297+
298+
return fmt.Sprintf("%s\n%s", slide, status)
299+
}
300+
301+
func (m *Model) shouldHandleViewportNavigation(keyPress string) bool {
302+
scrollKeys := map[string]bool{
303+
"up": true,
304+
"down": true,
305+
"pgup": true,
306+
"pgdown": true,
307+
"home": true,
308+
"end": true,
309+
"ctrl+u": true,
310+
"ctrl+d": true,
311+
"ctrl+b": true,
312+
"ctrl+f": true,
313+
}
314+
315+
if keyPress == "j" || keyPress == "k" {
316+
return true
317+
}
318+
319+
return scrollKeys[keyPress]
320+
}
321+
322+
func (m *Model) updateViewportContent() {
323+
if !m.ready || len(m.Slides) == 0 {
324+
return
325+
}
326+
327+
r, _ := glamour.NewTermRenderer(m.Theme, glamour.WithWordWrap(m.viewport.Width))
328+
slide := m.Slides[m.Page]
329+
slide = code.HideComments(slide)
330+
slide, err := r.Render(slide)
331+
slide = strings.ReplaceAll(slide, "\t", tabSpaces)
332+
slide += m.VirtualText
333+
if err != nil {
334+
slide = fmt.Sprintf("Error: Could not render markdown! (%v)", err)
335+
}
336+
337+
slide = "\n\n" + slide
338+
339+
m.viewport.SetContent(slide)
340+
}
341+
342+
func (m *Model) bufferIsNumeric() bool {
343+
if m.buffer == "" {
344+
return false
345+
}
346+
for _, r := range m.buffer {
347+
if r < '0' || r > '9' {
348+
return false
349+
}
350+
}
351+
return true
251352
}
252353

253354
func (m *Model) paging() string {
@@ -301,6 +402,7 @@ func (m *Model) SetPage(page int) {
301402

302403
m.VirtualText = ""
303404
m.Page = page
405+
m.updateViewportContent()
304406
}
305407

306408
// Pages returns all the folien in the presentation.

internal/navigation/navigation.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,12 @@ func Navigate(state State, keyPress string) State {
5252
Page: targetSlide,
5353
TotalSlides: state.TotalSlides,
5454
}
55-
case " ", "down", "j", "right", "l", "enter", "n", "pgdown":
55+
case " ", "right", "l", "enter", "n":
5656
return State{
5757
Page: navigateNext(state),
5858
TotalSlides: state.TotalSlides,
5959
}
60-
case "up", "k", "left", "h", "p", "pgup", "N":
60+
case "left", "h", "p", "N":
6161
return State{
6262
Page: navigatePrevious(state),
6363
TotalSlides: state.TotalSlides,

internal/navigation/navigation_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ func TestNavigation(t *testing.T) {
1414
}{
1515
{target: 0},
1616
{keys: "l", target: 1},
17-
{keys: "jjjjjjjjjj", target: 10},
18-
{keys: "jjjjjjjjjjjjj", target: 10},
17+
{keys: "jjjjjjjjjj", target: 0},
18+
{keys: "jjjjjjjjjjjjj", target: 0},
1919
{keys: "G", target: 10},
2020
{keys: "llgg", target: 0},
21-
{keys: "2j", target: 2},
22-
{keys: "0j", target: 1},
21+
{keys: "2jll", target: 2},
22+
{keys: "0jl", target: 1},
2323
{keys: "-11G", target: 10},
2424
{keys: "0G", target: 0},
2525
{keys: "3G", target: 2},

main.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,11 @@ func root(cmd *cobra.Command, args []string) error {
7474
return err
7575
}
7676

77-
p := tea.NewProgram(presentation, tea.WithAltScreen())
77+
p := tea.NewProgram(
78+
presentation,
79+
tea.WithAltScreen(),
80+
tea.WithMouseCellMotion(),
81+
)
7882
if _, err := p.Run(); err != nil {
7983
return err
8084
}

0 commit comments

Comments
 (0)