From 196d68f069f861391b94cd8179fc1bebce471313 Mon Sep 17 00:00:00 2001 From: Jeffrey Faer Date: Sun, 18 Feb 2024 21:59:43 -0700 Subject: [PATCH 1/2] fix(cleanup): Remove killed sessions from state before adjusting session names. --- tmux/state/state.go | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/tmux/state/state.go b/tmux/state/state.go index f08a7ca..b793cf7 100644 --- a/tmux/state/state.go +++ b/tmux/state/state.go @@ -17,7 +17,7 @@ type State struct { // tmux sessions in srv with their associated repositories. sessions map[SessionName]*tmux.Session // An index of unqualified repo names that exist in sessions. - unqualifiedRepos map[string]bool + unqualifiedRepos map[string]int // Representative examples of each api.Repository in sessions. repos map[RepoName]api.Repository } @@ -31,7 +31,7 @@ func New(srv *tmux.Server) (*State, error) { st := &State{ srv: srv, sessions: make(map[SessionName]*tmux.Session), - unqualifiedRepos: make(map[string]bool), + unqualifiedRepos: make(map[string]int), repos: make(map[RepoName]api.Repository), } // An index from directory to api.Repository. @@ -66,7 +66,7 @@ func New(srv *tmux.Server) (*State, error) { parsed := ParseSessionName(repo, name) st.sessions[parsed] = sesh - st.unqualifiedRepos[parsed.Repo] = true + st.unqualifiedRepos[parsed.Repo]++ st.repos[parsed.RepoName] = repo logger.Info("Found repository in tmux session.", "name", parsed) } @@ -74,7 +74,7 @@ func New(srv *tmux.Server) (*State, error) { } func (st *State) sessionNameString(n SessionName) string { - if len(st.unqualifiedRepos) > 1 || (len(st.unqualifiedRepos) == 1 && !st.unqualifiedRepos[n.Repo]) { + if len(st.unqualifiedRepos) > 1 || (len(st.unqualifiedRepos) == 1 && st.unqualifiedRepos[n.Repo] == 0) { return n.RepoString() } return n.WorkUnitString() @@ -119,7 +119,7 @@ func (st *State) NewSession(repo api.Repository, workUnitName string) (*tmux.Ses } st.sessions[name] = sesh - st.unqualifiedRepos[name.Repo] = true + st.unqualifiedRepos[name.Repo]++ st.repos[name.RepoName] = repo if err := st.updateSessionNames(); err != nil { slog.Warn("Failed to update tmux session names.", "error", err) @@ -200,10 +200,17 @@ func (st *State) PruneSessions() error { } for _, sesh := range toRemove { - slog.Info("Killing session.", "session_id", sesh.ID, "name", invalidSessions[sesh]) + n := invalidSessions[sesh] + slog.Info("Killing session.", "session_id", sesh.ID, "name", n) if err := sesh.Kill(); err != nil { return err } + delete(st.sessions, n) + st.unqualifiedRepos[n.Repo]-- + if st.unqualifiedRepos[n.Repo] == 0 { + delete(st.unqualifiedRepos, n.Repo) + delete(st.repos, n.RepoName) + } } if err := st.updateSessionNames(); err != nil { From c7b44a2fe604bb43b92c11b6202ade9e43c8352e Mon Sep 17 00:00:00 2001 From: Jeffrey Faer Date: Sun, 18 Feb 2024 22:06:21 -0700 Subject: [PATCH 2/2] feat(tmux): Automatically adjust status-left-length when renaming sessions. --- tmux/properties.go | 6 +----- tmux/server.go | 7 +++++++ tmux/session.go | 17 ++++++++++++++++- tmux/state/state.go | 24 +++++++++++++++++++++++- tmux/tmux.go | 11 +++++++++++ 5 files changed, 58 insertions(+), 7 deletions(-) diff --git a/tmux/properties.go b/tmux/properties.go index 0aed2ea..9ba35fe 100644 --- a/tmux/properties.go +++ b/tmux/properties.go @@ -1,9 +1,5 @@ package tmux -import ( - "fmt" -) - // properties is a helper function to make implementing property lookups for // different tmux entities a little easier. // keys are all the properties being fetched. @@ -17,7 +13,7 @@ func properties[T ~string](keys []T, fn func([]string) ([]string, error)) (map[T props, err := fn(keyStrings) if err != nil { - return nil, fmt.Errorf("failed to fetch properties %s: %w", keyStrings, err) + return nil, err } res := make(map[T]string, len(keys)) for i, prop := range props { diff --git a/tmux/server.go b/tmux/server.go index 03e234d..81759d2 100644 --- a/tmux/server.go +++ b/tmux/server.go @@ -275,3 +275,10 @@ func (srv *Server) resolveTargetSession(s TargetSession) (string, error) { func (srv *Server) Kill() error { return srv.command("kill-server").Run() } + +func (srv *Server) SetOption(opt Option, val string) error { + if err := srv.command("set-option", "-s", string(opt), val).Run(); err != nil { + return fmt.Errorf("set-option -s %q %q: %w", opt, val, err) + } + return nil +} diff --git a/tmux/session.go b/tmux/session.go index 2d6d780..022f92e 100644 --- a/tmux/session.go +++ b/tmux/session.go @@ -68,7 +68,7 @@ func (s *Session) Property(prop SessionProperty) (string, error) { // Properties fetches properties about a session. func (s *Session) Properties(props ...SessionProperty) (map[SessionProperty]string, error) { res, err := properties(props, func(keys []string) ([]string, error) { - stdout, err := s.Server.command("display-message", "-t", s.ID, "-p", strings.Join(keys, "\n")).RunStdout() + stdout, err := s.DisplayMessage(strings.Join(keys, "\n")) if err != nil { return nil, err } @@ -104,3 +104,18 @@ func (s *Session) Kill() error { } return nil } + +func (s *Session) SetOption(opt Option, val string) error { + if err := s.Server.command("set-option", "-t", s.ID, string(opt), val).Run(); err != nil { + return fmt.Errorf("set-option -t %s %q %q: %w", s.ID, opt, val, err) + } + return nil +} + +func (s *Session) DisplayMessage(msg string) (string, error) { + stdout, err := s.Server.command("display-message", "-t", s.ID, "-p", msg).RunStdout() + if err != nil { + return "", fmt.Errorf("display-message -t %s %q: %w", s.ID, msg, err) + } + return stdout, nil +} diff --git a/tmux/state/state.go b/tmux/state/state.go index b793cf7..699ac46 100644 --- a/tmux/state/state.go +++ b/tmux/state/state.go @@ -6,6 +6,7 @@ import ( "log/slog" "maps" "slices" + "strconv" "strings" "github.com/JeffFaer/tmux-vcs-sync/api" @@ -222,17 +223,38 @@ func (st *State) PruneSessions() error { func (st *State) updateSessionNames() error { var errs []error for k, sesh := range st.sessions { - name, err := sesh.Property(tmux.SessionName) + props, err := sesh.Properties(tmux.SessionName, tmux.StatusLeft.SessionProperty(), tmux.StatusLeftLength.SessionProperty()) if err != nil { errs = append(errs, err) continue } + name := props[tmux.SessionName] + statusLeft := props[tmux.StatusLeft.SessionProperty()] + statusLeftLength := props[tmux.StatusLeftLength.SessionProperty()] if want := st.sessionNameString(k); name != want { if err := sesh.Rename(want); err != nil { errs = append(errs, err) continue } } + + msg, err := sesh.DisplayMessage(statusLeft) + if err != nil { + errs = append(errs, err) + continue + } + wantLength := len(msg) + got, err := strconv.Atoi(statusLeftLength) + if err != nil { + errs = append(errs, fmt.Errorf("tmux session %s has invalid %s value %q: %w", sesh.ID, tmux.StatusLeftLength, statusLeftLength, err)) + continue + } + if wantLength > got { + if err := sesh.SetOption(tmux.StatusLeftLength, strconv.Itoa(wantLength)); err != nil { + errs = append(errs, err) + continue + } + } } return errors.Join(errs...) } diff --git a/tmux/tmux.go b/tmux/tmux.go index 5d6d631..a4b9b51 100644 --- a/tmux/tmux.go +++ b/tmux/tmux.go @@ -19,3 +19,14 @@ func init() { panic(err) } } + +type Option string + +const ( + StatusLeft Option = "status-left" + StatusLeftLength Option = "status-left-length" +) + +func (opt Option) SessionProperty() SessionProperty { + return SessionProperty(fmt.Sprintf("#{%s}", opt)) +}