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
6668type 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.
126130func (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.
227279func (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
253354func (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.
0 commit comments