Skip to content

Instantly share code, notes, and snippets.

@codebykyle
Last active March 28, 2024 05:10
Show Gist options
  • Star 80 You must be signed in to star a gist
  • Fork 9 You must be signed in to fork a gist
  • Save codebykyle/b241e723ddd495aac4eaad9b8aa7c6bc to your computer and use it in GitHub Desktop.
Save codebykyle/b241e723ddd495aac4eaad9b8aa7c6bc to your computer and use it in GitHub Desktop.
Windows Terminal Split Pane Powershell Script - v2
using namespace System.Collections.Generic
# Encapsulate an arbitrary command
class PaneCommand {
[string]$Command
PaneCommand() {
$this.Command = "";
}
PaneCommand([string]$command) {
$this.Command = $command
}
[string]GetCommand() {
return $this.Command
}
[string]ToString() {
return $this.GetCommand();
}
}
# A proxy for Split Pane which takes in a command to run inside the pane
class Pane : PaneCommand {
[string]$ProfileName;
[string]$Orientation
[decimal]$Size;
Pane([string]$command) : base($command) {
$this.Orientation = '';
$this.ProfileName = "Windows Powershell"
$this.Size = 0.5;
}
Pane([string]$command, [string]$orientation) : base($command) {
$this.Orientation = $orientation;
$this.ProfileName = "Windows Powershell"
$this.Size = 0.5;
}
Pane([string]$command, [string]$orientation, [decimal]$size) : base($command) {
$this.Orientation = $orientation;
$this.ProfileName = "Windows Powershell"
$this.size = $size;
}
Pane([string]$ProfileName, [string]$command, [string]$orientation, [decimal]$size) : base($command) {
$this.Orientation = $orientation;
$this.ProfileName = $ProfileName;
$this.size = $size;
}
[string]GetCommand() {
return 'split-pane --size {0} {1} -p "{2}" -c {3}' -f $this.Size, $this.Orientation, $this.ProfileName, $this.Command
}
}
class TargetPane : PaneCommand {
[int]$SelectedIndex;
TargetPane([int]$index) {
$this.SelectedIndex = $index;
}
[string]GetCommand() {
return "focus-pane --target={0}" -f $this.SelectedIndex;
}
}
class MoveFocus : PaneCommand {
[string]$direction;
MoveFocus([string]$direction) {
$this.direction = $direction;
}
[string]GetCommand() {
return 'move-focus --direction {0}' -f $this.direction;
}
}
class PaneManager : PaneCommand {
[string]$InitialCommand;
[List[PaneCommand]]$PaneCommands;
[string]$ProfileName;
[string]$DefaultOrientation;
[double]$DefaultSize;
PaneManager() {
$this.PaneCommands = [List[PaneCommand]]::new();
$this.ProfileName = "Microsoft Powershell";
$this.DefaultOrientation = '-H';
$this.DefaultSize = 0.5;
$this.InitialCommand = "--maximized"
}
PaneManager([string]$ProfileName) {
$this.ProfileName = $ProfileName;
$this.DefaultOrientation = '-H';
$this.DefaultSize = 0.5;
}
[PaneManager]SetInitialCommand([string]$command) {
$this.InitialCommand = $command;
return $this;
}
[PaneManager]SetProfileName([string]$name) {
$this.ProfileName = $name;
return $this;
}
[PaneManager]SetDefaultOrientation([string]$orientation) {
$this.DefaultOrientation = $orientation;
return $this;
}
[PaneManager]SetDefaultSize([double]$size) {
$this.DefaultSize = $size;
return $this;
}
[PaneManager]SetOptions([string]$name, [string]$orientation, [double]$size) {
return $this.SetProfileName($name)
.SetDefaultOrientation($orientation)
.SetDefaultSize($size);
}
[PaneManager]AddPane([PaneManager]$manager) {
$manager.SetInitialCommand('');
$this.AddCommand($manager);
return $this;
}
[PaneManager]AddCommand([PaneCommand]$command) {
$this.PaneCommands.Add($command);
return $this;
}
[PaneManager]AddPane([string]$command, [string]$orientation, [decimal]$size) {
$newPane = $this.MakePane(
$this.ProfileName,
$command,
$orientation,
$size
);
$this.AddCommand($newPane);
return $this;
}
[Pane]MakePane($ProfileName, $command, $orientation, $size) {
$newPane = [Pane]::new($ProfileName, $command, $orientation, $size);
return $newPane;
}
[PaneManager]TargetPane([int]$index) {
$targetCommand = [TargetPane]::new($index)
$this.AddCommand($targetCommand)
return $this;
}
[PaneManager]MoveFocus([string]$direction) {
$targetCommand = [MoveFocus]::new($direction)
$this.AddCommand($targetCommand)
return $this;
}
[int]GetPaneCount() {
$count = 0;
foreach ($command in $this.PaneCommands)
{
if ($command -is [PaneManager]) {
$count += $command.GetPaneCount();
} elseif ($command -is [PaneCommand]) {
$count += 1;
}
}
return $count;
}
[string]GetCommand() {
$joinedCommands = $this.PaneCommands -join "; ";
if ($joinedCommands -eq "") {
return $this.InitialCommand;
}
$finalCommand = if ($this.InitialCommand -ne "") { "{0}; {1}" -f $this.InitialCommand, $joinedCommands} else { $joinedCommands };
return $finalCommand
}
}
# Your script here; see Gist comments
@codebykyle
Copy link
Author

This is a windows terminal launcher that is used to automatically layout and SSH into many servers. I have individual scripts for connecting to each server. Eg, 'connect_rbp01' is a powershell script with an SSH command:

ssh pi@rbp01.local

The following is the result of this file:
image

@codebykyle
Copy link
Author

codebykyle commented Nov 18, 2021

Hello everyone. Thank you for all the stars and the support. I have added some functionality to this. I added a fluent interface, support for different types of consoles, and nested windows.

Usage

Copy the above script into a file. Configure your script at the bottom to layout your panes, and then run the script.

The way this script works is by constructing a command to start Windows Terminal. We have a PaneManager class that makes adding repetitive commands a little easier. You can either use this script to generate a command for you, or run this directly through wt.

The following examples assume you have pasted the above script into a file, and you are editing, the very bottom of the file

Basic Usage

To get started, you need to instantiate a PaneManager class, which you can do so like this:

$paneManager = ([PaneManager]::new());

You also need to give the Pane Manager an initial script to run. This will run in the first window, before any splits are done. For example, to create a simple, single-pane SSH connection, you can run:

$paneManagerClass = [PaneManager]::new();
$paneManagerClass.SetInitialCommand("powershell ssh pi@rbp01.local");

start wt $paneManagerClass;

This will result in a single pane, running the command you specified:
image

To split a window, we can add a Pane. We need to tell it the direction to split on, and how big it should be. To add another SSH connection with a 50/50 split:

$paneManagerClass = [PaneManager]::new();
$paneManagerClass.SetInitialCommand("powershell ssh pi@rbp01.local");
$paneManagerClass.AddPane("powershell ssh pi@rbp02.local", '-V', 0.5);

start wt $paneManagerClass;

The second argument, '-V', means that we want to split the current pane, vertically. You can use '-H' if you want to split a pane horizontally.

image

You don't always have to override the initial command. Not specifying it will (currently) default to --maximized, which will maximize the current window, and open a local powershell window in the current directory.

If I want one panel for my local computer on top, and two SSH connections, I can use the following:

$paneManagerClass = [PaneManager]::new();
$paneManagerClass.AddPane("powershell ssh pi@rbp01.local", '-H', 0.5);
$paneManagerClass.AddPane("powershell ssh pi@rbp02.local", '-V', 0.5);

start wt $paneManagerClass;

Because we did not override the initial command, a default powershell window was already made. We split it in half horizontally, then split that in half vertically.

image

Targeting a specific pane

You will find, that if you follow this pattern long enough, you end up with a really nice Fibonacci sequence. This is because every time you add a new panel, it will cut the current box you are on, with relative size. Because of this, you can target a specific pane to cut either by its position, or its index.

$paneManagerClass = [PaneManager]::new();
$paneManagerClass.AddPane("powershell ssh pi@rbp01.local", '-V', 0.5);
$paneManagerClass.AddPane("powershell ssh pi@rbp02.local", '-H', 0.5);
$paneManagerClass.TargetPane(1);
$paneManagerClass.AddPane("powershell ssh pi@rbp03.local", '-V', 0.5);

start wt $paneManagerClass;

results in:

image

You can also target a specific panel by using direction, for example, if you want to move to the "left":

$paneManagerClass = [PaneManager]::new();
$paneManagerClass.AddPane("powershell ssh pi@rbp01.local", '-V', 0.5);
$paneManagerClass.AddPane("powershell ssh pi@rbp02.local", '-H', 0.5);
$paneManagerClass.MoveFocus("left");
$paneManagerClass.AddPane("powershell ssh pi@rbp03.local", '-V', 0.5);
start wt $paneManagerClass;

image

Different Profiles and Settings

You can launch WSL or Command Line, or other Windows Terminals profiles by using the settings on the Pane Manager class. Set the Profile, and all windows added after that will use that profile.

$paneManagerClass = [PaneManager]::new();
$paneManagerClass.AddPane("powershell ssh pi@rbp01.local", '-V', 0.5);
$paneManagerClass.AddPane("powershell ssh pi@rbp02.local", '-H', 0.5);
$paneManagerClass.SetProfileName("Ubuntu-20.04")
$paneManagerClass.AddPane("", '-V', 0.5);
start wt $paneManagerClass;

image

Fluent Interface

This now supports a fluent interface. Rather than having to declare each command, line by line, you can chain together commands. Please note the location of the . in the following code. Its position is important.

$paneManagerClass = ([PaneManager]::new()).
                    AddPane("powershell ssh pi@rbp01.local", '-V', 0.5).
                    AddPane("powershell ssh pi@rbp02.local", '-H', 0.5).
                    MoveFocus("left").
                    AddPane("powershell ssh pi@rbp03.local", '-V', 0.5);

start wt $paneManagerClass;

Nesting Panels

You can nest panels, although, its a little wonky. You should utilize the targeting for this to provide a decent result. You can use relative positioning with the MoveFocus command listed above.

$rbpRow1 = ([PaneManager]::new()).
                AddPane("powershell connect_rbp01", '-H', 0.75).
                AddPane("powershell connect_rbp02", '-V', 0.5);

$rbpRow2 = ([PaneManager]::new()).
                MoveFocus("left").
                AddPane("powershell connect_rbp03",  '-H', 0.66).
                MoveFocus("right").
                AddPane("powershell connect_rbp04", '-H', 0.66);

$rbpRow3 = ([PaneManager]::new()).
                MoveFocus("left").
                AddPane("powershell connect_rbp05", '-H', 0.5).
                MoveFocus("right").
                AddPane("powershell connect_rbp06", '-H', 0.5);

$piManagers = ([PaneManager])::new().
                AddPane($rbpRow1).
                AddPane($rbpRow2).
                AddPane($rbpRow3)

start wt $piManagers

image

Debugging & Generating

You may not want this giant block of functions and classes in your script. The Pane Manager is eventually being converted down to a command. You can use this script to generate a command, and save that to a file, or run it from a terminal. All you need to do is output the PaneManager as a string:

$rbpRow1 = ([PaneManager]::new()).
                AddPane("powershell connect_rbp01", '-H', 0.75).
                AddPane("powershell connect_rbp02", '-V', 0.5);

$rbpRow2 = ([PaneManager]::new()).
                MoveFocus("left").
                AddPane("powershell connect_rbp03",  '-H', 0.66).
                MoveFocus("right").
                AddPane("powershell connect_rbp04", '-H', 0.66);

$rbpRow3 = ([PaneManager]::new()).
                MoveFocus("left").
                AddPane("powershell connect_rbp05", '-H', 0.5).
                MoveFocus("right").
                AddPane("powershell connect_rbp06", '-H', 0.5);

$piManagers = ([PaneManager])::new().
                AddPane($rbpRow1).
                AddPane($rbpRow2).
                AddPane($rbpRow3)

echo $piManagers.ToString()

produces:
--maximized; split-pane --size 0.75 -H -p "Microsoft Powershell" -c powershell connect_rbp01; split-pane --size 0.5 -V -p "Microsoft Powershell" -c powershell connect_rbp02; move-focus --direction left; split-pane --size 0.66 -H -p "Microsoft Powershell" -c powershell connect_rbp03; move-focus --direction right; split-pane --size 0.66 -H -p "Microsoft Powershell" -c powershell connect_rbp04; move-focus --direction left; split-pane --size 0.5 -H -p "Microsoft Powershell" -c powershell connect_rbp05; move-focus --direction right; split-pane --size 0.5 -H -p "Microsoft Powershell" -c powershell connect_rbp06

Full Example Usage

$rbpRow1 = ([PaneManager]::new()).
                AddPane("powershell connect_rbp01", '-H', 0.75).
                AddPane("powershell connect_rbp02", '-V', 0.5);

$rbpRow2 = ([PaneManager]::new()).
                MoveFocus("left").
                AddPane("powershell connect_rbp03",  '-H', 0.66).
                MoveFocus("right").
                AddPane("powershell connect_rbp04", '-H', 0.66);

$rbpRow3 = ([PaneManager]::new()).
                MoveFocus("left").
                AddPane("powershell connect_rbp05", '-H', 0.5).
                MoveFocus("right").
                AddPane("powershell connect_rbp06", '-H', 0.5);

$piManagers = ([PaneManager])::new().
                AddPane($rbpRow1).
                AddPane($rbpRow2).
                AddPane($rbpRow3)

$topRow =  ([PaneManager]::new()).
                SetInitialCommand("--maximized powershell").
                AddPane("powershell connect_office", '-V', 0.75);

$bottomRow = ([PaneManager]::new()).
                AddPane("kubectl proxy", '-H', 0.1).
                TargetPane(1).
                AddPane($piManagers);

$paneManager = ([PaneManager]::new()).
                AddPane($topRow).
                TargetPane(0).
                AddPane($bottomRow);

echo $paneManager.ToString();
start wt $paneManager;

Which looks like:
image

Which generates:

--maximized; split-pane --size 0.75 -V -p "Microsoft Powershell" -c powershell connect_office; focus-pane --target=0; split-pane --size 0.1 -H -p "Microsoft Powershell" -c kubectl proxy; focus-pane --target=1; split-pane --size 0.75 -H -p "Microsoft Powershell" -c powershell connect_rbp01; split-pane --size 0.5 -V -p "Microsoft Powershell" -c powershell connect_rbp02; move-focus --direction left; split-pane --size 0.66 -H -p "Microsoft Powershell" -c powershell connect_rbp03; move-focus --direction right; split-pane --size 0.66 -H -p "Microsoft Powershell" -c powershell connect_rbp04; move-focus --direction left; split-pane --size 0.5 -H -p "Microsoft Powershell" -c powershell connect_rbp05; move-focus --direction right; split-pane --size 0.5 -H -p "Microsoft Powershell" -c powershell connect_rbp06

Please let me know if you have any questions

@codebykyle
Copy link
Author

Also, just for good measure, to the group of people who commented things along the lines of "Why re-invent tmux?", I say to you:

Parry this, casuals.

image

@fcmircea
Copy link

For those using non US-EN locales, specifically where the thousands separator is "." instead of ",", use the following code bit as a work around:

$culture = [System.Globalization.CultureInfo]::CreateSpecificCulture("en-US") $culture.NumberFormat.NumberDecimalSeparator = "." $culture.NumberFormat.NumberGroupSeparator = "," [System.Threading.Thread]::CurrentThread.CurrentCulture = $culture

@veasnama
Copy link

Thanks for your work, now I can make my workflow automatically.

@MrShortcut
Copy link

MrShortcut commented Nov 5, 2022

Thanks buddy i use this:

Set-Alias -Name t -Value tabRenameFN

function tabRenameFN {
  $culture = [System.Globalization.CultureInfo]::CreateSpecificCulture("en-US") 
  $culture.NumberFormat.NumberDecimalSeparator = "." 
  $culture.NumberFormat.NumberGroupSeparator = "," 
  [System.Threading.Thread]::CurrentThread.CurrentCulture = $culture

  $paneManagerClass = ([PaneManager]::new()).
                    AddPane("powershell", '-H', 0.3).
                    AddPane("powershell", '-V', 0.5).
                    MoveFocus("up");
  start wt $paneManagerClass;
}

running t command and enjoy.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment