def _feed_cli_with_input(text, editing_mode=EditingMode.EMACS, clipboard=None, history=None): """ Create a CommandLineInterface, feed it with the given user input and return the CLI object. This returns a (result, CLI) tuple. """ # If the given text doesn't end with a newline, the interface won't finish. assert text.endswith('\n') loop = PosixEventLoop() try: inp = PipeInput() inp.send_text(text) cli = CommandLineInterface( application=Application( buffer=Buffer(accept_action=AcceptAction.RETURN_DOCUMENT, history=history), editing_mode=editing_mode, clipboard=clipboard or InMemoryClipboard(), key_bindings_registry=KeyBindingManager.for_prompt().registry, ), eventloop=loop, input=inp, output=DummyOutput()) result = cli.run() return result, cli finally: loop.close() inp.close()
def feed_app_with_input(type, message, text, **kwargs): """ Create a CommandLineInterface, feed it with the given user input and return the CLI object. This returns a (result, CLI) tuple. note: this only works if you import your prompt and then this function!! """ # If the given text doesn't end with a newline, the interface won't finish. assert text.endswith('\n') application = getattr(prompts, type).question(message, **kwargs) loop = PosixEventLoop() try: inp = PipeInput() inp.send_text(text) cli = CommandLineInterface( application=application, eventloop=loop, input=inp, output=DummyOutput()) result = cli.run() return result, cli finally: loop.close() inp.close()
def _feed_cli_with_input(text, editing_mode=EditingMode.EMACS, clipboard=None, history=None): """ Create a CommandLineInterface, feed it with the given user input and return the CLI object. This returns a (result, CLI) tuple. """ # If the given text doesn't end with a newline, the interface won't finish. assert text.endswith('\n') from prompt_toolkit.eventloop.posix import PosixEventLoop as EventLoop loop = EventLoop() try: inp = PipeInput() inp.send_text(text) cli = CommandLineInterface(application=Application( buffer=Buffer(accept_action=AcceptAction.RETURN_DOCUMENT, history=history), editing_mode=editing_mode, clipboard=clipboard or InMemoryClipboard(), key_bindings_registry=KeyBindingManager.for_prompt().registry, ), eventloop=loop, input=inp, output=DummyOutput()) result = cli.run() return result, cli finally: loop.close() inp.close()
def __init__(self, source_file=None, startup_command=None): self.arrangement = Arrangement() self.layout_manager = LayoutManager(self) self._client_states = weakref.WeakKeyDictionary( ) # Mapping from CLI to ClientState. # Options self.enable_mouse_support = True self.enable_status = True self.enable_pane_status = False self.enable_bell = True self.remain_on_exit = False self.status_keys_vi_mode = False self.mode_keys_vi_mode = False self.history_limit = 2000 self.status_interval = 4 self.default_terminal = 'xterm-256color' self.status_left = '[#S] ' self.status_left_length = 20 self.status_right = ' %H:%M %d-%b-%y ' self.status_right_length = 20 self.window_status_current_format = '#I:#W#F' self.window_status_format = '#I:#W#F' self.session_name = '0' self.status_justify = Justify.LEFT self.default_shell = get_default_shell() self.options = ALL_OPTIONS self.window_options = ALL_WINDOW_OPTIONS # When no panes are available. self.original_cwd = os.getcwd() self.display_pane_numbers = False #: List of clients. self._runs_standalone = False self.connections = [] self.clis = {} # Mapping from Connection to CommandLineInterface. self._startup_done = False self.source_file = source_file self.startup_command = startup_command # Keep track of all the panes, by ID. (For quick lookup.) self.panes_by_id = weakref.WeakValueDictionary() # Socket information. self.socket = None self.socket_name = None # Create eventloop. self.eventloop = PosixEventLoop() # Key bindings manager. self.key_bindings_manager = KeyBindingsManager(self) self.style = PymuxStyle()
def __init__(self, source_file=None, startup_command=None): self.arrangement = Arrangement() self.layout_manager = LayoutManager(self) self._client_states = weakref.WeakKeyDictionary() # Mapping from CLI to ClientState. # Options self.enable_mouse_support = True self.enable_status = True self.enable_pane_status = False self.enable_bell = True self.remain_on_exit = False self.status_keys_vi_mode = False self.mode_keys_vi_mode = False self.history_limit = 2000 self.status_interval = 4 self.default_terminal = 'xterm-256color' self.status_left = '[#S] ' self.status_left_length = 20 self.status_right = ' %H:%M %d-%b-%y ' self.status_right_length = 20 self.window_status_current_format = '#I:#W#F' self.window_status_format = '#I:#W#F' self.session_name = '0' self.status_justify = Justify.LEFT self.default_shell = get_default_shell() self.options = ALL_OPTIONS self.window_options = ALL_WINDOW_OPTIONS # When no panes are available. self.original_cwd = os.getcwd() self.display_pane_numbers = False #: List of clients. self._runs_standalone = False self.connections = [] self.clis = {} # Mapping from Connection to CommandLineInterface. self._startup_done = False self.source_file = source_file self.startup_command = startup_command # Keep track of all the panes, by ID. (For quick lookup.) self.panes_by_id = weakref.WeakValueDictionary() # Socket information. self.socket = None self.socket_name = None # Create eventloop. self.eventloop = PosixEventLoop() # Key bindings manager. self.key_bindings_manager = KeyBindingsManager(self) self.style = PymuxStyle()
def _feed_cli_with_input(text, editing_mode=EditingMode.EMACS): """ Create a CommandLineInterface, feed it with the given user input and return the CLI object. This returns a (result, CLI) tuple. """ # If the given text doesn't end with a newline, the interface won't finish. assert text.endswith('\n') loop = PosixEventLoop() try: inp = PipeInput() inp.send_text(text) cli = CommandLineInterface( application=Application(editing_mode=editing_mode), eventloop=loop, input=inp, output=DummyOutput()) result = cli.run() return result, cli finally: loop.close() inp.close()
def run(): if not PTK3: if is_windows(): from prompt_toolkit.eventloop.win32 import Win32EventLoop loop = Win32EventLoop() else: from prompt_toolkit.eventloop.posix import PosixEventLoop from prompt_toolkit.eventloop.select import SelectSelector loop = PosixEventLoop(selector=SelectSelector) set_event_loop(loop) if not sys.stdin.isatty(): pager = Pager.from_pipe() pager.run() else: parser = argparse.ArgumentParser( description='Browse through a text file.') parser.add_argument('filename', metavar='filename', nargs='+', help='The file to be displayed.') parser.add_argument('--vi', help='Prefer Vi key bindings.', action='store_true') parser.add_argument('--emacs', help='Prefer Emacs key bindings.', action='store_true') args = parser.parse_args() # Determine input mode. vi_mode = 'vi' in os.environ.get('EDITOR', '').lower() if args.vi: vi_mode = True if args.emacs: vi_mode = False pager = Pager(vi_mode=vi_mode) # Open files. for filename in args.filename: # When a filename is given, take a lexer from that filename. lexer = PygmentsLexer.from_filename(filename, sync_from_start=False) pager.add_source(FileSource(filename, lexer=lexer)) # Run UI. pager.run()
class Pymux(object): """ The main Pymux application class. Usage: p = Pymux() p.listen_on_socket() p.run_server() Or: p = Pymux() p.run_standalone() """ def __init__(self, source_file=None, startup_command=None): self.arrangement = Arrangement() self.layout_manager = LayoutManager(self) self._client_states = weakref.WeakKeyDictionary( ) # Mapping from CLI to ClientState. # Options self.enable_mouse_support = True self.enable_status = True self.enable_bell = True self.remain_on_exit = False self.status_keys_vi_mode = False self.mode_keys_vi_mode = False self.history_limit = 2000 self.default_terminal = 'xterm-256color' self.status_left = '[#S] ' self.status_left_length = 20 self.status_right = ' %H:%M %d-%b-%y ' self.status_right_length = 20 self.window_status_current_format = '#I:#W#F' self.window_status_format = '#I:#W#F' self.session_name = '0' self.status_justify = Justify.LEFT self.default_shell = get_default_shell() self.options = ALL_OPTIONS self.window_options = ALL_WINDOW_OPTIONS # When no panes are available. self.original_cwd = os.getcwd() self.display_pane_numbers = False #: List of clients. self._runs_standalone = False self.connections = [] self.clis = {} # Mapping from Connection to CommandLineInterface. self._startup_done = False self.source_file = source_file self.startup_command = startup_command # Keep track of all the panes, by ID. (For quick lookup.) self.panes_by_id = weakref.WeakValueDictionary() # Socket information. self.socket = None self.socket_name = None # Create eventloop. self.eventloop = PosixEventLoop() # Key bindings manager. self.key_bindings_manager = KeyBindingsManager(self) self.style = PymuxStyle() def get_client_state(self, cli): """ Return the ClientState instance for this CommandLineInterface. """ try: return self._client_states[cli] except KeyError: s = ClientState() self._client_states[cli] = s return s def get_title(self, cli): """ The title to be displayed in the titlebar of the terminal. """ w = self.arrangement.get_active_window(cli) if w and w.active_process: title = w.active_process.screen.title else: title = '' if title: return '%s - Pymux' % (title, ) else: return 'Pymux' def get_window_size(self, cli): """ Get the size to be used for the DynamicBody. This will be the smallest size of all clients. """ get_active_window = self.arrangement.get_active_window active_window = get_active_window(cli) # Get connections watching the same window. connections = [ c for c in self.connections if c.cli and get_active_window(c.cli) == active_window ] rows = [c.size.rows for c in connections] columns = [c.size.columns for c in connections] if self._runs_standalone: r, c = _get_size(sys.stdout.fileno()) rows.append(r) columns.append(c) if rows and columns: return Size(rows=min(rows) - (1 if self.enable_status else 0), columns=min(columns)) else: return Size(rows=20, columns=80) def _create_pane(self, window=None, command=None, start_directory=None): """ Create a new :class:`pymux.arrangement.Pane` instance. (Don't put it in a window yet.) :param window: If a window is given, take the CWD of the current process of that window as the start path for this pane. :param command: If given, run this command instead of `self.default_shell`. :param start_directory: If given, use this as the CWD. """ assert window is None or isinstance(window, Window) assert command is None or isinstance(command, six.text_type) assert start_directory is None or isinstance(start_directory, six.text_type) def done_callback(): " When the process finishes. " if not self.remain_on_exit: # Remove pane from layout. self.arrangement.remove_pane(pane) # No panes left? -> Quit. if not self.arrangement.has_panes: self.eventloop.stop() self.invalidate() def bell(): " Sound bell on all clients. " if self.enable_bell: for c in self.clis.values(): c.output.bell() # Start directory. if start_directory: path = start_directory elif window and window.active_process: # When the path of the active process is known, # start the new process at the same location. path = window.active_process.get_cwd() else: path = None def before_exec(): " Called in the process fork (in the child process). " # Go to this directory. try: os.chdir(path or self.original_cwd) except OSError: pass # No such file or directory. # Set terminal variable. (We emulate xterm.) os.environ['TERM'] = self.default_terminal # Make sure to set the PYMUX environment variable. if self.socket_name: os.environ['PYMUX'] = '%s,%i' % (self.socket_name, pane.pane_id) if command: command = command.split() else: command = [self.default_shell] # Create process and pane. def has_priority(): return self.arrangement.pane_has_priority(pane) process = Process.from_command(self.eventloop, self.invalidate, command, done_callback, bell_func=bell, before_exec_func=before_exec, has_priority=has_priority) pane = Pane(process) # Keep track of panes. This is a WeakKeyDictionary, we only add, but # don't remove. self.panes_by_id[pane.pane_id] = pane logger.info('Created process %r.', command) process.start() return pane def invalidate(self): " Invalidate the UI for all clients. " for c in self.clis.values(): c.invalidate() def create_window(self, cli=None, command=None, start_directory=None, name=None): """ Create a new :class:`pymux.arrangement.Window` in the arrangement. :param cli: If been given, this window will be focussed for that client. """ assert cli is None or isinstance(cli, CommandLineInterface) assert command is None or isinstance(command, six.text_type) assert start_directory is None or isinstance(start_directory, six.text_type) pane = self._create_pane(None, command, start_directory=start_directory) self.arrangement.create_window(cli, pane, name=name) self.invalidate() def add_process(self, cli, command=None, vsplit=False, start_directory=None): """ Add a new process to the current window. (vsplit/hsplit). """ assert isinstance(cli, CommandLineInterface) assert command is None or isinstance(command, six.text_type) assert start_directory is None or isinstance(start_directory, six.text_type) window = self.arrangement.get_active_window(cli) pane = self._create_pane(window, command, start_directory=start_directory) window.add_pane(pane, vsplit=vsplit) self.invalidate() def kill_pane(self, pane): """ Kill the given pane, and remove it from the arrangement. """ assert isinstance(pane, Pane) # Send kill signal. if not pane.process.is_terminated: pane.process.send_signal(signal.SIGKILL) # Remove from layout. self.arrangement.remove_pane(pane) # No panes left? -> Quit. if not self.arrangement.has_panes: self.eventloop.stop() def leave_command_mode(self, cli, append_to_history=False): """ Leave the command/prompt mode. """ cli.buffers[COMMAND].reset(append_to_history=append_to_history) cli.buffers[PROMPT].reset(append_to_history=True) client_state = self.get_client_state(cli) client_state.command_mode = False client_state.prompt_command = '' client_state.confirm_command = '' def handle_command(self, cli, command): """ Handle command from the command line. """ handle_command(self, cli, command) def show_message(self, cli, message): """ Set a warning message. This will be shown at the bottom until a key has been pressed. :param cli: CommandLineInterface instance. (The client.) :param message: String. """ self.get_client_state(cli).message = message def create_cli(self, connection, output, input=None): """ Create `CommandLineInterface` instance for this connection. """ def get_title(): return self.get_title(cli) def on_focus_changed(): """ When the focus changes to a read/write buffer, make sure to go to insert mode. This happens when the ViState was set to NAVIGATION in the copy buffer. """ vi_state = self.key_bindings_manager.pt_key_bindings_manager.get_vi_state( cli) if cli.current_buffer.read_only(): vi_state.input_mode = InputMode.NAVIGATION else: vi_state.input_mode = InputMode.INSERT application = Application( layout=self.layout_manager.layout, key_bindings_registry=self.key_bindings_manager.registry, buffers=_BufferMapping(self), mouse_support=Condition(lambda cli: self.enable_mouse_support), use_alternate_screen=True, style=self.style, get_title=get_title) cli = CommandLineInterface(application=application, output=output, input=input, eventloop=self.eventloop) # Set render postpone time. (.1 instead of 0). # This small change ensures that if for a split second a process # outputs a lot of information, we don't give the highest priority to # rendering output. (Nobody reads that fast in real-time.) cli.max_render_postpone_time = .1 # Second. # Hide message when a key has been pressed. def key_pressed(): self.get_client_state(cli).message = None cli.input_processor.beforeKeyPress += key_pressed cli._is_running = True self.clis[connection] = cli # Redraw all CLIs. (Adding a new client could mean that the others # change size, so everything has to be redrawn.) self.invalidate() cli.on_invalidate += lambda: self.invalidate() # Handle start-up comands. # (Does initial key bindings.) if not self._startup_done: self._startup_done = True # Execute default config. for cmd in STARTUP_COMMANDS.splitlines(): self.handle_command(cli, cmd) # Source the given file. if self.source_file: call_command_handler('source-file', self, cli, [self.source_file]) # Make sure that there is one window created. self.create_window(cli, command=self.startup_command) return cli def get_connection_for_cli(self, cli): """ Return the `CommandLineInterface` instance for this connection, if any. `None` otherwise. """ for connection, c in self.clis.items(): if c == cli: return connection def detach_client(self, cli): """ Detach the client that belongs to this CLI. """ connection = self.get_connection_for_cli(cli) if connection is not None: connection.detach_and_close() # Redraw all clients -> Maybe their size has to change. self.invalidate() def listen_on_socket(self, socket_name=None): """ Listen for clients on a Unix socket. Returns the socket name. """ if self.socket is None: self.socket_name, self.socket = bind_socket(socket_name) self.socket.listen(0) self.eventloop.add_reader(self.socket.fileno(), self._socket_accept) # Set session_name according to socket name. if '.' in self.socket_name: self.session_name = self.socket_name.rsplit('.')[-1] logger.info('Listening on %r.' % self.socket_name) return self.socket_name def _socket_accept(self): """ Accept connection from client. """ logger.info('Client attached.') connection, client_address = self.socket.accept() # Note: We don't have to put this socket in non blocking mode. # This can cause crashes when sending big packets on OS X. connection = ServerConnection(self, connection, client_address) self.connections.append(connection) def run_server(self): # Ignore keyboard. (When people run "pymux server" and press Ctrl-C.) # Pymux has to be terminated by termining all the processes running in # its panes. def handle_sigint(*a): print('Ignoring keyboard interrupt.') signal.signal(signal.SIGINT, handle_sigint) # Run eventloop. # XXX: Both the PipeInput and DummyCallbacks are not used. # This is a workaround to run the PosixEventLoop continuously # without having a CommandLineInterface instance. # A better API in prompt_toolkit is desired. try: self.eventloop.run(PipeInput(), DummyCallbacks()) except: # When something bad happens, always dump the traceback. # (Otherwise, when running as a daemon, and stdout/stderr are not # available, it's hard to see what went wrong.) fd, path = tempfile.mkstemp(prefix='pymux.crash-') logger.fatal( 'Pymux has crashed, dumping traceback to {0}'.format(path)) os.write(fd, traceback.format_exc().encode('utf-8')) os.close(fd) raise # Clean up socket. os.remove(self.socket_name) def run_standalone(self, true_color=False): """ Run pymux standalone, rather than using a client/server architecture. This is mainly useful for debugging. """ self._runs_standalone = True cli = self.create_cli(connection=None, output=Vt100_Output.from_pty( sys.stdout, true_color=true_color)) cli._is_running = False cli.run()
class Pymux(object): """ The main Pymux application class. Usage: p = Pymux() p.listen_on_socket() p.run_server() Or: p = Pymux() p.run_standalone() """ def __init__(self, source_file=None, startup_command=None): self.arrangement = Arrangement() self.layout_manager = LayoutManager(self) self._client_states = weakref.WeakKeyDictionary() # Mapping from CLI to ClientState. # Options self.enable_mouse_support = True self.enable_status = True self.enable_pane_status = False self.enable_bell = True self.remain_on_exit = False self.status_keys_vi_mode = False self.mode_keys_vi_mode = False self.history_limit = 2000 self.status_interval = 4 self.default_terminal = 'xterm-256color' self.status_left = '[#S] ' self.status_left_length = 20 self.status_right = ' %H:%M %d-%b-%y ' self.status_right_length = 20 self.window_status_current_format = '#I:#W#F' self.window_status_format = '#I:#W#F' self.session_name = '0' self.status_justify = Justify.LEFT self.default_shell = get_default_shell() self.options = ALL_OPTIONS self.window_options = ALL_WINDOW_OPTIONS # When no panes are available. self.original_cwd = os.getcwd() self.display_pane_numbers = False #: List of clients. self._runs_standalone = False self.connections = [] self.clis = {} # Mapping from Connection to CommandLineInterface. self._startup_done = False self.source_file = source_file self.startup_command = startup_command # Keep track of all the panes, by ID. (For quick lookup.) self.panes_by_id = weakref.WeakValueDictionary() # Socket information. self.socket = None self.socket_name = None # Create eventloop. self.eventloop = PosixEventLoop() # Key bindings manager. self.key_bindings_manager = KeyBindingsManager(self) self.style = PymuxStyle() def _start_auto_refresh_thread(self): """ Start the background thread that auto refreshes all clients according to `self.status_interval`. """ def run(): while True: time.sleep(self.status_interval) self.invalidate() t = threading.Thread(target=run) t.daemon = True t.start() def get_client_state(self, cli): """ Return the ClientState instance for this CommandLineInterface. """ try: return self._client_states[cli] except KeyError: s = ClientState() self._client_states[cli] = s return s def get_title(self, cli): """ The title to be displayed in the titlebar of the terminal. """ w = self.arrangement.get_active_window(cli) if w and w.active_process: title = w.active_process.screen.title else: title = '' if title: return '%s - Pymux' % (title, ) else: return 'Pymux' def get_window_size(self, cli): """ Get the size to be used for the DynamicBody. This will be the smallest size of all clients. """ get_active_window = self.arrangement.get_active_window active_window = get_active_window(cli) # Get connections watching the same window. connections = [c for c in self.connections if c.cli and get_active_window(c.cli) == active_window] rows = [c.size.rows for c in connections] columns = [c.size.columns for c in connections] if self._runs_standalone: r, c = _get_size(sys.stdout.fileno()) rows.append(r) columns.append(c) if rows and columns: return Size(rows=min(rows) - (1 if self.enable_status else 0), columns=min(columns)) else: return Size(rows=20, columns=80) def _create_pane(self, window=None, command=None, start_directory=None): """ Create a new :class:`pymux.arrangement.Pane` instance. (Don't put it in a window yet.) :param window: If a window is given, take the CWD of the current process of that window as the start path for this pane. :param command: If given, run this command instead of `self.default_shell`. :param start_directory: If given, use this as the CWD. """ assert window is None or isinstance(window, Window) assert command is None or isinstance(command, six.text_type) assert start_directory is None or isinstance(start_directory, six.text_type) def done_callback(): " When the process finishes. " if not self.remain_on_exit: # Remove pane from layout. self.arrangement.remove_pane(pane) # No panes left? -> Quit. if not self.arrangement.has_panes: self.eventloop.stop() self.invalidate() def bell(): " Sound bell on all clients. " if self.enable_bell: for c in self.clis.values(): c.output.bell() # Start directory. if start_directory: path = start_directory elif window and window.active_process: # When the path of the active process is known, # start the new process at the same location. path = window.active_process.get_cwd() else: path = None def before_exec(): " Called in the process fork (in the child process). " # Go to this directory. try: os.chdir(path or self.original_cwd) except OSError: pass # No such file or directory. # Set terminal variable. (We emulate xterm.) os.environ['TERM'] = self.default_terminal # Make sure to set the PYMUX environment variable. if self.socket_name: os.environ['PYMUX'] = '%s,%i' % ( self.socket_name, pane.pane_id) if command: command = command.split() else: command = [self.default_shell] # Create process and pane. def has_priority(): return self.arrangement.pane_has_priority(pane) process = Process.from_command( self.eventloop, self.invalidate, command, done_callback, bell_func=bell, before_exec_func=before_exec, has_priority=has_priority) pane = Pane(process) # Keep track of panes. This is a WeakKeyDictionary, we only add, but # don't remove. self.panes_by_id[pane.pane_id] = pane logger.info('Created process %r.', command) process.start() return pane def invalidate(self): " Invalidate the UI for all clients. " for c in self.clis.values(): c.invalidate() def create_window(self, cli=None, command=None, start_directory=None, name=None): """ Create a new :class:`pymux.arrangement.Window` in the arrangement. :param cli: If been given, this window will be focussed for that client. """ assert cli is None or isinstance(cli, CommandLineInterface) assert command is None or isinstance(command, six.text_type) assert start_directory is None or isinstance(start_directory, six.text_type) pane = self._create_pane(None, command, start_directory=start_directory) self.arrangement.create_window(cli, pane, name=name) self.invalidate() def add_process(self, cli, command=None, vsplit=False, start_directory=None): """ Add a new process to the current window. (vsplit/hsplit). """ assert isinstance(cli, CommandLineInterface) assert command is None or isinstance(command, six.text_type) assert start_directory is None or isinstance(start_directory, six.text_type) window = self.arrangement.get_active_window(cli) pane = self._create_pane(window, command, start_directory=start_directory) window.add_pane(pane, vsplit=vsplit) self.invalidate() def kill_pane(self, pane): """ Kill the given pane, and remove it from the arrangement. """ assert isinstance(pane, Pane) # Send kill signal. if not pane.process.is_terminated: pane.process.send_signal(signal.SIGKILL) # Remove from layout. self.arrangement.remove_pane(pane) # No panes left? -> Quit. if not self.arrangement.has_panes: self.eventloop.stop() def leave_command_mode(self, cli, append_to_history=False): """ Leave the command/prompt mode. """ cli.buffers[COMMAND].reset(append_to_history=append_to_history) cli.buffers[PROMPT].reset(append_to_history=True) client_state = self.get_client_state(cli) client_state.command_mode = False client_state.prompt_command = '' client_state.confirm_command = '' def handle_command(self, cli, command): """ Handle command from the command line. """ handle_command(self, cli, command) def show_message(self, cli, message): """ Set a warning message. This will be shown at the bottom until a key has been pressed. :param cli: CommandLineInterface instance. (The client.) :param message: String. """ self.get_client_state(cli).message = message def create_cli(self, connection, output, input=None): """ Create `CommandLineInterface` instance for this connection. """ def get_title(): return self.get_title(cli) def on_focus_changed(): """ When the focus changes to a read/write buffer, make sure to go to insert mode. This happens when the ViState was set to NAVIGATION in the copy buffer. """ vi_state = cli.vi_state if cli.current_buffer.read_only(): vi_state.input_mode = InputMode.NAVIGATION else: vi_state.input_mode = InputMode.INSERT application = Application( layout=self.layout_manager.layout, key_bindings_registry=self.key_bindings_manager.registry, buffers=_BufferMapping(self), mouse_support=Condition(lambda cli: self.enable_mouse_support), use_alternate_screen=True, style=self.style, get_title=get_title, on_invalidate=(lambda cli: self.invalidate())) cli = CommandLineInterface( application=application, output=output, input=input, eventloop=self.eventloop) # Synchronize the Vi state with the CLI object. # (This is stored in the current class, but expected to be in the # CommandLineInterface.) def sync_vi_state(_): client_state = self.get_client_state(cli) VI = EditingMode.VI EMACS = EditingMode.EMACS if (client_state.confirm_text or client_state.prompt_command or client_state.command_mode): cli.editing_mode = VI if self.status_keys_vi_mode else EMACS else: cli.editing_mode = VI if self.mode_keys_vi_mode else EMACS cli.input_processor.beforeKeyPress += sync_vi_state cli.input_processor.afterKeyPress += sync_vi_state # Set render postpone time. (.1 instead of 0). # This small change ensures that if for a split second a process # outputs a lot of information, we don't give the highest priority to # rendering output. (Nobody reads that fast in real-time.) cli.max_render_postpone_time = .1 # Second. # Hide message when a key has been pressed. def key_pressed(_): self.get_client_state(cli).message = None cli.input_processor.beforeKeyPress += key_pressed cli._is_running = True self.clis[connection] = cli # Redraw all CLIs. (Adding a new client could mean that the others # change size, so everything has to be redrawn.) self.invalidate() # Handle start-up comands. # (Does initial key bindings.) if not self._startup_done: self._startup_done = True # Execute default config. for cmd in STARTUP_COMMANDS.splitlines(): self.handle_command(cli, cmd) # Source the given file. if self.source_file: call_command_handler('source-file', self, cli, [self.source_file]) # Make sure that there is one window created. self.create_window(cli, command=self.startup_command) return cli def get_connection_for_cli(self, cli): """ Return the `CommandLineInterface` instance for this connection, if any. `None` otherwise. """ for connection, c in self.clis.items(): if c == cli: return connection def detach_client(self, cli): """ Detach the client that belongs to this CLI. """ connection = self.get_connection_for_cli(cli) if connection is not None: connection.detach_and_close() # Redraw all clients -> Maybe their size has to change. self.invalidate() def listen_on_socket(self, socket_name=None): """ Listen for clients on a Unix socket. Returns the socket name. """ if self.socket is None: # Py2 uses 0027 and Py3 uses 0o027, but both know # how to create the right value from the string '0027'. old_umask = os.umask(int('0027', 8)) self.socket_name, self.socket = bind_socket(socket_name) _ = os.umask(old_umask) self.socket.listen(0) self.eventloop.add_reader(self.socket.fileno(), self._socket_accept) # Set session_name according to socket name. if '.' in self.socket_name: self.session_name = self.socket_name.rpartition('.')[-1] logger.info('Listening on %r.' % self.socket_name) return self.socket_name def _socket_accept(self): """ Accept connection from client. """ logger.info('Client attached.') connection, client_address = self.socket.accept() # Note: We don't have to put this socket in non blocking mode. # This can cause crashes when sending big packets on OS X. connection = ServerConnection(self, connection, client_address) self.connections.append(connection) def run_server(self): # Ignore keyboard. (When people run "pymux server" and press Ctrl-C.) # Pymux has to be terminated by termining all the processes running in # its panes. def handle_sigint(*a): print('Ignoring keyboard interrupt.') signal.signal(signal.SIGINT, handle_sigint) # Start background threads. self._start_auto_refresh_thread() # Run eventloop. # XXX: Both the PipeInput and DummyCallbacks are not used. # This is a workaround to run the PosixEventLoop continuously # without having a CommandLineInterface instance. # A better API in prompt_toolkit is desired. try: self.eventloop.run( PipeInput(), DummyCallbacks()) except: # When something bad happens, always dump the traceback. # (Otherwise, when running as a daemon, and stdout/stderr are not # available, it's hard to see what went wrong.) fd, path = tempfile.mkstemp(prefix='pymux.crash-') logger.fatal( 'Pymux has crashed, dumping traceback to {0}'.format(path)) os.write(fd, traceback.format_exc().encode('utf-8')) os.close(fd) raise finally: # Clean up socket. os.remove(self.socket_name) def run_standalone(self, true_color=False, ansi_colors_only=False): """ Run pymux standalone, rather than using a client/server architecture. This is mainly useful for debugging. """ self._runs_standalone = True self._start_auto_refresh_thread() cli = self.create_cli( connection=None, output=Vt100_Output.from_pty( sys.stdout, true_color=true_color, ansi_colors_only=ansi_colors_only)) cli._is_running = False cli.run()