mirror of
https://github.com/xonsh/xonsh.git
synced 2025-03-04 08:24:40 +01:00
Add $COMPLETION_MODE='menu-complete' to enable readline menu-complete (#3876)
* Add $COMPLETION_MODE='menu-complete' to enablereadline menu-complete -like behavor in completion. * Tests Co-authored-by: Bob Hyman <bob.hyman@bobssoftwareworks.com>
This commit is contained in:
parent
5d4745ca7b
commit
b44dc70af7
5 changed files with 165 additions and 0 deletions
28
news/ptk-menu-complete.rst
Normal file
28
news/ptk-menu-complete.rst
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
**Added:**
|
||||||
|
|
||||||
|
* Environment variable ``$COMPLETION_MODE`` controls kind of TAB completion used with prompt-toolkit shell.
|
||||||
|
``default``, the default, retains prior Xonsh behavior: first TAB displays the common prefix of matching completions,
|
||||||
|
next TAB selects the first or next available completion.
|
||||||
|
``menu-complete`` enables TAB behavior like ``readline`` command ``menu-complete``. First TAB selects the first matching
|
||||||
|
completion, subsequent TABs cycle through available completions till the last one. Next TAB after that displays
|
||||||
|
the common prefix, then the cycle repeats.
|
||||||
|
|
||||||
|
**Changed:**
|
||||||
|
|
||||||
|
* <news item>
|
||||||
|
|
||||||
|
**Deprecated:**
|
||||||
|
|
||||||
|
* <news item>
|
||||||
|
|
||||||
|
**Removed:**
|
||||||
|
|
||||||
|
* <news item>
|
||||||
|
|
||||||
|
**Fixed:**
|
||||||
|
|
||||||
|
* <news item>
|
||||||
|
|
||||||
|
**Security:**
|
||||||
|
|
||||||
|
* <news item>
|
|
@ -85,6 +85,10 @@ from xonsh.tools import (
|
||||||
all_permutations,
|
all_permutations,
|
||||||
register_custom_style,
|
register_custom_style,
|
||||||
simple_random_choice,
|
simple_random_choice,
|
||||||
|
is_completion_mode,
|
||||||
|
to_completion_mode,
|
||||||
|
is_completions_display_value,
|
||||||
|
to_completions_display_value,
|
||||||
)
|
)
|
||||||
from xonsh.environ import Env
|
from xonsh.environ import Env
|
||||||
|
|
||||||
|
@ -1819,3 +1823,79 @@ def test_register_custom_style(name, styles, refrules):
|
||||||
for rule, color in style.styles.items():
|
for rule, color in style.styles.items():
|
||||||
if str(rule) in refrules:
|
if str(rule) in refrules:
|
||||||
assert refrules[str(rule)] == color
|
assert refrules[str(rule)] == color
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"val, exp",
|
||||||
|
[
|
||||||
|
("default", True),
|
||||||
|
("menu-complete", True),
|
||||||
|
("def", False),
|
||||||
|
("xonsh", False),
|
||||||
|
("men", False),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_is_completion_mode(val, exp):
|
||||||
|
assert is_completion_mode(val) is exp
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"val, exp",
|
||||||
|
[
|
||||||
|
("", "default"),
|
||||||
|
(None, "default"),
|
||||||
|
("default", "default"),
|
||||||
|
("DEfaULT", "default"),
|
||||||
|
("m", "menu-complete"),
|
||||||
|
("mEnu_COMPlete", "menu-complete"),
|
||||||
|
("menu-complete", "menu-complete"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_to_completion_mode(val, exp):
|
||||||
|
assert to_completion_mode(val) == exp
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("val", ["de", "defa_ult", "men_", "menu_",])
|
||||||
|
def test_to_completion_mode_fail(val):
|
||||||
|
with pytest.warns(RuntimeWarning):
|
||||||
|
obs = to_completion_mode(val)
|
||||||
|
assert obs == "default"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"val, exp",
|
||||||
|
[
|
||||||
|
("none", True),
|
||||||
|
("single", True),
|
||||||
|
("multi", True),
|
||||||
|
("", False),
|
||||||
|
(None, False),
|
||||||
|
("argle", False),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_is_completions_display_value(val, exp):
|
||||||
|
assert is_completions_display_value(val) == exp
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"val, exp",
|
||||||
|
[
|
||||||
|
("none", "none"),
|
||||||
|
(False, "none"),
|
||||||
|
("false", "none"),
|
||||||
|
("single", "single"),
|
||||||
|
("readline", "single"),
|
||||||
|
("multi", "multi"),
|
||||||
|
(True, "multi"),
|
||||||
|
("TRUE", "multi"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_to_completions_display_value(val, exp):
|
||||||
|
to_completions_display_value(val) == exp
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("val", [1, "", "argle"])
|
||||||
|
def test_to_completions_display_value_fail(val):
|
||||||
|
with pytest.warns(RuntimeWarning):
|
||||||
|
obs = to_completions_display_value(val)
|
||||||
|
assert obs == "multi"
|
||||||
|
|
|
@ -57,6 +57,8 @@ from xonsh.tools import (
|
||||||
is_string_or_callable,
|
is_string_or_callable,
|
||||||
is_completions_display_value,
|
is_completions_display_value,
|
||||||
to_completions_display_value,
|
to_completions_display_value,
|
||||||
|
is_completion_mode,
|
||||||
|
to_completion_mode,
|
||||||
is_string_set,
|
is_string_set,
|
||||||
csv_to_set,
|
csv_to_set,
|
||||||
set_to_csv,
|
set_to_csv,
|
||||||
|
@ -851,6 +853,17 @@ def DEFAULT_VARS():
|
||||||
"``$COMPLETIONS_DISPLAY`` is ``single`` or ``multi``. This only affects the "
|
"``$COMPLETIONS_DISPLAY`` is ``single`` or ``multi``. This only affects the "
|
||||||
"prompt-toolkit shell.",
|
"prompt-toolkit shell.",
|
||||||
),
|
),
|
||||||
|
"COMPLETION_MODE": Var(
|
||||||
|
is_completion_mode,
|
||||||
|
to_completion_mode,
|
||||||
|
str,
|
||||||
|
"default",
|
||||||
|
"Mode of tab completion in prompt-toolkit shell (only).\n\n"
|
||||||
|
"'default', the default, selects the common prefix of completions on first TAB,\n"
|
||||||
|
"then cycles through all completions.\n"
|
||||||
|
"'menu-complete' selects the first whole completion on the first TAB, \n"
|
||||||
|
"then cycles through the remaining completions, then the common prefix.",
|
||||||
|
),
|
||||||
"COMPLETION_QUERY_LIMIT": Var(
|
"COMPLETION_QUERY_LIMIT": Var(
|
||||||
is_int,
|
is_int,
|
||||||
int,
|
int,
|
||||||
|
|
|
@ -109,6 +109,12 @@ def tab_insert_indent():
|
||||||
return bool(before_cursor.isspace())
|
return bool(before_cursor.isspace())
|
||||||
|
|
||||||
|
|
||||||
|
@Condition
|
||||||
|
def tab_menu_complete():
|
||||||
|
"""Checks whether completion mode is `menu-complete`"""
|
||||||
|
return builtins.__xonsh__.env.get("COMPLETION_MODE") == "menu-complete"
|
||||||
|
|
||||||
|
|
||||||
@Condition
|
@Condition
|
||||||
def beginning_of_line():
|
def beginning_of_line():
|
||||||
"""Check if cursor is at beginning of a line other than the first line in a
|
"""Check if cursor is at beginning of a line other than the first line in a
|
||||||
|
@ -206,6 +212,15 @@ def load_xonsh_bindings() -> KeyBindingsBase:
|
||||||
env = builtins.__xonsh__.env
|
env = builtins.__xonsh__.env
|
||||||
event.cli.current_buffer.insert_text(env.get("INDENT"))
|
event.cli.current_buffer.insert_text(env.get("INDENT"))
|
||||||
|
|
||||||
|
@handle(Keys.Tab, filter=~tab_insert_indent & tab_menu_complete)
|
||||||
|
def menu_complete_select(event):
|
||||||
|
"""Start completion in menu-complete mode, or tab to next completion"""
|
||||||
|
b = event.current_buffer
|
||||||
|
if b.complete_state:
|
||||||
|
b.complete_next()
|
||||||
|
else:
|
||||||
|
b.start_completion(select_first=True)
|
||||||
|
|
||||||
@handle(Keys.ControlX, Keys.ControlE, filter=~has_selection)
|
@handle(Keys.ControlX, Keys.ControlE, filter=~has_selection)
|
||||||
def open_editor(event):
|
def open_editor(event):
|
||||||
""" Open current buffer in editor """
|
""" Open current buffer in editor """
|
||||||
|
|
|
@ -1617,10 +1617,12 @@ def ptk2_color_depth_setter(x):
|
||||||
|
|
||||||
|
|
||||||
def is_completions_display_value(x):
|
def is_completions_display_value(x):
|
||||||
|
"""Enumerated values of ``$COMPLETIONS_DISPLAY``"""
|
||||||
return x in {"none", "single", "multi"}
|
return x in {"none", "single", "multi"}
|
||||||
|
|
||||||
|
|
||||||
def to_completions_display_value(x):
|
def to_completions_display_value(x):
|
||||||
|
"""Convert user input to value of ``$COMPLETIONS_DISPLAY``"""
|
||||||
x = str(x).lower()
|
x = str(x).lower()
|
||||||
if x in {"none", "false"}:
|
if x in {"none", "false"}:
|
||||||
x = "none"
|
x = "none"
|
||||||
|
@ -1636,6 +1638,33 @@ def to_completions_display_value(x):
|
||||||
return x
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
CANONIC_COMPLETION_MODES = frozenset({"default", "menu-complete"})
|
||||||
|
|
||||||
|
|
||||||
|
def is_completion_mode(x):
|
||||||
|
"""Enumerated values of $COMPLETION_MODE"""
|
||||||
|
return x in CANONIC_COMPLETION_MODES
|
||||||
|
|
||||||
|
|
||||||
|
def to_completion_mode(x):
|
||||||
|
"""Convert user input to value of $COMPLETION_MODE"""
|
||||||
|
y = str(x).casefold().replace("_", "-")
|
||||||
|
y = (
|
||||||
|
"default"
|
||||||
|
if y in ("", "d", "xonsh", "none", "def")
|
||||||
|
else "menu-complete"
|
||||||
|
if y in ("m", "menu", "menu-completion")
|
||||||
|
else y
|
||||||
|
)
|
||||||
|
if y not in CANONIC_COMPLETION_MODES:
|
||||||
|
warnings.warn(
|
||||||
|
f"'{x}' is not valid for $COMPLETION_MODE, must be one of {CANONIC_COMPLETION_MODES}. Using 'default'.",
|
||||||
|
RuntimeWarning,
|
||||||
|
)
|
||||||
|
y = "default"
|
||||||
|
return y
|
||||||
|
|
||||||
|
|
||||||
def is_str_str_dict(x):
|
def is_str_str_dict(x):
|
||||||
"""Tests if something is a str:str dictionary"""
|
"""Tests if something is a str:str dictionary"""
|
||||||
return isinstance(x, dict) and all(
|
return isinstance(x, dict) and all(
|
||||||
|
|
Loading…
Add table
Reference in a new issue