Repository: xonsh/xonsh

Issue:
Feature: Search substring in env variable completer
```xsh
$TRA<Tab>
# Show all variables with `*TRA*`
```

## For community
⬇️  **Please click the 👍 reaction instead of leaving a `+1` or 👍  comment**



Discussion:

Resolving patch:
diff --git a/xonsh/completer.py b/xonsh/completer.py
--- a/xonsh/completer.py
+++ b/xonsh/completer.py
@@ -284,9 +284,18 @@ def complete_from_context(self, completion_context, old_completer_args=None):
                     )
                 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
--- a/xonsh/completers/environment.py
+++ b/xonsh/completers/environment.py
@@ -2,7 +2,6 @@
 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


Test patch:
diff --git a/tests/completers/test_environment_completer.py b/tests/completers/test_environment_completer.py
--- 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
--- a/tests/test_completer.py
+++ b/tests/test_completer.py
@@ -155,3 +155,16 @@ def middle(*a):
     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"}