From 49b0a67eb95a5f55bace24c73e458c2eb224f1b7 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 19 Mar 2017 10:25:07 +1300 Subject: [PATCH] console options: add an overlay grid editor for sequence options --- mitmproxy/tools/console/grideditor/base.py | 16 +++++----- mitmproxy/tools/console/grideditor/editors.py | 17 +++++++++++ mitmproxy/tools/console/options.py | 13 ++++++-- mitmproxy/tools/console/overlay.py | 30 +++++++++++++++++-- mitmproxy/tools/console/signals.py | 3 ++ mitmproxy/tools/console/statusbar.py | 11 ++++--- test/mitmproxy/test_optmanager.py | 12 ++++++++ 7 files changed, 84 insertions(+), 18 deletions(-) diff --git a/mitmproxy/tools/console/grideditor/base.py b/mitmproxy/tools/console/grideditor/base.py index 4505bb979..d2ba47c3b 100644 --- a/mitmproxy/tools/console/grideditor/base.py +++ b/mitmproxy/tools/console/grideditor/base.py @@ -182,12 +182,12 @@ class GridWalker(urwid.ListWalker): self.edit_row = GridRow( self.focus_col, True, self.editor, self.lst[self.focus] ) - self.editor.master.loop.widget.footer.update(FOOTER_EDITING) + signals.footer_help.send(self, helptext=FOOTER_EDITING) self._modified() def stop_edit(self): if self.edit_row: - self.editor.master.loop.widget.footer.update(FOOTER) + signals.footer_help.send(self, helptext=FOOTER) try: val = self.edit_row.edit_col.get_data() except ValueError: @@ -276,9 +276,11 @@ class GridEditor(urwid.WidgetWrap): first_width = max(len(r), first_width) self.first_width = min(first_width, FIRST_WIDTH_MAX) - title = urwid.Text(self.title) - title = urwid.Padding(title, align="left", width=("relative", 100)) - title = urwid.AttrWrap(title, "heading") + title = None + if self.title: + title = urwid.Text(self.title) + title = urwid.Padding(title, align="left", width=("relative", 100)) + title = urwid.AttrWrap(title, "heading") headings = [] for i, col in enumerate(self.columns): @@ -297,10 +299,10 @@ class GridEditor(urwid.WidgetWrap): self.lb = GridListBox(self.walker) w = urwid.Frame( self.lb, - header=urwid.Pile([title, h]) + header=urwid.Pile([title, h]) if title else None ) super().__init__(w) - self.master.loop.widget.footer.update("") + signals.footer_help.send(self, helptext="") self.show_empty_msg() def show_empty_msg(self): diff --git a/mitmproxy/tools/console/grideditor/editors.py b/mitmproxy/tools/console/grideditor/editors.py index 0d9929aea..39e51b2b6 100644 --- a/mitmproxy/tools/console/grideditor/editors.py +++ b/mitmproxy/tools/console/grideditor/editors.py @@ -245,3 +245,20 @@ class SetCookieEditor(base.GridEditor): ] ) return vals + + +class OptionsEditor(base.GridEditor): + title = None + columns = [ + col_text.Column("") + ] + + def __init__(self, master, name, vals): + self.name = name + super().__init__(master, [[i] for i in vals], self.callback) + + def callback(self, vals): + setattr(self.master.options, self.name, [i[0] for i in vals]) + + def is_error(self, col, val): + pass diff --git a/mitmproxy/tools/console/options.py b/mitmproxy/tools/console/options.py index 479d39181..706605fc6 100644 --- a/mitmproxy/tools/console/options.py +++ b/mitmproxy/tools/console/options.py @@ -1,7 +1,7 @@ import urwid import blinker import textwrap -from typing import Optional +from typing import Optional, Sequence from mitmproxy import exceptions from mitmproxy.tools.console import common @@ -213,6 +213,16 @@ class OptionsList(urwid.ListBox): self.master.options.setter(foc.opt.name) ) ) + elif foc.opt.typespec == Sequence[str]: + self.master.overlay( + overlay.OptionsOverlay( + self.master, + foc.opt.name, + foc.opt.current() + ) + ) + else: + raise NotImplementedError() return super().keypress(size, key) @@ -269,4 +279,3 @@ class Options(urwid.Pile): i = self.widget_list.index(self.focus_item) tsize = self.get_item_size(size, i, True, item_rows) return self.focus_item.keypress(tsize, key) - diff --git a/mitmproxy/tools/console/overlay.py b/mitmproxy/tools/console/overlay.py index cf7fdfcba..e1dc50bf5 100644 --- a/mitmproxy/tools/console/overlay.py +++ b/mitmproxy/tools/console/overlay.py @@ -1,6 +1,10 @@ +import math + +import urwid + from mitmproxy.tools.console import common from mitmproxy.tools.console import signals -import urwid +from mitmproxy.tools.console import grideditor class SimpleOverlay(urwid.Overlay): @@ -15,9 +19,11 @@ class SimpleOverlay(urwid.Overlay): ) def keypress(self, size, key): + key = super().keypress(size, key) if key == "esc": signals.pop_view_state.send(self) - return super().keypress(size, key) + else: + return key class Choice(urwid.WidgetWrap): @@ -97,4 +103,22 @@ class Chooser(urwid.WidgetWrap): if key == "enter": self.callback(self.choices[self.walker.index]) signals.pop_view_state.send(self) - return super().keypress(size, key) \ No newline at end of file + return super().keypress(size, key) + + +class OptionsOverlay(urwid.WidgetWrap): + def __init__(self, master, name, vals): + cols, rows = master.ui.get_cols_rows() + super().__init__( + urwid.AttrWrap( + urwid.LineBox( + urwid.BoxAdapter( + grideditor.OptionsEditor(master, name, vals), + math.ceil(rows * 0.5) + ), + title="text" + ), + "background" + ) + ) + self.width = math.ceil(cols * 0.8) diff --git a/mitmproxy/tools/console/signals.py b/mitmproxy/tools/console/signals.py index cb71c5c12..ad2c29629 100644 --- a/mitmproxy/tools/console/signals.py +++ b/mitmproxy/tools/console/signals.py @@ -30,6 +30,9 @@ call_in = blinker.Signal() # Focus the body, footer or header of the main window focus = blinker.Signal() +# Focus the body, footer or header of the main window +footer_help = blinker.Signal() + # Fired when settings change update_settings = blinker.Signal() diff --git a/mitmproxy/tools/console/statusbar.py b/mitmproxy/tools/console/statusbar.py index f8fe53ed3..3f18bbb37 100644 --- a/mitmproxy/tools/console/statusbar.py +++ b/mitmproxy/tools/console/statusbar.py @@ -5,7 +5,6 @@ import urwid from mitmproxy.tools.console import common from mitmproxy.tools.console import pathedit from mitmproxy.tools.console import signals -from mitmproxy.utils import human class PromptPath: @@ -143,10 +142,15 @@ class StatusBar(urwid.WidgetWrap): super().__init__(urwid.Pile([self.ib, self.master.ab])) signals.update_settings.connect(self.sig_update) signals.flowlist_change.connect(self.sig_update) + signals.footer_help.connect(self.sig_footer_help) master.options.changed.connect(self.sig_update) master.view.focus.sig_change.connect(self.sig_update) self.redraw() + def sig_footer_help(self, sender, helptext): + self.helptext = helptext + self.redraw() + def sig_update(self, sender, updated=None): self.redraw() @@ -281,10 +285,5 @@ class StatusBar(urwid.WidgetWrap): ]), "heading") self.ib._w = status - def update(self, text): - self.helptext = text - self.redraw() - self.master.loop.draw_screen() - def selectable(self): return True diff --git a/test/mitmproxy/test_optmanager.py b/test/mitmproxy/test_optmanager.py index 6f87ac068..4dc2e9ca7 100644 --- a/test/mitmproxy/test_optmanager.py +++ b/test/mitmproxy/test_optmanager.py @@ -140,6 +140,18 @@ class Rec(): def test_subscribe(): o = TO() r = Rec() + + # pytest.raises keeps a reference here that interferes with the cleanup test + # further down. + try: + o.subscribe(r, ["unknown"]) + except exceptions.OptionsError: + pass + else: + raise AssertionError + + assert len(o.changed.receivers) == 0 + o.subscribe(r, ["two"]) o.one = 2 assert not r.called