We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
feature request: `phrase hello slash world` -> `hello/world`
I'm not sure what the best way to go about this is, it might be simple.

some more examples/unit tests:

- `phrase hello slash world` -> `hello/world` (currently `hello /world`)
- `phrase hello slash snake name of file slash world` -> `hello/name_of_file/world` (currently `hello/name_of_file_/world`)

Similarly: `phrase file dot extension` -> `file.extension`, though this one might be complicated by the fact that I have a formatter called `dot` defined so that `dot hello world` -> `.hello_world` because I use python so much.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `code/formatters.py`
Content:
```
1 from talon import Module, Context, actions, ui, imgui
2 from talon.grammar import Phrase
3 from typing import List, Union
4 import re
5 
6 ctx = Context()
7 key = actions.key
8 edit = actions.edit
9 
10 words_to_keep_lowercase = "a,an,the,at,by,for,in,is,of,on,to,up,and,as,but,or,nor".split(
11     ","
12 )
13 
14 # last_phrase has the last phrase spoken, WITHOUT formatting.
15 # This is needed for reformatting.
16 last_phrase = ""
17 
18 # formatted_phrase_history keeps the most recent formatted phrases, WITH formatting.
19 formatted_phrase_history = []
20 formatted_phrase_history_length = 20
21 
22 
23 def surround(by):
24     def func(i, word, last):
25         if i == 0:
26             word = by + word
27         if last:
28             word += by
29         return word
30 
31     return func
32 
33 
34 def format_phrase(m: Union[str, Phrase], fmtrs: str):
35     global last_phrase
36     last_phrase = m
37     words = []
38     if isinstance(m, str):
39         words = m.split(" ")
40     else:
41         if m.words[-1] == "over":
42             m.words = m.words[:-1]
43 
44         words = actions.dictate.parse_words(m)
45         words = actions.dictate.replace_words(words)
46 
47     result = format_phrase_no_history(words, fmtrs)
48 
49     # Add result to history.
50     global formatted_phrase_history
51     formatted_phrase_history.insert(0, result)
52     formatted_phrase_history = formatted_phrase_history[
53         :formatted_phrase_history_length
54     ]
55 
56     return result
57 
58 
59 def format_phrase_no_history(word_list, fmtrs: str):
60     fmtr_list = fmtrs.split(",")
61     words = []
62     spaces = True
63     for i, w in enumerate(word_list):
64         for name in reversed(fmtr_list):
65             smash, func = all_formatters[name]
66             w = func(i, w, i == len(word_list) - 1)
67             spaces = spaces and not smash
68         words.append(w)
69     sep = " " if spaces else ""
70     return sep.join(words)
71 
72 
73 NOSEP = True
74 SEP = False
75 
76 
77 def words_with_joiner(joiner):
78     """Pass through words unchanged, but add a separator between them."""
79 
80     def formatter_function(i, word, _):
81         return word if i == 0 else joiner + word
82 
83     return (NOSEP, formatter_function)
84 
85 
86 def first_vs_rest(first_func, rest_func=lambda w: w):
87     """Supply one or two transformer functions for the first and rest of
88     words respectively.
89 
90     Leave second argument out if you want all but the first word to be passed
91     through unchanged.
92     Set first argument to None if you want the first word to be passed
93     through unchanged."""
94     if first_func is None:
95         first_func = lambda w: w
96 
97     def formatter_function(i, word, _):
98         return first_func(word) if i == 0 else rest_func(word)
99 
100     return formatter_function
101 
102 
103 def every_word(word_func):
104     """Apply one function to every word."""
105 
106     def formatter_function(i, word, _):
107         return word_func(word)
108 
109     return formatter_function
110 
111 
112 formatters_dict = {
113     "NOOP": (SEP, lambda i, word, _: word),
114     "DOUBLE_UNDERSCORE": (NOSEP, first_vs_rest(lambda w: "__%s__" % w)),
115     "PRIVATE_CAMEL_CASE": (NOSEP, first_vs_rest(lambda w: w, lambda w: w.capitalize())),
116     "PROTECTED_CAMEL_CASE": (
117         NOSEP,
118         first_vs_rest(lambda w: w, lambda w: w.capitalize()),
119     ),
120     "PUBLIC_CAMEL_CASE": (NOSEP, every_word(lambda w: w.capitalize())),
121     "SNAKE_CASE": (
122         NOSEP,
123         first_vs_rest(lambda w: w.lower(), lambda w: "_" + w.lower()),
124     ),
125     "NO_SPACES": (NOSEP, every_word(lambda w: w)),
126     "DASH_SEPARATED": words_with_joiner("-"),
127     "TERMINAL_DASH_SEPARATED": (
128         NOSEP,
129         first_vs_rest(lambda w: " --" + w.lower(), lambda w: "-" + w.lower()),
130     ),
131     "DOUBLE_COLON_SEPARATED": words_with_joiner("::"),
132     "ALL_CAPS": (SEP, every_word(lambda w: w.upper())),
133     "ALL_LOWERCASE": (SEP, every_word(lambda w: w.lower())),
134     "DOUBLE_QUOTED_STRING": (SEP, surround('"')),
135     "SINGLE_QUOTED_STRING": (SEP, surround("'")),
136     "SPACE_SURROUNDED_STRING": (SEP, surround(" ")),
137     "DOT_SEPARATED": words_with_joiner("."),
138     "DOT_SNAKE": (NOSEP, lambda i, word, _: "." + word if i == 0 else "_" + word),
139     "SLASH_SEPARATED": (NOSEP, every_word(lambda w: "/" + w)),
140     "CAPITALIZE_FIRST_WORD": (SEP, first_vs_rest(lambda w: w.capitalize())),
141     "CAPITALIZE_ALL_WORDS": (
142         SEP,
143         lambda i, word, _: word.capitalize()
144         if i == 0 or word not in words_to_keep_lowercase
145         else word,
146     ),
147     "FIRST_THREE": (NOSEP, lambda i, word, _: word[0:3]),
148     "FIRST_FOUR": (NOSEP, lambda i, word, _: word[0:4]),
149     "FIRST_FIVE": (NOSEP, lambda i, word, _: word[0:5]),
150 }
151 
152 # This is the mapping from spoken phrases to formatters
153 formatters_words = {
154     "allcaps": formatters_dict["ALL_CAPS"],
155     "alldown": formatters_dict["ALL_LOWERCASE"],
156     "camel": formatters_dict["PRIVATE_CAMEL_CASE"],
157     "dotted": formatters_dict["DOT_SEPARATED"],
158     "dubstring": formatters_dict["DOUBLE_QUOTED_STRING"],
159     "dunder": formatters_dict["DOUBLE_UNDERSCORE"],
160     "hammer": formatters_dict["PUBLIC_CAMEL_CASE"],
161     "kebab": formatters_dict["DASH_SEPARATED"],
162     "packed": formatters_dict["DOUBLE_COLON_SEPARATED"],
163     "padded": formatters_dict["SPACE_SURROUNDED_STRING"],
164     # "say": formatters_dict["NOOP"],
165     "sentence": formatters_dict["CAPITALIZE_FIRST_WORD"],
166     "slasher": formatters_dict["SLASH_SEPARATED"],
167     "smash": formatters_dict["NO_SPACES"],
168     "snake": formatters_dict["SNAKE_CASE"],
169     # "speak": formatters_dict["NOOP"],
170     "string": formatters_dict["SINGLE_QUOTED_STRING"],
171     "title": formatters_dict["CAPITALIZE_ALL_WORDS"],
172     # disable a few formatters for now
173     # "tree": formatters_dict["FIRST_THREE"],
174     # "quad": formatters_dict["FIRST_FOUR"],
175     # "fiver": formatters_dict["FIRST_FIVE"],
176 }
177 
178 all_formatters = {}
179 all_formatters.update(formatters_dict)
180 all_formatters.update(formatters_words)
181 
182 mod = Module()
183 mod.list("formatters", desc="list of formatters")
184 
185 
186 @mod.capture
187 def formatters(m) -> str:
188     "Returns a comma-separated string of formatters e.g. 'SNAKE,DUBSTRING'"
189 
190 
191 @mod.capture
192 def format_text(m) -> str:
193     "Formats the text and returns a string"
194 
195 
196 @mod.action_class
197 class Actions:
198     def formatted_text(phrase: Union[str, Phrase], formatters: str) -> str:
199         """Formats a phrase according to formatters. formatters is a comma-separated string of formatters (e.g. 'CAPITALIZE_ALL_WORDS,DOUBLE_QUOTED_STRING')"""
200         return format_phrase(phrase, formatters)
201 
202     def formatters_help_toggle():
203         """Lists all formatters"""
204         if gui.showing:
205             gui.hide()
206         else:
207             gui.show()
208 
209     def formatters_recent_toggle():
210         """Toggles list of recent formatters"""
211         if recent_gui.showing:
212             recent_gui.hide()
213         else:
214             recent_gui.show()
215 
216     def formatters_recent_select(number: int):
217         """Inserts a recent formatter"""
218         if len(formatted_phrase_history) >= number:
219             return formatted_phrase_history[number - 1]
220         return ""
221 
222     def formatters_clear_last():
223         """Clears the last formatted phrase"""
224         if len(formatted_phrase_history) > 0:
225             for character in formatted_phrase_history[0]:
226                 actions.edit.delete()
227 
228     def formatters_reformat_last(formatters: str) -> str:
229         """Reformats last formatted phrase"""
230         global last_phrase
231         return format_phrase(last_phrase, formatters)
232 
233     def formatters_reformat_selection(formatters: str) -> str:
234         """Reformats the current selection."""
235         selected = edit.selected_text()
236         unformatted = re.sub(r"[^a-zA-Z0-9]+", " ", selected).lower()
237         # TODO: Separate out camelcase & studleycase vars
238 
239         # Delete separately for compatibility with programs that don't overwrite
240         # selected text (e.g. Emacs)
241         edit.delete()
242         text = actions.self.formatted_text(unformatted, formatters)
243         actions.insert(text)
244         return text
245 
246 
247 @ctx.capture(rule="{self.formatters}+")
248 def formatters(m):
249     return ",".join(m.formatters_list)
250 
251 
252 @ctx.capture(rule="<self.formatters> <user.text>")
253 def format_text(m):
254     return format_phrase(m.text, m.formatters)
255 
256 
257 ctx.lists["self.formatters"] = formatters_words.keys()
258 
259 
260 @imgui.open(software=False)
261 def gui(gui: imgui.GUI):
262     gui.text("List formatters")
263     gui.line()
264     for name in sorted(set(formatters_words.keys())):
265         gui.text(f"{name} | {format_phrase_no_history(['one', 'two', 'three'], name)}")
266 
267 
268 @imgui.open(software=False)
269 def recent_gui(gui: imgui.GUI):
270     gui.text("Recent formatters")
271     gui.line()
272     for index, result in enumerate(formatted_phrase_history, 1):
273         gui.text("{}. {}".format(index, result))
274
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/code/formatters.py b/code/formatters.py
--- a/code/formatters.py
+++ b/code/formatters.py
@@ -193,6 +193,22 @@
     "Formats the text and returns a string"
 
 
+class ImmuneString(object):
+    """Wrapper that makes a string immune from formatting."""
+
+    def __init__(self, string):
+        self.string = string
+
+
+@mod.capture
+def formatter_immune(m) -> ImmuneString:
+    """Text that can be interspersed into a formatter, e.g. characters.
+
+    It will be inserted directly, without being formatted.
+
+    """
+
+
 @mod.action_class
 class Actions:
     def formatted_text(phrase: Union[str, Phrase], formatters: str) -> str:
@@ -243,15 +259,45 @@
         actions.insert(text)
         return text
 
+    def insert_many(strings: List[str]) -> None:
+        """Insert a list of strings, sequentially."""
+        for string in strings:
+            actions.insert(string)
+
 
 @ctx.capture(rule="{self.formatters}+")
 def formatters(m):
     return ",".join(m.formatters_list)
 
 
-@ctx.capture(rule="<self.formatters> <user.text>")
+@ctx.capture(
+    # Add anything else into this that you want to be able to speak during a
+    # formatter.
+    rule="(<user.symbol_key> | <user.letter> | numb <number>)"
+)
+def formatter_immune(m) -> ImmuneString:
+    if hasattr(m, "number"):
+        value = m.number
+    else:
+        value = m[0]
+    return ImmuneString(str(value))
+
+
+@ctx.capture(
+    # Note that if the user speaks something like "snake dot", it will
+    # insert "dot" - otherwise, they wouldn't be able to insert punctuation
+    # words directly.
+    rule="<self.formatters> <user.text> (<user.text> | <user.formatter_immune>)*"
+)
 def format_text(m):
-    return format_phrase(m.text, m.formatters)
+    out = ""
+    formatters = m[0]
+    for chunk in m[1:]:
+        if isinstance(chunk, ImmuneString):
+            out += chunk.string
+        else:
+            out += format_phrase(chunk, formatters)
+    return out
 
 
 ctx.lists["self.formatters"] = formatters_words.keys()

{"golden_diff": "diff --git a/code/formatters.py b/code/formatters.py\n--- a/code/formatters.py\n+++ b/code/formatters.py\n@@ -193,6 +193,22 @@\n     \"Formats the text and returns a string\"\n \n \n+class ImmuneString(object):\n+    \"\"\"Wrapper that makes a string immune from formatting.\"\"\"\n+\n+    def __init__(self, string):\n+        self.string = string\n+\n+\n+@mod.capture\n+def formatter_immune(m) -> ImmuneString:\n+    \"\"\"Text that can be interspersed into a formatter, e.g. characters.\n+\n+    It will be inserted directly, without being formatted.\n+\n+    \"\"\"\n+\n+\n @mod.action_class\n class Actions:\n     def formatted_text(phrase: Union[str, Phrase], formatters: str) -> str:\n@@ -243,15 +259,45 @@\n         actions.insert(text)\n         return text\n \n+    def insert_many(strings: List[str]) -> None:\n+        \"\"\"Insert a list of strings, sequentially.\"\"\"\n+        for string in strings:\n+            actions.insert(string)\n+\n \n @ctx.capture(rule=\"{self.formatters}+\")\n def formatters(m):\n     return \",\".join(m.formatters_list)\n \n \n-@ctx.capture(rule=\"<self.formatters> <user.text>\")\n+@ctx.capture(\n+    # Add anything else into this that you want to be able to speak during a\n+    # formatter.\n+    rule=\"(<user.symbol_key> | <user.letter> | numb <number>)\"\n+)\n+def formatter_immune(m) -> ImmuneString:\n+    if hasattr(m, \"number\"):\n+        value = m.number\n+    else:\n+        value = m[0]\n+    return ImmuneString(str(value))\n+\n+\n+@ctx.capture(\n+    # Note that if the user speaks something like \"snake dot\", it will\n+    # insert \"dot\" - otherwise, they wouldn't be able to insert punctuation\n+    # words directly.\n+    rule=\"<self.formatters> <user.text> (<user.text> | <user.formatter_immune>)*\"\n+)\n def format_text(m):\n-    return format_phrase(m.text, m.formatters)\n+    out = \"\"\n+    formatters = m[0]\n+    for chunk in m[1:]:\n+        if isinstance(chunk, ImmuneString):\n+            out += chunk.string\n+        else:\n+            out += format_phrase(chunk, formatters)\n+    return out\n \n \n ctx.lists[\"self.formatters\"] = formatters_words.keys()\n", "issue": "feature request: `phrase hello slash world` -> `hello/world`\nI'm not sure what the best way to go about this is, it might be simple.\r\n\r\nsome more examples/unit tests:\r\n\r\n- `phrase hello slash world` -> `hello/world` (currently `hello /world`)\r\n- `phrase hello slash snake name of file slash world` -> `hello/name_of_file/world` (currently `hello/name_of_file_/world`)\r\n\r\nSimilarly: `phrase file dot extension` -> `file.extension`, though this one might be complicated by the fact that I have a formatter called `dot` defined so that `dot hello world` -> `.hello_world` because I use python so much.\n", "before_files": [{"content": "from talon import Module, Context, actions, ui, imgui\nfrom talon.grammar import Phrase\nfrom typing import List, Union\nimport re\n\nctx = Context()\nkey = actions.key\nedit = actions.edit\n\nwords_to_keep_lowercase = \"a,an,the,at,by,for,in,is,of,on,to,up,and,as,but,or,nor\".split(\n    \",\"\n)\n\n# last_phrase has the last phrase spoken, WITHOUT formatting.\n# This is needed for reformatting.\nlast_phrase = \"\"\n\n# formatted_phrase_history keeps the most recent formatted phrases, WITH formatting.\nformatted_phrase_history = []\nformatted_phrase_history_length = 20\n\n\ndef surround(by):\n    def func(i, word, last):\n        if i == 0:\n            word = by + word\n        if last:\n            word += by\n        return word\n\n    return func\n\n\ndef format_phrase(m: Union[str, Phrase], fmtrs: str):\n    global last_phrase\n    last_phrase = m\n    words = []\n    if isinstance(m, str):\n        words = m.split(\" \")\n    else:\n        if m.words[-1] == \"over\":\n            m.words = m.words[:-1]\n\n        words = actions.dictate.parse_words(m)\n        words = actions.dictate.replace_words(words)\n\n    result = format_phrase_no_history(words, fmtrs)\n\n    # Add result to history.\n    global formatted_phrase_history\n    formatted_phrase_history.insert(0, result)\n    formatted_phrase_history = formatted_phrase_history[\n        :formatted_phrase_history_length\n    ]\n\n    return result\n\n\ndef format_phrase_no_history(word_list, fmtrs: str):\n    fmtr_list = fmtrs.split(\",\")\n    words = []\n    spaces = True\n    for i, w in enumerate(word_list):\n        for name in reversed(fmtr_list):\n            smash, func = all_formatters[name]\n            w = func(i, w, i == len(word_list) - 1)\n            spaces = spaces and not smash\n        words.append(w)\n    sep = \" \" if spaces else \"\"\n    return sep.join(words)\n\n\nNOSEP = True\nSEP = False\n\n\ndef words_with_joiner(joiner):\n    \"\"\"Pass through words unchanged, but add a separator between them.\"\"\"\n\n    def formatter_function(i, word, _):\n        return word if i == 0 else joiner + word\n\n    return (NOSEP, formatter_function)\n\n\ndef first_vs_rest(first_func, rest_func=lambda w: w):\n    \"\"\"Supply one or two transformer functions for the first and rest of\n    words respectively.\n\n    Leave second argument out if you want all but the first word to be passed\n    through unchanged.\n    Set first argument to None if you want the first word to be passed\n    through unchanged.\"\"\"\n    if first_func is None:\n        first_func = lambda w: w\n\n    def formatter_function(i, word, _):\n        return first_func(word) if i == 0 else rest_func(word)\n\n    return formatter_function\n\n\ndef every_word(word_func):\n    \"\"\"Apply one function to every word.\"\"\"\n\n    def formatter_function(i, word, _):\n        return word_func(word)\n\n    return formatter_function\n\n\nformatters_dict = {\n    \"NOOP\": (SEP, lambda i, word, _: word),\n    \"DOUBLE_UNDERSCORE\": (NOSEP, first_vs_rest(lambda w: \"__%s__\" % w)),\n    \"PRIVATE_CAMEL_CASE\": (NOSEP, first_vs_rest(lambda w: w, lambda w: w.capitalize())),\n    \"PROTECTED_CAMEL_CASE\": (\n        NOSEP,\n        first_vs_rest(lambda w: w, lambda w: w.capitalize()),\n    ),\n    \"PUBLIC_CAMEL_CASE\": (NOSEP, every_word(lambda w: w.capitalize())),\n    \"SNAKE_CASE\": (\n        NOSEP,\n        first_vs_rest(lambda w: w.lower(), lambda w: \"_\" + w.lower()),\n    ),\n    \"NO_SPACES\": (NOSEP, every_word(lambda w: w)),\n    \"DASH_SEPARATED\": words_with_joiner(\"-\"),\n    \"TERMINAL_DASH_SEPARATED\": (\n        NOSEP,\n        first_vs_rest(lambda w: \" --\" + w.lower(), lambda w: \"-\" + w.lower()),\n    ),\n    \"DOUBLE_COLON_SEPARATED\": words_with_joiner(\"::\"),\n    \"ALL_CAPS\": (SEP, every_word(lambda w: w.upper())),\n    \"ALL_LOWERCASE\": (SEP, every_word(lambda w: w.lower())),\n    \"DOUBLE_QUOTED_STRING\": (SEP, surround('\"')),\n    \"SINGLE_QUOTED_STRING\": (SEP, surround(\"'\")),\n    \"SPACE_SURROUNDED_STRING\": (SEP, surround(\" \")),\n    \"DOT_SEPARATED\": words_with_joiner(\".\"),\n    \"DOT_SNAKE\": (NOSEP, lambda i, word, _: \".\" + word if i == 0 else \"_\" + word),\n    \"SLASH_SEPARATED\": (NOSEP, every_word(lambda w: \"/\" + w)),\n    \"CAPITALIZE_FIRST_WORD\": (SEP, first_vs_rest(lambda w: w.capitalize())),\n    \"CAPITALIZE_ALL_WORDS\": (\n        SEP,\n        lambda i, word, _: word.capitalize()\n        if i == 0 or word not in words_to_keep_lowercase\n        else word,\n    ),\n    \"FIRST_THREE\": (NOSEP, lambda i, word, _: word[0:3]),\n    \"FIRST_FOUR\": (NOSEP, lambda i, word, _: word[0:4]),\n    \"FIRST_FIVE\": (NOSEP, lambda i, word, _: word[0:5]),\n}\n\n# This is the mapping from spoken phrases to formatters\nformatters_words = {\n    \"allcaps\": formatters_dict[\"ALL_CAPS\"],\n    \"alldown\": formatters_dict[\"ALL_LOWERCASE\"],\n    \"camel\": formatters_dict[\"PRIVATE_CAMEL_CASE\"],\n    \"dotted\": formatters_dict[\"DOT_SEPARATED\"],\n    \"dubstring\": formatters_dict[\"DOUBLE_QUOTED_STRING\"],\n    \"dunder\": formatters_dict[\"DOUBLE_UNDERSCORE\"],\n    \"hammer\": formatters_dict[\"PUBLIC_CAMEL_CASE\"],\n    \"kebab\": formatters_dict[\"DASH_SEPARATED\"],\n    \"packed\": formatters_dict[\"DOUBLE_COLON_SEPARATED\"],\n    \"padded\": formatters_dict[\"SPACE_SURROUNDED_STRING\"],\n    # \"say\": formatters_dict[\"NOOP\"],\n    \"sentence\": formatters_dict[\"CAPITALIZE_FIRST_WORD\"],\n    \"slasher\": formatters_dict[\"SLASH_SEPARATED\"],\n    \"smash\": formatters_dict[\"NO_SPACES\"],\n    \"snake\": formatters_dict[\"SNAKE_CASE\"],\n    # \"speak\": formatters_dict[\"NOOP\"],\n    \"string\": formatters_dict[\"SINGLE_QUOTED_STRING\"],\n    \"title\": formatters_dict[\"CAPITALIZE_ALL_WORDS\"],\n    # disable a few formatters for now\n    # \"tree\": formatters_dict[\"FIRST_THREE\"],\n    # \"quad\": formatters_dict[\"FIRST_FOUR\"],\n    # \"fiver\": formatters_dict[\"FIRST_FIVE\"],\n}\n\nall_formatters = {}\nall_formatters.update(formatters_dict)\nall_formatters.update(formatters_words)\n\nmod = Module()\nmod.list(\"formatters\", desc=\"list of formatters\")\n\n\n@mod.capture\ndef formatters(m) -> str:\n    \"Returns a comma-separated string of formatters e.g. 'SNAKE,DUBSTRING'\"\n\n\n@mod.capture\ndef format_text(m) -> str:\n    \"Formats the text and returns a string\"\n\n\n@mod.action_class\nclass Actions:\n    def formatted_text(phrase: Union[str, Phrase], formatters: str) -> str:\n        \"\"\"Formats a phrase according to formatters. formatters is a comma-separated string of formatters (e.g. 'CAPITALIZE_ALL_WORDS,DOUBLE_QUOTED_STRING')\"\"\"\n        return format_phrase(phrase, formatters)\n\n    def formatters_help_toggle():\n        \"\"\"Lists all formatters\"\"\"\n        if gui.showing:\n            gui.hide()\n        else:\n            gui.show()\n\n    def formatters_recent_toggle():\n        \"\"\"Toggles list of recent formatters\"\"\"\n        if recent_gui.showing:\n            recent_gui.hide()\n        else:\n            recent_gui.show()\n\n    def formatters_recent_select(number: int):\n        \"\"\"Inserts a recent formatter\"\"\"\n        if len(formatted_phrase_history) >= number:\n            return formatted_phrase_history[number - 1]\n        return \"\"\n\n    def formatters_clear_last():\n        \"\"\"Clears the last formatted phrase\"\"\"\n        if len(formatted_phrase_history) > 0:\n            for character in formatted_phrase_history[0]:\n                actions.edit.delete()\n\n    def formatters_reformat_last(formatters: str) -> str:\n        \"\"\"Reformats last formatted phrase\"\"\"\n        global last_phrase\n        return format_phrase(last_phrase, formatters)\n\n    def formatters_reformat_selection(formatters: str) -> str:\n        \"\"\"Reformats the current selection.\"\"\"\n        selected = edit.selected_text()\n        unformatted = re.sub(r\"[^a-zA-Z0-9]+\", \" \", selected).lower()\n        # TODO: Separate out camelcase & studleycase vars\n\n        # Delete separately for compatibility with programs that don't overwrite\n        # selected text (e.g. Emacs)\n        edit.delete()\n        text = actions.self.formatted_text(unformatted, formatters)\n        actions.insert(text)\n        return text\n\n\n@ctx.capture(rule=\"{self.formatters}+\")\ndef formatters(m):\n    return \",\".join(m.formatters_list)\n\n\n@ctx.capture(rule=\"<self.formatters> <user.text>\")\ndef format_text(m):\n    return format_phrase(m.text, m.formatters)\n\n\nctx.lists[\"self.formatters\"] = formatters_words.keys()\n\n\n@imgui.open(software=False)\ndef gui(gui: imgui.GUI):\n    gui.text(\"List formatters\")\n    gui.line()\n    for name in sorted(set(formatters_words.keys())):\n        gui.text(f\"{name} | {format_phrase_no_history(['one', 'two', 'three'], name)}\")\n\n\n@imgui.open(software=False)\ndef recent_gui(gui: imgui.GUI):\n    gui.text(\"Recent formatters\")\n    gui.line()\n    for index, result in enumerate(formatted_phrase_history, 1):\n        gui.text(\"{}. {}\".format(index, result))\n", "path": "code/formatters.py"}], "after_files": [{"content": "from talon import Module, Context, actions, ui, imgui\nfrom talon.grammar import Phrase\nfrom typing import List, Union\nimport re\n\nctx = Context()\nkey = actions.key\nedit = actions.edit\n\nwords_to_keep_lowercase = \"a,an,the,at,by,for,in,is,of,on,to,up,and,as,but,or,nor\".split(\n    \",\"\n)\n\n# last_phrase has the last phrase spoken, WITHOUT formatting.\n# This is needed for reformatting.\nlast_phrase = \"\"\n\n# formatted_phrase_history keeps the most recent formatted phrases, WITH formatting.\nformatted_phrase_history = []\nformatted_phrase_history_length = 20\n\n\ndef surround(by):\n    def func(i, word, last):\n        if i == 0:\n            word = by + word\n        if last:\n            word += by\n        return word\n\n    return func\n\n\ndef format_phrase(m: Union[str, Phrase], fmtrs: str):\n    global last_phrase\n    last_phrase = m\n    words = []\n    if isinstance(m, str):\n        words = m.split(\" \")\n    else:\n        if m.words[-1] == \"over\":\n            m.words = m.words[:-1]\n\n        words = actions.dictate.parse_words(m)\n        words = actions.dictate.replace_words(words)\n\n    result = format_phrase_no_history(words, fmtrs)\n\n    # Add result to history.\n    global formatted_phrase_history\n    formatted_phrase_history.insert(0, result)\n    formatted_phrase_history = formatted_phrase_history[\n        :formatted_phrase_history_length\n    ]\n\n    return result\n\n\ndef format_phrase_no_history(word_list, fmtrs: str):\n    fmtr_list = fmtrs.split(\",\")\n    words = []\n    spaces = True\n    for i, w in enumerate(word_list):\n        for name in reversed(fmtr_list):\n            smash, func = all_formatters[name]\n            w = func(i, w, i == len(word_list) - 1)\n            spaces = spaces and not smash\n        words.append(w)\n    sep = \" \" if spaces else \"\"\n    return sep.join(words)\n\n\nNOSEP = True\nSEP = False\n\n\ndef words_with_joiner(joiner):\n    \"\"\"Pass through words unchanged, but add a separator between them.\"\"\"\n\n    def formatter_function(i, word, _):\n        return word if i == 0 else joiner + word\n\n    return (NOSEP, formatter_function)\n\n\ndef first_vs_rest(first_func, rest_func=lambda w: w):\n    \"\"\"Supply one or two transformer functions for the first and rest of\n    words respectively.\n\n    Leave second argument out if you want all but the first word to be passed\n    through unchanged.\n    Set first argument to None if you want the first word to be passed\n    through unchanged.\"\"\"\n    if first_func is None:\n        first_func = lambda w: w\n\n    def formatter_function(i, word, _):\n        return first_func(word) if i == 0 else rest_func(word)\n\n    return formatter_function\n\n\ndef every_word(word_func):\n    \"\"\"Apply one function to every word.\"\"\"\n\n    def formatter_function(i, word, _):\n        return word_func(word)\n\n    return formatter_function\n\n\nformatters_dict = {\n    \"NOOP\": (SEP, lambda i, word, _: word),\n    \"DOUBLE_UNDERSCORE\": (NOSEP, first_vs_rest(lambda w: \"__%s__\" % w)),\n    \"PRIVATE_CAMEL_CASE\": (NOSEP, first_vs_rest(lambda w: w, lambda w: w.capitalize())),\n    \"PROTECTED_CAMEL_CASE\": (\n        NOSEP,\n        first_vs_rest(lambda w: w, lambda w: w.capitalize()),\n    ),\n    \"PUBLIC_CAMEL_CASE\": (NOSEP, every_word(lambda w: w.capitalize())),\n    \"SNAKE_CASE\": (\n        NOSEP,\n        first_vs_rest(lambda w: w.lower(), lambda w: \"_\" + w.lower()),\n    ),\n    \"NO_SPACES\": (NOSEP, every_word(lambda w: w)),\n    \"DASH_SEPARATED\": words_with_joiner(\"-\"),\n    \"TERMINAL_DASH_SEPARATED\": (\n        NOSEP,\n        first_vs_rest(lambda w: \" --\" + w.lower(), lambda w: \"-\" + w.lower()),\n    ),\n    \"DOUBLE_COLON_SEPARATED\": words_with_joiner(\"::\"),\n    \"ALL_CAPS\": (SEP, every_word(lambda w: w.upper())),\n    \"ALL_LOWERCASE\": (SEP, every_word(lambda w: w.lower())),\n    \"DOUBLE_QUOTED_STRING\": (SEP, surround('\"')),\n    \"SINGLE_QUOTED_STRING\": (SEP, surround(\"'\")),\n    \"SPACE_SURROUNDED_STRING\": (SEP, surround(\" \")),\n    \"DOT_SEPARATED\": words_with_joiner(\".\"),\n    \"DOT_SNAKE\": (NOSEP, lambda i, word, _: \".\" + word if i == 0 else \"_\" + word),\n    \"SLASH_SEPARATED\": (NOSEP, every_word(lambda w: \"/\" + w)),\n    \"CAPITALIZE_FIRST_WORD\": (SEP, first_vs_rest(lambda w: w.capitalize())),\n    \"CAPITALIZE_ALL_WORDS\": (\n        SEP,\n        lambda i, word, _: word.capitalize()\n        if i == 0 or word not in words_to_keep_lowercase\n        else word,\n    ),\n    \"FIRST_THREE\": (NOSEP, lambda i, word, _: word[0:3]),\n    \"FIRST_FOUR\": (NOSEP, lambda i, word, _: word[0:4]),\n    \"FIRST_FIVE\": (NOSEP, lambda i, word, _: word[0:5]),\n}\n\n# This is the mapping from spoken phrases to formatters\nformatters_words = {\n    \"allcaps\": formatters_dict[\"ALL_CAPS\"],\n    \"alldown\": formatters_dict[\"ALL_LOWERCASE\"],\n    \"camel\": formatters_dict[\"PRIVATE_CAMEL_CASE\"],\n    \"dotted\": formatters_dict[\"DOT_SEPARATED\"],\n    \"dubstring\": formatters_dict[\"DOUBLE_QUOTED_STRING\"],\n    \"dunder\": formatters_dict[\"DOUBLE_UNDERSCORE\"],\n    \"hammer\": formatters_dict[\"PUBLIC_CAMEL_CASE\"],\n    \"kebab\": formatters_dict[\"DASH_SEPARATED\"],\n    \"packed\": formatters_dict[\"DOUBLE_COLON_SEPARATED\"],\n    \"padded\": formatters_dict[\"SPACE_SURROUNDED_STRING\"],\n    # \"say\": formatters_dict[\"NOOP\"],\n    \"sentence\": formatters_dict[\"CAPITALIZE_FIRST_WORD\"],\n    \"slasher\": formatters_dict[\"SLASH_SEPARATED\"],\n    \"smash\": formatters_dict[\"NO_SPACES\"],\n    \"snake\": formatters_dict[\"SNAKE_CASE\"],\n    # \"speak\": formatters_dict[\"NOOP\"],\n    \"string\": formatters_dict[\"SINGLE_QUOTED_STRING\"],\n    \"title\": formatters_dict[\"CAPITALIZE_ALL_WORDS\"],\n    # disable a few formatters for now\n    # \"tree\": formatters_dict[\"FIRST_THREE\"],\n    # \"quad\": formatters_dict[\"FIRST_FOUR\"],\n    # \"fiver\": formatters_dict[\"FIRST_FIVE\"],\n}\n\nall_formatters = {}\nall_formatters.update(formatters_dict)\nall_formatters.update(formatters_words)\n\nmod = Module()\nmod.list(\"formatters\", desc=\"list of formatters\")\n\n\n@mod.capture\ndef formatters(m) -> str:\n    \"Returns a comma-separated string of formatters e.g. 'SNAKE,DUBSTRING'\"\n\n\n@mod.capture\ndef format_text(m) -> str:\n    \"Formats the text and returns a string\"\n\n\nclass ImmuneString(object):\n    \"\"\"Wrapper that makes a string immune from formatting.\"\"\"\n\n    def __init__(self, string):\n        self.string = string\n\n\n@mod.capture\ndef formatter_immune(m) -> ImmuneString:\n    \"\"\"Text that can be interspersed into a formatter, e.g. characters.\n\n    It will be inserted directly, without being formatted.\n\n    \"\"\"\n\n\n@mod.action_class\nclass Actions:\n    def formatted_text(phrase: Union[str, Phrase], formatters: str) -> str:\n        \"\"\"Formats a phrase according to formatters. formatters is a comma-separated string of formatters (e.g. 'CAPITALIZE_ALL_WORDS,DOUBLE_QUOTED_STRING')\"\"\"\n        return format_phrase(phrase, formatters)\n\n    def formatters_help_toggle():\n        \"\"\"Lists all formatters\"\"\"\n        if gui.showing:\n            gui.hide()\n        else:\n            gui.show()\n\n    def formatters_recent_toggle():\n        \"\"\"Toggles list of recent formatters\"\"\"\n        if recent_gui.showing:\n            recent_gui.hide()\n        else:\n            recent_gui.show()\n\n    def formatters_recent_select(number: int):\n        \"\"\"Inserts a recent formatter\"\"\"\n        if len(formatted_phrase_history) >= number:\n            return formatted_phrase_history[number - 1]\n        return \"\"\n\n    def formatters_clear_last():\n        \"\"\"Clears the last formatted phrase\"\"\"\n        if len(formatted_phrase_history) > 0:\n            for character in formatted_phrase_history[0]:\n                actions.edit.delete()\n\n    def formatters_reformat_last(formatters: str) -> str:\n        \"\"\"Reformats last formatted phrase\"\"\"\n        global last_phrase\n        return format_phrase(last_phrase, formatters)\n\n    def formatters_reformat_selection(formatters: str) -> str:\n        \"\"\"Reformats the current selection.\"\"\"\n        selected = edit.selected_text()\n        unformatted = re.sub(r\"[^a-zA-Z0-9]+\", \" \", selected).lower()\n        # TODO: Separate out camelcase & studleycase vars\n\n        # Delete separately for compatibility with programs that don't overwrite\n        # selected text (e.g. Emacs)\n        edit.delete()\n        text = actions.self.formatted_text(unformatted, formatters)\n        actions.insert(text)\n        return text\n\n    def insert_many(strings: List[str]) -> None:\n        \"\"\"Insert a list of strings, sequentially.\"\"\"\n        for string in strings:\n            actions.insert(string)\n\n\n@ctx.capture(rule=\"{self.formatters}+\")\ndef formatters(m):\n    return \",\".join(m.formatters_list)\n\n\n@ctx.capture(\n    # Add anything else into this that you want to be able to speak during a\n    # formatter.\n    rule=\"(<user.symbol_key> | <user.letter> | numb <number>)\"\n)\ndef formatter_immune(m) -> ImmuneString:\n    if hasattr(m, \"number\"):\n        value = m.number\n    else:\n        value = m[0]\n    return ImmuneString(str(value))\n\n\n@ctx.capture(\n    # Note that if the user speaks something like \"snake dot\", it will\n    # insert \"dot\" - otherwise, they wouldn't be able to insert punctuation\n    # words directly.\n    rule=\"<self.formatters> <user.text> (<user.text> | <user.formatter_immune>)*\"\n)\ndef format_text(m):\n    out = \"\"\n    formatters = m[0]\n    for chunk in m[1:]:\n        if isinstance(chunk, ImmuneString):\n            out += chunk.string\n        else:\n            out += format_phrase(chunk, formatters)\n    return out\n\n\nctx.lists[\"self.formatters\"] = formatters_words.keys()\n\n\n@imgui.open(software=False)\ndef gui(gui: imgui.GUI):\n    gui.text(\"List formatters\")\n    gui.line()\n    for name in sorted(set(formatters_words.keys())):\n        gui.text(f\"{name} | {format_phrase_no_history(['one', 'two', 'three'], name)}\")\n\n\n@imgui.open(software=False)\ndef recent_gui(gui: imgui.GUI):\n    gui.text(\"Recent formatters\")\n    gui.line()\n    for index, result in enumerate(formatted_phrase_history, 1):\n        gui.text(\"{}. {}\".format(index, result))\n", "path": "code/formatters.py"}]}