We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
formatters should apply to selected text if they are spoken alone
for example `yeller` while some text is selected should pass the selected text through the formatter and replace the selected text with the formatted text.

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

{"golden_diff": "diff --git a/code/formatters.py b/code/formatters.py\n--- a/code/formatters.py\n+++ b/code/formatters.py\n@@ -1,9 +1,11 @@\n from talon import Module, Context, actions, ui, imgui\n from talon.grammar import Phrase\n from typing import List, Union\n+import re\n \n ctx = Context()\n key = actions.key\n+edit = actions.edit\n \n words_to_keep_lowercase = \"a,an,the,at,by,for,in,is,of,on,to,up,and,as,but,or,nor\".split(\n     \",\"\n@@ -228,6 +230,19 @@\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}+\")\n def formatters(m):\n", "issue": "formatters should apply to selected text if they are spoken alone\nfor example `yeller` while some text is selected should pass the selected text through the formatter and replace the selected text with the formatted text.\n", "before_files": [{"content": "from talon import Module, Context, actions, ui, imgui\nfrom talon.grammar import Phrase\nfrom typing import List, Union\n\nctx = Context()\nkey = actions.key\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\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\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"}]}