Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,29 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/),
and this project adheres to [Semantic Versioning](https://semver.org/).

## [0.3.0] - 2025-01-07

### Added

- **Tabs + Sections**: Combine multiple tabs with split sections in each tab
- Add `tabs: N` to config to create N tabs (2-10)
- Each tab uses your specified layout (duo, trio, quad, etc.)
- Example: `tabs: 3` + `layout: duo` = 3 tabs × 2 sections = 6 workspaces
- Commands fill in order: tab1-section1, tab1-section2, tab2-section1, ...

### Changed

- Setup wizard now asks "Do you want multiple tabs?" before layout selection
- Terminology: "panes" renamed to "sections" in wizard for clarity
- Help text updated to explain tabs + sections

### Removed

- **BREAKING**: `layout: tabs` is removed
- Migration: Change `layout: tabs` to `layout: 1` and add `tabs: N`
- Running with old config shows clear migration instructions
- The new system is more flexible: any layout can have tabs

## [0.2.7] - 2025-12-30

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.2.7
0.3.0
41 changes: 29 additions & 12 deletions bin/gpane
Original file line number Diff line number Diff line change
Expand Up @@ -44,29 +44,46 @@ open_session() {
[[ "${reuse_window}" == "true" ]] && window_mode="current"
echo " ${project_name}"
echo " dir: ${project_dir}"
echo " layout: ${resolved_layout}"

local panes_per_tab
panes_per_tab=$(compute_total_panes "${resolved_layout}")

if (( TABS_COUNT > 1 )); then
echo " layout: ${resolved_layout} × ${TABS_COUNT} tabs (${panes_per_tab} panes/tab)"
else
echo " layout: ${resolved_layout}"
fi
echo " window: ${window_mode}"
if is_tabs_layout "${resolved_layout}"; then
local num_tabs=${#PANE_COMMANDS[@]}
(( num_tabs > 10 )) && num_tabs=10
for ((i = 1; i <= num_tabs; i++)); do
local cmd="${PANE_COMMANDS[$i]:-}"
echo " tab ${i}/${num_tabs}: ${cmd:-<empty>}"

if (( TABS_COUNT > 1 )); then
# Tabs + panes mode
local cmd_idx=1
for ((t = 1; t <= TABS_COUNT; t++)); do
echo " tab ${t}/${TABS_COUNT}:"
for ((p = 1; p <= panes_per_tab; p++)); do
local cmd="${PANE_COMMANDS[$cmd_idx]:-}"
echo " pane ${p}: ${cmd:-<empty>}"
cmd_idx=$((cmd_idx + 1))
done
done
else
local total_panes
total_panes=$(compute_total_panes "${resolved_layout}")
for ((i = 1; i <= total_panes; i++)); do
# Single window with panes
for ((i = 1; i <= panes_per_tab; i++)); do
local cmd="${PANE_COMMANDS[$i]:-}"
echo " pane $i: ${cmd:-<empty>}"
done
fi
return 0
fi

# Check for deprecated layout: tabs syntax
if ! check_deprecated_tabs_layout "${resolved_layout}"; then
return 1
fi

# Route to appropriate layout handler
if is_tabs_layout "${resolved_layout}"; then
layout_tabs "${project_name}" "${project_dir}" "${reuse_window}" "${PANE_COMMANDS[@]}"
if (( TABS_COUNT > 1 )); then
layout_hybrid "${project_name}" "${project_dir}" "${resolved_layout}" "${TABS_COUNT}" "${reuse_window}" "${PANE_COMMANDS[@]}"
else
layout_dynamic "${project_name}" "${project_dir}" "${resolved_layout}" "${reuse_window}" "${PANE_COMMANDS[@]}"
fi
Expand Down
30 changes: 19 additions & 11 deletions config.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@
# Root directory containing your projects
projects_root: ~/Projects

# Default layout (see 'gpane help' for all options)
# Pane layouts: duo, trio, quad, dashboard, stacked, wide, or row notation (2-2, 1-3, etc.)
# Tab layout: tabs (one command per tab, up to 10)
# Default layout (sections per tab)
# Options: duo, trio, quad, dashboard, stacked, wide, or row notation (2-2, 1-3, etc.)
default_layout: duo

# Default commands for each pane/tab (empty string = just shell)
# Commands are applied in order: left-to-right, top-to-bottom for panes; tab 1, 2, 3... for tabs
# Optional: Number of tabs (2-10)
# If omitted or set to 1, creates a single window with the layout above
# tabs: 3

# Default commands for each section (empty string = just shell)
# Commands fill in order: left-to-right, top-to-bottom
# For multiple tabs: tab1-section1, tab1-section2, tab2-section1, tab2-section2, ...
default_commands:
- "claude"
- ""
Expand All @@ -26,16 +30,20 @@ default_commands:
# - "npm test"
# - ""
#
# # Example: 3 tabs, each with 2 sections (6 total workspaces)
# webapp:
# layout: tabs
# layout: duo
# tabs: 3
# commands:
# - "claude"
# - "npm run dev"
# - "npm test"
# - ""
# - "claude" # Tab 1, Left
# - "" # Tab 1, Right
# - "npm run dev" # Tab 2, Left
# - "npm test" # Tab 2, Right
# - "htop" # Tab 3, Left
# - "" # Tab 3, Right

# Tips:
# - Use "clear && claude" for a cleaner startup
# - For unattended workflows, Claude supports --dangerously-skip-permissions
# (use with caution - this bypasses all confirmation prompts)
# - Leave a pane/tab empty ("") for a regular shell
# - Leave a section empty ("") for a regular shell
8 changes: 4 additions & 4 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -1006,7 +1006,7 @@
<a href="#features">Features</a>
<a href="#layouts">Layouts</a>
<a href="#install">Install</a>
<span class="version-badge">v<span class="version-num">0.2.7</span></span>
<span class="version-badge">v<span class="version-num">0.3.0</span></span>
<a href="https://github.com/minorole/gsx" class="github-link" target="_blank">
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
Expand All @@ -1029,8 +1029,8 @@ <h1>One command.<br><em>Infinite focus.</em></h1>

<p class="hero-description">
Stop splitting panes manually. Open multiple projects in parallel —
Claude in one window, Aider in another, each project's layout and
commands configured exactly how you like.
Claude in one window, Aider in another, each project's layout, tabs,
and commands configured exactly how you like.
</p>

<div class="cta-group">
Expand Down Expand Up @@ -1095,7 +1095,7 @@ <h3>Multi-Project Launch</h3>
<div class="feature-card">
<div class="feature-number">05</div>
<h3>Dynamic Layouts</h3>
<p>2-10 panes in any configuration. Presets like quad and dashboard, or custom layouts like 2-2-5-1.</p>
<p>2-10 sections per window, optionally across multiple tabs. Presets like quad and dashboard, or custom layouts like 2-2-5-1.</p>
</div>

<div class="feature-card">
Expand Down
6 changes: 6 additions & 0 deletions lib/config.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ CONFIG_FILE="${CONFIG_DIR}/config.yaml"
# Global config variables (set by parse_config)
PROJECTS_ROOT=""
DEFAULT_LAYOUT="3-col"
TABS_COUNT=1 # Number of tabs (1 = single window, 2-10 = multiple tabs)

# Array of commands in spatial order (left-to-right, top-to-bottom)
typeset -a PANE_COMMANDS
Expand All @@ -31,6 +32,7 @@ parse_config() {
# Reset to defaults
PROJECTS_ROOT=""
DEFAULT_LAYOUT="3-col"
TABS_COUNT=1
PANE_COMMANDS=()

local line in_commands=false
Expand All @@ -57,6 +59,8 @@ parse_config() {
PROJECTS_ROOT="${PROJECTS_ROOT/#\~/${HOME}}"
elif [[ "${line}" =~ ^default_layout:[[:space:]]*(.+)$ ]]; then
DEFAULT_LAYOUT="${match[1]}"
elif [[ "${line}" =~ ^tabs:[[:space:]]*([0-9]+)$ ]]; then
TABS_COUNT="${match[1]}"
fi

# Parse default commands (array format: "- value")
Expand Down Expand Up @@ -129,6 +133,8 @@ parse_project_config() {
if [[ "${in_project}" == true ]]; then
if [[ "${line}" =~ ^[[:space:]]+layout:[[:space:]]*(.+)$ ]]; then
DEFAULT_LAYOUT="${match[1]}"
elif [[ "${line}" =~ ^[[:space:]]+tabs:[[:space:]]*([0-9]+)$ ]]; then
TABS_COUNT="${match[1]}"
# Handle commands array format
elif [[ "${in_commands}" == true ]]; then
if [[ "${line}" =~ ^[[:space:]]+-[[:space:]]+\"(.*)\"$ ]]; then
Expand Down
19 changes: 12 additions & 7 deletions lib/help.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,18 @@ Layouts:
Row notation: "2" (2 side-by-side), "2-2" (4-pane grid), "1-3" (1 top + 3 bottom)

Aliases:
tabs Multiple tabs (one command per tab, up to 10)
duo 2 panes side-by-side
trio 3 panes side-by-side
quad 2x2 grid (4 panes)
dashboard 1 top + 3 bottom (4 panes)
stacked 2 panes vertically
wide 3 top + 1 bottom (4 panes)
duo 2 sections side-by-side
trio 3 sections side-by-side
quad 2x2 grid (4 sections)
dashboard 1 top + 3 bottom (4 sections)
stacked 2 sections vertically
wide 3 top + 1 bottom (4 sections)

Tabs (optional):
Add 'tabs: N' to your config (2-10) to create multiple tabs.
Each tab will have the layout you specify.

Example: layout: duo + tabs: 3 = 3 tabs, each with 2 sections

Examples:
gpane setup # First-time setup
Expand Down
59 changes: 34 additions & 25 deletions lib/layouts.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ LAYOUT_ALIASES[stacked]="1-1"
LAYOUT_ALIASES[quad]="2-2"
LAYOUT_ALIASES[dashboard]="1-3"
LAYOUT_ALIASES[wide]="3-1"
LAYOUT_ALIASES[tabs]="tabs"

# Resolve layout alias to row notation
# Usage: resolve_layout_alias "quad" -> "2-2"
Expand Down Expand Up @@ -103,11 +102,6 @@ get_layout_info() {
LAYOUT_PANE_LABELS=()

case "${resolved}" in
tabs)
# Tabs layout: default to 4 tabs for setup wizard
LAYOUT_PANE_COUNT=4
LAYOUT_PANE_LABELS=("Tab 1" "Tab 2" "Tab 3" "Tab 4")
;;
2) LAYOUT_PANE_COUNT=2; LAYOUT_PANE_LABELS=("Left" "Right") ;;
3) LAYOUT_PANE_COUNT=3; LAYOUT_PANE_LABELS=("Left" "Middle" "Right") ;;
1-1) LAYOUT_PANE_COUNT=2; LAYOUT_PANE_LABELS=("Top" "Bottom") ;;
Expand All @@ -127,41 +121,56 @@ get_layout_info() {
}

# =============================================================================
# Tabs Layout
# Hybrid Layout (tabs + panes)
# =============================================================================

# Layout: tabs (one tab per command, no splits)
# Each command runs in its own tab
layout_tabs() {
# Layout: hybrid (multiple tabs, each with pane splits)
# Commands fill tabs in order: tab1-pane1, tab1-pane2, ..., tab2-pane1, ...
layout_hybrid() {
local project_name=$1
local project_dir=$2
local reuse_window=$3
shift 3
local layout_spec=$3
local tabs_count=$4
local reuse_window=$5
shift 5
local -a cmds=("$@")

local label="[${project_name}]"

# Require at least one command
if (( ${#cmds[@]} == 0 )); then
echo "Error: tabs layout requires at least one command" >&2
# Validate layout
if ! validate_layout "$layout_spec"; then
echo "Error: Invalid layout '$layout_spec'" >&2
return 1
fi

# Max 10 tabs (matches Cmd+1 through Cmd+0 shortcuts)
if (( ${#cmds[@]} > 10 )); then
echo "Warning: Limiting to 10 tabs (${#cmds[@]} commands provided)" >&2
cmds=("${cmds[@]:0:10}")
# Validate tabs count
if (( tabs_count < 1 || tabs_count > 10 )); then
echo "Error: tabs must be between 1 and 10 (got $tabs_count)" >&2
return 1
fi

run_layout "layout-tabs" "${label}" "${reuse_window}" "${project_dir}" "${cmds[@]}"
run_layout "layout-hybrid" "${label}" "${reuse_window}" "${project_dir}" "${layout_spec}" "${tabs_count}" "${cmds[@]}"
}

# Check if layout is tabs mode
is_tabs_layout() {
# Check for deprecated layout: tabs syntax
# Returns 1 and shows migration message if deprecated syntax detected
check_deprecated_tabs_layout() {
local layout=$1
local resolved
resolved=$(resolve_layout_alias "$layout")
[[ "$resolved" == "tabs" ]]
if [[ "$layout" == "tabs" ]]; then
echo "" >&2
echo "Error: 'layout: tabs' is deprecated." >&2
echo "" >&2
echo "Update your config to use the new tabs + panes system:" >&2
echo " layout: 1 # panes per tab (or duo, trio, quad, etc.)" >&2
echo " tabs: N # number of tabs (2-10)" >&2
echo "" >&2
echo "Example - 3 tabs, each with 2 panes:" >&2
echo " layout: duo" >&2
echo " tabs: 3" >&2
echo "" >&2
return 1
fi
return 0
}

# =============================================================================
Expand Down
Loading