commander: tab completion for command names

This commit is contained in:
Aldo Cortesi 2017-12-15 09:43:09 +13:00
parent 4d358c49fb
commit 8c0ba71fd8
3 changed files with 77 additions and 10 deletions

View File

@ -125,7 +125,7 @@ class Command:
class ParseResult(typing.NamedTuple):
value: str
type: type
type: typing.Type
class CommandManager:

View File

@ -1,23 +1,51 @@
import urwid
from urwid.text_layout import calc_coords
import typing
import abc
import mitmproxy.master
import mitmproxy.command
class CompletionState:
def __init__(self, parts: typing.Sequence[mitmproxy.command.ParseResult]) -> None:
self.parts = parts
class Completer:
@abc.abstractmethod
def cycle(self) -> str:
pass
class ListCompleter(Completer):
def __init__(
self,
start: str,
options: typing.Sequence[str],
) -> None:
self.start = start
self.options = [] # type: typing.Sequence[str]
for o in options:
if o.startswith(start):
self.options.append(o)
self.offset = 0
def cycle(self) -> str:
if not self.options:
return self.start
ret = self.options[self.offset]
self.offset = (self.offset + 1) % len(self.options)
return ret
class CompletionState(typing.NamedTuple):
completer: Completer
parse: typing.Sequence[mitmproxy.command.ParseResult]
class CommandBuffer():
def __init__(self, master: mitmproxy.master.Master, start: str = "") -> None:
self.master = master
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)].
# Cursor is always within the range [0:len(buffer)].
self._cursor = len(self.buf)
self.completion = None # type: CompletionState
@property
def cursor(self) -> int:
@ -42,16 +70,29 @@ class CommandBuffer():
self.cursor = self.cursor + 1
def cycle_completion(self) -> None:
parts = self.master.commands.parse_partial(self.buf[:self.cursor])
if parts[-1][1] == str:
return
raise ValueError
if not self.completion:
parts = self.master.commands.parse_partial(self.buf[:self.cursor])
if parts[-1].type == mitmproxy.command.Cmd:
self.completion = CompletionState(
completer = ListCompleter(
parts[-1].value,
self.master.commands.commands.keys(),
),
parse = parts,
)
if self.completion:
nxt = self.completion.completer.cycle()
buf = " ".join([i.value for i in self.completion.parse[:-1]]) + " " + nxt
buf = buf.strip()
self.buf = buf
self.cursor = len(self.buf)
def backspace(self) -> None:
if self.cursor == 0:
return
self.buf = self.buf[:self.cursor - 1] + self.buf[self.cursor:]
self.cursor = self.cursor - 1
self.completion = None
def insert(self, k: str) -> None:
"""
@ -59,6 +100,7 @@ class CommandBuffer():
"""
self.buf = self.buf = self.buf[:self.cursor] + k + self.buf[self.cursor:]
self.cursor += 1
self.completion = None
class CommandEdit(urwid.WidgetWrap):

View File

@ -2,6 +2,31 @@ from mitmproxy.tools.console.commander import commander
from mitmproxy.test import taddons
class TestListCompleter:
def test_cycle(self):
tests = [
[
"",
["a", "b", "c"],
["a", "b", "c", "a"]
],
[
"xxx",
["a", "b", "c"],
["xxx", "xxx", "xxx"]
],
[
"b",
["a", "b", "ba", "bb", "c"],
["b", "ba", "bb", "b"]
],
]
for start, options, cycle in tests:
c = commander.ListCompleter(start, options)
for expected in cycle:
assert c.cycle() == expected
class TestCommandBuffer:
def test_backspace(self):