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.

This commit is contained in:
2019-10-17 10:03:04 -06:00
parent 209a94614b
commit 5aeb62ef18
2 changed files with 148 additions and 125 deletions

131
main.go
View File

@ -52,6 +52,7 @@ func main() {
if err := g.MainLoop(); err != nil && !gocui.IsQuit(err) { if err := g.MainLoop(); err != nil && !gocui.IsQuit(err) {
fmt.Printf("%+v", err) fmt.Printf("%+v", err)
} }
go generateChannelTabCompletionSlice()
} }
// Gocui basic setup // Gocui basic setup
@ -277,6 +278,7 @@ func populateChat() {
return return
} else { } else {
go populateChat() go populateChat()
go generateChannelTabCompletionSlice()
return return
} }
} }
@ -338,6 +340,7 @@ func populateList() {
} }
time.Sleep(1 * time.Millisecond) time.Sleep(1 * time.Millisecond)
printToView("List", fmt.Sprintf("%s%s%s%s", channelsColor, recentPMs, recentChannels, noColor)) 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 // 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 { func handleTab() error {
inputString, err := getInputString("Input") inputString, err := getInputString("Input")
if err != nil { if err != nil {
@ -515,14 +398,14 @@ func handleTab() error {
var resultSlice []string var resultSlice []string
// if the word starts with a : its an emoji lookup // if the word starts with a : its an emoji lookup
if strings.HasPrefix(s, ":") { if strings.HasPrefix(s, ":") {
resultSlice = generateEmojiTabCompletionSlice(s) resultSlice = getEmojiTabCompletionSlice(s)
} else { } else {
// now in case the word (s) is a mention @something, lets remove it to normalize
if strings.HasPrefix(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) s = strings.Replace(s, "@", "", 1)
} }
// now call get the list of all possible cantidates that have that as a prefix // now call get the list of all possible cantidates that have that as a prefix
resultSlice = generateChannelTabCompletionSlice(s) resultSlice = getChannelTabCompletionSlice(s)
} }
rLen := len(resultSlice) rLen := len(resultSlice)
lcp := longestCommonPrefix(resultSlice) lcp := longestCommonPrefix(resultSlice)
@ -546,8 +429,6 @@ func handleTab() error {
return nil return nil
} }
// End tab completion
// Input handling // Input handling
func handleMessage(api keybase.ChatAPI) { func handleMessage(api keybase.ChatAPI) {
if _, ok := typeCommands[api.Msg.Content.Type]; ok { if _, ok := typeCommands[api.Msg.Content.Type]; ok {

142
tabComplete.go Normal file
View File

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