@@ -64,6 +64,307 @@ async fn insert_to_normal_mode_cursor_position() -> anyhow::Result<()> {
6464 Ok(())
6565}
6666
67+#[tokio::test(flavor = "multi_thread")]
68+async fn surround_by_character() -> anyhow::Result<()> {
69+ // Only pairs matching the passed character count
70+ test((
71+ "(so [many {go#[o|]#d} text] here)",
72+ "mi{",
73+ "(so [many {#[good|]#} text] here)",
74+ ))
75+ .await?;
76+ test((
77+ "(so [many {go#[o|]#d} text] here)",
78+ "mi[",
79+ "(so [#[many {good} text|]#] here)",
80+ ))
81+ .await?;
82+ test((
83+ "(so [many {go#[o|]#d} text] here)",
84+ "mi(",
85+ "(#[so [many {good} text] here|]#)",
86+ ))
87+ .await?;
88+
89+ // Works with characters that aren't pairs too
90+ test((
91+ "'so 'many 'go#[o|]#d' text' here'",
92+ "mi'",
93+ "'so 'many '#[good|]#' text' here'",
94+ ))
95+ .await?;
96+ test((
97+ "'so 'many 'go#[o|]#d' text' here'",
98+ "2mi'",
99+ "'so '#[many 'good' text|]#' here'",
100+ ))
101+ .await?;
102+ test((
103+ "'so \"many 'go#[o|]#d' text\" here'",
104+ "mi\"",
105+ "'so \"#[many 'good' text|]#\" here'",
106+ ))
107+ .await?;
108+
109+ Ok(())
110+}
111+
112+#[tokio::test(flavor = "multi_thread")]
113+async fn surround_inside_pair() -> anyhow::Result<()> {
114+ // Works at first character of buffer
115+ // TODO: Adjust test when opening pair failure is fixed
116+ test(("#[(|]#something)", "mim", "#[(|]#something)")).await?;
117+
118+ // Inside a valid pair selects pair
119+ test(("some (#[t|]#ext) here", "mim", "some (#[text|]#) here")).await?;
120+
121+ // On pair character selects pair
122+ // TODO: Opening pair character is a known failure case that needs addressing
123+ // test(("some #[(|]#text) here", "mim", "some (#[text|]#) here")).await?;
124+ test(("some (text#[)|]# here", "mim", "some (#[text|]#) here")).await?;
125+
126+ // No valid pair does nothing
127+ test(("so#[m|]#e (text) here", "mim", "so#[m|]#e (text) here")).await?;
128+
129+ // Count skips to outer pairs
130+ test((
131+ "(so (many (go#[o|]#d) text) here)",
132+ "1mim",
133+ "(so (many (#[good|]#) text) here)",
134+ ))
135+ .await?;
136+ test((
137+ "(so (many (go#[o|]#d) text) here)",
138+ "2mim",
139+ "(so (#[many (good) text|]#) here)",
140+ ))
141+ .await?;
142+ test((
143+ "(so (many (go#[o|]#d) text) here)",
144+ "3mim",
145+ "(#[so (many (good) text) here|]#)",
146+ ))
147+ .await?;
148+
149+ // Matching pairs outside selection don't match
150+ test((
151+ "((so)((many) go#[o|]#d (text))(here))",
152+ "mim",
153+ "((so)(#[(many) good (text)|]#)(here))",
154+ ))
155+ .await?;
156+ test((
157+ "((so)((many) go#[o|]#d (text))(here))",
158+ "2mim",
159+ "(#[(so)((many) good (text))(here)|]#)",
160+ ))
161+ .await?;
162+
163+ // Works with mixed braces
164+ test((
165+ "(so [many {go#[o|]#d} text] here)",
166+ "mim",
167+ "(so [many {#[good|]#} text] here)",
168+ ))
169+ .await?;
170+ test((
171+ "(so [many {go#[o|]#d} text] here)",
172+ "2mim",
173+ "(so [#[many {good} text|]#] here)",
174+ ))
175+ .await?;
176+ test((
177+ "(so [many {go#[o|]#d} text] here)",
178+ "3mim",
179+ "(#[so [many {good} text] here|]#)",
180+ ))
181+ .await?;
182+
183+ // Selection direction is preserved
184+ test((
185+ "(so [many {go#[|od]#} text] here)",
186+ "mim",
187+ "(so [many {#[|good]#} text] here)",
188+ ))
189+ .await?;
190+ test((
191+ "(so [many {go#[|od]#} text] here)",
192+ "2mim",
193+ "(so [#[|many {good} text]#] here)",
194+ ))
195+ .await?;
196+ test((
197+ "(so [many {go#[|od]#} text] here)",
198+ "3mim",
199+ "(#[|so [many {good} text] here]#)",
200+ ))
201+ .await?;
202+
203+ // Only pairs outside of full selection range are considered
204+ test((
205+ "(so (many (go#[od) |]#text) here)",
206+ "mim",
207+ "(so (#[many (good) text|]#) here)",
208+ ))
209+ .await?;
210+ test((
211+ "(so (many#[ (go|]#od) text) here)",
212+ "mim",
213+ "(so (#[many (good) text|]#) here)",
214+ ))
215+ .await?;
216+ test((
217+ "(so#[ (many (go|]#od) text) here)",
218+ "mim",
219+ "(#[so (many (good) text) here|]#)",
220+ ))
221+ .await?;
222+ test((
223+ "(so (many (go#[od) text) |]#here)",
224+ "mim",
225+ "(#[so (many (good) text) here|]#)",
226+ ))
227+ .await?;
228+
229+ // Works with multiple cursors
230+ test((
231+ "(so (many (good) text) #[he|]#re\nso (many (good) text) #(|he)#re)",
232+ "mim",
233+ "(#[so (many (good) text) here\nso (many (good) text) here|]#)",
234+ ))
235+ .await?;
236+
237+ Ok(())
238+}
239+
240+#[tokio::test(flavor = "multi_thread")]
241+async fn surround_around_pair() -> anyhow::Result<()> {
242+ // Works at first character of buffer
243+ // TODO: Adjust test when opening pair failure is fixed
244+ test(("#[(|]#something)", "mam", "#[(|]#something)")).await?;
245+
246+ // Inside a valid pair selects pair
247+ test(("some (#[t|]#ext) here", "mam", "some #[(text)|]# here")).await?;
248+
249+ // On pair character selects pair
250+ // TODO: Opening pair character is a known failure case that needs addressing
251+ // test(("some #[(|]#text) here", "mam", "some #[(text)|]# here")).await?;
252+ test(("some (text#[)|]# here", "mam", "some #[(text)|]# here")).await?;
253+
254+ // No valid pair does nothing
255+ test(("so#[m|]#e (text) here", "mam", "so#[m|]#e (text) here")).await?;
256+
257+ // Count skips to outer pairs
258+ test((
259+ "(so (many (go#[o|]#d) text) here)",
260+ "1mam",
261+ "(so (many #[(good)|]# text) here)",
262+ ))
263+ .await?;
264+ test((
265+ "(so (many (go#[o|]#d) text) here)",
266+ "2mam",
267+ "(so #[(many (good) text)|]# here)",
268+ ))
269+ .await?;
270+ test((
271+ "(so (many (go#[o|]#d) text) here)",
272+ "3mam",
273+ "#[(so (many (good) text) here)|]#",
274+ ))
275+ .await?;
276+
277+ // Matching pairs outside selection don't match
278+ test((
279+ "((so)((many) go#[o|]#d (text))(here))",
280+ "mam",
281+ "((so)#[((many) good (text))|]#(here))",
282+ ))
283+ .await?;
284+ test((
285+ "((so)((many) go#[o|]#d (text))(here))",
286+ "2mam",
287+ "#[((so)((many) good (text))(here))|]#",
288+ ))
289+ .await?;
290+
291+ // Works with mixed braces
292+ test((
293+ "(so [many {go#[o|]#d} text] here)",
294+ "mam",
295+ "(so [many #[{good}|]# text] here)",
296+ ))
297+ .await?;
298+ test((
299+ "(so [many {go#[o|]#d} text] here)",
300+ "2mam",
301+ "(so #[[many {good} text]|]# here)",
302+ ))
303+ .await?;
304+ test((
305+ "(so [many {go#[o|]#d} text] here)",
306+ "3mam",
307+ "#[(so [many {good} text] here)|]#",
308+ ))
309+ .await?;
310+
311+ // Selection direction is preserved
312+ test((
313+ "(so [many {go#[|od]#} text] here)",
314+ "mam",
315+ "(so [many #[|{good}]# text] here)",
316+ ))
317+ .await?;
318+ test((
319+ "(so [many {go#[|od]#} text] here)",
320+ "2mam",
321+ "(so #[|[many {good} text]]# here)",
322+ ))
323+ .await?;
324+ test((
325+ "(so [many {go#[|od]#} text] here)",
326+ "3mam",
327+ "#[|(so [many {good} text] here)]#",
328+ ))
329+ .await?;
330+
331+ // Only pairs outside of full selection range are considered
332+ test((
333+ "(so (many (go#[od) |]#text) here)",
334+ "mam",
335+ "(so #[(many (good) text)|]# here)",
336+ ))
337+ .await?;
338+ test((
339+ "(so (many#[ (go|]#od) text) here)",
340+ "mam",
341+ "(so #[(many (good) text)|]# here)",
342+ ))
343+ .await?;
344+ test((
345+ "(so#[ (many (go|]#od) text) here)",
346+ "mam",
347+ "#[(so (many (good) text) here)|]#",
348+ ))
349+ .await?;
350+ test((
351+ "(so (many (go#[od) text) |]#here)",
352+ "mam",
353+ "#[(so (many (good) text) here)|]#",
354+ ))
355+ .await?;
356+
357+ // Works with multiple cursors
358+ test((
359+ "(so (many (good) text) #[he|]#re\nso (many (good) text) #(|he)#re)",
360+ "mam",
361+ "#[(so (many (good) text) here\nso (many (good) text) here)|]#",
362+ ))
363+ .await?;
364+
365+ Ok(())
366+}
367+
67368/// Ensure the very initial cursor in an opened file is the width of
68369/// the first grapheme
69370#[tokio::test(flavor = "multi_thread")]