From 7c05d27f6d3ad79402b757b9d2c97f5abf300889 Mon Sep 17 00:00:00 2001 From: Josh Medeski Date: Thu, 19 Sep 2024 21:45:37 -0400 Subject: [PATCH] feat: add tmuxinator support (#171) * Added support for listing configs and connecting via tmuxinator with -T flag * feat: improve tmuxinator features - Simplify module names - Change connection strategy to use mapping based of connect Src - Add icon support to tmuxinator entries - Changed the priority to list and connect to tmuxinator before zoxide * chore: update mockery * fix: tests and remove log --------- Co-authored-by: kadriandev --- cloner/mock_Cloner.go | 2 +- configurator/mock_Configurator.go | 2 +- connector/config_test.go | 3 + connector/connect.go | 16 ++-- connector/connector.go | 20 ++-- connector/mock_Connector.go | 2 +- connector/tmux.go | 9 ++ connector/tmux_test.go | 3 + connector/tmuxinator.go | 23 +++++ dir/mock_Dir.go | 2 +- execwrap/mock_Exec.go | 2 +- execwrap/mock_ExecCmd.go | 2 +- git/mock_Git.go | 2 +- home/mock_Home.go | 2 +- icon/icon.go | 12 ++- icon/mock_Icon.go | 2 +- json/mock_Json.go | 2 +- lister/config.go | 3 +- lister/config_test.go | 4 +- lister/list.go | 8 +- lister/lister.go | 15 +-- lister/mock_Lister.go | 58 +++++++++++- lister/mock_srcStrategy.go | 2 +- lister/srcs.go | 10 +- lister/srcs_test.go | 2 +- lister/tmux_test.go | 4 +- lister/tmuxinator.go | 45 +++++++++ lister/tmuxinator_test.go | 38 ++++++++ lister/zoxide_test.go | 4 +- model/connect_opts.go | 5 +- model/sesh_session.go | 10 +- model/tmuxinator_config.go | 5 + namer/mock_Namer.go | 2 +- oswrap/mock_Os.go | 2 +- pathwrap/mock_Path.go | 2 +- runtimewrap/mock_Runtime.go | 2 +- seshcli/connect.go | 7 +- seshcli/list.go | 6 ++ seshcli/seshcli.go | 6 +- shell/mock_Shell.go | 2 +- startup/config.go | 5 + startup/mock_Startup.go | 2 +- tmux/mock_Tmux.go | 2 +- tmuxinator/list.go | 31 +++++++ tmuxinator/list_test.go | 28 ++++++ tmuxinator/mock_Tmuxinator.go | 148 ++++++++++++++++++++++++++++++ tmuxinator/tmuxinator.go | 23 +++++ zoxide/mock_Zoxide.go | 2 +- 48 files changed, 526 insertions(+), 63 deletions(-) create mode 100644 connector/tmuxinator.go create mode 100644 lister/tmuxinator.go create mode 100644 lister/tmuxinator_test.go create mode 100644 model/tmuxinator_config.go create mode 100644 tmuxinator/list.go create mode 100644 tmuxinator/list_test.go create mode 100644 tmuxinator/mock_Tmuxinator.go create mode 100644 tmuxinator/tmuxinator.go diff --git a/cloner/mock_Cloner.go b/cloner/mock_Cloner.go index b5d79b3..581f942 100644 --- a/cloner/mock_Cloner.go +++ b/cloner/mock_Cloner.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.45.1. DO NOT EDIT. +// Code generated by mockery v2.46.0. DO NOT EDIT. package cloner diff --git a/configurator/mock_Configurator.go b/configurator/mock_Configurator.go index 174e33c..2a489c2 100644 --- a/configurator/mock_Configurator.go +++ b/configurator/mock_Configurator.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.45.1. DO NOT EDIT. +// Code generated by mockery v2.46.0. DO NOT EDIT. package configurator diff --git a/connector/config_test.go b/connector/config_test.go index ca6c1e8..0f5087f 100644 --- a/connector/config_test.go +++ b/connector/config_test.go @@ -10,6 +10,7 @@ import ( "github.com/joshmedeski/sesh/namer" "github.com/joshmedeski/sesh/startup" "github.com/joshmedeski/sesh/tmux" + "github.com/joshmedeski/sesh/tmuxinator" "github.com/joshmedeski/sesh/zoxide" "github.com/stretchr/testify/assert" mock "github.com/stretchr/testify/mock" @@ -23,6 +24,7 @@ func TestConfigStrategy(t *testing.T) { mockStartup := new(startup.MockStartup) mockTmux := new(tmux.MockTmux) mockZoxide := new(zoxide.MockZoxide) + mockTmuxinator := new(tmuxinator.MockTmuxinator) c := &RealConnector{ model.Config{}, @@ -33,6 +35,7 @@ func TestConfigStrategy(t *testing.T) { mockStartup, mockTmux, mockZoxide, + mockTmuxinator, } mockTmux.On("AttachSession", mock.Anything).Return("attaching", nil) mockZoxide.On("Add", mock.Anything).Return(nil) diff --git a/connector/connect.go b/connector/connect.go index 3526ce7..5accd21 100644 --- a/connector/connect.go +++ b/connector/connect.go @@ -14,11 +14,19 @@ func (c *RealConnector) Connect(name string, opts model.ConnectOpts) (string, er // sesh connect --config (sesh list --config | fzf) strategies := []func(*RealConnector, string) (model.Connection, error){ tmuxStrategy, + tmuxinatorStrategy, configStrategy, dirStrategy, zoxideStrategy, } + connectStrategy := map[string]func(c *RealConnector, connection model.Connection, opts model.ConnectOpts) (string, error){ + "tmux": connectToTmux, + "tmuxinator": connectToTmuxinator, + "config": connectToTmux, + "zoxide": connectToTmux, + } + for _, strategy := range strategies { if connection, err := strategy(c, name); err != nil { return "", fmt.Errorf("failed to establish connection: %w", err) @@ -28,13 +36,7 @@ func (c *RealConnector) Connect(name string, opts model.ConnectOpts) (string, er if connection.AddToZoxide { c.zoxide.Add(connection.Session.Path) } - if connection.New { - c.tmux.NewSession(connection.Session.Name, connection.Session.Path) - c.startup.Exec(connection.Session) - } - // TODO: configure the ability to create a session in a detached way (like update) - // TODO: configure the ability to create a popup instead of switching (with no tmux bar?) - return c.tmux.SwitchOrAttach(connection.Session.Name, opts) + return connectStrategy[connection.Session.Src](c, connection, opts) } } diff --git a/connector/connector.go b/connector/connector.go index b94d3a3..585b53f 100644 --- a/connector/connector.go +++ b/connector/connector.go @@ -8,6 +8,7 @@ import ( "github.com/joshmedeski/sesh/namer" "github.com/joshmedeski/sesh/startup" "github.com/joshmedeski/sesh/tmux" + "github.com/joshmedeski/sesh/tmuxinator" "github.com/joshmedeski/sesh/zoxide" ) @@ -16,14 +17,15 @@ type Connector interface { } type RealConnector struct { - config model.Config - dir dir.Dir - home home.Home - lister lister.Lister - namer namer.Namer - startup startup.Startup - tmux tmux.Tmux - zoxide zoxide.Zoxide + config model.Config + dir dir.Dir + home home.Home + lister lister.Lister + namer namer.Namer + startup startup.Startup + tmux tmux.Tmux + zoxide zoxide.Zoxide + tmuxinator tmuxinator.Tmuxinator } func NewConnector( @@ -35,6 +37,7 @@ func NewConnector( startup startup.Startup, tmux tmux.Tmux, zoxide zoxide.Zoxide, + tmuxinator tmuxinator.Tmuxinator, ) Connector { return &RealConnector{ config, @@ -45,5 +48,6 @@ func NewConnector( startup, tmux, zoxide, + tmuxinator, } } diff --git a/connector/mock_Connector.go b/connector/mock_Connector.go index c0b74bb..fa37cd0 100644 --- a/connector/mock_Connector.go +++ b/connector/mock_Connector.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.45.1. DO NOT EDIT. +// Code generated by mockery v2.46.0. DO NOT EDIT. package connector diff --git a/connector/tmux.go b/connector/tmux.go index 27902e4..ea619eb 100644 --- a/connector/tmux.go +++ b/connector/tmux.go @@ -15,3 +15,12 @@ func tmuxStrategy(c *RealConnector, name string) (model.Connection, error) { // Switch: true }, nil } + +func connectToTmux(c *RealConnector, connection model.Connection, opts model.ConnectOpts) (string, error) { + if connection.New { + return c.tmux.SwitchOrAttach(connection.Session.Name, opts) + } + c.tmux.NewSession(connection.Session.Name, connection.Session.Path) + c.startup.Exec(connection.Session) + return c.tmux.SwitchOrAttach(connection.Session.Name, opts) +} diff --git a/connector/tmux_test.go b/connector/tmux_test.go index ed5be7e..7fa1fdc 100644 --- a/connector/tmux_test.go +++ b/connector/tmux_test.go @@ -10,6 +10,7 @@ import ( "github.com/joshmedeski/sesh/namer" "github.com/joshmedeski/sesh/startup" "github.com/joshmedeski/sesh/tmux" + "github.com/joshmedeski/sesh/tmuxinator" "github.com/joshmedeski/sesh/zoxide" "github.com/stretchr/testify/assert" mock "github.com/stretchr/testify/mock" @@ -23,6 +24,7 @@ func TestEstablishTmuxConnection(t *testing.T) { mockStartup := new(startup.MockStartup) mockTmux := new(tmux.MockTmux) mockZoxide := new(zoxide.MockZoxide) + mockTmuxinator := new(tmuxinator.MockTmuxinator) c := &RealConnector{ model.Config{}, @@ -33,6 +35,7 @@ func TestEstablishTmuxConnection(t *testing.T) { mockStartup, mockTmux, mockZoxide, + mockTmuxinator, } mockTmux.On("AttachSession", mock.Anything).Return("attaching", nil) mockZoxide.On("Add", mock.Anything).Return(nil) diff --git a/connector/tmuxinator.go b/connector/tmuxinator.go new file mode 100644 index 0000000..0edc16c --- /dev/null +++ b/connector/tmuxinator.go @@ -0,0 +1,23 @@ +package connector + +import ( + "github.com/joshmedeski/sesh/model" +) + +func tmuxinatorStrategy(c *RealConnector, name string) (model.Connection, error) { + session, exists := c.lister.FindTmuxinatorConfig(name) + if !exists { + return model.Connection{Found: false}, nil + } + + return model.Connection{ + Found: true, + Session: session, + New: true, + AddToZoxide: false, + }, nil +} + +func connectToTmuxinator(c *RealConnector, connection model.Connection, opts model.ConnectOpts) (string, error) { + return c.tmuxinator.Start(connection.Session.Name) +} diff --git a/dir/mock_Dir.go b/dir/mock_Dir.go index a5ee41a..3c3225f 100644 --- a/dir/mock_Dir.go +++ b/dir/mock_Dir.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.45.1. DO NOT EDIT. +// Code generated by mockery v2.46.0. DO NOT EDIT. package dir diff --git a/execwrap/mock_Exec.go b/execwrap/mock_Exec.go index a3509f8..72a6731 100644 --- a/execwrap/mock_Exec.go +++ b/execwrap/mock_Exec.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.45.1. DO NOT EDIT. +// Code generated by mockery v2.46.0. DO NOT EDIT. package execwrap diff --git a/execwrap/mock_ExecCmd.go b/execwrap/mock_ExecCmd.go index ce01172..8c5c250 100644 --- a/execwrap/mock_ExecCmd.go +++ b/execwrap/mock_ExecCmd.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.45.1. DO NOT EDIT. +// Code generated by mockery v2.46.0. DO NOT EDIT. package execwrap diff --git a/git/mock_Git.go b/git/mock_Git.go index c852a23..92ca961 100644 --- a/git/mock_Git.go +++ b/git/mock_Git.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.45.1. DO NOT EDIT. +// Code generated by mockery v2.46.0. DO NOT EDIT. package git diff --git a/home/mock_Home.go b/home/mock_Home.go index e3a1e33..340ba05 100644 --- a/home/mock_Home.go +++ b/home/mock_Home.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.45.1. DO NOT EDIT. +// Code generated by mockery v2.46.0. DO NOT EDIT. package home diff --git a/icon/icon.go b/icon/icon.go index 787bd4c..f9c771e 100644 --- a/icon/icon.go +++ b/icon/icon.go @@ -21,9 +21,10 @@ func NewIcon(config model.Config) Icon { } var ( - zoxideIcon string = "" - tmuxIcon string = "" - configIcon string = "" + zoxideIcon string = "" + tmuxIcon string = "" + configIcon string = "" + tmuxinatorIcon string = "" ) func ansiString(code int, s string) string { @@ -37,6 +38,9 @@ func (i *RealIcon) AddIcon(s model.SeshSession) string { case "tmux": icon = tmuxIcon colorCode = 34 // blue + case "tmuxinator": + icon = tmuxinatorIcon + colorCode = 33 // yellow case "zoxide": icon = zoxideIcon colorCode = 36 // cyan @@ -51,7 +55,7 @@ func (i *RealIcon) AddIcon(s model.SeshSession) string { } func (i *RealIcon) RemoveIcon(name string) string { - if strings.HasPrefix(name, tmuxIcon) || strings.HasPrefix(name, zoxideIcon) || strings.HasPrefix(name, configIcon) { + if strings.HasPrefix(name, tmuxIcon) || strings.HasPrefix(name, zoxideIcon) || strings.HasPrefix(name, configIcon) || strings.HasPrefix(name, tmuxinatorIcon) { return name[4:] } return name diff --git a/icon/mock_Icon.go b/icon/mock_Icon.go index 8c383b5..c9b1780 100644 --- a/icon/mock_Icon.go +++ b/icon/mock_Icon.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.45.1. DO NOT EDIT. +// Code generated by mockery v2.46.0. DO NOT EDIT. package icon diff --git a/json/mock_Json.go b/json/mock_Json.go index e070bc3..73b348b 100644 --- a/json/mock_Json.go +++ b/json/mock_Json.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.45.1. DO NOT EDIT. +// Code generated by mockery v2.46.0. DO NOT EDIT. package json diff --git a/lister/config.go b/lister/config.go index 30129c1..28fb319 100644 --- a/lister/config.go +++ b/lister/config.go @@ -26,6 +26,7 @@ func listConfig(l *RealLister) (model.SeshSessions, error) { Name: session.Name, Path: path, StartupCommand: session.StartupCommand, + Tmuxinator: session.Tmuxinator, } } } @@ -36,8 +37,8 @@ func listConfig(l *RealLister) (model.SeshSessions, error) { } func (l *RealLister) FindConfigSession(name string) (model.SeshSession, bool) { - sessions, _ := listConfig(l) key := configKey(name) + sessions, _ := listConfig(l) if session, exists := sessions.Directory[key]; exists { return session, exists } else { diff --git a/lister/config_test.go b/lister/config_test.go index 84e3bde..dd6fd2a 100644 --- a/lister/config_test.go +++ b/lister/config_test.go @@ -7,6 +7,7 @@ import ( "github.com/joshmedeski/sesh/home" "github.com/joshmedeski/sesh/model" "github.com/joshmedeski/sesh/tmux" + "github.com/joshmedeski/sesh/tmuxinator" "github.com/joshmedeski/sesh/zoxide" "github.com/stretchr/testify/assert" ) @@ -16,6 +17,7 @@ func TestListConfigSessions(t *testing.T) { mockHome.On("ExpandHome", "/Users/joshmedeski/.config/sesh").Return("/Users/joshmedeski/.config/sesh", nil) mockZoxide := new(zoxide.MockZoxide) mockTmux := new(tmux.MockTmux) + mockTmuxinator := new(tmuxinator.MockTmuxinator) config := model.Config{ SessionConfigs: []model.SessionConfig{ { @@ -24,7 +26,7 @@ func TestListConfigSessions(t *testing.T) { }, }, } - lister := NewLister(config, mockHome, mockTmux, mockZoxide) + lister := NewLister(config, mockHome, mockTmux, mockZoxide, mockTmuxinator) realLister, ok := lister.(*RealLister) if !ok { diff --git a/lister/list.go b/lister/list.go index 1891342..8a71f12 100644 --- a/lister/list.go +++ b/lister/list.go @@ -12,14 +12,16 @@ type ( Json bool Tmux bool Zoxide bool + Tmuxinator bool } srcStrategy func(*RealLister) (model.SeshSessions, error) ) var srcStrategies = map[string]srcStrategy{ - "tmux": listTmux, - "config": listConfig, - "zoxide": listZoxide, + "tmux": listTmux, + "config": listConfig, + "tmuxinator": listTmuxinator, + "zoxide": listZoxide, } func (l *RealLister) List(opts ListOptions) (model.SeshSessions, error) { diff --git a/lister/lister.go b/lister/lister.go index d47d913..df63f86 100644 --- a/lister/lister.go +++ b/lister/lister.go @@ -4,6 +4,7 @@ import ( "github.com/joshmedeski/sesh/home" "github.com/joshmedeski/sesh/model" "github.com/joshmedeski/sesh/tmux" + "github.com/joshmedeski/sesh/tmuxinator" "github.com/joshmedeski/sesh/zoxide" ) @@ -13,15 +14,17 @@ type Lister interface { GetLastTmuxSession() (model.SeshSession, bool) FindConfigSession(name string) (model.SeshSession, bool) FindZoxideSession(name string) (model.SeshSession, bool) + FindTmuxinatorConfig(name string) (model.SeshSession, bool) } type RealLister struct { - home home.Home - tmux tmux.Tmux - zoxide zoxide.Zoxide - config model.Config + config model.Config + home home.Home + tmux tmux.Tmux + zoxide zoxide.Zoxide + tmuxinator tmuxinator.Tmuxinator } -func NewLister(config model.Config, home home.Home, tmux tmux.Tmux, zoxide zoxide.Zoxide) Lister { - return &RealLister{home, tmux, zoxide, config} +func NewLister(config model.Config, home home.Home, tmux tmux.Tmux, zoxide zoxide.Zoxide, tmuxinator tmuxinator.Tmuxinator) Lister { + return &RealLister{config, home, tmux, zoxide, tmuxinator} } diff --git a/lister/mock_Lister.go b/lister/mock_Lister.go index 71ae468..7a2473d 100644 --- a/lister/mock_Lister.go +++ b/lister/mock_Lister.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.45.1. DO NOT EDIT. +// Code generated by mockery v2.46.0. DO NOT EDIT. package lister @@ -132,6 +132,62 @@ func (_c *MockLister_FindTmuxSession_Call) RunAndReturn(run func(string) (model. return _c } +// FindTmuxinatorConfig provides a mock function with given fields: name +func (_m *MockLister) FindTmuxinatorConfig(name string) (model.SeshSession, bool) { + ret := _m.Called(name) + + if len(ret) == 0 { + panic("no return value specified for FindTmuxinatorConfig") + } + + var r0 model.SeshSession + var r1 bool + if rf, ok := ret.Get(0).(func(string) (model.SeshSession, bool)); ok { + return rf(name) + } + if rf, ok := ret.Get(0).(func(string) model.SeshSession); ok { + r0 = rf(name) + } else { + r0 = ret.Get(0).(model.SeshSession) + } + + if rf, ok := ret.Get(1).(func(string) bool); ok { + r1 = rf(name) + } else { + r1 = ret.Get(1).(bool) + } + + return r0, r1 +} + +// MockLister_FindTmuxinatorConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindTmuxinatorConfig' +type MockLister_FindTmuxinatorConfig_Call struct { + *mock.Call +} + +// FindTmuxinatorConfig is a helper method to define mock.On call +// - name string +func (_e *MockLister_Expecter) FindTmuxinatorConfig(name interface{}) *MockLister_FindTmuxinatorConfig_Call { + return &MockLister_FindTmuxinatorConfig_Call{Call: _e.mock.On("FindTmuxinatorConfig", name)} +} + +func (_c *MockLister_FindTmuxinatorConfig_Call) Run(run func(name string)) *MockLister_FindTmuxinatorConfig_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *MockLister_FindTmuxinatorConfig_Call) Return(_a0 model.SeshSession, _a1 bool) *MockLister_FindTmuxinatorConfig_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockLister_FindTmuxinatorConfig_Call) RunAndReturn(run func(string) (model.SeshSession, bool)) *MockLister_FindTmuxinatorConfig_Call { + _c.Call.Return(run) + return _c +} + // FindZoxideSession provides a mock function with given fields: name func (_m *MockLister) FindZoxideSession(name string) (model.SeshSession, bool) { ret := _m.Called(name) diff --git a/lister/mock_srcStrategy.go b/lister/mock_srcStrategy.go index 28163fa..d459467 100644 --- a/lister/mock_srcStrategy.go +++ b/lister/mock_srcStrategy.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.45.1. DO NOT EDIT. +// Code generated by mockery v2.46.0. DO NOT EDIT. package lister diff --git a/lister/srcs.go b/lister/srcs.go index d4ee685..21b0bd9 100644 --- a/lister/srcs.go +++ b/lister/srcs.go @@ -9,11 +9,14 @@ func srcs(opts ListOptions) []string { if opts.Config { count++ } + if opts.Tmuxinator { + count++ + } if opts.Zoxide { count++ } if count == 0 { - return []string{"tmux", "config", "zoxide"} + return []string{"tmux", "config", "tmuxinator", "zoxide"} } srcs = make([]string, count) i := 0 @@ -25,8 +28,13 @@ func srcs(opts ListOptions) []string { srcs[i] = "config" i++ } + if opts.Tmuxinator { + srcs[i] = "tmuxinator" + i++ + } if opts.Zoxide { srcs[i] = "zoxide" + i++ } return srcs } diff --git a/lister/srcs_test.go b/lister/srcs_test.go index 7ea4f6e..c51f409 100644 --- a/lister/srcs_test.go +++ b/lister/srcs_test.go @@ -15,7 +15,7 @@ func TestSrcs(t *testing.T) { { name: "All options are false", opts: ListOptions{}, - expected: []string{"tmux", "config", "zoxide"}, + expected: []string{"tmux", "config", "tmuxinator", "zoxide"}, }, { name: "Only Tmux is true", diff --git a/lister/tmux_test.go b/lister/tmux_test.go index f33e519..90266b4 100644 --- a/lister/tmux_test.go +++ b/lister/tmux_test.go @@ -8,6 +8,7 @@ import ( "github.com/joshmedeski/sesh/home" "github.com/joshmedeski/sesh/model" "github.com/joshmedeski/sesh/tmux" + "github.com/joshmedeski/sesh/tmuxinator" "github.com/joshmedeski/sesh/zoxide" "github.com/stretchr/testify/assert" ) @@ -74,7 +75,8 @@ func TestListTmuxSessions(t *testing.T) { mockConfig := model.Config{} mockHome := new(home.MockHome) mockZoxide := new(zoxide.MockZoxide) - lister := NewLister(mockConfig, mockHome, mockTmux, mockZoxide) + mockTmuxinator := new(tmuxinator.MockTmuxinator) + lister := NewLister(mockConfig, mockHome, mockTmux, mockZoxide, mockTmuxinator) realLister, ok := lister.(*RealLister) if !ok { diff --git a/lister/tmuxinator.go b/lister/tmuxinator.go new file mode 100644 index 0000000..09a5642 --- /dev/null +++ b/lister/tmuxinator.go @@ -0,0 +1,45 @@ +package lister + +import ( + "fmt" + + "github.com/joshmedeski/sesh/model" +) + +func tmuxinatorKey(name string) string { + return fmt.Sprintf("tmuxinator:%s", name) +} + +func listTmuxinator(l *RealLister) (model.SeshSessions, error) { + tmuxinatorResults, err := l.tmuxinator.List() + if err != nil { + return model.SeshSessions{}, fmt.Errorf("couldn't list tmuxinator sessions: %q", err) + } + + numTmuxinatorResults := len(tmuxinatorResults) + orderedIndex := make([]string, numTmuxinatorResults) + directory := make(model.SeshSessionMap) + + for i, session := range tmuxinatorResults { + key := tmuxinatorKey(session.Name) + orderedIndex[i] = key + directory[key] = model.SeshSession{ + Src: "tmuxinator", + Name: session.Name, + } + } + return model.SeshSessions{ + Directory: directory, + OrderedIndex: orderedIndex, + }, nil +} + +func (l *RealLister) FindTmuxinatorConfig(name string) (model.SeshSession, bool) { + sessions, _ := listTmuxinator(l) + key := tmuxinatorKey(name) + if session, exists := sessions.Directory[key]; exists { + return session, exists + } else { + return model.SeshSession{}, false + } +} diff --git a/lister/tmuxinator_test.go b/lister/tmuxinator_test.go new file mode 100644 index 0000000..09148bd --- /dev/null +++ b/lister/tmuxinator_test.go @@ -0,0 +1,38 @@ +package lister + +import ( + "log" + "testing" + + "github.com/joshmedeski/sesh/home" + "github.com/joshmedeski/sesh/model" + "github.com/joshmedeski/sesh/tmux" + "github.com/joshmedeski/sesh/tmuxinator" + "github.com/joshmedeski/sesh/zoxide" + "github.com/stretchr/testify/assert" +) + +func TestListTmuxinatorConfigs(t *testing.T) { + t.Run("should list tmuxinator configs", func(t *testing.T) { + mockConfig := model.Config{} + mockHome := new(home.MockHome) + mockZoxide := new(zoxide.MockZoxide) + mockTmux := new(tmux.MockTmux) + mockTmuxinator := new(tmuxinator.MockTmuxinator) + mockTmuxinator.On("List").Return([]*model.TmuxinatorConfig{ + {Name: "sesh"}, + {Name: "dotfiles"}, + }, nil) + + lister := NewLister(mockConfig, mockHome, mockTmux, mockZoxide, mockTmuxinator) + + realLister, ok := lister.(*RealLister) + if !ok { + log.Fatal("Cannot convert lister to *RealLister") + } + sessions, err := listTmuxinator(realLister) + assert.Equal(t, "tmuxinator:sesh", sessions.OrderedIndex[0]) + assert.Equal(t, "tmuxinator:dotfiles", sessions.OrderedIndex[1]) + assert.Nil(t, err) + }) +} diff --git a/lister/zoxide_test.go b/lister/zoxide_test.go index d904c9a..d3c2ad9 100644 --- a/lister/zoxide_test.go +++ b/lister/zoxide_test.go @@ -7,6 +7,7 @@ import ( "github.com/joshmedeski/sesh/home" "github.com/joshmedeski/sesh/model" "github.com/joshmedeski/sesh/tmux" + "github.com/joshmedeski/sesh/tmuxinator" "github.com/joshmedeski/sesh/zoxide" "github.com/stretchr/testify/assert" ) @@ -17,6 +18,7 @@ func TestListZoxideSessions(t *testing.T) { mockHome := new(home.MockHome) mockZoxide := new(zoxide.MockZoxide) mockTmux := new(tmux.MockTmux) + mockTmuxinator := new(tmuxinator.MockTmuxinator) mockHome.On("ShortenHome", "/Users/joshmedeski/.config/sesh").Return("~/.config/sesh", nil) mockHome.On("ShortenHome", "/Users/joshmedeski/.config/fish").Return("~/.config/fish", nil) mockZoxide.On("ListResults").Return([]*model.ZoxideResult{ @@ -30,7 +32,7 @@ func TestListZoxideSessions(t *testing.T) { }, }, nil) - lister := NewLister(mockConfig, mockHome, mockTmux, mockZoxide) + lister := NewLister(mockConfig, mockHome, mockTmux, mockZoxide, mockTmuxinator) realLister, ok := lister.(*RealLister) if !ok { diff --git a/model/connect_opts.go b/model/connect_opts.go index 4631c34..d69225e 100644 --- a/model/connect_opts.go +++ b/model/connect_opts.go @@ -1,6 +1,7 @@ package model type ConnectOpts struct { - Command string - Switch bool + Command string + Switch bool + Tmuxinator bool } diff --git a/model/sesh_session.go b/model/sesh_session.go index b6a7937..a16cb2d 100644 --- a/model/sesh_session.go +++ b/model/sesh_session.go @@ -11,19 +11,21 @@ type ( SeshSessionMap map[string]SeshSession SeshSession struct { - Src string // The source of the session (config, tmux, zoxide) + Src string // The source of the session (config, tmux, zoxide, tmuxinator) Name string // The display name Path string // The absolute directory path StartupCommand string // The command to run when the session is started + Tmuxinator string // Name of the tmuxinator config Attached int // Whether the session is currently attached Windows int // The number of windows in the session Score float64 // The score of the session (from Zoxide) } SeshSrcs struct { - Config bool - Tmux bool - Zoxide bool + Config bool + Tmux bool + Tmuxinator bool + Zoxide bool } ) diff --git a/model/tmuxinator_config.go b/model/tmuxinator_config.go new file mode 100644 index 0000000..6e329ce --- /dev/null +++ b/model/tmuxinator_config.go @@ -0,0 +1,5 @@ +package model + +type TmuxinatorConfig struct { + Name string +} diff --git a/namer/mock_Namer.go b/namer/mock_Namer.go index 0a407f4..1272222 100644 --- a/namer/mock_Namer.go +++ b/namer/mock_Namer.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.45.1. DO NOT EDIT. +// Code generated by mockery v2.46.0. DO NOT EDIT. package namer diff --git a/oswrap/mock_Os.go b/oswrap/mock_Os.go index b0028bf..f701eee 100644 --- a/oswrap/mock_Os.go +++ b/oswrap/mock_Os.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.45.1. DO NOT EDIT. +// Code generated by mockery v2.46.0. DO NOT EDIT. package oswrap diff --git a/pathwrap/mock_Path.go b/pathwrap/mock_Path.go index 299bc3c..a943138 100644 --- a/pathwrap/mock_Path.go +++ b/pathwrap/mock_Path.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.45.1. DO NOT EDIT. +// Code generated by mockery v2.46.0. DO NOT EDIT. package pathwrap diff --git a/runtimewrap/mock_Runtime.go b/runtimewrap/mock_Runtime.go index 72c5d28..b5b2157 100644 --- a/runtimewrap/mock_Runtime.go +++ b/runtimewrap/mock_Runtime.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.45.1. DO NOT EDIT. +// Code generated by mockery v2.46.0. DO NOT EDIT. package runtimewrap diff --git a/seshcli/connect.go b/seshcli/connect.go index e112608..61de087 100644 --- a/seshcli/connect.go +++ b/seshcli/connect.go @@ -27,6 +27,11 @@ func Connect(c connector.Connector, i icon.Icon) *cli.Command { Aliases: []string{"c"}, Usage: "Execute a command when connecting to a new session. Will be ignored if the session exists.", }, + &cli.BoolFlag{ + Name: "tmuxinator", + Aliases: []string{"T"}, + Usage: "Use tmuxinator to start session if it doesnt exist", + }, }, Action: func(cCtx *cli.Context) error { if cCtx.NArg() == 0 { @@ -36,7 +41,7 @@ func Connect(c connector.Connector, i icon.Icon) *cli.Command { if name == "" { return nil } - opts := model.ConnectOpts{Switch: cCtx.Bool("switch"), Command: cCtx.String("command")} + opts := model.ConnectOpts{Switch: cCtx.Bool("switch"), Command: cCtx.String("command"), Tmuxinator: cCtx.Bool("tmuxinator")} trimmedName := i.RemoveIcon(name) if _, err := c.Connect(trimmedName, opts); err != nil { // TODO: add to logging diff --git a/seshcli/list.go b/seshcli/list.go index 42583e2..20d5568 100644 --- a/seshcli/list.go +++ b/seshcli/list.go @@ -47,6 +47,11 @@ func List(icon icon.Icon, json json.Json, list lister.Lister) *cli.Command { Aliases: []string{"i"}, Usage: "show icons", }, + &cli.BoolFlag{ + Name: "tmuxinator", + Aliases: []string{"T"}, + Usage: "show tmuxinator configs", + }, }, Action: func(cCtx *cli.Context) error { sessions, err := list.List(lister.ListOptions{ @@ -56,6 +61,7 @@ func List(icon icon.Icon, json json.Json, list lister.Lister) *cli.Command { Json: cCtx.Bool("json"), Tmux: cCtx.Bool("tmux"), Zoxide: cCtx.Bool("zoxide"), + Tmuxinator: cCtx.Bool("tmuxinator"), }) if err != nil { return fmt.Errorf("couldn't list sessions: %q", err) diff --git a/seshcli/seshcli.go b/seshcli/seshcli.go index b835adc..4b3f270 100644 --- a/seshcli/seshcli.go +++ b/seshcli/seshcli.go @@ -17,6 +17,7 @@ import ( "github.com/joshmedeski/sesh/shell" "github.com/joshmedeski/sesh/startup" "github.com/joshmedeski/sesh/tmux" + "github.com/joshmedeski/sesh/tmuxinator" "github.com/joshmedeski/sesh/zoxide" "github.com/urfave/cli/v2" ) @@ -38,6 +39,7 @@ func App(version string) cli.App { git := git.NewGit(shell) tmux := tmux.NewTmux(os, shell) zoxide := zoxide.NewZoxide(shell) + tmuxinator := tmuxinator.NewTmuxinator(shell) // config config, err := configurator.NewConfigurator(os, path, runtime).GetConfig() @@ -47,10 +49,10 @@ func App(version string) cli.App { } // core dependencies - lister := lister.NewLister(config, home, tmux, zoxide) + lister := lister.NewLister(config, home, tmux, zoxide, tmuxinator) startup := startup.NewStartup(config, lister, tmux) namer := namer.NewNamer(path, git) - connector := connector.NewConnector(config, dir, home, lister, namer, startup, tmux, zoxide) + connector := connector.NewConnector(config, dir, home, lister, namer, startup, tmux, zoxide, tmuxinator) icon := icon.NewIcon(config) return cli.App{ diff --git a/shell/mock_Shell.go b/shell/mock_Shell.go index 5537b4b..a283a6a 100644 --- a/shell/mock_Shell.go +++ b/shell/mock_Shell.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.45.1. DO NOT EDIT. +// Code generated by mockery v2.46.0. DO NOT EDIT. package shell diff --git a/startup/config.go b/startup/config.go index db05ae1..01f3dce 100644 --- a/startup/config.go +++ b/startup/config.go @@ -4,6 +4,11 @@ import "github.com/joshmedeski/sesh/model" func configStrategy(s *RealStartup, session model.SeshSession) (string, error) { config, exists := s.lister.FindConfigSession(session.Name) + + if exists && config.Tmuxinator != "" { + return config.Tmuxinator, nil + } + if exists && config.StartupCommand != "" { return config.StartupCommand, nil } diff --git a/startup/mock_Startup.go b/startup/mock_Startup.go index 18b039c..e84ac9a 100644 --- a/startup/mock_Startup.go +++ b/startup/mock_Startup.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.45.1. DO NOT EDIT. +// Code generated by mockery v2.46.0. DO NOT EDIT. package startup diff --git a/tmux/mock_Tmux.go b/tmux/mock_Tmux.go index a5fb90c..7c1781a 100644 --- a/tmux/mock_Tmux.go +++ b/tmux/mock_Tmux.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.45.1. DO NOT EDIT. +// Code generated by mockery v2.46.0. DO NOT EDIT. package tmux diff --git a/tmuxinator/list.go b/tmuxinator/list.go new file mode 100644 index 0000000..c449f78 --- /dev/null +++ b/tmuxinator/list.go @@ -0,0 +1,31 @@ +package tmuxinator + +import ( + "strings" + + "github.com/joshmedeski/sesh/model" +) + +func (t *RealTmuxinator) List() ([]*model.TmuxinatorConfig, error) { + res, err := t.shell.ListCmd("tmuxinator", "list") + if err != nil { + // NOTE: return empty list if error + return []*model.TmuxinatorConfig{}, nil + } + return parseTmuxinatorConfigsOutput(res) +} + +func parseTmuxinatorConfigsOutput(rawList []string) ([]*model.TmuxinatorConfig, error) { + cleanedList := strings.Split(rawList[1], " ") + sessions := make([]*model.TmuxinatorConfig, 0, len(cleanedList)) + for _, line := range cleanedList { + if len(line) > 0 { + session := &model.TmuxinatorConfig{ + Name: strings.TrimSpace(line), + } + sessions = append(sessions, session) + } + } + + return sessions, nil +} diff --git a/tmuxinator/list_test.go b/tmuxinator/list_test.go new file mode 100644 index 0000000..2925e13 --- /dev/null +++ b/tmuxinator/list_test.go @@ -0,0 +1,28 @@ +package tmuxinator + +import ( + "testing" + + "github.com/joshmedeski/sesh/model" + "github.com/joshmedeski/sesh/shell" + "github.com/stretchr/testify/assert" +) + +func TestListConfigs(t *testing.T) { + t.Run("List Tmuxinator Configs", func(t *testing.T) { + mockShell := new(shell.MockShell) + tmuxinator := &RealTmuxinator{shell: mockShell} + mockShell.EXPECT().ListCmd("tmuxinator", "list").Return([]string{ + "tmuxinator projects:", + "dotfiles sesh home", + }, nil) + expected := []*model.TmuxinatorConfig{ + {Name: "dotfiles"}, + {Name: "sesh"}, + {Name: "home"}, + } + actual, err := tmuxinator.List() + assert.Nil(t, err) + assert.Equal(t, expected, actual) + }) +} diff --git a/tmuxinator/mock_Tmuxinator.go b/tmuxinator/mock_Tmuxinator.go new file mode 100644 index 0000000..4e727f2 --- /dev/null +++ b/tmuxinator/mock_Tmuxinator.go @@ -0,0 +1,148 @@ +// Code generated by mockery v2.46.0. DO NOT EDIT. + +package tmuxinator + +import ( + model "github.com/joshmedeski/sesh/model" + mock "github.com/stretchr/testify/mock" +) + +// MockTmuxinator is an autogenerated mock type for the Tmuxinator type +type MockTmuxinator struct { + mock.Mock +} + +type MockTmuxinator_Expecter struct { + mock *mock.Mock +} + +func (_m *MockTmuxinator) EXPECT() *MockTmuxinator_Expecter { + return &MockTmuxinator_Expecter{mock: &_m.Mock} +} + +// List provides a mock function with given fields: +func (_m *MockTmuxinator) List() ([]*model.TmuxinatorConfig, error) { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for List") + } + + var r0 []*model.TmuxinatorConfig + var r1 error + if rf, ok := ret.Get(0).(func() ([]*model.TmuxinatorConfig, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() []*model.TmuxinatorConfig); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*model.TmuxinatorConfig) + } + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockTmuxinator_List_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'List' +type MockTmuxinator_List_Call struct { + *mock.Call +} + +// List is a helper method to define mock.On call +func (_e *MockTmuxinator_Expecter) List() *MockTmuxinator_List_Call { + return &MockTmuxinator_List_Call{Call: _e.mock.On("List")} +} + +func (_c *MockTmuxinator_List_Call) Run(run func()) *MockTmuxinator_List_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockTmuxinator_List_Call) Return(_a0 []*model.TmuxinatorConfig, _a1 error) *MockTmuxinator_List_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockTmuxinator_List_Call) RunAndReturn(run func() ([]*model.TmuxinatorConfig, error)) *MockTmuxinator_List_Call { + _c.Call.Return(run) + return _c +} + +// Start provides a mock function with given fields: targetSession +func (_m *MockTmuxinator) Start(targetSession string) (string, error) { + ret := _m.Called(targetSession) + + if len(ret) == 0 { + panic("no return value specified for Start") + } + + var r0 string + var r1 error + if rf, ok := ret.Get(0).(func(string) (string, error)); ok { + return rf(targetSession) + } + if rf, ok := ret.Get(0).(func(string) string); ok { + r0 = rf(targetSession) + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(targetSession) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockTmuxinator_Start_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Start' +type MockTmuxinator_Start_Call struct { + *mock.Call +} + +// Start is a helper method to define mock.On call +// - targetSession string +func (_e *MockTmuxinator_Expecter) Start(targetSession interface{}) *MockTmuxinator_Start_Call { + return &MockTmuxinator_Start_Call{Call: _e.mock.On("Start", targetSession)} +} + +func (_c *MockTmuxinator_Start_Call) Run(run func(targetSession string)) *MockTmuxinator_Start_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *MockTmuxinator_Start_Call) Return(_a0 string, _a1 error) *MockTmuxinator_Start_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockTmuxinator_Start_Call) RunAndReturn(run func(string) (string, error)) *MockTmuxinator_Start_Call { + _c.Call.Return(run) + return _c +} + +// NewMockTmuxinator creates a new instance of MockTmuxinator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockTmuxinator(t interface { + mock.TestingT + Cleanup(func()) +}) *MockTmuxinator { + mock := &MockTmuxinator{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/tmuxinator/tmuxinator.go b/tmuxinator/tmuxinator.go new file mode 100644 index 0000000..e8b6e46 --- /dev/null +++ b/tmuxinator/tmuxinator.go @@ -0,0 +1,23 @@ +package tmuxinator + +import ( + "github.com/joshmedeski/sesh/model" + "github.com/joshmedeski/sesh/shell" +) + +type Tmuxinator interface { + List() ([]*model.TmuxinatorConfig, error) + Start(targetSession string) (string, error) +} + +type RealTmuxinator struct { + shell shell.Shell +} + +func NewTmuxinator(shell shell.Shell) Tmuxinator { + return &RealTmuxinator{shell} +} + +func (t *RealTmuxinator) Start(targetSession string) (string, error) { + return t.shell.Cmd("tmuxinator", "start", targetSession) +} diff --git a/zoxide/mock_Zoxide.go b/zoxide/mock_Zoxide.go index 37f73e0..15a96c3 100644 --- a/zoxide/mock_Zoxide.go +++ b/zoxide/mock_Zoxide.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.45.1. DO NOT EDIT. +// Code generated by mockery v2.46.0. DO NOT EDIT. package zoxide