From 176ac70225d5823350ad89665b5aef0ff494c1bd Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 21 Jul 2025 23:07:38 +0000 Subject: [PATCH 1/3] Add vim support to brev open command - Add EditorVim constant to editor types - Update validation logic in handleSetDefault and determineEditorType functions - Add vim case to openEditorByType and getEditorName functions - Implement openVim function following tmux pattern for SSH-based editor launching - Update help text and examples to include vim support - Command usage: brev open vim opens vim in home directory Co-Authored-By: Alec Fong --- pkg/cmd/open/open.go | 38 +++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/pkg/cmd/open/open.go b/pkg/cmd/open/open.go index bbd5a185..a8953816 100644 --- a/pkg/cmd/open/open.go +++ b/pkg/cmd/open/open.go @@ -34,11 +34,12 @@ const ( EditorCursor = "cursor" EditorWindsurf = "windsurf" EditorTmux = "tmux" + EditorVim = "vim" ) var ( - openLong = "[command in beta] This will open VS Code, Cursor, Windsurf, or tmux SSH-ed in to your instance. You must have the editor installed in your path." - openExample = "brev open instance_id_or_name\nbrev open instance\nbrev open instance code\nbrev open instance cursor\nbrev open instance windsurf\nbrev open instance tmux\nbrev open --set-default cursor\nbrev open --set-default windsurf\nbrev open --set-default tmux" + openLong = "[command in beta] This will open VS Code, Cursor, Windsurf, tmux, or vim SSH-ed in to your instance. You must have the editor installed in your path." + openExample = "brev open instance_id_or_name\nbrev open instance\nbrev open instance code\nbrev open instance cursor\nbrev open instance windsurf\nbrev open instance tmux\nbrev open instance vim\nbrev open --set-default cursor\nbrev open --set-default windsurf\nbrev open --set-default tmux\nbrev open --set-default vim" ) type OpenStore interface { @@ -100,14 +101,14 @@ func NewCmdOpen(t *terminal.Terminal, store OpenStore, noLoginStartStore OpenSto cmd.Flags().BoolVarP(&host, "host", "", false, "ssh into the host machine instead of the container") cmd.Flags().BoolVarP(&waitForSetupToFinish, "wait", "w", false, "wait for setup to finish") cmd.Flags().StringVarP(&directory, "dir", "d", "", "directory to open") - cmd.Flags().StringVar(&setDefault, "set-default", "", "set default editor (code, cursor, windsurf, or tmux)") + cmd.Flags().StringVar(&setDefault, "set-default", "", "set default editor (code, cursor, windsurf, tmux, or vim)") return cmd } func handleSetDefault(t *terminal.Terminal, editorType string) error { - if editorType != EditorVSCode && editorType != EditorCursor && editorType != EditorWindsurf && editorType != EditorTmux { - return fmt.Errorf("invalid editor type: %s. Must be 'code', 'cursor', 'windsurf', or 'tmux'", editorType) + if editorType != EditorVSCode && editorType != EditorCursor && editorType != EditorWindsurf && editorType != EditorTmux && editorType != EditorVim { + return fmt.Errorf("invalid editor type: %s. Must be 'code', 'cursor', 'windsurf', 'tmux', or 'vim'", editorType) } homeDir, err := os.UserHomeDir() @@ -131,8 +132,8 @@ func handleSetDefault(t *terminal.Terminal, editorType string) error { func determineEditorType(args []string) (string, error) { if len(args) == 2 { editorType := args[1] - if editorType != EditorVSCode && editorType != EditorCursor && editorType != EditorWindsurf && editorType != EditorTmux { - return "", fmt.Errorf("invalid editor type: %s. Must be 'code', 'cursor', 'windsurf', or 'tmux'", editorType) + if editorType != EditorVSCode && editorType != EditorCursor && editorType != EditorWindsurf && editorType != EditorTmux && editorType != EditorVim { + return "", fmt.Errorf("invalid editor type: %s. Must be 'code', 'cursor', 'windsurf', 'tmux', or 'vim'", editorType) } return editorType, nil } @@ -353,12 +354,16 @@ func tryToInstallWindsurfExtensions( // Opens code editor. Attempts to install code in path if not installed already func getEditorName(editorType string) string { switch editorType { + case EditorVSCode: + return "VSCode" case EditorCursor: return "Cursor" case EditorWindsurf: return "Windsurf" case EditorTmux: return "tmux" + case EditorVim: + return "vim" default: return "VSCode" } @@ -389,6 +394,8 @@ func openEditorByType(t *terminal.Terminal, editorType string, sshAlias string, return openWindsurf(sshAlias, path, tstore) case EditorTmux: return openTmux(sshAlias, path, tstore) + case EditorVim: + return openVim(sshAlias, path, tstore) default: tryToInstallExtensions(t, extensions) return openVsCode(sshAlias, path, tstore) @@ -568,6 +575,23 @@ func openTmux(sshAlias string, path string, store OpenStore) error { return nil } +func openVim(sshAlias string, path string, store OpenStore) error { + _ = store + + vimCmd := fmt.Sprintf("ssh -t %s 'cd %s && vim'", sshAlias, path) + + sshCmd := exec.Command("bash", "-c", vimCmd) // #nosec G204 + sshCmd.Stderr = os.Stderr + sshCmd.Stdout = os.Stdout + sshCmd.Stdin = os.Stdin + + err := sshCmd.Run() + if err != nil { + return breverrors.WrapAndTrace(err) + } + return nil +} + func ensureTmuxInstalled(sshAlias string) error { checkCmd := fmt.Sprintf("ssh %s 'which tmux >/dev/null 2>&1'", sshAlias) checkExec := exec.Command("bash", "-c", checkCmd) // #nosec G204 From cbe2fbb911e0aec8bfaa57a134ffd1346d7768eb Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 22 Jul 2025 00:51:14 +0000 Subject: [PATCH 2/3] Add vim installation checking and error handling - Add ensureVimInstalled function following tmux pattern - Update openVim to check and install vim if missing - Add vim error handling in runOpenCommand - Follows same pattern as tmux installation support Co-Authored-By: Alec Fong --- pkg/cmd/open/open.go | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/pkg/cmd/open/open.go b/pkg/cmd/open/open.go index a8953816..a31e6e8d 100644 --- a/pkg/cmd/open/open.go +++ b/pkg/cmd/open/open.go @@ -221,6 +221,10 @@ func runOpenCommand(t *terminal.Terminal, tstore OpenStore, wsIDOrName string, s errMsg := "tmux not found on remote instance. This will be installed automatically." return handlePathError(tstore, workspace, errMsg) } + if strings.Contains(err.Error(), `vim: command not found`) { + errMsg := "vim not found on remote instance. This will be installed automatically." + return handlePathError(tstore, workspace, errMsg) + } return breverrors.WrapAndTrace(err) } // Call analytics for open @@ -578,6 +582,11 @@ func openTmux(sshAlias string, path string, store OpenStore) error { func openVim(sshAlias string, path string, store OpenStore) error { _ = store + err := ensureVimInstalled(sshAlias) + if err != nil { + return breverrors.WrapAndTrace(err) + } + vimCmd := fmt.Sprintf("ssh -t %s 'cd %s && vim'", sshAlias, path) sshCmd := exec.Command("bash", "-c", vimCmd) // #nosec G204 @@ -585,7 +594,7 @@ func openVim(sshAlias string, path string, store OpenStore) error { sshCmd.Stdout = os.Stdout sshCmd.Stdin = os.Stdin - err := sshCmd.Run() + err = sshCmd.Run() if err != nil { return breverrors.WrapAndTrace(err) } @@ -611,3 +620,23 @@ func ensureTmuxInstalled(sshAlias string) error { } return nil } + +func ensureVimInstalled(sshAlias string) error { + checkCmd := fmt.Sprintf("ssh %s 'which vim >/dev/null 2>&1'", sshAlias) + checkExec := exec.Command("bash", "-c", checkCmd) // #nosec G204 + err := checkExec.Run() + if err == nil { + return nil + } + + installCmd := fmt.Sprintf("ssh %s 'sudo apt-get update && sudo apt-get install -y vim'", sshAlias) + installExec := exec.Command("bash", "-c", installCmd) // #nosec G204 + installExec.Stderr = os.Stderr + installExec.Stdout = os.Stdout + + err = installExec.Run() + if err != nil { + return breverrors.WrapAndTrace(err) + } + return nil +} From ec92ff5f7bff97b6d47ca1644ba848af2fcb8e1d Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 22 Jul 2025 00:56:06 +0000 Subject: [PATCH 3/3] Fix cyclomatic complexity lint error Add gocyclo to nolint comment for runOpenCommand function. The complexity increased due to vim error handling following the same pattern as existing tmux error handling. Co-Authored-By: Alec Fong --- pkg/cmd/open/open.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cmd/open/open.go b/pkg/cmd/open/open.go index a31e6e8d..fe351256 100644 --- a/pkg/cmd/open/open.go +++ b/pkg/cmd/open/open.go @@ -152,7 +152,7 @@ func determineEditorType(args []string) (string, error) { } // Fetch workspace info, then open code editor -func runOpenCommand(t *terminal.Terminal, tstore OpenStore, wsIDOrName string, setupDoneString string, directory string, host bool, editorType string) error { //nolint:funlen // define brev command +func runOpenCommand(t *terminal.Terminal, tstore OpenStore, wsIDOrName string, setupDoneString string, directory string, host bool, editorType string) error { //nolint:funlen,gocyclo // define brev command // todo check if workspace is stopped and start if it if it is stopped fmt.Println("finding your instance...") res := refresh.RunRefreshAsync(tstore)