Repository: talonhub/community

Issue:
Windows switching could use fuzzier matching
Right now when you want to switch window focus you have to say a word the matches a generated list of running applications, and this list has an override that lets you more easily say the names of complex application names, for instance `term` is `iTerm2` or whatever.

I propose we change the way the list works so that actually we just use `focus <user.text>` for the command, and then `switcher_focus()` does the lookup for whatever text it gets inside the running application list. Doing it this way allows us to leverage any additional vocabulary that's been added to `vocabulary.py` without necessarily having to hardcode it again inside of `switcher.py`. 

in addition I propose that we introduce slightly fuzzer matching. IFor example, my `vocabulary.py` as a rule for "key pass" which becomes "keepass", and this is helpful when I am just talking to other people in chat or whatever. But the actual name of the running process is `KeePassXC`. I don't necessarily want to always be typing that exact word into chat with other people when I say `key pass` though. I suspect this type of thing will be increasingly common the more people are using it with different applications 

As it stands if I wanted to match the app with current switcher I'd have to put "key pass":"KeePassXC" override in `switcher.py`, instead of just matching the result I already have in `vocabulary.py`. 

In addition to using `vocabulary.py`, I propose we check for a partial match using `startswith()`, so then I would be able to say something like "focus key pass" and it would actually come through as `focus keepass`, which would in turn match against `keepassxc`. 

We can of course keep the overrides for certain things that are a bit harder to say in `switcher.py`, for instance I think people aren't going to have `term` becoming `iTerm2` in `vocabulary.py`.

This can mostly all be done with a simple `switcher_focus()` change like:

```
        running = ctx.lists["self.running"]
        wanted_app = None
        for running_name in running.keys():
            if running_name == name or running_name.lower().startswith(name):
                wanted_app = running[running_name]
                break
        if wanted_app is None:
            return

        for app in ui.apps():
            if app.name == wanted_app and not app.background:
                #os.system("i3-msg '[class=\"(?)%s\"] focus'" % app.name)
                app.focus()
                break
```

Not sending a PR for now as I figured I'd run it past others first.

One possible change could be that it does two passes, the first pass matches on explicit name matches so you don't accidentally match against something that has a more explicit match, and then if there are no explicit matches, it tries a second pass with fuzzy matching.


Discussion:
just noticed #136 tweaks switcher a bit, but I think the above still applies in spite of a move to a csv.
Interesting. I think I'll just grab this from your depot and give it a try. 
Sorry for the delay, took some time off.

I think this is a good idea, but I'd propose maybe combining both approaches via the capture?

```
@ctx.capture(rule="({self.running} | <user.text>)")
def running_applications(m):
    try: return m.running
    except AttributeError: return m.text
```




Resolving patch:
diff --git a/code/switcher.py b/code/switcher.py
--- a/code/switcher.py
+++ b/code/switcher.py
@@ -1,8 +1,9 @@
-from talon import app, Module, Context, actions, ui, imgui
-from talon.voice import Capture
+import os
 import re
 import time
-import os
+
+from talon import Context, Module, app, imgui, ui
+from talon.voice import Capture
 
 # Construct at startup a list of overides for application names (similar to how homophone list is managed)
 # ie for a given talon recognition word set  `one note`, recognized this in these switcher functions as `ONENOTE`
@@ -68,10 +69,18 @@ def get_words(name):
 class Actions:
     def switcher_focus(name: str):
         """Focus a new application by  name"""
-        for app in ui.apps():
-            # print(f"--------- app.name:{app.name}  app.bundler:{app.bundle}")
-            if name in app.name and not app.background:
-                app.focus()
+        running = ctx.lists["self.running"]
+        wanted_app = None
+        for running_name in running.keys():
+            if running_name == name or running_name.lower().startswith(name):
+                wanted_app = running[running_name]
+                break
+        if wanted_app is None:
+            return
+
+        for cur_app in ui.apps():
+            if cur_app.name == wanted_app and not cur_app.background:
+                cur_app.focus()
                 break
 
     def switcher_launch(path: str):
@@ -105,7 +114,7 @@ def update_lists():
             name = name.rsplit(".", 1)[0]
         words = get_words(name)
         for word in words:
-            if word and not word in running:
+            if word and word not in running:
                 running[word.lower()] = cur_app.name
         running[name.lower()] = cur_app.name
     for override in overrides:


Test patch: