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. try: get_event_loop().run_until_complete(self.done_f) 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 bind_and_listen_on_posix_socket(socket_name, accept_callback): """ :param accept_callback: Called with `PosixSocketConnection` when a new connection is established. """ assert socket_name is None or isinstance(socket_name, six.text_type) assert callable(accept_callback) # 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)) # Bind socket. socket_name, socket = _bind_posix_socket(socket_name) _ = os.umask(old_umask) # Listen on socket. socket.listen(0) def _accept_cb(): connection, client_address = 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. posix_connection = PosixSocketConnection(connection) accept_callback(posix_connection) get_event_loop().add_reader(socket.fileno(), _accept_cb) logger.info('Listening on %r.' % socket_name) return socket_name
def bind_and_listen_on_posix_socket(socket_name, accept_callback): """ :param accept_callback: Called with `PosixSocketConnection` when a new connection is established. """ assert socket_name is None or isinstance(socket_name, six.text_type) assert callable(accept_callback) # 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)) # Bind socket. socket_name, socket = _bind_posix_socket(socket_name) _ = os.umask(old_umask) # Listen on socket. socket.listen(0) def _accept_cb(): connection, client_address = 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. posix_connection = PosixSocketConnection(connection) accept_callback(posix_connection) get_event_loop().add_reader(socket.fileno(), _accept_cb) logger.info('Listening on %r.' % socket_name) return socket_name
def _read_chunk_from_socket(socket): """ (coroutine) Turn socket reading into coroutine. """ fd = socket.fileno() f = Future() def read_callback(): get_event_loop().remove_reader(fd) # Read next chunk. try: data = socket.recv(1024) except OSError as e: # On OSX, when we try to create a new window by typing "pymux # new-window" in a centain pane, very often we get the following # error: "OSError: [Errno 9] Bad file descriptor." # This doesn't seem very harmful, and we can just try again. logger.warning('Got OSError while reading data from client: %s. ' 'Trying again.', e) f.set_result('') return if data: f.set_result(data) else: f.set_exception(BrokenPipeError) get_event_loop().add_reader(fd, read_callback) return f
def _read_chunk_from_socket(socket): """ (coroutine) Turn socket reading into coroutine. """ fd = socket.fileno() f = Future() def read_callback(): get_event_loop().remove_reader(fd) # Read next chunk. try: data = socket.recv(1024) except OSError as e: # On OSX, when we try to create a new window by typing "pymux # new-window" in a centain pane, very often we get the following # error: "OSError: [Errno 9] Bad file descriptor." # This doesn't seem very harmful, and we can just try again. logger.warning( 'Got OSError while reading data from client: %s. ' 'Trying again.', e) f.set_result('') return if data: f.set_result(data) else: f.set_exception(BrokenPipeError) get_event_loop().add_reader(fd, read_callback) return f
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. try: get_event_loop().run_until_complete(self.done_f) 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 close(self): """ Close connection. """ self.socket.close() # Make sure to remove the reader from the event loop. get_event_loop().remove_reader(self._fd)
def pre_run(): if accept_default: # Validate and handle input. We use `call_from_executor` in # order to run it "soon" (during the next iteration of the # event loop), instead of right now. Otherwise, it won't # display the default value. get_event_loop().call_from_executor( self.default_buffer.validate_and_handle)
def _write_and_flush(self, text): if not text: return def write_and_flush(): self.log_field.log(text) get_event_loop().call_from_executor(write_and_flush)
def close(self): """ Close connection. """ self.socket.close() # Make sure to remove the reader from the event loop. get_event_loop().remove_reader(self._fd)
def screen_exit(): def application_exit(): data.application.exit() data.screen_down_event.set() eventloop.get_event_loop().call_from_executor(application_exit) data.screen_down_event.wait() while data.application.is_running: pass
def timer(): time.sleep(self.CPR_TIMEOUT) # Not set in the meantime -> not supported. if self.cpr_support == CPR_Support.UNKNOWN: self.cpr_support = CPR_Support.NOT_SUPPORTED if self.cpr_not_supported_callback: # Make sure to call this callback in the main thread. get_event_loop().call_from_executor(self.cpr_not_supported_callback)
def timer(): time.sleep(self.CPR_TIMEOUT) # Not set in the meantime -> not supported. if self.cpr_support == CPR_Support.UNKNOWN: self.cpr_support = CPR_Support.NOT_SUPPORTED if self.cpr_not_supported_callback: # Make sure to call this callback in the main thread. get_event_loop().call_from_executor(self.cpr_not_supported_callback)
def start(self): """ Start the telnet server. Don't forget to call `loop.run_forever()` after doing this. """ self._listen_socket = self._create_socket(self.host, self.port) logger.info('Listening for telnet connections on %s port %r', self.host, self.port) get_event_loop().add_reader(self._listen_socket, self._accept)
def close(self): """ Closed by client. """ if not self._closed: self._closed = True self.vt100_input.close() get_event_loop().remove_reader(self.conn) self.conn.close()
def wait_for_event(event): """ Wraps a win32 event into a `Future` and wait for it. """ f = Future() def ready(): get_event_loop().remove_win32_handle(event) f.set_result(None) get_event_loop().add_win32_handle(event, ready) return f
def pre_run2(): if pre_run: pre_run() if accept_default: # Validate and handle input. We use `call_from_executor` in # order to run it "soon" (during the next iteration of the # event loop), instead of right now. Otherwise, it won't # display the default value. get_event_loop().call_from_executor( self.default_buffer.validate_and_handle)
async def stop(self) -> None: if self._listen_socket: get_event_loop().remove_reader(self._listen_socket) self._listen_socket.close() # Wait for all applications to finish. for t in self._application_tasks: t.cancel() for t in self._application_tasks: await t
def _call_history_load(history): """ Helper: Call the history "load" method and return the result as a list of strings. """ result = [] async def call_load(): async for item in history.load(): result.append(item) get_event_loop().run_until_complete(call_load()) return result
def wait_for_event(event): """ Wraps a win32 event into a `Future` and wait for it. """ f = Future() def ready(): get_event_loop().remove_win32_handle(event) f.set_result(None) get_event_loop().add_win32_handle(event, ready) return f
def _write_and_flush(self, text): if not text: return def write_and_flush(): self.log_lines.extend(text.split("\n")) while len(self.log_lines) > MAXIMUM_LOG_PANE_LINE_COUNT: self.log_lines.popleft() new_text: str = "\n".join(self.log_lines) self.log_field.buffer.document = Document(text=new_text, cursor_position=len(new_text)) get_event_loop().call_from_executor(write_and_flush)
def test_empty_async_generator(): " Test asynchronous generator. " items = [] f = ensure_future(consume_async_generator( _empty_async_generator(), lambda: False, items.append)) # Run the event loop until all items are collected. get_event_loop().run_until_complete(f) assert items == [] # Check that `consume_async_generator` didn't fail. assert f.result() is None
def run_telnet(host, port): # Import it here, because the import causes an error in Windows; # and I want to be able to run the local version in Windows. from telnet.server import TelnetServer logging.basicConfig() logging.getLogger().setLevel(logging.INFO) server = TelnetServer(interact=launch_telnet_session, host=host, port=port) server.start() get_event_loop().set_exception_handler(exception_handler) get_event_loop().run_forever()
async def stop(self) -> None: if self._listen_socket: get_event_loop().remove_reader(self._listen_socket) self._listen_socket.close() # Wait for all applications to finish. for t in self._application_tasks: t.cancel() for t in self._application_tasks: try: await t except asyncio.CancelledError: logger.debug("Task %s cancelled", str(t))
def attach(self, detach_other_clients=False, color_depth=ColorDepth.DEPTH_8_BIT): assert isinstance(detach_other_clients, bool) self._send_size() self._send_packet({ 'cmd': 'start-gui', 'detach-others': detach_other_clients, 'color-depth': color_depth, 'term': os.environ.get('TERM', ''), 'data': '' }) f = ensure_future(self._start_reader()) with self._input.attach(self._input_ready): # Run as long as we have a connection with the server. get_event_loop().run_until_complete(f) # Run forever.
def run(self): """ Run the event loop for the interface. This starts the interaction. """ # Make sure everything is in sync, before starting. self.sync_with_prompt_toolkit() def pre_run(): # Start in navigation mode. self.application.vi_state.input_mode = InputMode.NAVIGATION # Run eventloop of prompt_toolkit. def run(): with _auto_refresh_context(self.application, .3): try: self.application.run(pre_run=pre_run) except BaseException as e: import traceback traceback.print_exc() print(e) self.thread = threading.Thread(target=run) self.thread.start() has_sigwinch = hasattr(signal, 'SIGWINCH') and in_main_thread() if has_sigwinch: loop = get_event_loop() loop.add_signal_handler(signal.SIGWINCH, self.application.invalidate)
def main(): loop = get_event_loop() f = ensure_future(my_coroutine()) # Run the event loop, until the coroutine is done. loop.run_until_complete(f) print(f.result())
def __init__(self, title=None, formatters=None, bottom_toolbar=None, style=None, key_bindings=None, file=None, output=None, input=None): assert formatters is None or (isinstance(formatters, list) and all( isinstance(fo, f.Formatter) for fo in formatters)) assert style is None or isinstance(style, BaseStyle) assert key_bindings is None or isinstance(key_bindings, KeyBindings) self.title = title self.formatters = formatters or create_default_formatters() self.bottom_toolbar = bottom_toolbar self.counters = [] self.style = style self.key_bindings = key_bindings self.output = output or create_output(stdout=file or sys.stderr) self.input = input or get_default_input() self._thread = None self._loop = get_event_loop() self._previous_winch_handler = None self._has_sigwinch = False
def __init__(self, title=None, formatters=None, bottom_toolbar=None, style=None, key_bindings=None, file=None, color_depth=None, output=None, input=None): assert formatters is None or ( isinstance(formatters, list) and all(isinstance(fo, Formatter) for fo in formatters)) assert style is None or isinstance(style, BaseStyle) assert key_bindings is None or isinstance(key_bindings, KeyBindings) self.title = title self.formatters = formatters or create_default_formatters() self.bottom_toolbar = bottom_toolbar self.counters = [] self.style = style self.key_bindings = key_bindings # Note that we use __stderr__ as default error output, because that # works best with `patch_stdout`. self.color_depth = color_depth self.output = output or create_output(stdout=file or sys.__stderr__) self.input = input or get_default_input() self._thread = None self._loop = get_event_loop() self._previous_winch_handler = None self._has_sigwinch = False
def main(): loop = get_event_loop() f = ensure_future(my_coroutine()) # Run the event loop, until the coroutine is done. loop.run_until_complete(f) print(f.result())
def test_generator_to_async_generator(): """ Test conversion of sync to asycn generator. This should run the synchronous parts in a background thread. """ async_gen = generator_to_async_generator(_sync_generator) items = [] async def consume_async_generator(): async for item in async_gen: items.append(item) # Run the event loop until all items are collected. get_event_loop().run_until_complete(consume_async_generator()) assert items == [1, 10]
def attach_win32_input(input, callback): """ Context manager that makes this input active in the current event loop. :param input: :class:`~prompt_toolkit.input.Input` object. :param input_ready_callback: Called when the input is ready to read. """ assert isinstance(input, Input) assert callable(callback) loop = get_event_loop() previous_callback = _current_callbacks.get(loop) # Add reader. loop.add_win32_handle(input.handle, callback) _current_callbacks[loop] = callback try: yield finally: loop.remove_win32_handle(input.handle) if previous_callback: loop.add_win32_handle(input.handle, previous_callback) _current_callbacks[loop] = previous_callback else: del _current_callbacks[loop]
def run(): with context() as ctx_id: self._context_id = ctx_id # Set input/output for all application running in this context. set_default_input(self.vt100_input) set_default_output(self.vt100_output) # Add reader. loop = get_event_loop() loop.add_reader(self.conn, handle_incoming_data) try: obj = self.interact(self) if _is_coroutine(obj): # Got an asyncio coroutine. import asyncio f = asyncio.ensure_future(obj) yield From(Future.from_asyncio_future(f)) else: # Got a prompt_toolkit coroutine. yield From(obj) except Exception as e: print('Got %s' % type(e).__name__, e) import traceback traceback.print_exc() raise finally: self.close()
def __init__(self, title=None, formatters=None, bottom_toolbar=None, style=None, key_bindings=None, file=None, color_depth=None, output=None, input=None): assert formatters is None or (isinstance(formatters, list) and all( isinstance(fo, Formatter) for fo in formatters)) assert style is None or isinstance(style, BaseStyle) assert key_bindings is None or isinstance(key_bindings, KeyBindings) self.title = title self.formatters = formatters or create_default_formatters() self.bottom_toolbar = bottom_toolbar self.counters = [] self.style = style self.key_bindings = key_bindings # Note that we use __stderr__ as default error output, because that # works best with `patch_stdout`. self.color_depth = color_depth self.output = output or create_output(stdout=file or sys.__stderr__) self.input = input or get_default_input() self._thread = None self._loop = get_event_loop() self._previous_winch_handler = None self._has_sigwinch = False
def attach_win32_input(input, callback): """ Context manager that makes this input active in the current event loop. :param input: :class:`~prompt_toolkit.input.Input` object. :param input_ready_callback: Called when the input is ready to read. """ assert isinstance(input, Input) assert callable(callback) loop = get_event_loop() previous_callback = _current_callbacks.get(loop) # Add reader. loop.add_win32_handle(input.handle, callback) _current_callbacks[loop] = callback try: yield finally: loop.remove_win32_handle(input.handle) if previous_callback: loop.add_win32_handle(input.handle, previous_callback) _current_callbacks[loop] = previous_callback else: del _current_callbacks[loop]
def _accept(self) -> None: """ Accept new incoming connection. """ if self._listen_socket is None: return # Should not happen. `_accept` is called after `start`. conn, addr = self._listen_socket.accept() logger.info("New connection %r %r", *addr) connection = TelnetConnection(conn, addr, self.interact, self, encoding=self.encoding, style=self.style) self.connections.add(connection) # Run application for this connection. async def run() -> None: logger.info("Starting interaction %r %r", *addr) try: await connection.run_application() except Exception as e: print(e) finally: self.connections.remove(connection) self._application_tasks.remove(task) logger.info("Stopping interaction %r %r", *addr) task = get_event_loop().create_task(run()) self._application_tasks.append(task)
async def run_application(self) -> None: """ Run application. """ def handle_incoming_data() -> None: data = self.conn.recv(1024) if data: self.feed(data) else: # Connection closed by client. logger.info("Connection closed by client. %r %r" % self.addr) self.close() # Add reader. loop = get_event_loop() loop.add_reader(self.conn, handle_incoming_data) try: # Wait for v100_output to be properly instantiated await self._ready.wait() with create_app_session(input=self.vt100_input, output=self.vt100_output): self.context = contextvars.copy_context() await self.interact(self) except Exception as e: print("Got %s" % type(e).__name__, e) import traceback traceback.print_exc() raise finally: self.close()
def __init__( self, title: AnyFormattedText = None, formatters: Optional[Sequence[Formatter]] = None, bottom_toolbar: AnyFormattedText = None, style: Optional[BaseStyle] = None, key_bindings: Optional[KeyBindings] = None, file: Optional[TextIO] = None, color_depth: Optional[ColorDepth] = None, output: Optional[Output] = None, input: Optional[Input] = None, ) -> None: self.title = title self.formatters = formatters or create_default_formatters() self.bottom_toolbar = bottom_toolbar self.counters: List[ProgressBarCounter[object]] = [] self.style = style self.key_bindings = key_bindings # Note that we use __stderr__ as default error output, because that # works best with `patch_stdout`. self.color_depth = color_depth self.output = output or get_app_session().output self.input = input or get_app_session().input self._thread: Optional[threading.Thread] = None self._loop = get_event_loop() self._app_loop = new_event_loop() self._has_sigwinch = False self._app_started = threading.Event()
def run_coroutine_in_terminal(async_func, render_cli_done=False): """ Suspend the current application and run this coroutine instead. `async_func` can be a coroutine or a function that returns a Future. :param async_func: A function that returns either a Future or coroutine when called. :returns: A `Future`. """ assert callable(async_func) loop = get_event_loop() # Make sure to run this function in the current `Application`, or if no # application is active, run it normally. app = get_app(return_none=True) if app is None: return ensure_future(async_func()) assert app._is_running # When a previous `run_in_terminal` call was in progress. Wait for that # to finish, before starting this one. Chain to previous call. previous_run_in_terminal_f = app._running_in_terminal_f new_run_in_terminal_f = loop.create_future() app._running_in_terminal_f = new_run_in_terminal_f def _run_in_t(): " Coroutine. " # Wait for the previous `run_in_terminal` to finish. if previous_run_in_terminal_f is not None: yield previous_run_in_terminal_f # Draw interface in 'done' state, or erase. if render_cli_done: app._redraw(render_as_done=True) else: app.renderer.erase() # Disable rendering. app._running_in_terminal = True # Detach input. try: with app.input.detach(): with app.input.cooked_mode(): result = yield From(async_func()) raise Return(result) # Same as: "return result" finally: # Redraw interface again. try: app._running_in_terminal = False app.renderer.reset() app._request_absolute_cursor_position() app._redraw() finally: new_run_in_terminal_f.set_result(None) return ensure_future(_run_in_t())
def test_generator_to_async_generator(): """ Test conversion of sync to asycn generator. This should run the synchronous parts in a background thread. """ async_gen = generator_to_async_generator(_sync_generator) items = [] f = ensure_future(consume_async_generator( async_gen, lambda: False, items.append)) # Run the event loop until all items are collected. get_event_loop().run_until_complete(f) assert items == [1, 10] # Check that `consume_async_generator` didn't fail. assert f.result() is None
def receive_content_from_fd(): # Read data from the source. tokens = source.read_chunk() data = handle_content(tokens) # Set document. insert_text(data) # Remove the reader when we received another whole page. # or when there is nothing more to read. if lines[0] <= 0 or source.eof(): if fd is not None: get_event_loop().remove_reader(fd) source_info.waiting_for_input_stream = False # Redraw. self.application.invalidate()
def detach_win32_input(input): assert isinstance(input, Input) loop = get_event_loop() previous = _current_callbacks.get(loop) if previous: loop.remove_win32_handle(input.handle) _current_callbacks[loop] = None try: yield finally: if previous: loop.add_win32_handle(input.handle, previous) _current_callbacks[loop] = previous
def run(self, pre_run=None, set_exception_handler=True, inputhook=None): """ A blocking 'run' call that waits until the UI is finished. :param set_exception_handler: When set, in case of an exception, go out of the alternate screen and hide the application, display the exception, and wait for the user to press ENTER. :param inputhook: None or a callable that takes an `InputHookContext`. """ loop = get_event_loop() def run(): f = self.run_async(pre_run=pre_run) run_until_complete(f, inputhook=inputhook) return f.result() def handle_exception(context): " Print the exception, using run_in_terminal. " # For Python 2: we have to get traceback at this point, because # we're still in the 'except:' block of the event loop where the # traceback is still available. Moving this code in the # 'print_exception' coroutine will loose the exception. tb = get_traceback_from_context(context) formatted_tb = ''.join(format_tb(tb)) def print_exception(): # Print output. Similar to 'loop.default_exception_handler', # but don't use logger. (This works better on Python 2.) print('\nUnhandled exception in event loop:') print(formatted_tb) print('Exception %s' % (context.get('exception'), )) yield From(_do_wait_for_enter('Press ENTER to continue...')) run_coroutine_in_terminal(print_exception) if set_exception_handler: # Run with patched exception handler. previous_exc_handler = loop.get_exception_handler() loop.set_exception_handler(handle_exception) try: return run() finally: loop.set_exception_handler(previous_exc_handler) else: run()
def main(): server = TelnetServer(interact=interact, port=2323) server.start() get_event_loop().run_forever()
def ready(): get_event_loop().remove_win32_handle(event) f.set_result(None)
def run_coroutine_in_terminal(async_func, render_cli_done=False): """ Suspend the current application and run this coroutine instead. `async_func` can be a coroutine or a function that returns a Future. :param async_func: A function that returns either a Future or coroutine when called. :returns: A `Future`. """ assert callable(async_func) loop = get_event_loop() # Make sure to run this function in the current `Application`, or if no # application is active, run it normally. app = get_app(return_none=True) if app is None: return ensure_future(async_func()) assert app._is_running # When a previous `run_in_terminal` call was in progress. Wait for that # to finish, before starting this one. Chain to previous call. previous_run_in_terminal_f = app._running_in_terminal_f new_run_in_terminal_f = loop.create_future() app._running_in_terminal_f = new_run_in_terminal_f def _run_in_t(): " Coroutine. " # Wait for the previous `run_in_terminal` to finish. if previous_run_in_terminal_f is not None: yield previous_run_in_terminal_f # Wait for all CPRs to arrive. We don't want to detach the input until # all cursor position responses have been arrived. Otherwise, the tty # will echo its input and can show stuff like ^[[39;1R. if app.input.responds_to_cpr: yield From(app.renderer.wait_for_cpr_responses()) # Draw interface in 'done' state, or erase. if render_cli_done: app._redraw(render_as_done=True) else: app.renderer.erase() # Disable rendering. app._running_in_terminal = True # Detach input. try: with app.input.detach(): with app.input.cooked_mode(): result = yield From(async_func()) raise Return(result) # Same as: "return result" finally: # Redraw interface again. try: app._running_in_terminal = False app.renderer.reset() app._request_absolute_cursor_position() app._redraw() finally: new_run_in_terminal_f.set_result(None) return ensure_future(_run_in_t())
def _run_async(): " Coroutine. " loop = get_event_loop() f = loop.create_future() self.future = f # XXX: make sure to set this before calling '_redraw'. # Counter for cancelling 'flush' timeouts. Every time when a key is # pressed, we start a 'flush' timer for flushing our escape key. But # when any subsequent input is received, a new timer is started and # the current timer will be ignored. flush_counter = [0] # Non local. # Reset. self.reset() self._pre_run(pre_run) # Feed type ahead input first. self.key_processor.feed_multiple(get_typeahead(self.input)) self.key_processor.process_keys() def read_from_input(): # Ignore when we aren't running anymore. This callback will # removed from the loop next time. (It could be that it was # still in the 'tasks' list of the loop.) # Except: if we need to process incoming CPRs. if not self._is_running and not self.renderer.waiting_for_cpr: return # Get keys from the input object. keys = self.input.read_keys() # Feed to key processor. self.key_processor.feed_multiple(keys) self.key_processor.process_keys() # Quit when the input stream was closed. if self.input.closed: f.set_exception(EOFError) else: # Increase this flush counter. flush_counter[0] += 1 counter = flush_counter[0] # Automatically flush keys. # (_daemon needs to be set, otherwise, this will hang the # application for .5 seconds before exiting.) run_in_executor( lambda: auto_flush_input(counter), _daemon=True) def auto_flush_input(counter): # Flush input after timeout. # (Used for flushing the enter key.) time.sleep(self.ttimeoutlen) if flush_counter[0] == counter: call_from_executor(flush_input) def flush_input(): if not self.is_done: # Get keys, and feed to key processor. keys = self.input.flush_keys() self.key_processor.feed_multiple(keys) self.key_processor.process_keys() if self.input.closed: f.set_exception(EOFError) # Enter raw mode. with self.input.raw_mode(): with self.input.attach(read_from_input): # Draw UI. self._request_absolute_cursor_position() self._redraw() has_sigwinch = hasattr(signal, 'SIGWINCH') and in_main_thread() if has_sigwinch: previous_winch_handler = loop.add_signal_handler( signal.SIGWINCH, self._on_resize) # Wait for UI to finish. try: result = yield From(f) finally: # In any case, when the application finishes. (Successful, # or because of an error.) try: self._redraw(render_as_done=True) finally: # _redraw has a good chance to fail if it calls widgets # with bad code. Make sure to reset the renderer anyway. self.renderer.reset() # Unset `is_running`, this ensures that possibly # scheduled draws won't paint during the following # yield. self._is_running = False # Detach event handlers for invalidate events. # (Important when a UIControl is embedded in # multiple applications, like ptterm in pymux. An # invalidate should not trigger a repaint in # terminated applications.) for ev in self._invalidate_events: ev -= self._invalidate_handler self._invalidate_events = [] # Wait for CPR responses. if self.input.responds_to_cpr: yield From(self.renderer.wait_for_cpr_responses()) if has_sigwinch: loop.add_signal_handler(signal.SIGWINCH, previous_winch_handler) # Wait for the run-in-terminals to terminate. previous_run_in_terminal_f = self._running_in_terminal_f if previous_run_in_terminal_f: yield From(previous_run_in_terminal_f) # Store unprocessed input as typeahead for next time. store_typeahead(self.input, self.key_processor.empty_queue()) raise Return(result)
def exit_immediately(): # Use `call_from_executor` to exit "soon", so that we still render one # initial time, before exiting the application. get_event_loop().call_from_executor( lambda: app.exit())
def _after_render(self, app): """ Each time when the rendering is done, we should see whether we need to read more data from the input pipe. """ # When the bottom is visible, read more input. # Try at least `info.window_height`, if this amount of data is # available. info = self.layout.dynamic_body.get_render_info() source = self.current_source source_info = self.source_info[source] b = source_info.buffer line_tokens = source_info.line_tokens if not source_info.waiting_for_input_stream and not source.eof() and info: lines_below_bottom = info.ui_content.line_count - info.last_visible_line() # Make sure to preload at least 2x the amount of lines on a page. if lines_below_bottom < info.window_height * 2 or self.forward_forever: # Lines to be loaded. lines = [info.window_height * 2 - lines_below_bottom] # nonlocal fd = source.get_fd() def handle_content(tokens): """ Handle tokens, update `line_tokens`, decrease line count and return list of characters. """ data = [] for token_char in tokens: char = token_char[1] if char == '\n': line_tokens.append([]) # Decrease line count. lines[0] -= 1 else: line_tokens[-1].append(token_char) data.append(char) return data def insert_text(list_of_fragments): document = Document(b.text + ''.join(list_of_fragments), b.cursor_position) b.set_document(document, bypass_readonly=True) if self.forward_forever: b.cursor_position = len(b.text) def receive_content_from_fd(): # Read data from the source. tokens = source.read_chunk() data = handle_content(tokens) # Set document. insert_text(data) # Remove the reader when we received another whole page. # or when there is nothing more to read. if lines[0] <= 0 or source.eof(): if fd is not None: get_event_loop().remove_reader(fd) source_info.waiting_for_input_stream = False # Redraw. self.application.invalidate() def receive_content_from_generator(): " (in executor) Read data from generator. " # Call `read_chunk` as long as we need more lines. while lines[0] > 0 and not source.eof(): tokens = source.read_chunk() data = handle_content(tokens) insert_text(data) # Schedule redraw. self.application.invalidate() source_info.waiting_for_input_stream = False # Set 'waiting_for_input_stream' and render. source_info.waiting_for_input_stream = True self.application.invalidate() # Add reader for stdin. if fd is not None: get_event_loop().add_reader(fd, receive_content_from_fd) else: # Execute receive_content_from_generator in thread. # (Don't use 'run_in_executor', because we need a daemon. t = threading.Thread(target=receive_content_from_generator) t.daemon = True t.start()