| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191 |
- // The package defines an extensible TUI via the bubbletea framework.
- //
- // While the package remains in v0.0.X releases, this TUI may be undocumented.
- package issues
- import (
- "fmt"
- "strings"
- tea "github.com/charmbracelet/bubbletea"
- "github.com/charmbracelet/lipgloss"
- )
- // The main bubbletea Model
- type Model struct {
- issue Issue
- collection IssueCollection
- content string
- Path string
- selection int // index for the collection browser
- // viewport viewport.Model
- }
- func (m Model) Init() tea.Cmd { return m.load }
- // Handles quit logic and viewport scroll and size updates
- func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
- switch msg := msg.(type) {
- case tea.KeyMsg:
- switch msg.String() {
- case "q":
- return m, tea.Quit
- case "k":
- if m.selection+1 < len(m.collection) {
- m.selection = m.selection + 1
- } else {
- m.selection = 0
- }
- return m, m.renderIssueCollection
- case "j":
- if m.selection != 0 {
- m.selection = m.selection - 1
- } else {
- m.selection = len(m.collection) - 1
- }
- return m, m.renderIssueCollection
- case "enter":
- m.Path = m.collection[m.selection].Path
- return m, m.load
- }
- case Issue:
- m.issue = msg
- return m, m.renderIssue
- case IssueCollection:
- m.collection = msg
- return m, m.renderIssueCollection
- case string:
- m.content = msg
- return m, nil
- }
- return m, nil
- }
- // Handles load logic
- func (m Model) load() tea.Msg {
- if IsIssue(m.Path) {
- issue, err := m.issue.NewFromPath(m.Path)
- if err != nil {
- return nil
- }
- return issue
- }
- if IsIssueCollection(m.Path) {
- collection, err := m.collection.NewFromPath(m.Path)
- if err != nil {
- return nil
- }
- return collection
- }
- return nil
- }
- // [lipgloss] style definitions
- var (
- titleStyle = lipgloss.NewStyle().
- Bold(true).
- Underline(true)
- statusStyle = lipgloss.NewStyle().
- Faint(true).
- Italic(true)
- variadicTitleStyle = lipgloss.NewStyle().
- Align(lipgloss.Left).
- Italic(true)
- variadicDataStyle = lipgloss.NewStyle().
- Width(40).
- BorderStyle(lipgloss.ASCIIBorder())
- borderStyle = lipgloss.NewStyle().
- Padding(1, 2).
- Margin(1).
- BorderStyle(lipgloss.NormalBorder())
- indexStyle = lipgloss.NewStyle().
- Italic(true)
- pointerStyle = lipgloss.NewStyle().
- Faint(true)
- collectionStyleLeft = lipgloss.NewStyle().
- Align(lipgloss.Left)
- )
- // Handles all view logic for [issue.Issue]
- func (m Model) renderIssue() tea.Msg {
- var output string
- // title
- output = output + titleStyle.Render(m.issue.Title)
- // status
- output = output + fmt.Sprintf("\n%s", statusStyle.Render(m.issue.Status.Data))
- // variadics
- var tags string
- for _, field := range m.issue.Tags.Fields {
- tags = tags + field.Path + ", "
- }
- tags = strings.TrimRight(tags, ", ")
- var blockedby string
- for _, field := range m.issue.Blockedby.Fields {
- blockedby = blockedby + field.Path + ", "
- }
- blockedby = strings.TrimRight(blockedby, ", ")
- if len(m.issue.Tags.Fields) > 0 {
- output = output + variadicTitleStyle.Render("\n\nTags:")
- output = output + fmt.Sprintf("\n%s", variadicDataStyle.Render(tags))
- }
- if len(m.issue.Blockedby.Fields) > 0 {
- output = output + variadicTitleStyle.Render("\n\nBlockedby:")
- output = output + fmt.Sprintf("\n%s", variadicDataStyle.Render(blockedby))
- }
- // description
- output = output + titleStyle.Render("\n\nDescription:\n")
- output = output + fmt.Sprintf("\n%s", m.issue.Description.Data)
- return borderStyle.Render(output)
- }
- func (m Model) renderIssueCollection() tea.Msg {
- var output string
- var left string
- output = output + "Issues in " + m.Path + "...\n\n"
- for i, issue := range m.collection {
- // pointer render
- if i == m.selection {
- left = left + pointerStyle.Render("-> ")
- } else {
- left = left + pointerStyle.Render(" ")
- }
- // index render
- left = left + "[" + indexStyle.Render(fmt.Sprintf("%d", i+1)) + "]: "
- // title render
- left = left + fmt.Sprintf("%s\n", titleStyle.Render(issue.Title))
- }
- output = output + collectionStyleLeft.Render(left)
- output = output + "\nj/k: down/up\tenter: select\tq: quit"
- return output
- }
- // Wraps [issue.Model.renderIssue] in a viewport
- func (m Model) View() string {
- if len(m.content) == 0 {
- return "loading..."
- }
- return m.content
- }
|