diff --git a/tests/test_ui.py b/tests/test_ui.py index 1e539651..5e55f564 100644 --- a/tests/test_ui.py +++ b/tests/test_ui.py @@ -22,6 +22,8 @@ def test_read_actions(patch_get_key): '\n', # Enter: '\r', + # Edit: + const.KEY_BACKSPACE, 'd', # Ignored: 'x', 'y', # Up: @@ -30,8 +32,9 @@ def test_read_actions(patch_get_key): const.KEY_DOWN, 'j', # Ctrl+C: const.KEY_CTRL_C, 'q']) - assert (list(islice(ui.read_actions(), 8)) + assert (list(islice(ui.read_actions(True), 10)) == [const.ACTION_SELECT, const.ACTION_SELECT, + const.ACTION_EDIT, const.ACTION_EDIT, const.ACTION_PREVIOUS, const.ACTION_PREVIOUS, const.ACTION_NEXT, const.ACTION_NEXT, const.ACTION_ABORT, const.ACTION_ABORT]) diff --git a/thefuck/const.py b/thefuck/const.py index d272f1b2..10fea854 100644 --- a/thefuck/const.py +++ b/thefuck/const.py @@ -14,15 +14,18 @@ KEY_DOWN = _GenConst('↓') KEY_CTRL_C = _GenConst('Ctrl+C') KEY_CTRL_N = _GenConst('Ctrl+N') KEY_CTRL_P = _GenConst('Ctrl+P') +KEY_BACKSPACE = _GenConst('Backspace') KEY_MAPPING = {'\x0e': KEY_CTRL_N, '\x03': KEY_CTRL_C, - '\x10': KEY_CTRL_P} + '\x10': KEY_CTRL_P, + '\x7f': KEY_BACKSPACE} ACTION_SELECT = _GenConst('select') ACTION_ABORT = _GenConst('abort') ACTION_PREVIOUS = _GenConst('previous') ACTION_NEXT = _GenConst('next') +ACTION_EDIT = _GenConst('edit') ALL_ENABLED = _GenConst('All rules enabled') DEFAULT_RULES = [ALL_ENABLED] diff --git a/thefuck/logs.py b/thefuck/logs.py index e064de67..0ba46ba3 100644 --- a/thefuck/logs.py +++ b/thefuck/logs.py @@ -56,10 +56,19 @@ def show_corrected_command(corrected_command): reset=color(colorama.Style.RESET_ALL))) -def confirm_text(corrected_command): +def edit_part(show_edit): + if show_edit: + return '/{yellow}e{bold}d{normal}it'.format( + yellow=color(colorama.Fore.YELLOW), + bold=color(colorama.Style.BRIGHT), + normal=color(colorama.Style.NORMAL)) + return '' + + +def confirm_text(corrected_command, show_edit): sys.stderr.write( (u'{prefix}{clear}{bold}{script}{reset}{side_effect} ' - u'[{green}enter{reset}/{blue}↑{reset}/{blue}↓{reset}' + u'[{green}enter{reset}{edit}{reset}/{blue}↑{reset}/{blue}↓{reset}' u'/{red}ctrl+c{reset}]').format( prefix=const.USER_COMMAND_MARK, script=corrected_command.script, @@ -69,7 +78,8 @@ def confirm_text(corrected_command): green=color(colorama.Fore.GREEN), red=color(colorama.Fore.RED), reset=color(colorama.Style.RESET_ALL), - blue=color(colorama.Fore.BLUE))) + blue=color(colorama.Fore.BLUE), + edit=edit_part(show_edit))) def debug(msg): diff --git a/thefuck/shells/fish.py b/thefuck/shells/fish.py index 7f354b8e..7f6a9c32 100644 --- a/thefuck/shells/fish.py +++ b/thefuck/shells/fish.py @@ -127,3 +127,10 @@ class Fish(Generic): history.write(entry.encode('utf-8')) else: history.write(entry) + + def can_edit(self): + return True + + def edit_command(self, command): + """Return the shell editable command""" + return 'commandline -r "' + command + '"' diff --git a/thefuck/shells/generic.py b/thefuck/shells/generic.py index aa81e2ac..94569815 100644 --- a/thefuck/shells/generic.py +++ b/thefuck/shells/generic.py @@ -121,6 +121,14 @@ class Generic(object): """ + def can_edit(self): + return False + + # FIXME: this is just an attempt to make it work in bash or zsh + def edit_command(self, command): + """Return the shell editable command""" + return 'export READLINE_LINE="' + command + '"' + def get_builtin_commands(self): """Returns shells builtin commands.""" return ['alias', 'bg', 'bind', 'break', 'builtin', 'case', 'cd', diff --git a/thefuck/types.py b/thefuck/types.py index 8c5770f4..92e64d1a 100644 --- a/thefuck/types.py +++ b/thefuck/types.py @@ -209,6 +209,7 @@ class CorrectedCommand(object): self.script = script self.side_effect = side_effect self.priority = priority + self.should_edit = False def __eq__(self, other): """Ignores `priority` field.""" @@ -232,6 +233,9 @@ class CorrectedCommand(object): of running fuck in case fixed command fails again. """ + if self.should_edit: + self.script = shell.edit_command(self.script) + if settings.repeat: repeat_fuck = '{} --repeat {}--force-command {}'.format( get_alias(), @@ -241,6 +245,10 @@ class CorrectedCommand(object): else: return self.script + def edit(self): + self.should_edit = True + return self + def run(self, old_cmd): """Runs command from rule for passed command. diff --git a/thefuck/ui.py b/thefuck/ui.py index 9c05db33..573fb36e 100644 --- a/thefuck/ui.py +++ b/thefuck/ui.py @@ -3,21 +3,24 @@ import sys from .conf import settings from .exceptions import NoRuleMatched +from .shells import shell from .system import get_key from .utils import get_alias from . import logs, const -def read_actions(): +def read_actions(can_edit): """Yields actions for pressed keys.""" while True: key = get_key() - # Handle arrows, j/k (qwerty), and n/e (colemak) + # Handle arrows, edit, j/k (qwerty), and n/e (colemak) if key in (const.KEY_UP, const.KEY_CTRL_N, 'k', 'e'): yield const.ACTION_PREVIOUS elif key in (const.KEY_DOWN, const.KEY_CTRL_P, 'j', 'n'): yield const.ACTION_NEXT + elif can_edit and key in (const.KEY_BACKSPACE, 'd'): + yield const.ACTION_EDIT elif key in (const.KEY_CTRL_C, 'q'): yield const.ACTION_ABORT elif key in ('\n', '\r'): @@ -78,18 +81,21 @@ def select_command(corrected_commands): logs.show_corrected_command(selector.value) return selector.value - logs.confirm_text(selector.value) + logs.confirm_text(selector.value, shell.can_edit()) - for action in read_actions(): + for action in read_actions(shell.can_edit()): if action == const.ACTION_SELECT: sys.stderr.write('\n') return selector.value + elif action == const.ACTION_EDIT: + sys.stderr.write('\n') + return selector.value.edit() elif action == const.ACTION_ABORT: logs.failed('\nAborted') return elif action == const.ACTION_PREVIOUS: selector.previous() - logs.confirm_text(selector.value) + logs.confirm_text(selector.value, shell.can_edit()) elif action == const.ACTION_NEXT: selector.next() - logs.confirm_text(selector.value) + logs.confirm_text(selector.value, shell.can_edit())