diff options
author | Ivan Blinkov <ivan@blinkov.ru> | 2022-02-10 16:47:10 +0300 |
---|---|---|
committer | Daniil Cherednik <dcherednik@yandex-team.ru> | 2022-02-10 16:47:10 +0300 |
commit | 1aeb9a455974457866f78722ad98114bafc84e8a (patch) | |
tree | e4340eaf1668684d83a0a58c36947c5def5350ad /contrib/python/prompt-toolkit/py2/prompt_toolkit/eventloop/posix.py | |
parent | bd5ef432f5cfb1e18851381329d94665a4c22470 (diff) | |
download | ydb-1aeb9a455974457866f78722ad98114bafc84e8a.tar.gz |
Restoring authorship annotation for Ivan Blinkov <ivan@blinkov.ru>. Commit 1 of 2.
Diffstat (limited to 'contrib/python/prompt-toolkit/py2/prompt_toolkit/eventloop/posix.py')
-rw-r--r-- | contrib/python/prompt-toolkit/py2/prompt_toolkit/eventloop/posix.py | 528 |
1 files changed, 264 insertions, 264 deletions
diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/eventloop/posix.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/eventloop/posix.py index f631dbd891..a2ef2ac3d8 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/eventloop/posix.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/eventloop/posix.py @@ -1,244 +1,244 @@ -from __future__ import unicode_literals -import fcntl -import os -import signal -import threading +from __future__ import unicode_literals +import fcntl +import os +import signal +import threading import time - -from prompt_toolkit.terminal.vt100_input import InputStream -from prompt_toolkit.utils import DummyContext, in_main_thread -from prompt_toolkit.input import Input -from .base import EventLoop, INPUT_TIMEOUT -from .callbacks import EventLoopCallbacks -from .inputhook import InputHookContext -from .posix_utils import PosixStdinReader -from .utils import TimeIt + +from prompt_toolkit.terminal.vt100_input import InputStream +from prompt_toolkit.utils import DummyContext, in_main_thread +from prompt_toolkit.input import Input +from .base import EventLoop, INPUT_TIMEOUT +from .callbacks import EventLoopCallbacks +from .inputhook import InputHookContext +from .posix_utils import PosixStdinReader +from .utils import TimeIt from .select import AutoSelector, Selector, fd_to_int - -__all__ = ( - 'PosixEventLoop', -) - + +__all__ = ( + 'PosixEventLoop', +) + _now = time.time - - -class PosixEventLoop(EventLoop): - """ - Event loop for posix systems (Linux, Mac os X). - """ + + +class PosixEventLoop(EventLoop): + """ + Event loop for posix systems (Linux, Mac os X). + """ def __init__(self, inputhook=None, selector=AutoSelector): - assert inputhook is None or callable(inputhook) + assert inputhook is None or callable(inputhook) assert issubclass(selector, Selector) - - self.running = False - self.closed = False - self._running = False - self._callbacks = None - - self._calls_from_executor = [] - self._read_fds = {} # Maps fd to handler. + + self.running = False + self.closed = False + self._running = False + self._callbacks = None + + self._calls_from_executor = [] + self._read_fds = {} # Maps fd to handler. self.selector = selector() - - # Create a pipe for inter thread communication. - self._schedule_pipe = os.pipe() - fcntl.fcntl(self._schedule_pipe[0], fcntl.F_SETFL, os.O_NONBLOCK) - - # Create inputhook context. - self._inputhook_context = InputHookContext(inputhook) if inputhook else None - - def run(self, stdin, callbacks): - """ - The input 'event loop'. - """ - assert isinstance(stdin, Input) - assert isinstance(callbacks, EventLoopCallbacks) - assert not self._running - - if self.closed: - raise Exception('Event loop already closed.') - - self._running = True - self._callbacks = callbacks - - inputstream = InputStream(callbacks.feed_key) - current_timeout = [INPUT_TIMEOUT] # Nonlocal - - # Create reader class. - stdin_reader = PosixStdinReader(stdin.fileno()) - - # Only attach SIGWINCH signal handler in main thread. - # (It's not possible to attach signal handlers in other threads. In - # that case we should rely on a the main thread to call this manually - # instead.) - if in_main_thread(): - ctx = call_on_sigwinch(self.received_winch) - else: - ctx = DummyContext() - - def read_from_stdin(): - " Read user input. " - # Feed input text. - data = stdin_reader.read() - inputstream.feed(data) - - # Set timeout again. - current_timeout[0] = INPUT_TIMEOUT - + + # Create a pipe for inter thread communication. + self._schedule_pipe = os.pipe() + fcntl.fcntl(self._schedule_pipe[0], fcntl.F_SETFL, os.O_NONBLOCK) + + # Create inputhook context. + self._inputhook_context = InputHookContext(inputhook) if inputhook else None + + def run(self, stdin, callbacks): + """ + The input 'event loop'. + """ + assert isinstance(stdin, Input) + assert isinstance(callbacks, EventLoopCallbacks) + assert not self._running + + if self.closed: + raise Exception('Event loop already closed.') + + self._running = True + self._callbacks = callbacks + + inputstream = InputStream(callbacks.feed_key) + current_timeout = [INPUT_TIMEOUT] # Nonlocal + + # Create reader class. + stdin_reader = PosixStdinReader(stdin.fileno()) + + # Only attach SIGWINCH signal handler in main thread. + # (It's not possible to attach signal handlers in other threads. In + # that case we should rely on a the main thread to call this manually + # instead.) + if in_main_thread(): + ctx = call_on_sigwinch(self.received_winch) + else: + ctx = DummyContext() + + def read_from_stdin(): + " Read user input. " + # Feed input text. + data = stdin_reader.read() + inputstream.feed(data) + + # Set timeout again. + current_timeout[0] = INPUT_TIMEOUT + # Quit when the input stream was closed. if stdin_reader.closed: self.stop() - self.add_reader(stdin, read_from_stdin) - self.add_reader(self._schedule_pipe[0], None) - - with ctx: - while self._running: - # Call inputhook. + self.add_reader(stdin, read_from_stdin) + self.add_reader(self._schedule_pipe[0], None) + + with ctx: + while self._running: + # Call inputhook. if self._inputhook_context: with TimeIt() as inputhook_timer: - def ready(wait): - " True when there is input ready. The inputhook should return control. " - return self._ready_for_reading(current_timeout[0] if wait else 0) != [] - self._inputhook_context.call_inputhook(ready) + def ready(wait): + " True when there is input ready. The inputhook should return control. " + return self._ready_for_reading(current_timeout[0] if wait else 0) != [] + self._inputhook_context.call_inputhook(ready) inputhook_duration = inputhook_timer.duration else: inputhook_duration = 0 - - # Calculate remaining timeout. (The inputhook consumed some of the time.) - if current_timeout[0] is None: - remaining_timeout = None - else: + + # Calculate remaining timeout. (The inputhook consumed some of the time.) + if current_timeout[0] is None: + remaining_timeout = None + else: remaining_timeout = max(0, current_timeout[0] - inputhook_duration) - - # Wait until input is ready. - fds = self._ready_for_reading(remaining_timeout) - - # When any of the FDs are ready. Call the appropriate callback. - if fds: - # Create lists of high/low priority tasks. The main reason - # for this is to allow painting the UI to happen as soon as - # possible, but when there are many events happening, we - # don't want to call the UI renderer 1000x per second. If - # the eventloop is completely saturated with many CPU - # intensive tasks (like processing input/output), we say - # that drawing the UI can be postponed a little, to make - # CPU available. This will be a low priority task in that - # case. - tasks = [] - low_priority_tasks = [] + + # Wait until input is ready. + fds = self._ready_for_reading(remaining_timeout) + + # When any of the FDs are ready. Call the appropriate callback. + if fds: + # Create lists of high/low priority tasks. The main reason + # for this is to allow painting the UI to happen as soon as + # possible, but when there are many events happening, we + # don't want to call the UI renderer 1000x per second. If + # the eventloop is completely saturated with many CPU + # intensive tasks (like processing input/output), we say + # that drawing the UI can be postponed a little, to make + # CPU available. This will be a low priority task in that + # case. + tasks = [] + low_priority_tasks = [] now = None # Lazy load time. (Fewer system calls.) - - for fd in fds: - # For the 'call_from_executor' fd, put each pending - # item on either the high or low priority queue. - if fd == self._schedule_pipe[0]: - for c, max_postpone_until in self._calls_from_executor: + + for fd in fds: + # For the 'call_from_executor' fd, put each pending + # item on either the high or low priority queue. + if fd == self._schedule_pipe[0]: + for c, max_postpone_until in self._calls_from_executor: if max_postpone_until is None: # Execute now. - tasks.append(c) - else: + tasks.append(c) + else: # Execute soon, if `max_postpone_until` is in the future. now = now or _now() if max_postpone_until < now: tasks.append(c) else: low_priority_tasks.append((c, max_postpone_until)) - self._calls_from_executor = [] - - # Flush all the pipe content. - os.read(self._schedule_pipe[0], 1024) - else: - handler = self._read_fds.get(fd) - if handler: - tasks.append(handler) - - # When there are high priority tasks, run all these. - # Schedule low priority tasks for the next iteration. - if tasks: - for t in tasks: - t() - - # Postpone low priority tasks. - for t, max_postpone_until in low_priority_tasks: - self.call_from_executor(t, _max_postpone_until=max_postpone_until) - else: - # Currently there are only low priority tasks -> run them right now. - for t, _ in low_priority_tasks: - t() - - else: - # Flush all pending keys on a timeout. (This is most - # important to flush the vt100 'Escape' key early when - # nothing else follows.) - inputstream.flush() - - # Fire input timeout event. - callbacks.input_timeout() - current_timeout[0] = None - - self.remove_reader(stdin) - self.remove_reader(self._schedule_pipe[0]) - - self._callbacks = None - - def _ready_for_reading(self, timeout=None): - """ - Return the file descriptors that are ready for reading. - """ + self._calls_from_executor = [] + + # Flush all the pipe content. + os.read(self._schedule_pipe[0], 1024) + else: + handler = self._read_fds.get(fd) + if handler: + tasks.append(handler) + + # When there are high priority tasks, run all these. + # Schedule low priority tasks for the next iteration. + if tasks: + for t in tasks: + t() + + # Postpone low priority tasks. + for t, max_postpone_until in low_priority_tasks: + self.call_from_executor(t, _max_postpone_until=max_postpone_until) + else: + # Currently there are only low priority tasks -> run them right now. + for t, _ in low_priority_tasks: + t() + + else: + # Flush all pending keys on a timeout. (This is most + # important to flush the vt100 'Escape' key early when + # nothing else follows.) + inputstream.flush() + + # Fire input timeout event. + callbacks.input_timeout() + current_timeout[0] = None + + self.remove_reader(stdin) + self.remove_reader(self._schedule_pipe[0]) + + self._callbacks = None + + def _ready_for_reading(self, timeout=None): + """ + Return the file descriptors that are ready for reading. + """ fds = self.selector.select(timeout) return fds - - def received_winch(self): - """ - Notify the event loop that SIGWINCH has been received - """ - # Process signal asynchronously, because this handler can write to the - # output, and doing this inside the signal handler causes easily - # reentrant calls, giving runtime errors.. - - # Furthur, this has to be thread safe. When the CommandLineInterface - # runs not in the main thread, this function still has to be called - # from the main thread. (The only place where we can install signal - # handlers.) - def process_winch(): - if self._callbacks: - self._callbacks.terminal_size_changed() - - self.call_from_executor(process_winch) - - def run_in_executor(self, callback): - """ - Run a long running function in a background thread. - (This is recommended for code that could block the event loop.) - Similar to Twisted's ``deferToThread``. - """ - # Wait until the main thread is idle. - # We start the thread by using `call_from_executor`. The event loop - # favours processing input over `calls_from_executor`, so the thread - # will not start until there is no more input to process and the main - # thread becomes idle for an instant. This is good, because Python - # threading favours CPU over I/O -- an autocompletion thread in the - # background would cause a significantly slow down of the main thread. - # It is mostly noticable when pasting large portions of text while - # having real time autocompletion while typing on. - def start_executor(): - threading.Thread(target=callback).start() - self.call_from_executor(start_executor) - - def call_from_executor(self, callback, _max_postpone_until=None): - """ - Call this function in the main event loop. - Similar to Twisted's ``callFromThread``. - + + def received_winch(self): + """ + Notify the event loop that SIGWINCH has been received + """ + # Process signal asynchronously, because this handler can write to the + # output, and doing this inside the signal handler causes easily + # reentrant calls, giving runtime errors.. + + # Furthur, this has to be thread safe. When the CommandLineInterface + # runs not in the main thread, this function still has to be called + # from the main thread. (The only place where we can install signal + # handlers.) + def process_winch(): + if self._callbacks: + self._callbacks.terminal_size_changed() + + self.call_from_executor(process_winch) + + def run_in_executor(self, callback): + """ + Run a long running function in a background thread. + (This is recommended for code that could block the event loop.) + Similar to Twisted's ``deferToThread``. + """ + # Wait until the main thread is idle. + # We start the thread by using `call_from_executor`. The event loop + # favours processing input over `calls_from_executor`, so the thread + # will not start until there is no more input to process and the main + # thread becomes idle for an instant. This is good, because Python + # threading favours CPU over I/O -- an autocompletion thread in the + # background would cause a significantly slow down of the main thread. + # It is mostly noticable when pasting large portions of text while + # having real time autocompletion while typing on. + def start_executor(): + threading.Thread(target=callback).start() + self.call_from_executor(start_executor) + + def call_from_executor(self, callback, _max_postpone_until=None): + """ + Call this function in the main event loop. + Similar to Twisted's ``callFromThread``. + :param _max_postpone_until: `None` or `time.time` value. For interal - use. If the eventloop is saturated, consider this task to be low - priority and postpone maximum until this timestamp. (For instance, - repaint is done using low priority.) - """ + use. If the eventloop is saturated, consider this task to be low + priority and postpone maximum until this timestamp. (For instance, + repaint is done using low priority.) + """ assert _max_postpone_until is None or isinstance(_max_postpone_until, float) - self._calls_from_executor.append((callback, _max_postpone_until)) - - if self._schedule_pipe: + self._calls_from_executor.append((callback, _max_postpone_until)) + + if self._schedule_pipe: try: os.write(self._schedule_pipe[1], b'x') except (AttributeError, IndexError, OSError): @@ -247,60 +247,60 @@ class PosixEventLoop(EventLoop): # - We catch `OSError` (actually BrokenPipeError), because the # main thread could have closed the pipe already. pass - - def stop(self): - """ - Stop the event loop. - """ - self._running = False - - def close(self): - self.closed = True - - # Close pipes. - schedule_pipe = self._schedule_pipe - self._schedule_pipe = None - - if schedule_pipe: - os.close(schedule_pipe[0]) - os.close(schedule_pipe[1]) - - if self._inputhook_context: - self._inputhook_context.close() - - def add_reader(self, fd, callback): - " Add read file descriptor to the event loop. " + + def stop(self): + """ + Stop the event loop. + """ + self._running = False + + def close(self): + self.closed = True + + # Close pipes. + schedule_pipe = self._schedule_pipe + self._schedule_pipe = None + + if schedule_pipe: + os.close(schedule_pipe[0]) + os.close(schedule_pipe[1]) + + if self._inputhook_context: + self._inputhook_context.close() + + def add_reader(self, fd, callback): + " Add read file descriptor to the event loop. " fd = fd_to_int(fd) - self._read_fds[fd] = callback + self._read_fds[fd] = callback self.selector.register(fd) - - def remove_reader(self, fd): - " Remove read file descriptor from the event loop. " + + def remove_reader(self, fd): + " Remove read file descriptor from the event loop. " fd = fd_to_int(fd) - if fd in self._read_fds: - del self._read_fds[fd] - + if fd in self._read_fds: + del self._read_fds[fd] + self.selector.unregister(fd) - - -class call_on_sigwinch(object): - """ - Context manager which Installs a SIGWINCH callback. - (This signal occurs when the terminal size changes.) - """ - def __init__(self, callback): - self.callback = callback - self.previous_callback = None - - def __enter__(self): - self.previous_callback = signal.signal(signal.SIGWINCH, lambda *a: self.callback()) - - def __exit__(self, *a, **kw): - if self.previous_callback is None: - # Normally, `signal.signal` should never return `None`. - # For some reason it happens here: - # https://github.com/jonathanslenders/python-prompt-toolkit/pull/174 - signal.signal(signal.SIGWINCH, 0) - else: - signal.signal(signal.SIGWINCH, self.previous_callback) + + +class call_on_sigwinch(object): + """ + Context manager which Installs a SIGWINCH callback. + (This signal occurs when the terminal size changes.) + """ + def __init__(self, callback): + self.callback = callback + self.previous_callback = None + + def __enter__(self): + self.previous_callback = signal.signal(signal.SIGWINCH, lambda *a: self.callback()) + + def __exit__(self, *a, **kw): + if self.previous_callback is None: + # Normally, `signal.signal` should never return `None`. + # For some reason it happens here: + # https://github.com/jonathanslenders/python-prompt-toolkit/pull/174 + signal.signal(signal.SIGWINCH, 0) + else: + signal.signal(signal.SIGWINCH, self.previous_callback) |