aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--mitmproxy/optmanager.py52
-rw-r--r--mitmproxy/tools/console/options.py150
-rw-r--r--mitmproxy/tools/console/statusbar.py6
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)))