Repository: talonhub/community

Issue:
Override dictate.replace_words instead of using user.replace_phrases
Per discussion with aegis, instead of using a new action user.replace_phrases in vocabulary.py we should just override dictate.replace_words, since we're (I think) compatible with it except for also supporting multi-word replacement. This means if talon grows native support for multi-word replacement we could just remove our implementation, eventually.

This should be pretty easy to do, just remember to replace the fallback code that invokes dictate.replace_words with actions.next instead.


Discussion:
I've just realized we're not EXACTLY compatible with dictate.replace_words, since it's documented to use dictate.word_map while we only use words_to_replace.csv. For instance, what if someone contextually overrides dictate.word_map?

Not sure what to do about that.
convo with aegis, he says not using dictate.word_map is not a problem for now.

Resolving patch:
diff --git a/code/dictation.py b/code/dictation.py
--- a/code/dictation.py
+++ b/code/dictation.py
@@ -60,7 +60,7 @@ def word(m) -> str:
     try:
         return m.vocabulary
     except AttributeError:
-        return " ".join(actions.user.replace_phrases(actions.dictate.parse_words(m.word)))
+        return " ".join(actions.dictate.replace_words(actions.dictate.parse_words(m.word)))
 
 @mod.capture(rule="({user.vocabulary} | <phrase>)+")
 def text(m) -> str:
@@ -93,7 +93,7 @@ def capture_to_words(m):
     words = []
     for item in m:
         words.extend(
-            actions.user.replace_phrases(actions.dictate.parse_words(item))
+            actions.dictate.replace_words(actions.dictate.parse_words(item))
             if isinstance(item, grammar.vm.Phrase)
             else [item])
     return words
@@ -107,7 +107,7 @@ def apply_formatting(m):
         if isinstance(item, Callable):
             item(formatter)
         else:
-            words = (actions.user.replace_phrases(actions.dictate.parse_words(item))
+            words = (actions.dictate.replace_words(actions.dictate.parse_words(item))
                      if isinstance(item, grammar.vm.Phrase)
                      else [item])
             for word in words:
diff --git a/code/formatters.py b/code/formatters.py
--- a/code/formatters.py
+++ b/code/formatters.py
@@ -43,9 +43,7 @@ def format_phrase(m: Union[str, Phrase], formatters: str):
         # TODO: is this still necessary, and if so why?
         if m.words[-1] == "over":
             m.words = m.words[:-1]
-
-        words = actions.dictate.parse_words(m)
-        words = actions.user.replace_phrases(words)
+        words = actions.dictate.replace_words(actions.dictate.parse_words(m))
 
     result = last_phrase_formatted = format_phrase_without_adding_to_history(words, formatters)
     actions.user.add_phrase_to_history(result)
diff --git a/code/vocabulary.py b/code/vocabulary.py
--- a/code/vocabulary.py
+++ b/code/vocabulary.py
@@ -58,19 +58,19 @@
 _word_map_defaults.update({word.lower(): word for word in _capitalize_defaults})
 
 
-# phrases_to_replace is a spoken form -> written form map, used by
-# `user.replace_phrases` to rewrite words and phrases Talon recognized.
-# This does not change the priority with which Talon recognizes
-# particular phrases over others.
+# phrases_to_replace is a spoken form -> written form map, used by our
+# implementation of `dictate.replace_words` (at bottom of file) to rewrite words
+# and phrases Talon recognized. This does not change the priority with which
+# Talon recognizes particular phrases over others.
 phrases_to_replace = get_list_from_csv(
     "words_to_replace.csv",
     headers=("Replacement", "Original"),
     default=_word_map_defaults
 )
 
-# "dictate.word_map" is used by `actions.dictate.replace_words`;
-# a built-in Talon action similar to `replace_phrases`, but supporting
-# only single-word replacements. Multi-word phrases are ignored.
+# "dictate.word_map" is used by Talon's built-in default implementation of
+# `dictate.replace_words`, but supports only single-word replacements.
+# Multi-word phrases are ignored.
 ctx.settings["dictate.word_map"] = phrases_to_replace
 
 
@@ -95,10 +95,6 @@
     default=_default_vocabulary,
 )
 
-# for quick verification of the reload
-# print(str(ctx.settings["dictate.word_map"]))
-# print(str(ctx.lists["user.vocabulary"]))
-
 class PhraseReplacer:
     """Utility for replacing phrases by other phrases inside text or word lists.
 
@@ -170,13 +166,12 @@ def replace_string(self, text: str) -> str:
 
 phrase_replacer = PhraseReplacer(phrases_to_replace)
 
-@mod.action_class
-class Actions:
-    def replace_phrases(words: Sequence[str]) -> Sequence[str]:
-        """Replace phrases according to words_to_replace.csv"""
+@ctx.action_class('dictate')
+class OverwrittenActions:
+    def replace_words(words: Sequence[str]) -> Sequence[str]:
         try:
             return phrase_replacer.replace(words)
         except:
-            # fall back to dictate.replace_words for error-robustness
+            # fall back to default implementation for error-robustness
             logging.error("phrase replacer failed!")
-            return actions.dictate.replace_words(words)
+            return actions.next(words)


Test patch: