Browse Source

Support for custom keys (#136)

* custom keys
pull/139/head 1.0.3
Rasmus Lindroth 4 years ago committed by GitHub
parent
commit
c080149570
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 226
      config.example.ini
  2. 300
      config/config.go
  3. 226
      config/default_config.go
  4. 14
      config/keys.go
  5. 2
      config/load.go
  6. 2
      main.go
  7. 22
      ui/composeview.go
  8. 7
      ui/helpview.go
  9. 450
      ui/input.go
  10. 4
      ui/item_list.go
  11. 30
      ui/item_status.go
  12. 20
      ui/item_user.go
  13. 4
      ui/linkview.go
  14. 12
      ui/mainview.go
  15. 4
      ui/voteview.go

226
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"

300
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

226
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"
`

14
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)

2
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)

2
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()

22
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)

7
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
}

450
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
}

4
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 <Enter> to open list %s", tview.Escape(data.Title)))
main.SetText(fmt.Sprintf("List %s", tview.Escape(data.Title)))
controls.SetText(controlItem)
}

30
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, " ")

20
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{

4
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))

12
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)

4
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).

Loading…
Cancel
Save