async def _run_async() -> _AppResult: " Coroutine. " loop = get_event_loop() f = loop.create_future() self.future = f # XXX: make sure to set this before calling '_redraw'. self.loop = loop self.context = contextvars.copy_context() # 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_task: Optional[asyncio.Task[None]] = None # 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() -> None: nonlocal flush_task # 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: # Automatically flush keys. if flush_task: flush_task.cancel() flush_task = self.create_background_task( auto_flush_input()) async def auto_flush_input() -> None: # Flush input after timeout. # (Used for flushing the enter key.) # This sleep can be cancelled, in that case we won't flush yet. await sleep(self.ttimeoutlen) flush_input() def flush_input() -> None: 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() self._start_auto_refresh_task() has_sigwinch = hasattr(signal, "SIGWINCH") and in_main_thread() if has_sigwinch: previous_winch_handler = signal.getsignal( signal.SIGWINCH) loop.add_signal_handler(signal.SIGWINCH, self._on_resize) # Wait for UI to finish. try: result = await 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: await self.renderer.wait_for_cpr_responses() if has_sigwinch: loop.remove_signal_handler(signal.SIGWINCH) signal.signal(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: await previous_run_in_terminal_f # Store unprocessed input as typeahead for next time. store_typeahead(self.input, self.key_processor.empty_queue()) return result
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 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)
async def _run_async() -> _AppResult: " Coroutine. " loop = get_event_loop() f = loop.create_future() self.future = f # XXX: make sure to set this before calling '_redraw'. self.loop = loop self.context = contextvars.copy_context() # 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 # 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() -> None: nonlocal flush_counter # 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 += 1 counter = flush_counter # Automatically flush keys. ensure_future(auto_flush_input(counter)) async def auto_flush_input(counter: int) -> None: # Flush input after timeout. # (Used for flushing the enter key.) await sleep(self.ttimeoutlen) if flush_counter == counter: flush_input() def flush_input() -> None: 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 = signal.getsignal(signal.SIGWINCH) loop.add_signal_handler(signal.SIGWINCH, self._on_resize) # Wait for UI to finish. try: result = await 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: await self.renderer.wait_for_cpr_responses() if has_sigwinch: loop.remove_signal_handler(signal.SIGWINCH) signal.signal(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: await previous_run_in_terminal_f # Store unprocessed input as typeahead for next time. store_typeahead(self.input, self.key_processor.empty_queue()) return result