@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
87
colors.go
87
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
|
||||
|
||||
@ -39,6 +39,7 @@ time_format = "15:04"
|
||||
foreground = "normal"
|
||||
[colors.message.header]
|
||||
foreground = "grey"
|
||||
bold = true
|
||||
[colors.message.mention]
|
||||
foreground = "green"
|
||||
italic = true
|
||||
@ -61,12 +62,16 @@ time_format = "15:04"
|
||||
[colors.message.reaction]
|
||||
foreground = "magenta"
|
||||
bold = true
|
||||
[colors.message.quote]
|
||||
foreground = "green"
|
||||
[colors.message.code]
|
||||
foreground = "cyan"
|
||||
foreground = "green"
|
||||
background = "grey"
|
||||
[colors.feed]
|
||||
[colors.feed.basic]
|
||||
foreground = "grey"
|
||||
[colors.feed.error]
|
||||
foreground = "red"
|
||||
[colors.feed.file]
|
||||
foreground = "yellow"
|
||||
`
|
||||
|
||||
@ -36,6 +36,7 @@ time_format = "15:04"
|
||||
foreground = "normal"
|
||||
[colors.message.header]
|
||||
foreground = "grey"
|
||||
bold = true
|
||||
[colors.message.mention]
|
||||
foreground = "green"
|
||||
italic = true
|
||||
@ -58,11 +59,15 @@ time_format = "15:04"
|
||||
[colors.message.reaction]
|
||||
foreground = "magenta"
|
||||
bold = true
|
||||
[colors.message.quote]
|
||||
foreground = "green"
|
||||
[colors.message.code]
|
||||
foreground = "cyan"
|
||||
foreground = "green"
|
||||
background = "grey"
|
||||
[colors.feed]
|
||||
[colors.feed.basic]
|
||||
foreground = "grey"
|
||||
[colors.feed.error]
|
||||
foreground = "red"
|
||||
[colors.feed.file]
|
||||
foreground = "yellow"
|
||||
|
||||
57
main.go
57
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)
|
||||
}
|
||||
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<code>\n")
|
||||
// TODO make background color cover whole line
|
||||
output = output.colorRegex("<code>(.*\n)*<code>", 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, "```", "<code>", -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 {
|
||||
|
||||
2
types.go
2
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"`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user