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 ```python
from wormhole import create, rlcompleter_helper from wormhole import create, rlcompleter_helper
w = create(appid, relay_url, reactor) 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() d = w.when_code()
``` ```

View File

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

View File

@ -1,11 +1,12 @@
from __future__ import print_function, unicode_literals from __future__ import print_function, unicode_literals
import sys
import six import six
from attr import attrs, attrib from attr import attrs, attrib
from twisted.internet.defer import inlineCallbacks, returnValue from twisted.internet.defer import inlineCallbacks, returnValue
from twisted.internet.threads import deferToThread, blockingCallFromThread from twisted.internet.threads import deferToThread, blockingCallFromThread
@attrs @attrs
class CodeInputter: class CodeInputter(object):
_input_helper = attrib() _input_helper = attrib()
_reactor = attrib() _reactor = attrib()
def __attrs_post_init__(self): def __attrs_post_init__(self):
@ -23,10 +24,14 @@ class CodeInputter:
# completer exceptions are normally silently discarded, which # completer exceptions are normally silently discarded, which
# makes debugging challenging # makes debugging challenging
print("completer exception: %s" % e) print("completer exception: %s" % e)
import traceback
traceback.print_exc()
raise e raise e
def completer(self, text, state): def completer(self, text, state):
self.used_completion = True self.used_completion = True
# debug
#import readline
#if state == 0: #if state == 0:
# print("", file=sys.stderr) # print("", file=sys.stderr)
#print("completer: '%s' %d '%d'" % (text, state, #print("completer: '%s' %d '%d'" % (text, state,
@ -68,10 +73,10 @@ class CodeInputter:
#sys.stderr.flush() #sys.stderr.flush()
return self._matches[state] return self._matches[state]
def input_code_with_completion(prompt, input_helper, code_length): def input_code_with_completion(prompt, input_helper, reactor):
try: try:
import readline import readline
c = CodeInputter(input_helper) c = CodeInputter(input_helper, reactor)
if readline.__doc__ and "libedit" in readline.__doc__: if readline.__doc__ and "libedit" in readline.__doc__:
readline.parse_and_bind("bind ^I rl_complete") readline.parse_and_bind("bind ^I rl_complete")
else: else:
@ -89,12 +94,45 @@ def input_code_with_completion(prompt, input_helper, code_length):
used_completion = c.used_completion if c else False used_completion = c.used_completion if c else False
return (code, used_completion) 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 @inlineCallbacks
def rlcompleter_helper(prompt, input_helper, reactor): def rlcompleter_helper(prompt, input_helper, reactor):
def warn_readline():
pass
t = reactor.addSystemEventTrigger("before", "shutdown", warn_readline) 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 (code, used_completion) = res
reactor.removeSystemEventTrigger(t) reactor.removeSystemEventTrigger(t)
returnValue(used_completion) returnValue(used_completion)

View File

@ -164,9 +164,13 @@ class TwistedReceiver:
if code: if code:
w.set_code(code) w.set_code(code)
else: else:
raise NotImplemented from .._rlcompleter import rlcompleter_helper
yield w.input_code("Enter receive wormhole code: ", # TODO used_completion = yield rlcompleter_helper("Enter receive wormhole code: ",
self.args.code_length) 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() yield w.when_code()
def _show_verifier(self, verifier): def _show_verifier(self, verifier):