Skip to content

Commit e921483

Browse files
committed
add the ability to append windows to a session
Modifies the behaviour when starting a session from within an existing tmux session: the user is given the choice to switch to new session, do nothing, or append to current session.
1 parent c50700b commit e921483

File tree

7 files changed

+259
-50
lines changed

7 files changed

+259
-50
lines changed

docs/cli.rst

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,23 @@ directory may be loaded with:
169169
170170
$ tmuxp load .
171171
172+
If you try to load a config file from within a tmux session, it will ask you
173+
if you want to load and attach to the new session, or just load detached.
174+
You can also load a config file and append the windows to the current active session.
175+
176+
::
177+
178+
Already inside TMUX, switch to session? yes/no
179+
Or (a)ppend windows in the current active session?
180+
[y/n/a]:
181+
182+
All of these options can be preselected to skip the prompt:
183+
184+
.. code-block:: bash
185+
$ tmuxp load -y config # load attached
186+
$ tmuxp load -d config # load detached
187+
$ tmuxp load -a config # append windows
188+
172189
Multiple sessions can be loaded at once. The first ones will be created
173190
without being attached. The last one will be attached if there is no
174191
``-d`` flag on the command line.

docs/quickstart.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ Load multiple tmux sessions at once:
5555
$ tmuxp load example.yaml anothersession.yaml
5656
5757
tmuxp will offer to ``switch-client`` for you if you're already in a
58-
session.
58+
session. You can also load a configuration, and append the windows to
59+
the current active session.
5960

6061
You can also have a custom tmuxp config directory by setting the
6162
``TMUX_CONFIGDIR`` in your environment variables.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
session_name: sample_three_windows
2+
windows:
3+
- window_name: first
4+
panes:
5+
- shell_command:
6+
- echo 'first window'
7+
- window_name: second
8+
panes:
9+
- shell_command:
10+
- echo 'second window'
11+
- window_name: third
12+
panes:
13+
- shell_command:
14+
- echo 'third window'
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
session_name: sample_two_windows
2+
windows:
3+
- window_name: first
4+
panes:
5+
- shell_command:
6+
- echo 'first window'
7+
- window_name: second
8+
panes:
9+
- shell_command:
10+
- echo 'second window'

tests/test_workspacebuilder.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -779,3 +779,60 @@ def test_plugin_system_multiple_plugins(session):
779779
# override methods are currently written
780780
proc = session.cmd('display-message', '-p', "'#W'")
781781
assert proc.stdout[0] == "'mp_test_awf'"
782+
783+
784+
def test_load_configs_same_session(server):
785+
yaml_config = loadfixture("workspacebuilder/three_windows.yaml")
786+
sconfig = kaptan.Kaptan(handler='yaml')
787+
sconfig = sconfig.import_config(yaml_config).get()
788+
789+
builder = WorkspaceBuilder(sconf=sconfig, server=server)
790+
builder.build()
791+
792+
assert len(server.sessions) == 1
793+
assert len(server.sessions[0]._windows) == 3
794+
795+
yaml_config = loadfixture("workspacebuilder/two_windows.yaml")
796+
797+
sconfig = kaptan.Kaptan(handler='yaml')
798+
sconfig = sconfig.import_config(yaml_config).get()
799+
800+
builder = WorkspaceBuilder(sconf=sconfig, server=server)
801+
builder.build()
802+
assert len(server.sessions) == 2
803+
assert len(server.sessions[1]._windows) == 2
804+
805+
yaml_config = loadfixture("workspacebuilder/two_windows.yaml")
806+
807+
sconfig = kaptan.Kaptan(handler='yaml')
808+
sconfig = sconfig.import_config(yaml_config).get()
809+
810+
builder = WorkspaceBuilder(sconf=sconfig, server=server)
811+
builder.build(server.sessions[1], True)
812+
813+
assert len(server.sessions) == 2
814+
assert len(server.sessions[1]._windows) == 4
815+
816+
817+
def test_load_configs_separate_sessions(server):
818+
yaml_config = loadfixture("workspacebuilder/three_windows.yaml")
819+
sconfig = kaptan.Kaptan(handler='yaml')
820+
sconfig = sconfig.import_config(yaml_config).get()
821+
822+
builder = WorkspaceBuilder(sconf=sconfig, server=server)
823+
builder.build()
824+
825+
assert len(server.sessions) == 1
826+
assert len(server.sessions[0]._windows) == 3
827+
828+
yaml_config = loadfixture("workspacebuilder/two_windows.yaml")
829+
830+
sconfig = kaptan.Kaptan(handler='yaml')
831+
sconfig = sconfig.import_config(yaml_config).get()
832+
833+
builder = WorkspaceBuilder(sconf=sconfig, server=server)
834+
builder.build()
835+
836+
assert len(server.sessions) == 2
837+
assert len(server.sessions[0]._windows) == 3
838+
assert len(server.sessions[1]._windows) == 2

tmuxp/cli.py

Lines changed: 123 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -448,7 +448,6 @@ def _reattach(builder):
448448
449449
If not, ``tmux attach-session`` loads the client to the target session.
450450
"""
451-
452451
for plugin in builder.plugins:
453452
plugin.reattach(builder.session)
454453
proc = builder.session.cmd('display-message', '-p', "'#S'")
@@ -462,6 +461,85 @@ def _reattach(builder):
462461
builder.session.attach_session()
463462

464463

464+
def _load_attached(builder, detached):
465+
"""
466+
Load config in new session and attach
467+
468+
Parameters
469+
----------
470+
builder: :class:`workspacebuilder.WorkspaceBuilder`
471+
detached : bool
472+
"""
473+
builder.build()
474+
475+
if 'TMUX' in os.environ: # tmuxp ran from inside tmux
476+
# unset TMUX, save it, e.g. '/tmp/tmux-1000/default,30668,0'
477+
tmux_env = os.environ.pop('TMUX')
478+
479+
if has_gte_version('2.6'):
480+
set_layout_hook(builder.session, 'client-session-changed')
481+
482+
builder.session.switch_client() # switch client to new session
483+
484+
os.environ['TMUX'] = tmux_env # set TMUX back again
485+
else:
486+
if has_gte_version('2.6'):
487+
# if attaching for first time
488+
set_layout_hook(builder.session, 'client-attached')
489+
490+
# for cases where user switches client for first time
491+
set_layout_hook(builder.session, 'client-session-changed')
492+
493+
if not detached:
494+
builder.session.attach_session()
495+
496+
497+
def _load_detached(builder):
498+
"""
499+
Load config in new session but don't attach
500+
501+
Parameters
502+
----------
503+
builder: :class:`workspacebuilder.WorkspaceBuilder`
504+
"""
505+
builder.build()
506+
507+
if has_gte_version('2.6'): # prepare for both cases
508+
set_layout_hook(builder.session, 'client-attached')
509+
set_layout_hook(builder.session, 'client-session-changed')
510+
511+
print('Session created in detached state.')
512+
513+
514+
def _load_append_windows_to_current_session(builder):
515+
"""
516+
Load config as new windows in current session
517+
518+
Parameters
519+
----------
520+
builder: :class:`workspacebuilder.WorkspaceBuilder`
521+
"""
522+
current_attached_session = builder.find_current_attached_session()
523+
builder.build(current_attached_session, append=True)
524+
if has_gte_version('2.6'): # prepare for both cases
525+
set_layout_hook(builder.session, 'client-attached')
526+
set_layout_hook(builder.session, 'client-session-changed')
527+
528+
529+
def _setup_plugins(builder):
530+
"""
531+
Runs after before_script
532+
533+
Parameters
534+
----------
535+
builder: :class:`workspacebuilder.WorkspaceBuilder`
536+
"""
537+
for plugin in builder.plugins:
538+
plugin.before_script(builder.session)
539+
540+
return builder.session
541+
542+
465543
def load_workspace(
466544
config_file,
467545
socket_name=None,
@@ -470,6 +548,7 @@ def load_workspace(
470548
colors=None,
471549
detached=False,
472550
answer_yes=False,
551+
append=False,
473552
):
474553
"""
475554
Load a tmux "workspace" session via tmuxp file.
@@ -490,7 +569,11 @@ def load_workspace(
490569
detached : bool
491570
Force detached state. default False.
492571
answer_yes : bool
493-
Assume yes when given prompt. default False.
572+
Assume yes when given prompt to attach in new session.
573+
Default False.
574+
append : bool
575+
Assume current when given prompt to append windows in same session.
576+
Default False.
494577
495578
Notes
496579
-----
@@ -595,9 +678,8 @@ def load_workspace(
595678

596679
session_name = sconfig['session_name']
597680

598-
# if the session already exists, prompt the user to attach. tmuxp doesn't
599-
# support incremental session building or appending (yet, PR's welcome!)
600-
if builder.session_exists(session_name):
681+
# if the session already exists, prompt the user to attach
682+
if builder.session_exists(session_name) and not append:
601683
if not detached and (
602684
answer_yes
603685
or click.confirm(
@@ -610,38 +692,37 @@ def load_workspace(
610692
return
611693

612694
try:
613-
builder.build() # load tmux session via workspace builder
614-
615-
if 'TMUX' in os.environ: # tmuxp ran from inside tmux
616-
if not detached and (
617-
answer_yes or click.confirm('Already inside TMUX, switch to session?')
618-
):
619-
# unset TMUX, save it, e.g. '/tmp/tmux-1000/default,30668,0'
620-
tmux_env = os.environ.pop('TMUX')
621-
622-
if has_gte_version('2.6'):
623-
set_layout_hook(builder.session, 'client-session-changed')
624-
625-
builder.session.switch_client() # switch client to new session
695+
if detached:
696+
_load_detached(builder)
697+
return _setup_plugins(builder)
626698

627-
os.environ['TMUX'] = tmux_env # set TMUX back again
628-
return builder.session
629-
else: # session created in the background, from within tmux
630-
if has_gte_version('2.6'): # prepare for both cases
631-
set_layout_hook(builder.session, 'client-attached')
632-
set_layout_hook(builder.session, 'client-session-changed')
699+
if append:
700+
if 'TMUX' in os.environ: # tmuxp ran from inside tmux
701+
_load_append_windows_to_current_session(builder)
702+
else:
703+
_load_attached(builder, detached)
633704

634-
sys.exit('Session created in detached state.')
635-
else: # tmuxp ran from inside tmux
636-
if has_gte_version('2.6'):
637-
# if attaching for first time
638-
set_layout_hook(builder.session, 'client-attached')
705+
return _setup_plugins(builder)
639706

640-
# for cases where user switches client for first time
641-
set_layout_hook(builder.session, 'client-session-changed')
707+
# append and answer_yes have no meaning if specified together
708+
elif answer_yes:
709+
_load_attached(builder, detached)
710+
return _setup_plugins(builder)
642711

643-
if not detached:
644-
builder.session.attach_session()
712+
if 'TMUX' in os.environ: # tmuxp ran from inside tmux
713+
msg = "Already inside TMUX, switch to session? yes/no\n"\
714+
"Or (a)ppend windows in the current active session?\n[y/n/a]"
715+
options = ['y', 'n', 'a']
716+
choice = click.prompt(msg, value_proc=_validate_choices(options))
717+
718+
if choice == 'y':
719+
_load_attached(builder, detached)
720+
elif choice == 'a':
721+
_load_append_windows_to_current_session(builder)
722+
else:
723+
_load_detached(builder)
724+
else:
725+
_load_attached(builder, detached)
645726

646727
except exc.TmuxpException as e:
647728
import traceback
@@ -663,11 +744,8 @@ def load_workspace(
663744
else:
664745
sys.exit()
665746

666-
# Runs after before_script
667-
for plugin in builder.plugins:
668-
plugin.before_script(builder.session)
747+
return _setup_plugins(builder)
669748

670-
return builder.session
671749

672750

673751
@click.group(context_settings={'obj': {}})
@@ -923,6 +1001,12 @@ def command_freeze(session_name, socket_name, socket_path, force):
9231001
@click.option(
9241002
'-d', 'detached', help='Load the session without attaching it', is_flag=True
9251003
)
1004+
@click.option(
1005+
'-a',
1006+
'append',
1007+
help='Load configuration, appending windows to the current session',
1008+
is_flag=True
1009+
)
9261010
@click.option(
9271011
'colors',
9281012
'-2',
@@ -945,6 +1029,7 @@ def command_load(
9451029
new_session_name,
9461030
answer_yes,
9471031
detached,
1032+
append,
9481033
colors,
9491034
log_file,
9501035
):
@@ -983,6 +1068,7 @@ def command_load(
9831068
'answer_yes': answer_yes,
9841069
'colors': colors,
9851070
'detached': detached,
1071+
'append': append,
9861072
}
9871073

9881074
if not config:

0 commit comments

Comments
 (0)