This commit is contained in:
Erik Winter 2024-09-17 07:44:26 +02:00
parent 97fb0206cf
commit 35185b72a2
5 changed files with 284 additions and 24 deletions

204
bubbletea-example/main.go Normal file
View File

@ -0,0 +1,204 @@
package bubbletea_example
import (
"fmt"
"os"
"github.com/charmbracelet/bubbles/help"
"github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/textarea"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)
const (
initialInputs = 2
maxInputs = 6
minInputs = 1
helpHeight = 5
)
var (
cursorStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("212"))
cursorLineStyle = lipgloss.NewStyle().
Background(lipgloss.Color("57")).
Foreground(lipgloss.Color("230"))
placeholderStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("238"))
endOfBufferStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("235"))
focusedPlaceholderStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("99"))
focusedBorderStyle = lipgloss.NewStyle().
Border(lipgloss.RoundedBorder()).
BorderForeground(lipgloss.Color("238"))
blurredBorderStyle = lipgloss.NewStyle().
Border(lipgloss.HiddenBorder())
)
type keymap = struct {
next, prev, add, remove, quit key.Binding
}
func newTextarea() textarea.Model {
t := textarea.New()
t.Prompt = ""
t.Placeholder = "Type something"
t.ShowLineNumbers = true
t.Cursor.Style = cursorStyle
t.FocusedStyle.Placeholder = focusedPlaceholderStyle
t.BlurredStyle.Placeholder = placeholderStyle
t.FocusedStyle.CursorLine = cursorLineStyle
t.FocusedStyle.Base = focusedBorderStyle
t.BlurredStyle.Base = blurredBorderStyle
t.FocusedStyle.EndOfBuffer = endOfBufferStyle
t.BlurredStyle.EndOfBuffer = endOfBufferStyle
t.KeyMap.DeleteWordBackward.SetEnabled(false)
t.KeyMap.LineNext = key.NewBinding(key.WithKeys("down"))
t.KeyMap.LinePrevious = key.NewBinding(key.WithKeys("up"))
t.Blur()
return t
}
type model struct {
width int
height int
keymap keymap
help help.Model
inputs []textarea.Model
focus int
}
func newModel() model {
m := model{
inputs: make([]textarea.Model, initialInputs),
help: help.New(),
keymap: keymap{
next: key.NewBinding(
key.WithKeys("tab"),
key.WithHelp("tab", "next"),
),
prev: key.NewBinding(
key.WithKeys("shift+tab"),
key.WithHelp("shift+tab", "prev"),
),
add: key.NewBinding(
key.WithKeys("ctrl+n"),
key.WithHelp("ctrl+n", "add an editor"),
),
remove: key.NewBinding(
key.WithKeys("ctrl+w"),
key.WithHelp("ctrl+w", "remove an editor"),
),
quit: key.NewBinding(
key.WithKeys("esc", "ctrl+c"),
key.WithHelp("esc", "quit"),
),
},
}
for i := 0; i < initialInputs; i++ {
m.inputs[i] = newTextarea()
}
m.inputs[m.focus].Focus()
m.updateKeybindings()
return m
}
func (m model) Init() tea.Cmd {
return textarea.Blink
}
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmds []tea.Cmd
switch msg := msg.(type) {
case tea.KeyMsg:
switch {
case key.Matches(msg, m.keymap.quit):
for i := range m.inputs {
m.inputs[i].Blur()
}
return m, tea.Quit
case key.Matches(msg, m.keymap.next):
m.inputs[m.focus].Blur()
m.focus++
if m.focus > len(m.inputs)-1 {
m.focus = 0
}
cmd := m.inputs[m.focus].Focus()
cmds = append(cmds, cmd)
case key.Matches(msg, m.keymap.prev):
m.inputs[m.focus].Blur()
m.focus--
if m.focus < 0 {
m.focus = len(m.inputs) - 1
}
cmd := m.inputs[m.focus].Focus()
cmds = append(cmds, cmd)
case key.Matches(msg, m.keymap.add):
m.inputs = append(m.inputs, newTextarea())
case key.Matches(msg, m.keymap.remove):
m.inputs = m.inputs[:len(m.inputs)-1]
if m.focus > len(m.inputs)-1 {
m.focus = len(m.inputs) - 1
}
}
case tea.WindowSizeMsg:
m.height = msg.Height
m.width = msg.Width
}
m.updateKeybindings()
m.sizeInputs()
// Update all textareas
for i := range m.inputs {
newModel, cmd := m.inputs[i].Update(msg)
m.inputs[i] = newModel
cmds = append(cmds, cmd)
}
return m, tea.Batch(cmds...)
}
func (m *model) sizeInputs() {
for i := range m.inputs {
m.inputs[i].SetWidth(m.width / len(m.inputs))
m.inputs[i].SetHeight(m.height - helpHeight)
}
}
func (m *model) updateKeybindings() {
m.keymap.add.SetEnabled(len(m.inputs) < maxInputs)
m.keymap.remove.SetEnabled(len(m.inputs) > minInputs)
}
func (m model) View() string {
help := m.help.ShortHelpView([]key.Binding{
m.keymap.next,
m.keymap.prev,
m.keymap.add,
m.keymap.remove,
m.keymap.quit,
})
var views []string
for i := range m.inputs {
views = append(views, m.inputs[i].View())
}
return lipgloss.JoinHorizontal(lipgloss.Top, views...) + "\n\n" + help
}
func main() {
if _, err := tea.NewProgram(newModel(), tea.WithAltScreen()).Run(); err != nil {
fmt.Println("Error while running program:", err)
os.Exit(1)
}
}

25
go.mod
View File

@ -1,3 +1,28 @@
module ewintr.nl/narratio module ewintr.nl/narratio
go 1.21 go 1.21
require (
github.com/charmbracelet/bubbles v0.17.1
github.com/charmbracelet/bubbletea v0.25.0
github.com/charmbracelet/lipgloss v0.9.1
)
require (
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.18 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.15.2 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.12.0 // indirect
golang.org/x/term v0.6.0 // indirect
golang.org/x/text v0.3.8 // indirect
)

42
go.sum Normal file
View File

@ -0,0 +1,42 @@
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/charmbracelet/bubbles v0.17.1 h1:0SIyjOnkrsfDo88YvPgAWvZMwXe26TP6drRvmkjyUu4=
github.com/charmbracelet/bubbles v0.17.1/go.mod h1:9HxZWlkCqz2PRwsCbYl7a3KXvGzFaDHpYbSYMJ+nE3o=
github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM=
github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg=
github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg=
github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I=
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34=
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=

33
main.go
View File

@ -2,43 +2,28 @@ package main
import ( import (
"fmt" "fmt"
"log"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"time"
) )
type Scene struct { type File struct {
Path string Path string
} }
func main() { func main() {
scenes, err := getScenes()
// Start Neovim with a server name
cmd := exec.Command("nvim", "--servername", "NVIM_SERVER", tempFile.Name())
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Start()
if err != nil { if err != nil {
log.Fatal(err) fmt.Println(err)
os.Exit(1)
} }
// Wait for Neovim to start for _, scene := range scenes {
time.Sleep(time.Second) fmt.Println(scene.Path)
// Send a command to the running Neovim instance
cmd = exec.Command("nvim", "--servername", "NVIM_SERVER", "--remote-send", ":edit /path/to/new/file<CR>")
err = cmd.Run()
if err != nil {
log.Fatal(err)
} }
} }
func getScenes() ([]Scene, error) { func getScenes() ([]File, error) {
scenes := make([]Scene, 0) scenes := make([]File, 0)
root := "content" root := "content"
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil { if err != nil {
@ -46,7 +31,7 @@ func getScenes() ([]Scene, error) {
} }
if !info.IsDir() { if !info.IsDir() {
if filepath.Ext(path) == ".md" { if filepath.Ext(path) == ".md" {
scenes = append(scenes, Scene{Path: path}) scenes = append(scenes, File{Path: path})
} }
} }
return nil return nil

4
tui/tui.go Normal file
View File

@ -0,0 +1,4 @@
package tui
type model struct {
}