diff --git a/README.md b/README.md index 3e45049..73bed94 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,26 @@ You can customize this however you want, see `man fzf` for more info on the diff sesh connect (sesh list | zf --height 24) ``` +## Configuration + +To configure `sesh`, you will need to create `sesh/sesh.toml` with `$XDG_CONFIG_HOME` or `$HOME/.config` as the root directory, depending on whether the former has been set. On MacOS, this will likely be `~/.config/` by default and therefore the configuration filepath would be `~/.config/sesh/sesh.toml`. See the [Go docs](https://cs.opensource.google/go/go/+/go1.21.6:src/os/file.go;l=460) for more information. + +An example of the `sesh.toml` file is shown below with all configurable options and their default values: + +```toml +default_startup_script = "~/git_repos/dotfiles/bin/sesh/default.sh" + +[[startup_scripts]] +session_path = "~/git_repos/sesh" +script_path = "~/git_repos/dotfiles/bin/sesh/sesh.sh" +``` + +### Startup Scripts + +Startup scripts will be ignored if the `--command/-c` flag is passed! Startup scripts will only be run upon session creation! + +If a `[[startup_scripts]]` entry is present for a given session path, then the startup script path specified by `script_path` will be run when the session is created. Otherwise, if `default_startup_script` is specified, then the script at that path will be executed. Make sure that the scripts specified are executable, e.g. `chmod +x ~/git_repos/dotfiles/bin/sesh/default.sh`. + ## Background (the "t" script) Sesh is the successor to my popular [t-smart-tmux-session-manager](https://github.com/joshmedeski/t-smart-tmux-session-manager) tmux plugin. After a year of development and over 250 stars, it's clear that people enjoy the idea of a smart session manager. However, I've always felt that the tmux plugin was a bit of a hack. It's a bash script that runs in the background and parses the output of tmux commands. It works, but it's not ideal and isn't flexible enough to support other terminal multiplexers. diff --git a/cmds/choose.go b/cmds/choose.go index eebe1c6..f35304c 100644 --- a/cmds/choose.go +++ b/cmds/choose.go @@ -9,6 +9,7 @@ import ( cli "github.com/urfave/cli/v2" + "github.com/joshmedeski/sesh/config" "github.com/joshmedeski/sesh/connect" "github.com/joshmedeski/sesh/session" ) @@ -65,7 +66,8 @@ func Choose() *cli.Command { } choice := strings.TrimSpace(cmdOutput.String()) // TODO: get choice from Session structs array - return connect.Connect(choice, false, "") + config := config.ParseConfigFile() + return connect.Connect(choice, false, "", &config) }, } } diff --git a/cmds/clone.go b/cmds/clone.go index bd07521..6952485 100644 --- a/cmds/clone.go +++ b/cmds/clone.go @@ -3,6 +3,7 @@ package cmds import ( cli "github.com/urfave/cli/v2" + "github.com/joshmedeski/sesh/config" "github.com/joshmedeski/sesh/connect" "github.com/joshmedeski/sesh/git" ) @@ -32,8 +33,8 @@ func Clone() *cli.Command { if err != nil { return cli.Exit(err, 1) } - - return connect.Connect(c.Path, false, "") + config := config.ParseConfigFile() + return connect.Connect(c.Path, false, "", &config) }, } } diff --git a/cmds/connect.go b/cmds/connect.go index 2fdcfb7..04bc47f 100644 --- a/cmds/connect.go +++ b/cmds/connect.go @@ -3,6 +3,7 @@ package cmds import ( cli "github.com/urfave/cli/v2" + "github.com/joshmedeski/sesh/config" "github.com/joshmedeski/sesh/connect" ) @@ -31,7 +32,8 @@ func Connect() *cli.Command { if session == "" { return cli.Exit("No session provided", 0) } - return connect.Connect(session, alwaysSwitch, command) + config := config.ParseConfigFile() + return connect.Connect(session, alwaysSwitch, command, &config) }, } } diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..a25b65a --- /dev/null +++ b/config/config.go @@ -0,0 +1,62 @@ +package config + +import ( + "fmt" + "os" + "path" + "path/filepath" + "runtime" + + "github.com/pelletier/go-toml/v2" +) + +type ( + Script struct { + SessionPath string `toml:"session_path"` + ScriptPath string `toml:"script_path"` + } + Config struct { + StartupScripts []Script `toml:"startup_scripts"` + DefaultStartupScript string `toml:"default_startup_script"` + } +) + +func getUserConfigDir() (string, error) { + switch runtime.GOOS { + case "darwin": + // typically ~/Library/Application Support, but we want to use ~/.config + homeDir, err := os.UserHomeDir() + if err != nil { + return "", err + } + return path.Join(homeDir, ".config"), nil + default: + return os.UserConfigDir() + } +} + +func ParseConfigFile() Config { + config := Config{} + configDir, err := getUserConfigDir() + if err != nil { + fmt.Printf( + "Error determining the user config directory: %s\nUsing default config instead", + err, + ) + return config + } + configPath := filepath.Join(configDir, "sesh", "sesh.toml") + data, err := os.ReadFile(configPath) + if err != nil { + return config + } + err = toml.Unmarshal(data, &config) + if err != nil { + fmt.Printf( + "Error parsing config file: %s\nUsing default config instead", + err, + ) + return config + } + return config +} diff --git a/connect/connect.go b/connect/connect.go index 9626281..2a45bed 100644 --- a/connect/connect.go +++ b/connect/connect.go @@ -3,24 +3,22 @@ package connect import ( "fmt" + "github.com/joshmedeski/sesh/config" "github.com/joshmedeski/sesh/session" "github.com/joshmedeski/sesh/tmux" "github.com/joshmedeski/sesh/zoxide" ) -func Connect(choice string, alwaysSwitch bool, command string) error { - session, err := session.Determine(choice) +func Connect(choice string, alwaysSwitch bool, command string, config *config.Config) error { + session, err := session.Determine(choice, config) if err != nil { return fmt.Errorf("unable to connect to %q: %w", choice, err) } - - if err := zoxide.Add(session.Path); err != nil { + if err = zoxide.Add(session.Path); err != nil { return fmt.Errorf("unable to connect to %q: %w", choice, err) } - - return tmux.Connect( - tmux.TmuxSession{Name: session.Name, Path: session.Path}, - alwaysSwitch, - command, - ) + return tmux.Connect(tmux.TmuxSession{ + Name: session.Name, + Path: session.Path, + }, alwaysSwitch, command, session.Path, config) } diff --git a/go.mod b/go.mod index 7d23211..5cb11bf 100644 --- a/go.mod +++ b/go.mod @@ -2,15 +2,17 @@ module github.com/joshmedeski/sesh go 1.21 -require github.com/urfave/cli/v2 v2.27.1 +require ( + github.com/pelletier/go-toml/v2 v2.1.1 + github.com/stretchr/testify v1.8.4 + github.com/urfave/cli/v2 v2.27.1 +) require ( github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/stretchr/objx v0.5.0 // indirect - github.com/stretchr/testify v1.8.4 // indirect github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 50d2f95..6404d93 100644 --- a/go.sum +++ b/go.sum @@ -1,30 +1,26 @@ -github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= +github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/urfave/cli/v2 v2.27.0 h1:uNs1K8JwTFL84X68j5Fjny6hfANh9nTlJ6dRtZAFAHY= -github.com/urfave/cli/v2 v2.27.0/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho= github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= -github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= -github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e h1:+SOyEddqYF09QP7vr7CgJ1eti3pY9Fn3LHO1M1r/0sI= github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/session/determine.go b/session/determine.go index 0468af3..2b202f3 100644 --- a/session/determine.go +++ b/session/determine.go @@ -3,9 +3,11 @@ package session import ( "fmt" "log" + + "github.com/joshmedeski/sesh/config" ) -func Determine(choice string) (s Session, err error) { +func Determine(choice string, config *config.Config) (s Session, err error) { path, err := DeterminePath(choice) if err != nil { return s, fmt.Errorf( @@ -16,7 +18,7 @@ func Determine(choice string) (s Session, err error) { } s.Path = path - name := DetermineName(path) + name := DetermineName(path, config) if name == "" { log.Fatal("Couldn't determine the session name", err) return s, fmt.Errorf( diff --git a/session/name.go b/session/name.go index 8a34330..82988f1 100644 --- a/session/name.go +++ b/session/name.go @@ -5,6 +5,7 @@ import ( "path/filepath" "strings" + "github.com/joshmedeski/sesh/config" "github.com/joshmedeski/sesh/git" ) @@ -47,9 +48,9 @@ func nameFromGit(result string) string { return nameFromGit } -// TODO: parent directory feature flag detection -func DetermineName(result string) string { +func DetermineName(result string, config *config.Config) string { name := result + // TODO: parent directory config option detection pathName := nameFromPath(result) if pathName != "" { name = pathName diff --git a/tmux/tmux.go b/tmux/tmux.go index 6b03d40..b69176e 100644 --- a/tmux/tmux.go +++ b/tmux/tmux.go @@ -3,9 +3,13 @@ package tmux import ( "bytes" "fmt" + "log" "os" "os/exec" "strings" + + "github.com/joshmedeski/sesh/config" + "github.com/joshmedeski/sesh/dir" ) func tmuxCmd(args []string) (string, error) { @@ -82,7 +86,38 @@ func NewSession(s TmuxSession) (string, error) { return out, nil } -func Connect(s TmuxSession, alwaysSwitch bool, command string) error { +func execStartupScript(name string, scriptPath string) error { + bash, err := exec.LookPath("bash") + if err != nil { + return err + } + cmd := strings.Join( + []string{bash, "-c", fmt.Sprintf("\"source %s\"", scriptPath)}, + " ", + ) + err = runPersistentCommand(name, cmd) + if err != nil { + return err + } + return nil +} + +func getStartupScript(sessionPath string, config *config.Config) string { + for _, script := range config.StartupScripts { + if dir.FullPath(script.SessionPath) == sessionPath { + return dir.FullPath(script.ScriptPath) + } + } + return "" +} + +func Connect( + s TmuxSession, + alwaysSwitch bool, + command string, + sessionPath string, + config *config.Config, +) error { isSession, _ := IsSession(s.Name) if !isSession { _, err := NewSession(s) @@ -91,6 +126,16 @@ func Connect(s TmuxSession, alwaysSwitch bool, command string) error { } if command != "" { runPersistentCommand(s.Name, command) + } else if scriptPath := getStartupScript(sessionPath, config); scriptPath != "" { + err := execStartupScript(s.Name, scriptPath) + if err != nil { + log.Fatal(err) + } + } else if config.DefaultStartupScript != "" { + err := execStartupScript(s.Name, config.DefaultStartupScript) + if err != nil { + log.Fatal(err) + } } } isAttached := isAttached() @@ -99,6 +144,5 @@ func Connect(s TmuxSession, alwaysSwitch bool, command string) error { } else { attachSession(s.Name) } - return nil }