caret

[ACTIVE] a command line tool for browsing Lobsters in your terminal
git clone git://git.figbert.com/caret.git
Log | Files | Refs | README | LICENSE

main.go (3177B)


      1 package main
      2 
      3 import (
      4 	"fmt"
      5 	"strings"
      6 
      7 	"git.figbert.com/caret/api"
      8 	"git.figbert.com/caret/comments"
      9 	"git.figbert.com/caret/ui"
     10 	"git.figbert.com/caret/utils"
     11 
     12 	"github.com/charmbracelet/bubbles/list"
     13 	"github.com/charmbracelet/bubbletea"
     14 )
     15 
     16 type model struct {
     17 	width, height int
     18 	page          int
     19 	msg           string
     20 	posts         list.Model
     21 }
     22 
     23 func (m model) Init() tea.Cmd {
     24 	return tea.Batch(api.FetchPage(m.page), startLoading)
     25 }
     26 
     27 func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
     28 	var cmds []tea.Cmd
     29 
     30 	switch msg := msg.(type) {
     31 	case tea.WindowSizeMsg:
     32 		m.width = msg.Width
     33 		m.height = msg.Height
     34 		m.posts.SetWidth(msg.Width)
     35 		m.posts.SetHeight(msg.Height - 4)
     36 	case tea.KeyMsg:
     37 		switch msg.String() {
     38 		case "ctrl+c":
     39 			return m, tea.Quit
     40 		case "o":
     41 			cmds = append(cmds, attemptToOpenPostURL(m.posts.SelectedItem()))
     42 		case "c":
     43 			cmds = append(cmds, attemptToOpenPostComments(m.posts.SelectedItem()))
     44 		case " ":
     45 			cmds = append(cmds, displayLinkedArticle(m.posts.SelectedItem()), startLoading)
     46 		case "enter":
     47 			cmds = append(cmds, fetchComments(m.posts.SelectedItem()), startLoading)
     48 		case "j":
     49 			if m.posts.Index() == len(m.posts.Items())-1 {
     50 				m.page++
     51 				cmds = append(cmds, api.FetchPage(m.page), startLoading)
     52 			}
     53 			fallthrough
     54 		default:
     55 			var cmd tea.Cmd
     56 			m.posts, cmd = m.posts.Update(msg)
     57 			cmds = append(cmds, cmd)
     58 		}
     59 	case api.PageMsg:
     60 		m.msg = ""
     61 		for _, pst := range []api.Post(msg) {
     62 			m.posts.SetItems(append(m.posts.Items(), item{post: pst}))
     63 		}
     64 	case api.PostMsg:
     65 		cmds = append(cmds, comments.Load(api.Post(msg)))
     66 	case api.ErrorMsg:
     67 		m.msg = fmt.Sprintf("Failed to fetch new stories -> %s", msg)
     68 		cmds = append(cmds, clearMsg)
     69 	case loading:
     70 		m.msg = "Loading…"
     71 	case utils.Error:
     72 		m.msg = "Error! How perplexing."
     73 		cmds = append(cmds, clearMsg)
     74 	case utils.NoExternalURL:
     75 		m.msg = "This post does not have an external URL to open."
     76 		cmds = append(cmds, clearMsg)
     77 	case platformUnsupported:
     78 		m.msg = "Exotic! Opening URLs on your OS is not yet supported. Reach out!"
     79 		cmds = append(cmds, clearMsg)
     80 	case utils.RunCommand:
     81 		cmds = append(cmds, run(msg))
     82 	case returnedFromReading, msgExpired:
     83 		m.msg = ""
     84 	}
     85 
     86 	return m, tea.Batch(cmds...)
     87 }
     88 
     89 func (m model) View() string {
     90 	var view strings.Builder
     91 
     92 	view.WriteString("\n " + ui.LobstersRed().Render("Lobsters"))
     93 
     94 	padding := m.width - 1 - 8 - len(m.msg) - 1
     95 	if padding < 0 {
     96 		errorDot := ui.LobstersRed().Render("● ")
     97 		padding = m.width - 1 - 8 - 1 - 1
     98 		if padding < 0 {
     99 			view.WriteString(" " + errorDot)
    100 		} else {
    101 			view.WriteString(strings.Repeat(" ", padding) + errorDot)
    102 		}
    103 	} else {
    104 		view.WriteString(strings.Repeat(" ", padding) + ui.Gray().Render(m.msg) + " ")
    105 	}
    106 
    107 	view.WriteString("\n " + ui.HR(m.width-2) + "\n")
    108 	if len(m.posts.Items()) > 0 {
    109 		view.WriteString(m.posts.View())
    110 	}
    111 
    112 	return view.String()
    113 }
    114 
    115 func main() {
    116 	m := model{
    117 		page:  1,
    118 		posts: list.New([]list.Item{}, itemDelegate{}, 0, 0),
    119 	}
    120 	m.posts.SetShowStatusBar(false)
    121 	m.posts.SetShowHelp(false)
    122 	m.posts.SetFilteringEnabled(false)
    123 	m.posts.SetShowTitle(false)
    124 	m.posts.SetShowPagination(false)
    125 
    126 	tea.NewProgram(m, tea.WithAltScreen()).Run()
    127 }