diff --git a/client/client.go b/client/client.go index fece67c..2c5d72b 100644 --- a/client/client.go +++ b/client/client.go @@ -7,9 +7,6 @@ import ( "github.com/fhs/gompd/mpd" "strings" - - "github.com/aditya-K2/gomp/utils" - "github.com/aditya-K2/tview" ) var ( @@ -39,22 +36,6 @@ func TogglePlayBack() error { return err } -func UpdatePlaylist(inputTable *tview.Table) { - _playlistAttr, _ := CONN.PlaylistInfo(-1, -1) - - inputTable.Clear() - for i, j := range _playlistAttr { - _, _, w, _ := inputTable.GetInnerRect() - if j["Title"] == "" || j["Artist"] == "" || j["Album"] == "" { - inputTable.SetCell(i, 0, tview.NewTableCell(utils.GetFormattedString(j["file"], w/3))) - } else { - inputTable.SetCell(i, 0, tview.NewTableCell(utils.GetFormattedString("[green]"+j["Title"], w/3))) - inputTable.SetCell(i, 1, tview.NewTableCell(utils.GetFormattedString("[magenta]"+j["Artist"], w/3))) - inputTable.SetCell(i, 2, tview.NewTableCell("[yellow]"+j["Album"])) - } - } -} - // The GenerateContentSlice returns a slice of the content to be displayed on the Search View. The Slice is generated // because the random nature of maps as they return values randomly hence the draw function keeps changing the order // in which the results appear. @@ -99,70 +80,6 @@ func GenerateContentSlice(selectedSuggestion string) ([]interface{}, error) { return ContentSlice, nil } -// UpdateSearchView as the name suggests Updates the Search View the idea is to basically keep a fourth option called -// Search in the Navigation bar which will render things from a global ContentSlice at least in the context of the main -// function this will also help in persisting the Search Results. -func UpdateSearchView(inputTable *tview.Table, c []interface{}) { - inputTable.Clear() - _, _, width, _ := inputTable.GetInnerRect() - for i, content := range c { - switch content.(type) { - case [3]string: - { - inputTable.SetCell(i, 0, tview.NewTableCell(utils.GetFormattedString("[green]"+content.([3]string)[0], width/3))) - inputTable.SetCell(i, 1, tview.NewTableCell(utils.GetFormattedString("[magenta]"+content.([3]string)[1], width/3))) - inputTable.SetCell(i, 2, tview.NewTableCell(utils.GetFormattedString("[yellow]"+content.([3]string)[2], width/3))) - } - case [2]string: - { - inputTable.SetCell(i, 0, tview.NewTableCell(utils.GetFormattedString("[green]"+content.([2]string)[0], width/3))) - inputTable.SetCell(i, 1, tview.NewTableCell(utils.GetFormattedString("[magenta]"+content.([2]string)[1], width/3))) - } - case string: - { - b := content.(string) - if !strings.HasPrefix(b, WHITE_AND_BOLD) { - inputTable.SetCell(i, 0, tview.NewTableCell("[green]"+content.(string))) - } else { - inputTable.SetCell(i, 0, tview.NewTableCell(content.(string)).SetSelectable(false)) - } - } - } - } -} - -func Update(f []FileNode, inputTable *tview.Table) { - inputTable.Clear() - for i, j := range f { - if len(j.Children) == 0 { - _songAttributes, err := CONN.ListAllInfo(j.AbsolutePath) - if err == nil && _songAttributes[0]["Title"] != "" { - _, _, w, _ := inputTable.GetInnerRect() - inputTable.SetCell(i, 0, - tview.NewTableCell("[green]"+utils.GetFormattedString(_songAttributes[0]["Title"], w/3)). - SetAlign(tview.AlignLeft)) - - inputTable.SetCell(i, 1, - tview.NewTableCell("[magenta]"+utils.GetFormattedString(_songAttributes[0]["Artist"], w/3)). - SetAlign(tview.AlignLeft)) - - inputTable.SetCell(i, 2, - tview.NewTableCell("[yellow]"+_songAttributes[0]["Album"]). - SetAlign(tview.AlignLeft)) - - } else if _songAttributes[0]["Title"] == "" { - inputTable.SetCell(i, 0, - tview.NewTableCell("[blue]"+j.Path). - SetAlign(tview.AlignLeft)) - } - } else { - inputTable.SetCell(i, 0, - tview.NewTableCell("[yellow::b]"+j.Path). - SetAlign(tview.AlignLeft)) - } - } -} - // GenerateArtistTree Artist Tree is a map of Artist to their Album Map // Album Tree is a map of the tracks in that particular album. func GenerateArtistTree() (map[string]map[string]map[string]string, error) { diff --git a/client/files.go b/client/files.go index 71cac40..aeadd35 100644 --- a/client/files.go +++ b/client/files.go @@ -6,12 +6,31 @@ import ( ) type FileNode struct { - Children []FileNode + Children []FileNode Path string Parent *FileNode AbsolutePath string } +// Source Interface For Fuzzy Searching. +type FileNodes []FileNode + +func (f FileNodes) String(i int) string { + if len(f[i].Children) == 0 { + _s, err := CONN.ListAllInfo(f[i].AbsolutePath) + if err != nil { + NotificationServer.Send(fmt.Sprintf("Could Not Get Information About the Node %s", f[i].Path)) + return f[i].Path + } + return _s[0]["Title"] + } + return f[i].Path +} + +func (f FileNodes) Len() int { + return len(f) +} + func (f *FileNode) AddChildren(path string) { if f.Path != "" { f.Children = append(f.Children, FileNode{Children: make([]FileNode, 0), Path: path, Parent: f, AbsolutePath: f.AbsolutePath + "/" + path}) diff --git a/client/updateView.go b/client/updateView.go new file mode 100644 index 0000000..8a5000b --- /dev/null +++ b/client/updateView.go @@ -0,0 +1,113 @@ +package client + +import ( + "fmt" + "strings" + + "github.com/aditya-K2/fuzzy" + "github.com/aditya-K2/gomp/utils" + "github.com/aditya-K2/tview" +) + +func UpdateBuffSearchView(inputTable *tview.Table, m fuzzy.Matches, f []FileNode) { + inputTable.Clear() + if m == nil || len(m) == 0 { + Update(f, inputTable) + } else { + for k, v := range m { + if len(f[v.Index].Children) != 0 { + inputTable.SetCellSimple(k, 0, utils.GetMatchedString(f[v.Index].Path, "#0000ff", "yellow", v.MatchedIndexes)) + } else { + _s, err := CONN.ListAllInfo(f[v.Index].AbsolutePath) + if err != nil { + NotificationServer.Send(fmt.Sprintf("Could Not Add %s to the Table", f[v.Index].Path)) + } else { + inputTable.SetCellSimple(k, 0, utils.GetMatchedString(_s[0]["Title"], "#fbff00", "green", v.MatchedIndexes)) + } + } + if k == 15 { + break + } + } + } +} + +func UpdatePlaylist(inputTable *tview.Table) { + _playlistAttr, _ := CONN.PlaylistInfo(-1, -1) + + inputTable.Clear() + for i, j := range _playlistAttr { + _, _, w, _ := inputTable.GetInnerRect() + if j["Title"] == "" || j["Artist"] == "" || j["Album"] == "" { + inputTable.SetCell(i, 0, tview.NewTableCell(utils.GetFormattedString(j["file"], w/3))) + } else { + inputTable.SetCell(i, 0, tview.NewTableCell(utils.GetFormattedString("[green]"+j["Title"], w/3))) + inputTable.SetCell(i, 1, tview.NewTableCell(utils.GetFormattedString("[magenta]"+j["Artist"], w/3))) + inputTable.SetCell(i, 2, tview.NewTableCell("[yellow]"+j["Album"])) + } + } +} + +// UpdateSearchView as the name suggests Updates the Search View the idea is to basically keep a fourth option called +// Search in the Navigation bar which will render things from a global ContentSlice at least in the context of the main +// function this will also help in persisting the Search Results. +func UpdateSearchView(inputTable *tview.Table, c []interface{}) { + inputTable.Clear() + _, _, width, _ := inputTable.GetInnerRect() + for i, content := range c { + switch content.(type) { + case [3]string: + { + inputTable.SetCell(i, 0, tview.NewTableCell(utils.GetFormattedString("[green]"+content.([3]string)[0], width/3))) + inputTable.SetCell(i, 1, tview.NewTableCell(utils.GetFormattedString("[magenta]"+content.([3]string)[1], width/3))) + inputTable.SetCell(i, 2, tview.NewTableCell(utils.GetFormattedString("[yellow]"+content.([3]string)[2], width/3))) + } + case [2]string: + { + inputTable.SetCell(i, 0, tview.NewTableCell(utils.GetFormattedString("[green]"+content.([2]string)[0], width/3))) + inputTable.SetCell(i, 1, tview.NewTableCell(utils.GetFormattedString("[magenta]"+content.([2]string)[1], width/3))) + } + case string: + { + b := content.(string) + if !strings.HasPrefix(b, WHITE_AND_BOLD) { + inputTable.SetCell(i, 0, tview.NewTableCell("[green]"+content.(string))) + } else { + inputTable.SetCell(i, 0, tview.NewTableCell(content.(string)).SetSelectable(false)) + } + } + } + } +} + +func Update(f []FileNode, inputTable *tview.Table) { + inputTable.Clear() + for i, j := range f { + if len(j.Children) == 0 { + _songAttributes, err := CONN.ListAllInfo(j.AbsolutePath) + if err == nil && _songAttributes[0]["Title"] != "" { + _, _, w, _ := inputTable.GetInnerRect() + inputTable.SetCell(i, 0, + tview.NewTableCell("[green]"+utils.GetFormattedString(_songAttributes[0]["Title"], w/3)). + SetAlign(tview.AlignLeft)) + + inputTable.SetCell(i, 1, + tview.NewTableCell("[magenta]"+utils.GetFormattedString(_songAttributes[0]["Artist"], w/3)). + SetAlign(tview.AlignLeft)) + + inputTable.SetCell(i, 2, + tview.NewTableCell("[yellow]"+_songAttributes[0]["Album"]). + SetAlign(tview.AlignLeft)) + + } else if _songAttributes[0]["Title"] == "" { + inputTable.SetCell(i, 0, + tview.NewTableCell("[blue]"+j.Path). + SetAlign(tview.AlignLeft)) + } + } else { + inputTable.SetCell(i, 0, + tview.NewTableCell("[yellow::b]"+j.Path). + SetAlign(tview.AlignLeft)) + } + } +} diff --git a/main.go b/main.go index 5ca3447..bb2eb03 100644 --- a/main.go +++ b/main.go @@ -4,17 +4,15 @@ import ( "strconv" "time" - "github.com/aditya-K2/gomp/ui/notify" - + "github.com/aditya-K2/gomp/cache" + "github.com/aditya-K2/gomp/client" + "github.com/aditya-K2/gomp/config" "github.com/aditya-K2/gomp/render" "github.com/aditya-K2/gomp/ui" - - "github.com/aditya-K2/gomp/client" + "github.com/aditya-K2/gomp/ui/notify" "github.com/aditya-K2/gomp/utils" "github.com/aditya-K2/fuzzy" - "github.com/aditya-K2/gomp/cache" - "github.com/aditya-K2/gomp/config" "github.com/fhs/gompd/mpd" "github.com/gdamore/tcell/v2" "github.com/spf13/viper" @@ -81,6 +79,7 @@ func main() { // This is the Slice that is used to Display Content in the SearchView var SearchContentSlice []interface{} + var Matches fuzzy.Matches // This Function Is Responsible for Changing the Focus it uses the Focus Map and Based on it Chooses // the Draw Function @@ -91,6 +90,8 @@ func main() { client.UpdateSearchView(UI.ExpandedView, SearchContentSlice) } else if ui.HasFocus("FileBrowser") { client.Update(dirTree.Children, UI.ExpandedView) + } else if ui.HasFocus("BuffSearchView") { + client.UpdateBuffSearchView(UI.ExpandedView, Matches, dirTree.Children) } return UI.ExpandedView.GetInnerRect() }) @@ -100,20 +101,36 @@ func main() { // the respective function in this case togglePlayBack is called. var FuncMap = map[string]func(){ "showChildrenContent": func() { - r, _ := UI.ExpandedView.GetSelection() if ui.HasFocus("FileBrowser") { + r, _ := UI.ExpandedView.GetSelection() + ui.SetFocus("FileBrowser") if len(dirTree.Children[r].Children) == 0 { id, _ := CONN.AddId(dirTree.Children[r].AbsolutePath, -1) CONN.PlayId(id) } else { client.Update(dirTree.Children[r].Children, UI.ExpandedView) dirTree = &dirTree.Children[r] + UI.ExpandedView.Select(0, 0) } } else if ui.HasFocus("Playlist") { + r, _ := UI.ExpandedView.GetSelection() CONN.Play(r) } else if ui.HasFocus("SearchView") { r, _ := UI.ExpandedView.GetSelection() client.AddToPlaylist(SearchContentSlice[r], true) + } else if ui.HasFocus("BuffSearchView") { + r, _ := UI.ExpandedView.GetSelection() + ui.SetFocus("FileBrowser") + if len(dirTree.Children[r].Children) == 0 { + id, _ := CONN.AddId(dirTree.Children[Matches[r].Index].AbsolutePath, -1) + CONN.PlayId(id) + } else { + client.Update(dirTree.Children[Matches[r].Index].Children, UI.ExpandedView) + dirTree = &dirTree.Children[Matches[r].Index] + } + UI.SearchBar.SetText("") + // Resetting Matches + Matches = nil } }, "togglePlayBack": func() { @@ -192,7 +209,13 @@ func main() { UI.Navbar.Select(3, 0) }, "quit": func() { - UI.App.Stop() + if ui.HasFocus("BuffSearchView") { + ui.SetFocus("FileBrowser") + UI.SearchBar.SetText("") + Matches = nil + } else { + UI.App.Stop() + } }, "stop": func() { CONN.Stop() @@ -214,6 +237,12 @@ func main() { "FocusSearch": func() { UI.App.SetFocus(UI.SearchBar) }, + "FocusBuffSearch": func() { + if ui.HasFocus("FileBrowser") || ui.HasFocus("BuffSearchView") { + ui.SetFocus("BuffSearchView") + UI.App.SetFocus(UI.SearchBar) + } + }, } // Generating the Key Map Based on the Function Map Here Basically the Values will be flipped @@ -222,19 +251,23 @@ func main() { config.GenerateKeyMap(FuncMap) UI.SearchBar.SetAutocompleteFunc(func(c string) []string { - if c != "" && c != " " && c != " " { - _, _, w, _ := UI.SearchBar.GetRect() - matches := fuzzy.Find(c, ArtistTreeContent) - var suggestions []string - for i, match := range matches { - if i == 10 { - break - } - suggestions = append(suggestions, utils.GetFormattedString(match.Str, w-2)) - } - return suggestions + if ui.HasFocus("BuffSearchView") { + return nil } else { - return make([]string, 0) + if c != "" && c != " " && c != " " { + _, _, w, _ := UI.SearchBar.GetRect() + matches := fuzzy.Find(c, ArtistTreeContent) + var suggestions []string + for i, match := range matches { + if i == 10 { + break + } + suggestions = append(suggestions, utils.GetFormattedString(match.Str, w-2)) + } + return suggestions + } else { + return make([]string, 0) + } } }) @@ -251,23 +284,41 @@ func main() { UI.SearchBar.SetDoneFunc(func(e tcell.Key) { if e == tcell.KeyEnter { UI.ExpandedView.Select(0, 0) - ui.SetFocus("SearchView") - SearchContentSlice = nil - SearchContentSlice, err = client.GenerateContentSlice(UI.SearchBar.GetText()) - if err != nil { - Notify.Send("Could Not Retrieve the Results") - } else { - UI.SearchBar.SetText("") + if ui.HasFocus("BuffSearchView") { UI.App.SetFocus(UI.ExpandedView) - UI.Navbar.Select(3, 0) + } else { + ui.SetFocus("SearchView") + SearchContentSlice = nil + SearchContentSlice, err = client.GenerateContentSlice(UI.SearchBar.GetText()) + if err != nil { + Notify.Send("Could Not Retrieve the Results") + } else { + UI.SearchBar.SetText("") + UI.App.SetFocus(UI.ExpandedView) + UI.Navbar.Select(3, 0) + } } } if e == tcell.KeyEscape { - ui.FocusMap["SearchView"] = false + if ui.HasFocus("SearchView") { + ui.FocusMap["SearchView"] = false + } else if ui.HasFocus("BuffSearchView") { + ui.SetFocus("FileBrowser") + Matches = nil + } + UI.SearchBar.SetText("") UI.App.SetFocus(UI.ExpandedView) } }) + UI.SearchBar.SetChangedFunc(func(text string) { + if ui.HasFocus("BuffSearchView") { + var f client.FileNodes = dirTree.Children + Matches = fuzzy.FindFrom(text, f) + client.UpdateBuffSearchView(UI.ExpandedView, Matches, dirTree.Children) + } + }) + go func() { for { UI.App.Draw() diff --git a/ui/focus.go b/ui/focus.go index b71c377..2251b85 100644 --- a/ui/focus.go +++ b/ui/focus.go @@ -10,6 +10,7 @@ func GenerateFocusMap() { FocusMap["Playlist"] = true FocusMap["FileBrowser"] = false FocusMap["SearchView"] = false + FocusMap["BuffSearchView"] = false } func HasFocus(s string) bool { diff --git a/utils/utils.go b/utils/utils.go index ee88706..23fdc29 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -1,6 +1,7 @@ package utils import ( + "fmt" "io/ioutil" "strconv" "strings" @@ -136,3 +137,37 @@ func CheckDirectoryFmt(path string) string { return path + "/" } } + +func GetMatchedString(s string, color string, nulcol string, matchedIndexes []int) string { + // The indexes that we will receive from the matchedIndexes are always sorted so we have to just + // add the color string at + // `indexValue + ( len(colorString) * k )` + // where k is the index of the indexValue in the matchedIndexes slice + // and we will need to also reset the colors, For that we check if the next indexValue in the matchedIndexes for + // the current indexValue is not the consecutive value ( v + 1 ) if yes ( is not consecutive ) then we add the reset + // color string at the k + 1 index in the string. + // for e.g. + // if we have the following matchedIndexes slice + // []int{ 1, 3, 4, 6} + // During the First Iteration matchedIndexes[k] = 1 and and matchedIndexes[k+1] are not consecutive so the nulcol + // string will be added to the matchedIndexes[k] + 1 index of the string + // During the Second Iteration as 3, 4 are consecutive the nulcol will be skipped. + color = fmt.Sprintf("[%s:-:bi]", color) + nulcol = fmt.Sprintf("[%s:-:b]", nulcol) + nulc := 0 + for k := range matchedIndexes { + s = InsertAt(s, color, matchedIndexes[k]+(len(color)*k)+nulc) + if k < len(matchedIndexes)-1 && matchedIndexes[k]-matchedIndexes[k+1] != 1 { + s = InsertAt(s, nulcol, (matchedIndexes[k]+1)+(len(color)*(k+1))+nulc) + nulc += len(nulcol) + } + if k == len(matchedIndexes)-1 { + s = InsertAt(s, nulcol, ((matchedIndexes[len(matchedIndexes)-1] + 1) + + (len(matchedIndexes) * len(color)) + + (len(nulcol) * (len(matchedIndexes) - 1)))) + } + } + // Adding the Nulcol at the Start + s = nulcol + s + return s +}