diff --git a/README.md b/README.md index e4ae472..b3ae4a1 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,7 @@ In order to integrate with tmux, you can add a binding to your tmux config (`tmu ```sh bind-key "T" run-shell "sesh connect \"$( - sesh list --icons | fzf-tmux -p 55%,60% \ + sesh list --icons | fzf-tmux -p 80%,70% \ --no-sort --ansi --border-label ' sesh ' --prompt '⚡ ' \ --header ' ^a all ^t tmux ^g configs ^x zoxide ^d tmux kill ^f find' \ --bind 'tab:down,btab:up' \ @@ -121,6 +121,8 @@ bind-key "T" run-shell "sesh connect \"$( --bind 'ctrl-x:change-prompt(📁 )+reload(sesh list -z --icons)' \ --bind 'ctrl-f:change-prompt(🔎 )+reload(fd -H -d 2 -t d -E .Trash . ~)' \ --bind 'ctrl-d:execute(tmux kill-session -t {2..})+change-prompt(⚡ )+reload(sesh list --icons)' \ + --preview-window 'right:55%' \ + --preview 'sesh preview {}' )\"" ``` @@ -222,9 +224,14 @@ mkdir -p ~/.config/sesh && touch ~/.config/sesh/sesh.toml The default session can be configured to run a command when connecting to a session. This is useful for running a dev server or starting a tmux plugin. +Additionally, you can define a preview command that runs when previewing the session's directory. This can be handy for displaying files with tools like [eza](https://github.com/eza-community/eza) or [lsd](https://github.com/lsd-rs/lsd). + +Note: The `{}` will be automatically replaced with the session's path. + ```toml [default_session] startup_command = "nvim -c ':Telescope find_files'" +preview_command = "eza --all --git --icons --color=always {}" ``` If you want to disable the default start command on a specific session, you can set `disable_startup_command = true`. @@ -235,7 +242,9 @@ A startup command is a command that is run when a session is created. It is usef **Note:** If you use the `--command/-c` flag, then the startup script will not be run. -I like to use a command that opens nvim on session startup: +I like to use a command that opens nvim on session startup. + +You can also define a preview command to display the contents of a specific file using [bat](https://github.com/sharkdp/bat) or any another file previewer of your choice. ```toml [[session]] @@ -247,6 +256,7 @@ startup_command = "ls" name = "tmux config" path = "~/c/dotfiles/.config/tmux" startup_command = "nvim tmux.conf" +preview_command = "bat --color=always ~/c/dotfiles/.config/tmux/tmux.conf" ``` ### Listing Configurations diff --git a/cloner/mock_Cloner.go b/cloner/mock_Cloner.go index b0cecf5..b81dc52 100644 --- a/cloner/mock_Cloner.go +++ b/cloner/mock_Cloner.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.3. DO NOT EDIT. +// Code generated by mockery v2.50.0. DO NOT EDIT. package cloner diff --git a/configurator/mock_Configurator.go b/configurator/mock_Configurator.go index 6019aaf..5c00e2c 100644 --- a/configurator/mock_Configurator.go +++ b/configurator/mock_Configurator.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.3. DO NOT EDIT. +// Code generated by mockery v2.50.0. DO NOT EDIT. package configurator @@ -20,7 +20,7 @@ func (_m *MockConfigurator) EXPECT() *MockConfigurator_Expecter { return &MockConfigurator_Expecter{mock: &_m.Mock} } -// GetConfig provides a mock function with given fields: +// GetConfig provides a mock function with no fields func (_m *MockConfigurator) GetConfig() (model.Config, error) { ret := _m.Called() diff --git a/connector/mock_Connector.go b/connector/mock_Connector.go index 4563604..ad5646c 100644 --- a/connector/mock_Connector.go +++ b/connector/mock_Connector.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.3. DO NOT EDIT. +// Code generated by mockery v2.50.0. DO NOT EDIT. package connector diff --git a/dir/mock_Dir.go b/dir/mock_Dir.go index 200d0a0..5406597 100644 --- a/dir/mock_Dir.go +++ b/dir/mock_Dir.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.3. DO NOT EDIT. +// Code generated by mockery v2.50.0. DO NOT EDIT. package dir diff --git a/execwrap/mock_Exec.go b/execwrap/mock_Exec.go index a1d990f..eb0f033 100644 --- a/execwrap/mock_Exec.go +++ b/execwrap/mock_Exec.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.3. DO NOT EDIT. +// Code generated by mockery v2.50.0. DO NOT EDIT. package execwrap diff --git a/execwrap/mock_ExecCmd.go b/execwrap/mock_ExecCmd.go index 4bde55b..7602488 100644 --- a/execwrap/mock_ExecCmd.go +++ b/execwrap/mock_ExecCmd.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.3. DO NOT EDIT. +// Code generated by mockery v2.50.0. DO NOT EDIT. package execwrap @@ -17,7 +17,7 @@ func (_m *MockExecCmd) EXPECT() *MockExecCmd_Expecter { return &MockExecCmd_Expecter{mock: &_m.Mock} } -// CombinedOutput provides a mock function with given fields: +// CombinedOutput provides a mock function with no fields func (_m *MockExecCmd) CombinedOutput() ([]byte, error) { ret := _m.Called() @@ -74,7 +74,7 @@ func (_c *MockExecCmd_CombinedOutput_Call) RunAndReturn(run func() ([]byte, erro return _c } -// Output provides a mock function with given fields: +// Output provides a mock function with no fields func (_m *MockExecCmd) Output() ([]byte, error) { ret := _m.Called() diff --git a/git/mock_Git.go b/git/mock_Git.go index 5f50c31..4dbbbbd 100644 --- a/git/mock_Git.go +++ b/git/mock_Git.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.3. DO NOT EDIT. +// Code generated by mockery v2.50.0. DO NOT EDIT. package git diff --git a/home/mock_Home.go b/home/mock_Home.go index 981cdd1..a2b1dec 100644 --- a/home/mock_Home.go +++ b/home/mock_Home.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.3. DO NOT EDIT. +// Code generated by mockery v2.50.0. DO NOT EDIT. package home diff --git a/icon/mock_Icon.go b/icon/mock_Icon.go index 3f35e72..45532d8 100644 --- a/icon/mock_Icon.go +++ b/icon/mock_Icon.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.3. DO NOT EDIT. +// Code generated by mockery v2.50.0. DO NOT EDIT. package icon diff --git a/json/mock_Json.go b/json/mock_Json.go index 9715ebf..6196c50 100644 --- a/json/mock_Json.go +++ b/json/mock_Json.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.3. DO NOT EDIT. +// Code generated by mockery v2.50.0. DO NOT EDIT. package json diff --git a/lister/config.go b/lister/config.go index f299e6d..c6e2f4a 100644 --- a/lister/config.go +++ b/lister/config.go @@ -31,6 +31,7 @@ func listConfig(l *RealLister) (model.SeshSessions, error) { Name: session.Name, Path: path, StartupCommand: session.StartupCommand, + PreviewCommand: session.PreviewCommand, DisableStartupCommand: session.DisableStartCommand, Tmuxinator: session.Tmuxinator, } diff --git a/lister/mock_Lister.go b/lister/mock_Lister.go index cdc4b14..1fa55c8 100644 --- a/lister/mock_Lister.go +++ b/lister/mock_Lister.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.3. DO NOT EDIT. +// Code generated by mockery v2.50.0. DO NOT EDIT. package lister @@ -244,7 +244,7 @@ func (_c *MockLister_FindZoxideSession_Call) RunAndReturn(run func(string) (mode return _c } -// GetAttachedTmuxSession provides a mock function with given fields: +// GetAttachedTmuxSession provides a mock function with no fields func (_m *MockLister) GetAttachedTmuxSession() (model.SeshSession, bool) { ret := _m.Called() @@ -299,7 +299,7 @@ func (_c *MockLister_GetAttachedTmuxSession_Call) RunAndReturn(run func() (model return _c } -// GetLastTmuxSession provides a mock function with given fields: +// GetLastTmuxSession provides a mock function with no fields func (_m *MockLister) GetLastTmuxSession() (model.SeshSession, bool) { ret := _m.Called() diff --git a/lister/mock_srcStrategy.go b/lister/mock_srcStrategy.go index d47eea4..415e541 100644 --- a/lister/mock_srcStrategy.go +++ b/lister/mock_srcStrategy.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.3. DO NOT EDIT. +// Code generated by mockery v2.50.0. DO NOT EDIT. package lister diff --git a/ls/ls.go b/ls/ls.go new file mode 100644 index 0000000..06e2181 --- /dev/null +++ b/ls/ls.go @@ -0,0 +1,37 @@ +package ls + +import ( + "github.com/joshmedeski/sesh/model" + "github.com/joshmedeski/sesh/shell" +) + +type Ls interface { + ListDirectory(name string) (string, error) +} + +type RealLs struct { + config model.Config + shell shell.Shell +} + +func NewLs(config model.Config, shell shell.Shell) Ls { + return &RealLs{config, shell} +} + +func (g *RealLs) ListDirectory(path string) (string, error) { + command := g.config.DefaultSessionConfig.PreviewCommand + if command == "" { + command = "ls {}" + } + + cmdParts, err := g.shell.PrepareCmd(command, map[string]string{"{}": path}) + if err != nil { + return "", err + } + + cmdOutput, err := g.shell.Cmd(cmdParts[0], cmdParts[1:]...) + if err != nil { + return "", err + } + return cmdOutput, nil +} diff --git a/ls/mock_Ls.go b/ls/mock_Ls.go new file mode 100644 index 0000000..f0c1545 --- /dev/null +++ b/ls/mock_Ls.go @@ -0,0 +1,88 @@ +// Code generated by mockery v2.50.0. DO NOT EDIT. + +package ls + +import mock "github.com/stretchr/testify/mock" + +// MockLs is an autogenerated mock type for the Ls type +type MockLs struct { + mock.Mock +} + +type MockLs_Expecter struct { + mock *mock.Mock +} + +func (_m *MockLs) EXPECT() *MockLs_Expecter { + return &MockLs_Expecter{mock: &_m.Mock} +} + +// ListDirectory provides a mock function with given fields: name +func (_m *MockLs) ListDirectory(name string) (string, error) { + ret := _m.Called(name) + + if len(ret) == 0 { + panic("no return value specified for ListDirectory") + } + + var r0 string + var r1 error + if rf, ok := ret.Get(0).(func(string) (string, error)); ok { + return rf(name) + } + if rf, ok := ret.Get(0).(func(string) string); ok { + r0 = rf(name) + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(name) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockLs_ListDirectory_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListDirectory' +type MockLs_ListDirectory_Call struct { + *mock.Call +} + +// ListDirectory is a helper method to define mock.On call +// - name string +func (_e *MockLs_Expecter) ListDirectory(name interface{}) *MockLs_ListDirectory_Call { + return &MockLs_ListDirectory_Call{Call: _e.mock.On("ListDirectory", name)} +} + +func (_c *MockLs_ListDirectory_Call) Run(run func(name string)) *MockLs_ListDirectory_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *MockLs_ListDirectory_Call) Return(_a0 string, _a1 error) *MockLs_ListDirectory_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockLs_ListDirectory_Call) RunAndReturn(run func(string) (string, error)) *MockLs_ListDirectory_Call { + _c.Call.Return(run) + return _c +} + +// NewMockLs creates a new instance of MockLs. 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 NewMockLs(t interface { + mock.TestingT + Cleanup(func()) +}) *MockLs { + mock := &MockLs{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/model/config.go b/model/config.go index 47d2c3a..2459023 100644 --- a/model/config.go +++ b/model/config.go @@ -13,6 +13,7 @@ type ( StartupCommand string `toml:"startup_command"` Tmuxp string `toml:"tmuxp"` Tmuxinator string `toml:"tmuxinator"` + PreviewCommand string `toml:"preview_command"` } SessionConfig struct { diff --git a/model/sesh_session.go b/model/sesh_session.go index f560279..0943af3 100644 --- a/model/sesh_session.go +++ b/model/sesh_session.go @@ -16,6 +16,7 @@ type ( Path string // The absolute directory path StartupCommand string // The command to run when the session is started + PreviewCommand string // The command to run when the session is previewed DisableStartupCommand bool // Ignore the default startup command if present Tmuxinator string // Name of the tmuxinator config Attached int // Whether the session is currently attached diff --git a/namer/mock_Namer.go b/namer/mock_Namer.go index d02737e..8a459e0 100644 --- a/namer/mock_Namer.go +++ b/namer/mock_Namer.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.3. DO NOT EDIT. +// Code generated by mockery v2.50.0. DO NOT EDIT. package namer diff --git a/oswrap/mock_Os.go b/oswrap/mock_Os.go index 01d48e2..39af705 100644 --- a/oswrap/mock_Os.go +++ b/oswrap/mock_Os.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.3. DO NOT EDIT. +// Code generated by mockery v2.50.0. DO NOT EDIT. package oswrap @@ -183,7 +183,7 @@ func (_c *MockOs_Stat_Call) RunAndReturn(run func(string) (fs.FileInfo, error)) return _c } -// UserConfigDir provides a mock function with given fields: +// UserConfigDir provides a mock function with no fields func (_m *MockOs) UserConfigDir() (string, error) { ret := _m.Called() @@ -238,7 +238,7 @@ func (_c *MockOs_UserConfigDir_Call) RunAndReturn(run func() (string, error)) *M return _c } -// UserHomeDir provides a mock function with given fields: +// UserHomeDir provides a mock function with no fields func (_m *MockOs) UserHomeDir() (string, error) { ret := _m.Called() diff --git a/pathwrap/mock_Path.go b/pathwrap/mock_Path.go index e201d73..d01ea7c 100644 --- a/pathwrap/mock_Path.go +++ b/pathwrap/mock_Path.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.3. DO NOT EDIT. +// Code generated by mockery v2.50.0. DO NOT EDIT. package pathwrap diff --git a/previewer/config.go b/previewer/config.go new file mode 100644 index 0000000..eb9d1d6 --- /dev/null +++ b/previewer/config.go @@ -0,0 +1,41 @@ +package previewer + +import ( + "github.com/joshmedeski/sesh/lister" + "github.com/joshmedeski/sesh/shell" +) + +type ConfigPreviewStrategy struct { + lister lister.Lister + shell shell.Shell +} + +func NewConfigStrategy(lister lister.Lister, shell shell.Shell) *ConfigPreviewStrategy { + return &ConfigPreviewStrategy{lister: lister, shell: shell} +} + +func (s *ConfigPreviewStrategy) Execute(name string) (string, error) { + session, configExists := s.lister.FindConfigSession(name) + if !configExists { + return "", nil + } + + if session.PreviewCommand == "" { + return "", nil + } + + replacements := map[string]string{ + "{}": session.Path, + } + cmdParts, err := s.shell.PrepareCmd(session.PreviewCommand, replacements) + if err != nil { + return "", err + } + + cmdOutput, err := s.shell.Cmd(cmdParts[0], cmdParts[1:]...) + if err != nil { + return "", err + } + + return cmdOutput, nil +} diff --git a/previewer/default_config.go b/previewer/default_config.go new file mode 100644 index 0000000..9e71758 --- /dev/null +++ b/previewer/default_config.go @@ -0,0 +1,30 @@ +package previewer + +import ( + "github.com/joshmedeski/sesh/lister" + "github.com/joshmedeski/sesh/ls" + "github.com/joshmedeski/sesh/model" +) + +type DefaultConfigPreviewStrategy struct { + lister lister.Lister + config model.Config + ls ls.Ls +} + +func NewDefaultConfigStrategy(lister lister.Lister, config model.Config, ls ls.Ls) *DefaultConfigPreviewStrategy { + return &DefaultConfigPreviewStrategy{lister: lister, config: config, ls: ls} +} + +func (s *DefaultConfigPreviewStrategy) Execute(name string) (string, error) { + session, configExists := s.lister.FindConfigSession(name) + if !configExists { + return "", nil + } + + out, err := s.ls.ListDirectory(session.Path) + if err != nil { + return "", err + } + return out, nil +} diff --git a/previewer/directory.go b/previewer/directory.go new file mode 100644 index 0000000..5371f61 --- /dev/null +++ b/previewer/directory.go @@ -0,0 +1,33 @@ +package previewer + +import ( + "github.com/joshmedeski/sesh/dir" + "github.com/joshmedeski/sesh/home" + "github.com/joshmedeski/sesh/ls" +) + +type DirectoryPreviewStrategy struct { + home home.Home + dir dir.Dir + ls ls.Ls +} + +func NewDirectoryStrategy(home home.Home, dir dir.Dir, ls ls.Ls) *DirectoryPreviewStrategy { + return &DirectoryPreviewStrategy{home: home, dir: dir, ls: ls} +} + +func (s *DirectoryPreviewStrategy) Execute(name string) (string, error) { + path, _ := s.home.ExpandHome(name) + isDir, absPath := s.dir.Dir(path) + + if isDir { + output, err := s.ls.ListDirectory(absPath) + if err != nil { + return "", err + } + + return output, nil + } + + return "", nil +} diff --git a/previewer/mock_PreviewStrategy.go b/previewer/mock_PreviewStrategy.go new file mode 100644 index 0000000..f540a26 --- /dev/null +++ b/previewer/mock_PreviewStrategy.go @@ -0,0 +1,88 @@ +// Code generated by mockery v2.50.0. DO NOT EDIT. + +package previewer + +import mock "github.com/stretchr/testify/mock" + +// MockPreviewStrategy is an autogenerated mock type for the PreviewStrategy type +type MockPreviewStrategy struct { + mock.Mock +} + +type MockPreviewStrategy_Expecter struct { + mock *mock.Mock +} + +func (_m *MockPreviewStrategy) EXPECT() *MockPreviewStrategy_Expecter { + return &MockPreviewStrategy_Expecter{mock: &_m.Mock} +} + +// Execute provides a mock function with given fields: name +func (_m *MockPreviewStrategy) Execute(name string) (string, error) { + ret := _m.Called(name) + + if len(ret) == 0 { + panic("no return value specified for Execute") + } + + var r0 string + var r1 error + if rf, ok := ret.Get(0).(func(string) (string, error)); ok { + return rf(name) + } + if rf, ok := ret.Get(0).(func(string) string); ok { + r0 = rf(name) + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(name) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockPreviewStrategy_Execute_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Execute' +type MockPreviewStrategy_Execute_Call struct { + *mock.Call +} + +// Execute is a helper method to define mock.On call +// - name string +func (_e *MockPreviewStrategy_Expecter) Execute(name interface{}) *MockPreviewStrategy_Execute_Call { + return &MockPreviewStrategy_Execute_Call{Call: _e.mock.On("Execute", name)} +} + +func (_c *MockPreviewStrategy_Execute_Call) Run(run func(name string)) *MockPreviewStrategy_Execute_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *MockPreviewStrategy_Execute_Call) Return(_a0 string, _a1 error) *MockPreviewStrategy_Execute_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockPreviewStrategy_Execute_Call) RunAndReturn(run func(string) (string, error)) *MockPreviewStrategy_Execute_Call { + _c.Call.Return(run) + return _c +} + +// NewMockPreviewStrategy creates a new instance of MockPreviewStrategy. 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 NewMockPreviewStrategy(t interface { + mock.TestingT + Cleanup(func()) +}) *MockPreviewStrategy { + mock := &MockPreviewStrategy{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/previewer/mock_Previewer.go b/previewer/mock_Previewer.go new file mode 100644 index 0000000..e33e050 --- /dev/null +++ b/previewer/mock_Previewer.go @@ -0,0 +1,88 @@ +// Code generated by mockery v2.50.0. DO NOT EDIT. + +package previewer + +import mock "github.com/stretchr/testify/mock" + +// MockPreviewer is an autogenerated mock type for the Previewer type +type MockPreviewer struct { + mock.Mock +} + +type MockPreviewer_Expecter struct { + mock *mock.Mock +} + +func (_m *MockPreviewer) EXPECT() *MockPreviewer_Expecter { + return &MockPreviewer_Expecter{mock: &_m.Mock} +} + +// Preview provides a mock function with given fields: name +func (_m *MockPreviewer) Preview(name string) (string, error) { + ret := _m.Called(name) + + if len(ret) == 0 { + panic("no return value specified for Preview") + } + + var r0 string + var r1 error + if rf, ok := ret.Get(0).(func(string) (string, error)); ok { + return rf(name) + } + if rf, ok := ret.Get(0).(func(string) string); ok { + r0 = rf(name) + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(name) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockPreviewer_Preview_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Preview' +type MockPreviewer_Preview_Call struct { + *mock.Call +} + +// Preview is a helper method to define mock.On call +// - name string +func (_e *MockPreviewer_Expecter) Preview(name interface{}) *MockPreviewer_Preview_Call { + return &MockPreviewer_Preview_Call{Call: _e.mock.On("Preview", name)} +} + +func (_c *MockPreviewer_Preview_Call) Run(run func(name string)) *MockPreviewer_Preview_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *MockPreviewer_Preview_Call) Return(_a0 string, _a1 error) *MockPreviewer_Preview_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockPreviewer_Preview_Call) RunAndReturn(run func(string) (string, error)) *MockPreviewer_Preview_Call { + _c.Call.Return(run) + return _c +} + +// NewMockPreviewer creates a new instance of MockPreviewer. 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 NewMockPreviewer(t interface { + mock.TestingT + Cleanup(func()) +}) *MockPreviewer { + mock := &MockPreviewer{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/previewer/previewer.go b/previewer/previewer.go new file mode 100644 index 0000000..06d0b01 --- /dev/null +++ b/previewer/previewer.go @@ -0,0 +1,60 @@ +package previewer + +import ( + "github.com/joshmedeski/sesh/dir" + "github.com/joshmedeski/sesh/home" + "github.com/joshmedeski/sesh/icon" + "github.com/joshmedeski/sesh/lister" + "github.com/joshmedeski/sesh/ls" + "github.com/joshmedeski/sesh/model" + "github.com/joshmedeski/sesh/shell" + "github.com/joshmedeski/sesh/tmux" +) + +type Previewer interface { + // Previews a session or directory + Preview(name string) (string, error) +} + +type RealPreviewer struct { + icon icon.Icon + strategies []PreviewStrategy +} + +func NewPreviewer( + lister lister.Lister, + tmux tmux.Tmux, + icon icon.Icon, + dir dir.Dir, + home home.Home, + ls ls.Ls, + config model.Config, + shell shell.Shell, +) Previewer { + strategies := []PreviewStrategy{ + NewTmuxStrategy(lister, tmux), + NewConfigStrategy(lister, shell), + NewDefaultConfigStrategy(lister, config, ls), + NewDirectoryStrategy(home, dir, ls), + } + + return &RealPreviewer{ + icon: icon, + strategies: strategies, + } +} + +func (p *RealPreviewer) Preview(name string) (string, error) { + trimmedName := p.icon.RemoveIcon(name) + + for _, strategy := range p.strategies { + output, err := strategy.Execute(trimmedName) + if err != nil { + return "", err + } + if output != "" { + return output, nil + } + } + return "", nil +} diff --git a/previewer/previewer_test.go b/previewer/previewer_test.go new file mode 100644 index 0000000..6cb4d6f --- /dev/null +++ b/previewer/previewer_test.go @@ -0,0 +1,235 @@ +package previewer + +import ( + "testing" + + "github.com/joshmedeski/sesh/dir" + "github.com/joshmedeski/sesh/home" + "github.com/joshmedeski/sesh/icon" + "github.com/joshmedeski/sesh/lister" + "github.com/joshmedeski/sesh/ls" + "github.com/joshmedeski/sesh/model" + "github.com/joshmedeski/sesh/shell" + "github.com/joshmedeski/sesh/tmux" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" +) + +const ( + testHomePath = "/home/test" + testDownloadPath = testHomePath + "/Downloads" + testCodePath = testHomePath + "/Code/JSXQL" +) + +type PreviewerTestSuite struct { + suite.Suite + mockLister *lister.MockLister + mockTmux *tmux.MockTmux + mockIcon *icon.MockIcon + mockDir *dir.MockDir + mockHome *home.MockHome + mockLs *ls.MockLs + mockShell *shell.MockShell + mockConnfig *model.Config + previewer Previewer +} + +func (suite *PreviewerTestSuite) SetupTest() { + suite.initializeMocks() + suite.initializePreviewer() +} + +func (suite *PreviewerTestSuite) TearDownTest() { + suite.mockLister.AssertExpectations(suite.T()) + suite.mockTmux.AssertExpectations(suite.T()) + suite.mockIcon.AssertExpectations(suite.T()) + suite.mockDir.AssertExpectations(suite.T()) + suite.mockHome.AssertExpectations(suite.T()) + suite.mockLs.AssertExpectations(suite.T()) + suite.mockShell.AssertExpectations(suite.T()) +} + +func (suite *PreviewerTestSuite) initializeMocks() { + suite.mockLister = new(lister.MockLister) + suite.mockTmux = new(tmux.MockTmux) + suite.mockIcon = new(icon.MockIcon) + suite.mockDir = new(dir.MockDir) + suite.mockHome = new(home.MockHome) + suite.mockLs = new(ls.MockLs) + suite.mockShell = new(shell.MockShell) +} + +func (suite *PreviewerTestSuite) initializePreviewer() { + suite.previewer = NewPreviewer( + suite.mockLister, + suite.mockTmux, + suite.mockIcon, + suite.mockDir, + suite.mockHome, + suite.mockLs, + model.Config{}, + suite.mockShell, + ) +} + +func TestPreviewerTestSuite(t *testing.T) { + suite.Run(t, new(PreviewerTestSuite)) +} + +func (suite *PreviewerTestSuite) TestPreview_TmuxStrategy() { + testCase := struct { + inputName string + trimmedName string + expectedOutput string + }{ + inputName: " test-session", + trimmedName: "test-session", + expectedOutput: "Fake tmux ansi output", + } + + suite.setupTmuxMocks(testCase.inputName, testCase.trimmedName, testCase.expectedOutput) + + output, err := suite.previewer.Preview(testCase.inputName) + + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), testCase.expectedOutput, output) +} + +func (suite *PreviewerTestSuite) TestPreview_DefaultConfigStrategy() { + testCase := struct { + inputName string + trimmedName string + expectedPath string + expectedOutput string + }{ + inputName: " Downloads 📥", + trimmedName: "Downloads 📥", + expectedPath: testDownloadPath, + expectedOutput: `.rw-r--r-- 761k test 8 apr 17:56 export.csv +.rw-r--r-- 93k test 17 feb 16:42 IMG-20240217-WA0002.jpg +.rw-r--r-- 63k test 8 apr 15:55 'La stella.epub' +drwxrwxr-x - test 16 dic 18:37 'Learning Go.pdf'`, + } + + suite.setupDefaultConfigMocks(testCase.inputName, testCase.trimmedName, testCase.expectedPath, testCase.expectedOutput) + + output, err := suite.previewer.Preview(testCase.inputName) + + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), testCase.expectedOutput, output) +} + +func (suite *PreviewerTestSuite) TestPreview_ConfigStrategy() { + testCase := struct { + inputName string + trimmedName string + previewCommand string + previewCommandParts []string + expectedPath string + expectedOutput string + }{ + inputName: "📁 JSXQL", + trimmedName: "JSXQL", + previewCommand: "ls -la", + previewCommandParts: []string{"ls", "-la"}, + expectedPath: testCodePath, + expectedOutput: `.rw-rw-r-- 299 test 15 dic 16:17 -- global.d.ts +.rw-rw-r-- 251 test 15 dic 16:17 -- index.tsx +.rw-rw-r-- 1,7Ki test 15 dic 16:17 -- jsxql.ts`, + } + + suite.setupConfigMocks(testCase.inputName, testCase.trimmedName, testCase.previewCommand, testCase.previewCommandParts, testCase.expectedPath, testCase.expectedOutput) + + output, err := suite.previewer.Preview(testCase.inputName) + + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), testCase.expectedOutput, output) +} + +func (suite *PreviewerTestSuite) TestPreview_DirectoryStrategy() { + testCase := struct { + inputName string + trimmedName string + expectedPath string + expectedOutput string + }{ + inputName: " ~/Code/JSXQL/", + trimmedName: "~/Code/JSXQL/", + expectedPath: testCodePath, + expectedOutput: `.rw-rw-r-- 299 test 15 dic 16:17 -- global.d.ts +.rw-rw-r-- 251 test 15 dic 16:17 -- index.tsx +.rw-rw-r-- 1,7Ki test 15 dic 16:17 -- jsxql.ts`, + } + + suite.setupDirectoryMocks(testCase.inputName, testCase.trimmedName, testCase.expectedPath, testCase.expectedOutput) + + output, err := suite.previewer.Preview(testCase.inputName) + + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), testCase.expectedOutput, output) +} + +func (suite *PreviewerTestSuite) TestPreview_NoMatch() { + testCase := struct { + inputName string + trimmedName string + }{ + inputName: "nonexistent", + trimmedName: "nonexistent", + } + + suite.setupNoMatchMocks(testCase.inputName, testCase.trimmedName) + + output, err := suite.previewer.Preview(testCase.inputName) + + assert.NoError(suite.T(), err) + assert.Empty(suite.T(), output) +} + +func (suite *PreviewerTestSuite) setupTmuxMocks(inputName, trimmedName, expectedOutput string) { + suite.mockIcon.On("RemoveIcon", inputName).Return(trimmedName) + suite.mockLister.On("FindTmuxSession", trimmedName).Return(model.SeshSession{ + Name: trimmedName, + Path: testHomePath + "/c/" + trimmedName, + }, true) + suite.mockTmux.On("CapturePane", trimmedName).Return(expectedOutput, nil) +} + +func (suite *PreviewerTestSuite) setupDefaultConfigMocks(inputName, trimmedName, expectedPath, expectedOutput string) { + suite.mockIcon.On("RemoveIcon", inputName).Return(trimmedName) + suite.mockLister.On("FindTmuxSession", trimmedName).Return(model.SeshSession{}, false) + suite.mockLister.On("FindConfigSession", trimmedName).Return(model.SeshSession{ + Name: trimmedName, + Path: expectedPath, + }, true) + suite.mockLs.On("ListDirectory", expectedPath).Return(expectedOutput, nil) +} + +func (suite *PreviewerTestSuite) setupConfigMocks(inputName string, trimmedName string, previewCommand string, previewCommandParts []string, expectedPath string, expectedOutput string) { + suite.mockIcon.On("RemoveIcon", inputName).Return(trimmedName) + suite.mockLister.On("FindTmuxSession", trimmedName).Return(model.SeshSession{}, false) + suite.mockLister.On("FindConfigSession", trimmedName).Return(model.SeshSession{ + Name: trimmedName, + Path: expectedPath, + PreviewCommand: previewCommand, + }, true) + suite.mockShell.On("PrepareCmd", previewCommand, map[string]string{"{}": expectedPath}).Return(previewCommandParts, nil) + suite.mockShell.On("Cmd", "ls", "-la").Return(expectedOutput, nil) +} + +func (suite *PreviewerTestSuite) setupDirectoryMocks(inputName, trimmedName, expectedPath, expectedOutput string) { + suite.mockIcon.On("RemoveIcon", inputName).Return(trimmedName) + suite.mockLister.On("FindTmuxSession", trimmedName).Return(model.SeshSession{}, false) + suite.mockLister.On("FindConfigSession", trimmedName).Return(model.SeshSession{}, false) + suite.mockHome.On("ExpandHome", trimmedName).Return(expectedPath, nil) + suite.mockDir.On("Dir", expectedPath).Return(true, expectedPath) + suite.mockLs.On("ListDirectory", expectedPath).Return(expectedOutput, nil) +} + +func (suite *PreviewerTestSuite) setupNoMatchMocks(inputName, trimmedName string) { + suite.mockIcon.On("RemoveIcon", inputName).Return(trimmedName) + suite.mockLister.On("FindTmuxSession", trimmedName).Return(model.SeshSession{}, false) + suite.mockLister.On("FindConfigSession", trimmedName).Return(model.SeshSession{}, false) + suite.mockHome.On("ExpandHome", trimmedName).Return("", nil) + suite.mockDir.On("Dir", "").Return(false, "") +} diff --git a/previewer/strategy.go b/previewer/strategy.go new file mode 100644 index 0000000..c4bec8a --- /dev/null +++ b/previewer/strategy.go @@ -0,0 +1,5 @@ +package previewer + +type PreviewStrategy interface { + Execute(name string) (string, error) +} diff --git a/previewer/tmux.go b/previewer/tmux.go new file mode 100644 index 0000000..f939d9f --- /dev/null +++ b/previewer/tmux.go @@ -0,0 +1,30 @@ +package previewer + +import ( + "github.com/joshmedeski/sesh/lister" + "github.com/joshmedeski/sesh/tmux" +) + +type TmuxPreviewStrategy struct { + lister lister.Lister + tmux tmux.Tmux +} + +func NewTmuxStrategy(lister lister.Lister, tmux tmux.Tmux) *TmuxPreviewStrategy { + return &TmuxPreviewStrategy{lister: lister, tmux: tmux} +} + +func (s *TmuxPreviewStrategy) Execute(name string) (string, error) { + session, sessionExists := s.lister.FindTmuxSession(name) + + if sessionExists { + output, err := s.tmux.CapturePane(session.Name) + if err != nil { + return "", err + } + + return output, nil + } + + return "", nil +} diff --git a/runtimewrap/mock_Runtime.go b/runtimewrap/mock_Runtime.go index deb2256..0fdfc12 100644 --- a/runtimewrap/mock_Runtime.go +++ b/runtimewrap/mock_Runtime.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.3. DO NOT EDIT. +// Code generated by mockery v2.50.0. DO NOT EDIT. package runtimewrap @@ -17,7 +17,7 @@ func (_m *MockRuntime) EXPECT() *MockRuntime_Expecter { return &MockRuntime_Expecter{mock: &_m.Mock} } -// GOOS provides a mock function with given fields: +// GOOS provides a mock function with no fields func (_m *MockRuntime) GOOS() string { ret := _m.Called() diff --git a/seshcli/preview.go b/seshcli/preview.go new file mode 100644 index 0000000..cc3f955 --- /dev/null +++ b/seshcli/preview.go @@ -0,0 +1,34 @@ +package seshcli + +import ( + "errors" + "fmt" + + "github.com/joshmedeski/sesh/previewer" + cli "github.com/urfave/cli/v2" +) + +func Preview(p previewer.Previewer) *cli.Command { + return &cli.Command{ + Name: "preview", + Aliases: []string{"p"}, + Usage: "Preview a session or directory", + UseShortOptionHandling: true, + Action: func(cCtx *cli.Context) error { + if cCtx.NArg() != 1 { + return errors.New("session name or directory is required") + } + + name := cCtx.Args().First() + + output, err := p.Preview(name) + if err != nil { + return cli.Exit(err, 1) + } + + fmt.Print(output) + + return nil + }, + } +} diff --git a/seshcli/seshcli.go b/seshcli/seshcli.go index 6ad3f1e..bbbffcd 100644 --- a/seshcli/seshcli.go +++ b/seshcli/seshcli.go @@ -15,9 +15,11 @@ import ( "github.com/joshmedeski/sesh/icon" "github.com/joshmedeski/sesh/json" "github.com/joshmedeski/sesh/lister" + "github.com/joshmedeski/sesh/ls" "github.com/joshmedeski/sesh/namer" "github.com/joshmedeski/sesh/oswrap" "github.com/joshmedeski/sesh/pathwrap" + "github.com/joshmedeski/sesh/previewer" "github.com/joshmedeski/sesh/runtimewrap" "github.com/joshmedeski/sesh/shell" "github.com/joshmedeski/sesh/startup" @@ -34,8 +36,8 @@ func App(version string) cli.App { runtime := runtimewrap.NewRunTime() // base dependencies - shell := shell.NewShell(exec) home := home.NewHome(os) + shell := shell.NewShell(exec, home) json := json.NewJson() // resource dependencies @@ -56,11 +58,13 @@ func App(version string) cli.App { slog.Debug("seshcli/seshcli.go: App", "version", version, "config", config) // core dependencies + ls := ls.NewLs(config, shell) lister := lister.NewLister(config, home, tmux, zoxide, tmuxinator) startup := startup.NewStartup(config, lister, tmux) namer := namer.NewNamer(path, git, home) connector := connector.NewConnector(config, dir, home, lister, namer, startup, tmux, zoxide, tmuxinator) icon := icon.NewIcon(config) + previewer := previewer.NewPreviewer(lister, tmux, icon, dir, home, ls, config, shell) cloner := cloner.NewCloner(connector, git) return cli.App{ @@ -73,6 +77,7 @@ func App(version string) cli.App { Connect(connector, icon, dir), Clone(cloner), Root(lister, namer), + Preview(previewer), }, } } diff --git a/shell/mock_Shell.go b/shell/mock_Shell.go index 8b003ab..97ad3f8 100644 --- a/shell/mock_Shell.go +++ b/shell/mock_Shell.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.3. DO NOT EDIT. +// Code generated by mockery v2.50.0. DO NOT EDIT. package shell @@ -161,6 +161,65 @@ func (_c *MockShell_ListCmd_Call) RunAndReturn(run func(string, ...string) ([]st return _c } +// PrepareCmd provides a mock function with given fields: cmd, replacements +func (_m *MockShell) PrepareCmd(cmd string, replacements map[string]string) ([]string, error) { + ret := _m.Called(cmd, replacements) + + if len(ret) == 0 { + panic("no return value specified for PrepareCmd") + } + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func(string, map[string]string) ([]string, error)); ok { + return rf(cmd, replacements) + } + if rf, ok := ret.Get(0).(func(string, map[string]string) []string); ok { + r0 = rf(cmd, replacements) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func(string, map[string]string) error); ok { + r1 = rf(cmd, replacements) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockShell_PrepareCmd_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PrepareCmd' +type MockShell_PrepareCmd_Call struct { + *mock.Call +} + +// PrepareCmd is a helper method to define mock.On call +// - cmd string +// - replacements map[string]string +func (_e *MockShell_Expecter) PrepareCmd(cmd interface{}, replacements interface{}) *MockShell_PrepareCmd_Call { + return &MockShell_PrepareCmd_Call{Call: _e.mock.On("PrepareCmd", cmd, replacements)} +} + +func (_c *MockShell_PrepareCmd_Call) Run(run func(cmd string, replacements map[string]string)) *MockShell_PrepareCmd_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string), args[1].(map[string]string)) + }) + return _c +} + +func (_c *MockShell_PrepareCmd_Call) Return(_a0 []string, _a1 error) *MockShell_PrepareCmd_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockShell_PrepareCmd_Call) RunAndReturn(run func(string, map[string]string) ([]string, error)) *MockShell_PrepareCmd_Call { + _c.Call.Return(run) + return _c +} + // NewMockShell creates a new instance of MockShell. 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 NewMockShell(t interface { diff --git a/shell/shell.go b/shell/shell.go index 4d02ccf..1c9fcc9 100644 --- a/shell/shell.go +++ b/shell/shell.go @@ -7,19 +7,22 @@ import ( "strings" "github.com/joshmedeski/sesh/execwrap" + "github.com/joshmedeski/sesh/home" ) type Shell interface { Cmd(cmd string, arg ...string) (string, error) ListCmd(cmd string, arg ...string) ([]string, error) + PrepareCmd(cmd string, replacements map[string]string) ([]string, error) } type RealShell struct { exec execwrap.Exec + home home.Home } -func NewShell(exec execwrap.Exec) Shell { - return &RealShell{exec} +func NewShell(exec execwrap.Exec, home home.Home) Shell { + return &RealShell{exec, home} } func (c *RealShell) Cmd(cmd string, args ...string) (string, error) { @@ -52,3 +55,27 @@ func (c *RealShell) ListCmd(cmd string, arg ...string) ([]string, error) { output, err := command.Output() return strings.Split(string(output), "\n"), err } + +func (c *RealShell) PrepareCmd(cmd string, replacements map[string]string) ([]string, error) { + cmdParts := strings.Split(cmd, " ") + result := make([]string, len(cmdParts)) + + for i, arg := range cmdParts { + if strings.HasPrefix(arg, "~") { + expanded, err := c.home.ExpandHome(arg) + if err != nil { + return nil, err + } + result[i] = expanded + continue + } + + if replacement, ok := replacements[arg]; ok { + result[i] = replacement + } else { + result[i] = arg + } + } + + return result, nil +} diff --git a/shell/shell_test.go b/shell/shell_test.go index 83a5a05..112d95a 100644 --- a/shell/shell_test.go +++ b/shell/shell_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/joshmedeski/sesh/execwrap" + "github.com/joshmedeski/sesh/home" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) @@ -44,3 +45,14 @@ drwxr-xr-x 8 joshmedeski staff 256 Apr 11 19:05 ../ assert.Equal(t, dirListingExpected, list) }) } + +func TestShellPrepareCmd(t *testing.T) { + t.Run("should succeed with correct replacements and expansions", func(t *testing.T) { + mockHome := new(home.MockHome) + shell := &RealShell{home: mockHome} + mockHome.On("ExpandHome", "~/.local/bin/rat").Return("/home/test/.local/bin/rat", nil) + cmdParts, err := shell.PrepareCmd("~/.local/bin/rat {}", map[string]string{"{}": "hello"}) + assert.Nil(t, err) + assert.Equal(t, []string{"/home/test/.local/bin/rat", "hello"}, cmdParts) + }) +} diff --git a/startup/mock_Startup.go b/startup/mock_Startup.go index a3550c4..00b2571 100644 --- a/startup/mock_Startup.go +++ b/startup/mock_Startup.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.3. DO NOT EDIT. +// Code generated by mockery v2.50.0. DO NOT EDIT. package startup diff --git a/tmux/mock_Tmux.go b/tmux/mock_Tmux.go index 98b58cb..548513d 100644 --- a/tmux/mock_Tmux.go +++ b/tmux/mock_Tmux.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.3. DO NOT EDIT. +// Code generated by mockery v2.50.0. DO NOT EDIT. package tmux @@ -76,7 +76,63 @@ func (_c *MockTmux_AttachSession_Call) RunAndReturn(run func(string) (string, er return _c } -// IsAttached provides a mock function with given fields: +// CapturePane provides a mock function with given fields: targetSession +func (_m *MockTmux) CapturePane(targetSession string) (string, error) { + ret := _m.Called(targetSession) + + if len(ret) == 0 { + panic("no return value specified for CapturePane") + } + + 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 +} + +// MockTmux_CapturePane_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CapturePane' +type MockTmux_CapturePane_Call struct { + *mock.Call +} + +// CapturePane is a helper method to define mock.On call +// - targetSession string +func (_e *MockTmux_Expecter) CapturePane(targetSession interface{}) *MockTmux_CapturePane_Call { + return &MockTmux_CapturePane_Call{Call: _e.mock.On("CapturePane", targetSession)} +} + +func (_c *MockTmux_CapturePane_Call) Run(run func(targetSession string)) *MockTmux_CapturePane_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *MockTmux_CapturePane_Call) Return(_a0 string, _a1 error) *MockTmux_CapturePane_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockTmux_CapturePane_Call) RunAndReturn(run func(string) (string, error)) *MockTmux_CapturePane_Call { + _c.Call.Return(run) + return _c +} + +// IsAttached provides a mock function with no fields func (_m *MockTmux) IsAttached() bool { ret := _m.Called() @@ -121,7 +177,7 @@ func (_c *MockTmux_IsAttached_Call) RunAndReturn(run func() bool) *MockTmux_IsAt return _c } -// ListSessions provides a mock function with given fields: +// ListSessions provides a mock function with no fields func (_m *MockTmux) ListSessions() ([]*model.TmuxSession, error) { ret := _m.Called() diff --git a/tmux/tmux.go b/tmux/tmux.go index 106e48d..c061522 100644 --- a/tmux/tmux.go +++ b/tmux/tmux.go @@ -13,6 +13,7 @@ type Tmux interface { AttachSession(targetSession string) (string, error) SendKeys(name string, command string) (string, error) SwitchClient(targetSession string) (string, error) + CapturePane(targetSession string) (string, error) SwitchOrAttach(name string, opts model.ConnectOpts) (string, error) } @@ -41,6 +42,10 @@ func (t *RealTmux) NewSession(sessionName string, startDir string) (string, erro return t.shell.Cmd("tmux", "new-session", "-d", "-s", sessionName, "-c", startDir) } +func (t *RealTmux) CapturePane(targetSession string) (string, error) { + return t.shell.Cmd("tmux", "capture-pane", "-e", "-p", "-t", targetSession) +} + func (t *RealTmux) IsAttached() bool { return len(t.os.Getenv("TMUX")) > 0 } diff --git a/tmuxinator/mock_Tmuxinator.go b/tmuxinator/mock_Tmuxinator.go index 8c0222a..c0f10a6 100644 --- a/tmuxinator/mock_Tmuxinator.go +++ b/tmuxinator/mock_Tmuxinator.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.3. DO NOT EDIT. +// Code generated by mockery v2.50.0. DO NOT EDIT. package tmuxinator @@ -20,7 +20,7 @@ func (_m *MockTmuxinator) EXPECT() *MockTmuxinator_Expecter { return &MockTmuxinator_Expecter{mock: &_m.Mock} } -// List provides a mock function with given fields: +// List provides a mock function with no fields func (_m *MockTmuxinator) List() ([]*model.TmuxinatorConfig, error) { ret := _m.Called() diff --git a/zoxide/mock_Zoxide.go b/zoxide/mock_Zoxide.go index 29166cf..a16f89c 100644 --- a/zoxide/mock_Zoxide.go +++ b/zoxide/mock_Zoxide.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.3. DO NOT EDIT. +// Code generated by mockery v2.50.0. DO NOT EDIT. package zoxide @@ -66,7 +66,7 @@ func (_c *MockZoxide_Add_Call) RunAndReturn(run func(string) error) *MockZoxide_ return _c } -// ListResults provides a mock function with given fields: +// ListResults provides a mock function with no fields func (_m *MockZoxide) ListResults() ([]*model.ZoxideResult, error) { ret := _m.Called()