Skip to content

Commit a181e2c

Browse files
committed
Initial import for TMUX executor
0 parents  commit a181e2c

File tree

5 files changed

+199
-0
lines changed

5 files changed

+199
-0
lines changed

requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
click

setup.py

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/usr/bin/env python3
2+
3+
from setuptools import setup
4+
5+
setup(
6+
name="tmuxrun",
7+
version="0.1.0",
8+
py_modules=['tmux'],
9+
install_requires=['click'],
10+
entry_points={
11+
'console_scripts': [
12+
'tmx = tmux.main:main',
13+
]
14+
}
15+
)

tmux/__init__.py

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
4+
from .tmux import TMux, Pan, start
5+
from .main import main
6+

tmux/main.py

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#!/usr/bin/env python3
2+
3+
import os
4+
import click
5+
6+
from . import tmux
7+
import json
8+
9+
@click.command()
10+
@click.argument('config')
11+
@click.option('--tmux', 'tmux_cmd', type=click.STRING, envvar="TMUX", default='tmux')
12+
def main(config: str, tmux_cmd: str):
13+
if os.path.exists(config):
14+
with open(config, 'rt') as fd:
15+
config = json.load(fd)
16+
tmux.start(config, tmux_cmd)

tmux/tmux.py

+161
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
#!/usr/bin/env python3
2+
3+
from typing import List, Tuple
4+
import os
5+
import math
6+
7+
class TMux:
8+
def __init__(self, cmd, rows=None, cols=None):
9+
self.pans: List['Pan'] = []
10+
self.cmds = [cmd, "-2", "new-session"]
11+
self.rows = rows
12+
self.cols = cols
13+
self.sync = False
14+
self.mouse = True
15+
def new_pan(self, row=None, col=None) -> 'Pan':
16+
p = Pan(row=row, col=col)
17+
self.pans.append(p)
18+
return p
19+
20+
def get_size(self) -> Tuple[int, int]:
21+
pans = max(len(self.pans),1)
22+
if self.rows is None:
23+
cols = self.cols
24+
if cols is None:
25+
cols = math.ceil(math.sqrt(pans))
26+
rows = (pans+cols-1) // cols
27+
return rows, cols
28+
else:
29+
cols = (pans+self.rows-1) // self.rows
30+
return self.rows, cols
31+
32+
def add_cmd(self, cmd):
33+
self.cmds.append(";")
34+
if type(cmd) is list:
35+
self.cmds += cmd
36+
else:
37+
self.cmds += str(cmd).split(" ")
38+
39+
def map_panes(self):
40+
rows, cols = self.get_size()
41+
42+
pans = []
43+
for row in range(rows):
44+
pans.append([None]*cols)
45+
46+
for w in self.pans:
47+
for idx in range(rows*cols):
48+
row = idx // cols
49+
col = idx % cols
50+
if pans[row][col] is not None:
51+
continue
52+
if (w.row is None or w.row == row) and \
53+
(w.col is None or w.col == col):
54+
pans[row][col] = w
55+
break
56+
return pans, rows, cols
57+
58+
def load_pan_configs(self, configs):
59+
for config in configs:
60+
pan = self.new_pan()
61+
pan.load(config)
62+
63+
def load(self, config):
64+
if type(config) is dict:
65+
for k, v in config.items():
66+
if k == 'rows':
67+
self.rows = int(v)
68+
elif k == 'cols':
69+
self.cols = int(v)
70+
elif k == 'pans':
71+
self.load_pan_configs(v)
72+
elif k == 'sync':
73+
self.sync = bool(v)
74+
elif k == 'mouse':
75+
self.mouse = bool(v)
76+
else:
77+
raise Exception(f"Unknonwn option={k}")
78+
elif type(config) is list:
79+
self.load_pan_configs(config)
80+
else:
81+
self.load_pan_configs([str(config)])
82+
83+
@staticmethod
84+
def percent_of(idx, total):
85+
remain = total - idx
86+
return remain * 100 // (remain+1)
87+
88+
def execute(self):
89+
if self.mouse:
90+
self.add_cmd(["set-option", "-g", "mouse", "on"])
91+
pans, rows, cols = self.map_panes()
92+
cwd = os.getcwd()
93+
for row in range(1, rows):
94+
self.add_cmd([
95+
"split-window", "-v",
96+
"-l", f"{TMux.percent_of(row, rows)}%",
97+
"-c", cwd ])
98+
99+
for row in range(rows):
100+
for col in range(1, cols):
101+
self.add_cmd(["select-pane", "-t", f"{row*cols+col-1}"])
102+
self.add_cmd([
103+
"split-window", "-h",
104+
"-l", f"{TMux.percent_of(col, cols)}%",
105+
"-c", cwd ])
106+
107+
for row in range(rows):
108+
for col in range(cols):
109+
item = pans[row][col]
110+
if item is None:
111+
continue
112+
self.add_cmd([
113+
"select-pane", "-t", f"{row*cols+col}"
114+
])
115+
item.execute(self)
116+
if self.sync:
117+
self.add_cmd(["set-option", "-w", "synchronize-panes", "on"])
118+
return os.execvp(self.cmds[0], self.cmds)
119+
120+
class Pan:
121+
def __init__(self, row=None, col=None):
122+
self.row = row
123+
self.col = col
124+
self.cmds = []
125+
126+
def add_cmd(self, cmd):
127+
if type(cmd) is list:
128+
self.cmds.append(cmd)
129+
else:
130+
self.cmds.append(["send-keys", cmd, "C-m"])
131+
132+
def load(self, config):
133+
if type(config) is object:
134+
for k, v in config.items():
135+
if k == 'row':
136+
self.row = int(v)
137+
elif k == 'col':
138+
self.col = int(v)
139+
elif k == 'cmds':
140+
for cmd in v:
141+
self.add_cmd(cmd)
142+
elif k == 'cmd':
143+
self.add_cmd(v)
144+
elif type(config) is list:
145+
for cmd in config:
146+
self.add_cmd(cmd)
147+
elif type(config) is str:
148+
self.add_cmd(config)
149+
150+
def add_keys(self, *args):
151+
self.cmds.append(["send-keys", *args])
152+
153+
def execute(self, tmux):
154+
for cmd in self.cmds:
155+
tmux.add_cmd(cmd)
156+
157+
158+
def start(config, tmux="tmux"):
159+
s = TMux(tmux)
160+
s.load(config)
161+
s.execute()

0 commit comments

Comments
 (0)