aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--mitmproxy/addons/__init__.py2
-rw-r--r--mitmproxy/addons/command_history.py144
-rw-r--r--mitmproxy/command.py6
-rw-r--r--mitmproxy/tools/_main.py1
-rw-r--r--mitmproxy/tools/console/commander/commander.py75
-rw-r--r--mitmproxy/tools/console/statusbar.py11
-rw-r--r--test/mitmproxy/addons/test_command_history.py306
-rw-r--r--test/mitmproxy/tools/console/test_commander.py405
8 files changed, 733 insertions, 217 deletions
diff --git a/mitmproxy/addons/__init__.py b/mitmproxy/addons/__init__.py
index 838fba9b..ee238938 100644
--- a/mitmproxy/addons/__init__.py
+++ b/mitmproxy/addons/__init__.py
@@ -4,6 +4,7 @@ from mitmproxy.addons import block
from mitmproxy.addons import browser
from mitmproxy.addons import check_ca
from mitmproxy.addons import clientplayback
+from mitmproxy.addons import command_history
from mitmproxy.addons import core
from mitmproxy.addons import cut
from mitmproxy.addons import disable_h2c
@@ -30,6 +31,7 @@ def default_addons():
anticomp.AntiComp(),
check_ca.CheckCA(),
clientplayback.ClientPlayback(),
+ command_history.CommandHistory(),
cut.Cut(),
disable_h2c.DisableH2C(),
export.Export(),
diff --git a/mitmproxy/addons/command_history.py b/mitmproxy/addons/command_history.py
new file mode 100644
index 00000000..ad4c2346
--- /dev/null
+++ b/mitmproxy/addons/command_history.py
@@ -0,0 +1,144 @@
+import atexit
+import collections
+import os
+import typing
+
+from mitmproxy import command
+from mitmproxy import ctx
+
+
+class CommandHistory:
+ def __init__(self, size: int = 300) -> None:
+ self.saved_commands: typing.Deque[str] = collections.deque(maxlen=size)
+ self.is_configured = False
+
+ self.filtered_commands: typing.Deque[str] = collections.deque()
+ self.current_index: int = -1
+ self.filter_str: str = ''
+ self.command_history_path: str = ''
+
+ atexit.register(self.cleanup)
+
+ def cleanup(self):
+ self._sync_saved_commands()
+
+ @property
+ def last_filtered_index(self):
+ return len(self.filtered_commands) - 1
+
+ @command.command("command_history.clear")
+ def clear_history(self):
+ self.saved_commands.clear()
+ self.filtered_commands.clear()
+
+ with open(self.command_history_path, 'w') as f:
+ f.truncate(0)
+ f.seek(0)
+ f.flush()
+ f.close()
+
+ self.restart()
+
+ @command.command("command_history.cancel")
+ def restart(self) -> None:
+ self.filtered_commands = self.saved_commands.copy()
+ self.current_index = -1
+
+ @command.command("command_history.next")
+ def get_next(self) -> str:
+
+ if self.current_index == -1 or self.current_index == self.last_filtered_index:
+ self.current_index = -1
+ return ''
+ elif self.current_index < self.last_filtered_index:
+ self.current_index += 1
+
+ ret = self.filtered_commands[self.current_index]
+
+ return ret
+
+ @command.command("command_history.prev")
+ def get_prev(self) -> str:
+
+ if self.current_index == -1:
+ if self.last_filtered_index >= 0:
+ self.current_index = self.last_filtered_index
+ else:
+ return ''
+
+ elif self.current_index > 0:
+ self.current_index -= 1
+
+ ret = self.filtered_commands[self.current_index]
+
+ return ret
+
+ @command.command("command_history.filter")
+ def set_filter(self, command: str) -> None:
+ self.filter_str = command
+
+ _filtered_commands = [c for c in self.saved_commands if c.startswith(command)]
+ self.filtered_commands = collections.deque(_filtered_commands)
+
+ if command and command not in self.filtered_commands:
+ self.filtered_commands.append(command)
+
+ self.current_index = -1
+
+ @command.command("command_history.add")
+ def add_command(self, command: str) -> None:
+ if command.strip() == '':
+ return
+
+ self._sync_saved_commands()
+
+ if command in self.saved_commands:
+ self.saved_commands.remove(command)
+ self.saved_commands.append(command)
+
+ _history_str = "\n".join(self.saved_commands)
+ with open(self.command_history_path, 'w') as f:
+ f.truncate(0)
+ f.seek(0)
+ f.write(_history_str)
+ f.flush()
+ f.close()
+
+ self.restart()
+
+ def _sync_saved_commands(self):
+ # First read all commands from the file to merge anything that may
+ # have come from a different instance of the mitmproxy or sister tools
+ if not os.path.exists(self.command_history_path):
+ return
+
+ with open(self.command_history_path, 'r') as f:
+ _history_lines = f.readlines()
+ f.close()
+
+ self.saved_commands.clear()
+ for l in _history_lines:
+ l = l.strip()
+ if l in self.saved_commands:
+ self.saved_commands.remove(l)
+ self.saved_commands.append(l.strip())
+
+ def configure(self, updated: typing.Set[str]):
+ if self.is_configured:
+ return
+
+ _command_history_dir = os.path.expanduser(ctx.options.confdir)
+ if not os.path.exists(_command_history_dir):
+ os.makedirs(_command_history_dir)
+
+ self.command_history_path = os.path.join(_command_history_dir, 'command_history')
+ _history_lines: typing.List[str] = []
+ if os.path.exists(self.command_history_path):
+ with open(self.command_history_path, 'r') as f:
+ _history_lines = f.readlines()
+ f.close()
+
+ for l in _history_lines:
+ self.add_command(l.strip())
+
+ self.is_configured = True
diff --git a/mitmproxy/command.py b/mitmproxy/command.py
index 6977ff91..48f9051e 100644
--- a/mitmproxy/command.py
+++ b/mitmproxy/command.py
@@ -100,8 +100,10 @@ class Command:
def prepare_args(self, args: typing.Sequence[str]) -> inspect.BoundArguments:
try:
bound_arguments = self.signature.bind(*args)
- except TypeError as v:
- raise exceptions.CommandError(f"Command argument mismatch: {v.args[0]}")
+ except TypeError:
+ expected = f'Expected: {str(self.signature.parameters)}'
+ received = f'Received: {str(args)}'
+ raise exceptions.CommandError(f"Command argument mismatch: \n\t{expected}\n\t{received}")
for name, value in bound_arguments.arguments.items():
convert_to = self.signature.parameters[name].annotation
diff --git a/mitmproxy/tools/_main.py b/mitmproxy/tools/_main.py
index 0163e8d3..b98bbe90 100644
--- a/mitmproxy/tools/_main.py
+++ b/mitmproxy/tools/_main.py
@@ -83,6 +83,7 @@ def run(
except SystemExit:
arg_check.check()
sys.exit(1)
+
try:
opts.set(*args.setoptions, defer=True)
optmanager.load_paths(
diff --git a/mitmproxy/tools/console/commander/commander.py b/mitmproxy/tools/console/commander/commander.py
index 02eda3bf..0f45aa1f 100644
--- a/mitmproxy/tools/console/commander/commander.py
+++ b/mitmproxy/tools/console/commander/commander.py
@@ -1,6 +1,4 @@
import abc
-import collections
-import copy
import typing
import urwid
@@ -11,6 +9,8 @@ import mitmproxy.flow
import mitmproxy.master
import mitmproxy.types
+from mitmproxy import command_lexer
+
class Completer:
@abc.abstractmethod
@@ -158,53 +158,15 @@ class CommandBuffer:
self.completion = None
-class CommandHistory:
- def __init__(self, master: mitmproxy.master.Master, size: int = 30) -> None:
- self.saved_commands: collections.deque = collections.deque(
- [CommandBuffer(master, "")],
- maxlen=size
- )
- self.index: int = 0
-
- @property
- def last_index(self):
- return len(self.saved_commands) - 1
-
- def get_next(self) -> typing.Optional[CommandBuffer]:
- if self.index < self.last_index:
- self.index = self.index + 1
- return self.saved_commands[self.index]
- return None
-
- def get_prev(self) -> typing.Optional[CommandBuffer]:
- if self.index > 0:
- self.index = self.index - 1
- return self.saved_commands[self.index]
- return None
-
- def add_command(self, command: CommandBuffer, execution: bool = False) -> None:
- if self.index == self.last_index or execution:
- last_item = self.saved_commands[-1]
- last_item_empty = not last_item.text
- if last_item.text == command.text or (last_item_empty and execution):
- self.saved_commands[-1] = copy.copy(command)
- else:
- self.saved_commands.append(command)
- if not execution and self.index < self.last_index:
- self.index += 1
- if execution:
- self.index = self.last_index
-
-
class CommandEdit(urwid.WidgetWrap):
leader = ": "
- def __init__(self, master: mitmproxy.master.Master,
- text: str, history: CommandHistory) -> None:
+ def __init__(self, master: mitmproxy.master.Master, text: str) -> None:
super().__init__(urwid.Text(self.leader))
self.master = master
+ self.active_filter = False
+ self.filter_str = ''
self.cbuf = CommandBuffer(master, text)
- self.history = history
self.update()
def keypress(self, size, key) -> None:
@@ -236,15 +198,36 @@ class CommandEdit(urwid.WidgetWrap):
self.cbuf.cursor = cursor_pos
elif key == "backspace":
self.cbuf.backspace()
+ if self.cbuf.text == '':
+ self.active_filter = False
+ self.master.commands.execute("command_history.filter ''")
+ self.filter_str = ''
elif key == "left" or key == "ctrl b":
self.cbuf.left()
elif key == "right" or key == "ctrl f":
self.cbuf.right()
elif key == "up" or key == "ctrl p":
- self.history.add_command(self.cbuf)
- self.cbuf = self.history.get_prev() or self.cbuf
+ if self.active_filter is False:
+ self.active_filter = True
+ self.filter_str = self.cbuf.text
+ _cmd = command_lexer.quote(self.cbuf.text)
+ self.master.commands.execute("command_history.filter %s" % _cmd)
+ cmd = self.master.commands.execute("command_history.prev")
+ self.cbuf = CommandBuffer(self.master, cmd)
elif key == "down" or key == "ctrl n":
- self.cbuf = self.history.get_next() or self.cbuf
+ prev_cmd = self.cbuf.text
+ cmd = self.master.commands.execute("command_history.next")
+
+ if cmd == '':
+ if prev_cmd == self.filter_str:
+ self.cbuf = CommandBuffer(self.master, prev_cmd)
+ else:
+ self.active_filter = False
+ self.master.commands.execute("command_history.filter ''")
+ self.filter_str = ''
+ self.cbuf = CommandBuffer(self.master, '')
+ else:
+ self.cbuf = CommandBuffer(self.master, cmd)
elif key == "shift tab":
self.cbuf.cycle_completion(False)
elif key == "tab":
diff --git a/mitmproxy/tools/console/statusbar.py b/mitmproxy/tools/console/statusbar.py
index 43f5170d..39141b97 100644
--- a/mitmproxy/tools/console/statusbar.py
+++ b/mitmproxy/tools/console/statusbar.py
@@ -3,10 +3,11 @@ from typing import Optional
import urwid
+import mitmproxy.tools.console.master # noqa
+from mitmproxy import command_lexer
from mitmproxy.tools.console import common
from mitmproxy.tools.console import signals
from mitmproxy.tools.console import commandexecutor
-import mitmproxy.tools.console.master # noqa
from mitmproxy.tools.console.commander import commander
@@ -43,8 +44,6 @@ class ActionBar(urwid.WidgetWrap):
signals.status_prompt_onekey.connect(self.sig_prompt_onekey)
signals.status_prompt_command.connect(self.sig_prompt_command)
- self.command_history = commander.CommandHistory(master)
-
self.prompting = None
self.onekey = False
@@ -104,7 +103,6 @@ class ActionBar(urwid.WidgetWrap):
self._w = commander.CommandEdit(
self.master,
partial,
- self.command_history,
)
if cursor is not None:
self._w.cbuf.cursor = cursor
@@ -134,7 +132,7 @@ class ActionBar(urwid.WidgetWrap):
def keypress(self, size, k):
if self.prompting:
if k == "esc":
- self.command_history.index = self.command_history.last_index
+ self.master.commands.execute('command_history.cancel')
self.prompt_done()
elif self.onekey:
if k == "enter":
@@ -142,7 +140,8 @@ class ActionBar(urwid.WidgetWrap):
elif k in self.onekey:
self.prompt_execute(k)
elif k == "enter":
- self.command_history.add_command(self._w.cbuf, True)
+ cmd = command_lexer.quote(self._w.cbuf.text)
+ self.master.commands.execute(f"command_history.add {cmd}")
self.prompt_execute(self._w.get_edit_text())
else:
if common.is_keypress(k):
diff --git a/test/mitmproxy/addons/test_command_history.py b/test/mitmproxy/addons/test_command_history.py
new file mode 100644
index 00000000..026ce53e
--- /dev/null
+++ b/test/mitmproxy/addons/test_command_history.py
@@ -0,0 +1,306 @@
+import os
+import pytest
+
+from mitmproxy import options
+from mitmproxy.addons import command_history
+from mitmproxy.test import taddons
+
+
+@pytest.fixture(autouse=True)
+def tctx(tmpdir):
+ # This runs before each test
+ dir_name = tmpdir.mkdir('mitmproxy').dirname
+ confdir = dir_name
+
+ opts = options.Options()
+ opts.set(*[f"confdir={confdir}"])
+ tctx = taddons.context(options=opts)
+ ch = command_history.CommandHistory()
+ tctx.master.addons.add(ch)
+ ch.configure([])
+
+ yield tctx
+
+ # This runs after each test
+ ch.cleanup()
+
+
+class TestCommandHistory:
+ def test_existing_command_history(self, tctx):
+ commands = ['cmd1', 'cmd2', 'cmd3']
+ confdir = tctx.options.confdir
+ f = open(os.path.join(confdir, 'command_history'), 'w')
+ f.write("\n".join(commands))
+ f.close()
+
+ history = command_history.CommandHistory()
+ history.configure([])
+
+ saved_commands = [cmd for cmd in history.saved_commands]
+ assert saved_commands == ['cmd1', 'cmd2', 'cmd3']
+
+ history.cleanup()
+
+ def test_add_command(self, tctx):
+ history = command_history.CommandHistory(3)
+ history.configure([])
+
+ history.add_command('cmd1')
+ history.add_command('cmd2')
+
+ saved_commands = [cmd for cmd in history.saved_commands]
+ assert saved_commands == ['cmd1', 'cmd2']
+
+ history.add_command('')
+ saved_commands = [cmd for cmd in history.saved_commands]
+ assert saved_commands == ['cmd1', 'cmd2']
+
+ # The history size is only 3. So, we forget the first
+ # one command, when adding fourth command
+ history.add_command('cmd3')
+ history.add_command('cmd4')
+ saved_commands = [cmd for cmd in history.saved_commands]
+ assert saved_commands == ['cmd2', 'cmd3', 'cmd4']
+
+ history.add_command('')
+ saved_commands = [cmd for cmd in history.saved_commands]
+ assert saved_commands == ['cmd2', 'cmd3', 'cmd4']
+
+ # Commands with the same text are not repeated in the history one by one
+ history.add_command('cmd3')
+ saved_commands = [cmd for cmd in history.saved_commands]
+ assert saved_commands == ['cmd2', 'cmd4', 'cmd3']
+
+ history.add_command('cmd2')
+ saved_commands = [cmd for cmd in history.saved_commands]
+ assert saved_commands == ['cmd4', 'cmd3', 'cmd2']
+
+ history.cleanup()
+
+ def test_get_next_and_prev(self, tctx):
+ history = command_history.CommandHistory(5)
+ history.configure([])
+
+ history.add_command('cmd1')
+
+ assert history.get_next() == ''
+ assert history.get_next() == ''
+ assert history.get_prev() == 'cmd1'
+ assert history.get_prev() == 'cmd1'
+ assert history.get_prev() == 'cmd1'
+ assert history.get_next() == ''
+ assert history.get_next() == ''
+
+ history.add_command('cmd2')
+
+ assert history.get_next() == ''
+ assert history.get_next() == ''
+ assert history.get_prev() == 'cmd2'
+ assert history.get_prev() == 'cmd1'
+ assert history.get_prev() == 'cmd1'
+ assert history.get_next() == 'cmd2'
+ assert history.get_next() == ''
+ assert history.get_next() == ''
+
+ history.add_command('cmd3')
+
+ assert history.get_next() == ''
+ assert history.get_next() == ''
+ assert history.get_prev() == 'cmd3'
+ assert history.get_prev() == 'cmd2'
+ assert history.get_prev() == 'cmd1'
+ assert history.get_prev() == 'cmd1'
+ assert history.get_next() == 'cmd2'
+ assert history.get_next() == 'cmd3'
+ assert history.get_next() == ''
+ assert history.get_next() == ''
+ assert history.get_prev() == 'cmd3'
+ assert history.get_prev() == 'cmd2'
+
+ history.add_command('cmd4')
+
+ assert history.get_prev() == 'cmd4'
+ assert history.get_prev() == 'cmd3'
+ assert history.get_prev() == 'cmd2'
+ assert history.get_prev() == 'cmd1'
+ assert history.get_prev() == 'cmd1'
+ assert history.get_next() == 'cmd2'
+ assert history.get_next() == 'cmd3'
+ assert history.get_next() == 'cmd4'
+ assert history.get_next() == ''
+ assert history.get_next() == ''
+
+ history.add_command('cmd5')
+ history.add_command('cmd6')
+
+ assert history.get_next() == ''
+ assert history.get_prev() == 'cmd6'
+ assert history.get_prev() == 'cmd5'
+ assert history.get_prev() == 'cmd4'
+ assert history.get_next() == 'cmd5'
+ assert history.get_prev() == 'cmd4'
+ assert history.get_prev() == 'cmd3'
+ assert history.get_prev() == 'cmd2'
+ assert history.get_next() == 'cmd3'
+ assert history.get_prev() == 'cmd2'
+ assert history.get_prev() == 'cmd2'
+ assert history.get_next() == 'cmd3'
+ assert history.get_next() == 'cmd4'
+ assert history.get_next() == 'cmd5'
+ assert history.get_next() == 'cmd6'
+ assert history.get_next() == ''
+ assert history.get_next() == ''
+
+ history.cleanup()
+
+ def test_clear(self, tctx):
+ history = command_history.CommandHistory(3)
+ history.configure([])
+
+ history.add_command('cmd1')
+ history.add_command('cmd2')
+ history.clear_history()
+
+ saved_commands = [cmd for cmd in history.saved_commands]
+ assert saved_commands == []
+
+ assert history.get_next() == ''
+ assert history.get_next() == ''
+ assert history.get_prev() == ''
+ assert history.get_prev() == ''
+
+ history.cleanup()
+
+ def test_filter(self, tctx):
+ history = command_history.CommandHistory(3)
+ history.configure([])
+
+ history.add_command('cmd1')
+ history.add_command('cmd2')
+ history.add_command('abc')
+ history.set_filter('c')
+
+ assert history.get_next() == ''
+ assert history.get_next() == ''
+ assert history.get_prev() == 'c'
+ assert history.get_prev() == 'cmd2'
+ assert history.get_prev() == 'cmd1'
+ assert history.get_prev() == 'cmd1'
+ assert history.get_next() == 'cmd2'
+ assert history.get_next() == 'c'
+ assert history.get_next() == ''
+ assert history.get_next() == ''
+
+ history.set_filter('')
+
+ assert history.get_next() == ''
+ assert history.get_next() == ''
+ assert history.get_prev() == 'abc'
+ assert history.get_prev() == 'cmd2'
+ assert history.get_prev() == 'cmd1'
+ assert history.get_prev() == 'cmd1'
+ assert history.get_next() == 'cmd2'
+ assert history.get_next() == 'abc'
+ assert history.get_next() == ''
+ assert history.get_next() == ''
+
+ history.cleanup()
+
+ def test_multiple_instances(self, tctx):
+ instances = [
+ command_history.CommandHistory(10),
+ command_history.CommandHistory(10),
+ command_history.CommandHistory(10)
+ ]
+
+ for i in instances:
+ i.configure([])
+ saved_commands = [cmd for cmd in i.saved_commands]
+ assert saved_commands == []
+
+ instances[0].add_command('cmd1')
+ saved_commands = [cmd for cmd in instances[0].saved_commands]
+ assert saved_commands == ['cmd1']
+
+ # These instances haven't yet added a new command, so they haven't
+ # yet reloaded their commands from the command file.
+ # This is expected, because if the user is filtering a command on
+ # another window, we don't want to interfere with that
+ saved_commands = [cmd for cmd in instances[1].saved_commands]
+ assert saved_commands == []
+ saved_commands = [cmd for cmd in instances[2].saved_commands]
+ assert saved_commands == []
+
+ # Since the second instanced added a new command, its list of
+ # saved commands has been updated to have the commands from the
+ # first instance + its own commands
+ instances[1].add_command('cmd2')
+ saved_commands = [cmd for cmd in instances[1].saved_commands]
+ assert saved_commands == ['cmd1', 'cmd2']
+
+ saved_commands = [cmd for cmd in instances[0].saved_commands]
+ assert saved_commands == ['cmd1']
+
+ # Third instance is still empty as it has not yet ran any command
+ saved_commands = [cmd for cmd in instances[2].saved_commands]
+ assert saved_commands == []
+
+ instances[2].add_command('cmd3')
+ saved_commands = [cmd for cmd in instances[2].saved_commands]
+ assert saved_commands == ['cmd1', 'cmd2', 'cmd3']
+
+ instances[0].add_command('cmd4')
+ saved_commands = [cmd for cmd in instances[0].saved_commands]
+ assert saved_commands == ['cmd1', 'cmd2', 'cmd3', 'cmd4']
+
+ instances.append(command_history.CommandHistory(10))
+ instances[3].configure([])
+ saved_commands = [cmd for cmd in instances[3].saved_commands]
+ assert saved_commands == ['cmd1', 'cmd2', 'cmd3', 'cmd4']
+
+ instances[0].add_command('cmd_before_close')
+ instances.pop(0)
+
+ saved_commands = [cmd for cmd in instances[0].saved_commands]
+ assert saved_commands == ['cmd1', 'cmd2']
+
+ instances[0].add_command('new_cmd')
+ saved_commands = [cmd for cmd in instances[0].saved_commands]
+ assert saved_commands == ['cmd1', 'cmd2', 'cmd3', 'cmd4', 'cmd_before_close', 'new_cmd']
+
+ instances.pop(0)
+ instances.pop(0)
+ instances.pop(0)
+
+ _path = os.path.join(tctx.options.confdir, 'command_history')
+ lines = open(_path, 'r').readlines()
+ saved_commands = [cmd.strip() for cmd in lines]
+ assert saved_commands == ['cmd1', 'cmd2', 'cmd3', 'cmd4', 'cmd_before_close', 'new_cmd']
+
+ instances = [
+ command_history.CommandHistory(10),
+ command_history.CommandHistory(10)
+ ]
+
+ for i in instances:
+ i.configure([])
+ i.clear_history()
+ saved_commands = [cmd for cmd in i.saved_commands]
+ assert saved_commands == []
+
+ instances[0].add_command('cmd1')
+ instances[0].add_command('cmd2')
+ instances[1].add_command('cmd3')
+ instances[1].add_command('cmd4')
+ instances[1].add_command('cmd5')
+
+ saved_commands = [cmd for cmd in instances[1].saved_commands]
+ assert saved_commands == ['cmd1', 'cmd2', 'cmd3', 'cmd4', 'cmd5']
+
+ instances.pop()
+ instances.pop()
+
+ _path = os.path.join(tctx.options.confdir, 'command_history')
+ lines = open(_path, 'r').readlines()
+ saved_commands = [cmd.strip() for cmd in lines]
+ assert saved_commands == ['cmd1', 'cmd2', 'cmd3', 'cmd4', 'cmd5']
diff --git a/test/mitmproxy/tools/console/test_commander.py b/test/mitmproxy/tools/console/test_commander.py
index a77be043..4fa10eb8 100644
--- a/test/mitmproxy/tools/console/test_commander.py
+++ b/test/mitmproxy/tools/console/test_commander.py
@@ -1,9 +1,30 @@
import pytest
+from mitmproxy import options
+from mitmproxy.addons import command_history
from mitmproxy.test import taddons
from mitmproxy.tools.console.commander import commander
+@pytest.fixture(autouse=True)
+def tctx(tmpdir):
+ # This runs before each test
+ dir_name = tmpdir.mkdir('mitmproxy').dirname
+ confdir = dir_name
+
+ opts = options.Options()
+ opts.set(*[f"confdir={confdir}"])
+ tctx = taddons.context(options=opts)
+ ch = command_history.CommandHistory()
+ tctx.master.addons.add(ch)
+ ch.configure([])
+
+ yield tctx
+
+ # This runs after each test
+ ch.cleanup()
+
+
class TestListCompleter:
def test_cycle(self):
tests = [
@@ -23,178 +44,237 @@ class TestListCompleter:
["b", "ba", "bb", "b"]
],
]
- for start, options, cycle in tests:
- c = commander.ListCompleter(start, options)
+ for start, opts, cycle in tests:
+ c = commander.ListCompleter(start, opts)
for expected in cycle:
assert c.cycle() == expected
class TestCommandEdit:
- def test_open_command_bar(self):
- with taddons.context() as tctx:
- history = commander.CommandHistory(tctx.master, size=3)
- edit = commander.CommandEdit(tctx.master, '', history)
- try:
- edit.update()
- except IndexError:
- pytest.faied("Unexpected IndexError")
+ def test_open_command_bar(self, tctx):
+ edit = commander.CommandEdit(tctx.master, '')
- def test_insert(self):
- with taddons.context() as tctx:
- history = commander.CommandHistory(tctx.master, size=3)
- edit = commander.CommandEdit(tctx.master, '', history)
- edit.keypress(1, 'a')
- assert edit.get_edit_text() == 'a'
-
- # Don't let users type a space before starting a command
- # as a usability feature
- history = commander.CommandHistory(tctx.master, size=3)
- edit = commander.CommandEdit(tctx.master, '', history)
- edit.keypress(1, ' ')
- assert edit.get_edit_text() == ''
+ try:
+ edit.update()
+ except IndexError:
+ pytest.faied("Unexpected IndexError")
- def test_backspace(self):
- with taddons.context() as tctx:
- history = commander.CommandHistory(tctx.master, size=3)
- edit = commander.CommandEdit(tctx.master, '', history)
- edit.keypress(1, 'a')
- edit.keypress(1, 'b')
- assert edit.get_edit_text() == 'ab'
- edit.keypress(1, 'backspace')
- assert edit.get_edit_text() == 'a'
+ def test_insert(self, tctx):
+ edit = commander.CommandEdit(tctx.master, '')
+ edit.keypress(1, 'a')
+ assert edit.get_edit_text() == 'a'
- def test_left(self):
- with taddons.context() as tctx:
- history = commander.CommandHistory(tctx.master, size=3)
- edit = commander.CommandEdit(tctx.master, '', history)
- edit.keypress(1, 'a')
- assert edit.cbuf.cursor == 1
- edit.keypress(1, 'left')
- assert edit.cbuf.cursor == 0
+ # Don't let users type a space before starting a command
+ # as a usability feature
+ edit = commander.CommandEdit(tctx.master, '')
+ edit.keypress(1, ' ')
+ assert edit.get_edit_text() == ''
- # Do it again to make sure it won't go negative
- edit.keypress(1, 'left')
- assert edit.cbuf.cursor == 0
+ def test_backspace(self, tctx):
+ edit = commander.CommandEdit(tctx.master, '')
- def test_right(self):
- with taddons.context() as tctx:
- history = commander.CommandHistory(tctx.master, size=3)
- edit = commander.CommandEdit(tctx.master, '', history)
- edit.keypress(1, 'a')
- assert edit.cbuf.cursor == 1
-
- # Make sure cursor won't go past the text
- edit.keypress(1, 'right')
- assert edit.cbuf.cursor == 1
-
- # Make sure cursor goes left and then back right
- edit.keypress(1, 'left')
- assert edit.cbuf.cursor == 0
- edit.keypress(1, 'right')
- assert edit.cbuf.cursor == 1
-
- def test_up_and_down(self):
- with taddons.context() as tctx:
- history = commander.CommandHistory(tctx.master, size=3)
- edit = commander.CommandEdit(tctx.master, '', history)
-
- buf = commander.CommandBuffer(tctx.master, 'cmd1')
- history.add_command(buf)
- buf = commander.CommandBuffer(tctx.master, 'cmd2')
- history.add_command(buf)
-
- edit.keypress(1, 'up')
- assert edit.get_edit_text() == 'cmd2'
- edit.keypress(1, 'up')
- assert edit.get_edit_text() == 'cmd1'
- edit.keypress(1, 'up')
- assert edit.get_edit_text() == 'cmd1'
-
- history = commander.CommandHistory(tctx.master, size=5)
- edit = commander.CommandEdit(tctx.master, '', history)
- edit.keypress(1, 'a')
- edit.keypress(1, 'b')
- edit.keypress(1, 'c')
- assert edit.get_edit_text() == 'abc'
- edit.keypress(1, 'up')
- assert edit.get_edit_text() == ''
- edit.keypress(1, 'down')
- assert edit.get_edit_text() == 'abc'
- edit.keypress(1, 'down')
- assert edit.get_edit_text() == 'abc'
-
- history = commander.CommandHistory(tctx.master, size=5)
- edit = commander.CommandEdit(tctx.master, '', history)
- buf = commander.CommandBuffer(tctx.master, 'cmd3')
- history.add_command(buf)
- edit.keypress(1, 'z')
- edit.keypress(1, 'up')
- assert edit.get_edit_text() == 'cmd3'
- edit.keypress(1, 'down')
- assert edit.get_edit_text() == 'z'
-
-
-class TestCommandHistory:
- def fill_history(self, commands):
- with taddons.context() as tctx:
- history = commander.CommandHistory(tctx.master, size=3)
- for c in commands:
- cbuf = commander.CommandBuffer(tctx.master, c)
- history.add_command(cbuf)
- return history, tctx.master
-
- def test_add_command(self):
- commands = ["command1", "command2"]
- history, tctx_master = self.fill_history(commands)
-
- saved_commands = [buf.text for buf in history.saved_commands]
- assert saved_commands == [""] + commands
-
- # The history size is only 3. So, we forget the first
- # one command, when adding fourth command
- cbuf = commander.CommandBuffer(tctx_master, "command3")
- history.add_command(cbuf)
- saved_commands = [buf.text for buf in history.saved_commands]
- assert saved_commands == commands + ["command3"]
-
- # Commands with the same text are not repeated in the history one by one
- history.add_command(cbuf)
- saved_commands = [buf.text for buf in history.saved_commands]
- assert saved_commands == commands + ["command3"]
-
- # adding command in execution mode sets index at the beginning of the history
- # and replace the last command buffer if it is empty or has the same text
- cbuf = commander.CommandBuffer(tctx_master, "")
- history.add_command(cbuf)
- history.index = 0
- cbuf = commander.CommandBuffer(tctx_master, "command4")
- history.add_command(cbuf, True)
- assert history.index == history.last_index
- saved_commands = [buf.text for buf in history.saved_commands]
- assert saved_commands == ["command2", "command3", "command4"]
-
- def test_get_next(self):
- commands = ["command1", "command2"]
- history, tctx_master = self.fill_history(commands)
-
- history.index = -1
- expected_items = ["", "command1", "command2"]
- for i in range(3):
- assert history.get_next().text == expected_items[i]
- # We are at the last item of the history
- assert history.get_next() is None
-
- def test_get_prev(self):
- commands = ["command1", "command2"]
- history, tctx_master = self.fill_history(commands)
-
- expected_items = ["command2", "command1", ""]
- history.index = history.last_index + 1
- for i in range(3):
- assert history.get_prev().text == expected_items[i]
- # We are at the first item of the history
- assert history.get_prev() is None
+ edit.keypress(1, 'a')
+ edit.keypress(1, 'b')
+ assert edit.get_edit_text() == 'ab'
+
+ edit.keypress(1, 'backspace')
+ assert edit.get_edit_text() == 'a'
+
+ def test_left(self, tctx):
+ edit = commander.CommandEdit(tctx.master, '')
+
+ edit.keypress(1, 'a')
+ assert edit.cbuf.cursor == 1
+
+ edit.keypress(1, 'left')
+ assert edit.cbuf.cursor == 0
+
+ # Do it again to make sure it won't go negative
+ edit.keypress(1, 'left')
+ assert edit.cbuf.cursor == 0
+
+ def test_right(self, tctx):
+ edit = commander.CommandEdit(tctx.master, '')
+
+ edit.keypress(1, 'a')
+ assert edit.cbuf.cursor == 1
+
+ # Make sure cursor won't go past the text
+ edit.keypress(1, 'right')
+ assert edit.cbuf.cursor == 1
+
+ # Make sure cursor goes left and then back right
+ edit.keypress(1, 'left')
+ assert edit.cbuf.cursor == 0
+
+ edit.keypress(1, 'right')
+ assert edit.cbuf.cursor == 1
+
+ def test_up_and_down(self, tctx):
+ edit = commander.CommandEdit(tctx.master, '')
+
+ tctx.master.commands.execute('command_history.clear')
+ tctx.master.commands.execute('command_history.add "cmd1"')
+
+ edit.keypress(1, 'up')
+ assert edit.get_edit_text() == 'cmd1'
+
+ edit.keypress(1, 'up')
+ assert edit.get_edit_text() == 'cmd1'
+
+ edit.keypress(1, 'down')
+ assert edit.get_edit_text() == ''
+
+ edit.keypress(1, 'down')
+ assert edit.get_edit_text() == ''
+
+ edit = commander.CommandEdit(tctx.master, '')
+
+ tctx.master.commands.execute('command_history.clear')
+ tctx.master.commands.execute('command_history.add "cmd1"')
+ tctx.master.commands.execute('command_history.add "cmd2"')
+
+ edit.keypress(1, 'up')
+ assert edit.get_edit_text() == 'cmd2'
+
+ edit.keypress(1, 'up')
+ assert edit.get_edit_text() == 'cmd1'
+
+ edit.keypress(1, 'up')
+ assert edit.get_edit_text() == 'cmd1'
+
+ edit.keypress(1, 'down')
+ assert edit.get_edit_text() == 'cmd2'
+
+ edit.keypress(1, 'down')
+ assert edit.get_edit_text() == ''
+
+ edit.keypress(1, 'a')
+ edit.keypress(1, 'b')
+ edit.keypress(1, 'c')
+ assert edit.get_edit_text() == 'abc'
+
+ edit.keypress(1, 'up')
+ assert edit.get_edit_text() == 'abc'
+
+ edit.keypress(1, 'down')
+ assert edit.get_edit_text() == 'abc'
+
+ edit.keypress(1, 'down')
+ assert edit.get_edit_text() == 'abc'
+
+ edit.keypress(1, 'up')
+ assert edit.get_edit_text() == 'abc'
+
+ edit = commander.CommandEdit(tctx.master, '')
+ tctx.master.commands.execute('command_history.add "cmd3"')
+
+ edit.keypress(1, 'z')
+ edit.keypress(1, 'up')
+ assert edit.get_edit_text() == 'z'
+
+ edit.keypress(1, 'down')
+ assert edit.get_edit_text() == 'z'
+
+ edit.keypress(1, 'down')
+ assert edit.get_edit_text() == 'z'
+
+ edit.keypress(1, 'backspace')
+ assert edit.get_edit_text() == ''
+
+ edit.keypress(1, 'up')
+ assert edit.get_edit_text() == 'cmd3'
+
+ edit.keypress(1, 'up')
+ assert edit.get_edit_text() == 'cmd2'
+
+ edit.keypress(1, 'up')
+ assert edit.get_edit_text() == 'cmd1'
+
+ edit.keypress(1, 'down')
+ assert edit.get_edit_text() == 'cmd2'
+
+ edit.keypress(1, 'down')
+ assert edit.get_edit_text() == 'cmd3'
+
+ edit.keypress(1, 'down')
+ assert edit.get_edit_text() == ''
+
+ edit.keypress(1, 'c')
+ assert edit.get_edit_text() == 'c'
+
+ edit.keypress(1, 'down')
+ assert edit.get_edit_text() == ''
+
+ edit.keypress(1, 'up')
+ assert edit.get_edit_text() == 'cmd3'
+
+ edit.keypress(1, 'down')
+ assert edit.get_edit_text() == ''
+
+ edit.keypress(1, 'up')
+ assert edit.get_edit_text() == 'cmd3'
+
+ edit.keypress(1, 'up')
+ assert edit.get_edit_text() == 'cmd2'
+
+ edit.keypress(1, 'down')
+ assert edit.get_edit_text() == 'cmd3'
+
+ edit.keypress(1, 'down')
+ assert edit.get_edit_text() == ''
+
+ edit.keypress(1, 'down')
+ assert edit.get_edit_text() == ''
+
+ edit.keypress(1, 'up')
+ assert edit.get_edit_text() == 'cmd3'
+
+ edit.keypress(1, 'up')
+ assert edit.get_edit_text() == 'cmd2'
+
+ edit.keypress(1, 'up')
+ assert edit.get_edit_text() == 'cmd1'
+
+ edit.keypress(1, 'up')
+ assert edit.get_edit_text() == 'cmd1'
+
+ edit.keypress(1, 'up')
+ assert edit.get_edit_text() == 'cmd1'
+
+ edit.keypress(1, 'down')
+ assert edit.get_edit_text() == 'cmd2'
+
+ edit.keypress(1, 'down')
+ assert edit.get_edit_text() == 'cmd3'
+
+ edit.keypress(1, 'down')
+ assert edit.get_edit_text() == ''
+
+ edit.keypress(1, 'down')
+ assert edit.get_edit_text() == ''
+
+ edit.keypress(1, 'backspace')
+ assert edit.get_edit_text() == ''
+
+ edit.keypress(1, 'up')
+ assert edit.get_edit_text() == 'cmd3'
+
+ edit.keypress(1, 'up')
+ assert edit.get_edit_text() == 'cmd2'
+
+ edit.keypress(1, 'up')
+ assert edit.get_edit_text() == 'cmd1'
+
+ edit.keypress(1, 'down')
+ assert edit.get_edit_text() == 'cmd2'
+
+ edit.keypress(1, 'down')
+ assert edit.get_edit_text() == 'cmd3'
+
+ edit.keypress(1, 'down')
+ assert edit.get_edit_text() == ''
class TestCommandBuffer:
@@ -255,8 +335,7 @@ class TestCommandBuffer:
cb.cursor = len(cb.text)
cb.cycle_completion()
- ch = commander.CommandHistory(tctx.master, 30)
- ce = commander.CommandEdit(tctx.master, "se", ch)
+ ce = commander.CommandEdit(tctx.master, "se")
ce.keypress(1, 'tab')
ce.update()
ret = ce.cbuf.render()