diff options
-rw-r--r-- | mitmproxy/optmanager.py | 52 | ||||
-rw-r--r-- | mitmproxy/tools/console/options.py | 150 | ||||
-rw-r--r-- | mitmproxy/tools/console/statusbar.py | 6 |
3 files changed, 131 insertions, 77 deletions
diff --git a/mitmproxy/optmanager.py b/mitmproxy/optmanager.py index c878528c..77990306 100644 --- a/mitmproxy/optmanager.py +++ b/mitmproxy/optmanager.py @@ -265,44 +265,50 @@ class OptManager: vals.update(self._setspec(i)) self.update(**vals) - def _setspec(self, spec): - d = {} - - parts = spec.split("=", maxsplit=1) - if len(parts) == 1: - optname, optval = parts[0], None - else: - optname, optval = parts[0], parts[1] + def parse_setval(self, optname: str, optstr: typing.Optional[str]) -> typing.Any: + """ + Convert a string to a value appropriate for the option type. + """ if optname not in self._options: raise exceptions.OptionsError("No such option %s" % optname) o = self._options[optname] if o.typespec in (str, typing.Optional[str]): - d[optname] = optval + return optstr elif o.typespec in (int, typing.Optional[int]): - if optval: + if optstr: try: - optval = int(optval) + return int(optstr) except ValueError: - raise exceptions.OptionsError("Not an integer: %s" % optval) - d[optname] = optval + raise exceptions.OptionsError("Not an integer: %s" % optstr) + elif o.typespec == int: + raise exceptions.OptionsError("Option is required: %s" % optname) + else: + return None elif o.typespec == bool: - if not optval or optval == "true": - v = True - elif optval == "false": - v = False + if not optstr or optstr == "true": + return True + elif optstr == "false": + return False else: raise exceptions.OptionsError( "Boolean must be \"true\", \"false\", or have the value " "omitted (a synonym for \"true\")." ) - d[optname] = v elif o.typespec == typing.Sequence[str]: - if not optval: - d[optname] = [] + if not optstr: + return [] else: - d[optname] = getattr(self, optname) + [optval] - else: # pragma: no cover - raise NotImplementedError("Unsupported option type: %s", o.typespec) + return getattr(self, optname) + [optstr] + raise NotImplementedError("Unsupported option type: %s", o.typespec) + + def _setspec(self, spec): + d = {} + parts = spec.split("=", maxsplit=1) + if len(parts) == 1: + optname, optval = parts[0], None + else: + optname, optval = parts[0], parts[1] + d[optname] = self.parse_setval(optname, optval) return d def make_parser(self, parser, optname, metavar=None, short=None): diff --git a/mitmproxy/tools/console/options.py b/mitmproxy/tools/console/options.py index 2f874c1f..5458bc76 100644 --- a/mitmproxy/tools/console/options.py +++ b/mitmproxy/tools/console/options.py @@ -1,8 +1,18 @@ import urwid import blinker import textwrap +from typing import Optional +from mitmproxy import exceptions from mitmproxy.tools.console import common +from mitmproxy.tools.console import signals + + +def can_edit_inplace(opt): + if opt.choices: + return False + if opt.typespec in [str, int, Optional[str], Optional[int]]: + return True footer = [ @@ -40,13 +50,14 @@ option_focus_change = blinker.Signal() class OptionItem(urwid.WidgetWrap): - def __init__(self, master, opt, focused, namewidth): - self.master, self.opt, self.focused = master, opt, focused + def __init__(self, walker, opt, focused, namewidth, editing): + self.walker, self.opt, self.focused = walker, opt, focused self.namewidth = namewidth - w = self.get_text() - urwid.WidgetWrap.__init__(self, w) + self.editing = editing + super().__init__(None) + self._w = self.get_widget() - def get_text(self): + def get_widget(self): val = self.opt.current() if self.opt.typespec == bool: displayval = "true" if val else "false" @@ -55,78 +66,104 @@ class OptionItem(urwid.WidgetWrap): else: displayval = str(val) - changed = self.master.options.has_changed(self.opt.name) + changed = self.walker.master.options.has_changed(self.opt.name) if self.focused: valstyle = "option_active_selected" if changed else "option_selected" else: valstyle = "option_active" if changed else "text" + + if self.editing: + valw = urwid.Edit(edit_text=displayval) + else: + valw = urwid.AttrMap( + urwid.Padding( + urwid.Text([(valstyle, displayval)]) + ), + valstyle + ) + return urwid.Columns( [ ( self.namewidth, urwid.Text([("title", self.opt.name.ljust(self.namewidth))]) ), - urwid.AttrMap( - urwid.Padding( - urwid.Text([(valstyle, displayval)]) - ), - valstyle - ) + valw ], - dividechars=2 + dividechars=2, + focus_column=1 ) + def get_edit_text(self): + return self._w[1].get_edit_text() + def selectable(self): return True - def keypress(self, xxx_todo_changeme, key): - if key == "enter": - if self.opt.typespec == bool: - self.master.options.toggler(self.opt.name)() - else: - return key + def keypress(self, size, key): + if self.editing: + self._w[1].keypress(size, key) + return key class OptionListWalker(urwid.ListWalker): def __init__(self, master): self.master = master + self.index = 0 + self.focusobj = None + self.opts = sorted(master.options.keys()) self.maxlen = max(len(i) for i in self.opts) - - # Trigger a help text update for the first selected item - first = self.master.options._options[self.opts[0]] - option_focus_change.send(first.help) + self.editing = False + self.set_focus(0) self.master.options.changed.connect(self.sig_mod) def sig_mod(self, *args, **kwargs): self._modified() - def _get(self, pos): + def start_editing(self): + self.editing = True + self.focus_obj = self._get(self.index, True) + self._modified() + + def stop_editing(self): + self.editing = False + self.focus_obj = self._get(self.index, False) + self._modified() + + def get_edit_text(self): + return self.focus_obj.get_edit_text() + + def _get(self, pos, editing): name = self.opts[pos] opt = self.master.options._options[name] - return OptionItem(self.master, opt, pos == self.index, self.maxlen) + return OptionItem( + self, opt, pos == self.index, self.maxlen, editing + ) def get_focus(self): - return self._get(self.index), self.index + return self.focus_obj, self.index def set_focus(self, index): + self.editing = False name = self.opts[index] opt = self.master.options._options[name] self.index = index + self.focus_obj = self._get(self.index, self.editing) option_focus_change.send(opt.help) def get_next(self, pos): if pos >= len(self.opts) - 1: return None, None pos = pos + 1 - return self._get(pos), pos + return self._get(pos, False), pos def get_prev(self, pos): pos = pos - 1 if pos < 0: return None, None - return self._get(pos), pos + return self._get(pos, False), pos class OptionsList(urwid.ListBox): @@ -136,34 +173,49 @@ class OptionsList(urwid.ListBox): super().__init__(self.walker) def keypress(self, size, key): - if key == "g": - self.set_focus(0) - self.walker._modified() - elif key == "G": - self.set_focus(len(self.walker.opts) - 1) - self.walker._modified() + if self.walker.editing: + if key == "enter": + foc, idx = self.get_focus() + v = self.walker.get_edit_text() + try: + d = self.master.options.parse_setval(foc.opt.name, v) + except exceptions.OptionsError as v: + signals.status_message.send(message=str(v)) + else: + self.master.options.update(**{foc.opt.name: d}) + self.walker.stop_editing() + elif key == "esc": + self.walker.stop_editing() else: - return urwid.ListBox.keypress(self, size, key) + if key == "g": + self.set_focus(0) + self.walker._modified() + elif key == "G": + self.set_focus(len(self.walker.opts) - 1) + self.walker._modified() + elif key == "enter": + foc, idx = self.get_focus() + if foc.opt.typespec == bool: + self.master.options.toggler(foc.opt.name)() + # Bust the focus widget cache + self.set_focus(self.walker.index) + elif can_edit_inplace(foc.opt): + self.walker.start_editing() + self.walker._modified() + return super().keypress(size, key) class OptionHelp(urwid.Frame): def __init__(self, master): self.master = master - - h = urwid.Text("Option Help") - h = urwid.Padding(h, align="left", width=("relative", 100)) - - self.inactive_header = urwid.AttrWrap(h, "heading_inactive") - self.active_header = urwid.AttrWrap(h, "heading") - - super().__init__(self.widget(""), header=self.inactive_header) + super().__init__(self.widget("")) + self.set_active(False) option_focus_change.connect(self.sig_mod) - def active(self, val): - if val: - self.header = self.active_header - else: - self.header = self.inactive_header + def set_active(self, val): + h = urwid.Text("Option Help") + style = "heading" if val else "heading_inactive" + self.header = urwid.AttrWrap(h, style) def widget(self, txt): cols, _ = self.master.ui.get_cols_rows() @@ -192,7 +244,7 @@ class Options(urwid.Pile): self.focus_position = ( self.focus_position + 1 ) % len(self.widget_list) - self.widget_list[1].active(self.focus_position == 1) + self.widget_list[1].set_active(self.focus_position == 1) key = None elif key == "D": self.master.options.reset() diff --git a/mitmproxy/tools/console/statusbar.py b/mitmproxy/tools/console/statusbar.py index 3e524972..f8fe53ed 100644 --- a/mitmproxy/tools/console/statusbar.py +++ b/mitmproxy/tools/console/statusbar.py @@ -224,11 +224,7 @@ class StatusBar(urwid.WidgetWrap): if self.master.options.console_focus_follow: opts.append("following") if self.master.options.stream_large_bodies: - opts.append( - "stream:%s" % human.pretty_size( - self.master.options.stream_large_bodies - ) - ) + opts.append(self.master.options.stream_large_bodies) if opts: r.append("[%s]" % (":".join(opts))) |