Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow running specific code blocks #192

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
4 changes: 4 additions & 0 deletions examples/code_blocks.md
Expand Up @@ -65,6 +65,10 @@ fmt.Println("Hello, world!")
console.log("Hello, world!")
```

```javascript
console.log("Hello, universe!")
```

---

### Lua
Expand Down
2 changes: 1 addition & 1 deletion internal/code/languages.go
Expand Up @@ -25,7 +25,7 @@ const (
Python = "python"
Ruby = "ruby"
Rust = "rust"
Java = "java"
Java = "java"
)

// Languages is a map of supported languages with their extensions and commands
Expand Down
1 change: 1 addition & 0 deletions internal/code/selection.go
@@ -0,0 +1 @@
package code
70 changes: 63 additions & 7 deletions internal/model/model.go
Expand Up @@ -7,6 +7,8 @@ import (
"fmt"
"io"
"os"
"regexp"
"strconv"
"strings"
"time"

Expand Down Expand Up @@ -48,6 +50,8 @@ type Model struct {
// original slides, it will be displayed on a slide and reset on page change
VirtualText string
Search navigation.Search

blocks *navigation.Blocks
}

type fileWatchMsg struct{}
Expand Down Expand Up @@ -102,6 +106,7 @@ func (m *Model) Load() error {
if m.Theme == nil {
m.Theme = styles.SelectTheme(metaData.Theme)
}
m.blocks = nil

return nil
}
Expand Down Expand Up @@ -140,6 +145,10 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, cmd
}

if m.blocks != nil {
return m.runCodeBlocks(msg)
}

switch keyPress {
case "/":
// Begin search
Expand All @@ -154,15 +163,11 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
blocks, err := code.Parse(m.Slides[m.Page])
if err != nil {
// We couldn't parse the code block on the screen
m.VirtualText = "\n" + err.Error()
m.SetVirtualText("\n" + err.Error())
return m, nil
}
var outs []string
for _, block := range blocks {
res := code.Execute(block)
outs = append(outs, res.Out)
}
m.VirtualText = strings.Join(outs, "\n")
m.blocks = navigation.NewBlocks(blocks)
return m, nil
case "y":
blocks, err := code.Parse(m.Slides[m.Page])
if err != nil {
Expand Down Expand Up @@ -236,6 +241,52 @@ func (m *Model) paging() string {
}
}

func (m *Model) runCodeBlocks(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
switch msg.Type {
case tea.KeyEnter:
m.blocks.ExecuteNext(m)
if m.blocks.Done() {
m.blocks = nil
}
return m, nil
case tea.KeyCtrlC, tea.KeyEscape:
m.blocks = nil
return m, nil
}

keyPress := msg.String()
switch keyPress {
case "a":
m.blocks.ExecuteAll(m)
m.blocks = nil
return m, nil
case "n":
m.blocks.ExecuteNext(m)
if m.blocks.Done() {
m.blocks = nil
}
return m, nil
}

digits := regexp.MustCompile(`\d+`)
match := digits.FindString(keyPress)
if match == "" {
return m, nil
}

idx, err := strconv.Atoi(match)
if err != nil {
m.SetVirtualText("\n" + err.Error())
return m, nil
}

m.blocks.ExecuteIdx(uint(idx), m)
if m.blocks.Done() {
m.blocks = nil
}
return m, nil
}

func readFile(path string) (string, error) {
s, err := os.Stat(path)
if err != nil {
Expand Down Expand Up @@ -310,3 +361,8 @@ func (m *Model) SetPage(page int) {
func (m *Model) Pages() []string {
return m.Slides
}

// SetVirtualText sets the virtual text for the presentation.
func (m *Model) SetVirtualText(text string) {
m.VirtualText = text
}
60 changes: 60 additions & 0 deletions internal/navigation/blocks.go
@@ -0,0 +1,60 @@
package navigation

import (
"github.com/maaslalani/slides/internal/code"
"strconv"
)

type Blocks struct {
blocks []code.Block
nextIdx uint
done bool
}

func NewBlocks(blocks []code.Block) *Blocks {
b := new(Blocks)
b.blocks = blocks
b.nextIdx = 0
b.checkDone()

return b
}

func (b *Blocks) ExecuteAll(m Model) {
for _, block := range b.blocks {
res := code.Execute(block)
m.SetVirtualText(res.Out)
}
b.nextIdx = uint(len(b.blocks))
b.checkDone()
}

func (b *Blocks) ExecuteNext(m Model) {
if b.done {
return
}

res := code.Execute(b.blocks[b.nextIdx])
m.SetVirtualText(res.Out)

b.nextIdx++
b.checkDone()
}

func (b *Blocks) ExecuteIdx(idx uint, m Model) {
if idx >= uint(len(b.blocks)) {
m.SetVirtualText("no code block with index [" + strconv.Itoa(int(idx)) + "]")
return
}

b.nextIdx = idx
b.ExecuteNext(m)
}

func (b *Blocks) Done() bool {
return b.done
}

func (b *Blocks) checkDone() {
b.done = b.nextIdx >= uint(len(b.blocks))
}
88 changes: 88 additions & 0 deletions internal/navigation/blocks_test.go
@@ -0,0 +1,88 @@
package navigation

import (
"github.com/maaslalani/slides/internal/code"
"github.com/stretchr/testify/require"
"testing"
)

func TestBlocks_ExecuteAll(t *testing.T) {
b := []code.Block{
{},
{},
{},
}
m := mockModel{}

blocks := NewBlocks(b)
require.False(t, blocks.Done())

blocks.ExecuteAll(&m)
require.True(t, blocks.Done())
}

func TestBlocks_ExecuteNext(t *testing.T) {
b := []code.Block{
{
Language: "javascript",
Code: `console.log("first block")`,
},
{
Language: "javascript",
Code: `console.log("second block")`,
},
{
Language: "javascript",
Code: `console.log("third block")`,
},
}
m := mockModel{}

blocks := NewBlocks(b)
require.False(t, blocks.Done())

blocks.ExecuteNext(&m)
require.Equal(t, "first block\n", m.virtualText)
require.False(t, blocks.Done())

blocks.ExecuteNext(&m)
require.Equal(t, "second block\n", m.virtualText)
require.False(t, blocks.Done())

blocks.ExecuteNext(&m)
require.Equal(t, "third block\n", m.virtualText)
require.True(t, blocks.Done())
}

func TestBlocks_ExecuteIdx(t *testing.T) {
b := []code.Block{
{
Language: "javascript",
Code: `console.log("first block")`,
},
{
Language: "javascript",
Code: `console.log("second block")`,
},
{
Language: "javascript",
Code: `console.log("third block")`,
},
}
m := mockModel{}

blocks := NewBlocks(b)
require.False(t, blocks.Done())

blocks.ExecuteIdx(1, &m)
require.Equal(t, "second block\n", m.virtualText)
require.False(t, blocks.Done())

blocks.ExecuteIdx(0, &m)
require.Equal(t, "first block\n", m.virtualText)
require.False(t, blocks.Done())

blocks.ExecuteIdx(2, &m)
require.Equal(t, "third block\n", m.virtualText)
require.True(t, blocks.Done())
}
31 changes: 31 additions & 0 deletions internal/navigation/model.go
@@ -0,0 +1,31 @@
package navigation

// Model is an interface for models.model, so that cycle imports are avoided
type Model interface {
CurrentPage() int
SetPage(page int)
Pages() []string
SetVirtualText(string)
}

type mockModel struct {
slides []string
page int
virtualText string
}

func (m *mockModel) CurrentPage() int {
return m.page
}

func (m *mockModel) SetPage(page int) {
m.page = page
}

func (m *mockModel) Pages() []string {
return m.slides
}

func (m *mockModel) SetVirtualText(text string) {
m.virtualText = text
}
7 changes: 0 additions & 7 deletions internal/navigation/search.go
Expand Up @@ -8,13 +8,6 @@ import (
"github.com/maaslalani/slides/styles"
)

// Model is an interface for models.model, so that cycle imports are avoided
type Model interface {
CurrentPage() int
SetPage(page int)
Pages() []string
}

// Search represents the current search
type Search struct {
// Active - Show search bar instead of author and date?
Expand Down
17 changes: 0 additions & 17 deletions internal/navigation/search_test.go
Expand Up @@ -4,23 +4,6 @@ import (
"testing"
)

type mockModel struct {
slides []string
page int
}

func (m *mockModel) CurrentPage() int {
return m.page
}

func (m *mockModel) SetPage(page int) {
m.page = page
}

func (m *mockModel) Pages() []string {
return m.slides
}

func TestSearch(t *testing.T) {
data := []string{
"hi",
Expand Down