mirror of
https://github.com/Grasscutters/mitmproxy.git
synced 2024-11-23 08:11:00 +00:00
console: add a keymap
This sketches out a keymap system for consone, and adds the first few top-level commands and mappings.
This commit is contained in:
parent
18edc11145
commit
be1b76b975
@ -37,7 +37,7 @@ class Command:
|
|||||||
def paramnames(self) -> typing.Sequence[str]:
|
def paramnames(self) -> typing.Sequence[str]:
|
||||||
return [typename(i, False) for i in self.paramtypes]
|
return [typename(i, False) for i in self.paramtypes]
|
||||||
|
|
||||||
def retname(self) -> typing.Sequence[str]:
|
def retname(self) -> str:
|
||||||
return typename(self.returntype, True) if self.returntype else ""
|
return typename(self.returntype, True) if self.returntype else ""
|
||||||
|
|
||||||
def signature_help(self) -> str:
|
def signature_help(self) -> str:
|
||||||
|
@ -353,9 +353,7 @@ class FlowListBox(urwid.ListBox):
|
|||||||
|
|
||||||
def keypress(self, size, key):
|
def keypress(self, size, key):
|
||||||
key = common.shortcuts(key)
|
key = common.shortcuts(key)
|
||||||
if key == ":":
|
if key == "A":
|
||||||
signals.status_prompt_command.send()
|
|
||||||
elif key == "A":
|
|
||||||
for f in self.master.view:
|
for f in self.master.view:
|
||||||
if f.intercepted:
|
if f.intercepted:
|
||||||
f.resume()
|
f.resume()
|
||||||
|
34
mitmproxy/tools/console/keymap.py
Normal file
34
mitmproxy/tools/console/keymap.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import typing
|
||||||
|
from mitmproxy.tools.console import commandeditor
|
||||||
|
|
||||||
|
|
||||||
|
class Keymap:
|
||||||
|
def __init__(self, master):
|
||||||
|
self.executor = commandeditor.CommandExecutor(master)
|
||||||
|
self.keys = {}
|
||||||
|
|
||||||
|
def add(self, key: str, command: str, context: str = "") -> None:
|
||||||
|
"""
|
||||||
|
Add a key to the key map. If context is empty, it's considered to be
|
||||||
|
a global binding.
|
||||||
|
"""
|
||||||
|
d = self.keys.setdefault(context, {})
|
||||||
|
d[key] = command
|
||||||
|
|
||||||
|
def get(self, context: str, key: str) -> typing.Optional[str]:
|
||||||
|
if context in self.keys:
|
||||||
|
return self.keys[context].get(key, None)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def handle(self, context: str, key: str) -> typing.Optional[str]:
|
||||||
|
"""
|
||||||
|
Returns the key if it has not been handled, or None.
|
||||||
|
"""
|
||||||
|
cmd = self.get(context, key)
|
||||||
|
if cmd:
|
||||||
|
return self.executor(cmd)
|
||||||
|
if cmd != "":
|
||||||
|
cmd = self.get("", key)
|
||||||
|
if cmd:
|
||||||
|
return self.executor(cmd)
|
||||||
|
return key
|
@ -24,6 +24,7 @@ from mitmproxy.tools.console import flowlist
|
|||||||
from mitmproxy.tools.console import flowview
|
from mitmproxy.tools.console import flowview
|
||||||
from mitmproxy.tools.console import grideditor
|
from mitmproxy.tools.console import grideditor
|
||||||
from mitmproxy.tools.console import help
|
from mitmproxy.tools.console import help
|
||||||
|
from mitmproxy.tools.console import keymap
|
||||||
from mitmproxy.tools.console import options
|
from mitmproxy.tools.console import options
|
||||||
from mitmproxy.tools.console import commands
|
from mitmproxy.tools.console import commands
|
||||||
from mitmproxy.tools.console import overlay
|
from mitmproxy.tools.console import overlay
|
||||||
@ -75,6 +76,58 @@ class UnsupportedLog:
|
|||||||
signals.add_log(strutils.bytes_to_escaped_str(message.content), "debug")
|
signals.add_log(strutils.bytes_to_escaped_str(message.content), "debug")
|
||||||
|
|
||||||
|
|
||||||
|
class ConsoleCommands:
|
||||||
|
"""
|
||||||
|
An addon that exposes console-specific commands.
|
||||||
|
"""
|
||||||
|
def __init__(self, master):
|
||||||
|
self.master = master
|
||||||
|
|
||||||
|
def command(self) -> None:
|
||||||
|
"""Prompt for a command."""
|
||||||
|
signals.status_prompt_command.send()
|
||||||
|
|
||||||
|
def view_commands(self) -> None:
|
||||||
|
"""View the commands list."""
|
||||||
|
self.master.view_commands()
|
||||||
|
|
||||||
|
def view_options(self) -> None:
|
||||||
|
"""View the options editor."""
|
||||||
|
self.master.view_options()
|
||||||
|
|
||||||
|
def view_help(self) -> None:
|
||||||
|
"""View help."""
|
||||||
|
self.master.view_help()
|
||||||
|
|
||||||
|
def exit(self) -> None:
|
||||||
|
"""Exit mitmproxy."""
|
||||||
|
raise urwid.ExitMainLoop
|
||||||
|
|
||||||
|
def view_pop(self) -> None:
|
||||||
|
"""
|
||||||
|
Pop a view off the console stack. At the top level, this prompts the
|
||||||
|
user to exit mitmproxy.
|
||||||
|
"""
|
||||||
|
signals.pop_view_state.send(self)
|
||||||
|
|
||||||
|
def load(self, l):
|
||||||
|
l.add_command("console.command", self.command)
|
||||||
|
l.add_command("console.exit", self.exit)
|
||||||
|
l.add_command("console.view.commands", self.view_commands)
|
||||||
|
l.add_command("console.view.help", self.view_help)
|
||||||
|
l.add_command("console.view.options", self.view_options)
|
||||||
|
l.add_command("console.view.pop", self.view_pop)
|
||||||
|
|
||||||
|
|
||||||
|
def default_keymap(km):
|
||||||
|
km.add(":", "console.command")
|
||||||
|
km.add("?", "console.view.help")
|
||||||
|
km.add("C", "console.view.commands")
|
||||||
|
km.add("O", "console.view.options")
|
||||||
|
km.add("Q", "console.exit")
|
||||||
|
km.add("q", "console.view.pop")
|
||||||
|
|
||||||
|
|
||||||
class ConsoleMaster(master.Master):
|
class ConsoleMaster(master.Master):
|
||||||
|
|
||||||
def __init__(self, options, server):
|
def __init__(self, options, server):
|
||||||
@ -84,6 +137,8 @@ class ConsoleMaster(master.Master):
|
|||||||
self.stream_path = None
|
self.stream_path = None
|
||||||
# This line is just for type hinting
|
# This line is just for type hinting
|
||||||
self.options = self.options # type: Options
|
self.options = self.options # type: Options
|
||||||
|
self.keymap = keymap.Keymap(self)
|
||||||
|
default_keymap(self.keymap)
|
||||||
self.options.errored.connect(self.options_error)
|
self.options.errored.connect(self.options_error)
|
||||||
|
|
||||||
self.logbuffer = urwid.SimpleListWalker([])
|
self.logbuffer = urwid.SimpleListWalker([])
|
||||||
@ -102,6 +157,7 @@ class ConsoleMaster(master.Master):
|
|||||||
self.view,
|
self.view,
|
||||||
UnsupportedLog(),
|
UnsupportedLog(),
|
||||||
readfile.ReadFile(),
|
readfile.ReadFile(),
|
||||||
|
ConsoleCommands(self),
|
||||||
)
|
)
|
||||||
|
|
||||||
def sigint_handler(*args, **kwargs):
|
def sigint_handler(*args, **kwargs):
|
||||||
@ -331,12 +387,13 @@ class ConsoleMaster(master.Master):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def view_help(self, helpctx):
|
def view_help(self):
|
||||||
|
hc = self.view_stack[0].helpctx
|
||||||
signals.push_view_state.send(
|
signals.push_view_state.send(
|
||||||
self,
|
self,
|
||||||
window = window.Window(
|
window = window.Window(
|
||||||
self,
|
self,
|
||||||
help.HelpView(helpctx),
|
help.HelpView(hc),
|
||||||
None,
|
None,
|
||||||
statusbar.StatusBar(self, help.footer),
|
statusbar.StatusBar(self, help.footer),
|
||||||
None
|
None
|
||||||
|
@ -82,8 +82,9 @@ class Window(urwid.Frame):
|
|||||||
|
|
||||||
def keypress(self, size, k):
|
def keypress(self, size, k):
|
||||||
k = super().keypress(size, k)
|
k = super().keypress(size, k)
|
||||||
if k == "?":
|
k = self.master.keymap.handle("", k)
|
||||||
self.master.view_help(self.helpctx)
|
if not k:
|
||||||
|
return
|
||||||
elif k == "i":
|
elif k == "i":
|
||||||
signals.status_prompt.send(
|
signals.status_prompt.send(
|
||||||
self,
|
self,
|
||||||
@ -91,23 +92,5 @@ class Window(urwid.Frame):
|
|||||||
text = self.master.options.intercept,
|
text = self.master.options.intercept,
|
||||||
callback = self.master.options.setter("intercept")
|
callback = self.master.options.setter("intercept")
|
||||||
)
|
)
|
||||||
elif k == "C":
|
|
||||||
self.master.view_commands()
|
|
||||||
elif k == "O":
|
|
||||||
self.master.view_options()
|
|
||||||
elif k == "Q":
|
|
||||||
raise urwid.ExitMainLoop
|
|
||||||
elif k == "q":
|
|
||||||
signals.pop_view_state.send(self)
|
|
||||||
elif k == "R":
|
|
||||||
signals.status_prompt_onekey.send(
|
|
||||||
self,
|
|
||||||
prompt = "Replay",
|
|
||||||
keys = (
|
|
||||||
("client", "c"),
|
|
||||||
("server", "s"),
|
|
||||||
),
|
|
||||||
callback = self.handle_replay,
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
return k
|
return k
|
||||||
|
@ -18,6 +18,9 @@ class TAddon:
|
|||||||
def cmd2(self, foo: str) -> str:
|
def cmd2(self, foo: str) -> str:
|
||||||
return 99
|
return 99
|
||||||
|
|
||||||
|
def empty(self) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class TestCommand:
|
class TestCommand:
|
||||||
def test_call(self):
|
def test_call(self):
|
||||||
@ -50,6 +53,9 @@ def test_simple():
|
|||||||
with pytest.raises(exceptions.CommandError, match="Usage"):
|
with pytest.raises(exceptions.CommandError, match="Usage"):
|
||||||
c.call("one.two too many args")
|
c.call("one.two too many args")
|
||||||
|
|
||||||
|
c.add("empty", a.empty)
|
||||||
|
c.call("empty")
|
||||||
|
|
||||||
|
|
||||||
def test_typename():
|
def test_typename():
|
||||||
assert command.typename(str, True) == "str"
|
assert command.typename(str, True) == "str"
|
||||||
|
Loading…
Reference in New Issue
Block a user