Browse Source

Merge pull request #42 from Rudi9719/dev

Update v2/dev to dev
v2/dev
Gregory Rudolph 5 years ago committed by GitHub
parent
commit
5898dd617c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      .gitignore
  2. 88
      cmdConfig.go
  3. 6
      cmdDownload.go
  4. 2
      cmdEdit.go
  5. 55
      cmdExec.go
  6. 2
      cmdHelp.go
  7. 8
      cmdJoin.go
  8. 2
      cmdReply.go
  9. 116
      cmdSet.go
  10. 2
      cmdStream.go
  11. 4
      cmdUploadFile.go
  12. 2
      cmdWall.go
  13. 174
      colors.go
  14. 72
      defaultConfig.go
  15. 47
      emojiMap.go
  16. 12
      go.mod
  17. 23
      go.sum
  18. 63
      kbtui.tml
  19. 68
      kbtui.toml
  20. 79
      mage.go
  21. 158
      main.go
  22. 12
      tabComplete.go
  23. 6
      tcmdShowReactions.go
  24. 73
      types.go
  25. 40
      userConfigs.go

1
.gitignore vendored

@ -6,3 +6,4 @@ emojiList.go @@ -6,3 +6,4 @@ emojiList.go
.idea/*
.idea
*.log
.travis.yml

88
cmdConfig.go

@ -0,0 +1,88 @@ @@ -0,0 +1,88 @@
// +build !rm_basic_commands allcommands setcmd
package main
import (
"fmt"
"io/ioutil"
"os"
"github.com/pelletier/go-toml"
)
func init() {
command := Command{
Cmd: []string{"config"},
Description: "Change various settings",
Help: "",
Exec: cmdConfig,
}
RegisterCommand(command)
}
func cmdConfig(cmd []string) {
var err error
switch {
case len(cmd) == 2:
if cmd[1] == "load" {
config, err = readConfig()
if err != nil {
printError(err.Error())
return
}
printInfoF("Config file loaded: $TEXT", config.Colors.Message.Attachment.stylize(config.filepath))
return
}
case len(cmd) > 2:
if cmd[1] == "load" {
config, err = readConfig(cmd[3])
if err != nil {
printError(err.Error())
return
}
printInfoF("Config file loaded: $TEXT", config.Colors.Message.Attachment.stylize(config.filepath))
return
}
}
printError("Must pass a valid command")
}
func readConfig(filepath ...string) (*Config, error) {
var result = new(Config)
var configFile string
var env bool
// Load default config first, this way any values missing from the provided config file will remain the default value
d := []byte(defaultConfig)
toml.Unmarshal(d, result)
switch len(filepath) {
case 0:
configFile, env = os.LookupEnv("KBTUI_CFG")
if !env {
configFile = "~/.config/kbtui.toml"
if _, err := os.Stat(configFile); os.IsNotExist(err) {
configFile = "kbtui.toml"
}
}
default:
configFile = filepath[0]
if _, err := os.Stat(configFile); os.IsNotExist(err) {
return result, fmt.Errorf("Unable to load config: %s not found", configFile)
}
}
f, err := ioutil.ReadFile(configFile)
if err != nil {
f = []byte(defaultConfig)
}
err = toml.Unmarshal(f, result)
if err != nil {
return result, err
}
result.filepath = configFile
return result, nil
}

6
cmdDownload.go

@ -21,7 +21,7 @@ func init() { @@ -21,7 +21,7 @@ func init() {
func cmdDownloadFile(cmd []string) {
if len(cmd) < 2 {
printInfo(fmt.Sprintf("%s%s $messageId $fileName - Download a file to user's downloadpath", cmdPrefix, cmd[0]))
printInfo(fmt.Sprintf("%s%s $messageId $fileName - Download a file to user's downloadpath", config.Basics.CmdPrefix, cmd[0]))
return
}
messageID, err := strconv.Atoi(cmd[1])
@ -46,8 +46,8 @@ func cmdDownloadFile(cmd []string) { @@ -46,8 +46,8 @@ func cmdDownloadFile(cmd []string) {
fileName = api.Result.Messages[0].Msg.Content.Attachment.Object.Filename
}
_, err = chat.Download(messageID, fmt.Sprintf("%s/%s", downloadPath, fileName))
channelName := messageLinkKeybaseColor.stylize(channel.Name)
_, err = chat.Download(messageID, fmt.Sprintf("%s/%s", config.Basics.DownloadPath, fileName))
channelName := config.Colors.Message.LinkKeybase.stylize(channel.Name)
if err != nil {
printErrorF(fmt.Sprintf("There was an error downloading %s from $TEXT", fileName), channelName)
} else {

2
cmdEdit.go

@ -26,6 +26,8 @@ func cmdEdit(cmd []string) { @@ -26,6 +26,8 @@ func cmdEdit(cmd []string) {
if len(cmd) == 2 {
messageID, _ = strconv.Atoi(cmd[1])
} else if lastMessage.ID != 0 {
message, _ := chat.ReadMessage(lastMessage.ID)
lastMessage.Type = message.Result.Messages[0].Msg.Content.Type
if lastMessage.Type != "text" {
printError("Last message isn't editable (is it an edit?)")
return

55
cmdExec.go

@ -0,0 +1,55 @@ @@ -0,0 +1,55 @@
// +build !rm_basic_commands allcommands execcmd
package main
import (
"fmt"
"strings"
)
func init() {
command := Command{
Cmd: []string{"exec", "ex"},
Description: "$keybase args - executes keybase $args and returns the output",
Help: "",
Exec: cmdExec,
}
RegisterCommand(command)
}
func cmdExec(cmd []string) {
l := len(cmd)
switch {
case l >= 2:
if cmd[1] == "keybase" {
// if the user types /exec keybase wallet list
// only send ["wallet", "list"]
runKeybaseExec(cmd[2:])
} else {
// send everything except the command
runKeybaseExec(cmd[1:])
}
case l == 1:
fallthrough
default:
printExecHelp()
}
}
func runKeybaseExec(args []string) {
outputBytes, err := k.Exec(args...)
if err != nil {
printToView("Feed", fmt.Sprintf("Exec error: %+v", err))
} else {
channel.Name = ""
// unjoin the chat
clearView("Chat")
setViewTitle("Input", fmt.Sprintf(" /exec %s ", strings.Join(args, " ")))
output := string(outputBytes)
printToView("Chat", fmt.Sprintf("%s", output))
}
}
func printExecHelp() {
printInfo(fmt.Sprintf("To execute a keybase command use %sexec <keybase args>", config.Basics.CmdPrefix))
}

2
cmdHelp.go

@ -25,7 +25,7 @@ func cmdHelp(cmd []string) { @@ -25,7 +25,7 @@ func cmdHelp(cmd []string) {
if len(cmd) == 1 {
sort.Strings(baseCommands)
for _, c := range baseCommands {
helpText = fmt.Sprintf("%s%s%s\t\t%s\n", helpText, cmdPrefix, c, commands[c].Description)
helpText = fmt.Sprintf("%s%s%s\t\t%s\n", helpText, config.Basics.CmdPrefix, c, commands[c].Description)
}
if len(typeCommands) > 0 {
for c := range typeCommands {

8
cmdJoin.go

@ -41,12 +41,14 @@ func cmdJoin(cmd []string) { @@ -41,12 +41,14 @@ func cmdJoin(cmd []string) {
channel.TopicName = ""
channel.MembersType = keybase.USER
}
printInfoF("You are joining: $TEXT", messageLinkKeybaseColor.stylize(joinedName))
printInfoF("You are joining: $TEXT", config.Colors.Message.LinkKeybase.stylize(joinedName))
clearView("Chat")
setViewTitle("Input", fmt.Sprintf(" %s ", joinedName))
lastChat = joinedName
autoScrollView("Chat")
go populateChat()
default:
printInfo(fmt.Sprintf("To join a team use %sjoin <team> <channel>", cmdPrefix))
printInfo(fmt.Sprintf("To join a PM use %sjoin <user>", cmdPrefix))
printInfo(fmt.Sprintf("To join a team use %sjoin <team> <channel>", config.Basics.CmdPrefix))
printInfo(fmt.Sprintf("To join a PM use %sjoin <user>", config.Basics.CmdPrefix))
}
}

2
cmdReply.go

@ -22,7 +22,7 @@ func init() { @@ -22,7 +22,7 @@ func init() {
func cmdReply(cmd []string) {
chat := k.NewChat(channel)
if len(cmd) < 2 {
printInfo(fmt.Sprintf("%s%s $ID - Reply to message $ID", cmdPrefix, cmd[0]))
printInfo(fmt.Sprintf("%s%s $ID - Reply to message $ID", config.Basics.CmdPrefix, cmd[0]))
return
}
messageID, err := strconv.Atoi(cmd[1])

116
cmdSet.go

@ -1,116 +0,0 @@ @@ -1,116 +0,0 @@
// +build !rm_basic_commands allcommands setcmd
package main
import (
"fmt"
"strings"
"github.com/pelletier/go-toml"
)
func init() {
command := Command{
Cmd: []string{"set", "config"},
Description: "Change various settings",
Help: "",
Exec: cmdSet,
}
RegisterCommand(command)
}
func printSetting(cmd []string) {
switch cmd[1] {
case "load":
loadFromToml()
printInfo("Loading config from toml")
case "downloadPath":
printInfo(fmt.Sprintf("Setting for %s -> %s", cmd[1], downloadPath))
case "outputFormat":
printInfo(fmt.Sprintf("Setting for %s -> %s", cmd[1], outputFormat))
case "dateFormat":
printInfo(fmt.Sprintf("Setting for %s -> %s", cmd[1], dateFormat))
case "timeFormat":
printInfo(fmt.Sprintf("Setting for %s -> %s", cmd[1], timeFormat))
case "cmdPrefix":
printInfo(fmt.Sprintf("Setting for %s -> %s", cmd[1], cmdPrefix))
default:
printError(fmt.Sprintf("Unknown config value %s", cmd[1]))
}
}
func cmdSet(cmd []string) {
if len(cmd) < 2 {
printError("No config value specified")
return
}
if len(cmd) < 3 {
printSetting(cmd)
return
}
switch cmd[1] {
case "downloadPath":
if len(cmd) != 3 {
printError("Invalid download path.")
}
downloadPath = cmd[2]
case "outputFormat":
outputFormat = strings.Join(cmd[1:], " ")
case "dateFormat":
dateFormat = strings.Join(cmd[1:], " ")
case "timeFormat":
timeFormat = strings.Join(cmd[1:], " ")
case "cmdPrefix":
cmdPrefix = cmd[2]
default:
printError(fmt.Sprintf("Unknown config value %s", cmd[1]))
}
}
func loadFromToml() {
config, err := toml.LoadFile("kbtui.tml")
if err != nil {
printError(fmt.Sprintf("Could not read config file: %+v", err))
return
}
colorless = config.GetDefault("Basics.colorless", false).(bool)
if config.Has("Basics.colorless") {
colorless = config.Get("Basics.colorless").(bool)
}
if config.Has("Basics.downloadPath") {
downloadPath = config.Get("Basics.downloadPath").(string)
}
if config.Has("Basics.cmdPrefix") {
cmdPrefix = config.Get("Basics.cmdPrefix").(string)
}
if config.Has("Formatting.outputFormat") {
outputFormat = config.Get("Formatting.outputFormat").(string)
}
if config.Has("Formatting.dateFormat") {
dateFormat = config.Get("Formatting.dateFormat").(string)
}
if config.Has("Formatting.timeFormat") {
timeFormat = config.Get("Formatting.timeFormat").(string)
}
channelsColor = styleFromConfig(config, "channels.basic")
channelsHeaderColor = styleFromConfig(config, "channels.header")
channelUnreadColor = styleFromConfig(config, "channels.unread")
mentionColor = styleFromConfig(config, "message.mention")
messageHeaderColor = styleFromConfig(config, "message.header")
messageIDColor = styleFromConfig(config, "message.id")
messageTimeColor = styleFromConfig(config, "message.time")
messageSenderDefaultColor = styleFromConfig(config, "message.sender_default")
messageSenderDeviceColor = styleFromConfig(config, "message.sender_device")
messageBodyColor = styleFromConfig(config, "message.body")
messageAttachmentColor = styleFromConfig(config, "message.attachment")
messageLinkURLColor = styleFromConfig(config, "message.link_url")
messageLinkKeybaseColor = styleFromConfig(config, "message.link_keybase")
messageReactionColor = styleFromConfig(config, "message.reaction")
messageCodeColor = styleFromConfig(config, "message.code")
feedColor = styleFromConfig(config, "feed.basic")
errorColor = styleFromConfig(config, "feed.error")
RunCommand("clean")
}

2
cmdStream.go

@ -22,6 +22,6 @@ func cmdStream(cmd []string) { @@ -22,6 +22,6 @@ func cmdStream(cmd []string) {
channel.Name = ""
printInfo("You are now viewing the formatted stream")
setViewTitle("Input", fmt.Sprintf(" Stream - Not in a chat. %sj to join ", cmdPrefix))
setViewTitle("Input", fmt.Sprintf(" Stream - Not in a chat. %sj to join ", config.Basics.CmdPrefix))
clearView("Chat")
}

4
cmdUploadFile.go

@ -21,7 +21,7 @@ func init() { @@ -21,7 +21,7 @@ func init() {
func cmdUploadFile(cmd []string) {
if len(cmd) < 2 {
printInfo(fmt.Sprintf("%s%s $filePath $fileName - Upload file from absolute path with optional name", cmdPrefix, cmd[0]))
printInfo(fmt.Sprintf("%s%s $filePath $fileName - Upload file from absolute path with optional name", config.Basics.CmdPrefix, cmd[0]))
return
}
filePath := cmd[1]
@ -40,7 +40,7 @@ func cmdUploadFile(cmd []string) { @@ -40,7 +40,7 @@ func cmdUploadFile(cmd []string) {
}
chat := k.NewChat(channel)
_, err := chat.Upload(fileName, filePath)
channelName := messageLinkKeybaseColor.stylize(channel.Name).string()
channelName := config.Colors.Message.LinkKeybase.stylize(channel.Name).string()
if err != nil {
printError(fmt.Sprintf("There was an error uploading %s to %s\n%+v", filePath, channelName, err))
} else {

2
cmdWall.go

@ -65,7 +65,7 @@ func cmdPopulateWall(cmd []string) { @@ -65,7 +65,7 @@ func cmdPopulateWall(cmd []string) {
return
}
printInfoF("Displaying public messages for user $TEXT", messageLinkKeybaseColor.stylize(requestedUsers))
printInfoF("Displaying public messages for user $TEXT", config.Colors.Message.LinkKeybase.stylize(requestedUsers))
for _, chann := range users {
chat := k.NewChat(chann)
api, err := chat.Read()

174
colors.go

@ -2,16 +2,12 @@ package main @@ -2,16 +2,12 @@ package main
import (
"fmt"
"github.com/pelletier/go-toml"
"regexp"
"strings"
)
// Begin Colors
type color int
const (
black color = iota
black int = iota
red
green
yellow
@ -19,103 +15,88 @@ const ( @@ -19,103 +15,88 @@ const (
magenta
cyan
grey
normal color = -1
normal int = -1
)
func colorFromString(s string) color {
s = strings.ToLower(s)
switch s {
case "black":
return black
case "red":
return red
case "green":
return green
case "yellow":
return yellow
case "purple":
return purple
case "magenta":
return magenta
case "cyan":
return cyan
case "grey":
return grey
case "normal":
var colorMapString = map[string]int{
"black": black,
"red": red,
"green": green,
"yellow": yellow,
"purple": purple,
"magenta": magenta,
"cyan": cyan,
"grey": grey,
"normal": normal,
}
var colorMapInt = map[int]string{
black: "black",
red: "red",
green: "green",
yellow: "yellow",
purple: "purple",
magenta: "magenta",
cyan: "cyan",
grey: "grey",
normal: "normal",
}
func colorFromString(color string) int {
var result int
color = strings.ToLower(color)
result, ok := colorMapString[color]
if !ok {
return normal
default:
printError(fmt.Sprintf("color `%s` cannot be parsed.", s))
}
return normal
}
// Style struct for specializing the style/color of a stylize
type Style struct {
foregroundColor color
backgroundColor color
bold bool
italic bool // Currently not supported by the UI library
underline bool
strikethrough bool // Currently not supported by the UI library
inverse bool
return result
}
var basicStyle = Style{normal, normal, false, false, false, false, false}
func styleFromConfig(config *toml.Tree, key string) Style {
key = "Colors." + key + "."
style := basicStyle
if config.Has(key + "foreground") {
style = style.withForeground(colorFromString(config.Get(key + "foreground").(string)))
}
if config.Has(key + "background") {
style = style.withForeground(colorFromString(config.Get(key + "background").(string)))
}
if config.GetDefault(key+"bold", false).(bool) {
style = style.withBold()
}
if config.GetDefault(key+"italic", false).(bool) {
style = style.withItalic()
}
if config.GetDefault(key+"underline", false).(bool) {
style = style.withUnderline()
}
if config.GetDefault(key+"strikethrough", false).(bool) {
style = style.withStrikethrough()
}
if config.GetDefault(key+"inverse", false).(bool) {
style = style.withInverse()
func colorFromInt(color int) string {
var result string
result, ok := colorMapInt[color]
if !ok {
return "normal"
}
return result
}
return style
var basicStyle = Style{
Foreground: colorMapInt[normal],
Background: colorMapInt[normal],
Italic: false,
Bold: false,
Underline: false,
Strikethrough: false,
Inverse: false,
}
func (s Style) withForeground(f color) Style {
s.foregroundColor = f
func (s Style) withForeground(color int) Style {
s.Foreground = colorFromInt(color)
return s
}
func (s Style) withBackground(f color) Style {
s.backgroundColor = f
func (s Style) withBackground(color int) Style {
s.Background = colorFromInt(color)
return s
}
func (s Style) withBold() Style {
s.bold = true
s.Bold = true
return s
}
func (s Style) withInverse() Style {
s.inverse = true
s.Inverse = true
return s
}
func (s Style) withItalic() Style {
s.italic = true
s.Italic = true
return s
}
func (s Style) withStrikethrough() Style {
s.strikethrough = true
s.Strikethrough = true
return s
}
func (s Style) withUnderline() Style {
s.underline = true
s.Underline = true
return s
}
@ -123,29 +104,29 @@ func (s Style) withUnderline() Style { @@ -123,29 +104,29 @@ func (s Style) withUnderline() Style {
// which essentially just adds on top. that is relevant in the case of
// bold/italic etc - it should add style - not clear.
func (s Style) toANSI() string {
if colorless {
if config.Basics.Colorless {
return ""
}
output := "\x1b[0m\x1b[0"
if s.foregroundColor != normal {
output += fmt.Sprintf(";%d", 30+s.foregroundColor)
if colorFromString(s.Foreground) != normal {
output += fmt.Sprintf(";%d", 30+colorFromString(s.Foreground))
}
if s.backgroundColor != normal {
output += fmt.Sprintf(";%d", 40+s.backgroundColor)
if colorFromString(s.Background) != normal {
output += fmt.Sprintf(";%d", 40+colorFromString(s.Background))
}
if s.bold {
if s.Bold {
output += ";1"
}
if s.italic {
if s.Italic {
output += ";3"
}
if s.underline {
if s.Underline {
output += ";4"
}
if s.inverse {
if s.Inverse {
output += ";7"
}
if s.strikethrough {
if s.Strikethrough {
output += ";9"
}
@ -195,22 +176,23 @@ func (t StyledString) replaceString(match string, value string) StyledString { @@ -195,22 +176,23 @@ func (t StyledString) replaceString(match string, value string) StyledString {
t.message = strings.Replace(t.message, match, value, -1)
return t
}
func (t StyledString) replaceRegex(match string, value StyledString) StyledString {
var re = regexp.MustCompile("(" + match + ")")
t.message = re.ReplaceAllString(t.message, value.stringFollowedByStyle(t.style))
return t
}
// Overrides current formatting
func (t StyledString) colorRegex(match string, style Style) StyledString {
re := regexp.MustCompile("(" + match + ")")
subStrings := re.FindAllString(t.message, -1)
for _, element := range subStrings {
cleanSubstring := style.stylize(removeFormatting(element))
t.message = strings.Replace(t.message, element, cleanSubstring.stringFollowedByStyle(t.style), -1)
}
locations := re.FindAllStringIndex(t.message, -1)
var newMessage string
var prevIndex int
for _, loc := range locations {
cleanSubstring := style.stylize(removeFormatting(string(t.message[loc[0]:loc[1]])))
newMessage += t.message[prevIndex:loc[0]]
newMessage += cleanSubstring.stringFollowedByStyle(t.style)
prevIndex = loc[1]
}
// Append any string after the final match
newMessage += t.message[prevIndex:len(t.message)]
t.message = newMessage
return t
// Old versionreturn t.replaceRegex(match, style.stylize(`$1`))
}
// Appends the other stylize at the end, but retains same style

72
defaultConfig.go

@ -0,0 +1,72 @@ @@ -0,0 +1,72 @@
package main
var defaultConfig = `
[basics]
download_path = "/tmp/"
colorless = false
unicode_emojis = true
# The prefix before evaluating a command
cmd_prefix = "/"
[formatting]
# BASH-like PS1 variable equivalent
output_format = "┌──[$USER@$DEVICE] [$ID] [$DATE - $TIME]\n└╼ $MSG"
output_stream_format = "┌──[$USER@$DEVICE] [$ID] [$DATE - $TIME]\n└╼ $MSG"
output_mention_format = "┌──[$USER@$DEVICE] [$ID] [$DATE - $TIME]\n└╼ $MSG"
pm_format = "PM from $USER@$DEVICE: $MSG"
# 02 = Day, Jan = Month, 06 = Year
date_format = "02Jan06"
# 15 = hours, 04 = minutes, 05 = seconds
time_format = "15:04"
[colors]
[colors.channels]
[colors.channels.basic]
foreground = "normal"
[colors.channels.header]
foreground = "magenta"
bold = true
[colors.channels.unread]
foreground = "green"
italic = true
[colors.message]
[colors.message.body]
foreground = "normal"
[colors.message.header]
foreground = "grey"
[colors.message.mention]
foreground = "green"
italic = true
bold = true
[colors.message.id]
foreground = "yellow"
[colors.message.time]
foreground = "magenta"
[colors.message.sender_default]
foreground = "cyan"
bold = true
[colors.message.sender_device]
foreground = "cyan"
[colors.message.attachment]
foreground = "red"
[colors.message.link_url]
foreground = "yellow"
[colors.message.link_keybase]
foreground = "yellow"
[colors.message.reaction]
foreground = "magenta"
bold = true
[colors.message.code]
foreground = "cyan"
background = "grey"
[colors.feed]
[colors.feed.basic]
foreground = "grey"
[colors.feed.error]
foreground = "red"
`

47
emojiMap.go

File diff suppressed because one or more lines are too long

12
go.mod

@ -0,0 +1,12 @@ @@ -0,0 +1,12 @@
module github.com/Rudi9719/kbtui
go 1.12
require (
github.com/awesome-gocui/gocui v0.6.0
github.com/magefile/mage v1.9.0
github.com/mattn/go-runewidth v0.0.5 // indirect
github.com/pelletier/go-toml v1.6.0
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
samhofi.us/x/keybase v0.0.0-20191023034410-b00e56e8dd3c
)

23
go.sum

@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/awesome-gocui/gocui v0.6.0 h1:hhDJiQC12tEsJNJ+iZBBVaSSLFYo9llFuYpQlL5JZVI=
github.com/awesome-gocui/gocui v0.6.0/go.mod h1:1QikxFaPhe2frKeKvEwZEIGia3haiOxOUXKinrv17mA=
github.com/awesome-gocui/termbox-go v0.0.0-20190427202837-c0aef3d18bcc h1:wGNpKcHU8Aadr9yOzsT3GEsFLS7HQu8HxQIomnekqf0=
github.com/awesome-gocui/termbox-go v0.0.0-20190427202837-c0aef3d18bcc/go.mod h1:tOy3o5Nf1bA17mnK4W41gD7PS3u4Cv0P0pqFcoWMy8s=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/magefile/mage v1.9.0 h1:t3AU2wNwehMCW97vuqQLtw6puppWXHO+O2MHo5a50XE=
github.com/magefile/mage v1.9.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.5 h1:jrGtp51JOKTWgvLFzfG6OtZOJcK2sEnzc/U+zw7TtbA=
github.com/mattn/go-runewidth v0.0.5/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/pelletier/go-toml v1.5.0 h1:5BakdOZdtKJ1FFk6QdL8iSGrMWsXgchNJcrnarjbmJQ=
github.com/pelletier/go-toml v1.5.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
github.com/pelletier/go-toml v1.6.0 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzIK4=
github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
samhofi.us/x/keybase v0.0.0-20191023034410-b00e56e8dd3c h1:qIKOKqYnRCx+O2IOz3a/lplrD0p1e3n/VoGOdrTGrVo=
samhofi.us/x/keybase v0.0.0-20191023034410-b00e56e8dd3c/go.mod h1:fcva80IUFyWcHtV4bBSzgKg07K6Rvuvi3GtGCLNGkyE=

63
kbtui.tml

@ -1,63 +0,0 @@ @@ -1,63 +0,0 @@
[Basics]
downloadPath = "/tmp/"
colorless = false
# The prefix before evaluating a command
cmdPrefix = "/"
[Formatting]
# BASH-like PS1 variable equivalent
outputFormat = "┌──[$USER@$DEVICE] [$ID] [$DATE - $TIME]\n└╼ $MSG"
# 02 = Day, Jan = Month, 06 = Year
dateFormat = "02Jan06"
# 15 = hours, 04 = minutes, 05 = seconds
timeFormat = "15:04"
[Colors]
[Colors.channels]
[Colors.channels.basic]
foreground = "normal"
[Colors.channels.header]
foreground = "magenta"
bold = true
[Colors.channels.unread]
foreground = "green"
italic = true
[Colors.message]
[Colors.message.body]
foreground = "normal"
[Colors.message.header]
foreground = "grey"
[Colors.message.mention]
foreground = "green"
italic = true
bold = true
[Colors.message.id]
foreground = "yellow"
[Colors.message.time]
foreground = "magenta"
[Colors.message.sender_default]
foreground = "cyan"
bold = true
[Colors.message.sender_device]
foreground = "cyan"
[Colors.message.attachment]
foreground = "red"
[Colors.message.link_url]
foreground = "yellow"
[Colors.message.link_keybase]
foreground = "yellow"
[Colors.message.reaction]
foreground = "magenta"
bold = true
[Colors.message.code]
foreground = "cyan"
background = "grey"
[Colors.feed]
[Colors.feed.basic]
foreground = "grey"
[Colors.feed.error]
foreground = "red"

68
kbtui.toml

@ -0,0 +1,68 @@ @@ -0,0 +1,68 @@
[basics]
download_path = "/tmp/"
colorless = false
unicode_emojis = true
# The prefix before evaluating a command
cmd_prefix = "/"
[formatting]
# BASH-like PS1 variable equivalent
output_format = "┌──[$USER@$DEVICE] [$ID] [$DATE - $TIME]\n└╼ $MSG"
output_stream_format = "┌──[$USER@$DEVICE] [$ID] [$DATE - $TIME]\n└╼ $MSG"
output_mention_format = "┌──[$USER@$DEVICE] [$ID] [$DATE - $TIME]\n└╼ $MSG"
pm_format = "PM from $USER@$DEVICE: $MSG"
# 02 = Day, Jan = Month, 06 = Year
date_format = "02Jan06"
# 15 = hours, 04 = minutes, 05 = seconds
time_format = "15:04"
[colors]
[colors.channels]
[colors.channels.basic]
foreground = "normal"
[colors.channels.header]
foreground = "magenta"
bold = true
[colors.channels.unread]
foreground = "green"
italic = true
[colors.message]
[colors.message.body]
foreground = "normal"
[colors.message.header]
foreground = "grey"
[colors.message.mention]
foreground = "green"
italic = true
bold = true
[colors.message.id]
foreground = "yellow"
[colors.message.time]
foreground = "magenta"
[colors.message.sender_default]
foreground = "cyan"
bold = true
[colors.message.sender_device]
foreground = "cyan"
[colors.message.attachment]
foreground = "red"
[colors.message.link_url]
foreground = "yellow"
[colors.message.link_keybase]
foreground = "yellow"
[colors.message.reaction]
foreground = "magenta"
bold = true
[colors.message.code]
foreground = "cyan"
background = "grey"
[colors.feed]
[colors.feed.basic]
foreground = "grey"
[colors.feed.error]
foreground = "red"

79
mage.go

@ -3,65 +3,13 @@ @@ -3,65 +3,13 @@
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"strings"
"github.com/magefile/mage/mg"
"github.com/magefile/mage/sh"
)
// emoji related constants
const emojiList = "https://raw.githubusercontent.com/CodeFreezr/emojo/master/db/v5/emoji-v5.json"
const emojiFileName = "emojiList.go"
// json parsing structure
type emoji struct {
Num int `json:"No"`
Emoji string `json:"Emoji"`
Category string `json:"Category"`
SubCategory string `json:"SubCategory"`
Unicode string `json:"Unicode"`
Name string `json:"Name"`
Tags string `json:"Tags"`
Shortcode string `json:"Shortcode"`
}
// This func downloaded and parses the emojis from online into a slice of all shortnames
// to be used as a lookup for tab completion for emojis
// this way the pull from GitHub only has to be done at build time.
func createEmojiSlice() ([]string, error) {
result, err := http.Get(emojiList)
if err != nil {
return nil, err
}
defer result.Body.Close()
emojiList, err := ioutil.ReadAll(result.Body)
if err != nil {
return nil, err
}
var emojis []emoji
if err := json.Unmarshal(emojiList, &emojis); err != nil {
return nil, err
}
var emojiSlice []string
for _, emj := range emojis {
if len(emj.Shortcode) == 0 || strings.Contains(emj.Shortcode, "_tone") {
// dont add them
continue
}
emojiSlice = append(emojiSlice, emj.Shortcode)
}
return emojiSlice, nil
}
func getRemotePackages() error {
var packages = []string{
"samhofi.us/x/keybase",
@ -87,28 +35,6 @@ func exit(err error) { @@ -87,28 +35,6 @@ func exit(err error) {
}
}
// Build kbtui with emoji lookup support
func BuildEmoji() error {
mg.Deps(getRemotePackages)
emojis, err := createEmojiSlice()
if err != nil {
return err
}
f, err := os.Create(emojiFileName)
if err != nil {
return err
}
defer f.Close()
fileContent := fmt.Sprintf("package main\n\nvar emojiSlice = %#v", emojis)
_, err = f.WriteString(fileContent)
if err != nil {
return err
}
f.Sync()
return nil
}
// Build kbtui with just the basic commands.
func Build() {
mg.Deps(getRemotePackages)
@ -157,7 +83,7 @@ func BuildAllCommands() { @@ -157,7 +83,7 @@ func BuildAllCommands() {
// Build kbtui with all Commands and TypeCommands enabled.
func BuildAllCommandsT() {
mg.Deps(getRemotePackages)
if err := sh.Run("go", "build", "-tags", "type_commands,allcommands"); err != nil {
if err := sh.Run("go", "build", "-tags", "type_commands allcommands"); err != nil {
defer func() {
exit(err)
}()
@ -167,8 +93,7 @@ func BuildAllCommandsT() { @@ -167,8 +93,7 @@ func BuildAllCommandsT() {
// Build kbtui with beta functionality
func BuildBeta() {
mg.Deps(getRemotePackages)
mg.Deps(BuildEmoji)
if err := sh.Run("go", "build", "-tags", "allcommands,showreactionscmd,emojiList,tabcompletion"); err != nil {
if err := sh.Run("go", "build", "-tags", "allcommands showreactionscmd tabcompletion execcmd"); err != nil {
defer func() {
exit(err)
}()

158
main.go

@ -22,9 +22,12 @@ var ( @@ -22,9 +22,12 @@ var (
channels []keybase.Channel
stream = false
lastMessage keybase.ChatAPI
lastChat = ""
g *gocui.Gui
)
var config *Config
func main() {
if !k.LoggedIn {
fmt.Println("You are not logged in.")
@ -37,6 +40,7 @@ func main() { @@ -37,6 +40,7 @@ func main() {
}
defer g.Close()
g.SetManagerFunc(layout)
RunCommand("config", "load")
go populateList()
go updateChatWindow()
if len(os.Args) > 1 {
@ -81,7 +85,7 @@ func layout(g *gocui.Gui) error { @@ -81,7 +85,7 @@ func layout(g *gocui.Gui) error {
chatView.Autoscroll = true
chatView.Wrap = true
welcomeText := basicStyle.stylize("Welcome $USER!\n\nYour chats will appear here.\nSupported commands are as follows:\n")
welcomeText = welcomeText.replace("$USER", mentionColor.stylize(k.Username))
welcomeText = welcomeText.replace("$USER", config.Colors.Message.Mention.stylize(k.Username))
fmt.Fprintln(chatView, welcomeText.string())
RunCommand("help")
}
@ -94,7 +98,7 @@ func layout(g *gocui.Gui) error { @@ -94,7 +98,7 @@ func layout(g *gocui.Gui) error {
}
inputView.Editable = true
inputView.Wrap = true
inputView.Title = fmt.Sprintf(" Not in a chat - write `%sj` to join", cmdPrefix)
inputView.Title = fmt.Sprintf(" Not in a chat - write `%sj` to join", config.Basics.CmdPrefix)
g.Cursor = true
}
if listView, err4 := g.SetView("List", 0, 0, maxX/2-maxX/3-1, maxY-1, 0); err4 != nil {
@ -106,7 +110,68 @@ func layout(g *gocui.Gui) error { @@ -106,7 +110,68 @@ func layout(g *gocui.Gui) error {
}
return nil
}
func scrollViewUp(v *gocui.View) error {
scrollView(v, -1)
return nil
}
func scrollViewDown(v *gocui.View) error {
scrollView(v, 1)
return nil
}
func scrollView(v *gocui.View, delta int) error {
if v != nil {
_, y := v.Size()
ox, oy := v.Origin()
if oy+delta > strings.Count(v.ViewBuffer(), "\n")-y {
v.Autoscroll = true
} else {
v.Autoscroll = false
if err := v.SetOrigin(ox, oy+delta); err != nil {
return err
}
}
}
return nil
}
func autoScrollView(vn string) error {
v, err := g.View(vn)
if err != nil {
return err
} else if v != nil {
v.Autoscroll = true
}
return nil
}
func initKeybindings() error {
if err := g.SetKeybinding("", gocui.KeyPgup, gocui.ModNone,
func(g *gocui.Gui, v *gocui.View) error {
cv, _ := g.View("Chat")
err := scrollViewUp(cv)
if err != nil {
return err
}
return nil
}); err != nil {
return err
}
if err := g.SetKeybinding("", gocui.KeyPgdn, gocui.ModNone,
func(g *gocui.Gui, v *gocui.View) error {
cv, _ := g.View("Chat")
err := scrollViewDown(cv)
if err != nil {
return err
}
return nil
}); err != nil {
return err
}
if err := g.SetKeybinding("", gocui.KeyEsc, gocui.ModNone,
func(g *gocui.Gui, v *gocui.View) error {
autoScrollView("Chat")
return nil
}); err != nil {
return err
}
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone,
func(g *gocui.Gui, v *gocui.View) error {
input, err := getInputString("Input")
@ -121,6 +186,13 @@ func initKeybindings() error { @@ -121,6 +186,13 @@ func initKeybindings() error {
}); err != nil {
return err
}
if err := g.SetKeybinding("", gocui.KeyCtrlZ, gocui.ModNone,
func(g *gocui.Gui, v *gocui.View) error {
cmdJoin([]string{"/join", lastChat})
return nil
}); err != nil {
return err
}
if err := g.SetKeybinding("Edit", gocui.KeyCtrlC, gocui.ModNone,
func(g *gocui.Gui, v *gocui.View) error {
popupView("Chat")
@ -254,7 +326,7 @@ func printError(message string) { @@ -254,7 +326,7 @@ func printError(message string) {
printErrorF(message)
}
func printErrorF(message string, parts ...StyledString) {
printToView("Feed", errorColor.sprintf(removeFormatting(message), parts...).string())
printToView("Feed", config.Colors.Feed.Error.sprintf(removeFormatting(message), parts...).string())
}
// this removes formatting
@ -264,15 +336,19 @@ func printInfo(message string) { @@ -264,15 +336,19 @@ func printInfo(message string) {
// this removes formatting
func printInfoF(message string, parts ...StyledString) {
printToView("Feed", feedColor.sprintf(removeFormatting(message), parts...).string())
printToView("Feed", config.Colors.Feed.Basic.sprintf(removeFormatting(message), parts...).string())
}
func printToView(viewName string, message string) {
g.Update(func(g *gocui.Gui) error {
updatingView, err := g.View(viewName)
if err != nil {
return err
} else {
if config.Basics.UnicodeEmojis {
message = emojiUnicodeConvert(message)
}
fmt.Fprintf(updatingView, "%s\n", message)
}
fmt.Fprintf(updatingView, "%s\n", message)
return nil
})
}
@ -336,7 +412,7 @@ func populateChat() { @@ -336,7 +412,7 @@ func populateChat() {
}
}
printToView("Chat", actuallyPrintMe)
go populateList()
}
func populateList() {
_, maxY := g.Size()
@ -344,10 +420,10 @@ func populateList() { @@ -344,10 +420,10 @@ func populateList() {
log.Printf("%+v", err)
} else {
clearView("List")
var textBase = channelsColor.stylize("")
var recentPMs = textBase.append(channelsHeaderColor.stylize("---[PMs]---\n"))
var textBase = config.Colors.Channels.Basic.stylize("")
var recentPMs = textBase.append(config.Colors.Channels.Header.stylize("---[PMs]---\n"))
var recentPMsCount = 0
var recentChannels = textBase.append(channelsHeaderColor.stylize("---[Teams]---\n"))
var recentChannels = textBase.append(config.Colors.Channels.Header.stylize("---[Teams]---\n"))
var recentChannelsCount = 0
for _, s := range testVar.Result.Conversations {
channels = append(channels, s.Channel)
@ -356,7 +432,7 @@ func populateList() { @@ -356,7 +432,7 @@ func populateList() {
if recentChannelsCount <= ((maxY - 2) / 3) {
channel := fmt.Sprintf("%s\n\t#%s\n", s.Channel.Name, s.Channel.TopicName)
if s.Unread {
recentChannels = recentChannels.append(channelUnreadColor.stylize("*" + channel))
recentChannels = recentChannels.append(config.Colors.Channels.Unread.stylize("*" + channel))
} else {
recentChannels = recentChannels.appendString(channel)
}
@ -366,7 +442,7 @@ func populateList() { @@ -366,7 +442,7 @@ func populateList() {
if recentPMsCount <= ((maxY - 2) / 3) {
pmName := fmt.Sprintf("%s\n", cleanChannelName(s.Channel.Name))
if s.Unread {
recentPMs = recentPMs.append(channelUnreadColor.stylize("*" + pmName))
recentPMs = recentPMs.append(config.Colors.Channels.Unread.stylize("*" + pmName))
} else {
recentPMs = recentPMs.appendString(pmName)
}
@ -383,35 +459,35 @@ func populateList() { @@ -383,35 +459,35 @@ func populateList() {
// Formatting
func formatMessageBody(body string) StyledString {
output := messageBodyColor.stylize(body)
output := config.Colors.Message.Body.stylize(body)
output = colorReplaceMentionMe(output)
output = output.colorRegex(`_[^_]*_`, messageBodyColor.withItalic())
output = output.colorRegex(`~[^~]*~`, messageBodyColor.withStrikethrough())
output = output.colorRegex(`@[\w_]*(\.[\w_]+)*`, messageLinkKeybaseColor)
output = output.colorRegex(`_[^_]*_`, config.Colors.Message.Body.withItalic())
output = output.colorRegex(`~[^~]*~`, config.Colors.Message.Body.withStrikethrough())
output = output.colorRegex(`@[\w_]*(\.[\w_]+)*`, config.Colors.Message.LinkKeybase)
// TODO change how bold, italic etc works, so it uses boldOn boldOff ([1m and [22m)
output = output.colorRegex(`\*[^\*]*\*`, messageBodyColor.withBold())
output = output.replaceString("```", "<code>")
output = output.colorRegex(`\*[^\*]*\*`, config.Colors.Message.Body.withBold())
output = output.replaceString("```", "\n<code>\n")
// TODO make background color cover whole line
output = output.colorRegex("<code>(.*\n)*<code>", messageCodeColor)
output = output.colorRegex("`[^`]*`", messageCodeColor)
output = output.colorRegex("<code>(.*\n)*<code>", config.Colors.Message.Code)
output = output.colorRegex("`[^`]*`", config.Colors.Message.Code)
// mention URL
output = output.colorRegex(`(https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*))`, messageLinkURLColor)
output = output.colorRegex(`(https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*))`, config.Colors.Message.LinkURL)
return output
}
// TODO use this more
func formatChannel(ch keybase.Channel) StyledString {
return messageLinkKeybaseColor.stylize(fmt.Sprintf("@%s#%s", ch.Name, ch.TopicName))
return config.Colors.Message.LinkKeybase.stylize(fmt.Sprintf("@%s#%s", ch.Name, ch.TopicName))
}
func colorReplaceMentionMe(msg StyledString) StyledString {
return msg.colorRegex("(@?"+k.Username+")", mentionColor)
return msg.colorRegex(`(@?\b`+k.Username+`\b)`, config.Colors.Message.Mention)
}
func colorUsername(username string) StyledString {
var color = messageSenderDefaultColor
var color = config.Colors.Message.SenderDefault
if username == k.Username {
color = mentionColor
color = config.Colors.Message.Mention
}
return color.stylize(username)
}
@ -422,27 +498,27 @@ func cleanChannelName(c string) string { @@ -422,27 +498,27 @@ func cleanChannelName(c string) string {
}
func formatMessage(api keybase.ChatAPI, formatString string) string {
ret := messageHeaderColor.stylize("")
ret := config.Colors.Message.Header.stylize("")
msgType := api.Msg.Content.Type
switch msgType {
case "text", "attachment":
ret = messageHeaderColor.stylize(formatString)
ret = config.Colors.Message.Header.stylize(formatString)
tm := time.Unix(int64(api.Msg.SentAt), 0)
var msg = formatMessageBody(api.Msg.Content.Text.Body)
if msgType == "attachment" {
msg = messageBodyColor.stylize("$TITLE\n$FILE")
msg = config.Colors.Message.Body.stylize("$TITLE\n$FILE")
attachment := api.Msg.Content.Attachment
msg = msg.replaceString("$TITLE", attachment.Object.Title)
msg = msg.replace("$FILE", messageAttachmentColor.stylize(fmt.Sprintf("[Attachment: %s]", attachment.Object.Filename)))
msg = msg.replace("$FILE", config.Colors.Message.Attachment.stylize(fmt.Sprintf("[Attachment: %s]", attachment.Object.Filename)))
}
user := colorUsername(api.Msg.Sender.Username)
device := messageSenderDeviceColor.stylize(api.Msg.Sender.DeviceName)
msgID := messageIDColor.stylize(fmt.Sprintf("%d", api.Msg.ID))
date := messageTimeColor.stylize(tm.Format(dateFormat))
msgTime := messageTimeColor.stylize(tm.Format(timeFormat))
device := config.Colors.Message.SenderDevice.stylize(api.Msg.Sender.DeviceName)
msgID := config.Colors.Message.ID.stylize(fmt.Sprintf("%d", api.Msg.ID))
date := config.Colors.Message.Time.stylize(tm.Format(config.Formatting.DateFormat))
msgTime := config.Colors.Message.Time.stylize(tm.Format(config.Formatting.TimeFormat))
channelName := messageIDColor.stylize(fmt.Sprintf("@%s#%s", api.Msg.Channel.Name, api.Msg.Channel.TopicName))
channelName := config.Colors.Message.ID.stylize(fmt.Sprintf("@%s#%s", api.Msg.Channel.Name, api.Msg.Channel.TopicName))
ret = ret.replace("$MSG", msg)
ret = ret.replace("$USER", user)
ret = ret.replace("$DEVICE", device)
@ -454,9 +530,9 @@ func formatMessage(api keybase.ChatAPI, formatString string) string { @@ -454,9 +530,9 @@ func formatMessage(api keybase.ChatAPI, formatString string) string {
return ret.string()
}
func formatOutput(api keybase.ChatAPI) string {
format := outputFormat
format := config.Formatting.OutputFormat
if stream {
format = outputStreamFormat
format = config.Formatting.OutputStreamFormat
}
return formatMessage(api, format)
}
@ -484,7 +560,7 @@ func handleMessage(api keybase.ChatAPI) { @@ -484,7 +560,7 @@ func handleMessage(api keybase.ChatAPI) {
if m.Text == k.Username {
// We are in a team
if topicName != channel.TopicName {
printInfo(formatMessage(api, mentionFormat))
printInfo(formatMessage(api, config.Formatting.OutputMentionFormat))
fmt.Print("\a")
}
@ -493,7 +569,7 @@ func handleMessage(api keybase.ChatAPI) { @@ -493,7 +569,7 @@ func handleMessage(api keybase.ChatAPI) {
}
} else {
if msgSender != channel.Name {
printInfo(formatMessage(api, pmFormat))
printInfo(formatMessage(api, config.Formatting.PMFormat))
fmt.Print("\a")
}
@ -511,7 +587,7 @@ func handleMessage(api keybase.ChatAPI) { @@ -511,7 +587,7 @@ func handleMessage(api keybase.ChatAPI) {
if api.Msg.Channel.MembersType == keybase.TEAM {
printToView("Chat", formatOutput(api))
} else {
printToView("Chat", formatMessage(api, pmFormat))
printToView("Chat", formatMessage(api, config.Formatting.PMFormat))
}
}
} else {
@ -545,8 +621,8 @@ func handleInput(viewName string) error { @@ -545,8 +621,8 @@ func handleInput(viewName string) error {
if inputString == "" {
return nil
}
if strings.HasPrefix(inputString, cmdPrefix) {
cmd := deleteEmpty(strings.Split(inputString[len(cmdPrefix):], " "))
if strings.HasPrefix(inputString, config.Basics.CmdPrefix) {
cmd := deleteEmpty(strings.Split(inputString[len(config.Basics.CmdPrefix):], " "))
if len(cmd) < 1 {
return nil
}
@ -565,6 +641,7 @@ func handleInput(viewName string) error { @@ -565,6 +641,7 @@ func handleInput(viewName string) error {
cmd[0] = inputString[:1]
RunCommand(cmd...)
} else {
inputString = resolveRootEmojis(inputString)
go sendChat(inputString)
}
// restore any tab completion view titles on input commit
@ -576,6 +653,7 @@ func handleInput(viewName string) error { @@ -576,6 +653,7 @@ func handleInput(viewName string) error {
return nil
}
func sendChat(message string) {
autoScrollView("Chat")
chat := k.NewChat(channel)
_, err := chat.Send(message)
if err != nil {

12
tabComplete.go

@ -66,7 +66,8 @@ func handleTab(viewName string) error { @@ -66,7 +66,8 @@ func handleTab(viewName string) error {
// Main tab completion functions
func getEmojiTabCompletionSlice(inputWord string) []string {
// use the emojiSlice from emojiList.go and filter it for the input word
resultSlice := filterStringSlice(emojiSlice, inputWord)
//resultSlice := filterStringSlice(emojiSlice, inputWord)
resultSlice := filterEmojiMap(emojiMap, inputWord)
return resultSlice
}
func getChannelTabCompletionSlice(inputWord string) []string {
@ -152,6 +153,15 @@ func filterStringSlice(ss []string, fv string) []string { @@ -152,6 +153,15 @@ func filterStringSlice(ss []string, fv string) []string {
}
return rs
}
func filterEmojiMap(eMap map[string]emojiData, fv string) []string {
var rs []string
for k, _ := range eMap {
if strings.HasPrefix(k, fv) {
rs = append(rs, k)
}
}
return rs
}
func longestCommonPrefix(ss []string) string {
// cover the case where the slice has no or one members
switch len(ss) {

6
tcmdShowReactions.go

@ -22,9 +22,9 @@ func init() { @@ -22,9 +22,9 @@ func init() {
func tcmdShowReactions(m keybase.ChatAPI) {
team := false
user := colorUsername(m.Msg.Sender.Username)
id := messageIDColor.stylize(fmt.Sprintf("%d", m.Msg.Content.Reaction.M))
reaction := messageReactionColor.stylize(m.Msg.Content.Reaction.B)
where := messageLinkKeybaseColor.stylize("a PM")
id := config.Colors.Message.ID.stylize(fmt.Sprintf("%d", m.Msg.Content.Reaction.M))
reaction := config.Colors.Message.Reaction.stylize(m.Msg.Content.Reaction.B)
where := config.Colors.Message.LinkKeybase.stylize("a PM")
if m.Msg.Channel.MembersType == keybase.TEAM {
team = true
where = formatChannel(m.Msg.Channel)

73
types.go

@ -17,3 +17,76 @@ type TypeCommand struct { @@ -17,3 +17,76 @@ type TypeCommand struct {
Description string // A short description of the command
Exec func(keybase.ChatAPI) // A function that takes a raw chat message as input
}
// Config holds user-configurable values
type Config struct {
filepath string `toml:"-"` // filepath is not stored in the config file, but is written to the Config struct so it's known where the config was loaded from
Basics Basics `toml:"basics"`
Formatting Formatting `toml:"formatting"`
Colors Colors `toml:"colors"`
}
// Basics holds the 'basics' section of the config file
type Basics struct {
DownloadPath string `toml:"download_path"`
Colorless bool `toml:"colorless"`
CmdPrefix string `toml:"cmd_prefix"`
UnicodeEmojis bool `toml:"unicode_emojis"`
}
// Formatting holds the 'formatting' section of the config file
type Formatting struct {
OutputFormat string `toml:"output_format"`
OutputStreamFormat string `toml:"output_stream_format"`
OutputMentionFormat string `toml:"output_mention_format"`
PMFormat string `toml:"pm_format"`
DateFormat string `toml:"date_format"`
TimeFormat string `toml:"time_format"`
}
// Colors holds the 'colors' section of the config file
type Colors struct {
Channels Channels `toml:"channels"`
Message Message `toml:"message"`
Feed Feed `toml:"feed"`
}
// Style holds basic style information
type Style struct {
Foreground string `toml:"foreground"`
Background string `toml:"background"`
Italic bool `toml:"italic"`
Bold bool `toml:"bold"`
Underline bool `toml:"underline"`
Strikethrough bool `toml:"strikethrough"`
Inverse bool `toml:"inverse"`
}
// Channels holds the style information for various elements of a channel
type Channels struct {
Basic Style `toml:"basic"`
Header Style `toml:"header"`
Unread Style `toml:"unread"`
}
// Message holds the style information for various elements of a message
type Message struct {
Body Style `toml:"body"`
Header Style `toml:"header"`
Mention Style `toml:"mention"`
ID Style `toml:"id"`
Time Style `toml:"time"`
SenderDefault Style `toml:"sender_default"`
SenderDevice Style `toml:"sender_device"`
Attachment Style `toml:"attachment"`
LinkURL Style `toml:"link_url"`
LinkKeybase Style `toml:"link_keybase"`
Reaction Style `toml:"reaction"`
Code Style `toml:"code"`
}
// Feed holds the style information for various elements of the feed window
type Feed struct {
Basic Style `toml:"basic"`
Error Style `toml:"error"`
}

40
userConfigs.go

@ -1,40 +0,0 @@ @@ -1,40 +0,0 @@
package main
// Path where Downloaded files will default to
var downloadPath = "/tmp/"
var colorless bool = false
var channelsColor = basicStyle
var channelUnreadColor = channelsColor.withForeground(green).withItalic()
var channelsHeaderColor = channelsColor.withForeground(magenta).withBold()
var mentionColor = basicStyle.withForeground(green)
var messageHeaderColor = basicStyle.withForeground(grey)
var messageIDColor = basicStyle.withForeground(yellow)
var messageTimeColor = basicStyle.withForeground(magenta)
var messageSenderDefaultColor = basicStyle.withForeground(cyan)
var messageSenderDeviceColor = messageSenderDefaultColor
var messageBodyColor = basicStyle
var messageAttachmentColor = basicStyle.withForeground(red)
var messageLinkURLColor = basicStyle.withForeground(yellow)
var messageLinkKeybaseColor = basicStyle.withForeground(yellow)
var messageReactionColor = basicStyle.withForeground(magenta)
var messageCodeColor = basicStyle.withBackground(grey).withForeground(cyan)
var feedColor = basicStyle.withForeground(grey)
var errorColor = basicStyle.withForeground(red)
// BASH-like PS1 variable equivalent
var outputFormat = "┌──[$USER@$DEVICE] [$ID] [$DATE - $TIME]\n└╼ $MSG"
var outputStreamFormat = "┌──[$TEAM] [$USER@$DEVICE] [$ID] [$DATE - $TIME]\n└╼ $MSG"
var mentionFormat = outputStreamFormat
var pmFormat = "PM from $USER@$DEVICE: $MSG"
// 02 = Day, Jan = Month, 06 = Year
var dateFormat = "02Jan06"
// 15 = hours, 04 = minutes, 05 = seconds
var timeFormat = "15:04"
// The prefix before evaluating a command
var cmdPrefix = "/"
Loading…
Cancel
Save