From 07c068452f7c38566a2f500d8b8b5539befe088a Mon Sep 17 00:00:00 2001 From: Alexander Butt-Piercey Date: Sat, 18 Feb 2023 14:23:04 +0100 Subject: [PATCH 1/7] Tmuxinator module Cleanup work Temp Rename Githuv Rebuild Github to make it more simple Clone edit and new projects Split form out Fix formatting Fix pulling prs Split client out from main module Remove repo Use explicit item Simplegithub Bring github back Format --- app/widget_maker.go | 4 ++ modules/tmuxinator/client/client.go | 50 +++++++++++++ modules/tmuxinator/form.go | 91 +++++++++++++++++++++++ modules/tmuxinator/keyboard.go | 86 ++++++++++++++++++++++ modules/tmuxinator/settings.go | 29 ++++++++ modules/tmuxinator/widget.go | 108 ++++++++++++++++++++++++++++ 6 files changed, 368 insertions(+) create mode 100644 modules/tmuxinator/client/client.go create mode 100644 modules/tmuxinator/form.go create mode 100644 modules/tmuxinator/keyboard.go create mode 100644 modules/tmuxinator/settings.go create mode 100644 modules/tmuxinator/widget.go diff --git a/app/widget_maker.go b/app/widget_maker.go index c12d4dc88..ff3bf8e71 100644 --- a/app/widget_maker.go +++ b/app/widget_maker.go @@ -86,6 +86,7 @@ import ( "github.com/wtfutil/wtf/modules/weatherservices/prettyweather" "github.com/wtfutil/wtf/modules/weatherservices/weather" "github.com/wtfutil/wtf/modules/zendesk" + "github.com/wtfutil/wtf/modules/tmuxinator" "github.com/wtfutil/wtf/wtf" ) @@ -314,6 +315,9 @@ func MakeWidget( case "textfile": settings := textfile.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = textfile.NewWidget(tviewApp, redrawChan, pages, settings) + case "tmuxinator": + settings := tmuxinator.NewSettingsFromYAML(moduleName, moduleConfig, config) + widget = tmuxinator.NewWidget(tviewApp, redrawChan, pages, settings) case "todo": settings := todo.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = todo.NewWidget(tviewApp, redrawChan, pages, settings) diff --git a/modules/tmuxinator/client/client.go b/modules/tmuxinator/client/client.go new file mode 100644 index 000000000..b62e5df98 --- /dev/null +++ b/modules/tmuxinator/client/client.go @@ -0,0 +1,50 @@ +package client + +import ( + "os/exec" + "fmt" + "strings" + "github.com/wtfutil/wtf/utils" +) + +func ProjectList() []string { + cmdString := `tmuxinator list | grep -v "tmuxinator projects:" | tr -s ' ' | tr '\n' ' '` + + cmd := exec.Command("sh", "-c", cmdString) + + return strings.Split(utils.ExecuteCommand(cmd), " ") +} + +func StartProject(projectName string) { + _, err := exec.Command("tmuxinator", "start", projectName).Output() + + if err != nil { + fmt.Println(err.Error()) + } +} + +func EditProject(projectName string) { + subcommand := fmt.Sprintf("tmuxinator edit %s", projectName) + _, err := exec.Command("tmux", "new-window", subcommand).Output() + + if err != nil { + fmt.Println(err.Error()) + } +} + +func DeleteProject(projectName string) { + _, err := exec.Command("tmuxinator", "delete", projectName).Output() + + if err != nil { + fmt.Println(err.Error()) + } +} + +func CopyProject(leftProj, rightProj string) { + subcommand := fmt.Sprintf("tmuxinator copy %s %s", leftProj, rightProj) + _, err := exec.Command("tmux", "new-window", subcommand).Output() + + if err != nil { + fmt.Println(err.Error()) + } +} diff --git a/modules/tmuxinator/form.go b/modules/tmuxinator/form.go new file mode 100644 index 000000000..61f3fd5b5 --- /dev/null +++ b/modules/tmuxinator/form.go @@ -0,0 +1,91 @@ +package tmuxinator + +import ( + "github.com/gdamore/tcell/v2" + "github.com/rivo/tview" + "github.com/wtfutil/wtf/wtf" +) + +const ( + modalHeight = 7 + modalWidth = 80 + offscreen = -1000 +) + +func (widget *Widget) processFormInput(prompt string, initValue string, onSave func(string)) { + form := widget.modalForm(prompt, initValue) + + saveFctn := func() { + onSave(form.GetFormItem(0).(*tview.InputField).GetText()) + + widget.pages.RemovePage("modal") + widget.tviewApp.SetFocus(widget.View) + widget.display() + } + + widget.addButtons(form, saveFctn) + widget.modalFocus(form) + + // Tell the app to force redraw the screen + widget.Base.RedrawChan <- true +} + +/* -------------------- Modal Form -------------------- */ + +func (widget *Widget) addButtons(form *tview.Form, saveFctn func()) { + widget.addSaveButton(form, saveFctn) + widget.addCancelButton(form) +} + +func (widget *Widget) addCancelButton(form *tview.Form) { + cancelFn := func() { + widget.pages.RemovePage("modal") + widget.tviewApp.SetFocus(widget.View) + widget.display() + } + + form.AddButton("Cancel", cancelFn) + form.SetCancelFunc(cancelFn) +} + +func (widget *Widget) addSaveButton(form *tview.Form, fctn func()) { + form.AddButton("Save", fctn) +} + +func (widget *Widget) modalFocus(form *tview.Form) { + frame := widget.modalFrame(form) + widget.pages.AddPage("modal", frame, false, true) + widget.tviewApp.SetFocus(frame) + + // Tell the app to force redraw the screen + widget.Base.RedrawChan <- true +} + +func (widget *Widget) modalForm(lbl, text string) *tview.Form { + form := tview.NewForm() + form.SetFieldBackgroundColor(wtf.ColorFor(widget.settings.common.Colors.Background)) + form.SetButtonsAlign(tview.AlignCenter) + form.SetButtonTextColor(wtf.ColorFor(widget.settings.common.Colors.Text)) + + form.AddInputField(lbl, text, 60, nil, nil) + + return form +} + +func (widget *Widget) modalFrame(form *tview.Form) *tview.Frame { + frame := tview.NewFrame(form) + frame.SetBorders(0, 0, 0, 0, 0, 0) + frame.SetRect(offscreen, offscreen, modalWidth, modalHeight) + frame.SetBorder(true) + frame.SetBorders(1, 1, 0, 0, 1, 1) + + drawFunc := func(screen tcell.Screen, x, y, width, height int) (int, int, int, int) { + w, h := screen.Size() + frame.SetRect((w/2)-(width/2), (h/2)-(height/2), width, height) + return x, y, width, height + } + + frame.SetDrawFunc(drawFunc) + + return frame +} diff --git a/modules/tmuxinator/keyboard.go b/modules/tmuxinator/keyboard.go new file mode 100644 index 000000000..b55b31608 --- /dev/null +++ b/modules/tmuxinator/keyboard.go @@ -0,0 +1,86 @@ +package tmuxinator + +import ( + "github.com/gdamore/tcell/v2" + tc "github.com/wtfutil/wtf/modules/tmuxinator/client" + "strings" +) + +func (widget *Widget) initializeKeyboardControls() { + widget.InitializeHelpTextKeyboardControl(widget.ShowHelp) + widget.InitializeRefreshKeyboardControl(widget.Refresh) + + widget.SetKeyboardChar("j", widget.Next, "Select next project") + widget.SetKeyboardChar("k", widget.Prev, "Select previous project") + widget.SetKeyboardChar("e", widget.editProject, "Edit project") + widget.SetKeyboardChar("n", widget.newProject, "Create new project") + widget.SetKeyboardChar("c", widget.cloneProject, "Clone existing project") + + widget.SetKeyboardKey(tcell.KeyDown, widget.Next, "Select next project") + widget.SetKeyboardKey(tcell.KeyUp, widget.Prev, "Select previous project") + widget.SetKeyboardKey(tcell.KeyEnter, widget.startProject, "Start or go to project") + widget.SetKeyboardKey(tcell.KeyEsc, widget.Unselect, "Clear selection") +} + +func (widget *Widget) Next() { + widget.Selected++ + + if widget.Selected >= widget.MaxItems() { + widget.Selected = 0 + } + + widget.display() +} + +func (widget *Widget) Prev() { + widget.Selected-- + + if widget.Selected < 0 { + widget.Selected = widget.MaxItems() - 1 + } + + widget.display() +} + +func (widget *Widget) Unselect() { + widget.Selected = -1 + widget.display() +} + +func (widget *Widget) startProject() { + if widget.GetSelected() >= 0 && len(widget.Items) > 0 { + projectName := widget.Items[widget.GetSelected()] + + tc.StartProject(projectName) + } +} + +func (widget *Widget) editProject() { + if widget.GetSelected() >= 0 && len(widget.Items) > 0 { + projectName := widget.Items[widget.GetSelected()] + + tc.EditProject(projectName) + } +} + +func (widget *Widget) newProject() { + widget.processFormInput("New Project:", "", func(t string) { + projectName := strings.ReplaceAll(t, " ", "_") + + tc.EditProject(projectName) + widget.Base.RedrawChan <- true + }) +} + +func (widget *Widget) cloneProject() { + if widget.GetSelected() >= 0 && len(widget.Items) > 0 { + currentProjectName := widget.Items[widget.GetSelected()] + + widget.processFormInput("Copy Project:", currentProjectName, func(t string) { + newProjectName := strings.ReplaceAll(t, " ", "_") + + tc.CopyProject(currentProjectName, newProjectName) + widget.Base.RedrawChan <- true + }) + } +} diff --git a/modules/tmuxinator/settings.go b/modules/tmuxinator/settings.go new file mode 100644 index 000000000..ab9023504 --- /dev/null +++ b/modules/tmuxinator/settings.go @@ -0,0 +1,29 @@ +package tmuxinator + +import ( + "github.com/olebedev/config" + "github.com/wtfutil/wtf/cfg" +) + +const ( + defaultFocusable = true + defaultTitle = "Tmuxinator Projects" +) + +type Settings struct { + common *cfg.Common +} + +func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { + common := cfg.NewCommonSettingsFromModule( + name, + defaultTitle, + defaultFocusable, + ymlConfig, + globalConfig, + ) + + settings := Settings{common: common} + + return &settings +} diff --git a/modules/tmuxinator/widget.go b/modules/tmuxinator/widget.go new file mode 100644 index 000000000..878d0609c --- /dev/null +++ b/modules/tmuxinator/widget.go @@ -0,0 +1,108 @@ +package tmuxinator + +import ( + "fmt" + "github.com/rivo/tview" + tc "github.com/wtfutil/wtf/modules/tmuxinator/client" + "github.com/wtfutil/wtf/view" +) + +type Widget struct { + pages *tview.Pages + + settings *Settings + Selected int + maxItems int + Items []string + + tviewApp *tview.Application + view.ScrollableWidget +} + +func NewWidget(tviewApp *tview.Application, redrawChan chan bool, pages *tview.Pages, settings *Settings) *Widget { + widget := Widget{ + ScrollableWidget: view.NewScrollableWidget(tviewApp, redrawChan, pages, settings.common), + + tviewApp: tviewApp, + settings: settings, + pages: pages, + } + + widget.initializeKeyboardControls() + + widget.Items = tc.ProjectList() + + widget.Unselect() + + return &widget +} + +/* -------------------- Exported Functions -------------------- */ + +func (widget *Widget) GetSelected() int { + if widget.Selected < 0 { + return 0 + } + + return widget.Selected +} + +func (widget *Widget) MaxItems() int { + return len(widget.Items) +} + +func (widget *Widget) Refresh() { + widget.Items = tc.ProjectList() + widget.Unselect() + widget.display() +} + +func (widget *Widget) RowColor(idx int) string { + if widget.View.HasFocus() && (idx == widget.GetSelected()) { + foreground := widget.CommonSettings().Colors.RowTheme.HighlightedForeground + + return fmt.Sprintf( + "%s:%s", + foreground, + widget.CommonSettings().Colors.RowTheme.HighlightedBackground, + ) + } + + if idx%2 == 0 { + return fmt.Sprintf( + "%s:%s", + widget.settings.common.Colors.RowTheme.EvenForeground, + widget.settings.common.Colors.RowTheme.EvenBackground, + ) + } + + return fmt.Sprintf( + "%s:%s", + widget.settings.common.Colors.RowTheme.OddForeground, + widget.settings.common.Colors.RowTheme.OddBackground, + ) +} + +/* -------------------- Unexported Functions -------------------- */ + +func (widget *Widget) display() { + widget.Redraw(func() (string, string, bool) { + return widget.CommonSettings().Title, widget.content(), false + }) +} + +func (widget *Widget) content() string { + cnt := "" + + if len(widget.Items) <= 0 { + cnt += " [grey]No projects found[white]\n" + } + + for idx, projectName := range widget.Items { + cnt += fmt.Sprintf("[%s] %s \n", + widget.RowColor(idx), + projectName) + } + + return cnt +} From 1ac6a57d546fd23b594155c5b3c2b4ae1e8970fa Mon Sep 17 00:00:00 2001 From: Alexander Butt-Piercey Date: Mon, 27 Feb 2023 22:28:38 +0100 Subject: [PATCH 2/7] gofmt --- app/widget_maker.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/widget_maker.go b/app/widget_maker.go index ff3bf8e71..3d4705a2f 100644 --- a/app/widget_maker.go +++ b/app/widget_maker.go @@ -70,6 +70,7 @@ import ( "github.com/wtfutil/wtf/modules/stocks/yfinance" "github.com/wtfutil/wtf/modules/subreddit" "github.com/wtfutil/wtf/modules/textfile" + "github.com/wtfutil/wtf/modules/tmuxinator" "github.com/wtfutil/wtf/modules/todo" "github.com/wtfutil/wtf/modules/todo_plus" "github.com/wtfutil/wtf/modules/transmission" @@ -86,7 +87,6 @@ import ( "github.com/wtfutil/wtf/modules/weatherservices/prettyweather" "github.com/wtfutil/wtf/modules/weatherservices/weather" "github.com/wtfutil/wtf/modules/zendesk" - "github.com/wtfutil/wtf/modules/tmuxinator" "github.com/wtfutil/wtf/wtf" ) From 2a98f9a76e50b0e6494db48006e11f41e0fc1a1e Mon Sep 17 00:00:00 2001 From: Alexander Butt-Piercey Date: Mon, 27 Feb 2023 22:29:38 +0100 Subject: [PATCH 3/7] gofmt --- modules/tmuxinator/client/client.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/tmuxinator/client/client.go b/modules/tmuxinator/client/client.go index b62e5df98..2af3a6ea0 100644 --- a/modules/tmuxinator/client/client.go +++ b/modules/tmuxinator/client/client.go @@ -1,10 +1,10 @@ package client import ( - "os/exec" "fmt" - "strings" "github.com/wtfutil/wtf/utils" + "os/exec" + "strings" ) func ProjectList() []string { @@ -19,7 +19,7 @@ func StartProject(projectName string) { _, err := exec.Command("tmuxinator", "start", projectName).Output() if err != nil { - fmt.Println(err.Error()) + fmt.Println(err.Error()) } } @@ -28,7 +28,7 @@ func EditProject(projectName string) { _, err := exec.Command("tmux", "new-window", subcommand).Output() if err != nil { - fmt.Println(err.Error()) + fmt.Println(err.Error()) } } @@ -36,7 +36,7 @@ func DeleteProject(projectName string) { _, err := exec.Command("tmuxinator", "delete", projectName).Output() if err != nil { - fmt.Println(err.Error()) + fmt.Println(err.Error()) } } @@ -45,6 +45,6 @@ func CopyProject(leftProj, rightProj string) { _, err := exec.Command("tmux", "new-window", subcommand).Output() if err != nil { - fmt.Println(err.Error()) + fmt.Println(err.Error()) } } From 091910faa923284149e03decbaeb58f08e0034ed Mon Sep 17 00:00:00 2001 From: Alexander Butt-Piercey Date: Mon, 27 Feb 2023 22:32:23 +0100 Subject: [PATCH 4/7] Remove unused public prop --- modules/tmuxinator/widget.go | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/tmuxinator/widget.go b/modules/tmuxinator/widget.go index 878d0609c..4d8943db1 100644 --- a/modules/tmuxinator/widget.go +++ b/modules/tmuxinator/widget.go @@ -12,7 +12,6 @@ type Widget struct { settings *Settings Selected int - maxItems int Items []string tviewApp *tview.Application From 359aa4aa09ec9a0af6aba996a3751dd494b9aa46 Mon Sep 17 00:00:00 2001 From: Alexander Butt-Piercey Date: Mon, 27 Feb 2023 22:56:28 +0100 Subject: [PATCH 5/7] Dont reference widget --- modules/tmuxinator/form.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/tmuxinator/form.go b/modules/tmuxinator/form.go index 61f3fd5b5..f48c1208e 100644 --- a/modules/tmuxinator/form.go +++ b/modules/tmuxinator/form.go @@ -48,7 +48,7 @@ func (widget *Widget) addCancelButton(form *tview.Form) { form.SetCancelFunc(cancelFn) } -func (widget *Widget) addSaveButton(form *tview.Form, fctn func()) { +func (_ *Widget) addSaveButton(form *tview.Form, fctn func()) { form.AddButton("Save", fctn) } @@ -72,7 +72,7 @@ func (widget *Widget) modalForm(lbl, text string) *tview.Form { return form } -func (widget *Widget) modalFrame(form *tview.Form) *tview.Frame { +func (_ *Widget) modalFrame(form *tview.Form) *tview.Frame { frame := tview.NewFrame(form) frame.SetBorders(0, 0, 0, 0, 0, 0) frame.SetRect(offscreen, offscreen, modalWidth, modalHeight) From cb9a8ae0875e98db50c4d4a854f756cda98db03e Mon Sep 17 00:00:00 2001 From: Alexander Butt-Piercey Date: Mon, 27 Feb 2023 23:01:45 +0100 Subject: [PATCH 6/7] Dont reference widget at all --- modules/tmuxinator/form.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/tmuxinator/form.go b/modules/tmuxinator/form.go index f48c1208e..407179adf 100644 --- a/modules/tmuxinator/form.go +++ b/modules/tmuxinator/form.go @@ -48,7 +48,7 @@ func (widget *Widget) addCancelButton(form *tview.Form) { form.SetCancelFunc(cancelFn) } -func (_ *Widget) addSaveButton(form *tview.Form, fctn func()) { +func (*Widget) addSaveButton(form *tview.Form, fctn func()) { form.AddButton("Save", fctn) } @@ -72,7 +72,7 @@ func (widget *Widget) modalForm(lbl, text string) *tview.Form { return form } -func (_ *Widget) modalFrame(form *tview.Form) *tview.Frame { +func (*Widget) modalFrame(form *tview.Form) *tview.Frame { frame := tview.NewFrame(form) frame.SetBorders(0, 0, 0, 0, 0, 0) frame.SetRect(offscreen, offscreen, modalWidth, modalHeight) From 6dc951d24ff8c664b29263351e0963f9c7dba768 Mon Sep 17 00:00:00 2001 From: Alexander Butt-Piercey Date: Wed, 1 Mar 2023 13:12:10 +0100 Subject: [PATCH 7/7] Fix small bug where white space was appended at the end of a slice --- modules/tmuxinator/client/client.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/modules/tmuxinator/client/client.go b/modules/tmuxinator/client/client.go index 2af3a6ea0..486967eb5 100644 --- a/modules/tmuxinator/client/client.go +++ b/modules/tmuxinator/client/client.go @@ -12,7 +12,17 @@ func ProjectList() []string { cmd := exec.Command("sh", "-c", cmdString) - return strings.Split(utils.ExecuteCommand(cmd), " ") + result := strings.Split(utils.ExecuteCommand(cmd), " ") + + var projects []string + + for _, str := range result { + if str != "" { + projects = append(projects, str) + } + } + + return projects } func StartProject(projectName string) {