You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
115 lines
4.0 KiB
115 lines
4.0 KiB
"""
|
|
Similar to `PyOS_InputHook` of the Python API. Some eventloops can have an
|
|
inputhook to allow easy integration with other event loops.
|
|
|
|
When the eventloop of prompt-toolkit is idle, it can call such a hook. This
|
|
hook can call another eventloop that runs for a short while, for instance to
|
|
keep a graphical user interface responsive.
|
|
|
|
It's the responsibility of this hook to exit when there is input ready.
|
|
There are two ways to detect when input is ready:
|
|
|
|
- Call the `input_is_ready` method periodically. Quit when this returns `True`.
|
|
|
|
- Add the `fileno` as a watch to the external eventloop. Quit when file descriptor
|
|
becomes readable. (But don't read from it.)
|
|
|
|
Note that this is not the same as checking for `sys.stdin.fileno()`. The
|
|
eventloop of prompt-toolkit allows thread-based executors, for example for
|
|
asynchronous autocompletion. When the completion for instance is ready, we
|
|
also want prompt-toolkit to gain control again in order to display that.
|
|
|
|
An alternative to using input hooks, is to create a custom `EventLoop` class that
|
|
controls everything.
|
|
"""
|
|
from __future__ import unicode_literals
|
|
import os
|
|
import threading
|
|
from prompt_toolkit.utils import is_windows
|
|
from .select import select_fds
|
|
|
|
__all__ = [
|
|
'InputHookContext',
|
|
]
|
|
|
|
|
|
class InputHookContext(object):
|
|
"""
|
|
Given as a parameter to the inputhook.
|
|
"""
|
|
def __init__(self):
|
|
self._input_is_ready = None
|
|
self._r, self._w = os.pipe()
|
|
|
|
def input_is_ready(self):
|
|
"""
|
|
Return True when the input is ready.
|
|
"""
|
|
return self._input_is_ready(wait=False)
|
|
|
|
def fileno(self):
|
|
"""
|
|
File descriptor that will become ready when the event loop needs to go on.
|
|
"""
|
|
return self._r
|
|
|
|
def call_inputhook(self, input_is_ready_func, inputhook):
|
|
"""
|
|
Call the inputhook. (Called by a prompt-toolkit eventloop.)
|
|
|
|
:param input_is_ready_func: A callable which returns True when there is
|
|
input ready for the main event loop to process. This means that we
|
|
should quit our input hook. This callable takes a boolean `wait`.
|
|
Wen `True` this needs to be a blocking call which only returns when
|
|
there is input ready.
|
|
:param inputhook: This is a callable that runs the inputhook. This
|
|
function should take this object (the `InputHookContext`) as input.
|
|
"""
|
|
assert callable(input_is_ready_func)
|
|
assert callable(inputhook)
|
|
|
|
self._input_is_ready = input_is_ready_func
|
|
|
|
# Start thread that activates this pipe when there is input to process.
|
|
def thread():
|
|
input_is_ready_func(wait=True)
|
|
os.write(self._w, b'x')
|
|
|
|
threading.Thread(target=thread).start()
|
|
|
|
# Call inputhook.
|
|
inputhook(self)
|
|
|
|
# Flush the read end of the pipe.
|
|
try:
|
|
# Before calling 'os.read', call select.select. This is required
|
|
# when the gevent monkey patch has been applied. 'os.read' is never
|
|
# monkey patched and won't be cooperative, so that would block all
|
|
# other select() calls otherwise.
|
|
# See: http://www.gevent.org/gevent.os.html
|
|
|
|
# Note: On Windows, this is apparently not an issue.
|
|
# However, if we would ever want to add a select call, it
|
|
# should use `windll.kernel32.WaitForMultipleObjects`,
|
|
# because `select.select` can't wait for a pipe on Windows.
|
|
if not is_windows():
|
|
select_fds([self._r], timeout=None)
|
|
|
|
os.read(self._r, 1024)
|
|
except OSError:
|
|
# This happens when the window resizes and a SIGWINCH was received.
|
|
# We get 'Error: [Errno 4] Interrupted system call'
|
|
# Just ignore.
|
|
pass
|
|
self._input_is_ready = None
|
|
|
|
def close(self):
|
|
"""
|
|
Clean up resources.
|
|
"""
|
|
if self._r:
|
|
os.close(self._r)
|
|
os.close(self._w)
|
|
|
|
self._r = self._w = None
|