From 8e6724bdb6ce32ab5abebeba3abecc2e3feaded1 Mon Sep 17 00:00:00 2001 From: Gregory Rudolph Date: Wed, 16 Oct 2019 19:24:06 -0400 Subject: [PATCH 01/53] refactor viewTitle() to setViewTitle() --- cmdEdit.go | 2 +- cmdJoin.go | 2 +- cmdStream.go | 2 +- main.go | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cmdEdit.go b/cmdEdit.go index cd4fdff..af40142 100644 --- a/cmdEdit.go +++ b/cmdEdit.go @@ -42,7 +42,7 @@ func cmdEdit(cmd []string) { clearView("Edit") popupView("Edit") printToView("Edit", fmt.Sprintf("/e %d %s", messageId, editString)) - viewTitle("Edit", fmt.Sprintf(" Editing message %d ", messageId)) + setViewTitle("Edit", fmt.Sprintf(" Editing message %d ", messageId)) return } if len(cmd) < 3 { diff --git a/cmdJoin.go b/cmdJoin.go index 1e66613..fa5aee0 100644 --- a/cmdJoin.go +++ b/cmdJoin.go @@ -43,7 +43,7 @@ func cmdJoin(cmd []string) { } printToView("Feed", fmt.Sprintf("You are joining: %s", joinedName)) clearView("Chat") - viewTitle("Input", fmt.Sprintf(" %s ", joinedName)) + setViewTitle("Input", fmt.Sprintf(" %s ", joinedName)) go populateChat() default: printToView("Feed", fmt.Sprintf("To join a team use %sjoin ", cmdPrefix)) diff --git a/cmdStream.go b/cmdStream.go index 9978e6e..ee68867 100644 --- a/cmdStream.go +++ b/cmdStream.go @@ -18,6 +18,6 @@ func cmdStream(cmd []string) { channel.Name = "" printToView("Feed", "You are now viewing the formatted stream") - viewTitle("Input", " Stream - Not in a chat /j to join ") + setViewTitle("Input", " Stream - Not in a chat /j to join ") clearView("Chat") } diff --git a/main.go b/main.go index fbb1493..13dfcc0 100644 --- a/main.go +++ b/main.go @@ -52,7 +52,7 @@ func main() { } } -func viewTitle(viewName string, title string) { +func setViewTitle(viewName string, title string) { g.Update(func(g *gocui.Gui) error { updatingView, err := g.View(viewName) if err != nil { @@ -381,7 +381,7 @@ func handleTab() error { } else if rLen > 5 { newViewTitle = fmt.Sprintf("%s|| %s +%d more", originalViewTitle, strings.Join(resultSlice[:6], " "), rLen-5) } - viewTitle("Input", newViewTitle) + setViewTitle("Input", newViewTitle) remainder := stringRemainder(s, lcp) writeToView("Input", remainder) } @@ -650,7 +650,7 @@ func handleInput(viewName string) error { } // restore any tab completion view titles on input commit if newViewTitle := getViewTitle(viewName); newViewTitle != "" { - viewTitle(viewName, newViewTitle) + setViewTitle(viewName, newViewTitle) } go populateList() From 077aff092851310f042e7ec18ee19681afcdfa82 Mon Sep 17 00:00:00 2001 From: Gregory Rudolph Date: Wed, 16 Oct 2019 19:34:50 -0400 Subject: [PATCH 02/53] Cleaning up --- main.go | 444 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 222 insertions(+), 222 deletions(-) diff --git a/main.go b/main.go index 13dfcc0..64cce3c 100644 --- a/main.go +++ b/main.go @@ -12,17 +12,19 @@ import ( "samhofi.us/x/keybase" ) -var typeCommands = make(map[string]TypeCommand) -var commands = make(map[string]Command) -var baseCommands = make([]string, 0) - -var dev = false -var k = keybase.NewKeybase() -var channel keybase.Channel -var channels []keybase.Channel -var stream = false -var lastMessage keybase.ChatAPI -var g *gocui.Gui +var ( + typeCommands = make(map[string]TypeCommand) + commands = make(map[string]Command) + baseCommands = make([]string, 0) + + dev = false + k = keybase.NewKeybase() + channel keybase.Channel + channels []keybase.Channel + stream = false + lastMessage keybase.ChatAPI + g *gocui.Gui +) func main() { if !k.LoggedIn { @@ -52,6 +54,99 @@ func main() { } } +// Gocui basic setup +func layout(g *gocui.Gui) error { + maxX, maxY := g.Size() + if editView, err := g.SetView("Edit", maxX/2-maxX/3+1, maxY/2, maxX-2, maxY/2+10, 0); err != nil { + if !gocui.IsUnknownView(err) { + return err + } + editView.Editable = true + editView.Wrap = true + fmt.Fprintln(editView, "Edit window. Should disappear") + } + if feedView, err := g.SetView("Feed", maxX/2-maxX/3, 0, maxX-1, maxY/5, 0); err != nil { + if !gocui.IsUnknownView(err) { + return err + } + feedView.Autoscroll = true + feedView.Wrap = true + feedView.Title = "Feed Window" + fmt.Fprintln(feedView, "Feed Window - If you are mentioned or receive a PM it will show here") + } + if chatView, err2 := g.SetView("Chat", maxX/2-maxX/3, maxY/5+1, maxX-1, maxY-5, 0); err2 != nil { + if !gocui.IsUnknownView(err2) { + return err2 + } + chatView.Autoscroll = true + chatView.Wrap = true + fmt.Fprintf(chatView, "Welcome %s!\n\nYour chats will appear here.\nSupported commands are as follows:\n\n", k.Username) + RunCommand("help") + } + if inputView, err3 := g.SetView("Input", maxX/2-maxX/3, maxY-4, maxX-1, maxY-1, 0); err3 != nil { + if !gocui.IsUnknownView(err3) { + return err3 + } + if _, err := g.SetCurrentView("Input"); err != nil { + return err + } + inputView.Editable = true + inputView.Wrap = true + inputView.Title = fmt.Sprintf(" Not in a chat - write `%sj` to join", cmdPrefix) + g.Cursor = true + } + if listView, err4 := g.SetView("List", 0, 0, maxX/2-maxX/3-1, maxY-1, 0); err4 != nil { + if !gocui.IsUnknownView(err4) { + return err4 + } + listView.Title = "Channels" + fmt.Fprintf(listView, "Lists\nWindow\nTo view\n activity") + } + return nil +} +func initKeybindings() error { + if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, + func(g *gocui.Gui, v *gocui.View) error { + input, err := getInputString("Input") + if err != nil { + return err + } + if input != "" { + clearView("Input") + return nil + } else { + return gocui.ErrQuit + } + }); err != nil { + return err + } + if err := g.SetKeybinding("Input", gocui.KeyEnter, gocui.ModNone, + func(g *gocui.Gui, v *gocui.View) error { + return handleInput("Input") + }); err != nil { + return err + } + if err := g.SetKeybinding("Input", gocui.KeyTab, gocui.ModNone, + func(g *gocui.Gui, v *gocui.View) error { + return handleTab() + }); err != nil { + return err + } + if err := g.SetKeybinding("Edit", gocui.KeyEnter, gocui.ModNone, + func(g *gocui.Gui, v *gocui.View) error { + popupView("Chat") + popupView("Input") + return handleInput("Edit") + + }); err != nil { + return err + } + return nil +} + +// End gocui basic setup + +// Gocui helper funcs func setViewTitle(viewName string, title string) { g.Update(func(g *gocui.Gui) error { updatingView, err := g.View(viewName) @@ -63,7 +158,6 @@ func setViewTitle(viewName string, title string) { return nil }) } - func getViewTitle(viewName string) string { view, err := g.View(viewName) if err != nil { @@ -74,7 +168,6 @@ func getViewTitle(viewName string) string { return strings.Split(view.Title, "||")[0] } } - func popupView(viewName string) { _, err := g.SetCurrentView(viewName) if err != nil { @@ -96,7 +189,59 @@ func popupView(viewName string) { }) } +func clearView(viewName string) { + g.Update(func(g *gocui.Gui) error { + inputView, err := g.View(viewName) + if err != nil { + return err + } else { + inputView.Clear() + inputView.SetCursor(0, 0) + inputView.SetOrigin(0, 0) + } + return nil + }) + +} +func writeToView(viewName string, message string) { + g.Update(func(g *gocui.Gui) error { + updatingView, err := g.View(viewName) + if err != nil { + return err + } else { + for _, c := range message { + updatingView.EditWrite(c) + } + } + return nil + }) +} +func printToView(viewName string, message string) { + g.Update(func(g *gocui.Gui) error { + updatingView, err := g.View(viewName) + if err != nil { + return err + } else { + fmt.Fprintf(updatingView, "%s\n", message) + } + return nil + }) +} + +// End gocui helper funcs + +// Update/Populate views automatically +func updateChatWindow() { + runOpts := keybase.RunOptions{ + Dev: dev, + } + k.Run(func(api keybase.ChatAPI) { + handleMessage(api) + }, + runOpts) + +} func populateChat() { lastMessage.ID = 0 chat := k.NewChat(channel) @@ -144,14 +289,49 @@ func populateChat() { printToView("Chat", actuallyPrintMe) } +func populateList() { + _, maxY := g.Size() + if testVar, err := k.ChatList(); err != nil { + log.Printf("%+v", err) + } else { -func sendChat(message string) { - chat := k.NewChat(channel) - _, err := chat.Send(message) - if err != nil { - printToView("Feed", fmt.Sprintf("There was an error %+v", err)) + clearView("List") + var recentPMs = fmt.Sprintf("%s---[PMs]---%s\n", channelsHeaderColor, channelsColor) + var recentPMsCount = 0 + var recentChannels = fmt.Sprintf("%s---[Teams]---%s\n", channelsHeaderColor, channelsColor) + var recentChannelsCount = 0 + for _, s := range testVar.Result.Conversations { + channels = append(channels, s.Channel) + if s.Channel.MembersType == keybase.TEAM { + recentChannelsCount++ + if recentChannelsCount <= ((maxY - 2) / 3) { + if s.Unread { + recentChannels += fmt.Sprintf("%s*", color(0)) + } + recentChannels += fmt.Sprintf("%s\n\t#%s\n%s", s.Channel.Name, s.Channel.TopicName, channelsColor) + } + } else { + recentPMsCount++ + if recentPMsCount <= ((maxY - 2) / 3) { + if s.Unread { + recentChannels += fmt.Sprintf("%s*", color(0)) + } + recentPMs += fmt.Sprintf("%s\n%s", cleanChannelName(s.Channel.Name), channelsColor) + } + } + } + time.Sleep(1 * time.Millisecond) + printToView("List", fmt.Sprintf("%s%s%s%s", channelsColor, recentPMs, recentChannels, noColor)) } } + +// End update/populate views automatically + +// Formatting +func cleanChannelName(c string) string { + newChannelName := strings.Replace(c, fmt.Sprintf("%s,", k.Username), "", 1) + return strings.Replace(newChannelName, fmt.Sprintf(",%s", k.Username), "", 1) +} func formatOutput(api keybase.ChatAPI) string { ret := "" msgType := api.Msg.Content.Type @@ -185,42 +365,9 @@ func formatOutput(api keybase.ChatAPI) string { return ret } -func populateList() { - _, maxY := g.Size() - if testVar, err := k.ChatList(); err != nil { - log.Printf("%+v", err) - } else { - - clearView("List") - var recentPMs = fmt.Sprintf("%s---[PMs]---%s\n", channelsHeaderColor, channelsColor) - var recentPMsCount = 0 - var recentChannels = fmt.Sprintf("%s---[Teams]---%s\n", channelsHeaderColor, channelsColor) - var recentChannelsCount = 0 - for _, s := range testVar.Result.Conversations { - channels = append(channels, s.Channel) - if s.Channel.MembersType == keybase.TEAM { - recentChannelsCount++ - if recentChannelsCount <= ((maxY - 2) / 3) { - if s.Unread { - recentChannels += fmt.Sprintf("%s*", color(0)) - } - recentChannels += fmt.Sprintf("%s\n\t#%s\n%s", s.Channel.Name, s.Channel.TopicName, channelsColor) - } - } else { - recentPMsCount++ - if recentPMsCount <= ((maxY - 2) / 3) { - if s.Unread { - recentChannels += fmt.Sprintf("%s*", color(0)) - } - recentPMs += fmt.Sprintf("%s\n%s", cleanChannelName(s.Channel.Name), channelsColor) - } - } - } - time.Sleep(1 * time.Millisecond) - printToView("List", fmt.Sprintf("%s%s%s%s", channelsColor, recentPMs, recentChannels, noColor)) - } -} +// End formatting +// Tab completion func getCurrentChannelMembership() []string { var rs []string if channel.Name != "" { @@ -244,7 +391,6 @@ func getCurrentChannelMembership() []string { } return rs } - func filterStringSlice(ss []string, fv string) []string { var rs []string for _, s := range ss { @@ -254,7 +400,6 @@ func filterStringSlice(ss []string, fv string) []string { } return rs } - func longestCommonPrefix(ss []string) string { // cover the case where the slice has no or one members switch len(ss) { @@ -283,7 +428,6 @@ func longestCommonPrefix(ss []string) string { // to cover the case where all members are equal, just return one return min } - func stringRemainder(aStr, bStr string) string { var long, short string //figure out which string is longer @@ -307,7 +451,6 @@ func stringRemainder(aStr, bStr string) string { // return whatever's left of the longer string return long[i:] } - func appendIfNotInSlice(ss []string, s string) []string { for _, element := range ss { if element == s { @@ -316,7 +459,6 @@ func appendIfNotInSlice(ss []string, s string) []string { } return append(ss, s) } - func generateChannelTabCompletionSlice(inputWord string) []string { // create a slice to hold the values var firstSlice []string @@ -340,13 +482,11 @@ func generateChannelTabCompletionSlice(inputWord string) []string { resultSlice := filterStringSlice(firstSlice, inputWord) return resultSlice } - func generateEmojiTabCompletionSlice(inputWord string) []string { // use the emojiSlice from emojiList.go and filter it for the input word resultSlice := filterStringSlice(emojiSlice, inputWord) return resultSlice } - func handleTab() error { inputString, err := getInputString("Input") if err != nil { @@ -390,164 +530,9 @@ func handleTab() error { return nil } -func clearView(viewName string) { - g.Update(func(g *gocui.Gui) error { - inputView, err := g.View(viewName) - if err != nil { - return err - } else { - inputView.Clear() - inputView.SetCursor(0, 0) - inputView.SetOrigin(0, 0) - } - return nil - }) - -} - -func writeToView(viewName string, message string) { - g.Update(func(g *gocui.Gui) error { - updatingView, err := g.View(viewName) - if err != nil { - return err - } else { - for _, c := range message { - updatingView.EditWrite(c) - } - } - return nil - }) -} - -func printToView(viewName string, message string) { - g.Update(func(g *gocui.Gui) error { - updatingView, err := g.View(viewName) - if err != nil { - return err - } else { - fmt.Fprintf(updatingView, "%s\n", message) - } - return nil - }) -} - -func layout(g *gocui.Gui) error { - maxX, maxY := g.Size() - if editView, err := g.SetView("Edit", maxX/2-maxX/3+1, maxY/2, maxX-2, maxY/2+10, 0); err != nil { - if !gocui.IsUnknownView(err) { - return err - } - editView.Editable = true - editView.Wrap = true - fmt.Fprintln(editView, "Edit window. Should disappear") - } - if feedView, err := g.SetView("Feed", maxX/2-maxX/3, 0, maxX-1, maxY/5, 0); err != nil { - if !gocui.IsUnknownView(err) { - return err - } - feedView.Autoscroll = true - feedView.Wrap = true - feedView.Title = "Feed Window" - fmt.Fprintln(feedView, "Feed Window - If you are mentioned or receive a PM it will show here") - } - if chatView, err2 := g.SetView("Chat", maxX/2-maxX/3, maxY/5+1, maxX-1, maxY-5, 0); err2 != nil { - if !gocui.IsUnknownView(err2) { - return err2 - } - chatView.Autoscroll = true - chatView.Wrap = true - fmt.Fprintf(chatView, "Welcome %s!\n\nYour chats will appear here.\nSupported commands are as follows:\n\n", k.Username) - RunCommand("help") - } - if inputView, err3 := g.SetView("Input", maxX/2-maxX/3, maxY-4, maxX-1, maxY-1, 0); err3 != nil { - if !gocui.IsUnknownView(err3) { - return err3 - } - if _, err := g.SetCurrentView("Input"); err != nil { - return err - } - inputView.Editable = true - inputView.Wrap = true - inputView.Title = fmt.Sprintf(" Not in a chat - write `%sj` to join", cmdPrefix) - g.Cursor = true - } - if listView, err4 := g.SetView("List", 0, 0, maxX/2-maxX/3-1, maxY-1, 0); err4 != nil { - if !gocui.IsUnknownView(err4) { - return err4 - } - listView.Title = "Channels" - fmt.Fprintf(listView, "Lists\nWindow\nTo view\n activity") - } - return nil -} - -func getInputString(viewName string) (string, error) { - inputView, err := g.View(viewName) - if err != nil { - return "", err - } - retString := inputView.Buffer() - retString = strings.Replace(retString, "\n", "", 800) - return retString, err -} - -func initKeybindings() error { - if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, - func(g *gocui.Gui, v *gocui.View) error { - input, err := getInputString("Input") - if err != nil { - return err - } - if input != "" { - clearView("Input") - return nil - } else { - return gocui.ErrQuit - } - }); err != nil { - return err - } - if err := g.SetKeybinding("Input", gocui.KeyEnter, gocui.ModNone, - func(g *gocui.Gui, v *gocui.View) error { - return handleInput("Input") - }); err != nil { - return err - } - if err := g.SetKeybinding("Input", gocui.KeyTab, gocui.ModNone, - func(g *gocui.Gui, v *gocui.View) error { - return handleTab() - }); err != nil { - return err - } - if err := g.SetKeybinding("Edit", gocui.KeyEnter, gocui.ModNone, - func(g *gocui.Gui, v *gocui.View) error { - popupView("Chat") - popupView("Input") - return handleInput("Edit") - - }); err != nil { - return err - } - return nil -} - -func updateChatWindow() { - - runOpts := keybase.RunOptions{ - Dev: dev, - } - k.Run(func(api keybase.ChatAPI) { - handleMessage(api) - }, - runOpts) - -} - -func cleanChannelName(c string) string { - newChannelName := strings.Replace(c, fmt.Sprintf("%s,", k.Username), "", 1) - return strings.Replace(newChannelName, fmt.Sprintf(",%s", k.Username), "", 1) -} +// End tab completion +// Input handling func handleMessage(api keybase.ChatAPI) { if _, ok := typeCommands[api.Msg.Content.Type]; ok { if api.Msg.Channel.MembersType == channel.MembersType && cleanChannelName(api.Msg.Channel.Name) == channel.Name { @@ -612,9 +597,16 @@ func handleMessage(api keybase.ChatAPI) { } } } - -// It seems that golang doesn't have filter and other high order functions :'( -func delete_empty(s []string) []string { +func getInputString(viewName string) (string, error) { + inputView, err := g.View(viewName) + if err != nil { + return "", err + } + retString := inputView.Buffer() + retString = strings.Replace(retString, "\n", "", 800) + return retString, err +} +func deleteEmpty(s []string) []string { var r []string for _, str := range s { if str != "" { @@ -623,7 +615,6 @@ func delete_empty(s []string) []string { } return r } - func handleInput(viewName string) error { clearView(viewName) inputString, _ := getInputString(viewName) @@ -631,7 +622,7 @@ func handleInput(viewName string) error { return nil } if strings.HasPrefix(inputString, cmdPrefix) { - cmd := delete_empty(strings.Split(inputString[len(cmdPrefix):], " ")) + cmd := deleteEmpty(strings.Split(inputString[len(cmdPrefix):], " ")) if c, ok := commands[cmd[0]]; ok { c.Exec(cmd) return nil @@ -656,6 +647,15 @@ func handleInput(viewName string) error { go populateList() return nil } +func sendChat(message string) { + chat := k.NewChat(channel) + _, err := chat.Send(message) + if err != nil { + printToView("Feed", fmt.Sprintf("There was an error %+v", err)) + } +} + +// End input handling func quit(g *gocui.Gui, v *gocui.View) error { return gocui.ErrQuit From 1aac6fc7cec28f6f7c1eb6a5fc23d0fac38f1bf4 Mon Sep 17 00:00:00 2001 From: Gregory Rudolph Date: Wed, 16 Oct 2019 19:45:01 -0400 Subject: [PATCH 03/53] Bugfix: Empty command crashes the program #9 --- main.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/main.go b/main.go index 13dfcc0..a3ead73 100644 --- a/main.go +++ b/main.go @@ -632,6 +632,9 @@ func handleInput(viewName string) error { } if strings.HasPrefix(inputString, cmdPrefix) { cmd := delete_empty(strings.Split(inputString[len(cmdPrefix):], " ")) + if len(cmd) < 1 { + return nil + } if c, ok := commands[cmd[0]]; ok { c.Exec(cmd) return nil From 9fbf0c52f202949297465a266b5b9f04f7d05b02 Mon Sep 17 00:00:00 2001 From: Gregory Rudolph Date: Wed, 16 Oct 2019 19:58:33 -0400 Subject: [PATCH 04/53] Refactor delete_empty as per Go naming conventions --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 56af9bf..702683d 100644 --- a/main.go +++ b/main.go @@ -622,7 +622,7 @@ func handleInput(viewName string) error { return nil } if strings.HasPrefix(inputString, cmdPrefix) { - cmd := delete_empty(strings.Split(inputString[len(cmdPrefix):], " ")) + cmd := deleteEmpty(strings.Split(inputString[len(cmdPrefix):], " ")) if len(cmd) < 1 { return nil } From b212310ec4f1415b21696caeb6a7ee762888dd67 Mon Sep 17 00:00:00 2001 From: Gregory Rudolph Date: Thu, 17 Oct 2019 08:20:20 -0400 Subject: [PATCH 05/53] Clean up unused else statement --- main.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/main.go b/main.go index 702683d..93624e9 100644 --- a/main.go +++ b/main.go @@ -571,16 +571,12 @@ func handleMessage(api keybase.ChatAPI) { } } if api.Msg.Channel.MembersType == channel.MembersType && cleanChannelName(api.Msg.Channel.Name) == channel.Name { - if channel.MembersType == keybase.TEAM && channel.TopicName != api.Msg.Channel.TopicName { - // Do nothing, wrong channel - } else { - + if channel.MembersType == keybase.TEAM && channel.TopicName == api.Msg.Channel.TopicName { printToView("Chat", formatOutput(api)) chat := k.NewChat(channel) lastMessage.ID = api.Msg.ID chat.Read(api.Msg.ID) } - } } else { if api.Msg.Channel.MembersType == keybase.TEAM { From a974bc90c5781bc2bb3e47c38a1a9fb39eb39620 Mon Sep 17 00:00:00 2001 From: Gregory Rudolph Date: Thu, 17 Oct 2019 08:20:27 -0400 Subject: [PATCH 06/53] We have colour now --- userConfigs.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/userConfigs.go b/userConfigs.go index 2319247..f01bdde 100644 --- a/userConfigs.go +++ b/userConfigs.go @@ -17,7 +17,7 @@ var messageBodyColor = noColor var messageAttachmentColor = color(2) var messageLinkColor = color(4) -// BASH-like PS1 variable equivalent (without colours) +// BASH-like PS1 variable equivalent var outputFormat = "┌──[$USER@$DEVICE] [$ID] [$DATE - $TIME]\n└╼ $MSG" // 02 = Day, Jan = Month, 06 = Year From 03969db8a97db1350c65e2af8b4f6552e9b32683 Mon Sep 17 00:00:00 2001 From: Gregory Rudolph Date: Thu, 17 Oct 2019 08:39:05 -0400 Subject: [PATCH 07/53] Fix write to feed to be print instead --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 93624e9..696fc2d 100644 --- a/main.go +++ b/main.go @@ -162,7 +162,7 @@ func getViewTitle(viewName string) string { view, err := g.View(viewName) if err != nil { // in case there is active tab completion, filter that to just the view title and not the completion options. - writeToView("Feed", fmt.Sprintf("Error getting view title: %s", err)) + printToView("Feed", fmt.Sprintf("Error getting view title: %s", err)) return "" } else { return strings.Split(view.Title, "||")[0] From d52856813783fb1c31157ed18f41a869a60e3a80 Mon Sep 17 00:00:00 2001 From: Gregory Rudolph Date: Thu, 17 Oct 2019 08:45:16 -0400 Subject: [PATCH 08/53] Fix bug where Ctrl+C in edit will close kbtui rather than return to regular views --- main.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/main.go b/main.go index 696fc2d..b0c0432 100644 --- a/main.go +++ b/main.go @@ -120,6 +120,15 @@ func initKeybindings() error { }); err != nil { return err } + if err := g.SetKeybinding("Edit", gocui.KeyCtrlC, gocui.ModNone, + func(g *gocui.Gui, v *gocui.View) error { + popupView("Chat") + popupView("Input") + clearView("Edit") + return nil + }); err != nil { + return err + } if err := g.SetKeybinding("Input", gocui.KeyEnter, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error { return handleInput("Input") From 09953ead7d8d761b2bee85aeab378e783e610ef5 Mon Sep 17 00:00:00 2001 From: Gregory Rudolph Date: Thu, 17 Oct 2019 08:50:54 -0400 Subject: [PATCH 09/53] Bugfix where PMs wouldn't auto-update chat view --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index b0c0432..fa3a731 100644 --- a/main.go +++ b/main.go @@ -580,7 +580,7 @@ func handleMessage(api keybase.ChatAPI) { } } if api.Msg.Channel.MembersType == channel.MembersType && cleanChannelName(api.Msg.Channel.Name) == channel.Name { - if channel.MembersType == keybase.TEAM && channel.TopicName == api.Msg.Channel.TopicName { + if channel.MembersType == keybase.USER || channel.MembersType == keybase.TEAM && channel.TopicName == api.Msg.Channel.TopicName { printToView("Chat", formatOutput(api)) chat := k.NewChat(channel) lastMessage.ID = api.Msg.ID From 3b0711a782da8db79a568b9d29b5846462dd17f8 Mon Sep 17 00:00:00 2001 From: Gregory Rudolph Date: Thu, 17 Oct 2019 08:54:37 -0400 Subject: [PATCH 10/53] Feature: Up arrow to edit previous message --- main.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/main.go b/main.go index fa3a731..82cc72b 100644 --- a/main.go +++ b/main.go @@ -150,6 +150,13 @@ func initKeybindings() error { }); err != nil { return err } + if err := g.SetKeybinding("Input", gocui.KeyArrowUp, gocui.ModNone, + func(g *gocui.Gui, v *gocui.View) error { + RunCommand("edit") + return nil + }); err != nil { + return err + } return nil } From aca97bf503a73c5c3fa4d6654930f4f560e95748 Mon Sep 17 00:00:00 2001 From: Gregory Rudolph Date: Thu, 17 Oct 2019 09:24:29 -0400 Subject: [PATCH 11/53] Increase size of wallet confirmation code --- cmdWallet.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmdWallet.go b/cmdWallet.go index 4f610ca..b26eca2 100644 --- a/cmdWallet.go +++ b/cmdWallet.go @@ -34,7 +34,7 @@ func cmdWallet(cmd []string) { chars := []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789") - length := 5 + length := 8 var b strings.Builder for i := 0; i < length; i++ { b.WriteRune(chars[rand.Intn(len(chars))]) From 2544c4db9efbc1ff47e7a97622a657949c833287 Mon Sep 17 00:00:00 2001 From: Gregory Rudolph Date: Thu, 17 Oct 2019 09:27:13 -0400 Subject: [PATCH 12/53] Update wall help --- cmdWall.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmdWall.go b/cmdWall.go index 66ca34a..b34db98 100644 --- a/cmdWall.go +++ b/cmdWall.go @@ -15,7 +15,7 @@ import ( func init() { command := Command{ Cmd: []string{"wall", "w"}, - Description: "- Show public messages for a user", + Description: "$user / !all - Show public messages for a user or all users you follow", Help: "", Exec: cmdWall, } From f7a0368665b6cdf1d4834126c03cf0ba3350964e Mon Sep 17 00:00:00 2001 From: Gregory Rudolph Date: Thu, 17 Oct 2019 09:38:22 -0400 Subject: [PATCH 13/53] Feature: Allow relative paths for file upload --- cmdUploadFile.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/cmdUploadFile.go b/cmdUploadFile.go index 8151f2c..4fd9da7 100644 --- a/cmdUploadFile.go +++ b/cmdUploadFile.go @@ -4,12 +4,14 @@ package main import ( "fmt" + "os" + "strings" ) func init() { command := Command{ Cmd: []string{"upload", "u"}, - Description: "$filePath $fileName - Upload file with optional name", + Description: "$filePath $fileName - Upload file from absolute path with optional name", Help: "", Exec: cmdUploadFile, } @@ -19,6 +21,13 @@ func init() { func cmdUploadFile(cmd []string) { filePath := cmd[1] + if !strings.HasPrefix(filePath, "/") { + dir, err := os.Getwd() + if err != nil { + printToView("Feed", fmt.Sprintf("There was an error determining path %+v", err)) + } + filePath = fmt.Sprintf("%s/%s", dir, filePath) + } var fileName string if len(cmd) == 3 { fileName = cmd[2] @@ -28,7 +37,7 @@ func cmdUploadFile(cmd []string) { chat := k.NewChat(channel) _, err := chat.Upload(fileName, filePath) if err != nil { - printToView("Feed", fmt.Sprintf("There was an error uploading %s to %s", filePath, channel.Name)) + printToView("Feed", fmt.Sprintf("There was an error uploading %s to %s\n%+v", filePath, channel.Name, err)) } else { printToView("Feed", fmt.Sprintf("Uploaded %s to %s", filePath, channel.Name)) } From 209a94614b59520a7ea8c7d3460828bff4dcbf62 Mon Sep 17 00:00:00 2001 From: Gregory Rudolph Date: Thu, 17 Oct 2019 09:55:23 -0400 Subject: [PATCH 14/53] Ignore emojiList.go, and auto gofmt it. --- .gitignore | 1 + mage.go | 1 + 2 files changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 3a15600..f43bc69 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ kbtui +emojiList.go *~ .\#* \#*\# diff --git a/mage.go b/mage.go index 164444f..b2737d8 100644 --- a/mage.go +++ b/mage.go @@ -79,6 +79,7 @@ func BuildEmoji() error { return err } f.Sync() + sh.Run("go", "fmt") return nil } From b55359ceed210431999a95dc46e2f2d9be0d72a3 Mon Sep 17 00:00:00 2001 From: Gregory Rudolph Date: Thu, 17 Oct 2019 10:01:03 -0400 Subject: [PATCH 15/53] Remove go fmt --- mage.go | 1 - 1 file changed, 1 deletion(-) diff --git a/mage.go b/mage.go index b2737d8..164444f 100644 --- a/mage.go +++ b/mage.go @@ -79,7 +79,6 @@ func BuildEmoji() error { return err } f.Sync() - sh.Run("go", "fmt") return nil } From ec5c14d2cd91866ec6eae1bf2fa9b734e5fdcf10 Mon Sep 17 00:00:00 2001 From: Gregory Rudolph Date: Thu, 17 Oct 2019 10:31:51 -0400 Subject: [PATCH 16/53] Better error handling --- cmdReply.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/cmdReply.go b/cmdReply.go index 98ec995..aa2c231 100644 --- a/cmdReply.go +++ b/cmdReply.go @@ -3,6 +3,7 @@ package main import ( + "fmt" "strconv" "strings" ) @@ -20,8 +21,11 @@ func init() { func cmdReply(cmd []string) { chat := k.NewChat(channel) - messageId, err := strconv.Atoi(cmd[1]) - _, err = chat.Reply(messageId, strings.Join(cmd[2:], " ")) + messageID, err := strconv.Atoi(cmd[1]) + if err != nil { + printToView("Feed", fmt.Sprintf("There was an error determining message ID %s", cmd[1])) + } + _, err = chat.Reply(messageID, strings.Join(cmd[2:], " ")) if err != nil { printToView("Feed", "There was an error with your reply.") } From 80d2f79379f0d6ba62a383e6910b0014335a7e9b Mon Sep 17 00:00:00 2001 From: Gregory Rudolph Date: Thu, 17 Oct 2019 10:32:08 -0400 Subject: [PATCH 17/53] Color date same as time for users who separate the two --- main.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/main.go b/main.go index 82cc72b..914f8f5 100644 --- a/main.go +++ b/main.go @@ -368,14 +368,14 @@ func formatOutput(api keybase.ChatAPI) string { user := colorUsername(api.Msg.Sender.Username, c) device := colorText(api.Msg.Sender.DeviceName, messageSenderDeviceColor, c) - msgId := colorText(fmt.Sprintf("%d", api.Msg.ID), messageIdColor, c) - ts := colorText(fmt.Sprintf("%s", tm.Format(timeFormat)), messageTimeColor, c) + msgID := colorText(fmt.Sprintf("%d", api.Msg.ID), messageIdColor, c) + ts := colorText(tm.Format(timeFormat), messageTimeColor, c) ret = strings.Replace(ret, "$MSG", msg, 1) ret = strings.Replace(ret, "$USER", user, 1) ret = strings.Replace(ret, "$DEVICE", device, 1) - ret = strings.Replace(ret, "$ID", msgId, 1) + ret = strings.Replace(ret, "$ID", msgID, 1) ret = strings.Replace(ret, "$TIME", ts, 1) - ret = strings.Replace(ret, "$DATE", fmt.Sprintf("%s", tm.Format(dateFormat)), 1) + ret = strings.Replace(ret, "$DATE", colorText(tm.Format(dateFormat), messageTimeColor, c), 1) ret = strings.Replace(ret, "```", fmt.Sprintf("\n\n"), -1) } return ret From 9bb1bb60bd23e8cc25b338278cf53000fdf873e1 Mon Sep 17 00:00:00 2001 From: Gregory Rudolph Date: Thu, 17 Oct 2019 10:33:37 -0400 Subject: [PATCH 18/53] Odd bug with attachments displaying a random message, squashed. --- main.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/main.go b/main.go index 914f8f5..dd3bf0e 100644 --- a/main.go +++ b/main.go @@ -363,9 +363,8 @@ func formatOutput(api keybase.ChatAPI) string { msg = colorRegex(msg, `(https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*))`, messageLinkColor, messageBodyColor) msg = colorText(colorReplaceMentionMe(msg, messageBodyColor), messageBodyColor, c) if msgType == "attachment" { - msg = fmt.Sprintf("%s\n%s", msg, colorText("[Attachment]", messageAttachmentColor, c)) + msg = fmt.Sprintf("%s", colorText("[Attachment]", messageAttachmentColor, c)) } - user := colorUsername(api.Msg.Sender.Username, c) device := colorText(api.Msg.Sender.DeviceName, messageSenderDeviceColor, c) msgID := colorText(fmt.Sprintf("%d", api.Msg.ID), messageIdColor, c) From fb1bb0c0fa8b779925cbeb78aa28f8af521c7c1d Mon Sep 17 00:00:00 2001 From: Gregory Rudolph Date: Thu, 17 Oct 2019 11:03:28 -0400 Subject: [PATCH 19/53] Remove emojilist --- emojiList.go | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 emojiList.go diff --git a/emojiList.go b/emojiList.go deleted file mode 100644 index fb3d40e..0000000 --- a/emojiList.go +++ /dev/null @@ -1,3 +0,0 @@ -package main - -var emojiSlice = []string{":grinning:", ":grin:", ":joy:", ":rofl:", ":smiley:", ":grin:", ":sweat_smile:", ":laughing:", ":wink:", ":blush:", ":yum:", ":sunglasses:", ":heart_eyes:", ":kissing_heart:", ":kissing:", ":kissing_smiling_eyes:", ":kissing_closed_eyes:", ":relaxed:", ":slight_smile:", ":hugging:", ":star_struck:", ":thinking:", ":face_with_raised_eyebrow:", ":neutral_face:", ":expressionless:", ":no_mouth:", ":rolling_eyes:", ":smirk:", ":persevere:", ":disappointed_relieved:", ":open_mouth:", ":zipper_mouth:", ":hushed:", ":sleepy:", ":tired_face:", ":sleeping:", ":relieved:", ":stuck_out_tongue:", ":stuck_out_tongue_winking_eye:", ":stuck_out_tongue_closed_eyes:", ":drooling_face:", ":unamused:", ":sweat:", ":pensive:", ":confused:", ":upside_down:", ":money_mouth:", ":astonished:", ":frowning2:", ":slight_frown:", ":confounded:", ":disappointed:", ":worried:", ":triumph:", ":cry:", ":sob:", ":frowning:", ":anguished:", ":fearful:", ":weary:", ":exploding_head:", ":grimacing:", ":cold_sweat:", ":scream:", ":flushed:", ":crazy_face:", ":dizzy_face:", ":rage:", ":angry:", ":face_with_symbols_over_mouth:", ":mask:", ":thermometer_face:", ":head_bandage:", ":nauseated_face:", ":face_vomiting:", ":sneezing_face:", ":innocent:", ":cowboy:", ":clown:", ":lying_face:", ":shushing_face:", ":face_with_hand_over_mouth:", ":face_with_monocle:", ":nerd:", ":smiling_imp:", ":imp:", ":japanese_ogre:", ":japanese_goblin:", ":skull:", ":skull_crossbones:", ":ghost:", ":alien:", ":space_invader:", ":robot:", ":poop:", ":smiley_cat:", ":smile_cat:", ":joy_cat:", ":heart_eyes_cat:", ":smirk_cat:", ":kissing_cat:", ":scream_cat:", ":crying_cat_face:", ":pouting_cat:", ":see_no_evil:", ":hear_no_evil:", ":speak_no_evil:", ":baby:", ":child:", ":boy:", ":girl:", ":adult:", ":man:", ":woman:", ":older_adult:", ":older_man:", ":older_woman:", ":man_health_worker:", ":woman_health_worker:", ":man_student:", ":woman_student:", ":man_teacher:", ":woman_teacher:", ":man_judge:", ":woman_judge:", ":man_farmer:", ":woman_farmer:", ":man_cook:", ":woman_cook:", ":man_mechanic:", ":woman_mechanic:", ":man_factory_worker:", ":woman_factory_worker:", ":man_office_worker:", ":woman_office_worker:", ":man_scientist:", ":woman_scientist:", ":man_technologist:", ":woman_technologist:", ":man_singer:", ":woman_singer:", ":man_artist:", ":woman_artist:", ":man_pilot:", ":woman_pilot:", ":man_astronaut:", ":woman_astronaut:", ":man_firefighter:", ":woman_firefighter:", ":police_officer:", ":man_police_officer:", ":woman_police_officer:", ":detective:", ":man_detective:", ":woman_detective:", ":guard:", ":man_guard:", ":woman_guard:", ":construction_worker:", ":man_construction_worker:", ":woman_construction_worker:", ":prince:", ":princess:", ":person_wearing_turban:", ":man_wearing_turban:", ":woman_wearing_turban:", ":man_with_chinese_cap:", ":woman_with_headscarf:", ":bearded_person:", ":blond_haired_person:", ":blond-haired_man:", ":blond-haired_woman:", ":man_in_tuxedo:", ":bride_with_veil:", ":pregnant_woman:", ":breast_feeding:", ":angel:", ":santa:", ":mrs_claus:", ":mage:", ":woman_mage:", ":man_mage:", ":fairy:", ":woman_fairy:", ":man_fairy:", ":vampire:", ":woman_vampire:", ":man_vampire:", ":merperson:", ":mermaid:", ":merman:", ":elf:", ":woman_elf:", ":man_elf:", ":genie:", ":woman_genie:", ":man_genie:", ":zombie:", ":woman_zombie:", ":man_zombie:", ":person_frowning:", ":man_frowning:", ":woman_frowning:", ":person_pouting:", ":man_pouting:", ":woman_pouting:", ":person_gesturing_no:", ":man_gesturing_no:", ":woman_gesturing_no:", ":person_gesturing_ok:", ":man_gesturing_ok:", ":woman_gesturing_ok:", ":person_tipping_hand:", ":man_tipping_hand:", ":woman_tipping_hand:", ":person_raising_hand:", ":man_raising_hand:", ":woman_raising_hand:", ":person_bowing:", ":man_bowing:", ":woman_bowing:", ":person_facepalming:", ":man_facepalming:", ":woman_facepalming:", ":person_shrugging:", ":man_shrugging:", ":woman_shrugging:", ":person_getting_massage:", ":man_getting_face_massage:", ":woman_getting_face_massage:", ":person_getting_haircut:", ":man_getting_haircut:", ":woman_getting_haircut:", ":person_walking:", ":man_walking:", ":woman_walking:", ":person_running:", ":man_running:", ":woman_running:", ":dancer:", ":man_dancing:", ":people_with_bunny_ears_partying:", ":men_with_bunny_ears_partying:", ":women_with_bunny_ears_partying:", ":person_in_steamy_room:", ":woman_in_steamy_room:", ":man_in_steamy_room:", ":person_climbing:", ":woman_climbing:", ":man_climbing:", ":person_in_lotus_position:", ":woman_in_lotus_position:", ":man_in_lotus_position:", ":bath:", ":sleeping_accommodation:", ":speaking_head:", ":bust_in_silhouette:", ":busts_in_silhouette:", ":person_fencing:", ":horse_racing:", ":skier:", ":snowboarder:", ":person_golfing:", ":man_golfing:", ":woman_golfing:", ":person_surfing:", ":man_surfing:", ":woman_surfing:", ":person_rowing_boat:", ":man_rowing_boat:", ":woman_rowing_boat:", ":person_swimming:", ":man_swimming:", ":woman_swimming:", ":person_bouncing_ball:", ":man_bouncing_ball:", ":woman_bouncing_ball:", ":person_lifting_weights:", ":man_lifting_weights:", ":woman_lifting_weights:", ":person_biking:", ":man_biking:", ":woman_biking:", ":person_mountain_biking:", ":man_mountain_biking:", ":woman_mountain_biking:", ":race_car:", ":motorcycle:", ":person_doing_cartwheel:", ":man_cartwheeling:", ":woman_cartwheeling:", ":people_wrestling:", ":men_wrestling:", ":women_wrestling:", ":person_playing_water_polo:", ":man_playing_water_polo:", ":woman_playing_water_polo:", ":person_playing_handball:", ":man_playing_handball:", ":woman_playing_handball:", ":person_juggling:", ":man_juggling:", ":woman_juggling:", ":couple:", ":two_men_holding_hands:", ":two_women_holding_hands:", ":couplekiss:", ":kiss_woman_man:", ":kiss_mm:", ":kiss_ww:", ":couple_with_heart:", ":couple_with_heart_woman_man:", ":couple_mm:", ":couple_ww:", ":family:", ":family_man_woman_boy:", ":family_mwg:", ":family_mwgb:", ":family_mwbb:", ":family_mwgg:", ":family_mmb:", ":family_mmg:", ":family_mmgb:", ":family_mmbb:", ":family_mmgg:", ":family_wwb:", ":family_wwg:", ":family_wwgb:", ":family_wwbb:", ":family_wwgg:", ":family_man_boy:", ":family_man_boy_boy:", ":family_man_girl:", ":family_man_girl_boy:", ":family_man_girl_girl:", ":family_woman_boy:", ":family_woman_boy_boy:", ":family_woman_girl:", ":family_woman_girl_boy:", ":family_woman_girl_girl:", ":selfie:", ":muscle:", ":point_left:", ":point_right:", ":point_up:", ":point_up_2:", ":middle_finger:", ":point_down:", ":v:", ":fingers_crossed:", ":vulcan:", ":metal:", ":call_me:", ":raised_hand:", ":ok_hand:", ":thumbsup:", ":thumbsdown:", ":fist:", ":punch:", ":left_facing_fist:", ":right_facing_fist:", ":raised_back_of_hand:", ":wave:", ":love_you_gesture:", ":writing_hand:", ":clap:", ":open_hands:", ":raised_hands:", ":palms_up_together:", ":pray:", ":handshake:", ":nail_care:", ":ear:", ":nose:", ":footprints:", ":eyes:", ":eye:", ":eye_in_speech_bubble:", ":brain:", ":tongue:", ":lips:", ":kiss:", ":cupid:", ":heart:", ":heartbeat:", ":broken_heart:", ":two_hearts:", ":sparkling_heart:", ":heartpulse:", ":blue_heart:", ":green_heart:", ":yellow_heart:", ":orange_heart:", ":purple_heart:", ":black_heart:", ":gift_heart:", ":revolving_hearts:", ":heart_decoration:", ":heart_exclamation:", ":love_letter:", ":zzz:", ":anger:", ":bomb:", ":boom:", ":sweat_drops:", ":dash:", ":dizzy:", ":speech_balloon:", ":speech_left:", ":anger_right:", ":thought_balloon:", ":hole:", ":eyeglasses:", ":dark_sunglasses:", ":necktie:", ":shirt:", ":jeans:", ":scarf:", ":gloves:", ":coat:", ":socks:", ":dress:", ":kimono:", ":bikini:", ":womans_clothes:", ":purse:", ":handbag:", ":pouch:", ":shopping_bags:", ":school_satchel:", ":mans_shoe:", ":athletic_shoe:", ":high_heel:", ":sandal:", ":boot:", ":crown:", ":womans_hat:", ":tophat:", ":mortar_board:", ":billed_cap:", ":helmet_with_cross:", ":prayer_beads:", ":lipstick:", ":ring:", ":gem:", ":monkey_face:", ":monkey:", ":gorilla:", ":dog:", ":dog2:", ":poodle:", ":wolf:", ":fox:", ":cat:", ":cat2:", ":lion_face:", ":tiger:", ":tiger2:", ":leopard:", ":horse:", ":racehorse:", ":unicorn:", ":zebra:", ":deer:", ":cow:", ":ox:", ":water_buffalo:", ":cow2:", ":pig:", ":pig2:", ":boar:", ":pig_nose:", ":ram:", ":sheep:", ":goat:", ":dromedary_camel:", ":camel:", ":giraffe:", ":elephant:", ":rhino:", ":mouse:", ":mouse2:", ":rat:", ":hamster:", ":rabbit:", ":rabbit2:", ":chipmunk:", ":hedgehog:", ":bat:", ":bear:", ":koala:", ":panda_face:", ":feet:", ":turkey:", ":chicken:", ":rooster:", ":hatching_chick:", ":baby_chick:", ":hatched_chick:", ":bird:", ":penguin:", ":dove:", ":eagle:", ":duck:", ":owl:", ":frog:", ":crocodile:", ":turtle:", ":lizard:", ":snake:", ":dragon_face:", ":dragon:", ":sauropod:", ":t_rex:", ":whale:", ":whale2:", ":dolphin:", ":fish:", ":tropical_fish:", ":blowfish:", ":shark:", ":octopus:", ":shell:", ":crab:", ":shrimp:", ":squid:", ":snail:", ":butterfly:", ":bug:", ":ant:", ":bee:", ":beetle:", ":cricket:", ":spider:", ":spider_web:", ":scorpion:", ":bouquet:", ":cherry_blossom:", ":white_flower:", ":rosette:", ":rose:", ":wilted_rose:", ":hibiscus:", ":sunflower:", ":blossom:", ":tulip:", ":seedling:", ":evergreen_tree:", ":deciduous_tree:", ":palm_tree:", ":cactus:", ":ear_of_rice:", ":herb:", ":shamrock:", ":four_leaf_clover:", ":maple_leaf:", ":fallen_leaf:", ":leaves:", ":grapes:", ":melon:", ":watermelon:", ":tangerine:", ":lemon:", ":banana:", ":pineapple:", ":apple:", ":green_apple:", ":pear:", ":peach:", ":cherries:", ":strawberry:", ":kiwi:", ":tomato:", ":coconut:", ":avocado:", ":eggplant:", ":potato:", ":carrot:", ":corn:", ":hot_pepper:", ":cucumber:", ":broccoli:", ":mushroom:", ":peanuts:", ":chestnut:", ":bread:", ":croissant:", ":french_bread:", ":pretzel:", ":pancakes:", ":cheese:", ":meat_on_bone:", ":poultry_leg:", ":cut_of_meat:", ":bacon:", ":hamburger:", ":fries:", ":pizza:", ":hotdog:", ":sandwich:", ":taco:", ":burrito:", ":stuffed_flatbread:", ":egg:", ":cooking:", ":shallow_pan_of_food:", ":stew:", ":bowl_with_spoon:", ":salad:", ":popcorn:", ":canned_food:", ":bento:", ":rice_cracker:", ":rice_ball:", ":rice:", ":curry:", ":ramen:", ":spaghetti:", ":sweet_potato:", ":oden:", ":sushi:", ":fried_shrimp:", ":fish_cake:", ":dango:", ":dumpling:", ":fortune_cookie:", ":takeout_box:", ":icecream:", ":shaved_ice:", ":ice_cream:", ":doughnut:", ":cookie:", ":birthday:", ":cake:", ":pie:", ":chocolate_bar:", ":candy:", ":lollipop:", ":custard:", ":honey_pot:", ":baby_bottle:", ":milk:", ":coffee:", ":tea:", ":sake:", ":champagne:", ":wine_glass:", ":cocktail:", ":tropical_drink:", ":beer:", ":beers:", ":champagne_glass:", ":tumbler_glass:", ":cup_with_straw:", ":chopsticks:", ":fork_knife_plate:", ":fork_and_knife:", ":spoon:", ":knife:", ":amphora:", ":earth_africa:", ":earth_americas:", ":earth_asia:", ":globe_with_meridians:", ":map:", ":japan:", ":mountain_snow:", ":mountain:", ":volcano:", ":mount_fuji:", ":camping:", ":beach:", ":desert:", ":island:", ":park:", ":stadium:", ":classical_building:", ":construction_site:", ":homes:", ":cityscape:", ":house_abandoned:", ":house:", ":house_with_garden:", ":office:", ":post_office:", ":european_post_office:", ":hospital:", ":bank:", ":hotel:", ":love_hotel:", ":convenience_store:", ":school:", ":department_store:", ":factory:", ":japanese_castle:", ":european_castle:", ":wedding:", ":tokyo_tower:", ":statue_of_liberty:", ":church:", ":mosque:", ":synagogue:", ":shinto_shrine:", ":kaaba:", ":fountain:", ":tent:", ":foggy:", ":night_with_stars:", ":sunrise_over_mountains:", ":sunrise:", ":city_dusk:", ":city_sunset:", ":bridge_at_night:", ":hotsprings:", ":milky_way:", ":carousel_horse:", ":ferris_wheel:", ":roller_coaster:", ":barber:", ":circus_tent:", ":performing_arts:", ":frame_photo:", ":art:", ":slot_machine:", ":steam_locomotive:", ":railway_car:", ":bullettrain_side:", ":bullettrain_front:", ":train2:", ":metro:", ":light_rail:", ":station:", ":tram:", ":monorail:", ":mountain_railway:", ":train:", ":bus:", ":oncoming_bus:", ":trolleybus:", ":minibus:", ":ambulance:", ":fire_engine:", ":police_car:", ":oncoming_police_car:", ":taxi:", ":oncoming_taxi:", ":red_car:", ":oncoming_automobile:", ":blue_car:", ":truck:", ":articulated_lorry:", ":tractor:", ":bike:", ":scooter:", ":motor_scooter:", ":busstop:", ":motorway:", ":railway_track:", ":fuelpump:", ":rotating_light:", ":traffic_light:", ":vertical_traffic_light:", ":construction:", ":octagonal_sign:", ":anchor:", ":sailboat:", ":canoe:", ":speedboat:", ":cruise_ship:", ":ferry:", ":motorboat:", ":ship:", ":airplane:", ":airplane_small:", ":airplane_departure:", ":airplane_arriving:", ":seat:", ":helicopter:", ":suspension_railway:", ":mountain_cableway:", ":aerial_tramway:", ":satellite_orbital:", ":rocket:", ":flying_saucer:", ":bellhop:", ":door:", ":bed:", ":couch:", ":toilet:", ":shower:", ":bathtub:", ":hourglass:", ":hourglass_flowing_sand:", ":watch:", ":alarm_clock:", ":stopwatch:", ":timer:", ":clock:", ":clock12:", ":clock1230:", ":clock1:", ":clock130:", ":clock2:", ":clock230:", ":clock3:", ":clock330:", ":clock4:", ":clock430:", ":clock5:", ":clock530:", ":clock6:", ":clock630:", ":clock7:", ":clock730:", ":clock8:", ":clock830:", ":clock9:", ":clock930:", ":clock10:", ":clock1030:", ":clock11:", ":clock1130:", ":new_moon:", ":waxing_crescent_moon:", ":first_quarter_moon:", ":waxing_gibbous_moon:", ":full_moon:", ":waning_gibbous_moon:", ":last_quarter_moon:", ":waning_crescent_moon:", ":crescent_moon:", ":new_moon_with_face:", ":first_quarter_moon_with_face:", ":last_quarter_moon_with_face:", ":thermometer:", ":sunny:", ":full_moon_with_face:", ":sun_with_face:", ":star:", ":star2:", ":stars:", ":cloud:", ":partly_sunny:", ":thunder_cloud_rain:", ":white_sun_small_cloud:", ":white_sun_cloud:", ":white_sun_rain_cloud:", ":cloud_rain:", ":cloud_snow:", ":cloud_lightning:", ":cloud_tornado:", ":fog:", ":wind_blowing_face:", ":cyclone:", ":rainbow:", ":closed_umbrella:", ":umbrella2:", ":umbrella:", ":beach_umbrella:", ":zap:", ":snowflake:", ":snowman2:", ":snowman:", ":comet:", ":fire:", ":droplet:", ":ocean:", ":jack_o_lantern:", ":christmas_tree:", ":fireworks:", ":sparkler:", ":sparkles:", ":balloon:", ":tada:", ":confetti_ball:", ":tanabata_tree:", ":bamboo:", ":dolls:", ":flags:", ":wind_chime:", ":rice_scene:", ":ribbon:", ":gift:", ":reminder_ribbon:", ":tickets:", ":ticket:", ":military_medal:", ":trophy:", ":medal:", ":first_place:", ":second_place:", ":third_place:", ":soccer:", ":baseball:", ":basketball:", ":volleyball:", ":football:", ":rugby_football:", ":tennis:", ":8ball:", ":bowling:", ":cricket_game:", ":field_hockey:", ":hockey:", ":ping_pong:", ":badminton:", ":boxing_glove:", ":martial_arts_uniform:", ":goal:", ":dart:", ":golf:", ":ice_skate:", ":fishing_pole_and_fish:", ":running_shirt_with_sash:", ":ski:", ":sled:", ":curling_stone:", ":video_game:", ":joystick:", ":game_die:", ":spades:", ":hearts:", ":diamonds:", ":clubs:", ":black_joker:", ":mahjong:", ":flower_playing_cards:", ":mute:", ":speaker:", ":sound:", ":loud_sound:", ":loudspeaker:", ":mega:", ":postal_horn:", ":bell:", ":no_bell:", ":musical_score:", ":musical_note:", ":notes:", ":microphone2:", ":level_slider:", ":control_knobs:", ":microphone:", ":headphones:", ":radio:", ":saxophone:", ":guitar:", ":musical_keyboard:", ":trumpet:", ":violin:", ":drum:", ":iphone:", ":calling:", ":telephone:", ":telephone_receiver:", ":pager:", ":fax:", ":battery:", ":electric_plug:", ":computer:", ":desktop:", ":printer:", ":keyboard:", ":mouse_three_button:", ":trackball:", ":minidisc:", ":floppy_disk:", ":cd:", ":dvd:", ":movie_camera:", ":film_frames:", ":projector:", ":clapper:", ":tv:", ":camera:", ":camera_with_flash:", ":video_camera:", ":vhs:", ":mag:", ":mag_right:", ":microscope:", ":telescope:", ":satellite:", ":candle:", ":bulb:", ":flashlight:", ":izakaya_lantern:", ":notebook_with_decorative_cover:", ":closed_book:", ":book:", ":green_book:", ":blue_book:", ":orange_book:", ":books:", ":notebook:", ":ledger:", ":page_with_curl:", ":scroll:", ":page_facing_up:", ":newspaper:", ":newspaper2:", ":bookmark_tabs:", ":bookmark:", ":label:", ":moneybag:", ":yen:", ":dollar:", ":euro:", ":pound:", ":money_with_wings:", ":credit_card:", ":chart:", ":currency_exchange:", ":heavy_dollar_sign:", ":envelope:", ":e-mail:", ":incoming_envelope:", ":envelope_with_arrow:", ":outbox_tray:", ":inbox_tray:", ":package:", ":mailbox:", ":mailbox_closed:", ":mailbox_with_mail:", ":mailbox_with_no_mail:", ":postbox:", ":ballot_box:", ":pencil2:", ":black_nib:", ":pen_fountain:", ":pen_ballpoint:", ":paintbrush:", ":crayon:", ":pencil:", ":briefcase:", ":file_folder:", ":open_file_folder:", ":dividers:", ":date:", ":calendar:", ":notepad_spiral:", ":calendar_spiral:", ":card_index:", ":chart_with_upwards_trend:", ":chart_with_downwards_trend:", ":bar_chart:", ":clipboard:", ":pushpin:", ":round_pushpin:", ":paperclip:", ":paperclips:", ":straight_ruler:", ":triangular_ruler:", ":scissors:", ":card_box:", ":file_cabinet:", ":wastebasket:", ":lock:", ":unlock:", ":lock_with_ink_pen:", ":closed_lock_with_key:", ":key:", ":key2:", ":hammer:", ":pick:", ":hammer_pick:", ":tools:", ":dagger:", ":crossed_swords:", ":gun:", ":bow_and_arrow:", ":shield:", ":wrench:", ":nut_and_bolt:", ":gear:", ":compression:", ":alembic:", ":scales:", ":link:", ":chains:", ":syringe:", ":pill:", ":smoking:", ":coffin:", ":urn:", ":moyai:", ":oil:", ":crystal_ball:", ":shopping_cart:", ":atm:", ":put_litter_in_its_place:", ":potable_water:", ":wheelchair:", ":mens:", ":womens:", ":restroom:", ":baby_symbol:", ":wc:", ":passport_control:", ":customs:", ":baggage_claim:", ":left_luggage:", ":warning:", ":children_crossing:", ":no_entry:", ":no_entry_sign:", ":no_bicycles:", ":no_smoking:", ":do_not_litter:", ":non-potable_water:", ":no_pedestrians:", ":no_mobile_phones:", ":underage:", ":radioactive:", ":biohazard:", ":arrow_up:", ":arrow_upper_right:", ":arrow_right:", ":arrow_lower_right:", ":arrow_down:", ":arrow_lower_left:", ":arrow_left:", ":arrow_upper_left:", ":arrow_up_down:", ":left_right_arrow:", ":leftwards_arrow_with_hook:", ":arrow_right_hook:", ":arrow_heading_up:", ":arrow_heading_down:", ":arrows_clockwise:", ":arrows_counterclockwise:", ":back:", ":end:", ":on:", ":soon:", ":top:", ":place_of_worship:", ":atom:", ":om_symbol:", ":star_of_david:", ":wheel_of_dharma:", ":yin_yang:", ":cross:", ":orthodox_cross:", ":star_and_crescent:", ":peace:", ":menorah:", ":six_pointed_star:", ":aries:", ":taurus:", ":gemini:", ":cancer:", ":leo:", ":virgo:", ":libra:", ":scorpius:", ":sagittarius:", ":capricorn:", ":aquarius:", ":pisces:", ":ophiuchus:", ":twisted_rightwards_arrows:", ":repeat:", ":repeat_one:", ":arrow_forward:", ":fast_forward:", ":track_next:", ":play_pause:", ":arrow_backward:", ":rewind:", ":track_previous:", ":arrow_up_small:", ":arrow_double_up:", ":arrow_down_small:", ":arrow_double_down:", ":pause_button:", ":stop_button:", ":record_button:", ":eject:", ":cinema:", ":low_brightness:", ":high_brightness:", ":signal_strength:", ":vibration_mode:", ":mobile_phone_off:", ":female_sign:", ":male_sign:", ":medical_symbol:", ":recycle:", ":fleur-de-lis:", ":trident:", ":name_badge:", ":beginner:", ":o:", ":white_check_mark:", ":ballot_box_with_check:", ":heavy_check_mark:", ":heavy_multiplication_x:", ":x:", ":negative_squared_cross_mark:", ":heavy_plus_sign:", ":heavy_minus_sign:", ":heavy_division_sign:", ":curly_loop:", ":loop:", ":part_alternation_mark:", ":eight_spoked_asterisk:", ":eight_pointed_black_star:", ":sparkle:", ":bangbang:", ":interrobang:", ":question:", ":grey_question:", ":grey_exclamation:", ":exclamation:", ":wavy_dash:", ":copyright:", ":registered:", ":tm:", ":hash:", ":asterisk:", ":zero:", ":one:", ":two:", ":three:", ":four:", ":five:", ":six:", ":seven:", ":eight:", ":nine:", ":keycap_ten:", ":100:", ":capital_abcd:", ":abcd:", ":1234:", ":symbols:", ":abc:", ":a:", ":ab:", ":b:", ":cl:", ":cool:", ":free:", ":information_source:", ":id:", ":m:", ":new:", ":ng:", ":o2:", ":ok:", ":parking:", ":sos:", ":up:", ":vs:", ":koko:", ":sa:", ":u6708:", ":u6709:", ":u6307:", ":ideograph_advantage:", ":u5272:", ":u7121:", ":u7981:", ":accept:", ":u7533:", ":u5408:", ":u7a7a:", ":congratulations:", ":secret:", ":u55b6:", ":u6e80:", ":black_small_square:", ":white_small_square:", ":white_medium_square:", ":black_medium_square:", ":white_medium_small_square:", ":black_medium_small_square:", ":black_large_square:", ":white_large_square:", ":large_orange_diamond:", ":large_blue_diamond:", ":small_orange_diamond:", ":small_blue_diamond:", ":small_red_triangle:", ":small_red_triangle_down:", ":diamond_shape_with_a_dot_inside:", ":radio_button:", ":black_square_button:", ":white_square_button:", ":white_circle:", ":black_circle:", ":red_circle:", ":blue_circle:", ":checkered_flag:", ":triangular_flag_on_post:", ":crossed_flags:", ":flag_black:", ":flag_white:", ":rainbow_flag:", ":flag_ac:", ":flag_ad:", ":flag_ae:", ":flag_af:", ":flag_ag:", ":flag_ai:", ":flag_al:", ":flag_am:", ":flag_ao:", ":flag_aq:", ":flag_ar:", ":flag_as:", ":flag_at:", ":flag_au:", ":flag_aw:", ":flag_ax:", ":flag_az:", ":flag_ba:", ":flag_bb:", ":flag_bd:", ":flag_be:", ":flag_bf:", ":flag_bg:", ":flag_bh:", ":flag_bi:", ":flag_bj:", ":flag_bl:", ":flag_bm:", ":flag_bn:", ":flag_bo:", ":flag_bq:", ":flag_br:", ":flag_bs:", ":flag_bt:", ":flag_bv:", ":flag_bw:", ":flag_by:", ":flag_bz:", ":flag_ca:", ":flag_cc:", ":flag_cd:", ":flag_cf:", ":flag_cg:", ":flag_ch:", ":flag_ci:", ":flag_ck:", ":flag_cl:", ":flag_cm:", ":flag_cn:", ":flag_co:", ":flag_cp:", ":flag_cr:", ":flag_cu:", ":flag_cv:", ":flag_cw:", ":flag_cx:", ":flag_cy:", ":flag_cz:", ":flag_de:", ":flag_dg:", ":flag_dj:", ":flag_dk:", ":flag_dm:", ":flag_do:", ":flag_dz:", ":flag_ea:", ":flag_ec:", ":flag_ee:", ":flag_eg:", ":flag_eh:", ":flag_er:", ":flag_es:", ":flag_et:", ":flag_eu:", ":flag_fi:", ":flag_fj:", ":flag_fk:", ":flag_fm:", ":flag_fo:", ":flag_fr:", ":flag_ga:", ":flag_gb:", ":flag_gd:", ":flag_ge:", ":flag_gf:", ":flag_gg:", ":flag_gh:", ":flag_gi:", ":flag_gl:", ":flag_gm:", ":flag_gn:", ":flag_gp:", ":flag_gq:", ":flag_gr:", ":flag_gs:", ":flag_gt:", ":flag_gu:", ":flag_gw:", ":flag_gy:", ":flag_hk:", ":flag_hm:", ":flag_hn:", ":flag_hr:", ":flag_ht:", ":flag_hu:", ":flag_ic:", ":flag_id:", ":flag_ie:", ":flag_il:", ":flag_im:", ":flag_in:", ":flag_io:", ":flag_iq:", ":flag_ir:", ":flag_is:", ":flag_it:", ":flag_je:", ":flag_jm:", ":flag_jo:", ":flag_jp:", ":flag_ke:", ":flag_kg:", ":flag_kh:", ":flag_ki:", ":flag_km:", ":flag_kn:", ":flag_kp:", ":flag_kr:", ":flag_kw:", ":flag_ky:", ":flag_kz:", ":flag_la:", ":flag_lb:", ":flag_lc:", ":flag_li:", ":flag_lk:", ":flag_lr:", ":flag_ls:", ":flag_lt:", ":flag_lu:", ":flag_lv:", ":flag_ly:", ":flag_ma:", ":flag_mc:", ":flag_md:", ":flag_me:", ":flag_mf:", ":flag_mg:", ":flag_mh:", ":flag_mk:", ":flag_ml:", ":flag_mm:", ":flag_mn:", ":flag_mo:", ":flag_mp:", ":flag_mq:", ":flag_mr:", ":flag_ms:", ":flag_mt:", ":flag_mu:", ":flag_mv:", ":flag_mw:", ":flag_mx:", ":flag_my:", ":flag_mz:", ":flag_na:", ":flag_nc:", ":flag_ne:", ":flag_nf:", ":flag_ng:", ":flag_ni:", ":flag_nl:", ":flag_no:", ":flag_np:", ":flag_nr:", ":flag_nu:", ":flag_nz:", ":flag_om:", ":flag_pa:", ":flag_pe:", ":flag_pf:", ":flag_pg:", ":flag_ph:", ":flag_pk:", ":flag_pl:", ":flag_pm:", ":flag_pn:", ":flag_pr:", ":flag_ps:", ":flag_pt:", ":flag_pw:", ":flag_py:", ":flag_qa:", ":flag_re:", ":flag_ro:", ":flag_rs:", ":flag_ru:", ":flag_rw:", ":flag_sa:", ":flag_sb:", ":flag_sc:", ":flag_sd:", ":flag_se:", ":flag_sg:", ":flag_sh:", ":flag_si:", ":flag_sj:", ":flag_sk:", ":flag_sl:", ":flag_sm:", ":flag_sn:", ":flag_so:", ":flag_sr:", ":flag_ss:", ":flag_st:", ":flag_sv:", ":flag_sx:", ":flag_sy:", ":flag_sz:", ":flag_ta:", ":flag_tc:", ":flag_td:", ":flag_tf:", ":flag_tg:", ":flag_th:", ":flag_tj:", ":flag_tk:", ":flag_tl:", ":flag_tm:", ":flag_tn:", ":flag_to:", ":flag_tr:", ":flag_tt:", ":flag_tv:", ":flag_tw:", ":flag_tz:", ":flag_ua:", ":flag_ug:", ":flag_um:", ":united_nations:", ":flag_us:", ":flag_uy:", ":flag_uz:", ":flag_va:", ":flag_vc:", ":flag_ve:", ":flag_vg:", ":flag_vi:", ":flag_vn:", ":flag_vu:", ":flag_wf:", ":flag_ws:", ":flag_xk:", ":flag_ye:", ":flag_yt:", ":flag_za:", ":flag_zm:", ":flag_zw:", ":england:", ":scotland:", ":wales:"} From 5aeb62ef187e4be37d4b72d95ffa42fcd5138ef2 Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Thu, 17 Oct 2019 10:03:04 -0600 Subject: [PATCH 20/53] moved tab completion to separate file, updated handleTab() to new structure. Created new global to hold a copy of the tab completion slice thats always up to date instead of generating each time, hooked completion options updates into view updates and channel join activity. --- main.go | 131 +++------------------------------------------ tabComplete.go | 142 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 148 insertions(+), 125 deletions(-) create mode 100644 tabComplete.go diff --git a/main.go b/main.go index 82cc72b..f090b7e 100644 --- a/main.go +++ b/main.go @@ -52,6 +52,7 @@ func main() { if err := g.MainLoop(); err != nil && !gocui.IsQuit(err) { fmt.Printf("%+v", err) } + go generateChannelTabCompletionSlice() } // Gocui basic setup @@ -277,6 +278,7 @@ func populateChat() { return } else { go populateChat() + go generateChannelTabCompletionSlice() return } } @@ -338,6 +340,7 @@ func populateList() { } time.Sleep(1 * time.Millisecond) printToView("List", fmt.Sprintf("%s%s%s%s", channelsColor, recentPMs, recentChannels, noColor)) + go generateRecentTabCompletionSlice() } } @@ -383,126 +386,6 @@ func formatOutput(api keybase.ChatAPI) string { // End formatting -// Tab completion -func getCurrentChannelMembership() []string { - var rs []string - if channel.Name != "" { - t := k.NewTeam(channel.Name) - if testVar, err := t.MemberList(); err != nil { - return rs // then this isn't a team, its a PM or there was an error in the API call - } else { - for _, m := range testVar.Result.Members.Owners { - rs = append(rs, fmt.Sprintf("%+v", m.Username)) - } - for _, m := range testVar.Result.Members.Admins { - rs = append(rs, fmt.Sprintf("%+v", m.Username)) - } - for _, m := range testVar.Result.Members.Writers { - rs = append(rs, fmt.Sprintf("%+v", m.Username)) - } - for _, m := range testVar.Result.Members.Readers { - rs = append(rs, fmt.Sprintf("%+v", m.Username)) - } - } - } - return rs -} -func filterStringSlice(ss []string, fv string) []string { - var rs []string - for _, s := range ss { - if strings.HasPrefix(s, fv) { - rs = append(rs, s) - } - } - return rs -} -func longestCommonPrefix(ss []string) string { - // cover the case where the slice has no or one members - switch len(ss) { - case 0: - return "" - case 1: - return ss[0] - } - // all strings are compared by bytes here forward (TBD unicode normalization?) - // establish min, max lenth members of the slice by iterating over the members - min, max := ss[0], ss[0] - for _, s := range ss[1:] { - switch { - case s < min: - min = s - case s > max: - max = s - } - } - // then iterate over the characters from min to max, as soon as chars don't match return - for i := 0; i < len(min) && i < len(max); i++ { - if min[i] != max[i] { - return min[:i] - } - } - // to cover the case where all members are equal, just return one - return min -} -func stringRemainder(aStr, bStr string) string { - var long, short string - //figure out which string is longer - switch { - case len(aStr) < len(bStr): - short = aStr - long = bStr - default: - short = bStr - long = aStr - } - // iterate over the strings using an external iterator so we don't lose the value - i := 0 - for i < len(short) && i < len(long) { - if short[i] != long[i] { - // the strings aren't equal so don't return anything - return "" - } - i++ - } - // return whatever's left of the longer string - return long[i:] -} -func appendIfNotInSlice(ss []string, s string) []string { - for _, element := range ss { - if element == s { - return ss - } - } - return append(ss, s) -} -func generateChannelTabCompletionSlice(inputWord string) []string { - // create a slice to hold the values - var firstSlice []string - // iterate over all the conversation results - for _, s := range channels { - if s.MembersType == keybase.TEAM { - // its a team so add the topic name as a possible tab completion - firstSlice = appendIfNotInSlice(firstSlice, s.TopicName) - firstSlice = appendIfNotInSlice(firstSlice, s.Name) - } else { - // its a user, so clean the name and append the users name as a possible tab completion - firstSlice = appendIfNotInSlice(firstSlice, cleanChannelName(s.Name)) - } - } - // next fetch all members of the current channel and add them to the slice - secondSlice := getCurrentChannelMembership() - for _, m := range secondSlice { - firstSlice = appendIfNotInSlice(firstSlice, m) - } - // now return the resultSlice which contains all that are prefixed with inputWord - resultSlice := filterStringSlice(firstSlice, inputWord) - return resultSlice -} -func generateEmojiTabCompletionSlice(inputWord string) []string { - // use the emojiSlice from emojiList.go and filter it for the input word - resultSlice := filterStringSlice(emojiSlice, inputWord) - return resultSlice -} func handleTab() error { inputString, err := getInputString("Input") if err != nil { @@ -515,14 +398,14 @@ func handleTab() error { var resultSlice []string // if the word starts with a : its an emoji lookup if strings.HasPrefix(s, ":") { - resultSlice = generateEmojiTabCompletionSlice(s) + resultSlice = getEmojiTabCompletionSlice(s) } else { - // now in case the word (s) is a mention @something, lets remove it to normalize if strings.HasPrefix(s, "@") { + // now in case the word (s) is a mention @something, lets remove it to normalize s = strings.Replace(s, "@", "", 1) } // now call get the list of all possible cantidates that have that as a prefix - resultSlice = generateChannelTabCompletionSlice(s) + resultSlice = getChannelTabCompletionSlice(s) } rLen := len(resultSlice) lcp := longestCommonPrefix(resultSlice) @@ -546,8 +429,6 @@ func handleTab() error { return nil } -// End tab completion - // Input handling func handleMessage(api keybase.ChatAPI) { if _, ok := typeCommands[api.Msg.Content.Type]; ok { diff --git a/tabComplete.go b/tabComplete.go new file mode 100644 index 0000000..1271064 --- /dev/null +++ b/tabComplete.go @@ -0,0 +1,142 @@ +package main + +import ( + "fmt" + "strings" + + "samhofi.us/x/keybase" +) + +var ( + tabSlice []string +) + +// 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) + return resultSlice +} +func getChannelTabCompletionSlice(inputWord string) []string { + // use the tabSlice from above and filter it for the input word + resultSlice := filterStringSlice(tabSlice, inputWord) + return resultSlice +} + +//Generator Functions +func generateChannelTabCompletionSlice() { + // fetch all members of the current channel and add them to the slice + channelSlice := getCurrentChannelMembership() + for _, m := range channelSlice { + tabSlice = appendIfNotInSlice(tabSlice, m) + } +} +func generateRecentTabCompletionSlice() { + var recentSlice []string + for _, s := range channels { + if s.MembersType == keybase.TEAM { + // its a team so add the topic name and channel name + recentSlice = appendIfNotInSlice(recentSlice, s.TopicName) + recentSlice = appendIfNotInSlice(recentSlice, s.Name) + } else { + //its a user, so clean the name and append + recentSlice = appendIfNotInSlice(recentSlice, cleanChannelName(s.Name)) + } + } + for _, s := range recentSlice { + tabSlice = appendIfNotInSlice(tabSlice, s) + } +} + +// Helper functions +func getCurrentChannelMembership() []string { + var rs []string + if channel.Name != "" { + t := k.NewTeam(channel.Name) + if testVar, err := t.MemberList(); err != nil { + return rs // then this isn't a team, its a PM or there was an error in the API call + } else { + for _, m := range testVar.Result.Members.Owners { + rs = append(rs, fmt.Sprintf("%+v", m.Username)) + } + for _, m := range testVar.Result.Members.Admins { + rs = append(rs, fmt.Sprintf("%+v", m.Username)) + } + for _, m := range testVar.Result.Members.Writers { + rs = append(rs, fmt.Sprintf("%+v", m.Username)) + } + for _, m := range testVar.Result.Members.Readers { + rs = append(rs, fmt.Sprintf("%+v", m.Username)) + } + } + } + return rs +} +func filterStringSlice(ss []string, fv string) []string { + var rs []string + for _, s := range ss { + if strings.HasPrefix(s, fv) { + rs = append(rs, s) + } + } + return rs +} +func longestCommonPrefix(ss []string) string { + // cover the case where the slice has no or one members + switch len(ss) { + case 0: + return "" + case 1: + return ss[0] + } + // all strings are compared by bytes here forward (TBD unicode normalization?) + // establish min, max lenth members of the slice by iterating over the members + min, max := ss[0], ss[0] + for _, s := range ss[1:] { + switch { + case s < min: + min = s + case s > max: + max = s + } + } + // then iterate over the characters from min to max, as soon as chars don't match return + for i := 0; i < len(min) && i < len(max); i++ { + if min[i] != max[i] { + return min[:i] + } + } + // to cover the case where all members are equal, just return one + return min +} +func stringRemainder(aStr, bStr string) string { + var long, short string + //figure out which string is longer + switch { + case len(aStr) < len(bStr): + short = aStr + long = bStr + default: + short = bStr + long = aStr + } + // iterate over the strings using an external iterator so we don't lose the value + i := 0 + for i < len(short) && i < len(long) { + if short[i] != long[i] { + // the strings aren't equal so don't return anything + return "" + } + i++ + } + // return whatever's left of the longer string + return long[i:] +} +func appendIfNotInSlice(ss []string, s string) []string { + for _, element := range ss { + if element == s { + return ss + } + } + return append(ss, s) +} From 5ed7cb6972a905ec261b1d1f4bafc1dfcb7ca361 Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Thu, 17 Oct 2019 11:00:55 -0600 Subject: [PATCH 21/53] moved all tabcomplete to external file --- main.go | 44 -------------------------------------------- tabComplete.go | 48 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 47 insertions(+), 45 deletions(-) diff --git a/main.go b/main.go index f090b7e..9074c9f 100644 --- a/main.go +++ b/main.go @@ -4,7 +4,6 @@ import ( "fmt" "log" "os" - "regexp" "strings" "time" @@ -386,49 +385,6 @@ func formatOutput(api keybase.ChatAPI) string { // End formatting -func handleTab() error { - inputString, err := getInputString("Input") - if err != nil { - return err - } else { - // if you successfully get an input string, grab the last word from the string - ss := regexp.MustCompile(`[ #]`).Split(inputString, -1) - s := ss[len(ss)-1] - // create a variable in which to store the result - var resultSlice []string - // if the word starts with a : its an emoji lookup - if strings.HasPrefix(s, ":") { - resultSlice = getEmojiTabCompletionSlice(s) - } else { - if strings.HasPrefix(s, "@") { - // now in case the word (s) is a mention @something, lets remove it to normalize - s = strings.Replace(s, "@", "", 1) - } - // now call get the list of all possible cantidates that have that as a prefix - resultSlice = getChannelTabCompletionSlice(s) - } - rLen := len(resultSlice) - lcp := longestCommonPrefix(resultSlice) - if lcp != "" { - originalViewTitle := getViewTitle("Input") - newViewTitle := "" - if rLen >= 1 && originalViewTitle != "" { - if rLen == 1 { - newViewTitle = originalViewTitle - } else if rLen <= 5 { - newViewTitle = fmt.Sprintf("%s|| %s", originalViewTitle, strings.Join(resultSlice, " ")) - } else if rLen > 5 { - newViewTitle = fmt.Sprintf("%s|| %s +%d more", originalViewTitle, strings.Join(resultSlice[:6], " "), rLen-5) - } - setViewTitle("Input", newViewTitle) - remainder := stringRemainder(s, lcp) - writeToView("Input", remainder) - } - } - } - return nil -} - // Input handling func handleMessage(api keybase.ChatAPI) { if _, ok := typeCommands[api.Msg.Content.Type]; ok { diff --git a/tabComplete.go b/tabComplete.go index 1271064..7f784b9 100644 --- a/tabComplete.go +++ b/tabComplete.go @@ -1,7 +1,9 @@ +// +build !rm_basic_commands allcommands tabcompletion package main import ( "fmt" + "regexp" "strings" "samhofi.us/x/keybase" @@ -11,6 +13,50 @@ var ( tabSlice []string ) +// This defines the handleTab function thats called by key bindind tab for the input control. +func handleTab() error { + inputString, err := getInputString("Input") + if err != nil { + return err + } else { + // if you successfully get an input string, grab the last word from the string + ss := regexp.MustCompile(`[ #]`).Split(inputString, -1) + s := ss[len(ss)-1] + // create a variable in which to store the result + var resultSlice []string + // if the word starts with a : its an emoji lookup + if strings.HasPrefix(s, ":") { + resultSlice = getEmojiTabCompletionSlice(s) + } else { + if strings.HasPrefix(s, "@") { + // now in case the word (s) is a mention @something, lets remove it to normalize + s = strings.Replace(s, "@", "", 1) + } + // now call get the list of all possible cantidates that have that as a prefix + resultSlice = getChannelTabCompletionSlice(s) + } + rLen := len(resultSlice) + lcp := longestCommonPrefix(resultSlice) + if lcp != "" { + originalViewTitle := getViewTitle("Input") + newViewTitle := "" + if rLen >= 1 && originalViewTitle != "" { + if rLen == 1 { + newViewTitle = originalViewTitle + } else if rLen <= 5 { + newViewTitle = fmt.Sprintf("%s|| %s", originalViewTitle, strings.Join(resultSlice, " ")) + } else if rLen > 5 { + newViewTitle = fmt.Sprintf("%s|| %s +%d more", originalViewTitle, strings.Join(resultSlice[:6], " "), rLen-5) + } + setViewTitle("Input", newViewTitle) + remainder := stringRemainder(s, lcp) + writeToView("Input", remainder) + } + } + } + return nil +} + // Main tab completion functions func getEmojiTabCompletionSlice(inputWord string) []string { // use the emojiSlice from emojiList.go and filter it for the input word @@ -23,7 +69,7 @@ func getChannelTabCompletionSlice(inputWord string) []string { return resultSlice } -//Generator Functions +//Generator Functions (should be called externally when chat/list/join changes func generateChannelTabCompletionSlice() { // fetch all members of the current channel and add them to the slice channelSlice := getCurrentChannelMembership() From f7144f3ad5a0150f6399e8702c2152c06430a974 Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Thu, 17 Oct 2019 11:01:16 -0600 Subject: [PATCH 22/53] updated mage for tabComplete option --- mage.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mage.go b/mage.go index b2737d8..e6d0824 100644 --- a/mage.go +++ b/mage.go @@ -116,5 +116,5 @@ func BuildAllCommandsT() { // Build kbtui with beta functionality func BuildBeta() { mg.Deps(BuildEmoji) - sh.Run("go", "build", "-tags", "allcommands,showreactionscmd,emojiList") + sh.Run("go", "build", "-tags", "allcommands,showreactionscmd,emojiList,tabcompletion") } From dffff400ff8ada034e6819b04fbf3b5ce807a873 Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Thu, 17 Oct 2019 11:04:51 -0600 Subject: [PATCH 23/53] changed handleTab() to accomodate viewName --- main.go | 2 +- tabComplete.go | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/main.go b/main.go index 9074c9f..1a7fc10 100644 --- a/main.go +++ b/main.go @@ -137,7 +137,7 @@ func initKeybindings() error { } if err := g.SetKeybinding("Input", gocui.KeyTab, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error { - return handleTab() + return handleTab("Input") }); err != nil { return err } diff --git a/tabComplete.go b/tabComplete.go index 7f784b9..7bbbb39 100644 --- a/tabComplete.go +++ b/tabComplete.go @@ -14,8 +14,8 @@ var ( ) // This defines the handleTab function thats called by key bindind tab for the input control. -func handleTab() error { - inputString, err := getInputString("Input") +func handleTab(viewName string) error { + inputString, err := getInputString(viewName) if err != nil { return err } else { @@ -48,9 +48,9 @@ func handleTab() error { } else if rLen > 5 { newViewTitle = fmt.Sprintf("%s|| %s +%d more", originalViewTitle, strings.Join(resultSlice[:6], " "), rLen-5) } - setViewTitle("Input", newViewTitle) + setViewTitle(viewName, newViewTitle) remainder := stringRemainder(s, lcp) - writeToView("Input", remainder) + writeToView(viewName, remainder) } } } From d07156402bca8db528382448fb9a756a2417ece1 Mon Sep 17 00:00:00 2001 From: Gregory Rudolph Date: Thu, 17 Oct 2019 13:21:00 -0400 Subject: [PATCH 24/53] Bugfix: Input crashes kbtui #13 --- cmdReply.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cmdReply.go b/cmdReply.go index aa2c231..457daf1 100644 --- a/cmdReply.go +++ b/cmdReply.go @@ -21,13 +21,19 @@ func init() { func cmdReply(cmd []string) { chat := k.NewChat(channel) + if len(cmd) < 2 { + printToView("Feed", fmt.Sprintf("%sreply $ID - Reply to message $ID", cmdPrefix)) + return + } messageID, err := strconv.Atoi(cmd[1]) if err != nil { printToView("Feed", fmt.Sprintf("There was an error determining message ID %s", cmd[1])) + return } _, err = chat.Reply(messageID, strings.Join(cmd[2:], " ")) if err != nil { printToView("Feed", "There was an error with your reply.") + return } return } From 73d9bfdd5dc46b2ec663a8acea59966e7e52833d Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Thu, 17 Oct 2019 13:19:07 -0600 Subject: [PATCH 25/53] added command tab completion --- tabComplete.go | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/tabComplete.go b/tabComplete.go index 7bbbb39..0391c1a 100644 --- a/tabComplete.go +++ b/tabComplete.go @@ -10,7 +10,8 @@ import ( ) var ( - tabSlice []string + tabSlice []string + commandSlice []string ) // This defines the handleTab function thats called by key bindind tab for the input control. @@ -27,6 +28,10 @@ func handleTab(viewName string) error { // if the word starts with a : its an emoji lookup if strings.HasPrefix(s, ":") { resultSlice = getEmojiTabCompletionSlice(s) + } else if strings.HasPrefix(s, "/") { + generateCommandTabCompletionSlice() + s = strings.Replace(s, "/", "", 1) + resultSlice = getCommandTabCompletionSlice(s) } else { if strings.HasPrefix(s, "@") { // now in case the word (s) is a mention @something, lets remove it to normalize @@ -68,6 +73,11 @@ func getChannelTabCompletionSlice(inputWord string) []string { resultSlice := filterStringSlice(tabSlice, inputWord) return resultSlice } +func getCommandTabCompletionSlice(inputWord string) []string { + // use the commandSlice from above and filter it for the input word + resultSlice := filterStringSlice(commandSlice, inputWord) + return resultSlice +} //Generator Functions (should be called externally when chat/list/join changes func generateChannelTabCompletionSlice() { @@ -77,6 +87,19 @@ func generateChannelTabCompletionSlice() { tabSlice = appendIfNotInSlice(tabSlice, m) } } +func generateCommandTabCompletionSlice() { + // get the maps of all built commands - this should only need to be done on startup + // removing typeCommands for now, since they aren't actually commands you can type - contrary to the naming + /*for commandString1 := range typeCommands { + commandSlice = appendIfNotInSlice(commandSlice, commandString1) + }*/ + for commandString2 := range commands { + commandSlice = appendIfNotInSlice(commandSlice, commandString2) + } + for _, commandString3 := range baseCommands { + commandSlice = appendIfNotInSlice(commandSlice, commandString3) + } +} func generateRecentTabCompletionSlice() { var recentSlice []string for _, s := range channels { From 8f2b2110e06642aa2ce0dc79a4ed5de63be49026 Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Thu, 17 Oct 2019 13:52:23 -0600 Subject: [PATCH 26/53] propogated build errors to top level. Fixes bug issue #11 'build doesn't return correct status code' --- mage.go | 44 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/mage.go b/mage.go index 164444f..1cb2fcd 100644 --- a/mage.go +++ b/mage.go @@ -82,16 +82,32 @@ func BuildEmoji() error { return nil } +// proper error reporting and exit code +func exit(err error) { + if err != nil { + fmt.Fprintf(os.Stderr, "%+v\n", err) + os.Exit(1) + } +} + // Build kbtui with just the basic commands. func Build() { - sh.Run("go", "build") + if err := sh.Run("go", "build"); err != nil { + defer func() { + exit(err) + }() + } } // Build kbtui with the basic commands, and the ShowReactions "TypeCommand". // The ShowReactions TypeCommand will print a message in the feed window when // a reaction is received in the current conversation. func BuildShowReactions() { - sh.Run("go", "build", "-tags", "showreactionscmd") + if err := sh.Run("go", "build", "-tags", "showreactionscmd"); err != nil { + defer func() { + exit(err) + }() + } } // Build kbtui with the basec commands, and the AutoReact "TypeCommand". @@ -99,21 +115,37 @@ func BuildShowReactions() { // received in the current conversation. This gets pretty annoying, and // is not recommended. func BuildAutoReact() { - sh.Run("go", "build", "-tags", "autoreactcmd") + if err := sh.Run("go", "build", "-tags", "autoreactcmd"); err != nil { + defer func() { + exit(err) + }() + } } // Build kbtui with all commands and TypeCommands disabled. func BuildAllCommands() { - sh.Run("go", "build", "-tags", "allcommands") + if err := sh.Run("go", "build", "-tags", "allcommands"); err != nil { + defer func() { + exit(err) + }() + } } // Build kbtui with all Commands and TypeCommands enabled. func BuildAllCommandsT() { - sh.Run("go", "build", "-tags", "type_commands,allcommands") + if err := sh.Run("go", "build", "-tags", "type_commands,allcommands"); err != nil { + defer func() { + exit(err) + }() + } } // Build kbtui with beta functionality func BuildBeta() { mg.Deps(BuildEmoji) - sh.Run("go", "build", "-tags", "allcommands,showreactionscmd,emojiList") + if err := sh.Run("go", "build", "-tags", "allcommands,showreactionscmd,emojiList,tabcompletion"); err != nil { + defer func() { + exit(err) + }() + } } From 09c207d4be7f0924950fb775ad55664668b3dc80 Mon Sep 17 00:00:00 2001 From: Gregory Rudolph Date: Fri, 18 Oct 2019 07:27:09 -0400 Subject: [PATCH 27/53] Bugfix: extra junk after +/- commands cause crash #20 --- main.go | 1 + 1 file changed, 1 insertion(+) diff --git a/main.go b/main.go index dd3bf0e..050047b 100644 --- a/main.go +++ b/main.go @@ -649,6 +649,7 @@ func handleInput(viewName string) error { } if inputString[:1] == "+" || inputString[:1] == "-" { cmd := strings.Split(inputString, " ") + cmd[0] = inputString[:1] RunCommand(cmd...) } else { go sendChat(inputString) From dd634fd06b374537a74eeb2d8c3ea1fb5b498d83 Mon Sep 17 00:00:00 2001 From: Gregory Rudolph Date: Fri, 18 Oct 2019 07:33:29 -0400 Subject: [PATCH 28/53] Bugfix: another command crashes kbtui #19 --- cmdDownload.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cmdDownload.go b/cmdDownload.go index 9affc93..8bfd1d6 100644 --- a/cmdDownload.go +++ b/cmdDownload.go @@ -19,6 +19,11 @@ func init() { } func cmdDownloadFile(cmd []string) { + + if len(cmd) < 2 { + printToView("Feed", fmt.Sprintf("/%s $messageId $fileName - Download a file to user's downloadpath", cmd[0])) + return + } messageID, _ := strconv.Atoi(cmd[1]) var fileName string if len(cmd) == 3 { From 05764f85d9a6418f5b6c236bdebbc10bd387cfe0 Mon Sep 17 00:00:00 2001 From: Gregory Rudolph Date: Fri, 18 Oct 2019 07:40:14 -0400 Subject: [PATCH 29/53] Bugfix similar to #19 --- cmdUploadFile.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmdUploadFile.go b/cmdUploadFile.go index 4fd9da7..3d76d4f 100644 --- a/cmdUploadFile.go +++ b/cmdUploadFile.go @@ -20,6 +20,10 @@ func init() { } func cmdUploadFile(cmd []string) { + if len(cmd) < 2 { + printToView("Feed", fmt.Sprintf("%s%s $filePath $fileName - Upload file from absolute path with optional name", cmdPrefix, cmd[0])) + return + } filePath := cmd[1] if !strings.HasPrefix(filePath, "/") { dir, err := os.Getwd() From 931582ed6769d106f49501e681bf7bdb9f0883e5 Mon Sep 17 00:00:00 2001 From: Gregory Rudolph Date: Fri, 18 Oct 2019 07:40:47 -0400 Subject: [PATCH 30/53] Use cmdPrefix instead of hardcoded / --- cmdDownload.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmdDownload.go b/cmdDownload.go index 8bfd1d6..ff6cbbf 100644 --- a/cmdDownload.go +++ b/cmdDownload.go @@ -21,7 +21,7 @@ func init() { func cmdDownloadFile(cmd []string) { if len(cmd) < 2 { - printToView("Feed", fmt.Sprintf("/%s $messageId $fileName - Download a file to user's downloadpath", cmd[0])) + printToView("Feed", fmt.Sprintf("%s%s $messageId $fileName - Download a file to user's downloadpath", cmdPrefix, cmd[0])) return } messageID, _ := strconv.Atoi(cmd[1]) From cc129d466f503f62316f63ca77b0ea801e8fa5b4 Mon Sep 17 00:00:00 2001 From: Gregory Rudolph Date: Fri, 18 Oct 2019 07:42:07 -0400 Subject: [PATCH 31/53] Show command used instead of hardcoded reply --- cmdReply.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmdReply.go b/cmdReply.go index 457daf1..898d5e3 100644 --- a/cmdReply.go +++ b/cmdReply.go @@ -22,7 +22,7 @@ func init() { func cmdReply(cmd []string) { chat := k.NewChat(channel) if len(cmd) < 2 { - printToView("Feed", fmt.Sprintf("%sreply $ID - Reply to message $ID", cmdPrefix)) + printToView("Feed", fmt.Sprintf("%s%s $ID - Reply to message $ID", cmdPrefix, cmd[0])) return } messageID, err := strconv.Atoi(cmd[1]) From 09fb7ae56cb9e4796ccfb65a0abde9f7962a5f9d Mon Sep 17 00:00:00 2001 From: Gregory Rudolph Date: Fri, 18 Oct 2019 08:16:58 -0400 Subject: [PATCH 32/53] Update readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 220778f..3ce29d2 100644 --- a/README.md +++ b/README.md @@ -42,5 +42,5 @@ You may see an error with `go get ./` about PATHs, that may be safely ignored. If you see an error about a missing dependancy during a build, you'll want to resolve that. -Occasionally when @dxb updates his API it will be necessary to run -`go get -u ./` +Occasionally when [@dxb](https://keybase.io/dxb) updates his API it will be necessary to run +`go get -u ./` or `go get -u samhofi.us/x/keybase` From 8cd30ab64372ff4652e546fc73ed1984ba2b76b3 Mon Sep 17 00:00:00 2001 From: Gregory Rudolph Date: Fri, 18 Oct 2019 10:33:47 -0400 Subject: [PATCH 33/53] Update README.md for go get commands --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3ce29d2..d814c18 100644 --- a/README.md +++ b/README.md @@ -31,16 +31,16 @@ go get -u github.com/rudi9719/kbtui ``` Or you can do the following: ``` -go get ./ +go get ./... go run build.go go run build.go {build, buildBeta... etc} ./kbtui ``` -You may see an error with `go get ./` about PATHs, that may be safely ignored. +You may see an error with `go get ./...` about PATHs, that may be safely ignored. If you see an error about a missing dependancy during a build, you'll want to resolve that. Occasionally when [@dxb](https://keybase.io/dxb) updates his API it will be necessary to run -`go get -u ./` or `go get -u samhofi.us/x/keybase` +`go get -u ./...` or `go get -u samhofi.us/x/keybase` From 553bfe55fb78160d5fd5b15cf1037fc5b8d0ad70 Mon Sep 17 00:00:00 2001 From: Gregory Rudolph Date: Fri, 18 Oct 2019 11:17:31 -0400 Subject: [PATCH 34/53] Travis, are you there? --- .travis.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..2fc40fb --- /dev/null +++ b/.travis.yml @@ -0,0 +1,12 @@ +language: go + +go: + - tip + - 1.13.x + +install: true + +script: + - go vet ./... + - go fmt ./... + - go run build.go buildBeta \ No newline at end of file From 3fe0b19392624569532e9a8e740ad5f7ce5a9e9f Mon Sep 17 00:00:00 2001 From: Gregory Rudolph Date: Fri, 18 Oct 2019 11:58:19 -0400 Subject: [PATCH 35/53] Travis works --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 2fc40fb..c2d9320 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,8 @@ go: install: true script: + - go get -u ./... + - go get -u github.com/magefile/mage/mage - go vet ./... - go fmt ./... - go run build.go buildBeta \ No newline at end of file From 0e4171efe770cb7b2e65e820b375cedbbbc8dafa Mon Sep 17 00:00:00 2001 From: Gregory Rudolph Date: Fri, 18 Oct 2019 12:28:57 -0400 Subject: [PATCH 36/53] Travis please come back to dev --- .travis.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index c2d9320..085d22d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,8 +7,9 @@ go: install: true script: - - go get -u ./... + - go get -u samhofi.us/x/keybase + - go get -u github.com/awesome-gocui/gocui - go get -u github.com/magefile/mage/mage + - go run build.go buildBeta - go vet ./... - - go fmt ./... - - go run build.go buildBeta \ No newline at end of file + - go fmt ./... \ No newline at end of file From 390a190bba0da504486554643ba8d75cd653bb88 Mon Sep 17 00:00:00 2001 From: Gregory Rudolph Date: Fri, 18 Oct 2019 12:32:34 -0400 Subject: [PATCH 37/53] Failing go vet, due to lack of blank line --- tabComplete.go | 1 + 1 file changed, 1 insertion(+) diff --git a/tabComplete.go b/tabComplete.go index 0391c1a..4aaabd7 100644 --- a/tabComplete.go +++ b/tabComplete.go @@ -1,4 +1,5 @@ // +build !rm_basic_commands allcommands tabcompletion + package main import ( From 707195232e9b55296401af80a4a14ed512f5ffa1 Mon Sep 17 00:00:00 2001 From: Gregory Rudolph Date: Fri, 18 Oct 2019 14:50:02 -0400 Subject: [PATCH 38/53] Better attachment handling after PR to samhofi.us/x/keybase --- cmdDownload.go | 21 +++++++++++++++++---- main.go | 2 +- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/cmdDownload.go b/cmdDownload.go index ff6cbbf..796517c 100644 --- a/cmdDownload.go +++ b/cmdDownload.go @@ -24,16 +24,29 @@ func cmdDownloadFile(cmd []string) { printToView("Feed", fmt.Sprintf("%s%s $messageId $fileName - Download a file to user's downloadpath", cmdPrefix, cmd[0])) return } - messageID, _ := strconv.Atoi(cmd[1]) + messageID, err := strconv.Atoi(cmd[1]) + if err != nil { + printToView("Feed", "There was an error converting your messageID to an int") + return + } + chat := k.NewChat(channel) + api, err := chat.ReadMessage(messageID) + if err != nil { + printToView("Feed", fmt.Sprintf("There was an error pulling message %d", messageID)) + return + } + if api.Result.Messages[0].Msg.Content.Type != "attachment" { + printToView("Feed", "No attachment detected") + return + } var fileName string if len(cmd) == 3 { fileName = cmd[2] } else { - fileName = "" + fileName = api.Result.Messages[0].Msg.Content.Attachment.Object.Filename } - chat := k.NewChat(channel) - _, err := chat.Download(messageID, fmt.Sprintf("%s/%s", downloadPath, fileName)) + _, err = chat.Download(messageID, fmt.Sprintf("%s/%s", downloadPath, fileName)) if err != nil { printToView("Feed", fmt.Sprintf("There was an error downloading %s from %s", fileName, channel.Name)) } else { diff --git a/main.go b/main.go index efa5640..2d8cec0 100644 --- a/main.go +++ b/main.go @@ -365,7 +365,7 @@ func formatOutput(api keybase.ChatAPI) string { msg = colorRegex(msg, `(https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*))`, messageLinkColor, messageBodyColor) msg = colorText(colorReplaceMentionMe(msg, messageBodyColor), messageBodyColor, c) if msgType == "attachment" { - msg = fmt.Sprintf("%s", colorText("[Attachment]", messageAttachmentColor, c)) + msg = fmt.Sprintf("%s\n%s", api.Msg.Content.Attachment.Object.Title, colorText(fmt.Sprintf("[Attachment: %s]", api.Msg.Content.Attachment.Object.Filename), messageAttachmentColor, c)) } user := colorUsername(api.Msg.Sender.Username, c) device := colorText(api.Msg.Sender.DeviceName, messageSenderDeviceColor, c) From 514059e990b0e27e17d078fd84a9d817fcd28e84 Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 21 Oct 2019 13:57:26 -0400 Subject: [PATCH 39/53] Separate standard lib imports from remote imports --- mage.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mage.go b/mage.go index 1cb2fcd..c9346e4 100644 --- a/mage.go +++ b/mage.go @@ -5,12 +5,13 @@ package main import ( "encoding/json" "fmt" - "github.com/magefile/mage/mg" - "github.com/magefile/mage/sh" "io/ioutil" "net/http" "os" "strings" + + "github.com/magefile/mage/mg" + "github.com/magefile/mage/sh" ) // emoji related constants From dbf53680e61df88d391b47aaff56b9adff99147f Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 21 Oct 2019 14:58:26 -0400 Subject: [PATCH 40/53] Download package updates before building --- mage.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/mage.go b/mage.go index c9346e4..ff15790 100644 --- a/mage.go +++ b/mage.go @@ -62,8 +62,25 @@ func createEmojiSlice() ([]string, error) { return emojiSlice, nil } +func getRemotePackages() error { + var packages = []string{ + "samhofi.us/x/keybase", + "github.com/awesome-gocui/gocui", + "github.com/magefile/mage/mage", + "github.com/magefile/mage/mg", + "github.com/magefile/mage/sh", + } + for _, p := range packages { + if err := sh.Run("go", "get", "-u", p); err != nil { + return err + } + } + return nil +} + // Build kbtui with emoji lookup support func BuildEmoji() error { + mg.Deps(getRemotePackages) emojis, err := createEmojiSlice() if err != nil { return err @@ -93,6 +110,7 @@ func exit(err error) { // Build kbtui with just the basic commands. func Build() { + mg.Deps(getRemotePackages) if err := sh.Run("go", "build"); err != nil { defer func() { exit(err) @@ -104,6 +122,7 @@ func Build() { // The ShowReactions TypeCommand will print a message in the feed window when // a reaction is received in the current conversation. func BuildShowReactions() { + mg.Deps(getRemotePackages) if err := sh.Run("go", "build", "-tags", "showreactionscmd"); err != nil { defer func() { exit(err) @@ -116,6 +135,7 @@ func BuildShowReactions() { // received in the current conversation. This gets pretty annoying, and // is not recommended. func BuildAutoReact() { + mg.Deps(getRemotePackages) if err := sh.Run("go", "build", "-tags", "autoreactcmd"); err != nil { defer func() { exit(err) @@ -125,6 +145,7 @@ func BuildAutoReact() { // Build kbtui with all commands and TypeCommands disabled. func BuildAllCommands() { + mg.Deps(getRemotePackages) if err := sh.Run("go", "build", "-tags", "allcommands"); err != nil { defer func() { exit(err) @@ -134,6 +155,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 { defer func() { exit(err) @@ -143,6 +165,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 { defer func() { From 14c7f9353a3d823449ea2b23b91eaa7353552c3d Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 21 Oct 2019 14:59:39 -0400 Subject: [PATCH 41/53] Move exit helper func above first build target --- mage.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/mage.go b/mage.go index ff15790..5fd0f28 100644 --- a/mage.go +++ b/mage.go @@ -78,6 +78,14 @@ func getRemotePackages() error { return nil } +// proper error reporting and exit code +func exit(err error) { + if err != nil { + fmt.Fprintf(os.Stderr, "%+v\n", err) + os.Exit(1) + } +} + // Build kbtui with emoji lookup support func BuildEmoji() error { mg.Deps(getRemotePackages) @@ -100,14 +108,6 @@ func BuildEmoji() error { return nil } -// proper error reporting and exit code -func exit(err error) { - if err != nil { - fmt.Fprintf(os.Stderr, "%+v\n", err) - os.Exit(1) - } -} - // Build kbtui with just the basic commands. func Build() { mg.Deps(getRemotePackages) From 112d2da2e863eb8696048343764fc1d6fd2226da Mon Sep 17 00:00:00 2001 From: Gregory Rudolph Date: Mon, 21 Oct 2019 15:09:04 -0400 Subject: [PATCH 42/53] Basic implementation of config file --- cmdSet.go | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/cmdSet.go b/cmdSet.go index fabdc06..0a2dacf 100644 --- a/cmdSet.go +++ b/cmdSet.go @@ -5,6 +5,8 @@ package main import ( "fmt" "strings" + + "github.com/pelletier/go-toml" ) func init() { @@ -26,7 +28,8 @@ func cmdSet(cmd []string) { if len(cmd) < 3 { switch cmd[1] { case "load": - printToView("Feed", "Load values from file?") + loadFromToml() + printToView("Feed", fmt.Sprintf("Loading config from toml")) case "downloadPath": printToView("Feed", fmt.Sprintf("Setting for %s -> %s", cmd[1], downloadPath)) case "outputFormat": @@ -62,3 +65,16 @@ func cmdSet(cmd []string) { } } +func loadFromToml() { + config, err := toml.LoadFile("kbtui.tml") + if err != nil { + printToView("Feed", fmt.Sprintf("Could not read config file: %+v", err)) + return + } + colorless = config.Get("Basics.colorless").(bool) + downloadPath = config.Get("Basics.downloadPath").(string) + cmdPrefix = config.Get("Basics.cmdPrefix").(string) + outputFormat = config.Get("Formatting.outputFormat").(string) + dateFormat = config.Get("Formatting.dateFormat").(string) + timeFormat = config.Get("Formatting.timeFormat").(string) +} From b960ffdde4e3383c3afec0617882fa32d761a178 Mon Sep 17 00:00:00 2001 From: Gregory Rudolph Date: Mon, 21 Oct 2019 15:16:44 -0400 Subject: [PATCH 43/53] Feature: add Config TOML file #17 --- kbtui.tml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 kbtui.tml diff --git a/kbtui.tml b/kbtui.tml new file mode 100644 index 0000000..4ce8ecb --- /dev/null +++ b/kbtui.tml @@ -0,0 +1,30 @@ +[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] +channelsColor = 8 +channelsHeaderColor = 6 +noColor = -1 +mentionColor = 3 +messageHeaderColor = 8 +messageIdColor = 7 +messageTimeColor = 6 +messageSenderDefaultColor = 8 +messageSenderDeviceColor = 8 +messageBodyColor = -1 +messageAttachmentColor = 2 +messageLinkColor = 4 From db6e367facb4f5a1198ff32b3d4ca2b4cf03a2fc Mon Sep 17 00:00:00 2001 From: Gregory Rudolph Date: Tue, 22 Oct 2019 08:05:51 -0400 Subject: [PATCH 44/53] Bugfix: Up arrow in stream/start crashes application --- cmdEdit.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/cmdEdit.go b/cmdEdit.go index af40142..889ac5f 100644 --- a/cmdEdit.go +++ b/cmdEdit.go @@ -25,10 +25,16 @@ func cmdEdit(cmd []string) { if len(cmd) == 2 || len(cmd) == 1 { if len(cmd) == 2 { messageId, _ = strconv.Atoi(cmd[1]) - } else { + } else if lastMessage.ID != 0 { + if lastMessage.Type != "text" { + printToView("Feed", "Last message isn't editable (is it an edit?)") + return + } messageId = lastMessage.ID + } else { + printToView("Feed", "No message to edit") + return } - origMessage, _ := chat.ReadMessage(messageId) if origMessage.Result.Messages[0].Msg.Content.Type != "text" { printToView("Feed", fmt.Sprintf("%+v", origMessage)) From 1fd39aa9abd0d1756c98a179f3002fa81fc8222a Mon Sep 17 00:00:00 2001 From: Gregory Rudolph Date: Tue, 22 Oct 2019 08:29:40 -0400 Subject: [PATCH 45/53] Bugfix: Add nullcheck for toml values from #18 --- cmdSet.go | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/cmdSet.go b/cmdSet.go index 0a2dacf..8757102 100644 --- a/cmdSet.go +++ b/cmdSet.go @@ -71,10 +71,22 @@ func loadFromToml() { printToView("Feed", fmt.Sprintf("Could not read config file: %+v", err)) return } - colorless = config.Get("Basics.colorless").(bool) - downloadPath = config.Get("Basics.downloadPath").(string) - cmdPrefix = config.Get("Basics.cmdPrefix").(string) - outputFormat = config.Get("Formatting.outputFormat").(string) - dateFormat = config.Get("Formatting.dateFormat").(string) - timeFormat = config.Get("Formatting.timeFormat").(string) + 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) + } } From 1d412ed5fd14f615c64e7f6094a89820f2d846c4 Mon Sep 17 00:00:00 2001 From: Gregory Rudolph Date: Tue, 22 Oct 2019 08:32:05 -0400 Subject: [PATCH 46/53] Update Travis now that mage auto-checks dependancies --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 085d22d..b17a781 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,8 +7,6 @@ go: install: true script: - - go get -u samhofi.us/x/keybase - - go get -u github.com/awesome-gocui/gocui - go get -u github.com/magefile/mage/mage - go run build.go buildBeta - go vet ./... From 8cab261b1d48f4c1598f320f04d4b893b3e0568c Mon Sep 17 00:00:00 2001 From: Gregory Rudolph Date: Tue, 22 Oct 2019 08:32:30 -0400 Subject: [PATCH 47/53] Add go-toml dep for #17 in mage --- mage.go | 1 + 1 file changed, 1 insertion(+) diff --git a/mage.go b/mage.go index 5fd0f28..5a4e11e 100644 --- a/mage.go +++ b/mage.go @@ -69,6 +69,7 @@ func getRemotePackages() error { "github.com/magefile/mage/mage", "github.com/magefile/mage/mg", "github.com/magefile/mage/sh", + "github.com/pelletier/go-toml", } for _, p := range packages { if err := sh.Run("go", "get", "-u", p); err != nil { From cc917a515f6ef24aa886472721988c5ae991feb4 Mon Sep 17 00:00:00 2001 From: Gregory Rudolph Date: Tue, 22 Oct 2019 08:45:37 -0400 Subject: [PATCH 48/53] Remove printSetting switch from cmdSet --- cmdSet.go | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/cmdSet.go b/cmdSet.go index 8757102..68a2d39 100644 --- a/cmdSet.go +++ b/cmdSet.go @@ -19,32 +19,34 @@ func init() { RegisterCommand(command) } +func printSetting(cmd []string) { + switch cmd[1] { + case "load": + loadFromToml() + printToView("Feed", fmt.Sprintf("Loading config from toml")) + case "downloadPath": + printToView("Feed", fmt.Sprintf("Setting for %s -> %s", cmd[1], downloadPath)) + case "outputFormat": + printToView("Feed", fmt.Sprintf("Setting for %s -> %s", cmd[1], outputFormat)) + case "dateFormat": + printToView("Feed", fmt.Sprintf("Setting for %s -> %s", cmd[1], dateFormat)) + case "timeFormat": + printToView("Feed", fmt.Sprintf("Setting for %s -> %s", cmd[1], timeFormat)) + case "cmdPrefix": + printToView("Feed", fmt.Sprintf("Setting for %s -> %s", cmd[1], cmdPrefix)) + default: + printToView("Feed", fmt.Sprintf("Unknown config value %s", cmd[1])) + } + return +} func cmdSet(cmd []string) { if len(cmd) < 2 { printToView("Feed", "No config value specified") return } if len(cmd) < 3 { - switch cmd[1] { - case "load": - loadFromToml() - printToView("Feed", fmt.Sprintf("Loading config from toml")) - case "downloadPath": - printToView("Feed", fmt.Sprintf("Setting for %s -> %s", cmd[1], downloadPath)) - case "outputFormat": - printToView("Feed", fmt.Sprintf("Setting for %s -> %s", cmd[1], outputFormat)) - case "dateFormat": - printToView("Feed", fmt.Sprintf("Setting for %s -> %s", cmd[1], dateFormat)) - case "timeFormat": - printToView("Feed", fmt.Sprintf("Setting for %s -> %s", cmd[1], timeFormat)) - case "cmdPrefix": - printToView("Feed", fmt.Sprintf("Setting for %s -> %s", cmd[1], cmdPrefix)) - default: - printToView("Feed", fmt.Sprintf("Unknown config value %s", cmd[1])) - } - - return + printSetting(cmd) } switch cmd[1] { case "downloadPath": From 2dd635669c3ce3899914258f97260f57ac6ea1e4 Mon Sep 17 00:00:00 2001 From: Gregory Rudolph Date: Tue, 22 Oct 2019 09:06:45 -0400 Subject: [PATCH 49/53] Linting code --- cmdEdit.go | 20 ++++++++++---------- cmdHelp.go | 2 +- cmdReact.go | 12 ++++++------ main.go | 43 ++++++++++++++++++++----------------------- 4 files changed, 37 insertions(+), 40 deletions(-) diff --git a/cmdEdit.go b/cmdEdit.go index 889ac5f..58526cf 100644 --- a/cmdEdit.go +++ b/cmdEdit.go @@ -11,7 +11,7 @@ import ( func init() { command := Command{ Cmd: []string{"edit", "e"}, - Description: "$messageId - Edit a message (messageID is optional)", + Description: "$messageID - Edit a message (messageID is optional)", Help: "", Exec: cmdEdit, } @@ -20,22 +20,22 @@ func init() { } func cmdEdit(cmd []string) { - var messageId int + var messageID int chat := k.NewChat(channel) if len(cmd) == 2 || len(cmd) == 1 { if len(cmd) == 2 { - messageId, _ = strconv.Atoi(cmd[1]) + messageID, _ = strconv.Atoi(cmd[1]) } else if lastMessage.ID != 0 { if lastMessage.Type != "text" { printToView("Feed", "Last message isn't editable (is it an edit?)") return } - messageId = lastMessage.ID + messageID = lastMessage.ID } else { printToView("Feed", "No message to edit") return } - origMessage, _ := chat.ReadMessage(messageId) + origMessage, _ := chat.ReadMessage(messageID) if origMessage.Result.Messages[0].Msg.Content.Type != "text" { printToView("Feed", fmt.Sprintf("%+v", origMessage)) return @@ -47,19 +47,19 @@ func cmdEdit(cmd []string) { editString := origMessage.Result.Messages[0].Msg.Content.Text.Body clearView("Edit") popupView("Edit") - printToView("Edit", fmt.Sprintf("/e %d %s", messageId, editString)) - setViewTitle("Edit", fmt.Sprintf(" Editing message %d ", messageId)) + printToView("Edit", fmt.Sprintf("/e %d %s", messageID, editString)) + setViewTitle("Edit", fmt.Sprintf(" Editing message %d ", messageID)) return } if len(cmd) < 3 { printToView("Feed", "Not enough options for Edit") return } - messageId, _ = strconv.Atoi(cmd[1]) + messageID, _ = strconv.Atoi(cmd[1]) newMessage := strings.Join(cmd[2:], " ") - _, err := chat.Edit(messageId, newMessage) + _, err := chat.Edit(messageID, newMessage) if err != nil { - printToView("Feed", fmt.Sprintf("Error editing message %d, %+v", messageId, err)) + printToView("Feed", fmt.Sprintf("Error editing message %d, %+v", messageID, err)) } } diff --git a/cmdHelp.go b/cmdHelp.go index 0c9b1fa..bbf71de 100644 --- a/cmdHelp.go +++ b/cmdHelp.go @@ -28,7 +28,7 @@ func cmdHelp(cmd []string) { helpText = fmt.Sprintf("%s%s%s\t\t%s\n", helpText, cmdPrefix, c, commands[c].Description) } if len(typeCommands) > 0 { - for c, _ := range typeCommands { + for c := range typeCommands { tCommands = append(tCommands, typeCommands[c].Name) } sort.Strings(tCommands) diff --git a/cmdReact.go b/cmdReact.go index 1175216..ddd92f6 100644 --- a/cmdReact.go +++ b/cmdReact.go @@ -10,7 +10,7 @@ import ( func init() { command := Command{ Cmd: []string{"react", "r", "+"}, - Description: "$messageId $reaction - React to a message (messageID is optional)", + Description: "$messageID $reaction - React to a message (messageID is optional)", Help: "", Exec: cmdReact, } @@ -20,7 +20,7 @@ func init() { func cmdReact(cmd []string) { if len(cmd) > 2 { - reactToMessageId(cmd[1], strings.Join(cmd[2:], " ")) + reactToMessageID(cmd[1], strings.Join(cmd[2:], " ")) } else if len(cmd) == 2 { reactToMessage(cmd[1]) } @@ -30,13 +30,13 @@ func cmdReact(cmd []string) { func reactToMessage(reaction string) { doReact(lastMessage.ID, reaction) } -func reactToMessageId(messageId string, reaction string) { - ID, _ := strconv.Atoi(messageId) +func reactToMessageID(messageID string, reaction string) { + ID, _ := strconv.Atoi(messageID) doReact(ID, reaction) } -func doReact(messageId int, reaction string) { +func doReact(messageID int, reaction string) { chat := k.NewChat(channel) - _, err := chat.React(messageId, reaction) + _, err := chat.React(messageID, reaction) if err != nil { printToView("Feed", "There was an error reacting to the message.") } diff --git a/main.go b/main.go index 2d8cec0..6805fb5 100644 --- a/main.go +++ b/main.go @@ -114,9 +114,8 @@ func initKeybindings() error { if input != "" { clearView("Input") return nil - } else { - return gocui.ErrQuit } + return gocui.ErrQuit }); err != nil { return err } @@ -168,9 +167,8 @@ func setViewTitle(viewName string, title string) { updatingView, err := g.View(viewName) if err != nil { return err - } else { - updatingView.Title = title } + updatingView.Title = title return nil }) } @@ -180,9 +178,9 @@ func getViewTitle(viewName string) string { // in case there is active tab completion, filter that to just the view title and not the completion options. printToView("Feed", fmt.Sprintf("Error getting view title: %s", err)) return "" - } else { - return strings.Split(view.Title, "||")[0] } + return strings.Split(view.Title, "||")[0] + } func popupView(viewName string) { _, err := g.SetCurrentView(viewName) @@ -197,10 +195,10 @@ func popupView(viewName string) { updatingView, err := g.View(viewName) if err != nil { return err - } else { - viewX, viewY := updatingView.Size() - updatingView.MoveCursor(viewX, viewY, true) } + viewX, viewY := updatingView.Size() + updatingView.MoveCursor(viewX, viewY, true) + return nil }) @@ -210,11 +208,11 @@ func clearView(viewName string) { inputView, err := g.View(viewName) if err != nil { return err - } else { - inputView.Clear() - inputView.SetCursor(0, 0) - inputView.SetOrigin(0, 0) } + inputView.Clear() + inputView.SetCursor(0, 0) + inputView.SetOrigin(0, 0) + return nil }) @@ -224,11 +222,11 @@ func writeToView(viewName string, message string) { updatingView, err := g.View(viewName) if err != nil { return err - } else { - for _, c := range message { - updatingView.EditWrite(c) - } } + for _, c := range message { + updatingView.EditWrite(c) + } + return nil }) } @@ -237,9 +235,8 @@ func printToView(viewName string, message string) { updatingView, err := g.View(viewName) if err != nil { return err - } else { - fmt.Fprintf(updatingView, "%s\n", message) } + fmt.Fprintf(updatingView, "%s\n", message) return nil }) } @@ -275,11 +272,11 @@ func populateChat() { if err2 != nil { printToView("Feed", fmt.Sprintf("%+v", err)) return - } else { - go populateChat() - go generateChannelTabCompletionSlice() - return } + go populateChat() + go generateChannelTabCompletionSlice() + return + } var printMe []string var actuallyPrintMe string From e084c16ba85931048bc81a63c8352d6c063b7cc9 Mon Sep 17 00:00:00 2001 From: Gregory Rudolph Date: Tue, 22 Oct 2019 09:18:27 -0400 Subject: [PATCH 50/53] Bugfix #24: Set cursor of popupView to 0,0 to avoid strange input bugs --- main.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/main.go b/main.go index 6805fb5..cea2ab1 100644 --- a/main.go +++ b/main.go @@ -196,8 +196,7 @@ func popupView(viewName string) { if err != nil { return err } - viewX, viewY := updatingView.Size() - updatingView.MoveCursor(viewX, viewY, true) + updatingView.MoveCursor(0, 0, true) return nil From 5eb23ff9935437b9a89e747e140a87e2e42ab695 Mon Sep 17 00:00:00 2001 From: Gregory Rudolph Date: Tue, 22 Oct 2019 09:30:18 -0400 Subject: [PATCH 51/53] Bug #24: Cursor now moves to end of message --- cmdEdit.go | 1 + main.go | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/cmdEdit.go b/cmdEdit.go index 58526cf..ed950eb 100644 --- a/cmdEdit.go +++ b/cmdEdit.go @@ -49,6 +49,7 @@ func cmdEdit(cmd []string) { popupView("Edit") printToView("Edit", fmt.Sprintf("/e %d %s", messageID, editString)) setViewTitle("Edit", fmt.Sprintf(" Editing message %d ", messageID)) + moveCursorToEnd("Edit") return } if len(cmd) < 3 { diff --git a/main.go b/main.go index cea2ab1..96f787d 100644 --- a/main.go +++ b/main.go @@ -202,6 +202,22 @@ func popupView(viewName string) { }) } +func moveCursorToEnd(viewName string) { + g.Update(func(g *gocui.Gui) error { + inputView, err := g.View(viewName) + if err != nil { + return err + } + inputString, _ := getInputString(viewName) + stringLen := len(inputString) + maxX, _ := inputView.Size() + x := stringLen % maxX + y := stringLen / maxX + inputView.MoveCursor(x, y, true) + return nil + + }) +} func clearView(viewName string) { g.Update(func(g *gocui.Gui) error { inputView, err := g.View(viewName) From acf822e5a15813a9a60cb4e84b22096b0b0a2336 Mon Sep 17 00:00:00 2001 From: Gregory Rudolph Date: Tue, 22 Oct 2019 09:42:11 -0400 Subject: [PATCH 52/53] Add origin to moveCursorToEnd() --- main.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/main.go b/main.go index 96f787d..0e2f84d 100644 --- a/main.go +++ b/main.go @@ -213,6 +213,8 @@ func moveCursorToEnd(viewName string) { maxX, _ := inputView.Size() x := stringLen % maxX y := stringLen / maxX + inputView.SetCursor(0, 0) + inputView.SetOrigin(0, 0) inputView.MoveCursor(x, y, true) return nil From 20a687208a9d9562a26d19f6c6258d8e6d4670d8 Mon Sep 17 00:00:00 2001 From: Gregory Rudolph Date: Tue, 22 Oct 2019 13:44:49 -0400 Subject: [PATCH 53/53] Linting tabComplete for future Travis checks --- tabComplete.go | 99 +++++++++++++++++++++++++------------------------- 1 file changed, 50 insertions(+), 49 deletions(-) diff --git a/tabComplete.go b/tabComplete.go index 4aaabd7..2c90f88 100644 --- a/tabComplete.go +++ b/tabComplete.go @@ -20,46 +20,46 @@ func handleTab(viewName string) error { inputString, err := getInputString(viewName) if err != nil { return err + } + // if you successfully get an input string, grab the last word from the string + ss := regexp.MustCompile(`[ #]`).Split(inputString, -1) + s := ss[len(ss)-1] + // create a variable in which to store the result + var resultSlice []string + // if the word starts with a : its an emoji lookup + if strings.HasPrefix(s, ":") { + resultSlice = getEmojiTabCompletionSlice(s) + } else if strings.HasPrefix(s, "/") { + generateCommandTabCompletionSlice() + s = strings.Replace(s, "/", "", 1) + resultSlice = getCommandTabCompletionSlice(s) } else { - // if you successfully get an input string, grab the last word from the string - ss := regexp.MustCompile(`[ #]`).Split(inputString, -1) - s := ss[len(ss)-1] - // create a variable in which to store the result - var resultSlice []string - // if the word starts with a : its an emoji lookup - if strings.HasPrefix(s, ":") { - resultSlice = getEmojiTabCompletionSlice(s) - } else if strings.HasPrefix(s, "/") { - generateCommandTabCompletionSlice() - s = strings.Replace(s, "/", "", 1) - resultSlice = getCommandTabCompletionSlice(s) - } else { - if strings.HasPrefix(s, "@") { - // now in case the word (s) is a mention @something, lets remove it to normalize - s = strings.Replace(s, "@", "", 1) - } - // now call get the list of all possible cantidates that have that as a prefix - resultSlice = getChannelTabCompletionSlice(s) + if strings.HasPrefix(s, "@") { + // now in case the word (s) is a mention @something, lets remove it to normalize + s = strings.Replace(s, "@", "", 1) } - rLen := len(resultSlice) - lcp := longestCommonPrefix(resultSlice) - if lcp != "" { - originalViewTitle := getViewTitle("Input") - newViewTitle := "" - if rLen >= 1 && originalViewTitle != "" { - if rLen == 1 { - newViewTitle = originalViewTitle - } else if rLen <= 5 { - newViewTitle = fmt.Sprintf("%s|| %s", originalViewTitle, strings.Join(resultSlice, " ")) - } else if rLen > 5 { - newViewTitle = fmt.Sprintf("%s|| %s +%d more", originalViewTitle, strings.Join(resultSlice[:6], " "), rLen-5) - } - setViewTitle(viewName, newViewTitle) - remainder := stringRemainder(s, lcp) - writeToView(viewName, remainder) + // now call get the list of all possible cantidates that have that as a prefix + resultSlice = getChannelTabCompletionSlice(s) + } + rLen := len(resultSlice) + lcp := longestCommonPrefix(resultSlice) + if lcp != "" { + originalViewTitle := getViewTitle("Input") + newViewTitle := "" + if rLen >= 1 && originalViewTitle != "" { + if rLen == 1 { + newViewTitle = originalViewTitle + } else if rLen <= 5 { + newViewTitle = fmt.Sprintf("%s|| %s", originalViewTitle, strings.Join(resultSlice, " ")) + } else if rLen > 5 { + newViewTitle = fmt.Sprintf("%s|| %s +%d more", originalViewTitle, strings.Join(resultSlice[:6], " "), rLen-5) } + setViewTitle(viewName, newViewTitle) + remainder := stringRemainder(s, lcp) + writeToView(viewName, remainder) } } + return nil } @@ -123,22 +123,23 @@ func getCurrentChannelMembership() []string { var rs []string if channel.Name != "" { t := k.NewTeam(channel.Name) - if testVar, err := t.MemberList(); err != nil { + testVar, err := t.MemberList() + if err != nil { return rs // then this isn't a team, its a PM or there was an error in the API call - } else { - for _, m := range testVar.Result.Members.Owners { - rs = append(rs, fmt.Sprintf("%+v", m.Username)) - } - for _, m := range testVar.Result.Members.Admins { - rs = append(rs, fmt.Sprintf("%+v", m.Username)) - } - for _, m := range testVar.Result.Members.Writers { - rs = append(rs, fmt.Sprintf("%+v", m.Username)) - } - for _, m := range testVar.Result.Members.Readers { - rs = append(rs, fmt.Sprintf("%+v", m.Username)) - } } + for _, m := range testVar.Result.Members.Owners { + rs = append(rs, fmt.Sprintf("%+v", m.Username)) + } + for _, m := range testVar.Result.Members.Admins { + rs = append(rs, fmt.Sprintf("%+v", m.Username)) + } + for _, m := range testVar.Result.Members.Writers { + rs = append(rs, fmt.Sprintf("%+v", m.Username)) + } + for _, m := range testVar.Result.Members.Readers { + rs = append(rs, fmt.Sprintf("%+v", m.Username)) + } + } return rs }