diff --git a/news/env_completion.rst b/news/env_completion.rst new file mode 100644 index 000000000..8492ee4c4 --- /dev/null +++ b/news/env_completion.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* Env variables completion: now use substring for search and then sort results by the position of substring and then alphabetically. PR 5388. + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/tests/completers/test_environment_completer.py b/tests/completers/test_environment_completer.py index f05161988..f6ff87d79 100644 --- a/tests/completers/test_environment_completer.py +++ b/tests/completers/test_environment_completer.py @@ -12,30 +12,30 @@ def parser(): @pytest.mark.parametrize( "cmd", ( - "ls $WO", - "ls /home/$WO", - "ls '/home/$WO'", - "ls @('hi ' + $WO", + "ls $WOW", + "ls /home/$WOW", + "ls '/home/$WOW'", + "ls @('hi ' + $WOW", ), ) def test_simple(cmd, xession, monkeypatch, parser): - xession.env.update({"WOW": 1}) + xession.env.update({"WOWZER": 1}) context = parser.parse(cmd, len(cmd)) comps, lprefix = complete_environment_vars(context) # account for the ending quote if cmd[-1] in "'": - assert lprefix == 4 + assert lprefix == 5 else: - assert lprefix == 3 - assert set(comps) == {"$WOW"} + assert lprefix == 4 + assert set(comps) == {"$WOWZER"} def test_rich_completions(xession, monkeypatch, parser): - xession.env.update({"WOW": 1}) - xession.env.register("WOW", type=int, doc="Nice Docs!") + xession.env.update({"WOWZER": 1}) + xession.env.register("WOWZER", type=int, doc="Nice Docs!") - context = parser.parse("$WO", 3) + context = parser.parse("$WOW", 4) completion = next(complete_environment_vars(context)[0]) - assert completion.display == "$WOW [int]" + assert completion.display == "$WOWZER [int]" assert completion.description == "Nice Docs!" diff --git a/tests/test_completer.py b/tests/test_completer.py index 18ca10865..67af4890c 100644 --- a/tests/test_completer.py +++ b/tests/test_completer.py @@ -155,3 +155,16 @@ def test_non_exclusive(completer, completers_mock, middle_result, exp): completers_mock["c"] = non_exclusive_completer(lambda *a: {"c1", "c2"}) assert completer.complete("", "", 0, 0, {})[0] == exp + + +def test_env_completer_sort(completer, completers_mock): + @contextual_command_completer + def comp(context: CommandContext): + return {"$SUPER_WOW", "$WOW1", "$WOW0", "$MID_WOW"} + + completers_mock["a"] = comp + + comps = completer.complete( + "$WOW", "$WOW", 4, 0, {}, multiline_text="'$WOW'", cursor_index=4 + ) + assert set(comps[0]) == {"$WOW0", "$WOW1", "$MID_WOW", "$SUPER_WOW"} diff --git a/xonsh/completer.py b/xonsh/completer.py index 918c88e1b..e0700ad1d 100644 --- a/xonsh/completer.py +++ b/xonsh/completer.py @@ -284,9 +284,18 @@ class Completer: ) break - def sortkey(s): - # todo: should sort with prefix > substring > fuzzy - return s.lstrip(''''"''').lower() + prefix = None + if completion_context: + prefix = completion_context.command.prefix + if prefix.startswith("$"): + prefix = prefix[1:] + + def sortkey(s): + """Sort values by prefix position and then alphabetically.""" + return (s.lower().find(prefix.lower()), s.lower()) + else: + # Fallback sort. + sortkey = lambda s: s.lstrip(''''"''').lower() # the last completer's lprefix is returned. other lprefix values are inside the RichCompletions. return tuple(sorted(completions, key=sortkey)), lprefix diff --git a/xonsh/completers/environment.py b/xonsh/completers/environment.py index 3752a1459..205d00a5a 100644 --- a/xonsh/completers/environment.py +++ b/xonsh/completers/environment.py @@ -2,7 +2,6 @@ from xonsh.built_ins import XSH from xonsh.completers.tools import ( RichCompletion, contextual_completer, - get_filter_function, non_exclusive_completer, ) from xonsh.parsers.completion_context import CompletionContext @@ -27,15 +26,14 @@ def complete_environment_vars(context: CompletionContext): lprefix = len(key) + 1 if context.command is not None and context.command.is_after_closing_quote: lprefix += 1 - filter_func = get_filter_function() env = XSH.env + vars = [k for k, v in env.items() if key.lower() in k.lower()] return ( RichCompletion( "$" + k, - display=f"${k} [{type(v).__name__}]", + display=f"${k} [{type(env[k]).__name__}]", description=env.get_docs(k).doc, ) - for k, v in env.items() - if filter_func(k, key) + for k in vars ), lprefix