console: auto-generate context key binding help

This commit is contained in:
Aldo Cortesi 2017-06-11 16:23:44 +12:00
parent fd68eca351
commit 7caa7e7538
12 changed files with 112 additions and 79 deletions

View File

@ -1,7 +1,6 @@
import urwid
import blinker
import textwrap
from mitmproxy.tools.console import common
from mitmproxy.tools.console import layoutwidget
from mitmproxy.tools.console import signals

View File

@ -5,7 +5,6 @@ from mitmproxy.tools.console import layoutwidget
import mitmproxy.tools.console.master # noqa
class FlowItem(urwid.WidgetWrap):
def __init__(self, master, flow):

View File

@ -80,10 +80,10 @@ class GridRow(urwid.WidgetWrap):
) -> None:
self.focused = focused
self.editor = editor
self.edit_col = None # type: Optional[Cell]
self.edit_col = None # type: typing.Optional[Cell]
errors = values[1]
self.fields = [] # type: Sequence[Any]
self.fields = [] # type: typing.Sequence[typing.Any]
for i, v in enumerate(values[0]):
if focused == i and editing:
self.edit_col = self.editor.columns[i].Edit(v)
@ -132,11 +132,11 @@ class GridWalker(urwid.ListWalker):
lst: typing.Iterable[list],
editor: "GridEditor"
) -> None:
self.lst = [(i, set()) for i in lst] # type: Sequence[Tuple[Any, Set]]
self.lst = [(i, set()) for i in lst] # type: typing.Sequence[typing.Tuple[typing.Any, typing.Set]]
self.editor = editor
self.focus = 0
self.focus_col = 0
self.edit_row = None # type: Optional[GridRow]
self.edit_row = None # type: typing.Optional[GridRow]
def _modified(self):
self.editor.show_empty_msg()
@ -266,6 +266,7 @@ FIRST_WIDTH_MIN = 20
class BaseGridEditor(urwid.WidgetWrap):
title = ""
keyctx = "grideditor"
def __init__(
@ -317,7 +318,7 @@ class BaseGridEditor(urwid.WidgetWrap):
signals.footer_help.send(self, helptext="")
self.show_empty_msg()
def view_popping(self):
def layout_popping(self):
res = []
for i in self.walker.lst:
if not i[1] and any([x for x in i[0]]):
@ -445,7 +446,7 @@ class BaseGridEditor(urwid.WidgetWrap):
class GridEditor(BaseGridEditor):
title = None # type: str
columns = None # type: Sequence[Column]
columns = None # type: typing.Sequence[Column]
keyctx = "grideditor"
def __init__(
@ -501,8 +502,8 @@ class FocusEditor(urwid.WidgetWrap, layoutwidget.LayoutWidget):
def key_responder(self):
return self._w
def view_popping(self):
self.call(self._w, "view_popping")
def layout_popping(self):
self.call(self._w, "layout_popping")
def focus_changed(self):
if self.master.view.focus.flow:

View File

@ -9,7 +9,6 @@ from mitmproxy.tools.console.grideditor import col_text
from mitmproxy.tools.console.grideditor import col_bytes
from mitmproxy.tools.console.grideditor import col_subgrid
from mitmproxy.tools.console import signals
from mitmproxy.net.http import user_agents
from mitmproxy.net.http import Headers

View File

@ -1,35 +1,67 @@
import platform
import urwid
from mitmproxy import flowfilter
from mitmproxy.tools.console import common
from mitmproxy.tools.console import layoutwidget
from mitmproxy import version
footer = [
("heading", 'mitmproxy {} (Python {}) '.format(version.VERSION, platform.python_version())),
('heading_key', "q"), ":back ",
]
from mitmproxy.tools.console import tabs
class HelpView(urwid.ListBox, layoutwidget.LayoutWidget):
class HelpView(tabs.Tabs, layoutwidget.LayoutWidget):
title = "Help"
keyctx = "help"
def __init__(self, help_context):
urwid.ListBox.__init__(
self,
self.helptext()
def __init__(self, master):
self.master = master
self.helpctx = ""
super().__init__(
[
[self.keybindings_title, self.keybindings],
[self.filtexp_title, self.filtexp],
]
)
def helptext(self):
def keybindings_title(self):
return "Key Bindings"
def format_keys(self, binds):
kvs = []
for b in binds:
k = b.key
if b.key == " ":
k = "space"
kvs.append((k, b.command))
return common.format_keyvals(kvs)
def keybindings(self):
text = [
urwid.Text(
[
("title", "Keybindings for this view")
]
)
]
if self.helpctx:
text.extend(self.format_keys(self.master.keymap.list(self.helpctx)))
text.append(
urwid.Text(
[
"\n",
("title", "Global Keybindings"),
]
)
)
text.extend(self.format_keys(self.master.keymap.list("global")))
return urwid.ListBox(text)
def filtexp_title(self):
return "Filter Expressions"
def filtexp(self):
text = []
text.append(urwid.Text([("head", "\n\nFilter expressions:\n")]))
text.extend(common.format_keyvals(flowfilter.help, key="key", val="text", indent=4))
text.append(
urwid.Text(
[
@ -51,11 +83,11 @@ class HelpView(urwid.ListBox, layoutwidget.LayoutWidget):
text.extend(
common.format_keyvals(examples, key="key", val="text", indent=4)
)
return text
return urwid.ListBox(text)
def keypress(self, size, key):
if key == "m_start":
self.set_focus(0)
elif key == "m_end":
self.set_focus(len(self.body.contents))
return urwid.ListBox.keypress(self, size, key)
def layout_pushed(self, prev):
"""
We are just about to push a window onto the stack.
"""
self.helpctx = prev.keyctx
self.show()

View File

@ -49,6 +49,11 @@ class Keymap:
return self.keys[context].get(key, None)
return None
def list(self, context: str) -> typing.Sequence[Binding]:
b = [b for b in self.bindings if context in b.contexts]
b.sort(key=lambda x: x.key)
return b
def handle(self, context: str, key: str) -> typing.Optional[str]:
"""
Returns the key if it has not been handled, or None.

View File

@ -6,7 +6,7 @@ class LayoutWidget:
overlay must comply with this API.
"""
# Title is only required for windows, not overlay components
title = None
title = ""
keyctx = ""
def key_responder(self):
@ -29,8 +29,14 @@ class LayoutWidget:
"""
pass
def view_popping(self):
def layout_popping(self):
"""
We are just about to pop a window off the stack, or exit an overlay.
"""
pass
def layout_pushed(self, prev):
"""
We have just pushed a window onto the stack.
"""
pass

View File

@ -6,7 +6,6 @@ from typing import Optional, Sequence
from mitmproxy import exceptions
from mitmproxy import optmanager
from mitmproxy.tools.console import common
from mitmproxy.tools.console import layoutwidget
from mitmproxy.tools.console import signals
from mitmproxy.tools.console import overlay

View File

@ -35,8 +35,8 @@ class SimpleOverlay(urwid.Overlay, layoutwidget.LayoutWidget):
def view_changed(self):
return self.widget.view_changed()
def view_popping(self):
return self.widget.view_popping()
def layout_popping(self):
return self.widget.layout_popping()
class Choice(urwid.WidgetWrap):
@ -157,5 +157,5 @@ class OptionsOverlay(urwid.WidgetWrap, layoutwidget.LayoutWidget):
def key_responder(self):
return self.ge.key_responder()
def view_popping(self):
return self.ge.view_popping()
def layout_popping(self):
return self.ge.layout_popping()

View File

@ -30,7 +30,7 @@ class WindowStack:
flowview = flowview.FlowView(master),
commands = commands.Commands(master),
options = options.Options(master),
help = help.HelpView(None),
help = help.HelpView(master),
eventlog = eventlog.EventLog(master),
edit_focus_query = grideditor.QueryEditor(master),
@ -66,20 +66,21 @@ class WindowStack:
def push(self, wname):
if self.stack[-1] == wname:
return
prev = self.top_window()
self.stack.append(wname)
self.call("layout_pushed", prev)
def pop(self, *args, **kwargs):
"""
Pop off the stack, return True if we're already at the top.
"""
if self.overlay:
self.call("view_popping")
self.overlay = None
elif len(self.stack) > 1:
self.call("view_popping")
self.stack.pop()
else:
if not self.overlay and len(self.stack) == 1:
return True
self.call("layout_popping")
if self.overlay:
self.overlay = None
else:
self.stack.pop()
def call(self, name, *args, **kwargs):
"""

View File

@ -1,11 +0,0 @@
import mitmproxy.tools.console.help as help
from ....conftest import skip_appveyor
@skip_appveyor
class TestHelp:
def test_helptext(self):
h = help.HelpView(None)
assert h.helptext()

View File

@ -16,6 +16,7 @@ def test_bind():
assert km.get("options", "key")
assert km.get("commands", "key")
assert not km.get("flowlist", "key")
assert len((km.list("commands"))) == 1
km.handle("unknown", "unknown")
assert not km.executor.called
@ -27,3 +28,5 @@ def test_bind():
km.executor = mock.Mock()
km.handle("options", "glob")
assert km.executor.called
assert len((km.list("global"))) == 1