"""WizardKit: tmux Functions""" # vim: sts=2 sw=2 ts=2 import logging import pathlib from wk.exe import run_program from wk.std import PLATFORM # STATIC_VARIABLES LOG = logging.getLogger(__name__) # Functions def capture_pane(pane_id=None): """Capture text from current or target pane, returns str.""" cmd = ['tmux', 'capture-pane', '-p'] if pane_id: cmd.extend(['-t', pane_id]) # Capture and return proc = run_program(cmd, check=False) return proc.stdout.strip() def clear_pane(pane_id=None): """Clear pane buffer for current or target pane.""" cmd = ['tmux', 'send-keys', '-R'] if pane_id: cmd.extend(['-t', pane_id]) # Clear pane run_program(cmd, check=False) def fix_layout(panes, layout, forced=False): """Fix pane sizes based on layout.""" if not (forced or layout_needs_fixed(panes, layout)): # Layout should be fine return # Update panes for name, data in layout.items(): # Skip missing panes if name not in panes: continue # Resize pane(s) pane_list = panes[name] if isinstance(pane_list, str): pane_list = [pane_list] for pane_id in pane_list: if name == 'Current': pane_id = None try: resize_pane(pane_id, **data) except RuntimeError: # Assuming pane was closed just before resizing pass def get_pane_size(pane_id=None): """Get current or target pane size, returns tuple.""" cmd = ['tmux', 'display', '-p'] if pane_id: cmd.extend(['-t', pane_id]) cmd.append('#{pane_width} #{pane_height}') # Get resolution proc = run_program(cmd, check=False) width, height = proc.stdout.strip().split() width = int(width) height = int(height) # Done return (width, height) def kill_all_panes(pane_id=None): """Kill all panes except for the current or target pane.""" cmd = ['tmux', 'kill-pane', '-a'] if pane_id: cmd.extend(['-t', pane_id]) # Kill run_program(cmd, check=False) def kill_pane(*pane_ids): """Kill pane(s) by id.""" cmd = ['tmux', 'kill-pane', '-t'] # Iterate over all passed pane IDs for pane_id in pane_ids: run_program(cmd+[pane_id], check=False) def layout_needs_fixed(panes, layout): """Check if layout needs fixed, returns bool.""" needs_fixed = False # Check panes for name, data in layout.items(): # Skip unpredictably sized panes if not data.get('Check', False): continue # Skip missing panes if name not in panes: continue # Check pane size(s) pane_list = panes[name] if isinstance(pane_list, str): pane_list = [pane_list] for pane_id in pane_list: try: width, height = get_pane_size(pane_id) except ValueError: # Pane may have disappeared during this loop continue if data.get('width', False) and data['width'] != width: needs_fixed = True if data.get('height', False) and data['height'] != height: needs_fixed = True # Done return needs_fixed def poll_pane(pane_id): """Check if pane exists, returns bool.""" cmd = ['tmux', 'list-panes', '-F', '#D'] # Get list of panes proc = run_program(cmd, check=False) existant_panes = proc.stdout.splitlines() # Check if pane exists return pane_id in existant_panes def prep_action( cmd=None, working_dir=None, text=None, watch_file=None, watch_cmd='cat'): """Prep action to perform during a tmux call, returns list. This will prep for running a basic command, displaying text on screen, or monitoring a file. The last option uses cat by default but can be overridden by using the watch_cmd. """ action_cmd = [] if working_dir: action_cmd.extend(['-c', working_dir]) if cmd: # Basic command action_cmd.append(cmd) elif text: # Display text echo_cmd = ['echo'] if PLATFORM == 'Linux': echo_cmd.append('-e') action_cmd.extend([ 'watch', '--color', '--exec', '--no-title', '--interval', '1', ]) action_cmd.extend(echo_cmd) action_cmd.append(text) elif watch_file: # Monitor file prep_file(watch_file) if watch_cmd == 'cat': action_cmd.extend([ 'watch', '--color', '--no-title', '--interval', '1', 'cat', ]) elif watch_cmd == 'tail': action_cmd.extend(['tail', '-f']) action_cmd.append(watch_file) else: LOG.error('No action specified') raise RuntimeError('No action specified') # Done return action_cmd def prep_file(path): """Check if file exists and create empty file if not.""" path = pathlib.Path(path).resolve() try: path.touch(exist_ok=False) except FileExistsError: # Leave existing files alone pass def resize_pane(pane_id=None, width=None, height=None, **kwargs): """Resize current or target pane. NOTE: kwargs is only here to make calling this function easier by dropping any extra kwargs passed. """ cmd = ['tmux', 'resize-pane'] # Safety checks if not (width or height): LOG.error('Neither width nor height specified') raise RuntimeError('Neither width nor height specified') # Finish building cmd if pane_id: cmd.extend(['-t', pane_id]) if width: cmd.extend(['-x', str(width)]) if height: cmd.extend(['-y', str(height)]) # Resize run_program(cmd, check=False) def split_window( lines=None, percent=None, behind=False, vertical=False, target_id=None, **action): """Split tmux window, run action, and return pane_id as str.""" cmd = ['tmux', 'split-window', '-d', '-PF', '#D'] # Safety checks if not (lines or percent): LOG.error('Neither lines nor percent specified') raise RuntimeError('Neither lines nor percent specified') # New pane placement if behind: cmd.append('-b') if vertical: cmd.append('-v') else: cmd.append('-h') if target_id: cmd.extend(['-t', target_id]) # New pane size if lines: cmd.extend(['-l', str(lines)]) elif percent: cmd.extend(['-p', str(percent)]) # New pane action cmd.extend(prep_action(**action)) # Run and return pane_id proc = run_program(cmd, check=False) return proc.stdout.strip() def respawn_pane(pane_id, **action): """Respawn pane with action.""" cmd = ['tmux', 'respawn-pane', '-k', '-t', pane_id] cmd.extend(prep_action(**action)) # Respawn run_program(cmd, check=False) def zoom_pane(pane_id=None): """Toggle zoom status for current or target pane.""" cmd = ['tmux', 'resize-pane', '-Z'] if pane_id: cmd.extend(['-t', pane_id]) # Toggle run_program(cmd, check=False) if __name__ == '__main__': print("This file is not meant to be called directly.")