DEVELOPMENT ENVIRONMENT

~liljamo/tamma

b6eee0eb2d241b747c16e18ca4da4a1c145c3485 — Jonni Liljamo 28 days ago 27aeaa8
feat: add a way to view output
3 files changed, 95 insertions(+), 15 deletions(-)

M main.go
M styles/styles.go
M types/keymaps.go
M main.go => main.go +59 -15
@@ 9,14 9,17 @@
package main

import (
	"bytes"
	"fmt"
	"os"
	"os/exec"

	"git.src.quest/~skye/tamma/styles"
	"git.src.quest/~skye/tamma/types"
	"github.com/charmbracelet/bubbles/help"
	"github.com/charmbracelet/bubbles/key"
	"github.com/charmbracelet/bubbles/list"
	"github.com/charmbracelet/bubbles/viewport"
	tea "github.com/charmbracelet/bubbletea"
	"github.com/charmbracelet/lipgloss"
	"gopkg.in/yaml.v3"


@@ 27,21 30,29 @@ type state int
const (
	targetSelect state = iota
	actionSelect
	viewOutput
)

type model struct {
	width          int
	height         int
	err            string
	state          state
	targetList     list.Model
	selectedTarget types.TargetItem
	actionList     list.Model
	selectedAction types.ActionItem
	actionListKeys *types.ActionListKeyMap
	width              int
	height             int
	err                string
	out                string
	state              state
	targetList         list.Model
	selectedTarget     types.TargetItem
	actionList         list.Model
	selectedAction     types.ActionItem
	actionListKeys     *types.ActionListKeyMap
	outputViewport     viewport.Model
	outputViewportKeys *types.OutputViewportKeyMap
	outputHelp         help.Model
}

type execFinishedMsg struct{ err error }
type execFinishedMsg struct {
	err error
	out bytes.Buffer
}

func (m model) Init() tea.Cmd {
	return nil


@@ 73,8 84,10 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
						m.err = err.Error()
					} else {
						c := exec.Command("sh", "-c", execString)
						var out bytes.Buffer
						c.Stdout = &out
						return m, tea.ExecProcess(c, func(err error) tea.Msg {
							return execFinishedMsg{err}
							return execFinishedMsg{err, out}
						})
					}
				}


@@ 88,6 101,8 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
					m.actionList.ResetSelected()
					m.state = targetSelect
				}
			case viewOutput:
				m.state = actionSelect
			}
		case "c":
			switch m.state {


@@ 109,8 124,15 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
					}
				}
			}
		case "o":
			switch m.state {
			case actionSelect:
				m.state = viewOutput
			}
		}
	case execFinishedMsg:
		m.out = string(msg.out.Bytes())
		m.outputViewport.SetContent(m.out)
		if msg.err != nil {
			m.err = msg.err.Error()
		}


@@ 125,6 147,10 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
		var cmd tea.Cmd
		m.actionList, cmd = m.actionList.Update(msg)
		return m, cmd
	case viewOutput:
		var cmd tea.Cmd
		m.outputViewport, cmd = m.outputViewport.Update(msg)
		return m, cmd
	}

	return m, nil


@@ 153,6 179,13 @@ func (m model) View() string {
			}
			msg += styles.ActionExecString.Width(tlw).SetString(execString).String()
		}
	case viewOutput:
		m.outputViewport.Height = m.height - 20
		m.outputViewport.Width = m.width - 2
		msg += m.outputViewport.View()
		msg += "\n\npager-like keys apply, plus the following:\n"
		msg += m.outputHelp.View(m.outputViewportKeys)
		msg += "\n"
	}

	if m.err != "" {


@@ 229,22 262,33 @@ func main() {
		return []key.Binding{
			actionListKeys.Back,
			actionListKeys.CopyCommand,
			actionListKeys.ViewOutput,
		}
	}
	actionList.AdditionalFullHelpKeys = func() []key.Binding {
		return []key.Binding{
			actionListKeys.Back,
			actionListKeys.CopyCommand,
			actionListKeys.ViewOutput,
		}
	}
	actionList.Styles.Title = styles.ListTitle
	actionList.Styles.PaginationStyle = styles.ListPaginaton

	outputViewport := viewport.New(20, 20)
	outputViewport.SetHorizontalStep(6) // 6 will be default in bubbles v2
	outputViewport.Style = styles.ViewportStyle
	outputViewportKeys := types.NewOutputViewportKeyMap()
	outputHelp := help.New()

	p := tea.NewProgram(model{
		state:          targetSelect,
		targetList:     targetList,
		actionList:     actionList,
		actionListKeys: actionListKeys,
		state:              targetSelect,
		targetList:         targetList,
		actionList:         actionList,
		actionListKeys:     actionListKeys,
		outputViewport:     outputViewport,
		outputViewportKeys: outputViewportKeys,
		outputHelp:         outputHelp,
	}, tea.WithAltScreen())
	if _, err := p.Run(); err != nil {
		fmt.Printf("An error occured while running: %v\n", err)

M styles/styles.go => styles/styles.go +2 -0
@@ 32,4 32,6 @@ var (
	ActionExecString = lipgloss.NewStyle().
				AlignHorizontal(lipgloss.Center).
				Foreground(lipgloss.Color("145"))

	ViewportStyle = lipgloss.NewStyle().MarginLeft(2)
)

M types/keymaps.go => types/keymaps.go +34 -0
@@ 30,6 30,7 @@ func NewTargetListKeyMap() *TargetListKeyMap {
type ActionListKeyMap struct {
	Back        key.Binding
	CopyCommand key.Binding
	ViewOutput  key.Binding
}

// NewActionListKeyMap returns a new ActionListKeyMap.


@@ 43,5 44,38 @@ func NewActionListKeyMap() *ActionListKeyMap {
			key.WithKeys("c"),
			key.WithHelp("c", "copy cmd"),
		),
		ViewOutput: key.NewBinding(
			key.WithKeys("o"),
			key.WithHelp("o", "view output"),
		),
	}
}

// OutputViewportKeyMap defines key bindings for the output viewport.
type OutputViewportKeyMap struct {
	Back key.Binding
}

// NewOutputViewportKeyMap returns a new OutputViewportKeyMap.
func NewOutputViewportKeyMap() *OutputViewportKeyMap {
	return &OutputViewportKeyMap{
		Back: key.NewBinding(
			key.WithKeys("q"),
			key.WithHelp("q", "back"),
		),
	}
}

// ShortHelp returns keybindings to be shown in the mini help view. It's part
// of the key.Map interface.
func (k OutputViewportKeyMap) ShortHelp() []key.Binding {
	return []key.Binding{k.Back}
}

// FullHelp returns keybindings for the expanded help view. It's part of the
// key.Map interface.
func (k OutputViewportKeyMap) FullHelp() [][]key.Binding {
	return [][]key.Binding{
		{k.Back},
	}
}