1
0
mirror of https://github.com/nvbn/thefuck.git synced 2025-01-31 10:11:14 +00:00

#N/A: Support editing command

This commit is contained in:
Pablo Santiago Blum de Aguiar 2017-12-17 02:53:30 -02:00
parent 6975d30818
commit 15372fcb90
8 changed files with 89 additions and 16 deletions

View File

@ -43,6 +43,18 @@ class TestCorrectedCommand(object):
out, _ = capsys.readouterr()
assert out[:-1] == printed
def test_run_with_edit(self, capsys, monkeypatch, mocker):
script = "git branch"
edit_tpl = 'editor "{}"'
monkeypatch.setattr(
'thefuck.types.shell.edit_command',
lambda script: edit_tpl.format(script),
)
command = CorrectedCommand(script, None, 1000).edit()
command.run(Command(script, ''))
out, _ = capsys.readouterr()
assert out[:-1] == edit_tpl.format(script)
class TestRule(object):
def test_from_path(self, mocker):

View File

@ -16,12 +16,15 @@ def patch_get_key(monkeypatch):
return patch
def test_read_actions(patch_get_key):
@pytest.mark.parametrize("shell_can_edit", [True, False])
def test_read_actions(shell_can_edit, patch_get_key):
patch_get_key([
# Enter:
'\n',
# Enter:
'\r',
# Edit:
const.KEY_BACKSPACE, 'd',
# Ignored:
'x', 'y',
# Up:
@ -30,11 +33,17 @@ 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))
== [const.ACTION_SELECT, const.ACTION_SELECT,
const.ACTION_PREVIOUS, const.ACTION_PREVIOUS,
const.ACTION_NEXT, const.ACTION_NEXT,
const.ACTION_ABORT, const.ACTION_ABORT])
expected_actions = [const.ACTION_SELECT, const.ACTION_SELECT,
const.ACTION_PREVIOUS, const.ACTION_PREVIOUS,
const.ACTION_NEXT, const.ACTION_NEXT,
const.ACTION_ABORT, const.ACTION_ABORT]
number_of_items = 8
if shell_can_edit:
expected_actions.insert(2, const.ACTION_EDIT)
expected_actions.insert(2, const.ACTION_EDIT)
number_of_items = 10
assert (list(islice(ui.read_actions(shell_can_edit), number_of_items))
== expected_actions)
def test_command_selector():
@ -106,3 +115,14 @@ class TestSelectCommand(object):
u'{mark}\x1b[1K\rcd [enter/↑/↓/ctrl+c]\n'
).format(mark=const.USER_COMMAND_MARK)
assert capsys.readouterr() == ('', stderr)
def test_with_edit(self, capsys, patch_get_key, commands, monkeypatch):
monkeypatch.setattr('thefuck.ui.shell.can_edit', lambda: True)
patch_get_key([const.KEY_BACKSPACE, '\n'])
command = ui.select_command(iter(commands))
assert command == commands[0]
assert command.should_edit is True
stderr = (
u'{mark}\x1b[1K\rls [enter/edit/↑/↓/ctrl+c]\n'
).format(mark=const.USER_COMMAND_MARK)
assert capsys.readouterr() == ('', stderr)

View File

@ -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]

View File

@ -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):

View File

@ -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 u'commandline -r "{}"'.format(command)

View File

@ -121,6 +121,13 @@ class Generic(object):
"""
def can_edit(self):
return False
def edit_command(self, command):
"""Return the shell editable command"""
return command
def get_builtin_commands(self):
"""Returns shells builtin commands."""
return ['alias', 'bg', 'bind', 'break', 'builtin', 'case', 'cd',

View File

@ -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.

View File

@ -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())