diff --git a/cmdConfig.go b/cmdConfig.go index 5d7355b..a32373d 100644 --- a/cmdConfig.go +++ b/cmdConfig.go @@ -31,7 +31,7 @@ func cmdConfig(cmd []string) { printError(err.Error()) return } - printInfoF("Config file loaded: $TEXT", config.Colors.Message.Attachment.stylize(config.filepath)) + printInfoF("Config file loaded: $TEXT", config.Colors.Feed.File.stylize(config.filepath)) return } case len(cmd) > 2: @@ -41,7 +41,7 @@ func cmdConfig(cmd []string) { printError(err.Error()) return } - printInfoF("Config file loaded: $TEXT", config.Colors.Message.Attachment.stylize(config.filepath)) + printInfoF("Config file loaded: $TEXT", config.Colors.Feed.File.stylize(config.filepath)) return } } diff --git a/cmdDownload.go b/cmdDownload.go index c78df27..0c1b54b 100644 --- a/cmdDownload.go +++ b/cmdDownload.go @@ -48,9 +48,10 @@ func cmdDownloadFile(cmd []string) { _, err = chat.Download(messageID, fmt.Sprintf("%s/%s", config.Basics.DownloadPath, fileName)) channelName := config.Colors.Message.LinkKeybase.stylize(channel.Name) + fileNameStylizied := config.Colors.Feed.File.stylize(fileName) if err != nil { - printErrorF(fmt.Sprintf("There was an error downloading %s from $TEXT", fileName), channelName) + printErrorF("There was an error downloading $TEXT from $TEXT", fileNameStylizied, channelName) } else { - printInfoF(fmt.Sprintf("Downloaded %s from $TEXT", fileName), channelName) + printInfoF("Downloaded $TEXT from $TEXT", fileNameStylizied, channelName) } } diff --git a/cmdUploadFile.go b/cmdUploadFile.go index 07d05a8..df91686 100644 --- a/cmdUploadFile.go +++ b/cmdUploadFile.go @@ -40,10 +40,11 @@ func cmdUploadFile(cmd []string) { } chat := k.NewChat(channel) _, err := chat.Upload(fileName, filePath) - channelName := config.Colors.Message.LinkKeybase.stylize(channel.Name).string() + channelName := config.Colors.Message.LinkKeybase.stylize(channel.Name) + fileNameStylized := config.Colors.Feed.File.stylize(filePath) if err != nil { - printError(fmt.Sprintf("There was an error uploading %s to %s\n%+v", filePath, channelName, err)) + printError(fmt.Sprintf("There was an error uploading %s to %s\n%+v", filePath, channel.Name, err)) } else { - printInfo(fmt.Sprintf("Uploaded %s to %s", filePath, channelName)) + printInfoF("Uploaded $TEXT to $TEXT", fileNameStylized, channelName) } } diff --git a/colors.go b/colors.go index 4782966..c5400a2 100644 --- a/colors.go +++ b/colors.go @@ -79,6 +79,7 @@ func (s Style) withBackground(color int) Style { s.Background = colorFromInt(color) return s } + func (s Style) withBold() Style { s.Bold = true return s @@ -107,30 +108,31 @@ func (s Style) toANSI() string { if config.Basics.Colorless { return "" } - output := "\x1b[0m\x1b[0" + styleSlice := []string{"0"} + if colorFromString(s.Foreground) != normal { - output += fmt.Sprintf(";%d", 30+colorFromString(s.Foreground)) + styleSlice = append(styleSlice, fmt.Sprintf("%d", 30+colorFromString(s.Foreground))) } if colorFromString(s.Background) != normal { - output += fmt.Sprintf(";%d", 40+colorFromString(s.Background)) + styleSlice = append(styleSlice, fmt.Sprintf("%d", 40+colorFromString(s.Background))) } if s.Bold { - output += ";1" + styleSlice = append(styleSlice, "1") } if s.Italic { - output += ";3" + styleSlice = append(styleSlice, "3") } if s.Underline { - output += ";4" + styleSlice = append(styleSlice, "4") } if s.Inverse { - output += ";7" + styleSlice = append(styleSlice, "7") } if s.Strikethrough { - output += ";9" + styleSlice = append(styleSlice, "9") } - return output + "m" + return "\x1b[" + strings.Join(styleSlice, ";") + "m" } // End Colors @@ -142,6 +144,12 @@ type StyledString struct { style Style } +func (ss StyledString) withStyle(style Style) StyledString { + return StyledString{ss.message, style} +} + +// TODO change StyledString to have styles at start-end indexes. + // TODO handle all formatting types func (s Style) sprintf(base string, parts ...StyledString) StyledString { text := s.stylize(removeFormatting(base)) @@ -158,51 +166,58 @@ func (s Style) sprintf(base string, parts ...StyledString) StyledString { func (s Style) stylize(msg string) StyledString { return StyledString{msg, s} } -func (t StyledString) stringFollowedByStyle(style Style) string { - return t.style.toANSI() + t.message + style.toANSI() +func (ss StyledString) stringFollowedByStyle(style Style) string { + return ss.style.toANSI() + ss.message + style.toANSI() } -func (t StyledString) string() string { - return t.stringFollowedByStyle(basicStyle) +func (ss StyledString) string() string { + return ss.stringFollowedByStyle(basicStyle) } -func (t StyledString) replace(match string, value StyledString) StyledString { - return t.replaceN(match, value, -1) +func (ss StyledString) replace(match string, value StyledString) StyledString { + return ss.replaceN(match, value, -1) } -func (t StyledString) replaceN(match string, value StyledString, n int) StyledString { - t.message = strings.Replace(t.message, match, value.stringFollowedByStyle(t.style), n) - return t +func (ss StyledString) replaceN(match string, value StyledString, n int) StyledString { + ss.message = strings.Replace(ss.message, match, value.stringFollowedByStyle(ss.style), n) + return ss } -func (t StyledString) replaceString(match string, value string) StyledString { - t.message = strings.Replace(t.message, match, value, -1) - return t +func (ss StyledString) replaceString(match string, value string) StyledString { + ss.message = strings.Replace(ss.message, match, value, -1) + return ss } // Overrides current formatting -func (t StyledString) colorRegex(match string, style Style) StyledString { - re := regexp.MustCompile("(" + match + ")") - locations := re.FindAllStringIndex(t.message, -1) +func (ss StyledString) colorRegex(match string, style Style) StyledString { + return ss.regexReplaceFunc(match, func(subString string) string { + return style.stylize(removeFormatting(subString)).stringFollowedByStyle(ss.style) + }) +} + +// Replacer function takes the current match as input and should return how the match should be preseneted instead +func (ss StyledString) regexReplaceFunc(match string, replacer func(string) string) StyledString { + re := regexp.MustCompile(match) + locations := re.FindAllStringIndex(ss.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) + newSubstring := replacer(ss.message[loc[0]:loc[1]]) + newMessage += ss.message[prevIndex:loc[0]] + newMessage += newSubstring prevIndex = loc[1] } // Append any string after the final match - newMessage += t.message[prevIndex:len(t.message)] - t.message = newMessage - return t + newMessage += ss.message[prevIndex:len(ss.message)] + ss.message = newMessage + return ss } // Appends the other stylize at the end, but retains same style -func (t StyledString) append(other StyledString) StyledString { - t.message = t.message + other.stringFollowedByStyle(t.style) - return t +func (ss StyledString) append(other StyledString) StyledString { + ss.message = ss.message + other.stringFollowedByStyle(ss.style) + return ss } -func (t StyledString) appendString(other string) StyledString { - t.message += other - return t +func (ss StyledString) appendString(other string) StyledString { + ss.message += other + return ss } // Begin Formatting diff --git a/defaultConfig.go b/defaultConfig.go index 5859e69..0471703 100644 --- a/defaultConfig.go +++ b/defaultConfig.go @@ -24,49 +24,54 @@ 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.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" + [colors.message] + [colors.message.body] + foreground = "normal" + [colors.message.header] + foreground = "grey" + bold = true + [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.quote] + foreground = "green" + [colors.message.code] + foreground = "green" + background = "grey" + [colors.feed] + [colors.feed.basic] + foreground = "grey" + [colors.feed.error] + foreground = "red" + [colors.feed.file] + foreground = "yellow" ` diff --git a/go.mod b/go.mod deleted file mode 100644 index 8be19cf..0000000 --- a/go.mod +++ /dev/null @@ -1,12 +0,0 @@ -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 -) diff --git a/go.sum b/go.sum deleted file mode 100644 index 097bc30..0000000 --- a/go.sum +++ /dev/null @@ -1,23 +0,0 @@ -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= diff --git a/kbtui.toml b/kbtui.toml index a72723f..fa98691 100644 --- a/kbtui.toml +++ b/kbtui.toml @@ -21,48 +21,53 @@ 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.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" + [colors.message] + [colors.message.body] + foreground = "normal" + [colors.message.header] + foreground = "grey" + bold = true + [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.quote] + foreground = "green" + [colors.message.code] + foreground = "green" + background = "grey" + [colors.feed] + [colors.feed.basic] + foreground = "grey" + [colors.feed.error] + foreground = "red" + [colors.feed.file] + foreground = "yellow" diff --git a/main.go b/main.go index cdf1acb..67f2a06 100644 --- a/main.go +++ b/main.go @@ -9,6 +9,7 @@ import ( "github.com/awesome-gocui/gocui" "samhofi.us/x/keybase" + "unicode/utf8" ) var ( @@ -333,6 +334,9 @@ func printErrorF(message string, parts ...StyledString) { func printInfo(message string) { printInfoF(message) } +func printInfoStyledString(message StyledString) { + printInfoF("$TEXT", message) +} // this removes formatting func printInfoF(message string, parts ...StyledString) { @@ -343,12 +347,12 @@ func printToView(viewName string, message string) { updatingView, err := g.View(viewName) if err != nil { return err - } else { - if config.Basics.UnicodeEmojis { - message = emojiUnicodeConvert(message) - } - fmt.Fprintf(updatingView, "%s\n", message) } + + if config.Basics.UnicodeEmojis { + message = emojiUnicodeConvert(message) + } + fmt.Fprintf(updatingView, "%s\n", message) return nil }) } @@ -459,21 +463,34 @@ func populateList() { // Formatting func formatMessageBody(body string) StyledString { - output := config.Colors.Message.Body.stylize(body) + message := config.Colors.Message.Body.stylize(body) - output = colorReplaceMentionMe(output) - 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) + message = colorReplaceMentionMe(message) + message = message.colorRegex(`_[^_]*_`, config.Colors.Message.Body.withItalic()) + message = message.colorRegex(`~[^~]*~`, config.Colors.Message.Body.withStrikethrough()) + message = message.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(`\*[^\*]*\*`, config.Colors.Message.Body.withBold()) - output = output.replaceString("```", "\n\n") - // TODO make background color cover whole line - output = output.colorRegex("(.*\n)*", config.Colors.Message.Code) - output = output.colorRegex("`[^`]*`", config.Colors.Message.Code) + message = message.colorRegex(`\*[^\*]*\*`, config.Colors.Message.Body.withBold()) + message = message.colorRegex(">.*$", config.Colors.Message.Quote) + message = message.regexReplaceFunc("```(.*\n)*```", func(match string) string { + maxWidth, _ := g.Size() + output := "\n" + match = strings.Replace(strings.Replace(match, "```", "", -1), "\t", " ", -1) + lines := strings.Split(match, "\n") + for _, line := range lines { + maxLineLength := maxWidth/2 + maxWidth/3 - 2 + spaces := maxLineLength - utf8.RuneCountInString(line) + for i := 1; spaces < 0; i++ { + spaces = i*maxLineLength - utf8.RuneCountInString(line) + } + output += line + strings.Repeat(" ", spaces) + "\n" + } + return config.Colors.Message.Code.stylize(output).stringFollowedByStyle(message.style) + }) + message = message.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()@:%_\+.~#?&//=]*))`, config.Colors.Message.LinkURL) - return output + message = message.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 message } // TODO use this more @@ -497,7 +514,7 @@ func cleanChannelName(c string) string { return strings.Replace(newChannelName, fmt.Sprintf(",%s", k.Username), "", 1) } -func formatMessage(api keybase.ChatAPI, formatString string) string { +func formatMessage(api keybase.ChatAPI, formatString string) StyledString { ret := config.Colors.Message.Header.stylize("") msgType := api.Msg.Content.Type switch msgType { @@ -527,14 +544,14 @@ func formatMessage(api keybase.ChatAPI, formatString string) string { ret = ret.replace("$DATE", date) ret = ret.replace("$TEAM", channelName) } - return ret.string() + return ret } func formatOutput(api keybase.ChatAPI) string { format := config.Formatting.OutputFormat if stream { format = config.Formatting.OutputStreamFormat } - return formatMessage(api, format) + return formatMessage(api, format).string() } // End formatting @@ -560,7 +577,7 @@ func handleMessage(api keybase.ChatAPI) { if m.Text == k.Username { // We are in a team if topicName != channel.TopicName { - printInfo(formatMessage(api, config.Formatting.OutputMentionFormat)) + printInfoStyledString(formatMessage(api, config.Formatting.OutputMentionFormat)) fmt.Print("\a") } @@ -569,7 +586,7 @@ func handleMessage(api keybase.ChatAPI) { } } else { if msgSender != channel.Name { - printInfo(formatMessage(api, config.Formatting.PMFormat)) + printInfoStyledString(formatMessage(api, config.Formatting.PMFormat)) fmt.Print("\a") } @@ -587,7 +604,7 @@ func handleMessage(api keybase.ChatAPI) { if api.Msg.Channel.MembersType == keybase.TEAM { printToView("Chat", formatOutput(api)) } else { - printToView("Chat", formatMessage(api, config.Formatting.PMFormat)) + printToView("Chat", formatMessage(api, config.Formatting.PMFormat).string()) } } } else { diff --git a/types.go b/types.go index 9ce3de3..1d68216 100644 --- a/types.go +++ b/types.go @@ -82,6 +82,7 @@ type Message struct { LinkURL Style `toml:"link_url"` LinkKeybase Style `toml:"link_keybase"` Reaction Style `toml:"reaction"` + Quote Style `toml:"quote"` Code Style `toml:"code"` } @@ -89,4 +90,5 @@ type Message struct { type Feed struct { Basic Style `toml:"basic"` Error Style `toml:"error"` + File Style `toml:"file"` }