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

commit 41a66392830d05a97d5309540648e82134203a45
parent 9e9c0d4bbf7f1b37918049c93c39b1871b1c5220
Author: FIGBERT <figbert@figbert.com>
Date:   Thu, 14 Sep 2023 00:18:35 -0700

Add support for opening comment sections in Caret

Diffstat:
Mapi/api.go | 26++++++++++++++++++++++----
Mapi/schema.go | 27++++++++++++++-------------
Marticle/article.go | 30+++++++++++-------------------
Darticle/style.go | 214-------------------------------------------------------------------------------
Mcmds.go | 38++++++++++++++++----------------------
Acomments/comments.go | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acomments/header.go | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Mitem.go | 4++--
Mmain.go | 19+++++++++++--------
Aui/glamour.go | 214+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mui/styles.go | 8++++++++
Autils/api.go | 8++++++++
Autils/tea.go | 7+++++++
13 files changed, 427 insertions(+), 282 deletions(-)

diff --git a/api/api.go b/api/api.go @@ -9,6 +9,7 @@ import ( ) type PageMsg []Post +type PostMsg Post type ErrorMsg struct{} func FetchPage(index int) tea.Cmd { @@ -18,11 +19,28 @@ func FetchPage(index int) tea.Cmd { return ErrorMsg{} } - return PageMsg(*posts) + return PageMsg(posts) } } -func page(i int) (*[]Post, error) { +func FetchPost(id string) tea.Cmd { + return func() tea.Msg { + resp, err := http.Get(fmt.Sprintf("https://lobste.rs/s/%s.json", id)) + if err != nil { + return ErrorMsg{} + } + defer resp.Body.Close() + + var post Post + err = json.NewDecoder(resp.Body).Decode(&post) + if err != nil { + return ErrorMsg{} + } + return PostMsg(post) + } +} + +func page(i int) ([]Post, error) { resp, err := http.Get(fmt.Sprintf("https://lobste.rs/page/%d.json", i)) if err != nil { return nil, err @@ -30,6 +48,6 @@ func page(i int) (*[]Post, error) { defer resp.Body.Close() var posts []Post - json.NewDecoder(resp.Body).Decode(&posts) - return &posts, nil + err = json.NewDecoder(resp.Body).Decode(&posts) + return posts, err } diff --git a/api/schema.go b/api/schema.go @@ -30,17 +30,18 @@ type Comment struct { } type Post struct { - ShortID string `json:"short_id"` - ShortIDURL string `json:"short_id_url"` - CreatedAt string `json:"created_at"` - Headline string `json:"title"` - URL string `json:"url"` - Score int `json:"score"` - Flags int `json:"flags"` - CommentCount int `json:"comment_count"` - Paragraph string `json:"description"` - DescriptionPlain string `json:"description_plain"` - CommentsURL string `json:"comments_url"` - User User `json:"submitter_user"` - Tags []string `json:"tags"` + ShortID string `json:"short_id"` + ShortIDURL string `json:"short_id_url"` + CreatedAt string `json:"created_at"` + Headline string `json:"title"` + URL string `json:"url"` + Score int `json:"score"` + Flags int `json:"flags"` + CommentCount int `json:"comment_count"` + Paragraph string `json:"description"` + DescriptionPlain string `json:"description_plain"` + CommentsURL string `json:"comments_url"` + User User `json:"submitter_user"` + Tags []string `json:"tags"` + Comments []Comment `json:"comments"` } diff --git a/article/article.go b/article/article.go @@ -7,10 +7,10 @@ import ( "time" "git.figbert.com/caret/ui" + "git.figbert.com/caret/utils" "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/glamour" - gloss "github.com/charmbracelet/lipgloss" md "github.com/JohannesKaufmann/html-to-markdown" "github.com/JohannesKaufmann/html-to-markdown/plugin" @@ -18,50 +18,42 @@ import ( "github.com/go-shiori/go-readability" ) -type Error struct{} -type Start string -type RunCommand *exec.Cmd -type ReturnedFromReading struct{} - -func DisplayLinkedArticle(cmd *exec.Cmd) tea.Cmd { - return tea.ExecProcess(cmd, func(_ error) tea.Msg { - return ReturnedFromReading{} - }) -} - func LoadLinkedArticle(url string) tea.Cmd { return func() tea.Msg { + if url == "" { + return utils.NoExternalURL{} + } + title, article, err := getArticle(url) if err != nil { - return Error{} + return utils.Error{} } header := getReaderModeMetaBlock(title, url) - renderer, err := glamour.NewTermRenderer(glamour.WithStyles(clxStyleConfig)) + renderer, err := glamour.NewTermRenderer(glamour.WithStyles(ui.GlamourConfig)) if err != nil { - return Error{} + return utils.Error{} } markdown, err := renderer.Render(article) if err != nil { - return Error{} + return utils.Error{} } renderedArticle := header + markdown - promptStyle := gloss.NewStyle().Foreground(ui.LobstersRed()).Background(gloss.Color("7")).Padding(0, 1) command := exec.Command("less", "--RAW-CONTROL-CHARS", "--ignore-case", "--tilde", "--use-color", - "-P?e"+promptStyle.Render("End"), + "-P?e"+ui.Prompt().Render("End"), "-DSy", "-DP-") command.Stdin = strings.NewReader(renderedArticle) - return RunCommand(command) + return utils.RunCommand(command) } } diff --git a/article/style.go b/article/style.go @@ -1,214 +0,0 @@ -package article - -import ( - "github.com/charmbracelet/glamour/ansi" -) - -var clxStyleConfig = ansi.StyleConfig{ - Document: ansi.StyleBlock{ - StylePrimitive: ansi.StylePrimitive{ - BlockPrefix: "\n", - BlockSuffix: "\n", - Color: stringPtr("252"), - }, - Margin: uintPtr(2), - }, - BlockQuote: ansi.StyleBlock{ - StylePrimitive: ansi.StylePrimitive{Color: stringPtr("244")}, - Indent: uintPtr(1), - IndentToken: stringPtr("│ "), - }, - List: ansi.StyleList{ - LevelIndent: 2, - }, - Heading: ansi.StyleBlock{ - StylePrimitive: ansi.StylePrimitive{ - BlockSuffix: "\n", - Color: stringPtr("39"), - Bold: boolPtr(true), - }, - }, - H1: ansi.StyleBlock{ - StylePrimitive: ansi.StylePrimitive{ - Prefix: " ", - Suffix: " ", - Color: stringPtr("228"), - BackgroundColor: stringPtr("63"), - Bold: boolPtr(true), - }, - }, - H2: ansi.StyleBlock{ - StylePrimitive: ansi.StylePrimitive{ - Color: stringPtr("252"), - }, - }, - H3: ansi.StyleBlock{ - StylePrimitive: ansi.StylePrimitive{ - Underline: boolPtr(true), - }, - }, - H4: ansi.StyleBlock{ - StylePrimitive: ansi.StylePrimitive{ - Color: stringPtr("220"), - Underline: boolPtr(true), - }, - }, - H5: ansi.StyleBlock{ - StylePrimitive: ansi.StylePrimitive{ - Prefix: "##### ", - }, - }, - H6: ansi.StyleBlock{ - StylePrimitive: ansi.StylePrimitive{ - Prefix: "###### ", - Color: stringPtr("35"), - Bold: boolPtr(false), - }, - }, - Strikethrough: ansi.StylePrimitive{ - CrossedOut: boolPtr(true), - }, - Emph: ansi.StylePrimitive{ - Italic: boolPtr(true), - }, - Strong: ansi.StylePrimitive{ - Bold: boolPtr(true), - }, - HorizontalRule: ansi.StylePrimitive{ - Color: stringPtr("240"), - Format: "\n--------\n", - }, - Item: ansi.StylePrimitive{ - BlockPrefix: "• ", - }, - Enumeration: ansi.StylePrimitive{ - BlockPrefix: ". ", - }, - Task: ansi.StyleTask{ - StylePrimitive: ansi.StylePrimitive{}, - Ticked: "[✓] ", - Unticked: "[ ] ", - }, - Link: ansi.StylePrimitive{Color: stringPtr("#7d0e09")}, - LinkText: ansi.StylePrimitive{Bold: boolPtr(true)}, - Image: ansi.StylePrimitive{ - Color: stringPtr("245"), - Underline: boolPtr(true), - }, - ImageText: ansi.StylePrimitive{ - Color: stringPtr("88"), - Format: "Image: {{.text}} →", - }, - Code: ansi.StyleBlock{ - StylePrimitive: ansi.StylePrimitive{ - Color: stringPtr("1"), - Italic: boolPtr(true), - }, - }, - CodeBlock: ansi.StyleCodeBlock{ - StyleBlock: ansi.StyleBlock{ - StylePrimitive: ansi.StylePrimitive{ - Color: stringPtr("244"), - }, - Margin: uintPtr(2), - }, - Chroma: &ansi.Chroma{ - Text: ansi.StylePrimitive{ - Color: stringPtr("#C4C4C4"), - }, - Error: ansi.StylePrimitive{ - Color: stringPtr("#F1F1F1"), - BackgroundColor: stringPtr("#F05B5B"), - }, - Comment: ansi.StylePrimitive{ - Color: stringPtr("#676767"), - }, - CommentPreproc: ansi.StylePrimitive{ - Color: stringPtr("#FF875F"), - }, - Keyword: ansi.StylePrimitive{ - Color: stringPtr("#00AAFF"), - }, - KeywordReserved: ansi.StylePrimitive{ - Color: stringPtr("#FF5FD2"), - }, - KeywordNamespace: ansi.StylePrimitive{ - Color: stringPtr("#FF5F87"), - }, - KeywordType: ansi.StylePrimitive{ - Color: stringPtr("#6E6ED8"), - }, - Operator: ansi.StylePrimitive{ - Color: stringPtr("#EF8080"), - }, - Punctuation: ansi.StylePrimitive{ - Color: stringPtr("#E8E8A8"), - }, - Name: ansi.StylePrimitive{ - Color: stringPtr("#C4C4C4"), - }, - NameBuiltin: ansi.StylePrimitive{ - Color: stringPtr("#FF8EC7"), - }, - NameTag: ansi.StylePrimitive{ - Color: stringPtr("#B083EA"), - }, - NameAttribute: ansi.StylePrimitive{ - Color: stringPtr("#7A7AE6"), - }, - NameClass: ansi.StylePrimitive{ - Color: stringPtr("#F1F1F1"), - Underline: boolPtr(true), - Bold: boolPtr(true), - }, - NameDecorator: ansi.StylePrimitive{ - Color: stringPtr("#FFFF87"), - }, - NameFunction: ansi.StylePrimitive{ - Color: stringPtr("#00D787"), - }, - LiteralNumber: ansi.StylePrimitive{ - Color: stringPtr("#6EEFC0"), - }, - LiteralString: ansi.StylePrimitive{ - Color: stringPtr("#C69669"), - }, - LiteralStringEscape: ansi.StylePrimitive{ - Color: stringPtr("#AFFFD7"), - }, - GenericDeleted: ansi.StylePrimitive{ - Color: stringPtr("#FD5B5B"), - }, - GenericEmph: ansi.StylePrimitive{ - Italic: boolPtr(true), - }, - GenericInserted: ansi.StylePrimitive{ - Color: stringPtr("#00D787"), - }, - GenericStrong: ansi.StylePrimitive{ - Bold: boolPtr(true), - }, - GenericSubheading: ansi.StylePrimitive{ - Color: stringPtr("#777777"), - }, - Background: ansi.StylePrimitive{ - BackgroundColor: stringPtr("#373737"), - }, - }, - }, - Table: ansi.StyleTable{ - StyleBlock: ansi.StyleBlock{ - StylePrimitive: ansi.StylePrimitive{}, - }, - CenterSeparator: stringPtr("┼"), - ColumnSeparator: stringPtr("│"), - RowSeparator: stringPtr("─"), - }, - DefinitionDescription: ansi.StylePrimitive{ - BlockPrefix: "\n🠶 ", - }, -} - -func boolPtr(b bool) *bool { return &b } -func stringPtr(s string) *string { return &s } -func uintPtr(u uint) *uint { return &u } diff --git a/cmds.go b/cmds.go @@ -5,27 +5,25 @@ import ( "runtime" "time" + "git.figbert.com/caret/api" "git.figbert.com/caret/article" + "git.figbert.com/caret/utils" "github.com/charmbracelet/bubbles/list" "github.com/charmbracelet/bubbletea" ) -type genericError struct{} -type postHasNoLinkToOpen struct{} +type returnedFromReading struct{} type platformUnsupported struct{} type msgExpired struct{} type loading struct{} func attemptToOpenPostURL(itm list.Item) tea.Cmd { return func() tea.Msg { - i, ok := itm.(item) - if !ok { - return genericError{} - } + i := itm.(item) if i.post.URL == "" { - return postHasNoLinkToOpen{} + return utils.NoExternalURL{} } success := openURL(i.post.URL) @@ -39,10 +37,7 @@ func attemptToOpenPostURL(itm list.Item) tea.Cmd { func attemptToOpenPostComments(itm list.Item) tea.Cmd { return func() tea.Msg { - i, ok := itm.(item) - if !ok { - return genericError{} - } + i := itm.(item) success := openURL(i.post.CommentsURL) if !success { @@ -79,17 +74,16 @@ func startLoading() tea.Msg { return loading{} } -func displayLinkedArticle(itm list.Item) tea.Cmd { - return func() tea.Msg { - i, ok := itm.(item) - if !ok { - return genericError{} - } +func displayLinkedArticle(i list.Item) tea.Cmd { + return article.LoadLinkedArticle(i.(item).post.URL) +} - if i.post.URL == "" { - return postHasNoLinkToOpen{} - } +func fetchComments(i list.Item) tea.Cmd { + return api.FetchPost(i.(item).post.ShortID) +} - return article.Start(i.post.URL) - } +func run(cmd *exec.Cmd) tea.Cmd { + return tea.ExecProcess(cmd, func(_ error) tea.Msg { + return returnedFromReading{} + }) } diff --git a/comments/comments.go b/comments/comments.go @@ -0,0 +1,61 @@ +package comments + +import ( + "fmt" + "os/exec" + "strings" + + "git.figbert.com/caret/api" + "git.figbert.com/caret/ui" + "git.figbert.com/caret/utils" + + "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/glamour" + gloss "github.com/charmbracelet/lipgloss" + + "github.com/dustin/go-humanize" +) + +func Load(post api.Post) tea.Cmd { + return func() tea.Msg { + renderer, err := glamour.NewTermRenderer(glamour.WithStyles(ui.GlamourConfig)) + if err != nil { + return utils.Error{} + } + + var page strings.Builder + page.WriteString(header(&post)) + + for _, comment := range post.Comments { + cmnt, err := renderer.Render(comment.CommentPlain) + if err != nil { + return utils.Error{} + } + cmnt = strings.TrimSpace(cmnt) + + headline := fmt.Sprintf( + " %s %s", + ui.Bold().Render(comment.User.Username), + ui.SecondaryText().Render(humanize.Time( + utils.TimeFromAPI(comment.CreatedAt), + )), + ) + + composite := fmt.Sprintf("%s\n%s", headline, cmnt) + margin := gloss.NewStyle().MarginLeft(2 * comment.Depth) + page.WriteString("\n\n" + margin.Render(composite)) + } + + command := exec.Command("less", + "--RAW-CONTROL-CHARS", + "--ignore-case", + "--tilde", + "--use-color", + "-P?e"+ui.Prompt().Render("End"), + "-DSy", + "-DP-") + command.Stdin = strings.NewReader(page.String()) + + return utils.RunCommand(command) + } +} diff --git a/comments/header.go b/comments/header.go @@ -0,0 +1,53 @@ +package comments + +import ( + "fmt" + "strings" + + "git.figbert.com/caret/api" + "git.figbert.com/caret/ui" + "git.figbert.com/caret/utils" + + gloss "github.com/charmbracelet/lipgloss" + + "github.com/dustin/go-humanize" +) + +func header(post *api.Post) string { + blockWidth := 72 + textWidth := blockWidth - 2 + + box := gloss.NewStyle(). + BorderStyle(gloss.RoundedBorder()). + PaddingLeft(1). + PaddingRight(1). + MarginLeft(1). + MarginTop(1). + Width(blockWidth) + + url := post.URL + urlRunes := []rune(url) + if len(urlRunes) > textWidth { + url = string(append(urlRunes[:textWidth-1], '…')) + } + + posted := utils.TimeFromAPI(post.CreatedAt) + + user := fmt.Sprintf("by %s %s", ui.Title().Render(post.User.Username), ui.SecondaryText().Render(humanize.Time(posted))) + id := fmt.Sprintf("ID %s", gloss.NewStyle().Foreground(gloss.Color("2")).Render(post.ShortID)) + cmnts := fmt.Sprintf("%s comments", gloss.NewStyle().Foreground(gloss.Color("5")).Render(fmt.Sprintf("%d", post.CommentCount))) + karma := fmt.Sprintf("%s karma", ui.TagText().Render(fmt.Sprintf("%d", post.Score))) + + topSpacer := strings.Repeat(" ", textWidth-gloss.Width(user)-gloss.Width(id)) + bottomSpacer := strings.Repeat(" ", textWidth-gloss.Width(cmnts)-gloss.Width(karma)) + + return box.Render( + fmt.Sprintf( + "%s\n%s\n\n%s%s%s\n%s%s%s", + post.Headline, + gloss.NewStyle().Foreground(gloss.Color("4")).Render(url), + user, topSpacer, id, + cmnts, bottomSpacer, karma, + ), + ) +} diff --git a/item.go b/item.go @@ -4,10 +4,10 @@ import ( "fmt" "net/url" "strings" - "time" "git.figbert.com/caret/api" "git.figbert.com/caret/ui" + "git.figbert.com/caret/utils" "github.com/dustin/go-humanize" ) @@ -27,7 +27,7 @@ func (i item) Title() string { return fmt.Sprintf("%s %s", i.post.Headline, displayURL) } func (itm item) Description() string { - created, _ := time.Parse("2006-01-02T15:04:05.999-07:00", itm.post.CreatedAt) + created := utils.TimeFromAPI(itm.post.CreatedAt) intro := ui.SecondaryText().Render( fmt.Sprintf( diff --git a/main.go b/main.go @@ -4,8 +4,9 @@ import ( "strings" "git.figbert.com/caret/api" - "git.figbert.com/caret/article" + "git.figbert.com/caret/comments" "git.figbert.com/caret/ui" + "git.figbert.com/caret/utils" "github.com/charmbracelet/bubbles/list" "github.com/charmbracelet/bubbletea" @@ -41,6 +42,8 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { cmds = append(cmds, attemptToOpenPostComments(m.posts.SelectedItem())) case " ": cmds = append(cmds, displayLinkedArticle(m.posts.SelectedItem()), startLoading) + case "enter": + cmds = append(cmds, fetchComments(m.posts.SelectedItem()), startLoading) case "j": if m.posts.Index() == len(m.posts.Items())-1 { m.page++ @@ -57,25 +60,25 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { for _, pst := range []api.Post(msg) { m.posts.SetItems(append(m.posts.Items(), item{post: pst})) } + case api.PostMsg: + cmds = append(cmds, comments.Load(api.Post(msg))) case api.ErrorMsg: m.msg = "Failed to fetch new stories." cmds = append(cmds, clearMsg) case loading: m.msg = "Loading…" - case genericError, article.Error: + case utils.Error: m.msg = "Error! How perplexing." cmds = append(cmds, clearMsg) - case postHasNoLinkToOpen: + case utils.NoExternalURL: m.msg = "This post does not have an external URL to open." cmds = append(cmds, clearMsg) case platformUnsupported: m.msg = "Exotic! Opening URLs on your OS is not yet supported. Reach out!" cmds = append(cmds, clearMsg) - case article.Start: - cmds = append(cmds, article.LoadLinkedArticle(string(msg))) - case article.RunCommand: - cmds = append(cmds, article.DisplayLinkedArticle(msg)) - case article.ReturnedFromReading, msgExpired: + case utils.RunCommand: + cmds = append(cmds, run(msg)) + case returnedFromReading, msgExpired: m.msg = "" } diff --git a/ui/glamour.go b/ui/glamour.go @@ -0,0 +1,214 @@ +package ui + +import ( + "github.com/charmbracelet/glamour/ansi" +) + +var GlamourConfig = ansi.StyleConfig{ + Document: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + BlockPrefix: "\n", + BlockSuffix: "\n", + Color: stringPtr("252"), + }, + Margin: uintPtr(2), + }, + BlockQuote: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{Color: stringPtr("244")}, + Indent: uintPtr(1), + IndentToken: stringPtr("│ "), + }, + List: ansi.StyleList{ + LevelIndent: 2, + }, + Heading: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + BlockSuffix: "\n", + Color: stringPtr("39"), + Bold: boolPtr(true), + }, + }, + H1: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Prefix: " ", + Suffix: " ", + Color: stringPtr("228"), + BackgroundColor: stringPtr("63"), + Bold: boolPtr(true), + }, + }, + H2: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Color: stringPtr("252"), + }, + }, + H3: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Underline: boolPtr(true), + }, + }, + H4: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Color: stringPtr("220"), + Underline: boolPtr(true), + }, + }, + H5: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Prefix: "##### ", + }, + }, + H6: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Prefix: "###### ", + Color: stringPtr("35"), + Bold: boolPtr(false), + }, + }, + Strikethrough: ansi.StylePrimitive{ + CrossedOut: boolPtr(true), + }, + Emph: ansi.StylePrimitive{ + Italic: boolPtr(true), + }, + Strong: ansi.StylePrimitive{ + Bold: boolPtr(true), + }, + HorizontalRule: ansi.StylePrimitive{ + Color: stringPtr("240"), + Format: "\n--------\n", + }, + Item: ansi.StylePrimitive{ + BlockPrefix: "• ", + }, + Enumeration: ansi.StylePrimitive{ + BlockPrefix: ". ", + }, + Task: ansi.StyleTask{ + StylePrimitive: ansi.StylePrimitive{}, + Ticked: "[✓] ", + Unticked: "[ ] ", + }, + Link: ansi.StylePrimitive{Color: stringPtr("#7d0e09")}, + LinkText: ansi.StylePrimitive{Bold: boolPtr(true)}, + Image: ansi.StylePrimitive{ + Color: stringPtr("245"), + Underline: boolPtr(true), + }, + ImageText: ansi.StylePrimitive{ + Color: stringPtr("88"), + Format: "Image: {{.text}} →", + }, + Code: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Color: stringPtr("1"), + Italic: boolPtr(true), + }, + }, + CodeBlock: ansi.StyleCodeBlock{ + StyleBlock: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Color: stringPtr("244"), + }, + Margin: uintPtr(2), + }, + Chroma: &ansi.Chroma{ + Text: ansi.StylePrimitive{ + Color: stringPtr("#C4C4C4"), + }, + Error: ansi.StylePrimitive{ + Color: stringPtr("#F1F1F1"), + BackgroundColor: stringPtr("#F05B5B"), + }, + Comment: ansi.StylePrimitive{ + Color: stringPtr("#676767"), + }, + CommentPreproc: ansi.StylePrimitive{ + Color: stringPtr("#FF875F"), + }, + Keyword: ansi.StylePrimitive{ + Color: stringPtr("#00AAFF"), + }, + KeywordReserved: ansi.StylePrimitive{ + Color: stringPtr("#FF5FD2"), + }, + KeywordNamespace: ansi.StylePrimitive{ + Color: stringPtr("#FF5F87"), + }, + KeywordType: ansi.StylePrimitive{ + Color: stringPtr("#6E6ED8"), + }, + Operator: ansi.StylePrimitive{ + Color: stringPtr("#EF8080"), + }, + Punctuation: ansi.StylePrimitive{ + Color: stringPtr("#E8E8A8"), + }, + Name: ansi.StylePrimitive{ + Color: stringPtr("#C4C4C4"), + }, + NameBuiltin: ansi.StylePrimitive{ + Color: stringPtr("#FF8EC7"), + }, + NameTag: ansi.StylePrimitive{ + Color: stringPtr("#B083EA"), + }, + NameAttribute: ansi.StylePrimitive{ + Color: stringPtr("#7A7AE6"), + }, + NameClass: ansi.StylePrimitive{ + Color: stringPtr("#F1F1F1"), + Underline: boolPtr(true), + Bold: boolPtr(true), + }, + NameDecorator: ansi.StylePrimitive{ + Color: stringPtr("#FFFF87"), + }, + NameFunction: ansi.StylePrimitive{ + Color: stringPtr("#00D787"), + }, + LiteralNumber: ansi.StylePrimitive{ + Color: stringPtr("#6EEFC0"), + }, + LiteralString: ansi.StylePrimitive{ + Color: stringPtr("#C69669"), + }, + LiteralStringEscape: ansi.StylePrimitive{ + Color: stringPtr("#AFFFD7"), + }, + GenericDeleted: ansi.StylePrimitive{ + Color: stringPtr("#FD5B5B"), + }, + GenericEmph: ansi.StylePrimitive{ + Italic: boolPtr(true), + }, + GenericInserted: ansi.StylePrimitive{ + Color: stringPtr("#00D787"), + }, + GenericStrong: ansi.StylePrimitive{ + Bold: boolPtr(true), + }, + GenericSubheading: ansi.StylePrimitive{ + Color: stringPtr("#777777"), + }, + Background: ansi.StylePrimitive{ + BackgroundColor: stringPtr("#373737"), + }, + }, + }, + Table: ansi.StyleTable{ + StyleBlock: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{}, + }, + CenterSeparator: stringPtr("┼"), + ColumnSeparator: stringPtr("│"), + RowSeparator: stringPtr("─"), + }, + DefinitionDescription: ansi.StylePrimitive{ + BlockPrefix: "\n🠶 ", + }, +} + +func boolPtr(b bool) *bool { return &b } +func stringPtr(s string) *string { return &s } +func uintPtr(u uint) *uint { return &u } diff --git a/ui/styles.go b/ui/styles.go @@ -14,6 +14,10 @@ func TagText() gloss.Style { return gloss.NewStyle().Foreground(Orange()) } +func Bold() gloss.Style { + return gloss.NewStyle().Bold(true) +} + func LobstersRed() gloss.Color { return gloss.Color("#AC130D") } @@ -34,3 +38,7 @@ func HR(width int) string { Foreground(Gray()). Render() } + +func Prompt() gloss.Style { + return gloss.NewStyle().Foreground(LobstersRed()).Background(gloss.Color("7")).Padding(0, 1) +} diff --git a/utils/api.go b/utils/api.go @@ -0,0 +1,8 @@ +package utils + +import "time" + +func TimeFromAPI(str string) time.Time { + created, _ := time.Parse("2006-01-02T15:04:05.999-07:00", str) + return created +} diff --git a/utils/tea.go b/utils/tea.go @@ -0,0 +1,7 @@ +package utils + +import "os/exec" + +type Error struct{} +type RunCommand *exec.Cmd +type NoExternalURL struct{}