This repository has been archived on 2024-07-28. You can view files and clone it, but cannot push or open issues or pull requests.
cairde/ui/buffer.go
decentral1se e79c2f0160
Some checks failed
continuous-integration/drone/pr Build is failing
WIP: buffers and command history [ci skip]
2024-02-01 11:26:43 +01:00

169 lines
4.7 KiB
Go

package ui
import (
"fmt"
"os"
"path"
"strings"
"github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea"
)
const (
// Status is the status buffer.
Status = iota
// Profile is the profile buffer.
Profile
)
// BufferHandler is the concrete functionality of a Buffer.
type BufferHandler interface {
validate(m model, input string) error // validate incoming input
handle(input, hiddenInput string) tea.Msg // handle and dispatch messages from input
persist(m *model, input string) // store buffer input for persistence
close(m model) error // gracefully destroy a buffer
}
// Buffer is a user-facing interactive buffer in the TUI.
type Buffer struct {
name string // user-friendly buffer name for the menu
inputChannel chan string // channel for passing input to handlers
viewport viewport.Model // viewport for rendering lines
viewportLines []string // lines for viewport renderer
viewportIsReady bool // whether or not the viewport is ready to render
inputHistory []string // buffer input history for optional persistence
persistence bool // whether or not to persist buffer input to file
BufferHandler
}
// StatusBuffer is the privileged status buffer where e.g. you input
// program-wide commands. It can also report general status information, e.g.
// the status of ACN connectivity.
type StatusBufferHandler Buffer
func (s StatusBufferHandler) validate(m model, input string) error {
if string(input[0]) != "/" {
return fmt.Errorf("Woops, this is not a chat buffer. Only commands are allowed")
}
return nil
}
func (s StatusBufferHandler) handle(input, hiddenInput string) tea.Msg {
cmds := strings.Split(input, " ")
switch {
case cmds[0] == "help":
return cmdMsg{output: strings.Split(cmdHelp, "\n")}
case cmds[0] == "quit":
return turnAcnOffMsg{quitProgram: true}
case cmds[0] == "start":
return showGettingStartedGuideMsg{}
case cmds[0] == "profile":
if cmds[1] != "unlock" && cmds[1] != "info" && cmds[1] != "create" {
profileHelp := `Unknown "profile" command?
/profile create <name> <password> <password> | Create a new profile
/profile info | Show profile information
/profile unlock <password> | Unlock profile(s)`
return cmdMsg{output: strings.Split(profileHelp, "\n")}
}
switch {
case cmds[1] == "unlock":
if len(cmds) != 3 {
return cmdMsg{output: strings.Split(cmdHelp, "\n")}
}
return profileUnlockMsg{
password: strings.TrimSpace(hiddenInput),
}
case cmds[1] == "info":
return profileInfoMsg{}
case cmds[1] == "create":
if len(cmds) != 5 {
return cmdMsg{output: strings.Split(cmdHelp, "\n")}
}
passwords := strings.Split(strings.TrimSpace(hiddenInput), " ")
if len(passwords) != 2 {
return cmdMsg{output: []string{"Profile create: unable to parse hidden input"}}
}
if passwords[0] != passwords[1] {
return cmdMsg{output: []string{"Profile create: passwords do not match?"}}
}
return profileCreateMsg{
name: cmds[2],
password: passwords[1],
}
}
case cmds[0] == "acn":
if cmds[1] != "on" && cmds[1] != "off" {
acnHelp := `Unknown "acn" command?
/acn on | Turn on the Tor ACN
/acn off | Turn off the Tor ACN`
return cmdMsg{output: strings.Split(acnHelp, "\n")}
}
switch {
case cmds[1] == "on":
return turnAcnOnMsg{}
case cmds[1] == "off":
return turnAcnOffMsg{}
}
case cmds[0] == "clear":
return clearScreenMsg{}
}
return cmdMsg{output: []string{unknownCmdHelp}}
}
func (s StatusBufferHandler) persist(m *model, input string) {
buffer := m.buffers[m.menuState]
if buffer.persistence {
buffer.inputHistory = append(buffer.inputHistory, input)
m.buffers[m.menuState] = buffer
}
}
func (s StatusBufferHandler) close(m model) error {
buffer := m.buffers[m.menuState]
cairdeLogsDir := path.Join(m.userDir, "logs")
bufferLogsPath := path.Join(cairdeLogsDir, buffer.name)
if !buffer.persistence {
return nil
}
f, err := os.OpenFile(bufferLogsPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0664)
if err != nil {
return fmt.Errorf("unable to create/open file %s: %s", bufferLogsPath, err)
}
for _, cmd := range buffer.inputHistory {
if _, err := f.WriteString(cmd + "\n"); err != nil {
return fmt.Errorf("unable to append input to %s: %s", bufferLogsPath, err)
}
}
if err := f.Close(); err != nil {
return fmt.Errorf("unable to close %s file handler: %s", bufferLogsPath, err)
}
return nil
}
// ProfileBuffer is the profile buffer. There can be several profile buffers
// which correspond to several profiles. Profile buffers take input and report
// output for profile-specific information, e.g. invites from a new contact.
type ProfileBuffer Buffer