From d43118ae164dc72a185fcec95874f7e7f0aeefec Mon Sep 17 00:00:00 2001 From: Lonny Wong Date: Sat, 13 Jan 2024 22:38:08 +0800 Subject: [PATCH] fix multi-select with keywords #73 --- tssh/prompt.go | 2 +- tssh/tmgr.go | 8 +----- tssh/tmgr_iterm2.go | 67 +++++++++++++++++++++++++++++++++------------ tssh/tmgr_tmux.go | 43 ++++++++++++++++++++++++++--- tssh/tmgr_wt.go | 25 +++++++++++++++-- 5 files changed, 114 insertions(+), 31 deletions(-) diff --git a/tssh/prompt.go b/tssh/prompt.go index eaca95c..50f8d6d 100644 --- a/tssh/prompt.go +++ b/tssh/prompt.go @@ -608,7 +608,7 @@ func chooseAlias(keywords string) (string, bool, error) { fmt.Fprintf(os.Stderr, "\033[0;32m%s %s\033[0m\r\n", promptSelectedIcon, h.Alias) } if len(selectedHosts) > 1 && termMgr != nil { - termMgr.openTerminals(prompt.openType, selectedHosts) + termMgr.openTerminals(keywords, prompt.openType, selectedHosts) } return selectedHosts[0].Alias, false, nil } diff --git a/tssh/tmgr.go b/tssh/tmgr.go index 7133808..abc5987 100644 --- a/tssh/tmgr.go +++ b/tssh/tmgr.go @@ -39,7 +39,7 @@ const ( ) type terminalManager interface { - openTerminals(openType int, hosts []*sshHost) + openTerminals(keywords string, openType int, hosts []*sshHost) } func getTerminalManager() terminalManager { @@ -79,12 +79,6 @@ func getPanesMatrix(hosts []*sshHost) [][]*paneHost { return matrix } -func appendArgs(alias string, args ...string) []string { - args = append(args, os.Args...) - args = append(args, alias) - return args -} - func setTerminalTitle(title string) { fmt.Fprintf(os.Stderr, "\033]0;%s\007", title) } diff --git a/tssh/tmgr_iterm2.go b/tssh/tmgr_iterm2.go index 1fc943b..6cceb86 100644 --- a/tssh/tmgr_iterm2.go +++ b/tssh/tmgr_iterm2.go @@ -37,13 +37,15 @@ import ( ) type iterm2Mgr struct { - app iterm2.App + app iterm2.App + keywords string } -func (m *iterm2Mgr) openTerminals(openType int, hosts []*sshHost) { +func (m *iterm2Mgr) openTerminals(keywords string, openType int, hosts []*sshHost) { if len(hosts) < 2 { return } + m.keywords = keywords switch openType { case openTermDefault: if len(hosts) > 36 { @@ -64,11 +66,30 @@ func (m *iterm2Mgr) setTitle(session iterm2.Session, alias string) { _ = session.Inject([]byte(fmt.Sprintf("\033]0;%s\007", alias))) } -func (m *iterm2Mgr) execCmd(session iterm2.Session, alias string) { - cmd := shellescape.QuoteCommand(append(os.Args, alias)) +func (m *iterm2Mgr) execCmd(session iterm2.Session, alias string) error { + var cmdArgs []string + keywordsMatched := false + for _, arg := range os.Args { + if m.keywords != "" && arg == m.keywords { + if keywordsMatched { + return fmt.Errorf("unable to handle duplicate keywords '%s'", m.keywords) + } + keywordsMatched = true + cmdArgs = append(cmdArgs, alias) + continue + } + cmdArgs = append(cmdArgs, arg) + } + if m.keywords == "" { + cmdArgs = append(cmdArgs, alias) + } else if !keywordsMatched { + return fmt.Errorf("unable to handle replace keywords '%s'", m.keywords) + } + cmd := shellescape.QuoteCommand(cmdArgs) if err := session.SendText(fmt.Sprintf("%s\n", cmd)); err != nil { - warning("Failed to send text: %v", err) + return fmt.Errorf("failed to send text: %v", err) } + return nil } func (m *iterm2Mgr) getCurrentWindowSession() (iterm2.Window, iterm2.Session) { @@ -109,20 +130,23 @@ func (m *iterm2Mgr) openWindows(hosts []*sshHost) { window, err := m.app.CreateWindow() if err != nil { warning("Failed to create window: %v", err) - continue + return } tabs, err := window.ListTabs() if err != nil || len(tabs) == 0 { warning("Failed to list tabs: %v", err) - continue + return } sessions, err := tabs[0].ListSessions() if err != nil || len(sessions) == 0 { warning("Failed to list sessions: %v", err) - continue + return } m.setTitle(sessions[0], host.Alias) - m.execCmd(sessions[0], host.Alias) + if err := m.execCmd(sessions[0], host.Alias); err != nil { + warning("Failed to execute command: %v", err) + return + } } } @@ -138,15 +162,18 @@ func (m *iterm2Mgr) openTabs(hosts []*sshHost) { tab, err := window.CreateTab() if err != nil { warning("Failed to create tab: %v", err) - continue + return } sessions, err := tab.ListSessions() if err != nil || len(sessions) == 0 { warning("Failed to list sessions: %v", err) - continue + return } m.setTitle(sessions[0], host.Alias) - m.execCmd(sessions[0], host.Alias) + if err := m.execCmd(sessions[0], host.Alias); err != nil { + warning("Failed to execute command: %v", err) + return + } } } @@ -163,11 +190,14 @@ func (m *iterm2Mgr) openPanes(hosts []*sshHost) { pane, err := session.SplitPane(iterm2.SplitPaneOptions{Vertical: false}) if err != nil { warning("Failed to split pane: %v", err) - continue + return } sessions[i] = pane m.setTitle(pane, matrix[i][0].alias) - m.execCmd(pane, matrix[i][0].alias) + if err := m.execCmd(pane, matrix[i][0].alias); err != nil { + warning("Failed to execute command: %v", err) + return + } } for i := 0; i < len(matrix); i++ { session := sessions[i] @@ -178,10 +208,13 @@ func (m *iterm2Mgr) openPanes(hosts []*sshHost) { pane, err := session.SplitPane(iterm2.SplitPaneOptions{Vertical: true}) if err != nil { warning("Failed to split pane: %v", err) - continue + return } m.setTitle(pane, matrix[i][j].alias) - m.execCmd(pane, matrix[i][j].alias) + if err := m.execCmd(pane, matrix[i][j].alias); err != nil { + warning("Failed to execute command: %v", err) + return + } } } } @@ -200,5 +233,5 @@ func getIterm2Manager() terminalManager { app.Close() }) debug("running in iTerm2") - return &iterm2Mgr{app} + return &iterm2Mgr{app: app} } diff --git a/tssh/tmgr_tmux.go b/tssh/tmgr_tmux.go index 2a79c83..8d43be1 100644 --- a/tssh/tmgr_tmux.go +++ b/tssh/tmgr_tmux.go @@ -28,6 +28,7 @@ SOFTWARE. package tssh import ( + "fmt" "os" "os/exec" "strconv" @@ -35,12 +36,14 @@ import ( ) type tmuxMgr struct { + keywords string } -func (m *tmuxMgr) openTerminals(openType int, hosts []*sshHost) { +func (m *tmuxMgr) openTerminals(keywords string, openType int, hosts []*sshHost) { if len(hosts) < 2 { return } + m.keywords = keywords switch openType { case openTermDefault: if len(hosts) > 36 { @@ -55,6 +58,28 @@ func (m *tmuxMgr) openTerminals(openType int, hosts []*sshHost) { } } +func (m *tmuxMgr) appendArgs(alias string, args ...string) ([]string, error) { + cmdArgs := args + keywordsMatched := false + for _, arg := range os.Args { + if m.keywords != "" && arg == m.keywords { + if keywordsMatched { + return nil, fmt.Errorf("unable to handle duplicate keywords '%s'", m.keywords) + } + keywordsMatched = true + cmdArgs = append(cmdArgs, alias) + continue + } + cmdArgs = append(cmdArgs, arg) + } + if m.keywords == "" { + cmdArgs = append(cmdArgs, alias) + } else if !keywordsMatched { + return nil, fmt.Errorf("unable to handle replace keywords '%s'", m.keywords) + } + return cmdArgs, nil +} + func (m *tmuxMgr) openWindows(hosts []*sshHost) { if err := exec.Command("tmux", "renamew", hosts[0].Alias).Run(); err != nil { warning("Failed to rename tmux window: %v", err) @@ -64,8 +89,14 @@ func (m *tmuxMgr) openWindows(hosts []*sshHost) { }) } for _, host := range hosts[1:] { - if err := exec.Command("tmux", appendArgs(host.Alias, "neww", "-n", host.Alias)...).Run(); err != nil { + args, err := m.appendArgs(host.Alias, "neww", "-n", host.Alias) + if err != nil { warning("Failed to open tmux window: %v", err) + return + } + if err := exec.Command("tmux", args...).Run(); err != nil { + warning("Failed to open tmux window: %v", err) + return } } } @@ -116,8 +147,12 @@ func (m *tmuxMgr) splitWindow(alias, axes, target, percentage string) string { if target == "" { return "" } - out, err := exec.Command("tmux", - appendArgs(alias, "splitw", axes, "-t", target, "-p", percentage, "-P", "-F", "#{pane_id}")...).Output() + args, err := m.appendArgs(alias, "splitw", axes, "-t", target, "-p", percentage, "-P", "-F", "#{pane_id}") + if err != nil { + warning("Failed to split tmux window: %v", err) + return "" + } + out, err := exec.Command("tmux", args...).Output() if err != nil { warning("Failed to split tmux window: %v", err) return "" diff --git a/tssh/tmgr_wt.go b/tssh/tmgr_wt.go index 53ec6b5..659e19a 100644 --- a/tssh/tmgr_wt.go +++ b/tssh/tmgr_wt.go @@ -38,12 +38,14 @@ import ( ) type wtMgr struct { + keywords string } -func (m *wtMgr) openTerminals(openType int, hosts []*sshHost) { +func (m *wtMgr) openTerminals(keywords string, openType int, hosts []*sshHost) { if len(hosts) < 2 { return } + m.keywords = keywords switch openType { case openTermDefault: if len(hosts) > 36 { @@ -64,13 +66,26 @@ func (m *wtMgr) execWt(alias string, args ...string) error { cmdArgs := []string{"/c", "wt"} cmdArgs = append(cmdArgs, args...) cmdArgs = append(cmdArgs, "--title", alias) + keywordsMatched := false for _, arg := range os.Args { if strings.Contains(arg, ";") { return fmt.Errorf("Windows Terminal does not support ';', use '|cat&&' instead.") } + if m.keywords != "" && arg == m.keywords { + if keywordsMatched { + return fmt.Errorf("unable to handle duplicate keywords '%s'", m.keywords) + } + keywordsMatched = true + cmdArgs = append(cmdArgs, alias) + continue + } cmdArgs = append(cmdArgs, arg) } - cmdArgs = append(cmdArgs, alias) + if m.keywords == "" { + cmdArgs = append(cmdArgs, alias) + } else if !keywordsMatched { + return fmt.Errorf("unable to handle replace keywords '%s'", m.keywords) + } return exec.Command("cmd", cmdArgs...).Run() } @@ -79,6 +94,7 @@ func (m *wtMgr) openWindows(hosts []*sshHost) { for _, host := range hosts[1:] { if err := m.execWt(host.Alias, "-w", "-1"); err != nil { warning("Failed to open wt window: %v", err) + return } } } @@ -88,6 +104,7 @@ func (m *wtMgr) openTabs(hosts []*sshHost) { for _, host := range hosts[1:] { if err := m.execWt(host.Alias, "-w", "0", "nt"); err != nil { warning("Failed to open wt tab: %v", err) + return } } } @@ -99,10 +116,12 @@ func (m *wtMgr) openPanes(hosts []*sshHost) { percentage := "." + strconv.Itoa(100/(i+1)) if err := m.execWt(matrix[i][0].alias, "-w", "0", "sp", "-H", "-s", percentage); err != nil { warning("Failed to split wt pane: %v", err) + return } time.Sleep(100 * time.Millisecond) // wait for new pane focus if err := exec.Command("cmd", "/c", "wt", "-w", "0", "mf", "up").Run(); err != nil { warning("Failed to move wt focus: %v", err) + return } time.Sleep(100 * time.Millisecond) // wait for new pane focus } @@ -110,6 +129,7 @@ func (m *wtMgr) openPanes(hosts []*sshHost) { if i > 0 { if err := exec.Command("cmd", "/c", "wt", "-w", "0", "mf", "down").Run(); err != nil { warning("Failed to move wt focus: %v", err) + return } time.Sleep(100 * time.Millisecond) // wait for new pane focus } @@ -117,6 +137,7 @@ func (m *wtMgr) openPanes(hosts []*sshHost) { percentage := "." + strconv.Itoa(100-100/(len(matrix[i])-j+1)) if err := m.execWt(matrix[i][j].alias, "-w", "0", "sp", "-V", "-s", percentage); err != nil { warning("Failed to split wt pane: %v", err) + return } time.Sleep(100 * time.Millisecond) // wait for new pane focus }