feat: supports panes and tmux layouts

This commit is contained in:
raj 2023-10-22 14:01:39 +05:30
parent 3e2f137700
commit 17604df9da
5 changed files with 236 additions and 88 deletions

View File

@ -11,15 +11,32 @@ import (
"gopkg.in/yaml.v2" "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 { type Configuration struct {
Name string `yaml:"name"` SessionName string `yaml:"name"`
SessionName string `yaml:"session_name"` EditorCommand string `yaml:"editor"`
WorkingDir string `yaml:"working_dir"` // New field for working directory WorkingDir string `yaml:"root"`
Tabs []struct { Windows []Window `yaml:"windows"`
Name string `yaml:"name"` LastOpened time.Time
Commands []string `yaml:"commands"` Attach bool `yaml:"attach"`
} `yaml:"tabs"` StartupWindow string `yaml:"startup_window"`
LastOpened time.Time `yaml:"last_opened"` 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 var configDir string
@ -99,6 +116,17 @@ func UpdateLastOpened(projectName string) error {
return nil 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) { func ListProjects() (map[string]*Configuration, error) {
projectConfigs := make(map[string]*Configuration) projectConfigs := make(map[string]*Configuration)
@ -155,39 +183,18 @@ func ProjectExists(projectName string) bool {
return true return true
} }
func CreateProject(projectName, sessionName, workingDir string, tabs []struct { func CreateProject(sessionName, workingDir string, windows []Window) (string, error) {
Name string configFile := ProjectConfigFilePath(sessionName)
Commands []string
},
) (string, error) {
configFile := ProjectConfigFilePath(projectName)
if _, err := os.Stat(configFile); err == nil { if _, err := os.Stat(configFile); err == nil {
return "", fmt.Errorf("Project with the name '%s' already exists", projectName) return "", fmt.Errorf("Project with the name '%s' already exists", sessionName)
}
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)
} }
newConfig := &Configuration{ newConfig := &Configuration{
Name: projectName,
SessionName: sessionName, SessionName: sessionName,
WorkingDir: workingDir, WorkingDir: workingDir,
Tabs: tabsWithYAMLTags, Windows: windows,
LastOpened: time.Now(), Attach: true,
} }
err := WriteConfigToFile(configFile, newConfig) err := WriteConfigToFile(configFile, newConfig)
@ -204,23 +211,10 @@ func WriteConfigToFile(filename string, config *Configuration) error {
return err return err
} }
indentedYAML := indentYAML(string(data), "") // Convert data to string err = os.WriteFile(filename, data, 0644)
err = os.WriteFile(filename, []byte(indentedYAML), 0644)
if err != nil { if err != nil {
return err return err
} }
return nil 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")
}

View File

@ -10,65 +10,91 @@ import (
"github.com/charmbracelet/log" "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 { func CreateTmuxSession(config *projectconfig.Configuration) error {
sessionName := config.SessionName sessionName := config.SessionName
// Check if the session exists // Check if the session exists
checkSessionCmd := exec.Command("tmux", "has-session", "-t", sessionName) checkSessionCmd := exec.Command("tmux", "has-session", "-t", sessionName)
log.Debug("Ran command", "command", checkSessionCmd.String())
if err := checkSessionCmd.Run(); err == nil { if err := checkSessionCmd.Run(); err == nil {
// If it exists, switch to the session // If it exists, switch to the session
switchSessionCmd := exec.Command("tmux", "switch-client", "-t", sessionName) switchSessionCmd := exec.Command("tmux", "switch-client", "-t", sessionName)
if err := switchSessionCmd.Run(); err != nil { if err := switchSessionCmd.Run(); err != nil {
return err return err
} }
log.Debug("Ran command", "command", switchSessionCmd.String())
} else { } else {
// If it doesn't exist, create the session // If it doesn't exist, create the session
createSessionCmd := exec.Command("tmux", "new-session", "-d", "-s", sessionName) createSessionCmd := exec.Command("tmux", "new-session", "-d", "-s", sessionName)
if err := createSessionCmd.Run(); err != nil { if err := createSessionCmd.Run(); err != nil {
return err return err
} }
log.Info("Ran command", "command", createSessionCmd.String()) log.Debug("Ran command", "command", createSessionCmd.String())
// Change the working directory // Change the working directory
changeDirCmd := exec.Command("tmux", "send-keys", "-t", sessionName, "cd "+config.WorkingDir, "Enter") changeDirCmd := exec.Command("tmux", "send-keys", "-t", sessionName, "cd "+config.WorkingDir, "Enter")
if err := changeDirCmd.Run(); err != nil { if err := changeDirCmd.Run(); err != nil {
return err 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 // Create the first window outside the loop
sendCommandsCmd := exec.Command("tmux", "send-keys", "-t", sessionName, strings.Join(config.Tabs[0].Commands, " && "), "Enter") // 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 { if err := sendCommandsCmd.Run(); err != nil {
return err return err
} }
log.Info("Ran command", "command", sendCommandsCmd.String()) log.Debug("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) // Rename the window to the specified name
if err := renameTabCmd.Run(); err != nil { renameWindowCmd := exec.Command("tmux", "rename-window", "-t", windowName, window.WindowName)
if err := renameWindowCmd.Run(); err != nil {
return err return err
} }
log.Debug("Ran command", "command", renameWindowCmd.String())
// Create and run commands for additional tabs // Create and run commands for additional panes in the window
for i, tab := range config.Tabs[1:] { for j, pane := range window.Panes[1:] {
windowName := fmt.Sprintf("%s:%d", sessionName, i+2) paneName := fmt.Sprintf("%s:%d.%d", sessionName, 1, j+2)
createWindowCmd := exec.Command("tmux", "new-window", "-t", windowName, "-n", tab.Name)
if err := createWindowCmd.Run(); err != nil { // Split the window horizontally
splitPaneCmd := exec.Command("tmux", "split-window", "-t", windowName, "-h", "-p", "50")
if err := splitPaneCmd.Run(); err != nil {
return err 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") // Select the new pane
if err := changeDirCmd.Run(); err != nil { selectPaneCmd := exec.Command("tmux", "select-pane", "-t", paneName)
if err := selectPaneCmd.Run(); err != nil {
return err 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 { if err := sendCommandsCmd.Run(); err != nil {
return err 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 // Select the initial window and switch to the session
@ -76,11 +102,88 @@ func CreateTmuxSession(config *projectconfig.Configuration) error {
if err := selectWindowCmd.Run(); err != nil { if err := selectWindowCmd.Run(); err != nil {
return err return err
} }
log.Debug("Ran command", "command", selectWindowCmd.String())
if config.Attach {
switchSessionCmd := exec.Command("tmux", "switch-client", "-t", sessionName) switchSessionCmd := exec.Command("tmux", "switch-client", "-t", sessionName)
if err := switchSessionCmd.Run(); err != nil { if err := switchSessionCmd.Run(); err != nil {
return err 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 return nil

View File

@ -8,6 +8,7 @@ import (
"github.com/xrehpicx/pee/controller" "github.com/xrehpicx/pee/controller"
"github.com/xrehpicx/pee/ui/filepicker" "github.com/xrehpicx/pee/ui/filepicker"
"github.com/xrehpicx/pee/ui/table" "github.com/xrehpicx/pee/ui/table"
"github.com/xrehpicx/pee/utils"
btable "github.com/charmbracelet/bubbles/table" btable "github.com/charmbracelet/bubbles/table"
"github.com/charmbracelet/log" "github.com/charmbracelet/log"
@ -43,7 +44,12 @@ var ListProjects = &cobra.Command{
selectedRow, action := table.Table(columns, rows) selectedRow, action := table.Table(columns, rows)
if action == "edit" { if action == "edit" {
// print a vim command to open the config file // 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" { if action == "open" {
ExecuteProjectEnv(selectedRow[0]) ExecuteProjectEnv(selectedRow[0])
@ -71,7 +77,7 @@ func ExecuteProjectEnv(projectName string) {
return return
} }
projectconfig.UpdateLastOpened(projectName) projectconfig.UpdateLastOpened(projectName)
log.Info("Created tmux session", "name", config.SessionName) log.Debug("Created tmux session", "name", config.SessionName)
} }
var InitCmd = &cobra.Command{ var InitCmd = &cobra.Command{
@ -94,36 +100,57 @@ var InitCmd = &cobra.Command{
log.Error(err) log.Error(err)
return return
} }
log.Info("Selected", "work_dir", selected) log.Debug("Selected", "work_dir", selected)
sessionName := projectName // Define the session configuration
tabs := []struct { workingDir := selected
Name string windows := []projectconfig.Window{
Commands []string
}{
{ {
Name: "editor", WindowName: "editor",
Commands: []string{"echo 'command to open ur 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'"},
}, },
{ {
Name: "dev server", ShellCommand: []string{"echo 'run dev server'"},
Commands: []string{"echo 'command to start dev server'", "echo 'command to just initialize ur dependencies'"}, },
},
}, },
{ {
Name: "git", WindowName: "ssh windows",
Commands: []string{"echo 'command to open ur git client (use lazygit its amazing)'"}, 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'"},
},
},
},
{
WindowName: "git",
Panes: []projectconfig.Pane{
{
ShellCommand: []string{"echo 'command to open your git client'"},
},
},
}, },
} }
logger := log.NewWithOptions(os.Stderr, log.Options{ logger := log.NewWithOptions(os.Stderr, log.Options{
ReportCaller: false, ReportCaller: false,
ReportTimestamp: false, ReportTimestamp: false,
}) })
ppath, err := projectconfig.CreateProject(projectName, sessionName, selected, tabs)
ppath, err := projectconfig.CreateProject(projectName, workingDir, windows)
if err != nil { if err != nil {
logger.Error(err) logger.Error(err)
} else { } else {
// logger.Info("Created Project", "path", ppath) fmt.Println("Created Project, set up your config by editing:", ppath)
fmt.Println("Created Project", "setup your config by editing: ", ppath)
} }
}, },
} }

BIN
ui/.DS_Store vendored Normal file

Binary file not shown.

24
utils/editor.go Normal file
View File

@ -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
}