make progress on rlcompleter, still broken

This commit is contained in:
Brian Warner 2017-03-19 16:40:16 -07:00
parent 1b5a0289a8
commit 07a49bfaca
4 changed files with 56 additions and 11 deletions

View File

@ -232,7 +232,7 @@ helper to do tab completion of wormhole codes:
```python
from wormhole import create, rlcompleter_helper
w = create(appid, relay_url, reactor)
rlcompleter_helper("Wormhole code:", w.input_code())
rlcompleter_helper("Wormhole code:", w.input_code(), reactor)
d = w.when_code()
```

View File

@ -5,6 +5,9 @@ from attr.validators import provides
from automat import MethodicalMachine
from . import _interfaces
def first(outputs):
return list(outputs)[0]
@attrs
@implementer(_interfaces.ICode)
class Code(object):
@ -79,7 +82,7 @@ class Code(object):
S0_idle.upon(set_code, enter=S4_known, outputs=[do_set_code])
S0_idle.upon(input_code, enter=S1_inputting_nameplate,
outputs=[do_start_input])
outputs=[do_start_input], collector=first)
S1_inputting_nameplate.upon(got_nameplate, enter=S2_inputting_words,
outputs=[do_middle_input])
S2_inputting_words.upon(finished_input, enter=S4_known,

View File

@ -1,11 +1,12 @@
from __future__ import print_function, unicode_literals
import sys
import six
from attr import attrs, attrib
from twisted.internet.defer import inlineCallbacks, returnValue
from twisted.internet.threads import deferToThread, blockingCallFromThread
@attrs
class CodeInputter:
class CodeInputter(object):
_input_helper = attrib()
_reactor = attrib()
def __attrs_post_init__(self):
@ -23,10 +24,14 @@ class CodeInputter:
# completer exceptions are normally silently discarded, which
# makes debugging challenging
print("completer exception: %s" % e)
import traceback
traceback.print_exc()
raise e
def completer(self, text, state):
self.used_completion = True
# debug
#import readline
#if state == 0:
# print("", file=sys.stderr)
#print("completer: '%s' %d '%d'" % (text, state,
@ -68,10 +73,10 @@ class CodeInputter:
#sys.stderr.flush()
return self._matches[state]
def input_code_with_completion(prompt, input_helper, code_length):
def input_code_with_completion(prompt, input_helper, reactor):
try:
import readline
c = CodeInputter(input_helper)
c = CodeInputter(input_helper, reactor)
if readline.__doc__ and "libedit" in readline.__doc__:
readline.parse_and_bind("bind ^I rl_complete")
else:
@ -89,12 +94,45 @@ def input_code_with_completion(prompt, input_helper, code_length):
used_completion = c.used_completion if c else False
return (code, used_completion)
def warn_readline():
# When our process receives a SIGINT, Twisted's SIGINT handler will
# stop the reactor and wait for all threads to terminate before the
# process exits. However, if we were waiting for
# input_code_with_completion() when SIGINT happened, the readline
# thread will be blocked waiting for something on stdin. Trick the
# user into satisfying the blocking read so we can exit.
print("\nCommand interrupted: please press Return to quit",
file=sys.stderr)
# Other potential approaches to this problem:
# * hard-terminate our process with os._exit(1), but make sure the
# tty gets reset to a normal mode ("cooked"?) first, so that the
# next shell command the user types is echoed correctly
# * track down the thread (t.p.threadable.getThreadID from inside the
# thread), get a cffi binding to pthread_kill, deliver SIGINT to it
# * allocate a pty pair (pty.openpty), replace sys.stdin with the
# slave, build a pty bridge that copies bytes (and other PTY
# things) from the real stdin to the master, then close the slave
# at shutdown, so readline sees EOF
# * write tab-completion and basic editing (TTY raw mode,
# backspace-is-erase) without readline, probably with curses or
# twisted.conch.insults
# * write a separate program to get codes (maybe just "wormhole
# --internal-get-code"), run it as a subprocess, let it inherit
# stdin/stdout, send it SIGINT when we receive SIGINT ourselves. It
# needs an RPC mechanism (over some extra file descriptors) to ask
# us to fetch the current nameplate_id list.
#
# Note that hard-terminating our process with os.kill(os.getpid(),
# signal.SIGKILL), or SIGTERM, doesn't seem to work: the thread
# doesn't see the signal, and we must still wait for stdin to make
# readline finish.
@inlineCallbacks
def rlcompleter_helper(prompt, input_helper, reactor):
def warn_readline():
pass
t = reactor.addSystemEventTrigger("before", "shutdown", warn_readline)
res = yield deferToThread(input_code_with_completion, prompt, input_helper)
res = yield deferToThread(input_code_with_completion, prompt, input_helper,
reactor)
(code, used_completion) = res
reactor.removeSystemEventTrigger(t)
returnValue(used_completion)

View File

@ -164,9 +164,13 @@ class TwistedReceiver:
if code:
w.set_code(code)
else:
raise NotImplemented
yield w.input_code("Enter receive wormhole code: ", # TODO
self.args.code_length)
from .._rlcompleter import rlcompleter_helper
used_completion = yield rlcompleter_helper("Enter receive wormhole code: ",
w.input_code(),
self._reactor)
if not used_completion:
print(" (note: you can use <Tab> to complete words)",
file=self.args.stderr)
yield w.when_code()
def _show_verifier(self, verifier):