def command_debug_info(): """ Print debug info to submit with Issues. """ def prepend_tab(strings): """ Prepend tab to strings in list. """ return list(map(lambda x: '\t%s' % x, strings)) def output_break(): """ Generate output break. """ return '-' * 25 def format_tmux_resp(std_resp): """ Format tmux command response for tmuxp stdout. """ return '\n'.join([ '\n'.join(prepend_tab(std_resp.stdout)), click.style('\n'.join(prepend_tab(std_resp.stderr)), fg='red'), ]) output = [ output_break(), 'environment:\n%s' % '\n'.join( prepend_tab([ 'dist: %s' % platform.platform(), 'arch: %s' % platform.machine(), 'uname: %s' % '; '.join(platform.uname()[:3]), 'version: %s' % platform.version(), ])), output_break(), 'python version: %s' % ' '.join(sys.version.split('\n')), 'system PATH: %s' % os.environ['PATH'], 'tmux version: %s' % get_version(), 'libtmux version: %s' % libtmux_version, 'tmuxp version: %s' % __version__, 'tmux path: %s' % which('tmux'), 'tmuxp path: %s' % tmuxp_path, 'shell: %s' % os.environ['SHELL'], output_break(), 'tmux sessions:\n%s' % format_tmux_resp(tmux_cmd('list-sessions')), 'tmux windows:\n%s' % format_tmux_resp(tmux_cmd('list-windows')), 'tmux panes:\n%s' % format_tmux_resp(tmux_cmd('list-panes')), 'tmux global options:\n%s' % format_tmux_resp(tmux_cmd('show-options', '-g')), 'tmux window options:\n%s' % format_tmux_resp(tmux_cmd('show-window-options', '-g')), ] tmuxp_echo('\n'.join(output))
def command_debug_info(): """ Print debug info to submit with Issues. """ def prepend_tab(strings): """ Prepend tab to strings in list. """ return list(map(lambda x: "\t%s" % x, strings)) def output_break(): """ Generate output break. """ return "-" * 25 def format_tmux_resp(std_resp): """ Format tmux command response for tmuxp stdout. """ return "\n".join([ "\n".join(prepend_tab(std_resp.stdout)), click.style("\n".join(prepend_tab(std_resp.stderr)), fg="red"), ]) output = [ output_break(), "environment:\n%s" % "\n".join( prepend_tab([ "dist: %s" % platform.platform(), "arch: %s" % platform.machine(), "uname: %s" % "; ".join(platform.uname()[:3]), "version: %s" % platform.version(), ])), output_break(), "python version: %s" % " ".join(sys.version.split("\n")), "system PATH: %s" % os.environ["PATH"], "tmux version: %s" % get_version(), "libtmux version: %s" % libtmux_version, "tmuxp version: %s" % __version__, "tmux path: %s" % which("tmux"), "tmuxp path: %s" % tmuxp_path, "shell: %s" % os.environ["SHELL"], output_break(), "tmux sessions:\n%s" % format_tmux_resp(tmux_cmd("list-sessions")), "tmux windows:\n%s" % format_tmux_resp(tmux_cmd("list-windows")), "tmux panes:\n%s" % format_tmux_resp(tmux_cmd("list-panes")), "tmux global options:\n%s" % format_tmux_resp(tmux_cmd("show-options", "-g")), "tmux window options:\n%s" % format_tmux_resp(tmux_cmd("show-window-options", "-g")), ] tmuxp_echo("\n".join(output))
def load_workspace( config_file, socket_name=None, socket_path=None, colors=None, detached=False, answer_yes=False, ): """ Load a tmux "workspace" session via tmuxp file. Parameters ---------- config_file : str absolute path to config file socket_name : str, optional ``tmux -L <socket-name>`` socket_path: str, optional ``tmux -S <socket-path>`` colors : str, optional '-2' Force tmux to support 256 colors detached : bool Force detached state. default False. answer_yes : bool Assume yes when given prompt. default False. Notes ----- tmuxp will check and load a configuration file. The file will use kaptan to load a JSON/YAML into a :py:obj:`dict`. Then :func:`config.expand` and :func:`config.trickle` will be used to expand any shorthands, template variables, or file paths relative to where the config/script is executed from. :func:`config.expand` accepts the directory of the config file, so the user's configuration can resolve absolute paths relative to where the config file is. In otherwords, if a config file at */var/moo/hi.yaml* has *./* in its configs, we want to be sure any file path with *./* is relative to */var/moo*, not the user's PWD. A :class:`libtmux.Server` object is created. No tmux server is started yet, just the object. The prepared configuration and the server object is passed into an instance of :class:`~tmuxp.workspacebuilder.WorkspaceBuilder`. A sanity check against :meth:`libtmux.common.which` is ran. It will raise an exception if tmux isn't found. If a tmux session under the same name as ``session_name`` in the tmuxp configuration exists, tmuxp offers to attach the session. Currently, tmuxp does not allow appending a workspace / incremental building on top of a current session (pull requests are welcome!). :meth:`~tmuxp.workspacebuilder.WorkspaceBuilder.build` will build the session in the background via using tmux's detached state (``-d``). After the session (workspace) is built, unless the user decided to load the session in the background via ``tmuxp -d`` (which is in the spirit of tmux's ``-d``), we need to prompt the user to attach the session. If the user is already inside a tmux client, which we detect via the ``TMUX`` environment variable bring present, we will prompt the user to switch their current client to it. If they're outside of tmux client - in a plain-old PTY - we will automatically ``attach``. If an exception is raised during the building of the workspace, tmuxp will prompt to cleanup (``$ tmux kill-session``) the session on the user's behalf. An exception raised during this process means it's not easy to predict how broken the session is. .. versionchanged:: tmux 2.6+ In tmux 2.6, the way layout and proportion's work when interfacing with tmux in a detached state (outside of a client) changed. Since tmuxp builds workspaces in a detached state, the WorkspaceBuilder isn't able to rely on functionality requiring awarness of session geometry, e.g. ``set-layout``. Thankfully, tmux is able to defer commands to run after the user performs certain actions, such as loading a client via ``attach-session`` or ``switch-client``. Upon client switch, ``client-session-changed`` is triggered [1]_. References ---------- .. [1] cmd-switch-client.c hook. GitHub repo for tmux. https://github.com/tmux/tmux/blob/2.6/cmd-switch-client.c#L132. Accessed April 8th, 2018. """ # here we ensure COLS and LINES is set for percentage config try: curses.initscr() finally: curses.endwin() # get the canonical path, eliminating any symlinks config_file = os.path.realpath(config_file) # kaptan allows us to open a yaml or json file as a dict sconfig = kaptan.Kaptan() sconfig = sconfig.import_config(config_file).get() # shapes configurations relative to config / profile file location sconfig = config.expand(sconfig, os.path.dirname(config_file)) # propagate config inheritance (e.g. session -> window, window -> pane) sconfig = config.trickle(sconfig) t = Server( # create tmux server object socket_name=socket_name, socket_path=socket_path, colors=colors ) which('tmux') # raise exception if tmux not found try: # load WorkspaceBuilder object for tmuxp config / tmux server builder = WorkspaceBuilder(sconf=sconfig, server=t) except exc.EmptyConfigException: click.echo('%s is empty or parsed no config data' % config_file, err=True) return session_name = sconfig['session_name'] # if the session already exists, prompt the user to attach. tmuxp doesn't # support incremental session building or appending (yet, PR's welcome!) if builder.session_exists(session_name): if not detached and ( answer_yes or click.confirm( '%s is already running. Attach?' % click.style(session_name, fg='green'), default=True, ) ): _reattach(builder.session) return try: click.echo( click.style('[Loading] ', fg='green') + click.style(config_file, fg='blue', bold=True) ) builder.build() # load tmux session via workspace builder if 'TMUX' in os.environ: # tmuxp ran from inside tmux if not detached and ( answer_yes or click.confirm('Already inside TMUX, switch to session?') ): # unset TMUX, save it, e.g. '/tmp/tmux-1000/default,30668,0' tmux_env = os.environ.pop('TMUX') if has_gte_version('2.6'): set_layout_hook(builder.session, 'client-session-changed') builder.session.switch_client() # switch client to new session os.environ['TMUX'] = tmux_env # set TMUX back again return builder.session else: # session created in the background, from within tmux if has_gte_version('2.6'): # prepare for both cases set_layout_hook(builder.session, 'client-attached') set_layout_hook(builder.session, 'client-session-changed') sys.exit('Session created in detached state.') else: # tmuxp ran from inside tmux if has_gte_version('2.6'): # if attaching for first time set_layout_hook(builder.session, 'client-attached') # for cases where user switches client for first time set_layout_hook(builder.session, 'client-session-changed') if not detached: builder.session.attach_session() except exc.TmuxpException as e: import traceback click.echo(traceback.format_exc(), err=True) click.echo(e, err=True) choice = click.prompt( 'Error loading workspace. (k)ill, (a)ttach, (d)etach?', value_proc=_validate_choices(['k', 'a', 'd']), default='k', ) if choice == 'k': builder.session.kill_session() click.echo('Session killed.') elif choice == 'a': if 'TMUX' in os.environ: builder.session.switch_client() else: builder.session.attach_session() else: sys.exit() return builder.session
def test_which_no_bin_found(): assert which('top') assert which('top', default_paths=[]) assert not which('top', default_paths=[], append_env_path=False) assert not which('top', default_paths=['/'], append_env_path=False)
def test_which_no_tmuxp_found(monkeypatch): monkeypatch.setenv("PATH", "/") which('tmuxp') which('tmuxp', '/')
def load_workspace(config_file, args): """Build config workspace. :param config_file: full path to config file :param type: string """ sconfig = kaptan.Kaptan() sconfig = sconfig.import_config(config_file).get() # expands configurations relative to config / profile file location sconfig = config.expand(sconfig, os.path.dirname(config_file)) sconfig = config.trickle(sconfig) t = Server(socket_name=args.socket_name, socket_path=args.socket_path, colors=args.colors) try: builder = WorkspaceBuilder(sconf=sconfig, server=t) except exc.EmptyConfigException: logger.error('%s is empty or parsed no config data' % config_file) return which('tmux') try: logger.info('Loading %s.' % config_file) builder.build() if 'TMUX' in os.environ: if not args.detached and ( args.answer_yes or prompt_yes_no('Already inside TMUX, switch to session?')): tmux_env = os.environ.pop('TMUX') builder.session.switch_client() os.environ['TMUX'] = tmux_env return else: sys.exit('Session created in detached state.') if not args.detached: builder.session.attach_session() except exc.TmuxSessionExists as e: if not args.detached and (args.answer_yes or prompt_yes_no('%s Attach?' % e)): if 'TMUX' in os.environ: builder.session.switch_client() else: builder.session.attach_session() return except exc.TmuxpException as e: import traceback print(traceback.format_exc()) logger.error(e) choice = prompt_choices( 'Error loading workspace. (k)ill, (a)ttach, (d)etach?', choices=['k', 'a', 'd'], default='k') if choice == 'k': builder.session.kill_session() print('Session killed.') elif choice == 'a': if 'TMUX' in os.environ: builder.session.switch_client() else: builder.session.attach_session() else: sys.exit()
def load_workspace(config_file, socket_name=None, socket_path=None, colors=None, attached=None, detached=None, answer_yes=False): """Build config workspace. :param config_file: full path to config file :param type: str """ # get the canonical path, eliminating any symlinks config_file = os.path.realpath(config_file) sconfig = kaptan.Kaptan() sconfig = sconfig.import_config(config_file).get() # expands configurations relative to config / profile file location sconfig = config.expand(sconfig, os.path.dirname(config_file)) sconfig = config.trickle(sconfig) t = Server(socket_name=socket_name, socket_path=socket_path, colors=colors) try: builder = WorkspaceBuilder(sconf=sconfig, server=t) except exc.EmptyConfigException: click.echo('%s is empty or parsed no config data' % config_file, err=True) return which('tmux') def reattach(session): if 'TMUX' in os.environ: session.switch_client() else: session.attach_session() session_name = sconfig['session_name'] if builder.session_exists(session_name): if not detached and (answer_yes or click.confirm( '%s is already running. Attach?' % click.style(session_name, fg='green'), default=True)): reattach(builder.session) return try: click.echo( click.style('[Loading] ', fg='green') + click.style(config_file, fg='blue', bold=True)) builder.build() if 'TMUX' in os.environ: # tmuxp ran from inside tmux if not detached and ( answer_yes or click.confirm('Already inside TMUX, switch to session?')): tmux_env = os.environ.pop('TMUX') if has_gte_version('2.6'): # if using -d from inside tmux session + switching inside # https://github.com/tmux/tmux/blob/2.6/cmd-switch-client.c#L132 set_layout_hook(builder.session, 'client-session-changed') builder.session.switch_client() os.environ['TMUX'] = tmux_env return builder.session else: # session created in the background, from within tmux if has_gte_version('2.6'): # prepare for both cases set_layout_hook(builder.session, 'client-attached') set_layout_hook(builder.session, 'client-session-changed') sys.exit('Session created in detached state.') # below: tmuxp ran outside of tmux if has_gte_version('2.6'): # if attaching for first time set_layout_hook(builder.session, 'client-attached') # for cases where user switches client for first time set_layout_hook(builder.session, 'client-session-changed') if not detached: builder.session.attach_session() except exc.TmuxpException as e: import traceback click.echo(traceback.format_exc(), err=True) click.echo(e, err=True) choice = click.prompt( 'Error loading workspace. (k)ill, (a)ttach, (d)etach?', value_proc=_validate_choices(['k', 'a', 'd']), default='k') if choice == 'k': builder.session.kill_session() click.echo('Session killed.') elif choice == 'a': if 'TMUX' in os.environ: builder.session.switch_client() else: builder.session.attach_session() else: sys.exit() return builder.session
def load_workspace(config_file, socket_name=None, socket_path=None, colors=None, attached=None, detached=None, answer_yes=False): """Build config workspace. :param config_file: full path to config file :param type: string """ sconfig = kaptan.Kaptan() sconfig = sconfig.import_config(config_file).get() # expands configurations relative to config / profile file location sconfig = config.expand(sconfig, os.path.dirname(config_file)) sconfig = config.trickle(sconfig) t = Server(socket_name=socket_name, socket_path=socket_path, colors=colors) try: builder = WorkspaceBuilder(sconf=sconfig, server=t) except exc.EmptyConfigException: click.echo('%s is empty or parsed no config data' % config_file, err=True) return which('tmux') def reattach(session): if 'TMUX' in os.environ: session.switch_client() else: session.attach_session() session_name = sconfig['session_name'] if builder.session_exists(session_name): if not detached and (answer_yes or click.confirm( '%s is already running. Attach?' % click.style(session_name, fg='green'), default=True)): reattach(builder.session) return try: click.echo( click.style('[Loading] ', fg='green') + click.style(config_file, fg='blue', bold=True)) builder.build() if 'TMUX' in os.environ: if not detached and ( answer_yes or click.confirm('Already inside TMUX, switch to session?')): tmux_env = os.environ.pop('TMUX') builder.session.switch_client() os.environ['TMUX'] = tmux_env return builder.session else: sys.exit('Session created in detached state.') if not detached: builder.session.attach_session() except exc.TmuxpException as e: import traceback click.echo(traceback.format_exc(), err=True) click.echo(e, err=True) choice = click.prompt( 'Error loading workspace. (k)ill, (a)ttach, (d)etach?', value_proc=_validate_choices(['k', 'a', 'd']), default='k') if choice == 'k': builder.session.kill_session() click.echo('Session killed.') elif choice == 'a': if 'TMUX' in os.environ: builder.session.switch_client() else: builder.session.attach_session() else: sys.exit() return builder.session
def load_workspace( config_file, socket_name=None, socket_path=None, colors=None, detached=False, answer_yes=False, ): """ Load a tmux "workspace" session via tmuxp file. Parameters ---------- config_file : str absolute path to config file socket_name : str, optional ``tmux -L <socket-name>`` socket_path: str, optional ``tmux -S <socket-path>`` colors : str, optional '-2' Force tmux to support 256 colors detached : bool Force detached state. default False. answer_yes : bool Assume yes when given prompt. default False. Notes ----- tmuxp will check and load a configuration file. The file will use kaptan to load a JSON/YAML into a :py:obj:`dict`. Then :func:`config.expand` and :func:`config.trickle` will be used to expand any shorthands, template variables, or file paths relative to where the config/script is executed from. :func:`config.expand` accepts the directory of the config file, so the user's configuration can resolve absolute paths relative to where the config file is. In otherwords, if a config file at */var/moo/hi.yaml* has *./* in its configs, we want to be sure any file path with *./* is relative to */var/moo*, not the user's PWD. A :class:`libtmux.Server` object is created. No tmux server is started yet, just the object. The prepared configuration and the server object is passed into an instance of :class:`~tmuxp.workspacebuilder.WorkspaceBuilder`. A sanity check against :meth:`libtmux.common.which` is ran. It will raise an exception if tmux isn't found. If a tmux session under the same name as ``session_name`` in the tmuxp configuration exists, tmuxp offers to attach the session. Currently, tmuxp does not allow appending a workspace / incremental building on top of a current session (pull requests are welcome!). :meth:`~tmuxp.workspacebuilder.WorkspaceBuilder.build` will build the session in the background via using tmux's detached state (``-d``). After the session (workspace) is built, unless the user decided to load the session in the background via ``tmuxp -d`` (which is in the spirit of tmux's ``-d``), we need to prompt the user to attach the session. If the user is already inside a tmux client, which we detect via the ``TMUX`` environment variable bring present, we will prompt the user to switch their current client to it. If they're outside of tmux client - in a plain-old PTY - we will automatically ``attach``. If an exception is raised during the building of the workspace, tmuxp will prompt to cleanup (``$ tmux kill-session``) the session on the user's behalf. An exception raised during this process means it's not easy to predict how broken the session is. .. versionchanged:: tmux 2.6+ In tmux 2.6, the way layout and proportion's work when interfacing with tmux in a detached state (outside of a client) changed. Since tmuxp builds workspaces in a detached state, the WorkspaceBuilder isn't able to rely on functionality requiring awarness of session geometry, e.g. ``set-layout``. Thankfully, tmux is able to defer commands to run after the user performs certain actions, such as loading a client via ``attach-session`` or ``switch-client``. Upon client switch, ``client-session-changed`` is triggered [1]_. References ---------- .. [1] cmd-switch-client.c hook. GitHub repo for tmux. https://github.com/tmux/tmux/blob/2.6/cmd-switch-client.c#L132. Accessed April 8th, 2018. """ # get the canonical path, eliminating any symlinks config_file = os.path.realpath(config_file) # kaptan allows us to open a yaml or json file as a dict sconfig = kaptan.Kaptan() sconfig = sconfig.import_config(config_file).get() # shapes configurations relative to config / profile file location sconfig = config.expand(sconfig, os.path.dirname(config_file)) # propagate config inheritance (e.g. session -> window, window -> pane) sconfig = config.trickle(sconfig) t = Server( # create tmux server object socket_name=socket_name, socket_path=socket_path, colors=colors ) which('tmux') # raise exception if tmux not found try: # load WorkspaceBuilder object for tmuxp config / tmux server builder = WorkspaceBuilder(sconf=sconfig, server=t) except exc.EmptyConfigException: click.echo('%s is empty or parsed no config data' % config_file, err=True) return session_name = sconfig['session_name'] # if the session already exists, prompt the user to attach. tmuxp doesn't # support incremental session building or appending (yet, PR's welcome!) if builder.session_exists(session_name): if not detached and ( answer_yes or click.confirm( '%s is already running. Attach?' % click.style(session_name, fg='green'), default=True, ) ): _reattach(builder.session) return try: click.echo( click.style('[Loading] ', fg='green') + click.style(config_file, fg='blue', bold=True) ) builder.build() # load tmux session via workspace builder if 'TMUX' in os.environ: # tmuxp ran from inside tmux if not detached and ( answer_yes or click.confirm('Already inside TMUX, switch to session?') ): # unset TMUX, save it, e.g. '/tmp/tmux-1000/default,30668,0' tmux_env = os.environ.pop('TMUX') if has_gte_version('2.6'): set_layout_hook(builder.session, 'client-session-changed') builder.session.switch_client() # switch client to new session os.environ['TMUX'] = tmux_env # set TMUX back again return builder.session else: # session created in the background, from within tmux if has_gte_version('2.6'): # prepare for both cases set_layout_hook(builder.session, 'client-attached') set_layout_hook(builder.session, 'client-session-changed') sys.exit('Session created in detached state.') else: # tmuxp ran from inside tmux if has_gte_version('2.6'): # if attaching for first time set_layout_hook(builder.session, 'client-attached') # for cases where user switches client for first time set_layout_hook(builder.session, 'client-session-changed') if not detached: builder.session.attach_session() except exc.TmuxpException as e: import traceback click.echo(traceback.format_exc(), err=True) click.echo(e, err=True) choice = click.prompt( 'Error loading workspace. (k)ill, (a)ttach, (d)etach?', value_proc=_validate_choices(['k', 'a', 'd']), default='k', ) if choice == 'k': builder.session.kill_session() click.echo('Session killed.') elif choice == 'a': if 'TMUX' in os.environ: builder.session.switch_client() else: builder.session.attach_session() else: sys.exit() return builder.session
def load_workspace(config_file, args): """Build config workspace. :param config_file: full path to config file :param type: string """ sconfig = kaptan.Kaptan() sconfig = sconfig.import_config(config_file).get() # expands configurations relative to config / profile file location sconfig = config.expand(sconfig, os.path.dirname(config_file)) sconfig = config.trickle(sconfig) t = Server( socket_name=args.socket_name, socket_path=args.socket_path, colors=args.colors ) try: builder = WorkspaceBuilder(sconf=sconfig, server=t) except exc.EmptyConfigException: logger.error('%s is empty or parsed no config data' % config_file) return which('tmux') try: logger.info('Loading %s.' % config_file) builder.build() if 'TMUX' in os.environ: if not args.detached and (args.answer_yes or prompt_yes_no( 'Already inside TMUX, switch to session?' )): tmux_env = os.environ.pop('TMUX') builder.session.switch_client() os.environ['TMUX'] = tmux_env return else: sys.exit('Session created in detached state.') if not args.detached: builder.session.attach_session() except exc.TmuxSessionExists as e: if not args.detached and ( args.answer_yes or prompt_yes_no('%s Attach?' % e) ): if 'TMUX' in os.environ: builder.session.switch_client() else: builder.session.attach_session() return except exc.TmuxpException as e: import traceback print(traceback.format_exc()) logger.error(e) choice = prompt_choices( 'Error loading workspace. (k)ill, (a)ttach, (d)etach?', choices=['k', 'a', 'd'], default='k' ) if choice == 'k': builder.session.kill_session() print('Session killed.') elif choice == 'a': if 'TMUX' in os.environ: builder.session.switch_client() else: builder.session.attach_session() else: sys.exit()
def load_workspace( config_file, socket_name=None, socket_path=None, colors=None, attached=None, detached=None, answer_yes=False ): """Build config workspace. :param config_file: full path to config file :param type: string """ sconfig = kaptan.Kaptan() sconfig = sconfig.import_config(config_file).get() # expands configurations relative to config / profile file location sconfig = config.expand(sconfig, os.path.dirname(config_file)) sconfig = config.trickle(sconfig) t = Server( socket_name=socket_name, socket_path=socket_path, colors=colors ) try: builder = WorkspaceBuilder(sconf=sconfig, server=t) except exc.EmptyConfigException: click.echo('%s is empty or parsed no config data' % config_file, err=True) return which('tmux') def reattach(session): if 'TMUX' in os.environ: session.switch_client() else: session.attach_session() session_name = sconfig['session_name'] if builder.session_exists(session_name): if not detached and ( answer_yes or click.confirm( '%s is already running. Attach?' % click.style(session_name, fg='green'), default=True) ): reattach(builder.session) return try: click.echo( click.style('[Loading] ', fg='green') + click.style(config_file, fg='blue', bold=True)) builder.build() if 'TMUX' in os.environ: if not detached and (answer_yes or click.confirm( 'Already inside TMUX, switch to session?' )): tmux_env = os.environ.pop('TMUX') builder.session.switch_client() os.environ['TMUX'] = tmux_env return builder.session else: sys.exit('Session created in detached state.') if not detached: builder.session.attach_session() except exc.TmuxpException as e: import traceback click.echo(traceback.format_exc(), err=True) click.echo(e, err=True) choice = click.prompt( 'Error loading workspace. (k)ill, (a)ttach, (d)etach?', value_proc=_validate_choices(['k', 'a', 'd']), default='k' ) if choice == 'k': builder.session.kill_session() click.echo('Session killed.') elif choice == 'a': if 'TMUX' in os.environ: builder.session.switch_client() else: builder.session.attach_session() else: sys.exit() return builder.session
def load_workspace( config_file, socket_name=None, socket_path=None, tmux_config_file=None, new_session_name=None, colors=None, detached=False, answer_yes=False, append=False, ): """ Load a tmux "workspace" session via tmuxp file. Parameters ---------- config_file : str absolute path to config file socket_name : str, optional ``tmux -L <socket-name>`` socket_path: str, optional ``tmux -S <socket-path>`` new_session_name: str, options ``tmux new -s <new_session_name>`` colors : str, optional '-2' Force tmux to support 256 colors detached : bool Force detached state. default False. answer_yes : bool Assume yes when given prompt to attach in new session. Default False. append : bool Assume current when given prompt to append windows in same session. Default False. Notes ----- tmuxp will check and load a configuration file. The file will use kaptan to load a JSON/YAML into a :py:obj:`dict`. Then :func:`config.expand` and :func:`config.trickle` will be used to expand any shorthands, template variables, or file paths relative to where the config/script is executed from. :func:`config.expand` accepts the directory of the config file, so the user's configuration can resolve absolute paths relative to where the config file is. In otherwords, if a config file at */var/moo/hi.yaml* has *./* in its configs, we want to be sure any file path with *./* is relative to */var/moo*, not the user's PWD. A :class:`libtmux.Server` object is created. No tmux server is started yet, just the object. The prepared configuration and the server object is passed into an instance of :class:`~tmuxp.workspacebuilder.WorkspaceBuilder`. A sanity check against :meth:`libtmux.common.which` is ran. It will raise an exception if tmux isn't found. If a tmux session under the same name as ``session_name`` in the tmuxp configuration exists, tmuxp offers to attach the session. Currently, tmuxp does not allow appending a workspace / incremental building on top of a current session (pull requests are welcome!). :meth:`~tmuxp.workspacebuilder.WorkspaceBuilder.build` will build the session in the background via using tmux's detached state (``-d``). After the session (workspace) is built, unless the user decided to load the session in the background via ``tmuxp -d`` (which is in the spirit of tmux's ``-d``), we need to prompt the user to attach the session. If the user is already inside a tmux client, which we detect via the ``TMUX`` environment variable bring present, we will prompt the user to switch their current client to it. If they're outside of tmux client - in a plain-old PTY - we will automatically ``attach``. If an exception is raised during the building of the workspace, tmuxp will prompt to cleanup (``$ tmux kill-session``) the session on the user's behalf. An exception raised during this process means it's not easy to predict how broken the session is. .. versionchanged:: tmux 2.6+ In tmux 2.6, the way layout and proportion's work when interfacing with tmux in a detached state (outside of a client) changed. Since tmuxp builds workspaces in a detached state, the WorkspaceBuilder isn't able to rely on functionality requiring awarness of session geometry, e.g. ``set-layout``. Thankfully, tmux is able to defer commands to run after the user performs certain actions, such as loading a client via ``attach-session`` or ``switch-client``. Upon client switch, ``client-session-changed`` is triggered [1]_. References ---------- .. [1] cmd-switch-client.c hook. GitHub repo for tmux. https://github.com/tmux/tmux/blob/2.6/cmd-switch-client.c#L132. Accessed April 8th, 2018. """ # get the canonical path, eliminating any symlinks config_file = os.path.realpath(config_file) tmuxp_echo( click.style("[Loading] ", fg="green") + click.style(config_file, fg="blue", bold=True)) # kaptan allows us to open a yaml or json file as a dict sconfig = kaptan.Kaptan() sconfig = sconfig.import_config(config_file).get() # shapes configurations relative to config / profile file location sconfig = config.expand(sconfig, os.path.dirname(config_file)) # Overwrite session name if new_session_name: sconfig["session_name"] = new_session_name # propagate config inheritance (e.g. session -> window, window -> pane) sconfig = config.trickle(sconfig) t = Server( # create tmux server object socket_name=socket_name, socket_path=socket_path, config_file=tmux_config_file, colors=colors, ) which("tmux") # raise exception if tmux not found try: # load WorkspaceBuilder object for tmuxp config / tmux server builder = WorkspaceBuilder(sconf=sconfig, plugins=load_plugins(sconfig), server=t) except exc.EmptyConfigException: tmuxp_echo("%s is empty or parsed no config data" % config_file, err=True) return session_name = sconfig["session_name"] # if the session already exists, prompt the user to attach if builder.session_exists(session_name) and not append: if not detached and (answer_yes or click.confirm( "%s is already running. Attach?" % click.style(session_name, fg="green"), default=True, )): _reattach(builder) return try: if detached: _load_detached(builder) return _setup_plugins(builder) if append: if "TMUX" in os.environ: # tmuxp ran from inside tmux _load_append_windows_to_current_session(builder) else: _load_attached(builder, detached) return _setup_plugins(builder) # append and answer_yes have no meaning if specified together elif answer_yes: _load_attached(builder, detached) return _setup_plugins(builder) if "TMUX" in os.environ: # tmuxp ran from inside tmux msg = ( "Already inside TMUX, switch to session? yes/no\n" "Or (a)ppend windows in the current active session?\n[y/n/a]") options = ["y", "n", "a"] choice = click.prompt(msg, value_proc=_validate_choices(options)) if choice == "y": _load_attached(builder, detached) elif choice == "a": _load_append_windows_to_current_session(builder) else: _load_detached(builder) else: _load_attached(builder, detached) except exc.TmuxpException as e: import traceback tmuxp_echo(traceback.format_exc(), err=True) tmuxp_echo(e, err=True) choice = click.prompt( "Error loading workspace. (k)ill, (a)ttach, (d)etach?", value_proc=_validate_choices(["k", "a", "d"]), default="k", ) if choice == "k": builder.session.kill_session() tmuxp_echo("Session killed.") elif choice == "a": _reattach(builder) else: sys.exit() return _setup_plugins(builder)
def test_which_no_bin_found(): assert which("top") assert which("top", default_paths=[]) assert not which("top", default_paths=[], append_env_path=False) assert not which("top", default_paths=["/"], append_env_path=False)