From 17604df9daa3d5dae400a5eeec75b12feb10c1ff Mon Sep 17 00:00:00 2001 From: raj Date: Sun, 22 Oct 2023 14:01:39 +0530 Subject: [PATCH] feat: supports panes and tmux layouts --- config/main.go | 90 +++++++++++------------- controller/tmux.go | 149 +++++++++++++++++++++++++++++++++------- project-manager/main.go | 61 +++++++++++----- ui/.DS_Store | Bin 0 -> 6148 bytes utils/editor.go | 24 +++++++ 5 files changed, 236 insertions(+), 88 deletions(-) create mode 100644 ui/.DS_Store create mode 100644 utils/editor.go diff --git a/config/main.go b/config/main.go index 7ab5c14..850c02b 100644 --- a/config/main.go +++ b/config/main.go @@ -11,15 +11,32 @@ import ( "gopkg.in/yaml.v2" ) +type Pane struct { + ShellCommand []string `yaml:"shell_command"` +} + +type Window struct { + WindowName string `yaml:"window_name"` + Layout string `yaml:"layout"` + ShellCommandBefore []string `yaml:"shell_command_before"` + Panes []Pane `yaml:"panes"` +} + type Configuration struct { - Name string `yaml:"name"` - SessionName string `yaml:"session_name"` - WorkingDir string `yaml:"working_dir"` // New field for working directory - Tabs []struct { - Name string `yaml:"name"` - Commands []string `yaml:"commands"` - } `yaml:"tabs"` - LastOpened time.Time `yaml:"last_opened"` + SessionName string `yaml:"name"` + EditorCommand string `yaml:"editor"` + WorkingDir string `yaml:"root"` + Windows []Window `yaml:"windows"` + LastOpened time.Time + Attach bool `yaml:"attach"` + StartupWindow string `yaml:"startup_window"` + StartupPane int `yaml:"startup_pane"` + OnProjectStart string `yaml:"on_project_start"` + OnProjectFirstStart string `yaml:"on_project_first_start"` + OnProjectRestart string `yaml:"on_project_restart"` + OnProjectExit string `yaml:"on_project_exit"` + OnProjectStop string `yaml:"on_project_stop"` + SocketName string `yaml:"socket_name"` } var configDir string @@ -99,6 +116,17 @@ func UpdateLastOpened(projectName string) error { return nil } +func GetEditorCommand(projectName string) (string, error) { + configFile := ProjectConfigFilePath(projectName) + + config, err := Load(configFile) + if err != nil { + return "", err + } + + return config.EditorCommand, nil +} + func ListProjects() (map[string]*Configuration, error) { projectConfigs := make(map[string]*Configuration) @@ -155,39 +183,18 @@ func ProjectExists(projectName string) bool { return true } -func CreateProject(projectName, sessionName, workingDir string, tabs []struct { - Name string - Commands []string -}, -) (string, error) { - configFile := ProjectConfigFilePath(projectName) +func CreateProject(sessionName, workingDir string, windows []Window) (string, error) { + configFile := ProjectConfigFilePath(sessionName) if _, err := os.Stat(configFile); err == nil { - return "", fmt.Errorf("Project with the name '%s' already exists", projectName) - } - - var tabsWithYAMLTags []struct { - Name string `yaml:"name"` - Commands []string `yaml:"commands"` - } - - for _, tab := range tabs { - tabWithYAMLTags := struct { - Name string `yaml:"name"` - Commands []string `yaml:"commands"` - }{ - Name: tab.Name, - Commands: tab.Commands, - } - tabsWithYAMLTags = append(tabsWithYAMLTags, tabWithYAMLTags) + return "", fmt.Errorf("Project with the name '%s' already exists", sessionName) } newConfig := &Configuration{ - Name: projectName, SessionName: sessionName, WorkingDir: workingDir, - Tabs: tabsWithYAMLTags, - LastOpened: time.Now(), + Windows: windows, + Attach: true, } err := WriteConfigToFile(configFile, newConfig) @@ -204,23 +211,10 @@ func WriteConfigToFile(filename string, config *Configuration) error { return err } - indentedYAML := indentYAML(string(data), "") // Convert data to string - - err = os.WriteFile(filename, []byte(indentedYAML), 0644) + err = os.WriteFile(filename, data, 0644) if err != nil { return err } return nil } - -func indentYAML(yamlString, prefix string) string { - lines := strings.Split(yamlString, "\n") - indentedLines := make([]string, len(lines)) - - for i, line := range lines { - indentedLines[i] = prefix + line - } - - return strings.Join(indentedLines, "\n") -} diff --git a/controller/tmux.go b/controller/tmux.go index 8c3b0a4..0d1ec6f 100644 --- a/controller/tmux.go +++ b/controller/tmux.go @@ -10,65 +10,91 @@ import ( "github.com/charmbracelet/log" ) -// CreateTmuxSession creates a tmux session based on the given Configuration. +// CreateTmuxSession creates a Tmux session based on the given Configuration. func CreateTmuxSession(config *projectconfig.Configuration) error { sessionName := config.SessionName // Check if the session exists checkSessionCmd := exec.Command("tmux", "has-session", "-t", sessionName) + log.Debug("Ran command", "command", checkSessionCmd.String()) if err := checkSessionCmd.Run(); err == nil { // If it exists, switch to the session switchSessionCmd := exec.Command("tmux", "switch-client", "-t", sessionName) if err := switchSessionCmd.Run(); err != nil { return err } + log.Debug("Ran command", "command", switchSessionCmd.String()) } else { // If it doesn't exist, create the session createSessionCmd := exec.Command("tmux", "new-session", "-d", "-s", sessionName) if err := createSessionCmd.Run(); err != nil { return err } - log.Info("Ran command", "command", createSessionCmd.String()) + log.Debug("Ran command", "command", createSessionCmd.String()) // Change the working directory changeDirCmd := exec.Command("tmux", "send-keys", "-t", sessionName, "cd "+config.WorkingDir, "Enter") if err := changeDirCmd.Run(); err != nil { return err } - log.Info("Ran command", "command", changeDirCmd.String()) + log.Debug("Ran command", "command", changeDirCmd.String()) - // Send commands to the session for the first tab - sendCommandsCmd := exec.Command("tmux", "send-keys", "-t", sessionName, strings.Join(config.Tabs[0].Commands, " && "), "Enter") + // Create the first window outside the loop + // createWindow(config, sessionName, 0) + + window := config.Windows[0] + windowName := fmt.Sprintf("%s:%d", sessionName, 1) + + sendCommandsCmd := exec.Command("tmux", "send-keys", "-t", windowName, strings.Join(window.Panes[0].ShellCommand, " && "), "Enter") if err := sendCommandsCmd.Run(); err != nil { return err } - log.Info("Ran command", "command", sendCommandsCmd.String()) - // Rename the tab to the specified name - renameTabCmd := exec.Command("tmux", "rename-window", "-t", sessionName+":1", config.Tabs[0].Name) - if err := renameTabCmd.Run(); err != nil { + log.Debug("Ran command", "command", sendCommandsCmd.String()) + + // Rename the window to the specified name + renameWindowCmd := exec.Command("tmux", "rename-window", "-t", windowName, window.WindowName) + if err := renameWindowCmd.Run(); err != nil { return err } + log.Debug("Ran command", "command", renameWindowCmd.String()) - // Create and run commands for additional tabs - for i, tab := range config.Tabs[1:] { - windowName := fmt.Sprintf("%s:%d", sessionName, i+2) - createWindowCmd := exec.Command("tmux", "new-window", "-t", windowName, "-n", tab.Name) - if err := createWindowCmd.Run(); err != nil { + // Create and run commands for additional panes in the window + for j, pane := range window.Panes[1:] { + paneName := fmt.Sprintf("%s:%d.%d", sessionName, 1, j+2) + + // Split the window horizontally + splitPaneCmd := exec.Command("tmux", "split-window", "-t", windowName, "-h", "-p", "50") + if err := splitPaneCmd.Run(); err != nil { return err } - log.Info("Ran command", "command", createWindowCmd.String()) + log.Debug("Ran command", "command", splitPaneCmd.String()) - changeDirCmd = exec.Command("tmux", "send-keys", "-t", windowName, "cd "+config.WorkingDir, "Enter") - if err := changeDirCmd.Run(); err != nil { + // Select the new pane + selectPaneCmd := exec.Command("tmux", "select-pane", "-t", paneName) + if err := selectPaneCmd.Run(); err != nil { return err } - log.Info("Ran command", "command", changeDirCmd.String()) + log.Debug("Ran command", "command", selectPaneCmd.String()) - sendCommandsCmd = exec.Command("tmux", "send-keys", "-t", windowName, strings.Join(tab.Commands, " && "), "Enter") + // Send commands to the pane + sendCommandsCmd := exec.Command("tmux", "send-keys", "-t", paneName, strings.Join(pane.ShellCommand, " && "), "Enter") if err := sendCommandsCmd.Run(); err != nil { return err } - log.Info("Ran command", "command", sendCommandsCmd.String()) + log.Debug("Ran command", "command", sendCommandsCmd.String()) + } + + if window.Layout != "" { + layoutCmd := exec.Command("tmux", "select-layout", "-t", windowName, window.Layout) + if err := layoutCmd.Run(); err != nil { + return err + } + log.Debug("Ran command", "command", layoutCmd.String()) + } + + // Create and run commands for each window inside the loop + for i := 1; i < len(config.Windows); i++ { + createWindow(config, sessionName, i) } // Select the initial window and switch to the session @@ -76,12 +102,89 @@ func CreateTmuxSession(config *projectconfig.Configuration) error { if err := selectWindowCmd.Run(); err != nil { return err } + log.Debug("Ran command", "command", selectWindowCmd.String()) - switchSessionCmd := exec.Command("tmux", "switch-client", "-t", sessionName) - if err := switchSessionCmd.Run(); err != nil { - return err + if config.Attach { + switchSessionCmd := exec.Command("tmux", "switch-client", "-t", sessionName) + if err := switchSessionCmd.Run(); err != nil { + return err + } + log.Debug("Ran command", "command", switchSessionCmd.String()) } } return nil } + +func createWindow(config *projectconfig.Configuration, sessionName string, index int) error { + if index >= len(config.Windows) { + return nil + } + window := config.Windows[index] + windowName := fmt.Sprintf("%s:%d", sessionName, index+1) + + // Create a new window + createWindowCmd := exec.Command("tmux", "new-window", "-t", sessionName, "-n", windowName) + if err := createWindowCmd.Run(); err != nil { + return err + } + log.Debug("Ran command", "command", createWindowCmd.String()) + + // Change the working directory for the window + changeDirCmd := exec.Command("tmux", "send-keys", "-t", windowName, "cd "+config.WorkingDir, "Enter") + if err := changeDirCmd.Run(); err != nil { + return err + } + log.Debug("Ran command", "command", changeDirCmd.String()) + + // Send commands to the window + sendCommandsCmd := exec.Command("tmux", "send-keys", "-t", windowName, strings.Join(window.Panes[0].ShellCommand, " && "), "Enter") + if err := sendCommandsCmd.Run(); err != nil { + return err + } + log.Debug("Ran command", "command", sendCommandsCmd.String()) + + // Rename the window to the specified name + renameWindowCmd := exec.Command("tmux", "rename-window", "-t", windowName, window.WindowName) + if err := renameWindowCmd.Run(); err != nil { + return err + } + log.Debug("Ran command", "command", renameWindowCmd.String()) + + // Create and run commands for additional panes in the window + for j, pane := range window.Panes[1:] { + paneName := fmt.Sprintf("%s:%d.%d", sessionName, index+1, j+2) + + // Split the window horizontally + splitPaneCmd := exec.Command("tmux", "split-window", "-t", windowName, "-h", "-p", "50") + if err := splitPaneCmd.Run(); err != nil { + return err + } + log.Debug("Ran command", "command", splitPaneCmd.String()) + + // Select the new pane + selectPaneCmd := exec.Command("tmux", "select-pane", "-t", paneName) + if err := selectPaneCmd.Run(); err != nil { + return err + } + log.Debug("Ran command", "command", selectPaneCmd.String()) + + // Send commands to the pane + sendCommandsCmd := exec.Command("tmux", "send-keys", "-t", paneName, strings.Join(pane.ShellCommand, " && "), "Enter") + if err := sendCommandsCmd.Run(); err != nil { + return err + } + log.Debug("Ran command", "command", sendCommandsCmd.String()) + } + + if window.Layout != "" { + + layoutCmd := exec.Command("tmux", "select-layout", "-t", windowName, window.Layout) + if err := layoutCmd.Run(); err != nil { + return err + } + log.Debug("Ran command", "command", layoutCmd.String()) + } + + return nil +} diff --git a/project-manager/main.go b/project-manager/main.go index 580da02..d283947 100644 --- a/project-manager/main.go +++ b/project-manager/main.go @@ -8,6 +8,7 @@ import ( "github.com/xrehpicx/pee/controller" "github.com/xrehpicx/pee/ui/filepicker" "github.com/xrehpicx/pee/ui/table" + "github.com/xrehpicx/pee/utils" btable "github.com/charmbracelet/bubbles/table" "github.com/charmbracelet/log" @@ -43,7 +44,12 @@ var ListProjects = &cobra.Command{ selectedRow, action := table.Table(columns, rows) if action == "edit" { // print a vim command to open the config file - fmt.Println("vim", projectconfig.ProjectConfigFilePath(selectedRow[0])) + editorCommand, err := projectconfig.GetEditorCommand(selectedRow[0]) + if err != nil { + editorCommand = "vim" + } + utils.EditFile(projectconfig.ProjectConfigFilePath(selectedRow[0]), editorCommand) + log.Debug("Opened config file", "file", projectconfig.ProjectConfigFilePath(selectedRow[0])) } if action == "open" { ExecuteProjectEnv(selectedRow[0]) @@ -71,7 +77,7 @@ func ExecuteProjectEnv(projectName string) { return } projectconfig.UpdateLastOpened(projectName) - log.Info("Created tmux session", "name", config.SessionName) + log.Debug("Created tmux session", "name", config.SessionName) } var InitCmd = &cobra.Command{ @@ -94,36 +100,57 @@ var InitCmd = &cobra.Command{ log.Error(err) return } - log.Info("Selected", "work_dir", selected) + log.Debug("Selected", "work_dir", selected) - sessionName := projectName - tabs := []struct { - Name string - Commands []string - }{ + // Define the session configuration + workingDir := selected + windows := []projectconfig.Window{ { - Name: "editor", - Commands: []string{"echo 'command to open ur editor'"}, + WindowName: "editor", + Layout: "8070,202x58,0,0[202x46,0,0,89,202x11,0,47,92]", + Panes: []projectconfig.Pane{ + { + ShellCommand: []string{"echo 'command to open your editor'"}, + }, + { + ShellCommand: []string{"echo 'run dev server'"}, + }, + }, }, { - Name: "dev server", - Commands: []string{"echo 'command to start dev server'", "echo 'command to just initialize ur dependencies'"}, + WindowName: "ssh windows", + ShellCommandBefore: []string{ + "ls -lr", + }, + Panes: []projectconfig.Pane{ + { + ShellCommand: []string{"echo 'command to open your ssh windows'"}, + }, + { + ShellCommand: []string{"echo 'command to open your ssh windows'"}, + }, + }, }, { - Name: "git", - Commands: []string{"echo 'command to open ur git client (use lazygit its amazing)'"}, + WindowName: "git", + Panes: []projectconfig.Pane{ + { + ShellCommand: []string{"echo 'command to open your git client'"}, + }, + }, }, } + logger := log.NewWithOptions(os.Stderr, log.Options{ ReportCaller: false, ReportTimestamp: false, }) - ppath, err := projectconfig.CreateProject(projectName, sessionName, selected, tabs) + + ppath, err := projectconfig.CreateProject(projectName, workingDir, windows) if err != nil { logger.Error(err) } else { - // logger.Info("Created Project", "path", ppath) - fmt.Println("Created Project", "setup your config by editing: ", ppath) + fmt.Println("Created Project, set up your config by editing:", ppath) } }, } diff --git a/ui/.DS_Store b/ui/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..b5b5ce05c61ace66afe5c8dde2bc1d00c2562343 GIT binary patch literal 6148 zcmeHKOHRW;4E2;EwPitB~cQI z6+)FQdEUfh$IhE5&Ja;pK5Z66b0V5R8AnqLJ;Lj(9T}O07lYiRqB~mCJzdeZG#vJ5eu-hN zsNobOGwx%VgPl-TJ~#t^%D^|+v`^Cj literal 0 HcmV?d00001 diff --git a/utils/editor.go b/utils/editor.go new file mode 100644 index 0000000..dbb9351 --- /dev/null +++ b/utils/editor.go @@ -0,0 +1,24 @@ +package utils + +import ( + "os" + "os/exec" +) + +func EditFile(filePath string, editorCommand string) error { + if editorCommand == "" { + editorCommand = "vim" + } + + cmd := exec.Command(editorCommand, filePath) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + err := cmd.Run() + if err != nil { + return err + } + + return nil +}