Introduce a custom widget for command editing

The builtin urwid.Edit widget is not sufficiently flexible for what we want to
do.
This commit is contained in:
Aldo Cortesi 2017-12-14 12:24:46 +13:00
parent 21324086c3
commit 04e19f9171
5 changed files with 126 additions and 11 deletions

View File

@ -1,19 +1,10 @@
import typing import typing
import urwid
from mitmproxy import exceptions from mitmproxy import exceptions
from mitmproxy import flow from mitmproxy import flow
from mitmproxy.tools.console import signals from mitmproxy.tools.console import signals
class CommandEdit(urwid.Edit):
def __init__(self, partial):
urwid.Edit.__init__(self, ":", partial)
def keypress(self, size, key):
return urwid.Edit.keypress(self, size, key)
class CommandExecutor: class CommandExecutor:
def __init__(self, master): def __init__(self, master):
self.master = master self.master = master

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,85 @@
import urwid
from urwid.text_layout import calc_coords
class CommandBuffer():
def __init__(self, start: str = ""):
self.buf = start
# This is the logical cursor position - the display cursor is one
# character further on. Cursor is always within the range [0:len(buffer)].
self._cursor = len(self.buf)
@property
def cursor(self):
return self._cursor
@cursor.setter
def cursor(self, x):
if x < 0:
self._cursor = 0
elif x > len(self.buf):
self._cursor = len(self.buf)
else:
self._cursor = x
def render(self):
return self.buf
def left(self):
self.cursor = self.cursor - 1
def right(self):
self.cursor = self.cursor + 1
def backspace(self):
if self.cursor == 0:
return
self.buf = self.buf[:self.cursor - 1] + self.buf[self.cursor:]
self.cursor = self.cursor - 1
def insert(self, k: str):
"""
Inserts text at the cursor.
"""
self.buf = self.buf = self.buf[:self.cursor] + k + self.buf[self.cursor:]
self.cursor += 1
class CommandEdit(urwid.WidgetWrap):
leader = ": "
def __init__(self, text):
self.cbuf = CommandBuffer(text)
self._w = urwid.Text(self.leader)
self.update()
def keypress(self, size, key):
if key == "backspace":
self.cbuf.backspace()
elif key == "left":
self.cbuf.left()
elif key == "right":
self.cbuf.right()
elif len(key) == 1:
self.cbuf.insert(key)
self.update()
def update(self):
self._w.set_text([self.leader, self.cbuf.render()])
def render(self, size, focus=False):
(maxcol,) = size
canv = self._w.render((maxcol,))
canv = urwid.CompositeCanvas(canv)
canv.cursor = self.get_cursor_coords((maxcol,))
return canv
def get_cursor_coords(self, size):
p = self.cbuf.cursor + len(self.leader)
trans = self._w.get_line_translation(size[0])
x, y = calc_coords(self._w.get_text()[0], trans, p)
return x, y
def get_value(self):
return self.cbuf.buf

View File

@ -6,6 +6,7 @@ from mitmproxy.tools.console import common
from mitmproxy.tools.console import signals from mitmproxy.tools.console import signals
from mitmproxy.tools.console import commandeditor from mitmproxy.tools.console import commandeditor
import mitmproxy.tools.console.master # noqa import mitmproxy.tools.console.master # noqa
from mitmproxy.tools.console.commander import commander
class PromptPath: class PromptPath:
@ -66,7 +67,7 @@ class ActionBar(urwid.WidgetWrap):
def sig_prompt_command(self, sender, partial=""): def sig_prompt_command(self, sender, partial=""):
signals.focus.send(self, section="footer") signals.focus.send(self, section="footer")
self._w = commandeditor.CommandEdit(partial) self._w = commander.CommandEdit(partial)
self.prompting = commandeditor.CommandExecutor(self.master) self.prompting = commandeditor.CommandExecutor(self.master)
def sig_prompt_onekey(self, sender, prompt, keys, callback, args=()): def sig_prompt_onekey(self, sender, prompt, keys, callback, args=()):
@ -100,7 +101,7 @@ class ActionBar(urwid.WidgetWrap):
elif k in self.onekey: elif k in self.onekey:
self.prompt_execute(k) self.prompt_execute(k)
elif k == "enter": elif k == "enter":
self.prompt_execute(self._w.get_edit_text()) self.prompt_execute(self._w.get_value())
else: else:
if common.is_keypress(k): if common.is_keypress(k):
self._w.keypress(size, k) self._w.keypress(size, k)

View File

@ -0,0 +1,37 @@
from mitmproxy.tools.console.commander import commander
class TestCommandBuffer:
def test_backspace(self):
tests = [
[("", 0), ("", 0)],
[("1", 0), ("1", 0)],
[("1", 1), ("", 0)],
[("123", 3), ("12", 2)],
[("123", 2), ("13", 1)],
[("123", 0), ("123", 0)],
]
for start, output in tests:
cb = commander.CommandBuffer()
cb.buf, cb.cursor = start[0], start[1]
cb.backspace()
assert cb.buf == output[0]
assert cb.cursor == output[1]
def test_insert(self):
tests = [
[("", 0), ("x", 1)],
[("a", 0), ("xa", 1)],
[("xa", 2), ("xax", 3)],
]
for start, output in tests:
cb = commander.CommandBuffer()
cb.buf, cb.cursor = start[0], start[1]
cb.insert("x")
assert cb.buf == output[0]
assert cb.cursor == output[1]