def server(request): t = Server() t.socket_name = 'tmuxp_test%s' % next(namer) def fin(): t.kill_server() request.addfinalizer(fin) return t
def server(request, socket_name): t = Server() t.socket_name = socket_name def fin(): t.kill_server() request.addfinalizer(fin) return t
def server(request, monkeypatch): t = Server() t.socket_name = "tmuxp_test%s" % next(namer) def fin(): t.kill_server() request.addfinalizer(fin) return t
def __init__(self): self.server = Server() self.miner_session = None self.miner_layout = '' # get the miner session if exists. # we don't use exact match # the session name is like 'multiminer-zen' if self.server.has_session(SESSION_NAME, exact=False): for sess in self.server.list_sessions(): if sess['session_name'].startswith(SESSION_NAME): self.miner_session = sess self.miner_layout = sess['session_name'][len(SESSION_NAME)+1:] break
def command_freeze(session_name, socket_name, socket_path): """Import teamocil config to tmuxp format.""" t = Server( socket_name=socket_name, socket_path=socket_path, ) try: session = t.find_where({ 'session_name': session_name }) if not session: raise exc.TmuxpException('Session not found.') except exc.TmuxpException as e: print(e) return sconf = freeze(session) configparser = kaptan.Kaptan() newconfig = config.inline(sconf) configparser.import_config(newconfig) config_format = "yaml" newconfig = configparser.export( 'yaml', indent=2, default_flow_style=False, safe=True ) save_to = os.path.abspath( os.path.join( get_config_dir(), '%s.%s' % (sconf.get('session_name'), config_format) ) ) dest_prompt = save_to dest = dest_prompt dest = os.path.abspath(os.path.relpath(os.path.expanduser(dest))) destdir = os.path.dirname(dest) if not os.path.isdir(destdir): os.makedirs(destdir) buf = open(dest, 'w') buf.write(newconfig) buf.close() print('Saved to %s.' % dest)
def command_shell( session_name, window_name, socket_name, socket_path, command, shell, use_pythonrc, use_vi_mode, ): """Launch python shell for tmux server, session, window and pane. Priority given to loaded session/wndow/pane objects: - session_name and window_name arguments - current shell: environmental variable of TMUX_PANE (which gives us window and session) - ``server.attached_session``, ``session.attached_window``, ``window.attached_pane`` """ server = Server(socket_name=socket_name, socket_path=socket_path) util.raise_if_tmux_not_running(server=server) current_pane = util.get_current_pane(server=server) session = util.get_session(server=server, session_name=session_name, current_pane=current_pane) window = util.get_window(session=session, window_name=window_name, current_pane=current_pane) pane = util.get_pane(window=window, current_pane=current_pane) # NOQA: F841 if command is not None: exec(command) else: if shell == 'pdb' or (os.getenv('PYTHONBREAKPOINT') and PY3 and PYMINOR >= 7): from ._compat import breakpoint as tmuxp_breakpoint tmuxp_breakpoint() return else: from .shell import launch launch( shell=shell, use_pythonrc=use_pythonrc, # shell: code use_vi_mode=use_vi_mode, # shell: ptpython, ptipython # tmux environment / libtmux variables server=server, session=session, window=window, pane=pane, )
def main(): """Main CLI application.""" parser = get_parser() argcomplete.autocomplete(parser, always_complete_options=False) args = parser.parse_args() log_level = 'INFO' if 'log_level' in args and isinstance(args.log_level, string_types): log_level = args.log_level.upper() setup_logger(level=log_level) try: has_required_tmux_version() except exc.TmuxpException as e: logger.error(e) sys.exit() util.oh_my_zsh_auto_title() t = Server( # noqa socket_name=args.socket_name, socket_path=args.socket_path, colors=args.colors) try: if not hasattr(args, 'callback'): parser.print_help() elif args.callback is command_load: command_load(args) elif args.callback is command_convert: command_convert(args) elif args.callback is command_import_teamocil: command_import_teamocil(args) elif args.callback is command_import_tmuxinator: command_import_tmuxinator(args) elif args.callback is command_freeze: command_freeze(args) elif args.callback is command_attach_session: command_attach_session(args) elif args.callback is command_kill_session: command_kill_session(args) except KeyboardInterrupt: pass
def SessionCompleter(prefix, parsed_args, **kwargs): """Return list of session names for argcomplete completer.""" t = Server(socket_name=parsed_args.socket_name, socket_path=parsed_args.socket_path) sessions_available = [ s.get('session_name') for s in t._sessions if s.get('session_name').startswith(' '.join(prefix)) ] if parsed_args.session_name and sessions_available: return [] return [ s.get('session_name') for s in t._sessions if s.get('session_name').startswith(prefix) ]
def command_kill_session(args): """Command to kill a tmux session.""" ctext = ' '.join(args.session_name) t = Server(socket_name=args.socket_name or None, socket_path=args.socket_path or None) try: session = next( (s for s in t.sessions if s.get('session_name') == ctext), None) if not session: raise exc.TmuxpException('Session not found.') except exc.TmuxpException as e: print(e) return try: session.kill_session() print("Killed session %s." % ctext) except exc.TmuxpException as e: logger.error(e)
def command_attach_session(args): """Command to attach / switch client to a tmux session.""" ctext = ' '.join(args.session_name) t = Server(socket_name=args.socket_name, socket_path=args.socket_path, colors=args.colors) try: session = next( (s for s in t.sessions if s.get('session_name') == ctext), None) if not session: raise exc.TmuxpException('Session not found.') except exc.TmuxpException as e: print(e) return if 'TMUX' in os.environ: del os.environ['TMUX'] session.switch_client() print('Inside tmux client, switching client.') else: session.attach_session() print('Attaching client.')
class MultiMiner(object): """ multi miner class use session name suffix as the identifier for runner plan Command List start [planname] run the corresponding plan, or the default if planname not given stop stop the miner tmux session TMUX commands you might use C-b d detach the current session C-b left go to the next pane on the left C-b right (or one of these other directions) C-b up C-b down """ def __init__(self): self.server = Server() self.miner_session = None self.miner_layout = '' # get the miner session if exists. # we don't use exact match # the session name is like 'multiminer-zen' if self.server.has_session(SESSION_NAME, exact=False): for sess in self.server.list_sessions(): if sess['session_name'].startswith(SESSION_NAME): self.miner_session = sess self.miner_layout = sess['session_name'][len(SESSION_NAME)+1:] break def _miner_exist(func): def _decorator(self, *args, **kwargs): if self.miner_session is None: print("NO MINER SESSION RUNNING") return return func(self, *args, **kwargs) return _decorator @_miner_exist def miners(self): pass @_miner_exist def stop(self): try: self.server.kill_session(SESSION_NAME) print("MINER STOPPED") except: # LibTmuxException pass def start(self, target="default"): # load config file with open("./config.yaml", 'r') as stream: try: config = yaml.load(stream) # kill all miner sessions if self.server.has_session(SESSION_NAME, exact=False): self.server.kill_session(target_session=SESSION_NAME) print("Killing the running miners ...") # run new session self.miner_session = self.server.new_session( session_name=SESSION_NAME + '-' + target, kill_session=True, ) self._build_layout(target, config) except yaml.YAMLError as e: print(e) except: pass pass def _build_layout(self, target, config): runner_config = config['runners'][target] # we use only one window, the default window default_window = self.miner_session.attached_window p = None for miner_conf in runner_config: device_config = miner_conf['devices'] if device_config is None or len(device_config) == 0: # skip if no or 0 device configed continue if p is None: p = default_window.attached_pane else: # split the current window for more panes p = default_window.split_window( target=p.id, attach=True, start_directory= None, vertical=False ) default_window.select_layout('even-horizontal') # default layout default_window.server._update_panes() wallet_config = config['wallets'][miner_conf['wallet']] miner_config = config['miners'][miner_conf['miner']] # build cmd cmd = self._build_miner_cmd(miner_conf, wallet_config, miner_config, device_config) print(cmd) p.send_keys(cmd, suppress_history=True) self.server.attach_session(SESSION_NAME) def _build_miner_cmd(self, miner_conf, wallet, miner, device): cmd = '' miner_name = miner_conf['miner'] if miner_name == 'zm': # zm --server servername.com --port 1234 --user username -- dev 0 1 2 --time --color # TODO: support temperature adaptation protocol = '' if wallet['ssl'] is True: protocol = 'ssl://' cmd = '%s --server %s%s --port %d --user %s --pass %s --dev %s --time --color' % (miner['location'], protocol, wallet['server'], wallet['port'], wallet['address'], wallet['pass'], " ".join(map(str, device))) if miner_name == 'bminer': # ./bminer -uri $SCHEME://$USERNAME@$POOL -api 127.0.0.1:1880 protocol = 'stratum://' if wallet['ssl'] is True: protocol = 'stratum+ssl://' cmd = '%s -uri %s%s:%s@%s:%s -devices %s' % (miner['location'], protocol, wallet['address'], wallet['pass'], wallet['server'], wallet['port'], ",".join(map(str, device))) if miner_name == 'ethminer': # ./ethminer -P stratum+ssl://0x2f112f0f47fda00fb52493a990d49a75faed69e3.miner1@us1.ethermine.org:5555 -U protocol = 'stratum+tcp://' if wallet['ssl'] is True: protocol = 'stratum+ssl://' cmd = '%s -P %s%s:%s@%s:%s -U --cuda-devices %s' % (miner['location'], protocol, wallet['address'], wallet['pass'], wallet['server'], wallet['port'], " ".join(map(str, device))) if miner_name == 'ccminer': protocol = 'stratum+tcp://' alg = miner_conf['wallet'].split('-')[-1] if wallet['ssl'] is True: print("ccminer not support ssl") return "exit" cmd = '%s -a %s -o %s%s:%s -u %s -p %s -d %s' % (miner['location'], alg, protocol, wallet['server'], wallet['port'], wallet['address'], wallet['pass'], ",".join(map(str, device))) return cmd
def command_freeze(args): """Import teamocil config to tmuxp format.""" ctext = ' '.join(args.session_name) t = Server(socket_name=args.socket_name, socket_path=args.socket_path, colors=args.colors) try: session = t.findWhere({'session_name': ctext}) if not session: raise exc.TmuxpException('Session not found.') except exc.TmuxpException as e: print(e) return sconf = freeze(session) configparser = kaptan.Kaptan() newconfig = config.inline(sconf) configparser.import_config(newconfig) config_format = prompt_choices('Convert to', choices=['yaml', 'json'], default='yaml') if config_format == 'yaml': newconfig = configparser.export('yaml', indent=2, default_flow_style=False, safe=True) elif config_format == 'json': newconfig = configparser.export('json', indent=2) else: sys.exit('Unknown config format.') print(newconfig) print('---------------------------------------------------------------') print('Configuration import does its best to convert teamocil files.\n') if args.answer_yes or prompt_yes_no( 'The new config *WILL* require adjusting afterwards. Save config?' ): dest = None while not dest: save_to = os.path.abspath( os.path.join( config_dir, '%s.%s' % (sconf.get('session_name'), config_format))) dest_prompt = prompt('Save to: ', save_to) if os.path.exists(dest_prompt): print('%s exists. Pick a new filename.' % dest_prompt) continue dest = dest_prompt dest = os.path.abspath(os.path.relpath(os.path.expanduser(dest))) if args.answer_yes or prompt_yes_no('Save to %s?' % dest): destdir = os.path.dirname(dest) if not os.path.isdir(destdir): os.makedirs(destdir) buf = open(dest, 'w') buf.write(newconfig) buf.close() print('Saved to %s.' % dest) else: print('tmuxp has examples in JSON and YAML format at ' '<http://tmuxp.readthedocs.org/en/latest/examples.html>\n' 'View tmuxp docs at <http://tmuxp.readthedocs.org/>.') sys.exit()
class AppController(Controller): """Main application class.""" def __init__(self, common: Common, args): super().__init__(common, type(self).__name__) self._scriptdir = os.path.dirname(os.path.abspath(__file__)) self._common = common self.is_exit = False self.workdir = os.getcwd() self.file = '' self.debug_bin = "t1" self.tmux_server = None self.tmux_session = None self.tmux_pwin_idx = '' self.tmux_window_vim = None self.tmux_curr_pan_id = '' self.tmux_pan_vim = None self.tmux_pan_gdb = None self.tmux_pan_gdbserver = None self.tmux_sesname = "" self.tmux_sesid = "" self.tmux_win_def_width = 800 self.tmux_win_def_height = 600 self.layout_conf = {} self.workSpace = None self.workLayouts = {} self.helper = {} self.helper['worklayouts_loaded'] = False self.ctx_gdb = None self.ctx_gdbserver = None self.cmd_gdb = "" self.cmd_gdbserver = '' self.curr_layout = '' # self.breakpoint = Breakpoint(common) # self.cursor = Cursor(common) # self.win = Win(common, self.cursor) def _wrap_async(self, func): """ Wraps `func` so that invocation of `func(args, kwargs)` happens from the main thread. This is a requirement of pynvim API when function call happens from other threads. Related issue: https://github.com/numirias/semshi/issues/25 """ def wrapper(*args, **kwargs): return self.vim.async_call(func, *args, **kwargs) return wrapper def create_gdb_local(self, args): modelGdb = Gdb(self._common, self, self.tmux_window_vim, self.workSpace.get_pane(self.curr_layout, Common.tmux_pane_builtin_gdb), self.debug_bin, self.gdb_output) if not modelGdb: return self.models_coll[modelGdb._name] = modelGdb # self.vim.command('let g:vimgdb_gdb = ' + modelGdb._name) self.vim.vars['vimgdb_gdb'] = modelGdb._name self.tmux_server._update_windows() self.tmux_server._update_panes() def create_gdb_remote(self, args): modelGdbserver = GdbServer(self._common, self, self.tmux_window_vim, self.workSpace.get_pane(self.curr_layout, Common.tmux_pane_builtin_gdbserver), self.debug_bin, self.gdbserver_output) if not modelGdbserver: return self.models_coll[modelGdbserver._name] = modelGdbserver # self.vim.command('let g:vimgdb_gdbserver = ' + modelGdbserver._name) self.vim.vars['vimgdb_gdbserver'] = modelGdbserver._name self.tmux_server._update_windows() self.tmux_server._update_panes() def _define_vimsigns(self): # Define the sign for current line the debugged program is executing. self.vim.call('sign_define', 'GdbCurrentLine', {'text': self.vim.vars['vimgdb_sign_currentline'], 'texthl': self.vim.vars['vimgdb_sign_currentline_color']}) # Define signs for the breakpoints. breaks = self.vim.vars['vimgdb_sign_breakpoints'] for i, brk in enumerate(breaks): #sign define GdbBreakpointEn text=● texthl=Search #sign define GdbBreakpointDis text=● texthl=Function #sign define GdbBreakpointDel text=● texthl=Comment self.vim.call('sign_define', f'GdbBreakpointEn{i+1}', {'text': brk, 'texthl': self.vim.vars['vimgdb_sign_breakp_color_en']}) self.vim.call('sign_define', f'GdbBreakpointDis{i+1}', {'text': brk, 'texthl': self.vim.vars['vimgdb_sign_breakp_color_dis']}) Common.vimsign_break_max += 1 def list_layout(self): names = '' for layoutname in self.workLayouts.keys(): names += layoutname + ', ' self.vim.command(f'echomsg "VimGdb layout: `{names}`"') def load_layout_conf(self): self.conf = self._scriptdir + "/../config/default.json" if os.path.isfile(Common.vimgdb_conffile): self.conf = Common.vimgdb_conffile #self.logger.info(f"connect config={self.conf}") with open(self.conf, 'r') as f: content = f.read() #decoded_data=content.encode().decode('utf-8-sig') self.layout_conf = json.loads(content) def build_workspace(self): self.load_layout_conf() tmux_builtin_panes = {} self.helper[Common.tmux_builtin_panes] = tmux_builtin_panes tmux_builtin_panes[Common.tmux_pane_builtin_main] = '' tmux_builtin_panes[Common.tmux_pane_builtin_gdb] = Gdb.get_cmdstr(self._scriptdir, self.debug_bin) # Avoid gdbserver start too ealier, waiting gdb done. #tmux_builtin_panes[Common.tmux_pane_builtin_gdbserver] = GdbServer.get_cmdstr(self._scriptdir, self.debug_bin) tmux_builtin_panes[Common.tmux_pane_builtin_gdbserver] = '' self.workSpace = Workspace(self._common, self.layout_conf, self.helper[Common.tmux_builtin_panes], self.workdir, self.tmux_server, self.tmux_win_def_width, self.tmux_win_def_height) self.build_set_current() if self.gdbMode == GdbMode.LOCAL: self.curr_layout = Common.tmux_layout_local elif self.gdbMode == GdbMode.REMOTE: self.curr_layout = Common.tmux_layout_remote self.build_all_layout_codes() self.build_layout(self.curr_layout) def build_all_layout_codes(self): if self.workLayouts and self.helper['worklayouts_loaded']: self.logger.info(f"connect Existed and don't need create layout={self.workLayouts}") return self.helper['worklayouts_loaded'] = True self.workLayouts.update(self.workSpace.build_all_layout_codes(Common.tmux_vimgdb_session_name)) self.logger.info(f"connect layout={self.workLayouts}") def build_set_current(self): # Tmux: reuse current tmux-window, but close all other panes in current window # for only current vim is the controled vim instance. # self.tmux_window_vim = self.tmux_session.new_window( # attach=True, # do not move to the new window # window_name="VimGdb", # start_directory=self.workdir, # window_index='', # # window_shell='', #"vim " + self.file, # ) self.tmux_window_vim = self.tmux_session.attached_window; assert isinstance(self.tmux_window_vim, Window) self.tmux_window_vim['window_name'] = self.curr_layout self.tmux_pane_vim = self.tmux_window_vim.attached_pane assert isinstance(self.tmux_pane_vim, Pane) self.tmux_pane_vim['pane_name'] = Common.tmux_pane_builtin_main def build_layout(self, layout: str): self.logger.info(f"connect rebuild layout '{layout}'") self.workSpace.build_one_layout(layout, self.tmux_session, self.tmux_window_vim, self.tmux_pane_vim, Common.tmux_pane_builtin_main) def layout_select(self, layout: str): if layout not in self.workLayouts: self.vim.command(f'echomsg "VimGdb layout `{layout}` not exist, check VimGdbLayoutList()"') return # But can't create new pane #self.tmux_window_vim.select_layout(self.workLayouts[layout]['layout']) self.workSpace.build_one_layout(layout, self.tmux_session, self.tmux_window_vim, self.tmux_pane_vim, Common.tmux_pane_builtin_main) def run(self, args): os.system(f'touch {Common.vimgdb_debugfile}; truncate -s 0 {Common.vimgdb_debugfile}') self.logger.info("==============================================") self.logger.info("==============================================") self.logger.info("==============================================") self.logger.info("==============================================") self.logger.info(" *** Gdb instance ***") self.logger.info("") self.logger.info("args=%s", args) arg_n = len(args) if arg_n < 2: self.vim.command('echomsg "Gdb start fail, should: call VimGdb(\'local\', \'<bin-file>\')"') return os.system(f'touch {Common.gdb_output}; truncate -s 0 {Common.gdb_output}') os.system(f'touch {Common.gdbserver_output}; truncate -s 0 {Common.gdbserver_output}') os.system(f'touch {Common.vimqf_backtrace}; truncate -s 0 {Common.vimqf_backtrace}') os.system(f'touch {Common.vimqf_breakpoint}; truncate -s 0 {Common.vimqf_breakpoint}') os.system(f'touch {Common.gdb_tmp_break}; truncate -s 0 {Common.gdb_tmp_break}') os.system(f'touch {Common.gdb_file_infolocal}; truncate -s 0 {Common.gdb_file_infolocal}') self.gdbMode = args[0] self.gdbArgs = args[1] # 't1 dut:8888 -u admin -p "" -t "gdb:trace"' chunks = re.split(' +', self.gdbArgs) if chunks: self.debug_bin = chunks[0] self.logger.info(f"Gdb starting '{self.debug_bin}' with {chunks[1:]} ...") else: self.debug_bin = self.gdbArgs self.logger.info(f"Gdb starting '{self.debug_bin}' ...") # let s:dir = expand('<sfile>:p:h') self.vim.command('let g:vimgdb_file = expand("%:p")') self.file = self.vim.eval('g:vimgdb_file') if len(self.file) < 1: self.vim.command('echomsg "Gdb start fail, no current file"') return tmux_info = subprocess.check_output( ['tmux', 'display-message', '-p', '#S;#{session_id};#{window_width};#{window_height};#{window_index};#{pane_id}']) tmux_info = tmux_info.decode() [self.tmux_sesname, self.tmux_sesid, self.tmux_win_def_width, self.tmux_win_def_height, self.tmux_pwin_idx, self.tmux_curr_pan_id] = tmux_info.strip().split(';') # option controller: kill other pane of current tmux window subprocess.check_output(['tmux', 'kill-pane', '-a', '-t', self.tmux_curr_pan_id]) self.logger.info(f"Tmux: #{self.tmux_sesid} '{self.tmux_sesname}' {self.tmux_win_def_width}x{self.tmux_win_def_height} cwd='{self.workdir}'") self.tmux_server = Server() self.tmux_session = self.tmux_server.get_by_id(self.tmux_sesid) self.build_workspace() self.vim.funcs.VimGdbInit() self._define_vimsigns() # Create model Cursor: _model = Cursor(self._common, self) if not _model: return self.models_coll[_model._name] = _model # Create model Breakpoint: _model = Breakpoint(self._common, self) if not _model: return self.models_coll[_model._name] = _model # Create view MainVimWin: _view = Win(self._common, self) if not _view: return self.views_coll[_view._name] = _view self.logger.info(f"VimGdb mode={self.gdbMode}", ) if self.gdbMode == GdbMode.LOCAL or self.gdbMode == GdbMode.REMOTE: self.create_gdb_local(args) if self.gdbMode == GdbMode.REMOTE: self.create_gdb_remote(args) ##self.tmux_window_vim.select_layout('main-horizontal') #self.tmux_window_vim.select_layout('main-vertical') # focus backto vim self.tmux_pane_vim.select_pane() # monitor all outfile if Common.tailModeSubprocess: self.logger.info("Start subprocess(tail -f) ...") t1 = threading.Thread(target=self.tail_files) #t1.setDaemon(True) t1.start() return
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 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 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 command_freeze(session_name, socket_name, socket_path): """Snapshot a session into a config. If SESSION_NAME is provided, snapshot that session. Otherwise, use the current session.""" t = Server(socket_name=socket_name, socket_path=socket_path) try: if session_name: session = t.find_where({'session_name': session_name}) else: session = t.list_sessions()[0] if not session: raise exc.TmuxpException('Session not found.') except exc.TmuxpException as e: print(e) return sconf = freeze(session) configparser = kaptan.Kaptan() newconfig = config.inline(sconf) configparser.import_config(newconfig) config_format = click.prompt( 'Convert to', value_proc=_validate_choices(['yaml', 'json']), default='yaml' ) if config_format == 'yaml': newconfig = configparser.export( 'yaml', indent=2, default_flow_style=False, safe=True ) elif config_format == 'json': newconfig = configparser.export('json', indent=2) else: sys.exit('Unknown config format.') print(newconfig) print( '---------------------------------------------------------------' '\n' 'Freeze does it best to snapshot live tmux sessions.\n' ) if click.confirm( 'The new config *WILL* require adjusting afterwards. Save config?' ): dest = None while not dest: save_to = os.path.abspath( os.path.join( get_config_dir(), '%s.%s' % (sconf.get('session_name'), config_format), ) ) dest_prompt = click.prompt( 'Save to: %s' % save_to, value_proc=get_abs_path, default=save_to, confirmation_prompt=True, ) if os.path.exists(dest_prompt): print('%s exists. Pick a new filename.' % dest_prompt) continue dest = dest_prompt dest = os.path.abspath(os.path.relpath(os.path.expanduser(dest))) if click.confirm('Save to %s?' % dest): destdir = os.path.dirname(dest) if not os.path.isdir(destdir): os.makedirs(destdir) buf = open(dest, 'w') buf.write(newconfig) buf.close() print('Saved to %s.' % dest) else: print( 'tmuxp has examples in JSON and YAML format at ' '<http://tmuxp.readthedocs.io/en/latest/examples.html>\n' 'View tmuxp docs at <http://tmuxp.readthedocs.io/>.' ) sys.exit()
def command_freeze(args): """Import teamocil config to tmuxp format.""" ctext = ' '.join(args.session_name) t = Server( socket_name=args.socket_name, socket_path=args.socket_path, colors=args.colors ) try: session = t.find_where({ 'session_name': ctext }) if not session: raise exc.TmuxpException('Session not found.') except exc.TmuxpException as e: print(e) return sconf = freeze(session) configparser = kaptan.Kaptan() newconfig = config.inline(sconf) configparser.import_config(newconfig) config_format = prompt_choices('Convert to', choices=[ 'yaml', 'json'], default='yaml') if config_format == 'yaml': newconfig = configparser.export( 'yaml', indent=2, default_flow_style=False, safe=True ) elif config_format == 'json': newconfig = configparser.export('json', indent=2) else: sys.exit('Unknown config format.') print(newconfig) print( '---------------------------------------------------------------') print( 'Configuration import does its best to convert teamocil files.\n') if args.answer_yes or prompt_yes_no( 'The new config *WILL* require adjusting afterwards. Save config?' ): dest = None while not dest: save_to = os.path.abspath( os.path.join( config_dir, '%s.%s' % (sconf.get('session_name'), config_format) ) ) dest_prompt = prompt('Save to: ', save_to) if os.path.exists(dest_prompt): print('%s exists. Pick a new filename.' % dest_prompt) continue dest = dest_prompt dest = os.path.abspath(os.path.relpath(os.path.expanduser(dest))) if args.answer_yes or prompt_yes_no('Save to %s?' % dest): destdir = os.path.dirname(dest) if not os.path.isdir(destdir): os.makedirs(destdir) buf = open(dest, 'w') buf.write(newconfig) buf.close() print('Saved to %s.' % dest) else: print( 'tmuxp has examples in JSON and YAML format at ' '<http://tmuxp.readthedocs.io/en/latest/examples.html>\n' 'View tmuxp docs at <http://tmuxp.readthedocs.io/>.' ) sys.exit()
def command_freeze( session_name, socket_name, config_format, save_to, socket_path, yes, quiet, force ): """Snapshot a session into a config. If SESSION_NAME is provided, snapshot that session. Otherwise, use the current session.""" t = Server(socket_name=socket_name, socket_path=socket_path) try: if session_name: session = t.find_where({"session_name": session_name}) else: session = util.get_session(t) if not session: raise exc.TmuxpException("Session not found.") except exc.TmuxpException as e: print(e) return sconf = freeze(session) configparser = kaptan.Kaptan() newconfig = config.inline(sconf) configparser.import_config(newconfig) if not quiet: print( "---------------------------------------------------------------" "\n" "Freeze does its best to snapshot live tmux sessions.\n" ) if not ( yes or click.confirm( "The new config *WILL* require adjusting afterwards. Save config?" ) ): if not quiet: print( "tmuxp has examples in JSON and YAML format at " "<http://tmuxp.git-pull.com/examples.html>\n" "View tmuxp docs at <http://tmuxp.git-pull.com/>." ) sys.exit() dest = save_to while not dest: save_to = os.path.abspath( os.path.join( get_config_dir(), "{}.{}".format(sconf.get("session_name"), config_format or "yaml"), ) ) dest_prompt = click.prompt( "Save to: %s" % save_to, value_proc=get_abs_path, default=save_to ) if not force and os.path.exists(dest_prompt): print("%s exists. Pick a new filename." % dest_prompt) continue dest = dest_prompt dest = os.path.abspath(os.path.relpath(os.path.expanduser(dest))) if config_format is None: valid_config_formats = ["json", "yaml"] _, config_format = os.path.splitext(dest) config_format = config_format[1:].lower() if config_format not in valid_config_formats: config_format = click.prompt( "Couldn't ascertain one of [%s] from file name. Convert to" % ", ".join(valid_config_formats), value_proc=_validate_choices(["yaml", "json"]), default="yaml", ) if config_format == "yaml": newconfig = configparser.export( "yaml", indent=2, default_flow_style=False, safe=True ) elif config_format == "json": newconfig = configparser.export("json", indent=2) if yes or click.confirm("Save to %s?" % dest): destdir = os.path.dirname(dest) if not os.path.isdir(destdir): os.makedirs(destdir) buf = open(dest, "w") buf.write(newconfig) buf.close() if not quiet: print("Saved to %s." % dest)
def session_completion(ctx, params, incomplete): t = Server() choices = [session.name for session in t.list_sessions()] return sorted([str(c) for c in choices if str(c).startswith(incomplete)])
def run(self, args): os.system(f'touch {Common.vimgdb_debugfile}; truncate -s 0 {Common.vimgdb_debugfile}') self.logger.info("==============================================") self.logger.info("==============================================") self.logger.info("==============================================") self.logger.info("==============================================") self.logger.info(" *** Gdb instance ***") self.logger.info("") self.logger.info("args=%s", args) arg_n = len(args) if arg_n < 2: self.vim.command('echomsg "Gdb start fail, should: call VimGdb(\'local\', \'<bin-file>\')"') return os.system(f'touch {Common.gdb_output}; truncate -s 0 {Common.gdb_output}') os.system(f'touch {Common.gdbserver_output}; truncate -s 0 {Common.gdbserver_output}') os.system(f'touch {Common.vimqf_backtrace}; truncate -s 0 {Common.vimqf_backtrace}') os.system(f'touch {Common.vimqf_breakpoint}; truncate -s 0 {Common.vimqf_breakpoint}') os.system(f'touch {Common.gdb_tmp_break}; truncate -s 0 {Common.gdb_tmp_break}') os.system(f'touch {Common.gdb_file_infolocal}; truncate -s 0 {Common.gdb_file_infolocal}') self.gdbMode = args[0] self.gdbArgs = args[1] # 't1 dut:8888 -u admin -p "" -t "gdb:trace"' chunks = re.split(' +', self.gdbArgs) if chunks: self.debug_bin = chunks[0] self.logger.info(f"Gdb starting '{self.debug_bin}' with {chunks[1:]} ...") else: self.debug_bin = self.gdbArgs self.logger.info(f"Gdb starting '{self.debug_bin}' ...") # let s:dir = expand('<sfile>:p:h') self.vim.command('let g:vimgdb_file = expand("%:p")') self.file = self.vim.eval('g:vimgdb_file') if len(self.file) < 1: self.vim.command('echomsg "Gdb start fail, no current file"') return tmux_info = subprocess.check_output( ['tmux', 'display-message', '-p', '#S;#{session_id};#{window_width};#{window_height};#{window_index};#{pane_id}']) tmux_info = tmux_info.decode() [self.tmux_sesname, self.tmux_sesid, self.tmux_win_def_width, self.tmux_win_def_height, self.tmux_pwin_idx, self.tmux_curr_pan_id] = tmux_info.strip().split(';') # option controller: kill other pane of current tmux window subprocess.check_output(['tmux', 'kill-pane', '-a', '-t', self.tmux_curr_pan_id]) self.logger.info(f"Tmux: #{self.tmux_sesid} '{self.tmux_sesname}' {self.tmux_win_def_width}x{self.tmux_win_def_height} cwd='{self.workdir}'") self.tmux_server = Server() self.tmux_session = self.tmux_server.get_by_id(self.tmux_sesid) self.build_workspace() self.vim.funcs.VimGdbInit() self._define_vimsigns() # Create model Cursor: _model = Cursor(self._common, self) if not _model: return self.models_coll[_model._name] = _model # Create model Breakpoint: _model = Breakpoint(self._common, self) if not _model: return self.models_coll[_model._name] = _model # Create view MainVimWin: _view = Win(self._common, self) if not _view: return self.views_coll[_view._name] = _view self.logger.info(f"VimGdb mode={self.gdbMode}", ) if self.gdbMode == GdbMode.LOCAL or self.gdbMode == GdbMode.REMOTE: self.create_gdb_local(args) if self.gdbMode == GdbMode.REMOTE: self.create_gdb_remote(args) ##self.tmux_window_vim.select_layout('main-horizontal') #self.tmux_window_vim.select_layout('main-vertical') # focus backto vim self.tmux_pane_vim.select_pane() # monitor all outfile if Common.tailModeSubprocess: self.logger.info("Start subprocess(tail -f) ...") t1 = threading.Thread(target=self.tail_files) #t1.setDaemon(True) t1.start() return
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 server(): t = Server() t.socket_name = 'tmuxp_test%s' % next(namer) return t
def command_shell(session_name, window_name, socket_name, socket_path, command): """Launch python shell for tmux server, session, window and pane. Priority given to loaded session/wndow/pane objects: - session_name and window_name arguments - current shell: environmental variable of TMUX_PANE (which gives us window and session) - ``server.attached_session``, ``session.attached_window``, ``window.attached_pane`` """ server = Server(socket_name=socket_name, socket_path=socket_path) try: server.sessions except LibTmuxException as e: if 'No such file or directory' in str(e): raise LibTmuxException( 'no tmux session found. Start a tmux session and try again. \n' 'Original error: ' + str(e) ) else: raise e current_pane = None if os.getenv('TMUX_PANE') is not None: try: current_pane = [ p for p in server._list_panes() if p.get('pane_id') == os.getenv('TMUX_PANE') ][0] except IndexError: pass try: if session_name: session = server.find_where({'session_name': session_name}) elif current_pane is not None: session = server.find_where({'session_id': current_pane['session_id']}) else: session = server.list_sessions()[0] if not session: raise exc.TmuxpException('Session not found: %s' % session_name) except exc.TmuxpException as e: print(e) return try: if window_name: window = session.find_where({'window_name': window_name}) if not window: raise exc.TmuxpException('Window not found: %s' % window_name) elif current_pane is not None: window = session.find_where({'window_id': current_pane['window_id']}) else: window = session.list_windows()[0] except exc.TmuxpException as e: print(e) return try: if current_pane is not None: pane = window.find_where({'pane_id': current_pane['pane_id']}) # NOQA: F841 else: pane = window.attached_pane # NOQA: F841 except exc.TmuxpException as e: print(e) return if command is not None: exec(command) else: from ._compat import breakpoint as tmuxp_breakpoint tmuxp_breakpoint()
def command_freeze(session_name, socket_name, socket_path): """Snapshot a session into a config. If SESSION_NAME is provided, snapshot that session. Otherwise, use the current session.""" t = Server(socket_name=socket_name, socket_path=socket_path) try: if session_name: session = t.find_where({'session_name': session_name}) else: session = t.list_sessions()[0] if not session: raise exc.TmuxpException('Session not found.') except exc.TmuxpException as e: print(e) return sconf = freeze(session) configparser = kaptan.Kaptan() newconfig = config.inline(sconf) configparser.import_config(newconfig) config_format = click.prompt( 'Convert to', value_proc=_validate_choices(['yaml', 'json']), default='yaml' ) if config_format == 'yaml': newconfig = configparser.export( 'yaml', indent=2, default_flow_style=False, safe=True ) elif config_format == 'json': newconfig = configparser.export('json', indent=2) else: sys.exit('Unknown config format.') print(newconfig) print( '---------------------------------------------------------------' '\n' 'Freeze does it best to snapshot live tmux sessions.\n' ) if click.confirm( 'The new config *WILL* require adjusting afterwards. Save config?' ): dest = None while not dest: save_to = os.path.abspath( os.path.join( get_config_dir(), '%s.%s' % (sconf.get('session_name'), config_format), ) ) dest_prompt = click.prompt( 'Save to: %s' % save_to, value_proc=get_abs_path, default=save_to ) if os.path.exists(dest_prompt): print('%s exists. Pick a new filename.' % dest_prompt) continue dest = dest_prompt dest = os.path.abspath(os.path.relpath(os.path.expanduser(dest))) if click.confirm('Save to %s?' % dest): destdir = os.path.dirname(dest) if not os.path.isdir(destdir): os.makedirs(destdir) buf = open(dest, 'w') buf.write(newconfig) buf.close() print('Saved to %s.' % dest) else: print( 'tmuxp has examples in JSON and YAML format at ' '<http://tmuxp.readthedocs.io/en/latest/examples.html>\n' 'View tmuxp docs at <http://tmuxp.readthedocs.io/>.' ) sys.exit()