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