From 1abaec68066210cf6ad930e496678e58dba03f78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=83=9E=E3=83=AA=E3=82=A6=E3=82=B9?= Date: Sun, 11 Jul 2021 22:59:45 -0500 Subject: [PATCH] Disabled timeline loading for specific CLI actions --- cli/cmdCmd.go | 78 ++--- mast/cmd.go | 816 +++++++++++++++++++++++++------------------------- tui/tui.go | 552 +++++++++++++++++----------------- 3 files changed, 724 insertions(+), 722 deletions(-) diff --git a/cli/cmdCmd.go b/cli/cmdCmd.go index ccec1b6..578d1d6 100644 --- a/cli/cmdCmd.go +++ b/cli/cmdCmd.go @@ -1,50 +1,52 @@ package cli import ( - "fmt" - "strings" - "os" + "fmt" + "os" + "strings" - "github.com/spf13/cobra" - "github.com/rivo/tview" + "github.com/rivo/tview" + "github.com/spf13/cobra" - "github.com/mrusme/gomphotherium/mast" - "github.com/mrusme/gomphotherium/tui" + "github.com/mrusme/gomphotherium/mast" + "github.com/mrusme/gomphotherium/tui" ) var cmdCmd = &cobra.Command{ - Use: "cmd", - Short: "Run command", - Long: "Run command directly from the command line.", - Run: func(cmd *cobra.Command, args []string) { - timeline := mast.NewTimeline(MastodonClient) - cmdReturn := mast.CmdProcessor( - &timeline, - strings.Join(args, " "), - mast.TriggerCLI, - ) - - switch cmdReturn { - case mast.CodeOk: - timeline.Load() - output, err := tui.RenderTimeline(&timeline, 72, flagShowImages) - if err != nil { - panic(err) - } - - fmt.Printf(tview.TranslateANSI(output)) - case mast.CodeTriggerNotSupported: - fmt.Printf("Command not supported from CLI!\n") - os.Exit(-1) - default: - fmt.Printf("%v\n", cmdReturn) - os.Exit(-1) - } - - return - }, + Use: "cmd", + Short: "Run command", + Long: "Run command directly from the command line.", + Run: func(cmd *cobra.Command, args []string) { + timeline := mast.NewTimeline(MastodonClient) + cmdReturn, loadTimeline := mast.CmdProcessor( + &timeline, + strings.Join(args, " "), + mast.TriggerCLI, + ) + + switch cmdReturn { + case mast.CodeOk: + if loadTimeline == true { + timeline.Load() + output, err := tui.RenderTimeline(&timeline, 72, flagShowImages) + if err != nil { + panic(err) + } + + fmt.Printf(tview.TranslateANSI(output)) + } + case mast.CodeTriggerNotSupported: + fmt.Printf("Command not supported from CLI!\n") + os.Exit(-1) + default: + fmt.Printf("%v\n", cmdReturn) + os.Exit(-1) + } + + return + }, } func init() { - rootCmd.AddCommand(cmdCmd) + rootCmd.AddCommand(cmdCmd) } diff --git a/mast/cmd.go b/mast/cmd.go index 540c810..9b32ac5 100644 --- a/mast/cmd.go +++ b/mast/cmd.go @@ -1,500 +1,500 @@ package mast import ( - "strings" - "strconv" - "regexp" - "errors" - "os" - "os/exec" - "runtime" - - "github.com/atotto/clipboard" - "github.com/mattn/go-mastodon" + "errors" + "os" + "os/exec" + "regexp" + "runtime" + "strconv" + "strings" + + "github.com/atotto/clipboard" + "github.com/mattn/go-mastodon" ) type CmdReturnCode int + const ( - CodeOk CmdReturnCode = 0 - CodeNotOk = 1 - CodeCommandNotFound = 2 - CodeUserNotFound = 3 - CodeTriggerNotSupported = 4 - - CodeQuit = -1 - CodeHelp = -2 + CodeOk CmdReturnCode = 0 + CodeNotOk = 1 + CodeCommandNotFound = 2 + CodeUserNotFound = 3 + CodeTriggerNotSupported = 4 + + CodeQuit = -1 + CodeHelp = -2 ) type CmdTrigger int + const ( - TriggerCLI = 0 - TriggerTUI = 1 + TriggerCLI = 0 + TriggerTUI = 1 ) -var CmdContentRegex = - regexp.MustCompile(`(?m)(( {0,1}~#| {0,1}~:)\[([^\[\]]*)\]| {0,1}~!!)`) -var CmdHandleAutoCompletionRegex = - regexp.MustCompile(`(?m)(^@| @|^whois @{0,1})([^ ]+)$`) +var CmdContentRegex = regexp.MustCompile(`(?m)(( {0,1}~#| {0,1}~:)\[([^\[\]]*)\]| {0,1}~!!)`) +var CmdHandleAutoCompletionRegex = regexp.MustCompile(`(?m)(^@| @|^whois @{0,1})([^ ]+)$`) -func CmdAvailable() ([]string) { - return []string{ - "home", - "local", - "public", - "notifications", - "hashtag", +func CmdAvailable() []string { + return []string{ + "home", + "local", + "public", + "notifications", + "hashtag", - "whois", + "whois", - "t", - "toot", + "t", + "toot", - "tp", - "tootprivate", + "tp", + "tootprivate", - "tu", - "tootunlisted", + "tu", + "tootunlisted", - "td", - "tootdirect", + "td", + "tootdirect", - "re", - "reply", + "re", + "reply", - "rep", - "replyprivate", + "rep", + "replyprivate", - "reu", - "replyunlisted", + "reu", + "replyunlisted", - "red", - "replydirect", + "red", + "replydirect", - "rt", - "retoot", - "boost", + "rt", + "retoot", + "boost", - "ut", - "unretoot", - "unboost", + "ut", + "unretoot", + "unboost", - "fav", - "unfav", + "fav", + "unfav", - "open", - "share", + "open", + "share", - // "search", + // "search", - "help", - "?", + "help", + "?", - "quit", - "exit", - "bye", - } + "quit", + "exit", + "bye", + } } -func CmdAutocompleter(input string, knownUsers map[string]string) ([]string) { - var entries []string +func CmdAutocompleter(input string, knownUsers map[string]string) []string { + var entries []string - if input == "" { - return entries - } + if input == "" { + return entries + } - handles := CmdHandleAutoCompletionRegex.FindAllStringSubmatch(input, -1) - if len(handles) > 0 { - handle := handles[0][2] + handles := CmdHandleAutoCompletionRegex.FindAllStringSubmatch(input, -1) + if len(handles) > 0 { + handle := handles[0][2] - for _, knownUser := range knownUsers { - if strings.HasPrefix(knownUser, handle) == true { - entries = append(entries, input + knownUser[len(handle):]) - } - } + for _, knownUser := range knownUsers { + if strings.HasPrefix(knownUser, handle) == true { + entries = append(entries, input+knownUser[len(handle):]) + } + } - return entries - } + return entries + } - for _, cmd := range CmdAvailable() { - if strings.HasPrefix(cmd, input) == true { - entries = append(entries, cmd) - } - } + for _, cmd := range CmdAvailable() { + if strings.HasPrefix(cmd, input) == true { + entries = append(entries, cmd) + } + } - return entries + return entries } -func CmdProcessor(timeline *Timeline, input string, trigger CmdTrigger) (CmdReturnCode) { - split := strings.SplitN(input, " ", 2) - cmd := split[0] - - args := "" - if len(split) == 2 { - args = split[1] - } - - switch cmd { - case "home": - timeline.Switch(TimelineHome, nil) - return CodeOk - case "local": - timeline.Switch(TimelineLocal, nil) - return CodeOk - case "public": - timeline.Switch(TimelinePublic, nil) - return CodeOk - case "notifications": - timeline.Switch(TimelineNotifications, nil) - return CodeOk - case "hashtag": - hashtag, isLocal, err := CmdHelperGetHashtagParams(args) - if err != nil { - return CodeNotOk - } - - timelineOptions := TimelineOptions{ - Hashtag: hashtag, - IsLocal: isLocal, - } - - timeline.Switch(TimelineHashtag, &timelineOptions) - return CodeOk - case "whois": - if trigger != TriggerTUI { - return CodeTriggerNotSupported - } - - var account *mastodon.Account - var err error - - accountID, accountKnown := timeline.KnownUsers[args] - if accountKnown == true { - account, err = timeline.User(accountID) - } - - if accountKnown == false || err != nil { - accounts, err := timeline.SearchUser(args, 1) - if err != nil || len(accounts) < 1 { - // TODO: pass info back to caller - return CodeUserNotFound - } - - account = accounts[0] - } - - timelineOptions := TimelineOptions{ - User: *account, - } - - timeline.Switch(TimelineUser, &timelineOptions) - return CodeOk - case "t", "toot": - return CmdToot(timeline, args, -1, VisibilityPublic) - case "tp", "tootprivate": - return CmdToot(timeline, args, -1, VisibilityPrivate) - case "tu", "tootunlisted": - return CmdToot(timeline, args, -1, VisibilityUnlisted) - case "td", "tootdirect": - return CmdToot(timeline, args, -1, VisibilityUnlisted) - case "re", "reply": - if trigger != TriggerTUI { - return CodeTriggerNotSupported - } - - tootId, args, err := CmdHelperGetReplyParams(args) - if err != nil { - return CodeNotOk - } - - return CmdToot(timeline, args, tootId, VisibilityPublic) - case "rep", "replyprivate": - if trigger != TriggerTUI { - return CodeTriggerNotSupported - } - - tootId, args, err := CmdHelperGetReplyParams(args) - if err != nil { - return CodeNotOk - } - - return CmdToot(timeline, args, tootId, VisibilityPrivate) - case "reu", "replyunlisted": - if trigger != TriggerTUI { - return CodeTriggerNotSupported - } - - tootId, args, err := CmdHelperGetReplyParams(args) - if err != nil { - return CodeNotOk - } - - return CmdToot(timeline, args, tootId, VisibilityUnlisted) - case "red", "replydirect": - if trigger != TriggerTUI { - return CodeTriggerNotSupported - } - - tootId, args, err := CmdHelperGetReplyParams(args) - if err != nil { - return CodeNotOk - } - - return CmdToot(timeline, args, tootId, VisibilityDirect) - case "rt", "retoot", "boost": - if trigger != TriggerTUI { - return CodeTriggerNotSupported - } - - tootId, err := CmdHelperGetBoostParams(args) - if err != nil { - return CodeNotOk - } - - return CmdBoost(timeline, tootId) - case "ut", "unretoot", "unboost": - if trigger != TriggerTUI { - return CodeTriggerNotSupported - } - - tootId, err := CmdHelperGetBoostParams(args) - if err != nil { - return CodeNotOk - } - - return CmdUnboost(timeline, tootId) - case "fav": - if trigger != TriggerTUI { - return CodeTriggerNotSupported - } - - tootId, err := CmdHelperGetFavParams(args) - if err != nil { - return CodeNotOk - } - - return CmdFav(timeline, tootId) - case "unfav": - if trigger != TriggerTUI { - return CodeTriggerNotSupported - } - - tootId, err := CmdHelperGetFavParams(args) - if err != nil { - return CodeNotOk - } - - return CmdUnfav(timeline, tootId) - case "open": - if trigger != TriggerTUI { - return CodeTriggerNotSupported - } - - tootId, err := CmdHelperGetOpenParams(args) - if err != nil { - return CodeNotOk - } - - return CmdOpen(timeline, tootId) - case "share": - if trigger != TriggerTUI { - return CodeTriggerNotSupported - } - - tootId, err := CmdHelperGetShareParams(args) - if err != nil { - return CodeNotOk - } - - return CmdShare(timeline, tootId) - case "?", "help": - if trigger != TriggerTUI { - return CodeTriggerNotSupported - } - - return CodeHelp - case "quit", "exit", "bye", "q": - if trigger != TriggerTUI { - return CodeTriggerNotSupported - } - - return CodeQuit - } - - return CodeCommandNotFound +func CmdProcessor(timeline *Timeline, input string, trigger CmdTrigger) (CmdReturnCode, bool) { + split := strings.SplitN(input, " ", 2) + cmd := split[0] + + args := "" + if len(split) == 2 { + args = split[1] + } + + switch cmd { + case "home": + timeline.Switch(TimelineHome, nil) + return CodeOk, true + case "local": + timeline.Switch(TimelineLocal, nil) + return CodeOk, true + case "public": + timeline.Switch(TimelinePublic, nil) + return CodeOk, true + case "notifications": + timeline.Switch(TimelineNotifications, nil) + return CodeOk, true + case "hashtag": + hashtag, isLocal, err := CmdHelperGetHashtagParams(args) + if err != nil { + return CodeNotOk, false + } + + timelineOptions := TimelineOptions{ + Hashtag: hashtag, + IsLocal: isLocal, + } + + timeline.Switch(TimelineHashtag, &timelineOptions) + return CodeOk, true + case "whois": + if trigger != TriggerTUI { + return CodeTriggerNotSupported, false + } + + var account *mastodon.Account + var err error + + accountID, accountKnown := timeline.KnownUsers[args] + if accountKnown == true { + account, err = timeline.User(accountID) + } + + if accountKnown == false || err != nil { + accounts, err := timeline.SearchUser(args, 1) + if err != nil || len(accounts) < 1 { + // TODO: pass info back to caller + return CodeUserNotFound, false + } + + account = accounts[0] + } + + timelineOptions := TimelineOptions{ + User: *account, + } + + timeline.Switch(TimelineUser, &timelineOptions) + return CodeOk, true + case "t", "toot": + return CmdToot(timeline, args, -1, VisibilityPublic), false + case "tp", "tootprivate": + return CmdToot(timeline, args, -1, VisibilityPrivate), false + case "tu", "tootunlisted": + return CmdToot(timeline, args, -1, VisibilityUnlisted), false + case "td", "tootdirect": + return CmdToot(timeline, args, -1, VisibilityUnlisted), false + case "re", "reply": + if trigger != TriggerTUI { + return CodeTriggerNotSupported, false + } + + tootId, args, err := CmdHelperGetReplyParams(args) + if err != nil { + return CodeNotOk, false + } + + return CmdToot(timeline, args, tootId, VisibilityPublic), false + case "rep", "replyprivate": + if trigger != TriggerTUI { + return CodeTriggerNotSupported, false + } + + tootId, args, err := CmdHelperGetReplyParams(args) + if err != nil { + return CodeNotOk, false + } + + return CmdToot(timeline, args, tootId, VisibilityPrivate), false + case "reu", "replyunlisted": + if trigger != TriggerTUI { + return CodeTriggerNotSupported, false + } + + tootId, args, err := CmdHelperGetReplyParams(args) + if err != nil { + return CodeNotOk, false + } + + return CmdToot(timeline, args, tootId, VisibilityUnlisted), false + case "red", "replydirect": + if trigger != TriggerTUI { + return CodeTriggerNotSupported, false + } + + tootId, args, err := CmdHelperGetReplyParams(args) + if err != nil { + return CodeNotOk, false + } + + return CmdToot(timeline, args, tootId, VisibilityDirect), false + case "rt", "retoot", "boost": + if trigger != TriggerTUI { + return CodeTriggerNotSupported, false + } + + tootId, err := CmdHelperGetBoostParams(args) + if err != nil { + return CodeNotOk, false + } + + return CmdBoost(timeline, tootId), false + case "ut", "unretoot", "unboost": + if trigger != TriggerTUI { + return CodeTriggerNotSupported, false + } + + tootId, err := CmdHelperGetBoostParams(args) + if err != nil { + return CodeNotOk, false + } + + return CmdUnboost(timeline, tootId), false + case "fav": + if trigger != TriggerTUI { + return CodeTriggerNotSupported, false + } + + tootId, err := CmdHelperGetFavParams(args) + if err != nil { + return CodeNotOk, false + } + + return CmdFav(timeline, tootId), false + case "unfav": + if trigger != TriggerTUI { + return CodeTriggerNotSupported, false + } + + tootId, err := CmdHelperGetFavParams(args) + if err != nil { + return CodeNotOk, false + } + + return CmdUnfav(timeline, tootId), false + case "open": + if trigger != TriggerTUI { + return CodeTriggerNotSupported, false + } + + tootId, err := CmdHelperGetOpenParams(args) + if err != nil { + return CodeNotOk, false + } + + return CmdOpen(timeline, tootId), false + case "share": + if trigger != TriggerTUI { + return CodeTriggerNotSupported, false + } + + tootId, err := CmdHelperGetShareParams(args) + if err != nil { + return CodeNotOk, false + } + + return CmdShare(timeline, tootId), false + case "?", "help": + if trigger != TriggerTUI { + return CodeTriggerNotSupported, false + } + + return CodeHelp, false + case "quit", "exit", "bye", "q": + if trigger != TriggerTUI { + return CodeTriggerNotSupported, false + } + + return CodeQuit, false + } + + return CodeCommandNotFound, false } func CmdHelperGetHashtagParams(args string) (string, bool, error) { - splitArgs := strings.SplitN(args, " ", 2) + splitArgs := strings.SplitN(args, " ", 2) - if len(splitArgs) < 2 { - return args, false, nil - } + if len(splitArgs) < 2 { + return args, false, nil + } - hashtag := splitArgs[0] - isLocal := false + hashtag := splitArgs[0] + isLocal := false - if strings.ToLower(splitArgs[1]) == "local" { - isLocal = true - } + if strings.ToLower(splitArgs[1]) == "local" { + isLocal = true + } - return hashtag, isLocal, nil + return hashtag, isLocal, nil } func CmdHelperGetTootIDFromString(s string) (int, error) { - tootId, err := strconv.Atoi(s) - if err != nil { - return -1, errors.New("Toot ID invalid!") - } + tootId, err := strconv.Atoi(s) + if err != nil { + return -1, errors.New("Toot ID invalid!") + } - return tootId, nil + return tootId, nil } func CmdHelperGetReplyParams(args string) (int, string, error) { - splitArgs := strings.SplitN(args, " ", 2) + splitArgs := strings.SplitN(args, " ", 2) - if len(splitArgs) < 2 { - return -1, args, errors.New("Toot ID missing!") - } + if len(splitArgs) < 2 { + return -1, args, errors.New("Toot ID missing!") + } - tootId, err := CmdHelperGetTootIDFromString(splitArgs[0]) - if err != nil { - return tootId, args, err - } + tootId, err := CmdHelperGetTootIDFromString(splitArgs[0]) + if err != nil { + return tootId, args, err + } - newArgs := splitArgs[1] + newArgs := splitArgs[1] - return tootId, newArgs, nil + return tootId, newArgs, nil } func CmdHelperGetBoostParams(args string) (int, error) { - return CmdHelperGetTootIDFromString(args) + return CmdHelperGetTootIDFromString(args) } func CmdHelperGetFavParams(args string) (int, error) { - return CmdHelperGetTootIDFromString(args) + return CmdHelperGetTootIDFromString(args) } func CmdHelperGetOpenParams(args string) (int, error) { - return CmdHelperGetTootIDFromString(args) + return CmdHelperGetTootIDFromString(args) } func CmdHelperGetShareParams(args string) (int, error) { - return CmdHelperGetTootIDFromString(args) + return CmdHelperGetTootIDFromString(args) } func CmdToot( - timeline *Timeline, - content string, - inReplyTo int, - visibility string) (CmdReturnCode) { - var status string = "" - var spoiler string = "" - var sensitive bool = false - var filesToUpload []string - - tokens := CmdContentRegex.FindAllStringSubmatch(content, -1) - for _, token := range tokens { - if len(token[0]) >= 3 && token[0][(len(token[0])-3):] == "~!!" { - sensitive = true - continue - } else if len(token) == 4 && len(token[2]) >= 2 { - switch token[2][(len(tokens[0][2])-2):] { - case "~#": - spoiler = token[3] - continue - case "~:": - filesToUpload = append(filesToUpload, token[3]) - continue - } - } - } - - status = CmdContentRegex.ReplaceAllString(content, "") - - _, err := timeline.Toot( - &status, - inReplyTo, - filesToUpload, - &visibility, - sensitive, - &spoiler, - ) - if err != nil { - return CodeNotOk - } - - return CodeOk + timeline *Timeline, + content string, + inReplyTo int, + visibility string) CmdReturnCode { + var status string = "" + var spoiler string = "" + var sensitive bool = false + var filesToUpload []string + + tokens := CmdContentRegex.FindAllStringSubmatch(content, -1) + for _, token := range tokens { + if len(token[0]) >= 3 && token[0][(len(token[0])-3):] == "~!!" { + sensitive = true + continue + } else if len(token) == 4 && len(token[2]) >= 2 { + switch token[2][(len(tokens[0][2]) - 2):] { + case "~#": + spoiler = token[3] + continue + case "~:": + filesToUpload = append(filesToUpload, token[3]) + continue + } + } + } + + status = CmdContentRegex.ReplaceAllString(content, "") + + _, err := timeline.Toot( + &status, + inReplyTo, + filesToUpload, + &visibility, + sensitive, + &spoiler, + ) + if err != nil { + return CodeNotOk + } + + return CodeOk } -func CmdBoost(timeline *Timeline, tootID int) (CmdReturnCode) { - _, err := timeline.Boost(tootID, true) - if err != nil { - return CodeNotOk - } +func CmdBoost(timeline *Timeline, tootID int) CmdReturnCode { + _, err := timeline.Boost(tootID, true) + if err != nil { + return CodeNotOk + } - return CodeOk + return CodeOk } -func CmdUnboost(timeline *Timeline, tootID int) (CmdReturnCode) { - _, err := timeline.Boost(tootID, false) - if err != nil { - return CodeNotOk - } +func CmdUnboost(timeline *Timeline, tootID int) CmdReturnCode { + _, err := timeline.Boost(tootID, false) + if err != nil { + return CodeNotOk + } - return CodeOk + return CodeOk } -func CmdFav(timeline *Timeline, tootID int) (CmdReturnCode) { - _, err := timeline.Fav(tootID, true) - if err != nil { - return CodeNotOk - } +func CmdFav(timeline *Timeline, tootID int) CmdReturnCode { + _, err := timeline.Fav(tootID, true) + if err != nil { + return CodeNotOk + } - return CodeOk + return CodeOk } -func CmdUnfav(timeline *Timeline, tootID int) (CmdReturnCode) { - _, err := timeline.Fav(tootID, false) - if err != nil { - return CodeNotOk - } +func CmdUnfav(timeline *Timeline, tootID int) CmdReturnCode { + _, err := timeline.Fav(tootID, false) + if err != nil { + return CodeNotOk + } - return CodeOk + return CodeOk } -func CmdOpen(timeline *Timeline, tootID int) (CmdReturnCode) { - var cmd *exec.Cmd - - url := timeline.Toots[tootID].Status.URL - - switch runtime.GOOS { - case "darwin": - cmd = exec.Command("open", url) - case "linux": - cmd = exec.Command("xdg-open", url) - case "windows": - cmd = exec.Command("rundll32", "url.dll,FileProtocolHandler", url) - default: - // errors.New("Platform not supported!") - return CodeNotOk - } - - cmd.Env = append(os.Environ()) - err := cmd.Start() - if err != nil { - return CodeNotOk - } - - return CodeOk +func CmdOpen(timeline *Timeline, tootID int) CmdReturnCode { + var cmd *exec.Cmd + + url := timeline.Toots[tootID].Status.URL + + switch runtime.GOOS { + case "darwin": + cmd = exec.Command("open", url) + case "linux": + cmd = exec.Command("xdg-open", url) + case "windows": + cmd = exec.Command("rundll32", "url.dll,FileProtocolHandler", url) + default: + // errors.New("Platform not supported!") + return CodeNotOk + } + + cmd.Env = append(os.Environ()) + err := cmd.Start() + if err != nil { + return CodeNotOk + } + + return CodeOk } -func CmdShare(timeline *Timeline, tootID int) (CmdReturnCode) { - url := timeline.Toots[tootID].Status.URL +func CmdShare(timeline *Timeline, tootID int) CmdReturnCode { + url := timeline.Toots[tootID].Status.URL - err := clipboard.WriteAll(url) - if err != nil { - return CodeNotOk - } + err := clipboard.WriteAll(url) + if err != nil { + return CodeNotOk + } - return CodeOk + return CodeOk } diff --git a/tui/tui.go b/tui/tui.go index 416de63..5579fd8 100644 --- a/tui/tui.go +++ b/tui/tui.go @@ -1,318 +1,318 @@ package tui import ( - "fmt" - "time" + "fmt" + "time" - "github.com/gdamore/tcell/v2" - "github.com/rivo/tview" + "github.com/gdamore/tcell/v2" + "github.com/rivo/tview" - "github.com/mattn/go-mastodon" - "github.com/mrusme/gomphotherium/mast" + "github.com/mattn/go-mastodon" + "github.com/mrusme/gomphotherium/mast" - "github.com/tj/go-termd" + "github.com/tj/go-termd" ) type ModeType int + const ( - NormalMode ModeType = 1 - CommandMode = 2 + NormalMode ModeType = 1 + CommandMode = 2 ) type TUIOptions struct { - ShowImages bool - AutoCompletion bool + ShowImages bool + AutoCompletion bool } type TUICore struct { - Client *mastodon.Client - App *tview.Application - CmdLine *tview.InputField - Profile *tview.TextView - Stream *tview.TextView - Grid *tview.Grid + Client *mastodon.Client + App *tview.Application + CmdLine *tview.InputField + Profile *tview.TextView + Stream *tview.TextView + Grid *tview.Grid - Prompt string - Mode ModeType + Prompt string + Mode ModeType - Timeline mast.Timeline - RenderedTimelineType mast.TimelineType + Timeline mast.Timeline + RenderedTimelineType mast.TimelineType - Options TUIOptions + Options TUIOptions - Help string + Help string } - func TUI(tuiCore TUICore) { - tview.Styles = tview.Theme{ - PrimitiveBackgroundColor: tcell.ColorDefault, - ContrastBackgroundColor: tcell.ColorTeal, - MoreContrastBackgroundColor: tcell.ColorTeal, - BorderColor: tcell.ColorWhite, - TitleColor: tcell.ColorWhite, - GraphicsColor: tcell.ColorWhite, - PrimaryTextColor: tcell.ColorDefault, - SecondaryTextColor: tcell.ColorBlue, - TertiaryTextColor: tcell.ColorGreen, - InverseTextColor: tcell.ColorBlack, - ContrastSecondaryTextColor: tcell.ColorDarkCyan, - } - - tuiCore.App = tview.NewApplication() - - tuiCore.Timeline = mast.NewTimeline(tuiCore.Client) - tuiCore.RenderedTimelineType = mast.TimelineHome - tuiCore.Timeline.Switch(mast.TimelineHome, nil) - - tuiCore.CmdLine = tview.NewInputField(). - SetLabelColor(tcell.ColorDefault). - SetFieldBackgroundColor(tcell.ColorDefault). - SetDoneFunc(func(key tcell.Key) { - if key == tcell.KeyEnter { - cmd := tuiCore.CmdLine.GetText() - tuiCore.CmdLine.SetText("") - retCode := mast.CmdProcessor(&tuiCore.Timeline, cmd, mast.TriggerTUI) - - switch retCode { - case mast.CodeOk: - if tuiCore.Timeline.GetCurrentType() == mast.TimelineUser && - tuiCore.RenderedTimelineType != mast.TimelineUser { - tuiCore.Grid. - RemoveItem(tuiCore.Stream). - AddItem(tuiCore.Profile, 0, 0, 1, 1, 0, 0, false). - AddItem(tuiCore.Stream, 1, 0, 1, 1, 0, 0, false) - } else if tuiCore.RenderedTimelineType == mast.TimelineUser && - tuiCore.Timeline.GetCurrentType() != mast.TimelineUser { - tuiCore.Grid. - RemoveItem(tuiCore.Profile). - RemoveItem(tuiCore.Stream). - AddItem(tuiCore.Stream, 0, 0, 2, 1, 0, 0, false) - } - tuiCore.UpdateTimeline(true) - case mast.CodeHelp: - tuiCore.ShowHelp() - case mast.CodeQuit: - tuiCore.App.Stop(); - } - } - }) - - if tuiCore.Options.AutoCompletion == true { - tuiCore.CmdLine.SetAutocompleteFunc(func(input string) ([]string) { - return mast.CmdAutocompleter(input, tuiCore.Timeline.KnownUsers) - }) - } - - tuiCore.Profile = tview.NewTextView(). - SetDynamicColors(true). - SetRegions(true). - SetWrap(true). - SetScrollable(false) - - tuiCore.Stream = tview.NewTextView(). - SetDynamicColors(true). - SetRegions(true). - SetWrap(true) - - tuiCore.Grid = tview.NewGrid(). - SetRows(8, 0, 1). - SetColumns(0). - SetBorders(true). - AddItem(tuiCore.Stream, 0, 0, 2, 1, 0, 0, false). - AddItem(tuiCore.CmdLine, 2, 0, 1, 1, 0, 0, false) - - tuiCore.App.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { - switch event.Key() { - case tcell.KeyCtrlR: - tuiCore.UpdateTimeline(true) - return nil - case tcell.KeyRune: - eventRune := event.Rune() - switch eventRune { - case ':': - if tuiCore.EnterCommandMode() == true { - return nil - } - case 'u', 'd', 'b', 'f': - if tuiCore.Mode == NormalMode { - _, _, _, h := tuiCore.Stream.Box.GetRect() - currentLine, _ := tuiCore.Stream.GetScrollOffset() - - var scrollLength int = 0 - if eventRune == 'u' || eventRune == 'd' { - scrollLength = int((h / 2)) - } else if eventRune == 'b' || eventRune == 'f' { - scrollLength = h - } - - var scrollTo int = currentLine - - if eventRune == 'u' || eventRune == 'b' { - scrollTo = currentLine - scrollLength - } else if eventRune == 'd' || eventRune == 'f' { - scrollTo = currentLine + scrollLength - } - - if scrollTo < 0 { - scrollTo = 0 - } - - tuiCore.Stream.ScrollTo(scrollTo, 0) - } - } - case tcell.KeyEscape: - if tuiCore.ExitCommandMode(false) == true { - return nil - } - case tcell.KeyCtrlQ: - tuiCore.App.Stop() - } - - return event - }) - - go func() { - for { - time.Sleep(time.Second * 2) - tuiCore.UpdateTimeline(true) - - if tuiCore.Mode == 0 { - tuiCore.ExitCommandMode(true) - } - - tuiCore.App.Draw() - time.Sleep(time.Second * 58) - } - }() - - if err := tuiCore.App.SetRoot(tuiCore.Grid, true).Run(); err != nil { - panic(err) - } + tview.Styles = tview.Theme{ + PrimitiveBackgroundColor: tcell.ColorDefault, + ContrastBackgroundColor: tcell.ColorTeal, + MoreContrastBackgroundColor: tcell.ColorTeal, + BorderColor: tcell.ColorWhite, + TitleColor: tcell.ColorWhite, + GraphicsColor: tcell.ColorWhite, + PrimaryTextColor: tcell.ColorDefault, + SecondaryTextColor: tcell.ColorBlue, + TertiaryTextColor: tcell.ColorGreen, + InverseTextColor: tcell.ColorBlack, + ContrastSecondaryTextColor: tcell.ColorDarkCyan, + } + + tuiCore.App = tview.NewApplication() + + tuiCore.Timeline = mast.NewTimeline(tuiCore.Client) + tuiCore.RenderedTimelineType = mast.TimelineHome + tuiCore.Timeline.Switch(mast.TimelineHome, nil) + + tuiCore.CmdLine = tview.NewInputField(). + SetLabelColor(tcell.ColorDefault). + SetFieldBackgroundColor(tcell.ColorDefault). + SetDoneFunc(func(key tcell.Key) { + if key == tcell.KeyEnter { + cmd := tuiCore.CmdLine.GetText() + tuiCore.CmdLine.SetText("") + retCode, _ := mast.CmdProcessor(&tuiCore.Timeline, cmd, mast.TriggerTUI) + + switch retCode { + case mast.CodeOk: + if tuiCore.Timeline.GetCurrentType() == mast.TimelineUser && + tuiCore.RenderedTimelineType != mast.TimelineUser { + tuiCore.Grid. + RemoveItem(tuiCore.Stream). + AddItem(tuiCore.Profile, 0, 0, 1, 1, 0, 0, false). + AddItem(tuiCore.Stream, 1, 0, 1, 1, 0, 0, false) + } else if tuiCore.RenderedTimelineType == mast.TimelineUser && + tuiCore.Timeline.GetCurrentType() != mast.TimelineUser { + tuiCore.Grid. + RemoveItem(tuiCore.Profile). + RemoveItem(tuiCore.Stream). + AddItem(tuiCore.Stream, 0, 0, 2, 1, 0, 0, false) + } + tuiCore.UpdateTimeline(true) + case mast.CodeHelp: + tuiCore.ShowHelp() + case mast.CodeQuit: + tuiCore.App.Stop() + } + } + }) + + if tuiCore.Options.AutoCompletion == true { + tuiCore.CmdLine.SetAutocompleteFunc(func(input string) []string { + return mast.CmdAutocompleter(input, tuiCore.Timeline.KnownUsers) + }) + } + + tuiCore.Profile = tview.NewTextView(). + SetDynamicColors(true). + SetRegions(true). + SetWrap(true). + SetScrollable(false) + + tuiCore.Stream = tview.NewTextView(). + SetDynamicColors(true). + SetRegions(true). + SetWrap(true) + + tuiCore.Grid = tview.NewGrid(). + SetRows(8, 0, 1). + SetColumns(0). + SetBorders(true). + AddItem(tuiCore.Stream, 0, 0, 2, 1, 0, 0, false). + AddItem(tuiCore.CmdLine, 2, 0, 1, 1, 0, 0, false) + + tuiCore.App.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + switch event.Key() { + case tcell.KeyCtrlR: + tuiCore.UpdateTimeline(true) + return nil + case tcell.KeyRune: + eventRune := event.Rune() + switch eventRune { + case ':': + if tuiCore.EnterCommandMode() == true { + return nil + } + case 'u', 'd', 'b', 'f': + if tuiCore.Mode == NormalMode { + _, _, _, h := tuiCore.Stream.Box.GetRect() + currentLine, _ := tuiCore.Stream.GetScrollOffset() + + var scrollLength int = 0 + if eventRune == 'u' || eventRune == 'd' { + scrollLength = int((h / 2)) + } else if eventRune == 'b' || eventRune == 'f' { + scrollLength = h + } + + var scrollTo int = currentLine + + if eventRune == 'u' || eventRune == 'b' { + scrollTo = currentLine - scrollLength + } else if eventRune == 'd' || eventRune == 'f' { + scrollTo = currentLine + scrollLength + } + + if scrollTo < 0 { + scrollTo = 0 + } + + tuiCore.Stream.ScrollTo(scrollTo, 0) + } + } + case tcell.KeyEscape: + if tuiCore.ExitCommandMode(false) == true { + return nil + } + case tcell.KeyCtrlQ: + tuiCore.App.Stop() + } + + return event + }) + + go func() { + for { + time.Sleep(time.Second * 2) + tuiCore.UpdateTimeline(true) + + if tuiCore.Mode == 0 { + tuiCore.ExitCommandMode(true) + } + + tuiCore.App.Draw() + time.Sleep(time.Second * 58) + } + }() + + if err := tuiCore.App.SetRoot(tuiCore.Grid, true).Run(); err != nil { + panic(err) + } } func (tuiCore *TUICore) ShowHelp() { - var c termd.Compiler - - help := tview.NewTextView(). - SetDynamicColors(true). - SetRegions(true). - SetWrap(true). - SetDoneFunc(func(key tcell.Key)() { - tuiCore.App.SetRoot(tuiCore.Grid, true) - return - }) - - // c.SyntaxHighlighter = termd.SyntaxTheme{ - // "keyword": termd.Style{}, - // "comment": termd.Style{ - // Color: "#323232", - // }, - // "literal": termd.Style{ - // Color: "#555555", - // }, - // "name": termd.Style{ - // Color: "#777777", - // }, - // "name.function": termd.Style{ - // Color: "#444444", - // }, - // "literal.string": termd.Style{ - // Color: "#333333", - // }, - // } - - rendered := c.Compile(tuiCore.Help) - fmt.Fprint(help, tview.TranslateANSI(rendered)) - - tuiCore.App.SetRoot(help, true) + var c termd.Compiler + + help := tview.NewTextView(). + SetDynamicColors(true). + SetRegions(true). + SetWrap(true). + SetDoneFunc(func(key tcell.Key) { + tuiCore.App.SetRoot(tuiCore.Grid, true) + return + }) + + // c.SyntaxHighlighter = termd.SyntaxTheme{ + // "keyword": termd.Style{}, + // "comment": termd.Style{ + // Color: "#323232", + // }, + // "literal": termd.Style{ + // Color: "#555555", + // }, + // "name": termd.Style{ + // Color: "#777777", + // }, + // "name.function": termd.Style{ + // Color: "#444444", + // }, + // "literal.string": termd.Style{ + // Color: "#333333", + // }, + // } + + rendered := c.Compile(tuiCore.Help) + fmt.Fprint(help, tview.TranslateANSI(rendered)) + + tuiCore.App.SetRoot(help, true) } func (tuiCore *TUICore) UpdateTimeline(scrollToEnd bool) bool { - _, _, w, _ := tuiCore.Stream.Box.GetInnerRect() - - err := tuiCore.Timeline.Load() - if err != nil { - // TODO: Display errors somewhere - return false - } - - currentTimelineType := tuiCore.Timeline.GetCurrentType() - if tuiCore.RenderedTimelineType != currentTimelineType || - currentTimelineType == mast.TimelineHashtag || - currentTimelineType == mast.TimelineUser { - tuiCore.Stream.Clear() - tuiCore.RenderedTimelineType = currentTimelineType - tuiCore.Timeline.LastRenderedIndex = -1 - } - - output, err := RenderTimeline( - &tuiCore.Timeline, - w, - tuiCore.Options.ShowImages, - ) - - if err != nil { - // TODO: Display errors somewhere - return false - } - - fmt.Fprint(tuiCore.Stream, tview.TranslateANSI(output)) - - if scrollToEnd == true { - tuiCore.Stream.ScrollToEnd() - } - - if currentTimelineType == mast.TimelineUser { - tuiCore.Profile.Clear() - - options := tuiCore.Timeline.GetCurrentOptions() - - profileOutput, err := RenderProfile( - &options.User, - w, - tuiCore.Options.ShowImages, - ) - - if err != nil { - // TODO: Display errors somewhere - return false - } - - fmt.Fprint(tuiCore.Profile, tview.TranslateANSI(profileOutput)) - } - - return true + _, _, w, _ := tuiCore.Stream.Box.GetInnerRect() + + err := tuiCore.Timeline.Load() + if err != nil { + // TODO: Display errors somewhere + return false + } + + currentTimelineType := tuiCore.Timeline.GetCurrentType() + if tuiCore.RenderedTimelineType != currentTimelineType || + currentTimelineType == mast.TimelineHashtag || + currentTimelineType == mast.TimelineUser { + tuiCore.Stream.Clear() + tuiCore.RenderedTimelineType = currentTimelineType + tuiCore.Timeline.LastRenderedIndex = -1 + } + + output, err := RenderTimeline( + &tuiCore.Timeline, + w, + tuiCore.Options.ShowImages, + ) + + if err != nil { + // TODO: Display errors somewhere + return false + } + + fmt.Fprint(tuiCore.Stream, tview.TranslateANSI(output)) + + if scrollToEnd == true { + tuiCore.Stream.ScrollToEnd() + } + + if currentTimelineType == mast.TimelineUser { + tuiCore.Profile.Clear() + + options := tuiCore.Timeline.GetCurrentOptions() + + profileOutput, err := RenderProfile( + &options.User, + w, + tuiCore.Options.ShowImages, + ) + + if err != nil { + // TODO: Display errors somewhere + return false + } + + fmt.Fprint(tuiCore.Profile, tview.TranslateANSI(profileOutput)) + } + + return true } func (tuiCore *TUICore) EnterCommandMode() bool { - if tuiCore.CmdLine.Box.HasFocus() == false { - tuiCore.App.SetFocus(tuiCore.CmdLine) - tuiCore.CmdLine.SetLabelColor(tcell.ColorTeal) + if tuiCore.CmdLine.Box.HasFocus() == false { + tuiCore.App.SetFocus(tuiCore.CmdLine) + tuiCore.CmdLine.SetLabelColor(tcell.ColorTeal) - tuiCore.Prompt = tuiCore.Timeline.Account.Username + ": " - tuiCore.CmdLine. - SetLabel(tuiCore.Prompt) + tuiCore.Prompt = tuiCore.Timeline.Account.Username + ": " + tuiCore.CmdLine. + SetLabel(tuiCore.Prompt) - tuiCore.Mode = CommandMode - return true - } + tuiCore.Mode = CommandMode + return true + } - return false + return false } func (tuiCore *TUICore) ExitCommandMode(force bool) bool { - if tuiCore.CmdLine.Box.HasFocus() == true || force == true { - tuiCore.App.SetFocus(tuiCore.Stream) - tuiCore.CmdLine.SetLabelColor(tcell.ColorDefault) + if tuiCore.CmdLine.Box.HasFocus() == true || force == true { + tuiCore.App.SetFocus(tuiCore.Stream) + tuiCore.CmdLine.SetLabelColor(tcell.ColorDefault) - tuiCore.Prompt = tuiCore.Timeline.Account.Username + " " - tuiCore.CmdLine. - SetLabel(tuiCore.Prompt) + tuiCore.Prompt = tuiCore.Timeline.Account.Username + " " + tuiCore.CmdLine. + SetLabel(tuiCore.Prompt) - tuiCore.Mode = NormalMode - return true - } + tuiCore.Mode = NormalMode + return true + } - return false + return false }