diff --git a/config.example.ini b/config.example.ini index f737cdb..8f996d8 100644 --- a/config.example.ini +++ b/config.example.ini @@ -316,3 +316,229 @@ list-selected-background=xrdb:color5 # The text color of selected list items. # default=xrdb:background list-selected-text=xrdb:background + +[input] +# You can edit the keys for tut below. +# +# The syntax is a bit weird, but it works. And I'll try to explain it as well as +# I can. +# +# Example: +# status-favorite="[F]avorite","Un[F]avorite",'f','F' +# status-delete="[D]elete",'d','D' +# +# status-favorite and status-delete differs because favorite can be in two +# states, so you will have to add two key hints. +# Most keys will only have on key hint. Look at the default value for reference. +# +# Key hints must be in some of the following formats. Remember the quotation +# marks. +# "" = empty +# "[D]elete" = Delete with a highlighted D +# "Un[F]ollow" = UnFollow with a highlighted F +# "[Enter]" = Enter where everything is highlighted +# "Yan[K]" = YanK with a highlighted K +# +# After the hint (or hints) you must set the keys. You can do this in two ways, +# with single quotation marks or double ones. +# +# The single ones are for single chars like 'a', 'b', 'c' and double marks are +# for special keys like "Enter". Remember that they are case sensetive. +# +# To find the names of special keys you have to go to the following site and +# look for "var KeyNames = map[Key]string{" +# +# https://github.com/gdamore/tcell/blob/master/key.go +# + +# Keys for moving down +# default="",'j','J',"Down" +global-down="",'j','J',"Down" + +# Keys for moving up +# default="",'k','K',"Up" +global-up="",'k','K',"Up" + +# To select items +# default="","Enter" +global-enter="","Enter" + +# To go back +# default="[Esc]","Esc" +global-back="[Esc]","Esc" + +# To go back and exit Tut +# default="[Q]uit",'q','Q' +global-exit="[Q]uit",'q','Q' + +# Move to the top +# default="",'g',"Home" +main-home="",'g',"Home" + +# Move to the bottom +# default="",'G',"End" +main-end="",'G',"End" + +# Go to previous feed +# default="",'h','H',"Left" +main-prev-feed="",'h','H',"Left" + +# Go to next feed +# default="",'l','L',"Right" +main-next-feed="",'l','L',"Right" + +# Focus on the notification list +# default="[N]otifications",'n','N' +main-notification-focus="[N]otifications",'n','N' + +# Compose a new toot +# default="",'c','C' +main-compose="",'c','C' + +# Open avatar +# default="[A]vatar",'a','A' +status-avatar="[A]vatar",'a','A' + +# Boost a toot +# default="[B]oost","Un[B]oost",'b','B' +status-boost="[B]oost","Un[B]oost",'b','B' + +# Delete a toot +# default="[D]elete",'d','D' +status-delete="[D]elete",'d','D' + +# Favorite a toot +# default="[F]avorite","Un[F]avorite",'f','F' +status-favorite="[F]avorite","Un[F]avorite",'f','F' + +# Open toots media files +# default="[M]edia",'m','M' +status-media="[M]edia",'m','M' + +# Open links +# default="[O]pen",'o','O' +status-links="[O]pen",'o','O' + +# Open poll +# default="[P]oll",'p','P' +status-poll="[P]oll",'p','P' + +# Reply to toot +# default="[R]eply",'r','R' +status-reply="[R]eply",'r','R' + +# Save/bookmark a toot +# default="[S]ave","Un[S]ave",'s','S' +status-bookmark="[S]ave","Un[S]ave",'s','S' + +# View thread +# default="[T]hread",'t','T' +status-thread="[T]hread",'t','T' + +# Open user profile +# default="[U]ser",'u','U' +status-user="[U]ser",'u','U' + +# Open the view mode +# default="[V]iew",'v','V' +status-view-focus="[V]iew",'v','V' + +# Yank the url of the toot +# default="[Y]ank",'y','Y' +status-yank="[Y]ank",'y','Y' + +# Remove the spoiler +# default="Press [Z] to toggle spoiler",'z','Z' +status-toggle-spoiler="Press [Z] to toggle spoiler",'z','Z' + +# View avatar +# default="[A]vatar",'a','A' +user-avatar="[A]vatar",'a','A' + +# Block the user +# default="[B]lock","Un[B]lock",'b','B' +user-block="[B]lock","Un[B]lock",'b','B' + +# Follow user +# default="[F]ollow","Un[F]ollow",'f','F' +user-follow="[F]ollow","Un[F]ollow",'f','F' + +# Mute user +# default="[M]ute","Un[M]ute",'m','M' +user-mute="[M]ute","Un[M]ute",'m','M' + +# Open links +# default="[O]pen",'o','O' +user-links="[O]pen",'o','O' + +# View user profile +# default="[U]ser",'u','U' +user-user="[U]ser",'u','U' + +# Open view mode +# default="[V]iew",'v','V' +user-view-focus="[V]iew",'v','V' + +# Yank the user URL +# default="[Y]ank",'y','Y' +user-yank="[Y]ank",'y','Y' + +# Open list +# default="[O]pen",'o','O' +list-open-feed="[O]pen",'o','O' + +# Open URL +# default="[O]pen",'o','O' +link-open="[O]pen",'o','O' + +# Yank the URL +# default="[Y]ank",'y','Y' +link-yank="[Y]ank",'y','Y' + +# Edit spoiler text on new toot +# default="[C]W text",'c','C' +compose-edit-spoiler="[C]W text",'c','C' + +# Edit the text on new toot +# default="[E]dit text",'e','E' +compose-edit-text="[E]dit text",'e','E' + +# Include a quote when replying +# default="[I]nclude quote",'i','I' +compose-include-quote="[I]nclude quote",'i','I' + +# Focus on adding media to toot +# default="[M]edia",'m','M' +compose-media-focus="[M]edia",'m','M' + +# Post the new toot +# default="[P]ost",'p','P' +compose-post="[P]ost",'p','P' + +# Toggle content warning on toot +# default="[T]oggle CW",'t','T' +compose-toggle-content-warning="[T]oggle CW",'t','T' + +# Edit the visibility on new toot +# default="[V]isibility",'v','V' +compose-visibility="[V]isibility",'v','V' + +# Delete media file +# default="[D]elete",'d','D' +media-delete="[D]elete",'d','D' + +# Edit the description on media file +# default="[E]dit desc",'e','E' +media-edit-desc="[E]dit desc",'e','E' + +# Add a new media file +# default="[A]dd",'a','A' +media-add="[A]dd",'a','A' + +# Vote on poll +# default="[V]ote",'v','V' +vote-vote="[V]ote",'v','V' + +# Select item to vote on +# default="[Enter] to select",' ', "Enter" +vote-select="[Enter] to select",' ', "Enter" diff --git a/config/config.go b/config/config.go index b1ee270..60d2c6e 100644 --- a/config/config.go +++ b/config/config.go @@ -2,11 +2,13 @@ package config import ( "embed" + "errors" "fmt" "io/ioutil" "log" "os" "path/filepath" + "regexp" "strings" "text/template" @@ -36,6 +38,7 @@ type Config struct { OpenCustom OpenCustom NotificationConfig Notification Templates Templates + Input Input } type General struct { @@ -171,17 +174,154 @@ type Templates struct { Help *template.Template } -func CreateDefaultConfig(filepath string) error { - f, err := os.Create(filepath) - if err != nil { - return err +var keyMatch = regexp.MustCompile("^\"(.*?)\\[(.*?)\\](.*?)\"$") + +func newHint(s string) []string { + matches := keyMatch.FindAllStringSubmatch(s, -1) + if len(matches) == 0 { + return []string{"", "", ""} } - defer f.Close() - _, err = f.WriteString(conftext) - if err != nil { - return err + if len(matches[0]) != 4 { + return []string{"", "", ""} } - return nil + return []string{matches[0][1], matches[0][2], matches[0][3]} +} + +func NewKey(s []string, double bool) (Key, error) { + var k Key + if len(s) < 2 { + return k, errors.New("key must have a minimum length of 2") + } + var start int + if double { + start = 1 + k = Key{ + Hint: [][]string{newHint(s[0]), newHint(s[1])}, + } + } else { + start = 0 + k = Key{ + Hint: [][]string{newHint(s[0])}, + } + } + var runes []rune + var keys []tcell.Key + for _, v := range s[start+1:] { + value := []rune(strings.TrimSpace(v)) + if len(value) < 3 { + return k, errors.New("key value must have a minimum length of 3") + } + if value[0] == '\'' { + if len(value) != 3 { + return k, fmt.Errorf("rune %s must only contain one char", string(value)) + } + runes = append(runes, value[1]) + } else if value[0] == '"' { + if value[len(value)-1] != '"' { + return k, fmt.Errorf("key %s must end with \"", string(value)) + } + keyName := string(value[1 : len(value)-1]) + found := false + var fk tcell.Key + for tk, tv := range tcell.KeyNames { + if tv == keyName { + found = true + fk = tk + break + } + } + if found { + keys = append(keys, fk) + } else { + return k, fmt.Errorf("no key named %s", keyName) + } + } else { + return k, fmt.Errorf("input %s is in the wrong format", string(value)) + } + } + k.Runes = runes + k.Keys = keys + + return k, nil +} + +type Key struct { + Hint [][]string + Runes []rune + Keys []tcell.Key +} + +func (k Key) Match(kb tcell.Key, rb rune) bool { + for _, ka := range k.Keys { + if ka == kb { + return true + } + } + for _, ra := range k.Runes { + if ra == rb { + return true + } + } + return false +} + +type Input struct { + GlobalDown Key + GlobalUp Key + GlobalEnter Key + GlobalBack Key + GlobalExit Key + + MainHome Key + MainEnd Key + MainPrevFeed Key + MainNextFeed Key + MainNotificationFocus Key + MainCompose Key + + StatusAvatar Key + StatusBoost Key + StatusDelete Key + StatusFavorite Key + StatusMedia Key + StatusLinks Key + StatusPoll Key + StatusReply Key + StatusBookmark Key + StatusThread Key + StatusUser Key + StatusViewFocus Key + StatusYank Key + StatusToggleSpoiler Key + + UserAvatar Key + UserBlock Key + UserFollow Key + UserMute Key + UserLinks Key + UserUser Key + UserViewFocus Key + UserYank Key + + ListOpenFeed Key + + LinkOpen Key + LinkYank Key + + ComposeEditSpoiler Key + ComposeEditText Key + ComposeIncludeQuote Key + ComposeMediaFocus Key + ComposePost Key + ComposeToggleContentWarning Key + ComposeVisibility Key + + MediaDelete Key + MediaEditDesc Key + MediaAdd Key + + VoteVote Key + VoteSelect Key } func parseColor(input string, def string, xrdb map[string]string) tcell.Color { @@ -611,6 +751,145 @@ func parseTemplates(cfg *ini.File) Templates { } } +func inputOrErr(cfg *ini.File, key string, double bool, def Key) Key { + if !cfg.Section("input").HasKey(key) { + return def + } + vals := cfg.Section("input").Key(key).Strings(",") + k, err := NewKey(vals, double) + if err != nil { + fmt.Printf("error parsing config for key %s. Error: %v\n", key, err) + os.Exit(1) + } + return k +} +func inputStrOrErr(vals []string, double bool) Key { + k, err := NewKey(vals, double) + if err != nil { + fmt.Printf("error parsing config. Error: %v\n", err) + os.Exit(1) + } + return k +} + +func parseInput(cfg *ini.File) Input { + ic := Input{ + GlobalDown: inputStrOrErr([]string{"\"\"", "'j'", "'J'", "\"Down\""}, false), + GlobalUp: inputStrOrErr([]string{"\"\"", "'k'", "'k'", "\"Up\""}, false), + GlobalEnter: inputStrOrErr([]string{"\"\"", "\"Enter\""}, false), + GlobalBack: inputStrOrErr([]string{"\"[Esc]\"", "\"Esc\""}, false), + GlobalExit: inputStrOrErr([]string{"\"[Q]uit\"", "'q'", "'Q'"}, false), + + MainHome: inputStrOrErr([]string{"\"\"", "'g'", "\"Home\""}, false), + MainEnd: inputStrOrErr([]string{"\"\"", "'G'", "\"End\""}, false), + MainPrevFeed: inputStrOrErr([]string{"\"\"", "'h'", "'H'", "\"Left\""}, false), + MainNextFeed: inputStrOrErr([]string{"\"\"", "'l'", "'L'", "\"Right\""}, false), + MainNotificationFocus: inputStrOrErr([]string{"\"[N]otifications\"", "'n'", "'N'"}, false), + MainCompose: inputStrOrErr([]string{"\"\"", "'c'", "'C'"}, false), + + StatusAvatar: inputStrOrErr([]string{"\"[A]vatar\"", "'a'", "'A'"}, false), + StatusBoost: inputStrOrErr([]string{"\"[B]oost\"", "\"Un[B]oost\"", "'b'", "'B'"}, true), + StatusDelete: inputStrOrErr([]string{"\"[D]elete\"", "'d'", "'D'"}, false), + StatusFavorite: inputStrOrErr([]string{"\"[F]avorite\"", "\"Un[F]avorite\"", "'f'", "'F'"}, true), + StatusMedia: inputStrOrErr([]string{"\"[M]edia\"", "'m'", "'M'"}, false), + StatusLinks: inputStrOrErr([]string{"\"[O]pen\"", "'o'", "'O'"}, false), + StatusPoll: inputStrOrErr([]string{"\"[P]oll\"", "'p'", "'P'"}, false), + StatusReply: inputStrOrErr([]string{"\"[R]eply\"", "'r'", "'R'"}, false), + StatusBookmark: inputStrOrErr([]string{"\"[S]ave\"", "\"Un[S]ave\"", "'s'", "'S'"}, true), + StatusThread: inputStrOrErr([]string{"\"[T]hread\"", "'t'", "'T'"}, false), + StatusUser: inputStrOrErr([]string{"\"[U]ser\"", "'u'", "'U'"}, false), + StatusViewFocus: inputStrOrErr([]string{"\"[V]iew\"", "'v'", "'V'"}, false), + StatusYank: inputStrOrErr([]string{"\"[Y]ank\"", "'y'", "'Y'"}, false), + StatusToggleSpoiler: inputStrOrErr([]string{"\"Press [Z] to toggle spoiler\"", "'z'", "'Z'"}, false), + + UserAvatar: inputStrOrErr([]string{"\"[A]vatar\"", "'a'", "'A'"}, false), + UserBlock: inputStrOrErr([]string{"\"[B]lock\"", "\"Un[B]lock\"", "'b'", "'B'"}, true), + UserFollow: inputStrOrErr([]string{"\"[F]ollow\"", "\"Un[F]ollow\"", "'f'", "'F'"}, true), + UserMute: inputStrOrErr([]string{"\"[M]ute\"", "\"Un[M]ute\"", "'m'", "'M'"}, true), + UserLinks: inputStrOrErr([]string{"\"[O]pen\"", "'o'", "'O'"}, false), + UserUser: inputStrOrErr([]string{"\"[U]ser\"", "'u'", "'U'"}, false), + UserViewFocus: inputStrOrErr([]string{"\"[V]iew\"", "'v'", "'V'"}, false), + UserYank: inputStrOrErr([]string{"\"[Y]ank\"", "'y'", "'Y'"}, false), + + ListOpenFeed: inputStrOrErr([]string{"\"[O]pen\"", "'o'", "'O'"}, false), + + LinkOpen: inputStrOrErr([]string{"\"[O]pen\"", "'o'", "'O'"}, false), + LinkYank: inputStrOrErr([]string{"\"[Y]ank\"", "'y'", "'Y'"}, false), + + ComposeEditSpoiler: inputStrOrErr([]string{"\"[C]W Text\"", "'c'", "'C'"}, false), + ComposeEditText: inputStrOrErr([]string{"\"[E]dit text\"", "'e'", "'E'"}, false), + ComposeIncludeQuote: inputStrOrErr([]string{"\"[I]nclude quote\"", "'i'", "'I'"}, false), + ComposeMediaFocus: inputStrOrErr([]string{"\"[M]edia\"", "'m'", "'M'"}, false), + ComposePost: inputStrOrErr([]string{"\"[P]ost\"", "'p'", "'P'"}, false), + ComposeToggleContentWarning: inputStrOrErr([]string{"\"[T]oggle CW\"", "'t'", "'T'"}, false), + ComposeVisibility: inputStrOrErr([]string{"\"[V]isibility\"", "'v'", "'V'"}, false), + + MediaDelete: inputStrOrErr([]string{"\"[D]elete\"", "'d'", "'D'"}, false), + MediaEditDesc: inputStrOrErr([]string{"\"[E]dit desc\"", "'e'", "'E'"}, false), + MediaAdd: inputStrOrErr([]string{"\"[A]dd\"", "'a'", "'A'"}, false), + + VoteVote: inputStrOrErr([]string{"\"[V]ote\"", "'v'", "'V'"}, false), + VoteSelect: inputStrOrErr([]string{"\"[Enter] to select\"", "' '", "\"Enter\""}, false), + } + ic.GlobalDown = inputOrErr(cfg, "global-down", false, ic.GlobalDown) + ic.GlobalUp = inputOrErr(cfg, "global-up", false, ic.GlobalUp) + ic.GlobalEnter = inputOrErr(cfg, "global-enter", false, ic.GlobalEnter) + ic.GlobalBack = inputOrErr(cfg, "global-back", false, ic.GlobalBack) + ic.GlobalExit = inputOrErr(cfg, "global-exit", false, ic.GlobalExit) + + ic.MainHome = inputOrErr(cfg, "main-home", false, ic.MainHome) + ic.MainEnd = inputOrErr(cfg, "main-end", false, ic.MainEnd) + ic.MainPrevFeed = inputOrErr(cfg, "main-prev-feed", false, ic.MainPrevFeed) + ic.MainNextFeed = inputOrErr(cfg, "main-next-feed", false, ic.MainNextFeed) + ic.MainNotificationFocus = inputOrErr(cfg, "main-notification-focus", false, ic.MainNotificationFocus) + ic.MainCompose = inputOrErr(cfg, "main-compose", false, ic.MainCompose) + + ic.StatusAvatar = inputOrErr(cfg, "status-avatar", false, ic.StatusAvatar) + ic.StatusBoost = inputOrErr(cfg, "status-boost", true, ic.StatusBoost) + ic.StatusDelete = inputOrErr(cfg, "status-delete", false, ic.StatusDelete) + ic.StatusFavorite = inputOrErr(cfg, "status-favorite", true, ic.StatusFavorite) + ic.StatusMedia = inputOrErr(cfg, "status-media", false, ic.StatusMedia) + ic.StatusLinks = inputOrErr(cfg, "status-links", false, ic.StatusLinks) + ic.StatusPoll = inputOrErr(cfg, "status-poll", false, ic.StatusPoll) + ic.StatusReply = inputOrErr(cfg, "status-reply", false, ic.StatusReply) + ic.StatusBookmark = inputOrErr(cfg, "status-bookmark", true, ic.StatusBookmark) + ic.StatusThread = inputOrErr(cfg, "status-thread", false, ic.StatusThread) + ic.StatusUser = inputOrErr(cfg, "status-user", false, ic.StatusUser) + ic.StatusViewFocus = inputOrErr(cfg, "status-view-focus", false, ic.StatusViewFocus) + ic.StatusYank = inputOrErr(cfg, "status-yank", false, ic.StatusYank) + ic.StatusToggleSpoiler = inputOrErr(cfg, "status-toggle-spoiler", false, ic.StatusToggleSpoiler) + + ic.UserAvatar = inputOrErr(cfg, "user-avatar", false, ic.UserAvatar) + ic.UserBlock = inputOrErr(cfg, "user-block", true, ic.UserBlock) + ic.UserFollow = inputOrErr(cfg, "user-follow", true, ic.UserFollow) + ic.UserMute = inputOrErr(cfg, "user-mute", true, ic.UserMute) + ic.UserLinks = inputOrErr(cfg, "user-links", false, ic.UserLinks) + ic.UserUser = inputOrErr(cfg, "user-user", false, ic.UserUser) + ic.UserViewFocus = inputOrErr(cfg, "user-view-focus", false, ic.UserViewFocus) + ic.UserYank = inputOrErr(cfg, "user-yank", false, ic.UserYank) + + ic.ListOpenFeed = inputOrErr(cfg, "list-open-feed", false, ic.ListOpenFeed) + + ic.LinkOpen = inputOrErr(cfg, "link-open", false, ic.LinkOpen) + ic.LinkYank = inputOrErr(cfg, "link-yank", false, ic.LinkYank) + + ic.ComposeEditSpoiler = inputOrErr(cfg, "compose-edit-spoiler", false, ic.ComposeEditSpoiler) + ic.ComposeEditText = inputOrErr(cfg, "compose-edit-text", false, ic.ComposeEditText) + ic.ComposeIncludeQuote = inputOrErr(cfg, "compose-include-quote", false, ic.ComposeIncludeQuote) + ic.ComposeMediaFocus = inputOrErr(cfg, "compose-media-focus", false, ic.ComposeMediaFocus) + ic.ComposePost = inputOrErr(cfg, "compose-post", false, ic.ComposePost) + ic.ComposeToggleContentWarning = inputOrErr(cfg, "compose-toggle-content-warning", false, ic.ComposeToggleContentWarning) + ic.ComposeVisibility = inputOrErr(cfg, "compose-visibility", false, ic.ComposeVisibility) + + ic.MediaDelete = inputOrErr(cfg, "media-delete", false, ic.MediaDelete) + ic.MediaEditDesc = inputOrErr(cfg, "media-edit-desc", false, ic.MediaEditDesc) + ic.MediaAdd = inputOrErr(cfg, "media-add", false, ic.MediaAdd) + + ic.VoteVote = inputOrErr(cfg, "vote-vote", false, ic.VoteVote) + ic.VoteSelect = inputOrErr(cfg, "vote-select", false, ic.VoteSelect) + return ic +} + func parseConfig(filepath string) (Config, error) { cfg, err := ini.LoadSources(ini.LoadOptions{ SpaceBeforeInlineComment: true, @@ -626,6 +905,7 @@ func parseConfig(filepath string) (Config, error) { conf.OpenCustom = parseCustom(cfg) conf.NotificationConfig = parseNotifications(cfg) conf.Templates = parseTemplates(cfg) + conf.Input = parseInput(cfg) return conf, nil } @@ -655,7 +935,7 @@ func checkConfig(filename string) (path string, exists bool, err error) { return path, true, err } -func createDefaultConfig(filepath string) error { +func CreateDefaultConfig(filepath string) error { f, err := os.Create(filepath) if err != nil { return err diff --git a/config/default_config.go b/config/default_config.go index c8dce57..e6f847a 100644 --- a/config/default_config.go +++ b/config/default_config.go @@ -318,4 +318,230 @@ list-selected-background=xrdb:color5 # The text color of selected list items. # default=xrdb:background list-selected-text=xrdb:background + +[input] +# You can edit the keys for tut below. +# +# The syntax is a bit weird, but it works. And I'll try to explain it as well as +# I can. +# +# Example: +# status-favorite="[F]avorite","Un[F]avorite",'f','F' +# status-delete="[D]elete",'d','D' +# +# status-favorite and status-delete differs because favorite can be in two +# states, so you will have to add two key hints. +# Most keys will only have on key hint. Look at the default value for reference. +# +# Key hints must be in some of the following formats. Remember the quotation +# marks. +# "" = empty +# "[D]elete" = Delete with a highlighted D +# "Un[F]ollow" = UnFollow with a highlighted F +# "[Enter]" = Enter where everything is highlighted +# "Yan[K]" = YanK with a highlighted K +# +# After the hint (or hints) you must set the keys. You can do this in two ways, +# with single quotation marks or double ones. +# +# The single ones are for single chars like 'a', 'b', 'c' and double marks are +# for special keys like "Enter". Remember that they are case sensetive. +# +# To find the names of special keys you have to go to the following site and +# look for "var KeyNames = map[Key]string{" +# +# https://github.com/gdamore/tcell/blob/master/key.go +# + +# Keys for moving down +# default="",'j','J',"Down" +global-down="",'j','J',"Down" + +# Keys for moving up +# default="",'k','K',"Up" +global-up="",'k','K',"Up" + +# To select items +# default="","Enter" +global-enter="","Enter" + +# To go back +# default="[Esc]","Esc" +global-back="[Esc]","Esc" + +# To go back and exit Tut +# default="[Q]uit",'q','Q' +global-exit="[Q]uit",'q','Q' + +# Move to the top +# default="",'g',"Home" +main-home="",'g',"Home" + +# Move to the bottom +# default="",'G',"End" +main-end="",'G',"End" + +# Go to previous feed +# default="",'h','H',"Left" +main-prev-feed="",'h','H',"Left" + +# Go to next feed +# default="",'l','L',"Right" +main-next-feed="",'l','L',"Right" + +# Focus on the notification list +# default="[N]otifications",'n','N' +main-notification-focus="[N]otifications",'n','N' + +# Compose a new toot +# default="",'c','C' +main-compose="",'c','C' + +# Open avatar +# default="[A]vatar",'a','A' +status-avatar="[A]vatar",'a','A' + +# Boost a toot +# default="[B]oost","Un[B]oost",'b','B' +status-boost="[B]oost","Un[B]oost",'b','B' + +# Delete a toot +# default="[D]elete",'d','D' +status-delete="[D]elete",'d','D' + +# Favorite a toot +# default="[F]avorite","Un[F]avorite",'f','F' +status-favorite="[F]avorite","Un[F]avorite",'f','F' + +# Open toots media files +# default="[M]edia",'m','M' +status-media="[M]edia",'m','M' + +# Open links +# default="[O]pen",'o','O' +status-links="[O]pen",'o','O' + +# Open poll +# default="[P]oll",'p','P' +status-poll="[P]oll",'p','P' + +# Reply to toot +# default="[R]eply",'r','R' +status-reply="[R]eply",'r','R' + +# Save/bookmark a toot +# default="[S]ave","Un[S]ave",'s','S' +status-bookmark="[S]ave","Un[S]ave",'s','S' + +# View thread +# default="[T]hread",'t','T' +status-thread="[T]hread",'t','T' + +# Open user profile +# default="[U]ser",'u','U' +status-user="[U]ser",'u','U' + +# Open the view mode +# default="[V]iew",'v','V' +status-view-focus="[V]iew",'v','V' + +# Yank the url of the toot +# default="[Y]ank",'y','Y' +status-yank="[Y]ank",'y','Y' + +# Remove the spoiler +# default="Press [Z] to toggle spoiler",'z','Z' +status-toggle-spoiler="Press [Z] to toggle spoiler",'z','Z' + +# View avatar +# default="[A]vatar",'a','A' +user-avatar="[A]vatar",'a','A' + +# Block the user +# default="[B]lock","Un[B]lock",'b','B' +user-block="[B]lock","Un[B]lock",'b','B' + +# Follow user +# default="[F]ollow","Un[F]ollow",'f','F' +user-follow="[F]ollow","Un[F]ollow",'f','F' + +# Mute user +# default="[M]ute","Un[M]ute",'m','M' +user-mute="[M]ute","Un[M]ute",'m','M' + +# Open links +# default="[O]pen",'o','O' +user-links="[O]pen",'o','O' + +# View user profile +# default="[U]ser",'u','U' +user-user="[U]ser",'u','U' + +# Open view mode +# default="[V]iew",'v','V' +user-view-focus="[V]iew",'v','V' + +# Yank the user URL +# default="[Y]ank",'y','Y' +user-yank="[Y]ank",'y','Y' + +# Open list +# default="[O]pen",'o','O' +list-open-feed="[O]pen",'o','O' + +# Open URL +# default="[O]pen",'o','O' +link-open="[O]pen",'o','O' + +# Yank the URL +# default="[Y]ank",'y','Y' +link-yank="[Y]ank",'y','Y' + +# Edit spoiler text on new toot +# default="[C]W text",'c','C' +compose-edit-spoiler="[C]W text",'c','C' + +# Edit the text on new toot +# default="[E]dit text",'e','E' +compose-edit-text="[E]dit text",'e','E' + +# Include a quote when replying +# default="[I]nclude quote",'i','I' +compose-include-quote="[I]nclude quote",'i','I' + +# Focus on adding media to toot +# default="[M]edia",'m','M' +compose-media-focus="[M]edia",'m','M' + +# Post the new toot +# default="[P]ost",'p','P' +compose-post="[P]ost",'p','P' + +# Toggle content warning on toot +# default="[T]oggle CW",'t','T' +compose-toggle-content-warning="[T]oggle CW",'t','T' + +# Edit the visibility on new toot +# default="[V]isibility",'v','V' +compose-visibility="[V]isibility",'v','V' + +# Delete media file +# default="[D]elete",'d','D' +media-delete="[D]elete",'d','D' + +# Edit the description on media file +# default="[E]dit desc",'e','E' +media-edit-desc="[E]dit desc",'e','E' + +# Add a new media file +# default="[A]dd",'a','A' +media-add="[A]dd",'a','A' + +# Vote on poll +# default="[V]ote",'v','V' +vote-vote="[V]ote",'v','V' + +# Select item to vote on +# default="[Enter] to select",' ', "Enter" +vote-select="[Enter] to select",' ', "Enter" ` diff --git a/config/keys.go b/config/keys.go index 0366468..af8be2a 100644 --- a/config/keys.go +++ b/config/keys.go @@ -6,6 +6,20 @@ import ( "github.com/gdamore/tcell/v2" ) +func ColorFromKey(c *Config, k Key, first bool) string { + if len(k.Hint) == 0 { + return "" + } + parts := k.Hint[0] + if !first && len(k.Hint) > 1 { + parts = k.Hint[1] + } + if len(parts) != 3 { + return "" + } + return ColorKey(c, parts[0], parts[1], parts[2]) +} + func ColorKey(c *Config, pre, key, end string) string { color := ColorMark(c.Style.TextSpecial2) normal := ColorMark(c.Style.Text) diff --git a/config/load.go b/config/load.go index e6e3a91..4e19083 100644 --- a/config/load.go +++ b/config/load.go @@ -17,7 +17,7 @@ func Load() *Config { os.Exit(1) } if !exists { - err = createDefaultConfig(path) + err = CreateDefaultConfig(path) if err != nil { fmt.Printf("Couldn't create default config. Error: %v\n", err) os.Exit(1) diff --git a/main.go b/main.go index 10fa093..02eef63 100644 --- a/main.go +++ b/main.go @@ -10,7 +10,7 @@ import ( "github.com/rivo/tview" ) -const version = "1.0.2" +const version = "1.0.3" func main() { util.MakeDirs() diff --git a/ui/composeview.go b/ui/composeview.go index 6cbba3b..c0f1da7 100644 --- a/ui/composeview.go +++ b/ui/composeview.go @@ -98,20 +98,20 @@ func (cv *ComposeView) SetControls(ctrl ComposeControls) { var items []string switch ctrl { case ComposeNormal: - items = append(items, config.ColorKey(cv.tutView.tut.Config, "", "P", "ost")) - items = append(items, config.ColorKey(cv.tutView.tut.Config, "", "E", "dit")) - items = append(items, config.ColorKey(cv.tutView.tut.Config, "", "V", "isibility")) - items = append(items, config.ColorKey(cv.tutView.tut.Config, "", "T", "oggle CW")) - items = append(items, config.ColorKey(cv.tutView.tut.Config, "", "C", "ontent warning text")) - items = append(items, config.ColorKey(cv.tutView.tut.Config, "", "M", "edia attachment")) + items = append(items, config.ColorFromKey(cv.tutView.tut.Config, cv.tutView.tut.Config.Input.ComposePost, true)) + items = append(items, config.ColorFromKey(cv.tutView.tut.Config, cv.tutView.tut.Config.Input.ComposeEditText, true)) + items = append(items, config.ColorFromKey(cv.tutView.tut.Config, cv.tutView.tut.Config.Input.ComposeVisibility, true)) + items = append(items, config.ColorFromKey(cv.tutView.tut.Config, cv.tutView.tut.Config.Input.ComposeToggleContentWarning, true)) + items = append(items, config.ColorFromKey(cv.tutView.tut.Config, cv.tutView.tut.Config.Input.ComposeEditSpoiler, true)) + items = append(items, config.ColorFromKey(cv.tutView.tut.Config, cv.tutView.tut.Config.Input.ComposeMediaFocus, true)) if cv.msg.Status != nil { - items = append(items, config.ColorKey(cv.tutView.tut.Config, "", "I", "nclude quote")) + items = append(items, config.ColorFromKey(cv.tutView.tut.Config, cv.tutView.tut.Config.Input.ComposeIncludeQuote, true)) } case ComposeMedia: - items = append(items, config.ColorKey(cv.tutView.tut.Config, "", "A", "dd")) - items = append(items, config.ColorKey(cv.tutView.tut.Config, "", "D", "elete")) - items = append(items, config.ColorKey(cv.tutView.tut.Config, "", "E", "dit desc")) - items = append(items, config.ColorKey(cv.tutView.tut.Config, "", "Esc", " Done")) + items = append(items, config.ColorFromKey(cv.tutView.tut.Config, cv.tutView.tut.Config.Input.MediaAdd, true)) + items = append(items, config.ColorFromKey(cv.tutView.tut.Config, cv.tutView.tut.Config.Input.MediaDelete, true)) + items = append(items, config.ColorFromKey(cv.tutView.tut.Config, cv.tutView.tut.Config.Input.MediaEditDesc, true)) + items = append(items, config.ColorFromKey(cv.tutView.tut.Config, cv.tutView.tut.Config.Input.GlobalBack, true)) } res := strings.Join(items, " ") cv.controls.SetText(res) diff --git a/ui/helpview.go b/ui/helpview.go index e28cedb..beaee77 100644 --- a/ui/helpview.go +++ b/ui/helpview.go @@ -2,6 +2,7 @@ package ui import ( "bytes" + "strings" "github.com/RasmusLindroth/tut/config" "github.com/rivo/tview" @@ -35,7 +36,11 @@ func NewHelpView(tv *TutView) *HelpView { panic(err) } hv.content.SetText(output.String()) - hv.controls.SetText(config.ColorKey(tv.tut.Config, "", "Esc/Q", "uit")) + var items []string + items = append(items, config.ColorFromKey(tv.tut.Config, tv.tut.Config.Input.GlobalBack, true)) + items = append(items, config.ColorFromKey(tv.tut.Config, tv.tut.Config.Input.GlobalExit, true)) + res := strings.Join(items, " ") + hv.controls.SetText(res) hv.View = newHelpViewUI(hv) return hv } diff --git a/ui/input.go b/ui/input.go index 09dd947..0c56563 100644 --- a/ui/input.go +++ b/ui/input.go @@ -46,27 +46,17 @@ func (tv *TutView) Input(event *tcell.EventKey) *tcell.EventKey { } func (tv *TutView) InputLoginView(event *tcell.EventKey) *tcell.EventKey { - if event.Key() == tcell.KeyRune { - switch event.Rune() { - case 'j', 'J': - tv.LoginView.Next() - return nil - case 'k', 'K': - tv.LoginView.Prev() - return nil - } - } else { - switch event.Key() { - case tcell.KeyEnter: - tv.LoginView.Selected() - return nil - case tcell.KeyUp: - tv.LoginView.Prev() - return nil - case tcell.KeyDown: - tv.LoginView.Next() - return nil - } + if tv.tut.Config.Input.GlobalDown.Match(event.Key(), event.Rune()) { + tv.LoginView.Next() + return nil + } + if tv.tut.Config.Input.GlobalUp.Match(event.Key(), event.Rune()) { + tv.LoginView.Prev() + return nil + } + if tv.tut.Config.Input.GlobalEnter.Match(event.Key(), event.Rune()) { + tv.LoginView.Selected() + return nil } return event } @@ -84,105 +74,75 @@ func (tv *TutView) InputMainView(event *tcell.EventKey) *tcell.EventKey { func (tv *TutView) InputMainViewFeed(event *tcell.EventKey) *tcell.EventKey { mainFocus := tv.TimelineFocus == FeedFocus - if event.Key() == tcell.KeyRune { - switch event.Rune() { - case 'g': - tv.Timeline.HomeItemFeed(mainFocus) - return nil - case 'G': - tv.Timeline.EndItemFeed(mainFocus) - return nil - case 'h', 'H': - if mainFocus { - tv.Timeline.PrevFeed() - } - return nil - case 'l', 'L': - if mainFocus { - tv.Timeline.NextFeed() - } - return nil - case 'j', 'J': - tv.Timeline.NextItemFeed(mainFocus) - return nil - case 'k', 'K': - tv.Timeline.PrevItemFeed(mainFocus) - return nil - case 'n', 'N': - if tv.tut.Config.General.NotificationFeed { - tv.FocusNotification() - } - return nil - case 'q', 'Q': - if mainFocus { - tv.Timeline.RemoveCurrent(true) - } else { - tv.FocusFeed() - } - return nil + + if tv.tut.Config.Input.MainHome.Match(event.Key(), event.Rune()) { + tv.Timeline.HomeItemFeed(mainFocus) + return nil + } + if tv.tut.Config.Input.MainEnd.Match(event.Key(), event.Rune()) { + tv.Timeline.EndItemFeed(mainFocus) + return nil + } + if tv.tut.Config.Input.MainPrevFeed.Match(event.Key(), event.Rune()) { + if mainFocus { + tv.Timeline.PrevFeed() } - } else { - switch event.Key() { - case tcell.KeyLeft: - if mainFocus { - tv.Timeline.PrevFeed() - return nil - } - return nil - case tcell.KeyRight: - if mainFocus { - tv.Timeline.NextFeed() - return nil - } - return nil - case tcell.KeyUp: - tv.Timeline.PrevItemFeed(mainFocus) - return nil - case tcell.KeyDown: - tv.Timeline.NextItemFeed(mainFocus) - return nil - case tcell.KeyHome: - tv.Timeline.HomeItemFeed(mainFocus) - return nil - case tcell.KeyEnd: - tv.Timeline.EndItemFeed(mainFocus) - return nil - case tcell.KeyEsc: - if mainFocus { - tv.Timeline.RemoveCurrent(false) - } else { - tv.FocusFeed() - } - return nil + return nil + } + if tv.tut.Config.Input.MainNextFeed.Match(event.Key(), event.Rune()) { + if mainFocus { + tv.Timeline.NextFeed() + } + return nil + } + if tv.tut.Config.Input.GlobalDown.Match(event.Key(), event.Rune()) { + tv.Timeline.NextItemFeed(mainFocus) + return nil + } + if tv.tut.Config.Input.GlobalUp.Match(event.Key(), event.Rune()) { + tv.Timeline.PrevItemFeed(mainFocus) + return nil + } + if tv.tut.Config.Input.MainNotificationFocus.Match(event.Key(), event.Rune()) { + if tv.tut.Config.General.NotificationFeed { + tv.FocusNotification() } + return nil + } + if tv.tut.Config.Input.GlobalExit.Match(event.Key(), event.Rune()) { + if mainFocus { + tv.Timeline.RemoveCurrent(true) + } else { + tv.FocusFeed() + } + return nil + } + if tv.tut.Config.Input.GlobalBack.Match(event.Key(), event.Rune()) { + if mainFocus { + tv.Timeline.RemoveCurrent(false) + } else { + tv.FocusFeed() + } + return nil } return tv.InputItem(event) } func (tv *TutView) InputMainViewContent(event *tcell.EventKey) *tcell.EventKey { - if event.Key() == tcell.KeyRune { - switch event.Rune() { - case 'j', 'J': - tv.Timeline.ScrollDown() - return nil - case 'k', 'K': - tv.Timeline.ScrollUp() - return nil - default: - return event - } + if tv.tut.Config.Input.GlobalDown.Match(event.Key(), event.Rune()) { + tv.Timeline.ScrollDown() + return nil + } + if tv.tut.Config.Input.GlobalUp.Match(event.Key(), event.Rune()) { + tv.Timeline.ScrollDown() + return nil } return tv.InputItem(event) } func (tv *TutView) InputHelp(event *tcell.EventKey) *tcell.EventKey { - switch event.Rune() { - case 'q': - tv.PrevFocus() - return nil - } - switch event.Key() { - case tcell.KeyEsc: + if tv.tut.Config.Input.GlobalBack.Match(event.Key(), event.Rune()) || + tv.tut.Config.Input.GlobalExit.Match(event.Key(), event.Rune()) { tv.PrevFocus() return nil } @@ -190,13 +150,8 @@ func (tv *TutView) InputHelp(event *tcell.EventKey) *tcell.EventKey { } func (tv *TutView) InputViewItem(event *tcell.EventKey) *tcell.EventKey { - switch event.Rune() { - case 'q': - tv.FocusMainNoHistory() - return nil - } - switch event.Key() { - case tcell.KeyEsc: + if tv.tut.Config.Input.GlobalBack.Match(event.Key(), event.Rune()) || + tv.tut.Config.Input.GlobalExit.Match(event.Key(), event.Rune()) { tv.FocusMainNoHistory() return nil } @@ -208,8 +163,7 @@ func (tv *TutView) InputItem(event *tcell.EventKey) *tcell.EventKey { if err != nil { return event } - switch event.Rune() { - case 'c', 'C': + if tv.tut.Config.Input.MainCompose.Match(event.Key(), event.Rune()) { tv.InitPost(nil) return nil } @@ -255,11 +209,11 @@ func (tv *TutView) InputStatus(event *tcell.EventKey, item api.Item, status *mas favorited := sr.Favourited bookmarked := sr.Bookmarked - switch event.Rune() { - case 'a', 'A': + if tv.tut.Config.Input.StatusAvatar.Match(event.Key(), event.Rune()) { openAvatar(tv, sr.Account) return nil - case 'b', 'B': + } + if tv.tut.Config.Input.StatusBoost.Match(event.Key(), event.Rune()) { txt := "boost" if boosted { txt = "unboost" @@ -277,7 +231,8 @@ func (tv *TutView) InputStatus(event *tcell.EventKey, item api.Item, status *mas tv.RedrawControls() }) return nil - case 'd', 'D': + } + if tv.tut.Config.Input.StatusDelete.Match(event.Key(), event.Rune()) { if !isMine { return nil } @@ -299,7 +254,8 @@ func (tv *TutView) InputStatus(event *tcell.EventKey, item api.Item, status *mas tv.RedrawContent() }) return nil - case 'f', 'F': + } + if tv.tut.Config.Input.StatusFavorite.Match(event.Key(), event.Rune()) { txt := "favorite" if favorited { txt = "unfavorite" @@ -317,25 +273,30 @@ func (tv *TutView) InputStatus(event *tcell.EventKey, item api.Item, status *mas tv.RedrawControls() }) return nil - case 'm', 'M': + } + if tv.tut.Config.Input.StatusMedia.Match(event.Key(), event.Rune()) { if hasMedia { openMedia(tv, sr) } return nil - case 'o', 'O': + } + if tv.tut.Config.Input.StatusLinks.Match(event.Key(), event.Rune()) { tv.SetPage(LinkFocus) return nil - case 'p', 'P': + } + if tv.tut.Config.Input.StatusPoll.Match(event.Key(), event.Rune()) { if !hasPoll { return nil } tv.VoteView.SetPoll(sr.Poll) tv.SetPage(VoteFocus) return nil - case 'r', 'R': + } + if tv.tut.Config.Input.StatusReply.Match(event.Key(), event.Rune()) { tv.InitPost(status) return nil - case 's', 'S': + } + if tv.tut.Config.Input.StatusBookmark.Match(event.Key(), event.Rune()) { txt := "save" if bookmarked { txt = "unsave" @@ -353,23 +314,28 @@ func (tv *TutView) InputStatus(event *tcell.EventKey, item api.Item, status *mas tv.RedrawControls() }) return nil - case 't', 'T': + } + if tv.tut.Config.Input.StatusThread.Match(event.Key(), event.Rune()) { tv.Timeline.AddFeed(NewThreadFeed(tv, item)) return nil - case 'u', 'U': + } + if tv.tut.Config.Input.StatusUser.Match(event.Key(), event.Rune()) { user, err := tv.tut.Client.GetUserByID(status.Account.ID) if err != nil { return nil } tv.Timeline.AddFeed(NewUserFeed(tv, user)) return nil - case 'v', 'V': + } + if tv.tut.Config.Input.StatusViewFocus.Match(event.Key(), event.Rune()) { tv.SetPage(ViewFocus) return nil - case 'y', 'Y': + } + if tv.tut.Config.Input.StatusYank.Match(event.Key(), event.Rune()) { copyToClipboard(status.URL) return nil - case 'z', 'Z': + } + if tv.tut.Config.Input.StatusToggleSpoiler.Match(event.Key(), event.Rune()) { if !hasSpoiler { return nil } @@ -387,11 +353,12 @@ func (tv *TutView) InputUser(event *tcell.EventKey, user *api.User) *tcell.Event blocking := user.Relation.Blocking muting := user.Relation.Muting following := user.Relation.Following - switch event.Rune() { - case 'a', 'A': + + if tv.tut.Config.Input.UserAvatar.Match(event.Key(), event.Rune()) { openAvatar(tv, *user.Data) return nil - case 'b', 'B': + } + if tv.tut.Config.Input.UserBlock.Match(event.Key(), event.Rune()) { txt := "block" if blocking { txt = "unblock" @@ -409,7 +376,8 @@ func (tv *TutView) InputUser(event *tcell.EventKey, user *api.User) *tcell.Event tv.RedrawControls() }) return nil - case 'f', 'F': + } + if tv.tut.Config.Input.UserFollow.Match(event.Key(), event.Rune()) { txt := "follow" if following { txt = "unfollow" @@ -427,7 +395,8 @@ func (tv *TutView) InputUser(event *tcell.EventKey, user *api.User) *tcell.Event tv.RedrawControls() }) return nil - case 'm', 'M': + } + if tv.tut.Config.Input.UserMute.Match(event.Key(), event.Rune()) { txt := "mute" if muting { txt = "unmute" @@ -445,21 +414,24 @@ func (tv *TutView) InputUser(event *tcell.EventKey, user *api.User) *tcell.Event tv.RedrawControls() }) return nil - case 'o', 'O': + } + if tv.tut.Config.Input.UserLinks.Match(event.Key(), event.Rune()) { tv.SetPage(LinkFocus) return nil - case 'u', 'U': + } + if tv.tut.Config.Input.UserUser.Match(event.Key(), event.Rune()) { tv.Timeline.AddFeed(NewUserFeed(tv, api.NewUserItem(user, true))) return nil - case 'v', 'V': + } + if tv.tut.Config.Input.UserViewFocus.Match(event.Key(), event.Rune()) { tv.SetPage(ViewFocus) return nil - case 'y', 'Y': + } + if tv.tut.Config.Input.UserYank.Match(event.Key(), event.Rune()) { copyToClipboard(user.Data.URL) return nil } - switch event.Key() { - case tcell.KeyEnter: + if tv.tut.Config.Input.GlobalEnter.Match(event.Key(), event.Rune()) { tv.Timeline.AddFeed(NewUserFeed(tv, api.NewUserItem(user, true))) return nil } @@ -467,13 +439,8 @@ func (tv *TutView) InputUser(event *tcell.EventKey, user *api.User) *tcell.Event } func (tv *TutView) InputList(event *tcell.EventKey, list *mastodon.List) *tcell.EventKey { - switch event.Rune() { - case 'o', 'O': - tv.Timeline.AddFeed(NewListFeed(tv, list)) - return nil - } - switch event.Key() { - case tcell.KeyEnter: + if tv.tut.Config.Input.ListOpenFeed.Match(event.Key(), event.Rune()) || + tv.tut.Config.Input.GlobalEnter.Match(event.Key(), event.Rune()) { tv.Timeline.AddFeed(NewListFeed(tv, list)) return nil } @@ -481,122 +448,104 @@ func (tv *TutView) InputList(event *tcell.EventKey, list *mastodon.List) *tcell. } func (tv *TutView) InputLinkView(event *tcell.EventKey) *tcell.EventKey { + if tv.tut.Config.Input.GlobalDown.Match(event.Key(), event.Rune()) { + tv.LinkView.Next() + return nil + } + if tv.tut.Config.Input.GlobalUp.Match(event.Key(), event.Rune()) { + tv.LinkView.Prev() + return nil + } + if tv.tut.Config.Input.LinkOpen.Match(event.Key(), event.Rune()) || + tv.tut.Config.Input.GlobalEnter.Match(event.Key(), event.Rune()) { + tv.LinkView.Open() + return nil + } + if tv.tut.Config.Input.LinkYank.Match(event.Key(), event.Rune()) { + tv.LinkView.Yank() + return nil + } + if tv.tut.Config.Input.GlobalBack.Match(event.Key(), event.Rune()) || + tv.tut.Config.Input.GlobalExit.Match(event.Key(), event.Rune()) { + tv.SetPage(MainFocus) + return nil + } if event.Key() == tcell.KeyRune { switch event.Rune() { - case 'j', 'J': - tv.LinkView.Next() - return nil - case 'k', 'K': - tv.LinkView.Prev() - return nil - case 'o', 'O': - tv.LinkView.Open() - return nil - case 'y', 'Y': - tv.LinkView.Yank() - return nil case '1', '2', '3', '4', '5': s := string(event.Rune()) i, _ := strconv.Atoi(s) tv.LinkView.OpenCustom(i) return nil - case 'q', 'Q': - tv.SetPage(MainFocus) - return nil - } - } else { - switch event.Key() { - case tcell.KeyEnter: - tv.LinkView.Open() - return nil - case tcell.KeyUp: - tv.LinkView.Prev() - return nil - case tcell.KeyDown: - tv.LinkView.Next() - return nil - case tcell.KeyEsc: - tv.SetPage(MainFocus) - return nil } } return event } func (tv *TutView) InputComposeView(event *tcell.EventKey) *tcell.EventKey { - if event.Key() == tcell.KeyRune { - switch event.Rune() { - case 'c', 'C': - tv.ComposeView.EditSpoiler() - return nil - case 'e', 'E': - tv.ComposeView.EditText() - return nil - case 'i', 'I': - tv.ComposeView.IncludeQuote() - return nil - case 'm', 'M': - tv.SetPage(MediaFocus) - return nil - case 'p', 'P': - tv.ComposeView.Post() - return nil - case 't', 'T': - tv.ComposeView.ToggleCW() - return nil - case 'v', 'V': - tv.ComposeView.FocusVisibility() - return nil - case 'q', 'Q': - tv.ModalView.Run( - "Do you want exit the compose view?", func() { - tv.FocusMainNoHistory() - }) - return nil - } - } else { - switch event.Key() { - case tcell.KeyEsc: - tv.ModalView.Run( - "Do you want exit the compose view?", func() { - tv.FocusMainNoHistory() - }) - return nil - } + if tv.tut.Config.Input.ComposeEditSpoiler.Match(event.Key(), event.Rune()) { + tv.ComposeView.EditSpoiler() + return nil + } + if tv.tut.Config.Input.ComposeEditText.Match(event.Key(), event.Rune()) { + tv.ComposeView.EditText() + return nil + } + if tv.tut.Config.Input.ComposeIncludeQuote.Match(event.Key(), event.Rune()) { + tv.ComposeView.IncludeQuote() + return nil + } + if tv.tut.Config.Input.ComposeMediaFocus.Match(event.Key(), event.Rune()) { + tv.SetPage(MediaFocus) + return nil + } + if tv.tut.Config.Input.ComposePost.Match(event.Key(), event.Rune()) { + tv.ComposeView.Post() + return nil + } + if tv.tut.Config.Input.ComposeToggleContentWarning.Match(event.Key(), event.Rune()) { + tv.ComposeView.ToggleCW() + return nil + } + if tv.tut.Config.Input.ComposeVisibility.Match(event.Key(), event.Rune()) { + tv.ComposeView.FocusVisibility() + return nil + } + if tv.tut.Config.Input.GlobalBack.Match(event.Key(), event.Rune()) || + tv.tut.Config.Input.GlobalExit.Match(event.Key(), event.Rune()) { + tv.ModalView.Run( + "Do you want exit the compose view?", func() { + tv.FocusMainNoHistory() + }) + return nil } return event } func (tv *TutView) InputMedia(event *tcell.EventKey) *tcell.EventKey { - switch event.Rune() { - case 'j', 'J': + if tv.tut.Config.Input.GlobalDown.Match(event.Key(), event.Rune()) { tv.ComposeView.media.Next() return nil - case 'k', 'K': + } + if tv.tut.Config.Input.GlobalUp.Match(event.Key(), event.Rune()) { tv.ComposeView.media.Prev() return nil - case 'd', 'D': + } + if tv.tut.Config.Input.MediaDelete.Match(event.Key(), event.Rune()) { tv.ComposeView.media.Delete() return nil - case 'e', 'E': + } + if tv.tut.Config.Input.MediaEditDesc.Match(event.Key(), event.Rune()) { tv.ComposeView.media.EditDesc() return nil - case 'a', 'A': + } + if tv.tut.Config.Input.MediaAdd.Match(event.Key(), event.Rune()) { tv.SetPage(MediaAddFocus) tv.ComposeView.media.SetFocus(false) return nil - case 'q', 'Q': - tv.SetPage(MediaFocus) - return nil } - switch event.Key() { - case tcell.KeyDown: - tv.ComposeView.media.Next() - return nil - case tcell.KeyUp: - tv.ComposeView.media.Prev() - return nil - case tcell.KeyEsc: + if tv.tut.Config.Input.GlobalBack.Match(event.Key(), event.Rune()) || + tv.tut.Config.Input.GlobalExit.Match(event.Key(), event.Rune()) { tv.SetPage(ComposeFocus) return nil } @@ -630,34 +579,25 @@ func (tv *TutView) InputMediaAdd(event *tcell.EventKey) *tcell.EventKey { } func (tv *TutView) InputVote(event *tcell.EventKey) *tcell.EventKey { - switch event.Rune() { - case 'j', 'J': + if tv.tut.Config.Input.GlobalDown.Match(event.Key(), event.Rune()) { tv.VoteView.Next() return nil - case 'k', 'K': + } + if tv.tut.Config.Input.GlobalUp.Match(event.Key(), event.Rune()) { tv.VoteView.Prev() return nil - case 'v', 'V': + } + if tv.tut.Config.Input.VoteVote.Match(event.Key(), event.Rune()) { tv.VoteView.Vote() return nil - case ' ': - tv.VoteView.ToggleSelect() - return nil - case 'q', 'Q': - tv.FocusMainNoHistory() - return nil } - switch event.Key() { - case tcell.KeyDown, tcell.KeyTAB: - tv.VoteView.Next() - return nil - case tcell.KeyUp, tcell.KeyBacktab: - tv.VoteView.Prev() - return nil - case tcell.KeyEnter: + if tv.tut.Config.Input.VoteSelect.Match(event.Key(), event.Rune()) || + tv.tut.Config.Input.GlobalEnter.Match(event.Key(), event.Rune()) { tv.VoteView.ToggleSelect() return nil - case tcell.KeyEsc: + } + if tv.tut.Config.Input.GlobalBack.Match(event.Key(), event.Rune()) || + tv.tut.Config.Input.GlobalExit.Match(event.Key(), event.Rune()) { tv.FocusMainNoHistory() return nil } diff --git a/ui/item_list.go b/ui/item_list.go index 15f983e..9e105a2 100644 --- a/ui/item_list.go +++ b/ui/item_list.go @@ -13,8 +13,8 @@ type List struct { func drawList(tut *Tut, data *mastodon.List, main *tview.TextView, controls *tview.TextView) { - controlItem := config.ColorKey(tut.Config, "", "O", "pen") + controlItem := config.ColorFromKey(tut.Config, tut.Config.Input.ListOpenFeed, true) - main.SetText(fmt.Sprintf("Press O or to open list %s", tview.Escape(data.Title))) + main.SetText(fmt.Sprintf("List %s", tview.Escape(data.Title))) controls.SetText(controlItem) } diff --git a/ui/item_status.go b/ui/item_status.go index 3245db2..c6b9f31 100644 --- a/ui/item_status.go +++ b/ui/item_status.go @@ -172,37 +172,37 @@ func drawStatus(tut *Tut, item api.Item, status *mastodon.Status, main *tview.Te var info []string if status.Favourited { - info = append(info, config.ColorKey(tut.Config, "Un", "F", "avorite")) + info = append(info, config.ColorFromKey(tut.Config, tut.Config.Input.StatusFavorite, false)) } else { - info = append(info, config.ColorKey(tut.Config, "", "F", "avorite")) + info = append(info, config.ColorFromKey(tut.Config, tut.Config.Input.StatusFavorite, true)) } if status.Reblogged { - info = append(info, config.ColorKey(tut.Config, "Un", "B", "oost")) + info = append(info, config.ColorFromKey(tut.Config, tut.Config.Input.StatusBoost, false)) } else { - info = append(info, config.ColorKey(tut.Config, "", "B", "oost")) + info = append(info, config.ColorFromKey(tut.Config, tut.Config.Input.StatusBoost, true)) } - info = append(info, config.ColorKey(tut.Config, "", "T", "hread")) - info = append(info, config.ColorKey(tut.Config, "", "R", "eply")) - info = append(info, config.ColorKey(tut.Config, "", "V", "iew")) - info = append(info, config.ColorKey(tut.Config, "", "U", "ser")) + info = append(info, config.ColorFromKey(tut.Config, tut.Config.Input.StatusThread, true)) + info = append(info, config.ColorFromKey(tut.Config, tut.Config.Input.StatusReply, true)) + info = append(info, config.ColorFromKey(tut.Config, tut.Config.Input.StatusViewFocus, true)) + info = append(info, config.ColorFromKey(tut.Config, tut.Config.Input.StatusUser, true)) if len(status.MediaAttachments) > 0 { - info = append(info, config.ColorKey(tut.Config, "", "M", "edia")) + info = append(info, config.ColorFromKey(tut.Config, tut.Config.Input.StatusMedia, true)) } _, _, _, length := item.URLs() if length > 0 { - info = append(info, config.ColorKey(tut.Config, "", "O", "pen")) + info = append(info, config.ColorFromKey(tut.Config, tut.Config.Input.StatusLinks, true)) } - info = append(info, config.ColorKey(tut.Config, "", "A", "vatar")) + info = append(info, config.ColorFromKey(tut.Config, tut.Config.Input.StatusAvatar, true)) if status.Account.ID == tut.Client.Me.ID { - info = append(info, config.ColorKey(tut.Config, "", "D", "elete")) + info = append(info, config.ColorFromKey(tut.Config, tut.Config.Input.StatusDelete, true)) } if !status.Bookmarked { - info = append(info, config.ColorKey(tut.Config, "", "S", "ave")) + info = append(info, config.ColorFromKey(tut.Config, tut.Config.Input.StatusBookmark, true)) } else { - info = append(info, config.ColorKey(tut.Config, "Un", "S", "ave")) + info = append(info, config.ColorFromKey(tut.Config, tut.Config.Input.StatusBookmark, false)) } - info = append(info, config.ColorKey(tut.Config, "", "Y", "ank")) + info = append(info, config.ColorFromKey(tut.Config, tut.Config.Input.StatusYank, true)) controlsS := strings.Join(info, " ") diff --git a/ui/item_user.go b/ui/item_user.go index fef4a99..7db93bb 100644 --- a/ui/item_user.go +++ b/ui/item_user.go @@ -84,29 +84,29 @@ func drawUser(tut *Tut, data *api.User, main *tview.TextView, controls *tview.Te var controlItems []string if tut.Client.Me.ID != user.ID { if relation.Following { - controlItems = append(controlItems, config.ColorKey(tut.Config, "Un", "F", "ollow")) + controlItems = append(controlItems, config.ColorFromKey(tut.Config, tut.Config.Input.UserFollow, false)) } else { - controlItems = append(controlItems, config.ColorKey(tut.Config, "", "F", "ollow")) + controlItems = append(controlItems, config.ColorFromKey(tut.Config, tut.Config.Input.UserFollow, true)) } if relation.Blocking { - controlItems = append(controlItems, config.ColorKey(tut.Config, "Un", "B", "lock")) + controlItems = append(controlItems, config.ColorFromKey(tut.Config, tut.Config.Input.UserBlock, false)) } else { - controlItems = append(controlItems, config.ColorKey(tut.Config, "", "B", "lock")) + controlItems = append(controlItems, config.ColorFromKey(tut.Config, tut.Config.Input.UserBlock, true)) } if relation.Muting { - controlItems = append(controlItems, config.ColorKey(tut.Config, "Un", "M", "ute")) + controlItems = append(controlItems, config.ColorFromKey(tut.Config, tut.Config.Input.UserMute, false)) } else { - controlItems = append(controlItems, config.ColorKey(tut.Config, "", "M", "ute")) + controlItems = append(controlItems, config.ColorFromKey(tut.Config, tut.Config.Input.UserMute, true)) } if len(urls) > 0 { - controlItems = append(controlItems, config.ColorKey(tut.Config, "", "O", "pen")) + controlItems = append(controlItems, config.ColorFromKey(tut.Config, tut.Config.Input.UserLinks, true)) } } if showUserControl { - controlItems = append(controlItems, config.ColorKey(tut.Config, "", "U", "ser")) + controlItems = append(controlItems, config.ColorFromKey(tut.Config, tut.Config.Input.UserUser, true)) } - controlItems = append(controlItems, config.ColorKey(tut.Config, "", "A", "vatar")) - controlItems = append(controlItems, config.ColorKey(tut.Config, "", "Y", "ank")) + controlItems = append(controlItems, config.ColorFromKey(tut.Config, tut.Config.Input.UserAvatar, true)) + controlItems = append(controlItems, config.ColorFromKey(tut.Config, tut.Config.Input.UserYank, true)) controlsS = strings.Join(controlItems, " ") ud := DisplayUserData{ diff --git a/ui/linkview.go b/ui/linkview.go index d44d1fb..43d63b8 100644 --- a/ui/linkview.go +++ b/ui/linkview.go @@ -33,8 +33,8 @@ func NewLinkView(tv *TutView) *LinkView { func linkViewUI(lv *LinkView) *tview.Flex { lv.controls.SetBorderPadding(0, 0, 1, 1) items := []string{ - config.ColorKey(lv.tutView.tut.Config, "", "O", "pen"), - config.ColorKey(lv.tutView.tut.Config, "", "Y", "ank"), + config.ColorFromKey(lv.tutView.tut.Config, lv.tutView.tut.Config.Input.LinkOpen, true), + config.ColorFromKey(lv.tutView.tut.Config, lv.tutView.tut.Config.Input.LinkYank, true), } for _, cust := range lv.tutView.tut.Config.OpenCustom.OpenCustoms { items = append(items, config.ColorKey(lv.tutView.tut.Config, "", fmt.Sprintf("%d", cust.Index), cust.Name)) diff --git a/ui/mainview.go b/ui/mainview.go index a0e76f5..dcc4774 100644 --- a/ui/mainview.go +++ b/ui/mainview.go @@ -1,6 +1,8 @@ package ui import ( + "fmt" + "github.com/RasmusLindroth/tut/config" "github.com/rivo/tview" ) @@ -50,9 +52,15 @@ func mainViewUI(mv *TutView) *tview.Flex { lp := mv.tut.Config.General.ListProportion cp := mv.tut.Config.General.ContentProportion nt.SetTextColor(mv.tut.Config.Style.Subtle) - nt.SetDynamicColors(false) - nt.SetText("[N]otifications") + parts := mv.tut.Config.Input.MainNotificationFocus.Hint + start, middle, end := "", "", "" + if len(parts) > 0 && len(parts[0]) == 3 { + start = parts[0][0] + middle = parts[0][1] + end = parts[0][2] + } + nt.SetText(tview.Escape(fmt.Sprintf("%s[%s]%s", start, middle, end))) var list *tview.Flex if mv.tut.Config.General.ListSplit == config.ListColumn { list = tview.NewFlex().SetDirection(tview.FlexColumn) diff --git a/ui/voteview.go b/ui/voteview.go index 9eb29a8..215b38f 100644 --- a/ui/voteview.go +++ b/ui/voteview.go @@ -35,8 +35,8 @@ func NewVoteView(tv *TutView) *VoteView { func voteViewUI(v *VoteView) *tview.Flex { var items []string - items = append(items, config.ColorKey(v.tutView.tut.Config, "Select ", "Space/Enter", "")) - items = append(items, config.ColorKey(v.tutView.tut.Config, "", "V", "ote")) + items = append(items, config.ColorFromKey(v.tutView.tut.Config, v.tutView.tut.Config.Input.VoteSelect, true)) + items = append(items, config.ColorFromKey(v.tutView.tut.Config, v.tutView.tut.Config.Input.VoteSelect, true)) v.controls.SetText(strings.Join(items, " ")) return tview.NewFlex().SetDirection(tview.FlexRow).