From 07a49bfacabeae8b141e4556243f3222d582c9d0 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Sun, 19 Mar 2017 16:40:16 -0700 Subject: [PATCH] make progress on rlcompleter, still broken --- docs/api.md | 2 +- src/wormhole/_code.py | 5 +++- src/wormhole/_rlcompleter.py | 50 +++++++++++++++++++++++++++++---- src/wormhole/cli/cmd_receive.py | 10 +++++-- 4 files changed, 56 insertions(+), 11 deletions(-) diff --git a/docs/api.md b/docs/api.md index 2299a21..47fae44 100644 --- a/docs/api.md +++ b/docs/api.md @@ -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() ``` diff --git a/src/wormhole/_code.py b/src/wormhole/_code.py index d19a9ee..edcac03 100644 --- a/src/wormhole/_code.py +++ b/src/wormhole/_code.py @@ -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, diff --git a/src/wormhole/_rlcompleter.py b/src/wormhole/_rlcompleter.py index f72451c..1e6db1b 100644 --- a/src/wormhole/_rlcompleter.py +++ b/src/wormhole/_rlcompleter.py @@ -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) diff --git a/src/wormhole/cli/cmd_receive.py b/src/wormhole/cli/cmd_receive.py index c6ec093..a15a62a 100644 --- a/src/wormhole/cli/cmd_receive.py +++ b/src/wormhole/cli/cmd_receive.py @@ -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 to complete words)", + file=self.args.stderr) yield w.when_code() def _show_verifier(self, verifier):