1use drag_and_drop::DragAndDrop;
2use futures::StreamExt;
3use indoc::indoc;
4use std::{cell::RefCell, rc::Rc, time::Instant};
5use unindent::Unindent;
6
7use super::*;
8use crate::test::{
9 assert_text_with_selections, build_editor, editor_lsp_test_context::EditorLspTestContext,
10 editor_test_context::EditorTestContext, select_ranges,
11};
12use gpui::{
13 executor::Deterministic,
14 geometry::{rect::RectF, vector::vec2f},
15 platform::{WindowBounds, WindowOptions},
16};
17use language::{FakeLspAdapter, LanguageConfig, LanguageRegistry, Point};
18use project::FakeFs;
19use settings::EditorSettings;
20use util::{
21 assert_set_eq,
22 test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
23};
24use workspace::{
25 item::{FollowableItem, ItemHandle},
26 NavigationEntry, Pane, ViewId,
27};
28
29#[gpui::test]
30fn test_edit_events(cx: &mut MutableAppContext) {
31 cx.set_global(Settings::test(cx));
32 let buffer = cx.add_model(|cx| {
33 let mut buffer = language::Buffer::new(0, "123456", cx);
34 buffer.set_group_interval(Duration::from_secs(1));
35 buffer
36 });
37
38 let events = Rc::new(RefCell::new(Vec::new()));
39 let (_, editor1) = cx.add_window(Default::default(), {
40 let events = events.clone();
41 |cx| {
42 cx.subscribe(&cx.handle(), move |_, _, event, _| {
43 if matches!(
44 event,
45 Event::Edited | Event::BufferEdited | Event::DirtyChanged
46 ) {
47 events.borrow_mut().push(("editor1", event.clone()));
48 }
49 })
50 .detach();
51 Editor::for_buffer(buffer.clone(), None, cx)
52 }
53 });
54 let (_, editor2) = cx.add_window(Default::default(), {
55 let events = events.clone();
56 |cx| {
57 cx.subscribe(&cx.handle(), move |_, _, event, _| {
58 if matches!(
59 event,
60 Event::Edited | Event::BufferEdited | Event::DirtyChanged
61 ) {
62 events.borrow_mut().push(("editor2", event.clone()));
63 }
64 })
65 .detach();
66 Editor::for_buffer(buffer.clone(), None, cx)
67 }
68 });
69 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
70
71 // Mutating editor 1 will emit an `Edited` event only for that editor.
72 editor1.update(cx, |editor, cx| editor.insert("X", cx));
73 assert_eq!(
74 mem::take(&mut *events.borrow_mut()),
75 [
76 ("editor1", Event::Edited),
77 ("editor1", Event::BufferEdited),
78 ("editor2", Event::BufferEdited),
79 ("editor1", Event::DirtyChanged),
80 ("editor2", Event::DirtyChanged)
81 ]
82 );
83
84 // Mutating editor 2 will emit an `Edited` event only for that editor.
85 editor2.update(cx, |editor, cx| editor.delete(&Delete, cx));
86 assert_eq!(
87 mem::take(&mut *events.borrow_mut()),
88 [
89 ("editor2", Event::Edited),
90 ("editor1", Event::BufferEdited),
91 ("editor2", Event::BufferEdited),
92 ]
93 );
94
95 // Undoing on editor 1 will emit an `Edited` event only for that editor.
96 editor1.update(cx, |editor, cx| editor.undo(&Undo, cx));
97 assert_eq!(
98 mem::take(&mut *events.borrow_mut()),
99 [
100 ("editor1", Event::Edited),
101 ("editor1", Event::BufferEdited),
102 ("editor2", Event::BufferEdited),
103 ("editor1", Event::DirtyChanged),
104 ("editor2", Event::DirtyChanged),
105 ]
106 );
107
108 // Redoing on editor 1 will emit an `Edited` event only for that editor.
109 editor1.update(cx, |editor, cx| editor.redo(&Redo, cx));
110 assert_eq!(
111 mem::take(&mut *events.borrow_mut()),
112 [
113 ("editor1", Event::Edited),
114 ("editor1", Event::BufferEdited),
115 ("editor2", Event::BufferEdited),
116 ("editor1", Event::DirtyChanged),
117 ("editor2", Event::DirtyChanged),
118 ]
119 );
120
121 // Undoing on editor 2 will emit an `Edited` event only for that editor.
122 editor2.update(cx, |editor, cx| editor.undo(&Undo, cx));
123 assert_eq!(
124 mem::take(&mut *events.borrow_mut()),
125 [
126 ("editor2", Event::Edited),
127 ("editor1", Event::BufferEdited),
128 ("editor2", Event::BufferEdited),
129 ("editor1", Event::DirtyChanged),
130 ("editor2", Event::DirtyChanged),
131 ]
132 );
133
134 // Redoing on editor 2 will emit an `Edited` event only for that editor.
135 editor2.update(cx, |editor, cx| editor.redo(&Redo, cx));
136 assert_eq!(
137 mem::take(&mut *events.borrow_mut()),
138 [
139 ("editor2", Event::Edited),
140 ("editor1", Event::BufferEdited),
141 ("editor2", Event::BufferEdited),
142 ("editor1", Event::DirtyChanged),
143 ("editor2", Event::DirtyChanged),
144 ]
145 );
146
147 // No event is emitted when the mutation is a no-op.
148 editor2.update(cx, |editor, cx| {
149 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
150
151 editor.backspace(&Backspace, cx);
152 });
153 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
154}
155
156#[gpui::test]
157fn test_undo_redo_with_selection_restoration(cx: &mut MutableAppContext) {
158 cx.set_global(Settings::test(cx));
159 let mut now = Instant::now();
160 let buffer = cx.add_model(|cx| language::Buffer::new(0, "123456", cx));
161 let group_interval = buffer.read(cx).transaction_group_interval();
162 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
163 let (_, editor) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
164
165 editor.update(cx, |editor, cx| {
166 editor.start_transaction_at(now, cx);
167 editor.change_selections(None, cx, |s| s.select_ranges([2..4]));
168
169 editor.insert("cd", cx);
170 editor.end_transaction_at(now, cx);
171 assert_eq!(editor.text(cx), "12cd56");
172 assert_eq!(editor.selections.ranges(cx), vec![4..4]);
173
174 editor.start_transaction_at(now, cx);
175 editor.change_selections(None, cx, |s| s.select_ranges([4..5]));
176 editor.insert("e", cx);
177 editor.end_transaction_at(now, cx);
178 assert_eq!(editor.text(cx), "12cde6");
179 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
180
181 now += group_interval + Duration::from_millis(1);
182 editor.change_selections(None, cx, |s| s.select_ranges([2..2]));
183
184 // Simulate an edit in another editor
185 buffer.update(cx, |buffer, cx| {
186 buffer.start_transaction_at(now, cx);
187 buffer.edit([(0..1, "a")], None, cx);
188 buffer.edit([(1..1, "b")], None, cx);
189 buffer.end_transaction_at(now, cx);
190 });
191
192 assert_eq!(editor.text(cx), "ab2cde6");
193 assert_eq!(editor.selections.ranges(cx), vec![3..3]);
194
195 // Last transaction happened past the group interval in a different editor.
196 // Undo it individually and don't restore selections.
197 editor.undo(&Undo, cx);
198 assert_eq!(editor.text(cx), "12cde6");
199 assert_eq!(editor.selections.ranges(cx), vec![2..2]);
200
201 // First two transactions happened within the group interval in this editor.
202 // Undo them together and restore selections.
203 editor.undo(&Undo, cx);
204 editor.undo(&Undo, cx); // Undo stack is empty here, so this is a no-op.
205 assert_eq!(editor.text(cx), "123456");
206 assert_eq!(editor.selections.ranges(cx), vec![0..0]);
207
208 // Redo the first two transactions together.
209 editor.redo(&Redo, cx);
210 assert_eq!(editor.text(cx), "12cde6");
211 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
212
213 // Redo the last transaction on its own.
214 editor.redo(&Redo, cx);
215 assert_eq!(editor.text(cx), "ab2cde6");
216 assert_eq!(editor.selections.ranges(cx), vec![6..6]);
217
218 // Test empty transactions.
219 editor.start_transaction_at(now, cx);
220 editor.end_transaction_at(now, cx);
221 editor.undo(&Undo, cx);
222 assert_eq!(editor.text(cx), "12cde6");
223 });
224}
225
226#[gpui::test]
227fn test_ime_composition(cx: &mut MutableAppContext) {
228 cx.set_global(Settings::test(cx));
229 let buffer = cx.add_model(|cx| {
230 let mut buffer = language::Buffer::new(0, "abcde", cx);
231 // Ensure automatic grouping doesn't occur.
232 buffer.set_group_interval(Duration::ZERO);
233 buffer
234 });
235
236 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
237 cx.add_window(Default::default(), |cx| {
238 let mut editor = build_editor(buffer.clone(), cx);
239
240 // Start a new IME composition.
241 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
242 editor.replace_and_mark_text_in_range(Some(0..1), "á", None, cx);
243 editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, cx);
244 assert_eq!(editor.text(cx), "äbcde");
245 assert_eq!(
246 editor.marked_text_ranges(cx),
247 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
248 );
249
250 // Finalize IME composition.
251 editor.replace_text_in_range(None, "ā", cx);
252 assert_eq!(editor.text(cx), "ābcde");
253 assert_eq!(editor.marked_text_ranges(cx), None);
254
255 // IME composition edits are grouped and are undone/redone at once.
256 editor.undo(&Default::default(), cx);
257 assert_eq!(editor.text(cx), "abcde");
258 assert_eq!(editor.marked_text_ranges(cx), None);
259 editor.redo(&Default::default(), cx);
260 assert_eq!(editor.text(cx), "ābcde");
261 assert_eq!(editor.marked_text_ranges(cx), None);
262
263 // Start a new IME composition.
264 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
265 assert_eq!(
266 editor.marked_text_ranges(cx),
267 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
268 );
269
270 // Undoing during an IME composition cancels it.
271 editor.undo(&Default::default(), cx);
272 assert_eq!(editor.text(cx), "ābcde");
273 assert_eq!(editor.marked_text_ranges(cx), None);
274
275 // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
276 editor.replace_and_mark_text_in_range(Some(4..999), "è", None, cx);
277 assert_eq!(editor.text(cx), "ābcdè");
278 assert_eq!(
279 editor.marked_text_ranges(cx),
280 Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
281 );
282
283 // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
284 editor.replace_text_in_range(Some(4..999), "ę", cx);
285 assert_eq!(editor.text(cx), "ābcdę");
286 assert_eq!(editor.marked_text_ranges(cx), None);
287
288 // Start a new IME composition with multiple cursors.
289 editor.change_selections(None, cx, |s| {
290 s.select_ranges([
291 OffsetUtf16(1)..OffsetUtf16(1),
292 OffsetUtf16(3)..OffsetUtf16(3),
293 OffsetUtf16(5)..OffsetUtf16(5),
294 ])
295 });
296 editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, cx);
297 assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
298 assert_eq!(
299 editor.marked_text_ranges(cx),
300 Some(vec![
301 OffsetUtf16(0)..OffsetUtf16(3),
302 OffsetUtf16(4)..OffsetUtf16(7),
303 OffsetUtf16(8)..OffsetUtf16(11)
304 ])
305 );
306
307 // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
308 editor.replace_and_mark_text_in_range(Some(1..2), "1", None, cx);
309 assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
310 assert_eq!(
311 editor.marked_text_ranges(cx),
312 Some(vec![
313 OffsetUtf16(1)..OffsetUtf16(2),
314 OffsetUtf16(5)..OffsetUtf16(6),
315 OffsetUtf16(9)..OffsetUtf16(10)
316 ])
317 );
318
319 // Finalize IME composition with multiple cursors.
320 editor.replace_text_in_range(Some(9..10), "2", cx);
321 assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
322 assert_eq!(editor.marked_text_ranges(cx), None);
323
324 editor
325 });
326}
327
328#[gpui::test]
329fn test_selection_with_mouse(cx: &mut gpui::MutableAppContext) {
330 cx.set_global(Settings::test(cx));
331
332 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
333 let (_, editor) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
334 editor.update(cx, |view, cx| {
335 view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx);
336 });
337 assert_eq!(
338 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
339 [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)]
340 );
341
342 editor.update(cx, |view, cx| {
343 view.update_selection(DisplayPoint::new(3, 3), 0, Vector2F::zero(), cx);
344 });
345
346 assert_eq!(
347 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
348 [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
349 );
350
351 editor.update(cx, |view, cx| {
352 view.update_selection(DisplayPoint::new(1, 1), 0, Vector2F::zero(), cx);
353 });
354
355 assert_eq!(
356 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
357 [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
358 );
359
360 editor.update(cx, |view, cx| {
361 view.end_selection(cx);
362 view.update_selection(DisplayPoint::new(3, 3), 0, Vector2F::zero(), cx);
363 });
364
365 assert_eq!(
366 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
367 [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
368 );
369
370 editor.update(cx, |view, cx| {
371 view.begin_selection(DisplayPoint::new(3, 3), true, 1, cx);
372 view.update_selection(DisplayPoint::new(0, 0), 0, Vector2F::zero(), cx);
373 });
374
375 assert_eq!(
376 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
377 [
378 DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1),
379 DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)
380 ]
381 );
382
383 editor.update(cx, |view, cx| {
384 view.end_selection(cx);
385 });
386
387 assert_eq!(
388 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
389 [DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)]
390 );
391}
392
393#[gpui::test]
394fn test_canceling_pending_selection(cx: &mut gpui::MutableAppContext) {
395 cx.set_global(Settings::test(cx));
396 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
397 let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
398
399 view.update(cx, |view, cx| {
400 view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx);
401 assert_eq!(
402 view.selections.display_ranges(cx),
403 [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)]
404 );
405 });
406
407 view.update(cx, |view, cx| {
408 view.update_selection(DisplayPoint::new(3, 3), 0, Vector2F::zero(), cx);
409 assert_eq!(
410 view.selections.display_ranges(cx),
411 [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
412 );
413 });
414
415 view.update(cx, |view, cx| {
416 view.cancel(&Cancel, cx);
417 view.update_selection(DisplayPoint::new(1, 1), 0, Vector2F::zero(), cx);
418 assert_eq!(
419 view.selections.display_ranges(cx),
420 [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
421 );
422 });
423}
424
425#[gpui::test]
426fn test_clone(cx: &mut gpui::MutableAppContext) {
427 let (text, selection_ranges) = marked_text_ranges(
428 indoc! {"
429 one
430 two
431 threeˇ
432 four
433 fiveˇ
434 "},
435 true,
436 );
437 cx.set_global(Settings::test(cx));
438 let buffer = MultiBuffer::build_simple(&text, cx);
439
440 let (_, editor) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
441
442 editor.update(cx, |editor, cx| {
443 editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
444 editor.fold_ranges(
445 [
446 Point::new(1, 0)..Point::new(2, 0),
447 Point::new(3, 0)..Point::new(4, 0),
448 ],
449 cx,
450 );
451 });
452
453 let (_, cloned_editor) = editor.update(cx, |editor, cx| {
454 cx.add_window(Default::default(), |cx| editor.clone(cx))
455 });
456
457 let snapshot = editor.update(cx, |e, cx| e.snapshot(cx));
458 let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx));
459
460 assert_eq!(
461 cloned_editor.update(cx, |e, cx| e.display_text(cx)),
462 editor.update(cx, |e, cx| e.display_text(cx))
463 );
464 assert_eq!(
465 cloned_snapshot
466 .folds_in_range(0..text.len())
467 .collect::<Vec<_>>(),
468 snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
469 );
470 assert_set_eq!(
471 cloned_editor.read(cx).selections.ranges::<Point>(cx),
472 editor.read(cx).selections.ranges(cx)
473 );
474 assert_set_eq!(
475 cloned_editor.update(cx, |e, cx| e.selections.display_ranges(cx)),
476 editor.update(cx, |e, cx| e.selections.display_ranges(cx))
477 );
478}
479
480#[gpui::test]
481fn test_navigation_history(cx: &mut gpui::MutableAppContext) {
482 cx.set_global(Settings::test(cx));
483 cx.set_global(DragAndDrop::<Workspace>::default());
484 use workspace::item::Item;
485 let (_, pane) = cx.add_window(Default::default(), |cx| Pane::new(None, cx));
486 let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
487
488 cx.add_view(&pane, |cx| {
489 let mut editor = build_editor(buffer.clone(), cx);
490 let handle = cx.handle();
491 editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
492
493 fn pop_history(editor: &mut Editor, cx: &mut MutableAppContext) -> Option<NavigationEntry> {
494 editor.nav_history.as_mut().unwrap().pop_backward(cx)
495 }
496
497 // Move the cursor a small distance.
498 // Nothing is added to the navigation history.
499 editor.change_selections(None, cx, |s| {
500 s.select_display_ranges([DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)])
501 });
502 editor.change_selections(None, cx, |s| {
503 s.select_display_ranges([DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)])
504 });
505 assert!(pop_history(&mut editor, cx).is_none());
506
507 // Move the cursor a large distance.
508 // The history can jump back to the previous position.
509 editor.change_selections(None, cx, |s| {
510 s.select_display_ranges([DisplayPoint::new(13, 0)..DisplayPoint::new(13, 3)])
511 });
512 let nav_entry = pop_history(&mut editor, cx).unwrap();
513 editor.navigate(nav_entry.data.unwrap(), cx);
514 assert_eq!(nav_entry.item.id(), cx.view_id());
515 assert_eq!(
516 editor.selections.display_ranges(cx),
517 &[DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)]
518 );
519 assert!(pop_history(&mut editor, cx).is_none());
520
521 // Move the cursor a small distance via the mouse.
522 // Nothing is added to the navigation history.
523 editor.begin_selection(DisplayPoint::new(5, 0), false, 1, cx);
524 editor.end_selection(cx);
525 assert_eq!(
526 editor.selections.display_ranges(cx),
527 &[DisplayPoint::new(5, 0)..DisplayPoint::new(5, 0)]
528 );
529 assert!(pop_history(&mut editor, cx).is_none());
530
531 // Move the cursor a large distance via the mouse.
532 // The history can jump back to the previous position.
533 editor.begin_selection(DisplayPoint::new(15, 0), false, 1, cx);
534 editor.end_selection(cx);
535 assert_eq!(
536 editor.selections.display_ranges(cx),
537 &[DisplayPoint::new(15, 0)..DisplayPoint::new(15, 0)]
538 );
539 let nav_entry = pop_history(&mut editor, cx).unwrap();
540 editor.navigate(nav_entry.data.unwrap(), cx);
541 assert_eq!(nav_entry.item.id(), cx.view_id());
542 assert_eq!(
543 editor.selections.display_ranges(cx),
544 &[DisplayPoint::new(5, 0)..DisplayPoint::new(5, 0)]
545 );
546 assert!(pop_history(&mut editor, cx).is_none());
547
548 // Set scroll position to check later
549 editor.set_scroll_position(Vector2F::new(5.5, 5.5), cx);
550 let original_scroll_position = editor.scroll_manager.anchor();
551
552 // Jump to the end of the document and adjust scroll
553 editor.move_to_end(&MoveToEnd, cx);
554 editor.set_scroll_position(Vector2F::new(-2.5, -0.5), cx);
555 assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
556
557 let nav_entry = pop_history(&mut editor, cx).unwrap();
558 editor.navigate(nav_entry.data.unwrap(), cx);
559 assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
560
561 // Ensure we don't panic when navigation data contains invalid anchors *and* points.
562 let mut invalid_anchor = editor.scroll_manager.anchor().top_anchor;
563 invalid_anchor.text_anchor.buffer_id = Some(999);
564 let invalid_point = Point::new(9999, 0);
565 editor.navigate(
566 Box::new(NavigationData {
567 cursor_anchor: invalid_anchor,
568 cursor_position: invalid_point,
569 scroll_anchor: ScrollAnchor {
570 top_anchor: invalid_anchor,
571 offset: Default::default(),
572 },
573 scroll_top_row: invalid_point.row,
574 }),
575 cx,
576 );
577 assert_eq!(
578 editor.selections.display_ranges(cx),
579 &[editor.max_point(cx)..editor.max_point(cx)]
580 );
581 assert_eq!(
582 editor.scroll_position(cx),
583 vec2f(0., editor.max_point(cx).row() as f32)
584 );
585
586 editor
587 });
588}
589
590#[gpui::test]
591fn test_cancel(cx: &mut gpui::MutableAppContext) {
592 cx.set_global(Settings::test(cx));
593 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
594 let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
595
596 view.update(cx, |view, cx| {
597 view.begin_selection(DisplayPoint::new(3, 4), false, 1, cx);
598 view.update_selection(DisplayPoint::new(1, 1), 0, Vector2F::zero(), cx);
599 view.end_selection(cx);
600
601 view.begin_selection(DisplayPoint::new(0, 1), true, 1, cx);
602 view.update_selection(DisplayPoint::new(0, 3), 0, Vector2F::zero(), cx);
603 view.end_selection(cx);
604 assert_eq!(
605 view.selections.display_ranges(cx),
606 [
607 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
608 DisplayPoint::new(3, 4)..DisplayPoint::new(1, 1),
609 ]
610 );
611 });
612
613 view.update(cx, |view, cx| {
614 view.cancel(&Cancel, cx);
615 assert_eq!(
616 view.selections.display_ranges(cx),
617 [DisplayPoint::new(3, 4)..DisplayPoint::new(1, 1)]
618 );
619 });
620
621 view.update(cx, |view, cx| {
622 view.cancel(&Cancel, cx);
623 assert_eq!(
624 view.selections.display_ranges(cx),
625 [DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1)]
626 );
627 });
628}
629
630#[gpui::test]
631fn test_fold(cx: &mut gpui::MutableAppContext) {
632 cx.set_global(Settings::test(cx));
633 let buffer = MultiBuffer::build_simple(
634 &"
635 impl Foo {
636 // Hello!
637
638 fn a() {
639 1
640 }
641
642 fn b() {
643 2
644 }
645
646 fn c() {
647 3
648 }
649 }
650 "
651 .unindent(),
652 cx,
653 );
654 let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
655
656 view.update(cx, |view, cx| {
657 view.change_selections(None, cx, |s| {
658 s.select_display_ranges([DisplayPoint::new(8, 0)..DisplayPoint::new(12, 0)]);
659 });
660 view.fold(&Fold, cx);
661 assert_eq!(
662 view.display_text(cx),
663 "
664 impl Foo {
665 // Hello!
666
667 fn a() {
668 1
669 }
670
671 fn b() {…
672 }
673
674 fn c() {…
675 }
676 }
677 "
678 .unindent(),
679 );
680
681 view.fold(&Fold, cx);
682 assert_eq!(
683 view.display_text(cx),
684 "
685 impl Foo {…
686 }
687 "
688 .unindent(),
689 );
690
691 view.unfold_lines(&UnfoldLines, cx);
692 assert_eq!(
693 view.display_text(cx),
694 "
695 impl Foo {
696 // Hello!
697
698 fn a() {
699 1
700 }
701
702 fn b() {…
703 }
704
705 fn c() {…
706 }
707 }
708 "
709 .unindent(),
710 );
711
712 view.unfold_lines(&UnfoldLines, cx);
713 assert_eq!(view.display_text(cx), buffer.read(cx).read(cx).text());
714 });
715}
716
717#[gpui::test]
718fn test_move_cursor(cx: &mut gpui::MutableAppContext) {
719 cx.set_global(Settings::test(cx));
720 let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx);
721 let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
722
723 buffer.update(cx, |buffer, cx| {
724 buffer.edit(
725 vec![
726 (Point::new(1, 0)..Point::new(1, 0), "\t"),
727 (Point::new(1, 1)..Point::new(1, 1), "\t"),
728 ],
729 None,
730 cx,
731 );
732 });
733
734 view.update(cx, |view, cx| {
735 assert_eq!(
736 view.selections.display_ranges(cx),
737 &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
738 );
739
740 view.move_down(&MoveDown, cx);
741 assert_eq!(
742 view.selections.display_ranges(cx),
743 &[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)]
744 );
745
746 view.move_right(&MoveRight, cx);
747 assert_eq!(
748 view.selections.display_ranges(cx),
749 &[DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4)]
750 );
751
752 view.move_left(&MoveLeft, cx);
753 assert_eq!(
754 view.selections.display_ranges(cx),
755 &[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)]
756 );
757
758 view.move_up(&MoveUp, cx);
759 assert_eq!(
760 view.selections.display_ranges(cx),
761 &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
762 );
763
764 view.move_to_end(&MoveToEnd, cx);
765 assert_eq!(
766 view.selections.display_ranges(cx),
767 &[DisplayPoint::new(5, 6)..DisplayPoint::new(5, 6)]
768 );
769
770 view.move_to_beginning(&MoveToBeginning, cx);
771 assert_eq!(
772 view.selections.display_ranges(cx),
773 &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
774 );
775
776 view.change_selections(None, cx, |s| {
777 s.select_display_ranges([DisplayPoint::new(0, 1)..DisplayPoint::new(0, 2)]);
778 });
779 view.select_to_beginning(&SelectToBeginning, cx);
780 assert_eq!(
781 view.selections.display_ranges(cx),
782 &[DisplayPoint::new(0, 1)..DisplayPoint::new(0, 0)]
783 );
784
785 view.select_to_end(&SelectToEnd, cx);
786 assert_eq!(
787 view.selections.display_ranges(cx),
788 &[DisplayPoint::new(0, 1)..DisplayPoint::new(5, 6)]
789 );
790 });
791}
792
793#[gpui::test]
794fn test_move_cursor_multibyte(cx: &mut gpui::MutableAppContext) {
795 cx.set_global(Settings::test(cx));
796 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε\n", cx);
797 let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
798
799 assert_eq!('ⓐ'.len_utf8(), 3);
800 assert_eq!('α'.len_utf8(), 2);
801
802 view.update(cx, |view, cx| {
803 view.fold_ranges(
804 vec![
805 Point::new(0, 6)..Point::new(0, 12),
806 Point::new(1, 2)..Point::new(1, 4),
807 Point::new(2, 4)..Point::new(2, 8),
808 ],
809 cx,
810 );
811 assert_eq!(view.display_text(cx), "ⓐⓑ…ⓔ\nab…e\nαβ…ε\n");
812
813 view.move_right(&MoveRight, cx);
814 assert_eq!(
815 view.selections.display_ranges(cx),
816 &[empty_range(0, "ⓐ".len())]
817 );
818 view.move_right(&MoveRight, cx);
819 assert_eq!(
820 view.selections.display_ranges(cx),
821 &[empty_range(0, "ⓐⓑ".len())]
822 );
823 view.move_right(&MoveRight, cx);
824 assert_eq!(
825 view.selections.display_ranges(cx),
826 &[empty_range(0, "ⓐⓑ…".len())]
827 );
828
829 view.move_down(&MoveDown, cx);
830 assert_eq!(
831 view.selections.display_ranges(cx),
832 &[empty_range(1, "ab…".len())]
833 );
834 view.move_left(&MoveLeft, cx);
835 assert_eq!(
836 view.selections.display_ranges(cx),
837 &[empty_range(1, "ab".len())]
838 );
839 view.move_left(&MoveLeft, cx);
840 assert_eq!(
841 view.selections.display_ranges(cx),
842 &[empty_range(1, "a".len())]
843 );
844
845 view.move_down(&MoveDown, cx);
846 assert_eq!(
847 view.selections.display_ranges(cx),
848 &[empty_range(2, "α".len())]
849 );
850 view.move_right(&MoveRight, cx);
851 assert_eq!(
852 view.selections.display_ranges(cx),
853 &[empty_range(2, "αβ".len())]
854 );
855 view.move_right(&MoveRight, cx);
856 assert_eq!(
857 view.selections.display_ranges(cx),
858 &[empty_range(2, "αβ…".len())]
859 );
860 view.move_right(&MoveRight, cx);
861 assert_eq!(
862 view.selections.display_ranges(cx),
863 &[empty_range(2, "αβ…ε".len())]
864 );
865
866 view.move_up(&MoveUp, cx);
867 assert_eq!(
868 view.selections.display_ranges(cx),
869 &[empty_range(1, "ab…e".len())]
870 );
871 view.move_up(&MoveUp, cx);
872 assert_eq!(
873 view.selections.display_ranges(cx),
874 &[empty_range(0, "ⓐⓑ…ⓔ".len())]
875 );
876 view.move_left(&MoveLeft, cx);
877 assert_eq!(
878 view.selections.display_ranges(cx),
879 &[empty_range(0, "ⓐⓑ…".len())]
880 );
881 view.move_left(&MoveLeft, cx);
882 assert_eq!(
883 view.selections.display_ranges(cx),
884 &[empty_range(0, "ⓐⓑ".len())]
885 );
886 view.move_left(&MoveLeft, cx);
887 assert_eq!(
888 view.selections.display_ranges(cx),
889 &[empty_range(0, "ⓐ".len())]
890 );
891 });
892}
893
894#[gpui::test]
895fn test_move_cursor_different_line_lengths(cx: &mut gpui::MutableAppContext) {
896 cx.set_global(Settings::test(cx));
897 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
898 let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
899 view.update(cx, |view, cx| {
900 view.change_selections(None, cx, |s| {
901 s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
902 });
903 view.move_down(&MoveDown, cx);
904 assert_eq!(
905 view.selections.display_ranges(cx),
906 &[empty_range(1, "abcd".len())]
907 );
908
909 view.move_down(&MoveDown, cx);
910 assert_eq!(
911 view.selections.display_ranges(cx),
912 &[empty_range(2, "αβγ".len())]
913 );
914
915 view.move_down(&MoveDown, cx);
916 assert_eq!(
917 view.selections.display_ranges(cx),
918 &[empty_range(3, "abcd".len())]
919 );
920
921 view.move_down(&MoveDown, cx);
922 assert_eq!(
923 view.selections.display_ranges(cx),
924 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
925 );
926
927 view.move_up(&MoveUp, cx);
928 assert_eq!(
929 view.selections.display_ranges(cx),
930 &[empty_range(3, "abcd".len())]
931 );
932
933 view.move_up(&MoveUp, cx);
934 assert_eq!(
935 view.selections.display_ranges(cx),
936 &[empty_range(2, "αβγ".len())]
937 );
938 });
939}
940
941#[gpui::test]
942fn test_beginning_end_of_line(cx: &mut gpui::MutableAppContext) {
943 cx.set_global(Settings::test(cx));
944 let buffer = MultiBuffer::build_simple("abc\n def", cx);
945 let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
946 view.update(cx, |view, cx| {
947 view.change_selections(None, cx, |s| {
948 s.select_display_ranges([
949 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
950 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4),
951 ]);
952 });
953 });
954
955 view.update(cx, |view, cx| {
956 view.move_to_beginning_of_line(&MoveToBeginningOfLine, cx);
957 assert_eq!(
958 view.selections.display_ranges(cx),
959 &[
960 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
961 DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
962 ]
963 );
964 });
965
966 view.update(cx, |view, cx| {
967 view.move_to_beginning_of_line(&MoveToBeginningOfLine, cx);
968 assert_eq!(
969 view.selections.display_ranges(cx),
970 &[
971 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
972 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
973 ]
974 );
975 });
976
977 view.update(cx, |view, cx| {
978 view.move_to_beginning_of_line(&MoveToBeginningOfLine, cx);
979 assert_eq!(
980 view.selections.display_ranges(cx),
981 &[
982 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
983 DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
984 ]
985 );
986 });
987
988 view.update(cx, |view, cx| {
989 view.move_to_end_of_line(&MoveToEndOfLine, cx);
990 assert_eq!(
991 view.selections.display_ranges(cx),
992 &[
993 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
994 DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
995 ]
996 );
997 });
998
999 // Moving to the end of line again is a no-op.
1000 view.update(cx, |view, cx| {
1001 view.move_to_end_of_line(&MoveToEndOfLine, cx);
1002 assert_eq!(
1003 view.selections.display_ranges(cx),
1004 &[
1005 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
1006 DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
1007 ]
1008 );
1009 });
1010
1011 view.update(cx, |view, cx| {
1012 view.move_left(&MoveLeft, cx);
1013 view.select_to_beginning_of_line(
1014 &SelectToBeginningOfLine {
1015 stop_at_soft_wraps: true,
1016 },
1017 cx,
1018 );
1019 assert_eq!(
1020 view.selections.display_ranges(cx),
1021 &[
1022 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0),
1023 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 2),
1024 ]
1025 );
1026 });
1027
1028 view.update(cx, |view, cx| {
1029 view.select_to_beginning_of_line(
1030 &SelectToBeginningOfLine {
1031 stop_at_soft_wraps: true,
1032 },
1033 cx,
1034 );
1035 assert_eq!(
1036 view.selections.display_ranges(cx),
1037 &[
1038 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0),
1039 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 0),
1040 ]
1041 );
1042 });
1043
1044 view.update(cx, |view, cx| {
1045 view.select_to_beginning_of_line(
1046 &SelectToBeginningOfLine {
1047 stop_at_soft_wraps: true,
1048 },
1049 cx,
1050 );
1051 assert_eq!(
1052 view.selections.display_ranges(cx),
1053 &[
1054 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0),
1055 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 2),
1056 ]
1057 );
1058 });
1059
1060 view.update(cx, |view, cx| {
1061 view.select_to_end_of_line(
1062 &SelectToEndOfLine {
1063 stop_at_soft_wraps: true,
1064 },
1065 cx,
1066 );
1067 assert_eq!(
1068 view.selections.display_ranges(cx),
1069 &[
1070 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3),
1071 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 5),
1072 ]
1073 );
1074 });
1075
1076 view.update(cx, |view, cx| {
1077 view.delete_to_end_of_line(&DeleteToEndOfLine, cx);
1078 assert_eq!(view.display_text(cx), "ab\n de");
1079 assert_eq!(
1080 view.selections.display_ranges(cx),
1081 &[
1082 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
1083 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4),
1084 ]
1085 );
1086 });
1087
1088 view.update(cx, |view, cx| {
1089 view.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
1090 assert_eq!(view.display_text(cx), "\n");
1091 assert_eq!(
1092 view.selections.display_ranges(cx),
1093 &[
1094 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
1095 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
1096 ]
1097 );
1098 });
1099}
1100
1101#[gpui::test]
1102fn test_prev_next_word_boundary(cx: &mut gpui::MutableAppContext) {
1103 cx.set_global(Settings::test(cx));
1104 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1105 let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
1106 view.update(cx, |view, cx| {
1107 view.change_selections(None, cx, |s| {
1108 s.select_display_ranges([
1109 DisplayPoint::new(0, 11)..DisplayPoint::new(0, 11),
1110 DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4),
1111 ])
1112 });
1113
1114 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1115 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1116
1117 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1118 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", view, cx);
1119
1120 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1121 assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", view, cx);
1122
1123 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1124 assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1125
1126 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1127 assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", view, cx);
1128
1129 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1130 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", view, cx);
1131
1132 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1133 assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1134
1135 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1136 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1137
1138 view.move_right(&MoveRight, cx);
1139 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1140 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1141
1142 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1143 assert_selection_ranges("use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}", view, cx);
1144
1145 view.select_to_next_word_end(&SelectToNextWordEnd, cx);
1146 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1147 });
1148}
1149
1150#[gpui::test]
1151fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut gpui::MutableAppContext) {
1152 cx.set_global(Settings::test(cx));
1153 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1154 let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
1155
1156 view.update(cx, |view, cx| {
1157 view.set_wrap_width(Some(140.), cx);
1158 assert_eq!(
1159 view.display_text(cx),
1160 "use one::{\n two::three::\n four::five\n};"
1161 );
1162
1163 view.change_selections(None, cx, |s| {
1164 s.select_display_ranges([DisplayPoint::new(1, 7)..DisplayPoint::new(1, 7)]);
1165 });
1166
1167 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1168 assert_eq!(
1169 view.selections.display_ranges(cx),
1170 &[DisplayPoint::new(1, 9)..DisplayPoint::new(1, 9)]
1171 );
1172
1173 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1174 assert_eq!(
1175 view.selections.display_ranges(cx),
1176 &[DisplayPoint::new(1, 14)..DisplayPoint::new(1, 14)]
1177 );
1178
1179 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1180 assert_eq!(
1181 view.selections.display_ranges(cx),
1182 &[DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4)]
1183 );
1184
1185 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1186 assert_eq!(
1187 view.selections.display_ranges(cx),
1188 &[DisplayPoint::new(2, 8)..DisplayPoint::new(2, 8)]
1189 );
1190
1191 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1192 assert_eq!(
1193 view.selections.display_ranges(cx),
1194 &[DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4)]
1195 );
1196
1197 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1198 assert_eq!(
1199 view.selections.display_ranges(cx),
1200 &[DisplayPoint::new(1, 14)..DisplayPoint::new(1, 14)]
1201 );
1202 });
1203}
1204
1205#[gpui::test]
1206async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
1207 let mut cx = EditorTestContext::new(cx);
1208
1209 let line_height = cx.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache()));
1210 cx.simulate_window_resize(cx.window_id, vec2f(100., 4. * line_height));
1211
1212 cx.set_state(
1213 &r#"
1214 ˇone
1215 two
1216 threeˇ
1217 four
1218 five
1219 six
1220 seven
1221 eight
1222 nine
1223 ten
1224 "#
1225 .unindent(),
1226 );
1227
1228 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
1229 cx.assert_editor_state(
1230 &r#"
1231 one
1232 two
1233 three
1234 ˇfour
1235 five
1236 sixˇ
1237 seven
1238 eight
1239 nine
1240 ten
1241 "#
1242 .unindent(),
1243 );
1244
1245 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
1246 cx.assert_editor_state(
1247 &r#"
1248 one
1249 two
1250 three
1251 four
1252 five
1253 six
1254 ˇseven
1255 eight
1256 nineˇ
1257 ten
1258 "#
1259 .unindent(),
1260 );
1261
1262 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
1263 cx.assert_editor_state(
1264 &r#"
1265 one
1266 two
1267 three
1268 ˇfour
1269 five
1270 sixˇ
1271 seven
1272 eight
1273 nine
1274 ten
1275 "#
1276 .unindent(),
1277 );
1278
1279 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
1280 cx.assert_editor_state(
1281 &r#"
1282 ˇone
1283 two
1284 threeˇ
1285 four
1286 five
1287 six
1288 seven
1289 eight
1290 nine
1291 ten
1292 "#
1293 .unindent(),
1294 );
1295
1296 // Test select collapsing
1297 cx.update_editor(|editor, cx| {
1298 editor.move_page_down(&MovePageDown::default(), cx);
1299 editor.move_page_down(&MovePageDown::default(), cx);
1300 editor.move_page_down(&MovePageDown::default(), cx);
1301 });
1302 cx.assert_editor_state(
1303 &r#"
1304 one
1305 two
1306 three
1307 four
1308 five
1309 six
1310 seven
1311 eight
1312 nine
1313 ˇten
1314 ˇ"#
1315 .unindent(),
1316 );
1317}
1318
1319#[gpui::test]
1320async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
1321 let mut cx = EditorTestContext::new(cx);
1322 cx.set_state("one «two threeˇ» four");
1323 cx.update_editor(|editor, cx| {
1324 editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
1325 assert_eq!(editor.text(cx), " four");
1326 });
1327}
1328
1329#[gpui::test]
1330fn test_delete_to_word_boundary(cx: &mut gpui::MutableAppContext) {
1331 cx.set_global(Settings::test(cx));
1332 let buffer = MultiBuffer::build_simple("one two three four", cx);
1333 let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
1334
1335 view.update(cx, |view, cx| {
1336 view.change_selections(None, cx, |s| {
1337 s.select_display_ranges([
1338 // an empty selection - the preceding word fragment is deleted
1339 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
1340 // characters selected - they are deleted
1341 DisplayPoint::new(0, 9)..DisplayPoint::new(0, 12),
1342 ])
1343 });
1344 view.delete_to_previous_word_start(&DeleteToPreviousWordStart, cx);
1345 });
1346
1347 assert_eq!(buffer.read(cx).read(cx).text(), "e two te four");
1348
1349 view.update(cx, |view, cx| {
1350 view.change_selections(None, cx, |s| {
1351 s.select_display_ranges([
1352 // an empty selection - the following word fragment is deleted
1353 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
1354 // characters selected - they are deleted
1355 DisplayPoint::new(0, 9)..DisplayPoint::new(0, 10),
1356 ])
1357 });
1358 view.delete_to_next_word_end(&DeleteToNextWordEnd, cx);
1359 });
1360
1361 assert_eq!(buffer.read(cx).read(cx).text(), "e t te our");
1362}
1363
1364#[gpui::test]
1365fn test_newline(cx: &mut gpui::MutableAppContext) {
1366 cx.set_global(Settings::test(cx));
1367 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
1368 let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
1369
1370 view.update(cx, |view, cx| {
1371 view.change_selections(None, cx, |s| {
1372 s.select_display_ranges([
1373 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
1374 DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
1375 DisplayPoint::new(1, 6)..DisplayPoint::new(1, 6),
1376 ])
1377 });
1378
1379 view.newline(&Newline, cx);
1380 assert_eq!(view.text(cx), "aa\naa\n \n bb\n bb\n");
1381 });
1382}
1383
1384#[gpui::test]
1385fn test_newline_with_old_selections(cx: &mut gpui::MutableAppContext) {
1386 cx.set_global(Settings::test(cx));
1387 let buffer = MultiBuffer::build_simple(
1388 "
1389 a
1390 b(
1391 X
1392 )
1393 c(
1394 X
1395 )
1396 "
1397 .unindent()
1398 .as_str(),
1399 cx,
1400 );
1401
1402 let (_, editor) = cx.add_window(Default::default(), |cx| {
1403 let mut editor = build_editor(buffer.clone(), cx);
1404 editor.change_selections(None, cx, |s| {
1405 s.select_ranges([
1406 Point::new(2, 4)..Point::new(2, 5),
1407 Point::new(5, 4)..Point::new(5, 5),
1408 ])
1409 });
1410 editor
1411 });
1412
1413 // Edit the buffer directly, deleting ranges surrounding the editor's selections
1414 buffer.update(cx, |buffer, cx| {
1415 buffer.edit(
1416 [
1417 (Point::new(1, 2)..Point::new(3, 0), ""),
1418 (Point::new(4, 2)..Point::new(6, 0), ""),
1419 ],
1420 None,
1421 cx,
1422 );
1423 assert_eq!(
1424 buffer.read(cx).text(),
1425 "
1426 a
1427 b()
1428 c()
1429 "
1430 .unindent()
1431 );
1432 });
1433
1434 editor.update(cx, |editor, cx| {
1435 assert_eq!(
1436 editor.selections.ranges(cx),
1437 &[
1438 Point::new(1, 2)..Point::new(1, 2),
1439 Point::new(2, 2)..Point::new(2, 2),
1440 ],
1441 );
1442
1443 editor.newline(&Newline, cx);
1444 assert_eq!(
1445 editor.text(cx),
1446 "
1447 a
1448 b(
1449 )
1450 c(
1451 )
1452 "
1453 .unindent()
1454 );
1455
1456 // The selections are moved after the inserted newlines
1457 assert_eq!(
1458 editor.selections.ranges(cx),
1459 &[
1460 Point::new(2, 0)..Point::new(2, 0),
1461 Point::new(4, 0)..Point::new(4, 0),
1462 ],
1463 );
1464 });
1465}
1466
1467#[gpui::test]
1468async fn test_newline_below(cx: &mut gpui::TestAppContext) {
1469 let mut cx = EditorTestContext::new(cx);
1470 cx.update(|cx| {
1471 cx.update_global::<Settings, _, _>(|settings, _| {
1472 settings.editor_overrides.tab_size = Some(NonZeroU32::new(4).unwrap());
1473 });
1474 });
1475
1476 let language = Arc::new(
1477 Language::new(
1478 LanguageConfig::default(),
1479 Some(tree_sitter_rust::language()),
1480 )
1481 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
1482 .unwrap(),
1483 );
1484 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
1485
1486 cx.set_state(indoc! {"
1487 const a: ˇA = (
1488 (ˇ
1489 «const_functionˇ»(ˇ),
1490 so«mˇ»et«hˇ»ing_ˇelse,ˇ
1491 )ˇ
1492 ˇ);ˇ
1493 "});
1494 cx.update_editor(|e, cx| e.newline_below(&NewlineBelow, cx));
1495 cx.assert_editor_state(indoc! {"
1496 const a: A = (
1497 ˇ
1498 (
1499 ˇ
1500 const_function(),
1501 ˇ
1502 ˇ
1503 something_else,
1504 ˇ
1505 ˇ
1506 ˇ
1507 ˇ
1508 )
1509 ˇ
1510 );
1511 ˇ
1512 ˇ
1513 "});
1514}
1515
1516#[gpui::test]
1517fn test_insert_with_old_selections(cx: &mut gpui::MutableAppContext) {
1518 cx.set_global(Settings::test(cx));
1519 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
1520 let (_, editor) = cx.add_window(Default::default(), |cx| {
1521 let mut editor = build_editor(buffer.clone(), cx);
1522 editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
1523 editor
1524 });
1525
1526 // Edit the buffer directly, deleting ranges surrounding the editor's selections
1527 buffer.update(cx, |buffer, cx| {
1528 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
1529 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
1530 });
1531
1532 editor.update(cx, |editor, cx| {
1533 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
1534
1535 editor.insert("Z", cx);
1536 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
1537
1538 // The selections are moved after the inserted characters
1539 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
1540 });
1541}
1542
1543#[gpui::test]
1544async fn test_tab(cx: &mut gpui::TestAppContext) {
1545 let mut cx = EditorTestContext::new(cx);
1546 cx.update(|cx| {
1547 cx.update_global::<Settings, _, _>(|settings, _| {
1548 settings.editor_overrides.tab_size = Some(NonZeroU32::new(3).unwrap());
1549 });
1550 });
1551 cx.set_state(indoc! {"
1552 ˇabˇc
1553 ˇ🏀ˇ🏀ˇefg
1554 dˇ
1555 "});
1556 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1557 cx.assert_editor_state(indoc! {"
1558 ˇab ˇc
1559 ˇ🏀 ˇ🏀 ˇefg
1560 d ˇ
1561 "});
1562
1563 cx.set_state(indoc! {"
1564 a
1565 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
1566 "});
1567 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1568 cx.assert_editor_state(indoc! {"
1569 a
1570 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
1571 "});
1572}
1573
1574#[gpui::test]
1575async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) {
1576 let mut cx = EditorTestContext::new(cx);
1577 let language = Arc::new(
1578 Language::new(
1579 LanguageConfig::default(),
1580 Some(tree_sitter_rust::language()),
1581 )
1582 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
1583 .unwrap(),
1584 );
1585 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
1586
1587 // cursors that are already at the suggested indent level insert
1588 // a soft tab. cursors that are to the left of the suggested indent
1589 // auto-indent their line.
1590 cx.set_state(indoc! {"
1591 ˇ
1592 const a: B = (
1593 c(
1594 d(
1595 ˇ
1596 )
1597 ˇ
1598 ˇ )
1599 );
1600 "});
1601 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1602 cx.assert_editor_state(indoc! {"
1603 ˇ
1604 const a: B = (
1605 c(
1606 d(
1607 ˇ
1608 )
1609 ˇ
1610 ˇ)
1611 );
1612 "});
1613
1614 // handle auto-indent when there are multiple cursors on the same line
1615 cx.set_state(indoc! {"
1616 const a: B = (
1617 c(
1618 ˇ ˇ
1619 ˇ )
1620 );
1621 "});
1622 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1623 cx.assert_editor_state(indoc! {"
1624 const a: B = (
1625 c(
1626 ˇ
1627 ˇ)
1628 );
1629 "});
1630}
1631
1632#[gpui::test]
1633async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
1634 let mut cx = EditorTestContext::new(cx);
1635 let language = Arc::new(
1636 Language::new(
1637 LanguageConfig::default(),
1638 Some(tree_sitter_rust::language()),
1639 )
1640 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
1641 .unwrap(),
1642 );
1643 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
1644
1645 cx.update(|cx| {
1646 cx.update_global::<Settings, _, _>(|settings, _| {
1647 settings.editor_overrides.tab_size = Some(4.try_into().unwrap());
1648 });
1649 });
1650
1651 cx.set_state(indoc! {"
1652 fn a() {
1653 if b {
1654 \t ˇc
1655 }
1656 }
1657 "});
1658
1659 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1660 cx.assert_editor_state(indoc! {"
1661 fn a() {
1662 if b {
1663 ˇc
1664 }
1665 }
1666 "});
1667}
1668
1669#[gpui::test]
1670async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
1671 let mut cx = EditorTestContext::new(cx);
1672
1673 cx.set_state(indoc! {"
1674 «oneˇ» «twoˇ»
1675 three
1676 four
1677 "});
1678 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1679 cx.assert_editor_state(indoc! {"
1680 «oneˇ» «twoˇ»
1681 three
1682 four
1683 "});
1684
1685 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
1686 cx.assert_editor_state(indoc! {"
1687 «oneˇ» «twoˇ»
1688 three
1689 four
1690 "});
1691
1692 // select across line ending
1693 cx.set_state(indoc! {"
1694 one two
1695 t«hree
1696 ˇ» four
1697 "});
1698 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1699 cx.assert_editor_state(indoc! {"
1700 one two
1701 t«hree
1702 ˇ» four
1703 "});
1704
1705 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
1706 cx.assert_editor_state(indoc! {"
1707 one two
1708 t«hree
1709 ˇ» four
1710 "});
1711
1712 // Ensure that indenting/outdenting works when the cursor is at column 0.
1713 cx.set_state(indoc! {"
1714 one two
1715 ˇthree
1716 four
1717 "});
1718 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1719 cx.assert_editor_state(indoc! {"
1720 one two
1721 ˇthree
1722 four
1723 "});
1724
1725 cx.set_state(indoc! {"
1726 one two
1727 ˇ three
1728 four
1729 "});
1730 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
1731 cx.assert_editor_state(indoc! {"
1732 one two
1733 ˇthree
1734 four
1735 "});
1736}
1737
1738#[gpui::test]
1739async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
1740 let mut cx = EditorTestContext::new(cx);
1741 cx.update(|cx| {
1742 cx.update_global::<Settings, _, _>(|settings, _| {
1743 settings.editor_overrides.hard_tabs = Some(true);
1744 });
1745 });
1746
1747 // select two ranges on one line
1748 cx.set_state(indoc! {"
1749 «oneˇ» «twoˇ»
1750 three
1751 four
1752 "});
1753 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1754 cx.assert_editor_state(indoc! {"
1755 \t«oneˇ» «twoˇ»
1756 three
1757 four
1758 "});
1759 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1760 cx.assert_editor_state(indoc! {"
1761 \t\t«oneˇ» «twoˇ»
1762 three
1763 four
1764 "});
1765 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
1766 cx.assert_editor_state(indoc! {"
1767 \t«oneˇ» «twoˇ»
1768 three
1769 four
1770 "});
1771 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
1772 cx.assert_editor_state(indoc! {"
1773 «oneˇ» «twoˇ»
1774 three
1775 four
1776 "});
1777
1778 // select across a line ending
1779 cx.set_state(indoc! {"
1780 one two
1781 t«hree
1782 ˇ»four
1783 "});
1784 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1785 cx.assert_editor_state(indoc! {"
1786 one two
1787 \tt«hree
1788 ˇ»four
1789 "});
1790 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1791 cx.assert_editor_state(indoc! {"
1792 one two
1793 \t\tt«hree
1794 ˇ»four
1795 "});
1796 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
1797 cx.assert_editor_state(indoc! {"
1798 one two
1799 \tt«hree
1800 ˇ»four
1801 "});
1802 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
1803 cx.assert_editor_state(indoc! {"
1804 one two
1805 t«hree
1806 ˇ»four
1807 "});
1808
1809 // Ensure that indenting/outdenting works when the cursor is at column 0.
1810 cx.set_state(indoc! {"
1811 one two
1812 ˇthree
1813 four
1814 "});
1815 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
1816 cx.assert_editor_state(indoc! {"
1817 one two
1818 ˇthree
1819 four
1820 "});
1821 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1822 cx.assert_editor_state(indoc! {"
1823 one two
1824 \tˇthree
1825 four
1826 "});
1827 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
1828 cx.assert_editor_state(indoc! {"
1829 one two
1830 ˇthree
1831 four
1832 "});
1833}
1834
1835#[gpui::test]
1836fn test_indent_outdent_with_excerpts(cx: &mut gpui::MutableAppContext) {
1837 cx.set_global(
1838 Settings::test(cx)
1839 .with_language_defaults(
1840 "TOML",
1841 EditorSettings {
1842 tab_size: Some(2.try_into().unwrap()),
1843 ..Default::default()
1844 },
1845 )
1846 .with_language_defaults(
1847 "Rust",
1848 EditorSettings {
1849 tab_size: Some(4.try_into().unwrap()),
1850 ..Default::default()
1851 },
1852 ),
1853 );
1854 let toml_language = Arc::new(Language::new(
1855 LanguageConfig {
1856 name: "TOML".into(),
1857 ..Default::default()
1858 },
1859 None,
1860 ));
1861 let rust_language = Arc::new(Language::new(
1862 LanguageConfig {
1863 name: "Rust".into(),
1864 ..Default::default()
1865 },
1866 None,
1867 ));
1868
1869 let toml_buffer =
1870 cx.add_model(|cx| Buffer::new(0, "a = 1\nb = 2\n", cx).with_language(toml_language, cx));
1871 let rust_buffer = cx.add_model(|cx| {
1872 Buffer::new(0, "const c: usize = 3;\n", cx).with_language(rust_language, cx)
1873 });
1874 let multibuffer = cx.add_model(|cx| {
1875 let mut multibuffer = MultiBuffer::new(0);
1876 multibuffer.push_excerpts(
1877 toml_buffer.clone(),
1878 [ExcerptRange {
1879 context: Point::new(0, 0)..Point::new(2, 0),
1880 primary: None,
1881 }],
1882 cx,
1883 );
1884 multibuffer.push_excerpts(
1885 rust_buffer.clone(),
1886 [ExcerptRange {
1887 context: Point::new(0, 0)..Point::new(1, 0),
1888 primary: None,
1889 }],
1890 cx,
1891 );
1892 multibuffer
1893 });
1894
1895 cx.add_window(Default::default(), |cx| {
1896 let mut editor = build_editor(multibuffer, cx);
1897
1898 assert_eq!(
1899 editor.text(cx),
1900 indoc! {"
1901 a = 1
1902 b = 2
1903
1904 const c: usize = 3;
1905 "}
1906 );
1907
1908 select_ranges(
1909 &mut editor,
1910 indoc! {"
1911 «aˇ» = 1
1912 b = 2
1913
1914 «const c:ˇ» usize = 3;
1915 "},
1916 cx,
1917 );
1918
1919 editor.tab(&Tab, cx);
1920 assert_text_with_selections(
1921 &mut editor,
1922 indoc! {"
1923 «aˇ» = 1
1924 b = 2
1925
1926 «const c:ˇ» usize = 3;
1927 "},
1928 cx,
1929 );
1930 editor.tab_prev(&TabPrev, cx);
1931 assert_text_with_selections(
1932 &mut editor,
1933 indoc! {"
1934 «aˇ» = 1
1935 b = 2
1936
1937 «const c:ˇ» usize = 3;
1938 "},
1939 cx,
1940 );
1941
1942 editor
1943 });
1944}
1945
1946#[gpui::test]
1947async fn test_backspace(cx: &mut gpui::TestAppContext) {
1948 let mut cx = EditorTestContext::new(cx);
1949
1950 // Basic backspace
1951 cx.set_state(indoc! {"
1952 onˇe two three
1953 fou«rˇ» five six
1954 seven «ˇeight nine
1955 »ten
1956 "});
1957 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
1958 cx.assert_editor_state(indoc! {"
1959 oˇe two three
1960 fouˇ five six
1961 seven ˇten
1962 "});
1963
1964 // Test backspace inside and around indents
1965 cx.set_state(indoc! {"
1966 zero
1967 ˇone
1968 ˇtwo
1969 ˇ ˇ ˇ three
1970 ˇ ˇ four
1971 "});
1972 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
1973 cx.assert_editor_state(indoc! {"
1974 zero
1975 ˇone
1976 ˇtwo
1977 ˇ threeˇ four
1978 "});
1979
1980 // Test backspace with line_mode set to true
1981 cx.update_editor(|e, _| e.selections.line_mode = true);
1982 cx.set_state(indoc! {"
1983 The ˇquick ˇbrown
1984 fox jumps over
1985 the lazy dog
1986 ˇThe qu«ick bˇ»rown"});
1987 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
1988 cx.assert_editor_state(indoc! {"
1989 ˇfox jumps over
1990 the lazy dogˇ"});
1991}
1992
1993#[gpui::test]
1994async fn test_delete(cx: &mut gpui::TestAppContext) {
1995 let mut cx = EditorTestContext::new(cx);
1996
1997 cx.set_state(indoc! {"
1998 onˇe two three
1999 fou«rˇ» five six
2000 seven «ˇeight nine
2001 »ten
2002 "});
2003 cx.update_editor(|e, cx| e.delete(&Delete, cx));
2004 cx.assert_editor_state(indoc! {"
2005 onˇ two three
2006 fouˇ five six
2007 seven ˇten
2008 "});
2009
2010 // Test backspace with line_mode set to true
2011 cx.update_editor(|e, _| e.selections.line_mode = true);
2012 cx.set_state(indoc! {"
2013 The ˇquick ˇbrown
2014 fox «ˇjum»ps over
2015 the lazy dog
2016 ˇThe qu«ick bˇ»rown"});
2017 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2018 cx.assert_editor_state("ˇthe lazy dogˇ");
2019}
2020
2021#[gpui::test]
2022fn test_delete_line(cx: &mut gpui::MutableAppContext) {
2023 cx.set_global(Settings::test(cx));
2024 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2025 let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
2026 view.update(cx, |view, cx| {
2027 view.change_selections(None, cx, |s| {
2028 s.select_display_ranges([
2029 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
2030 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
2031 DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
2032 ])
2033 });
2034 view.delete_line(&DeleteLine, cx);
2035 assert_eq!(view.display_text(cx), "ghi");
2036 assert_eq!(
2037 view.selections.display_ranges(cx),
2038 vec![
2039 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
2040 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)
2041 ]
2042 );
2043 });
2044
2045 cx.set_global(Settings::test(cx));
2046 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2047 let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
2048 view.update(cx, |view, cx| {
2049 view.change_selections(None, cx, |s| {
2050 s.select_display_ranges([DisplayPoint::new(2, 0)..DisplayPoint::new(0, 1)])
2051 });
2052 view.delete_line(&DeleteLine, cx);
2053 assert_eq!(view.display_text(cx), "ghi\n");
2054 assert_eq!(
2055 view.selections.display_ranges(cx),
2056 vec![DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)]
2057 );
2058 });
2059}
2060
2061#[gpui::test]
2062fn test_duplicate_line(cx: &mut gpui::MutableAppContext) {
2063 cx.set_global(Settings::test(cx));
2064 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2065 let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
2066 view.update(cx, |view, cx| {
2067 view.change_selections(None, cx, |s| {
2068 s.select_display_ranges([
2069 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
2070 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
2071 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
2072 DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
2073 ])
2074 });
2075 view.duplicate_line(&DuplicateLine, cx);
2076 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
2077 assert_eq!(
2078 view.selections.display_ranges(cx),
2079 vec![
2080 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
2081 DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
2082 DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
2083 DisplayPoint::new(6, 0)..DisplayPoint::new(6, 0),
2084 ]
2085 );
2086 });
2087
2088 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2089 let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
2090 view.update(cx, |view, cx| {
2091 view.change_selections(None, cx, |s| {
2092 s.select_display_ranges([
2093 DisplayPoint::new(0, 1)..DisplayPoint::new(1, 1),
2094 DisplayPoint::new(1, 2)..DisplayPoint::new(2, 1),
2095 ])
2096 });
2097 view.duplicate_line(&DuplicateLine, cx);
2098 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
2099 assert_eq!(
2100 view.selections.display_ranges(cx),
2101 vec![
2102 DisplayPoint::new(3, 1)..DisplayPoint::new(4, 1),
2103 DisplayPoint::new(4, 2)..DisplayPoint::new(5, 1),
2104 ]
2105 );
2106 });
2107}
2108
2109#[gpui::test]
2110fn test_move_line_up_down(cx: &mut gpui::MutableAppContext) {
2111 cx.set_global(Settings::test(cx));
2112 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
2113 let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
2114 view.update(cx, |view, cx| {
2115 view.fold_ranges(
2116 vec![
2117 Point::new(0, 2)..Point::new(1, 2),
2118 Point::new(2, 3)..Point::new(4, 1),
2119 Point::new(7, 0)..Point::new(8, 4),
2120 ],
2121 cx,
2122 );
2123 view.change_selections(None, cx, |s| {
2124 s.select_display_ranges([
2125 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
2126 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
2127 DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
2128 DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2),
2129 ])
2130 });
2131 assert_eq!(
2132 view.display_text(cx),
2133 "aa…bbb\nccc…eeee\nfffff\nggggg\n…i\njjjjj"
2134 );
2135
2136 view.move_line_up(&MoveLineUp, cx);
2137 assert_eq!(
2138 view.display_text(cx),
2139 "aa…bbb\nccc…eeee\nggggg\n…i\njjjjj\nfffff"
2140 );
2141 assert_eq!(
2142 view.selections.display_ranges(cx),
2143 vec![
2144 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
2145 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
2146 DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3),
2147 DisplayPoint::new(4, 0)..DisplayPoint::new(4, 2)
2148 ]
2149 );
2150 });
2151
2152 view.update(cx, |view, cx| {
2153 view.move_line_down(&MoveLineDown, cx);
2154 assert_eq!(
2155 view.display_text(cx),
2156 "ccc…eeee\naa…bbb\nfffff\nggggg\n…i\njjjjj"
2157 );
2158 assert_eq!(
2159 view.selections.display_ranges(cx),
2160 vec![
2161 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
2162 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
2163 DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
2164 DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2)
2165 ]
2166 );
2167 });
2168
2169 view.update(cx, |view, cx| {
2170 view.move_line_down(&MoveLineDown, cx);
2171 assert_eq!(
2172 view.display_text(cx),
2173 "ccc…eeee\nfffff\naa…bbb\nggggg\n…i\njjjjj"
2174 );
2175 assert_eq!(
2176 view.selections.display_ranges(cx),
2177 vec![
2178 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
2179 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
2180 DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
2181 DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2)
2182 ]
2183 );
2184 });
2185
2186 view.update(cx, |view, cx| {
2187 view.move_line_up(&MoveLineUp, cx);
2188 assert_eq!(
2189 view.display_text(cx),
2190 "ccc…eeee\naa…bbb\nggggg\n…i\njjjjj\nfffff"
2191 );
2192 assert_eq!(
2193 view.selections.display_ranges(cx),
2194 vec![
2195 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
2196 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
2197 DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3),
2198 DisplayPoint::new(4, 0)..DisplayPoint::new(4, 2)
2199 ]
2200 );
2201 });
2202}
2203
2204#[gpui::test]
2205fn test_move_line_up_down_with_blocks(cx: &mut gpui::MutableAppContext) {
2206 cx.set_global(Settings::test(cx));
2207 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
2208 let snapshot = buffer.read(cx).snapshot(cx);
2209 let (_, editor) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
2210 editor.update(cx, |editor, cx| {
2211 editor.insert_blocks(
2212 [BlockProperties {
2213 style: BlockStyle::Fixed,
2214 position: snapshot.anchor_after(Point::new(2, 0)),
2215 disposition: BlockDisposition::Below,
2216 height: 1,
2217 render: Arc::new(|_| Empty::new().boxed()),
2218 }],
2219 cx,
2220 );
2221 editor.change_selections(None, cx, |s| {
2222 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
2223 });
2224 editor.move_line_down(&MoveLineDown, cx);
2225 });
2226}
2227
2228#[gpui::test]
2229fn test_transpose(cx: &mut gpui::MutableAppContext) {
2230 cx.set_global(Settings::test(cx));
2231
2232 _ = cx
2233 .add_window(Default::default(), |cx| {
2234 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
2235
2236 editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
2237 editor.transpose(&Default::default(), cx);
2238 assert_eq!(editor.text(cx), "bac");
2239 assert_eq!(editor.selections.ranges(cx), [2..2]);
2240
2241 editor.transpose(&Default::default(), cx);
2242 assert_eq!(editor.text(cx), "bca");
2243 assert_eq!(editor.selections.ranges(cx), [3..3]);
2244
2245 editor.transpose(&Default::default(), cx);
2246 assert_eq!(editor.text(cx), "bac");
2247 assert_eq!(editor.selections.ranges(cx), [3..3]);
2248
2249 editor
2250 })
2251 .1;
2252
2253 _ = cx
2254 .add_window(Default::default(), |cx| {
2255 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
2256
2257 editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
2258 editor.transpose(&Default::default(), cx);
2259 assert_eq!(editor.text(cx), "acb\nde");
2260 assert_eq!(editor.selections.ranges(cx), [3..3]);
2261
2262 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
2263 editor.transpose(&Default::default(), cx);
2264 assert_eq!(editor.text(cx), "acbd\ne");
2265 assert_eq!(editor.selections.ranges(cx), [5..5]);
2266
2267 editor.transpose(&Default::default(), cx);
2268 assert_eq!(editor.text(cx), "acbde\n");
2269 assert_eq!(editor.selections.ranges(cx), [6..6]);
2270
2271 editor.transpose(&Default::default(), cx);
2272 assert_eq!(editor.text(cx), "acbd\ne");
2273 assert_eq!(editor.selections.ranges(cx), [6..6]);
2274
2275 editor
2276 })
2277 .1;
2278
2279 _ = cx
2280 .add_window(Default::default(), |cx| {
2281 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
2282
2283 editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
2284 editor.transpose(&Default::default(), cx);
2285 assert_eq!(editor.text(cx), "bacd\ne");
2286 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
2287
2288 editor.transpose(&Default::default(), cx);
2289 assert_eq!(editor.text(cx), "bcade\n");
2290 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
2291
2292 editor.transpose(&Default::default(), cx);
2293 assert_eq!(editor.text(cx), "bcda\ne");
2294 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
2295
2296 editor.transpose(&Default::default(), cx);
2297 assert_eq!(editor.text(cx), "bcade\n");
2298 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
2299
2300 editor.transpose(&Default::default(), cx);
2301 assert_eq!(editor.text(cx), "bcaed\n");
2302 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
2303
2304 editor
2305 })
2306 .1;
2307
2308 _ = cx
2309 .add_window(Default::default(), |cx| {
2310 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
2311
2312 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
2313 editor.transpose(&Default::default(), cx);
2314 assert_eq!(editor.text(cx), "🏀🍐✋");
2315 assert_eq!(editor.selections.ranges(cx), [8..8]);
2316
2317 editor.transpose(&Default::default(), cx);
2318 assert_eq!(editor.text(cx), "🏀✋🍐");
2319 assert_eq!(editor.selections.ranges(cx), [11..11]);
2320
2321 editor.transpose(&Default::default(), cx);
2322 assert_eq!(editor.text(cx), "🏀🍐✋");
2323 assert_eq!(editor.selections.ranges(cx), [11..11]);
2324
2325 editor
2326 })
2327 .1;
2328}
2329
2330#[gpui::test]
2331async fn test_clipboard(cx: &mut gpui::TestAppContext) {
2332 let mut cx = EditorTestContext::new(cx);
2333
2334 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
2335 cx.update_editor(|e, cx| e.cut(&Cut, cx));
2336 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
2337
2338 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
2339 cx.set_state("two ˇfour ˇsix ˇ");
2340 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2341 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
2342
2343 // Paste again but with only two cursors. Since the number of cursors doesn't
2344 // match the number of slices in the clipboard, the entire clipboard text
2345 // is pasted at each cursor.
2346 cx.set_state("ˇtwo one✅ four three six five ˇ");
2347 cx.update_editor(|e, cx| {
2348 e.handle_input("( ", cx);
2349 e.paste(&Paste, cx);
2350 e.handle_input(") ", cx);
2351 });
2352 cx.assert_editor_state(indoc! {"
2353 ( one✅
2354 three
2355 five ) ˇtwo one✅ four three six five ( one✅
2356 three
2357 five ) ˇ"});
2358
2359 // Cut with three selections, one of which is full-line.
2360 cx.set_state(indoc! {"
2361 1«2ˇ»3
2362 4ˇ567
2363 «8ˇ»9"});
2364 cx.update_editor(|e, cx| e.cut(&Cut, cx));
2365 cx.assert_editor_state(indoc! {"
2366 1ˇ3
2367 ˇ9"});
2368
2369 // Paste with three selections, noticing how the copied selection that was full-line
2370 // gets inserted before the second cursor.
2371 cx.set_state(indoc! {"
2372 1ˇ3
2373 9ˇ
2374 «oˇ»ne"});
2375 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2376 cx.assert_editor_state(indoc! {"
2377 12ˇ3
2378 4567
2379 9ˇ
2380 8ˇne"});
2381
2382 // Copy with a single cursor only, which writes the whole line into the clipboard.
2383 cx.set_state(indoc! {"
2384 The quick brown
2385 fox juˇmps over
2386 the lazy dog"});
2387 cx.update_editor(|e, cx| e.copy(&Copy, cx));
2388 cx.cx.assert_clipboard_content(Some("fox jumps over\n"));
2389
2390 // Paste with three selections, noticing how the copied full-line selection is inserted
2391 // before the empty selections but replaces the selection that is non-empty.
2392 cx.set_state(indoc! {"
2393 Tˇhe quick brown
2394 «foˇ»x jumps over
2395 tˇhe lazy dog"});
2396 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2397 cx.assert_editor_state(indoc! {"
2398 fox jumps over
2399 Tˇhe quick brown
2400 fox jumps over
2401 ˇx jumps over
2402 fox jumps over
2403 tˇhe lazy dog"});
2404}
2405
2406#[gpui::test]
2407async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
2408 let mut cx = EditorTestContext::new(cx);
2409 let language = Arc::new(Language::new(
2410 LanguageConfig::default(),
2411 Some(tree_sitter_rust::language()),
2412 ));
2413 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2414
2415 // Cut an indented block, without the leading whitespace.
2416 cx.set_state(indoc! {"
2417 const a: B = (
2418 c(),
2419 «d(
2420 e,
2421 f
2422 )ˇ»
2423 );
2424 "});
2425 cx.update_editor(|e, cx| e.cut(&Cut, cx));
2426 cx.assert_editor_state(indoc! {"
2427 const a: B = (
2428 c(),
2429 ˇ
2430 );
2431 "});
2432
2433 // Paste it at the same position.
2434 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2435 cx.assert_editor_state(indoc! {"
2436 const a: B = (
2437 c(),
2438 d(
2439 e,
2440 f
2441 )ˇ
2442 );
2443 "});
2444
2445 // Paste it at a line with a lower indent level.
2446 cx.set_state(indoc! {"
2447 ˇ
2448 const a: B = (
2449 c(),
2450 );
2451 "});
2452 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2453 cx.assert_editor_state(indoc! {"
2454 d(
2455 e,
2456 f
2457 )ˇ
2458 const a: B = (
2459 c(),
2460 );
2461 "});
2462
2463 // Cut an indented block, with the leading whitespace.
2464 cx.set_state(indoc! {"
2465 const a: B = (
2466 c(),
2467 « d(
2468 e,
2469 f
2470 )
2471 ˇ»);
2472 "});
2473 cx.update_editor(|e, cx| e.cut(&Cut, cx));
2474 cx.assert_editor_state(indoc! {"
2475 const a: B = (
2476 c(),
2477 ˇ);
2478 "});
2479
2480 // Paste it at the same position.
2481 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2482 cx.assert_editor_state(indoc! {"
2483 const a: B = (
2484 c(),
2485 d(
2486 e,
2487 f
2488 )
2489 ˇ);
2490 "});
2491
2492 // Paste it at a line with a higher indent level.
2493 cx.set_state(indoc! {"
2494 const a: B = (
2495 c(),
2496 d(
2497 e,
2498 fˇ
2499 )
2500 );
2501 "});
2502 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2503 cx.assert_editor_state(indoc! {"
2504 const a: B = (
2505 c(),
2506 d(
2507 e,
2508 f d(
2509 e,
2510 f
2511 )
2512 ˇ
2513 )
2514 );
2515 "});
2516}
2517
2518#[gpui::test]
2519fn test_select_all(cx: &mut gpui::MutableAppContext) {
2520 cx.set_global(Settings::test(cx));
2521 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
2522 let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
2523 view.update(cx, |view, cx| {
2524 view.select_all(&SelectAll, cx);
2525 assert_eq!(
2526 view.selections.display_ranges(cx),
2527 &[DisplayPoint::new(0, 0)..DisplayPoint::new(2, 3)]
2528 );
2529 });
2530}
2531
2532#[gpui::test]
2533fn test_select_line(cx: &mut gpui::MutableAppContext) {
2534 cx.set_global(Settings::test(cx));
2535 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
2536 let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
2537 view.update(cx, |view, cx| {
2538 view.change_selections(None, cx, |s| {
2539 s.select_display_ranges([
2540 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
2541 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
2542 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
2543 DisplayPoint::new(4, 2)..DisplayPoint::new(4, 2),
2544 ])
2545 });
2546 view.select_line(&SelectLine, cx);
2547 assert_eq!(
2548 view.selections.display_ranges(cx),
2549 vec![
2550 DisplayPoint::new(0, 0)..DisplayPoint::new(2, 0),
2551 DisplayPoint::new(4, 0)..DisplayPoint::new(5, 0),
2552 ]
2553 );
2554 });
2555
2556 view.update(cx, |view, cx| {
2557 view.select_line(&SelectLine, cx);
2558 assert_eq!(
2559 view.selections.display_ranges(cx),
2560 vec![
2561 DisplayPoint::new(0, 0)..DisplayPoint::new(3, 0),
2562 DisplayPoint::new(4, 0)..DisplayPoint::new(5, 5),
2563 ]
2564 );
2565 });
2566
2567 view.update(cx, |view, cx| {
2568 view.select_line(&SelectLine, cx);
2569 assert_eq!(
2570 view.selections.display_ranges(cx),
2571 vec![DisplayPoint::new(0, 0)..DisplayPoint::new(5, 5)]
2572 );
2573 });
2574}
2575
2576#[gpui::test]
2577fn test_split_selection_into_lines(cx: &mut gpui::MutableAppContext) {
2578 cx.set_global(Settings::test(cx));
2579 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
2580 let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
2581 view.update(cx, |view, cx| {
2582 view.fold_ranges(
2583 vec![
2584 Point::new(0, 2)..Point::new(1, 2),
2585 Point::new(2, 3)..Point::new(4, 1),
2586 Point::new(7, 0)..Point::new(8, 4),
2587 ],
2588 cx,
2589 );
2590 view.change_selections(None, cx, |s| {
2591 s.select_display_ranges([
2592 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
2593 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
2594 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
2595 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
2596 ])
2597 });
2598 assert_eq!(view.display_text(cx), "aa…bbb\nccc…eeee\nfffff\nggggg\n…i");
2599 });
2600
2601 view.update(cx, |view, cx| {
2602 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
2603 assert_eq!(
2604 view.display_text(cx),
2605 "aaaaa\nbbbbb\nccc…eeee\nfffff\nggggg\n…i"
2606 );
2607 assert_eq!(
2608 view.selections.display_ranges(cx),
2609 [
2610 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
2611 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
2612 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 0),
2613 DisplayPoint::new(5, 4)..DisplayPoint::new(5, 4)
2614 ]
2615 );
2616 });
2617
2618 view.update(cx, |view, cx| {
2619 view.change_selections(None, cx, |s| {
2620 s.select_display_ranges([DisplayPoint::new(5, 0)..DisplayPoint::new(0, 1)])
2621 });
2622 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
2623 assert_eq!(
2624 view.display_text(cx),
2625 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
2626 );
2627 assert_eq!(
2628 view.selections.display_ranges(cx),
2629 [
2630 DisplayPoint::new(0, 5)..DisplayPoint::new(0, 5),
2631 DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
2632 DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
2633 DisplayPoint::new(3, 5)..DisplayPoint::new(3, 5),
2634 DisplayPoint::new(4, 5)..DisplayPoint::new(4, 5),
2635 DisplayPoint::new(5, 5)..DisplayPoint::new(5, 5),
2636 DisplayPoint::new(6, 5)..DisplayPoint::new(6, 5),
2637 DisplayPoint::new(7, 0)..DisplayPoint::new(7, 0)
2638 ]
2639 );
2640 });
2641}
2642
2643#[gpui::test]
2644fn test_add_selection_above_below(cx: &mut gpui::MutableAppContext) {
2645 cx.set_global(Settings::test(cx));
2646 let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
2647 let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
2648
2649 view.update(cx, |view, cx| {
2650 view.change_selections(None, cx, |s| {
2651 s.select_display_ranges([DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)])
2652 });
2653 });
2654 view.update(cx, |view, cx| {
2655 view.add_selection_above(&AddSelectionAbove, cx);
2656 assert_eq!(
2657 view.selections.display_ranges(cx),
2658 vec![
2659 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
2660 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)
2661 ]
2662 );
2663 });
2664
2665 view.update(cx, |view, cx| {
2666 view.add_selection_above(&AddSelectionAbove, cx);
2667 assert_eq!(
2668 view.selections.display_ranges(cx),
2669 vec![
2670 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
2671 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)
2672 ]
2673 );
2674 });
2675
2676 view.update(cx, |view, cx| {
2677 view.add_selection_below(&AddSelectionBelow, cx);
2678 assert_eq!(
2679 view.selections.display_ranges(cx),
2680 vec![DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)]
2681 );
2682
2683 view.undo_selection(&UndoSelection, cx);
2684 assert_eq!(
2685 view.selections.display_ranges(cx),
2686 vec![
2687 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
2688 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)
2689 ]
2690 );
2691
2692 view.redo_selection(&RedoSelection, cx);
2693 assert_eq!(
2694 view.selections.display_ranges(cx),
2695 vec![DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)]
2696 );
2697 });
2698
2699 view.update(cx, |view, cx| {
2700 view.add_selection_below(&AddSelectionBelow, cx);
2701 assert_eq!(
2702 view.selections.display_ranges(cx),
2703 vec![
2704 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3),
2705 DisplayPoint::new(4, 3)..DisplayPoint::new(4, 3)
2706 ]
2707 );
2708 });
2709
2710 view.update(cx, |view, cx| {
2711 view.add_selection_below(&AddSelectionBelow, cx);
2712 assert_eq!(
2713 view.selections.display_ranges(cx),
2714 vec![
2715 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3),
2716 DisplayPoint::new(4, 3)..DisplayPoint::new(4, 3)
2717 ]
2718 );
2719 });
2720
2721 view.update(cx, |view, cx| {
2722 view.change_selections(None, cx, |s| {
2723 s.select_display_ranges([DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)])
2724 });
2725 });
2726 view.update(cx, |view, cx| {
2727 view.add_selection_below(&AddSelectionBelow, cx);
2728 assert_eq!(
2729 view.selections.display_ranges(cx),
2730 vec![
2731 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3),
2732 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 3)
2733 ]
2734 );
2735 });
2736
2737 view.update(cx, |view, cx| {
2738 view.add_selection_below(&AddSelectionBelow, cx);
2739 assert_eq!(
2740 view.selections.display_ranges(cx),
2741 vec![
2742 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3),
2743 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 3)
2744 ]
2745 );
2746 });
2747
2748 view.update(cx, |view, cx| {
2749 view.add_selection_above(&AddSelectionAbove, cx);
2750 assert_eq!(
2751 view.selections.display_ranges(cx),
2752 vec![DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)]
2753 );
2754 });
2755
2756 view.update(cx, |view, cx| {
2757 view.add_selection_above(&AddSelectionAbove, cx);
2758 assert_eq!(
2759 view.selections.display_ranges(cx),
2760 vec![DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)]
2761 );
2762 });
2763
2764 view.update(cx, |view, cx| {
2765 view.change_selections(None, cx, |s| {
2766 s.select_display_ranges([DisplayPoint::new(0, 1)..DisplayPoint::new(1, 4)])
2767 });
2768 view.add_selection_below(&AddSelectionBelow, cx);
2769 assert_eq!(
2770 view.selections.display_ranges(cx),
2771 vec![
2772 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
2773 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4),
2774 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2),
2775 ]
2776 );
2777 });
2778
2779 view.update(cx, |view, cx| {
2780 view.add_selection_below(&AddSelectionBelow, cx);
2781 assert_eq!(
2782 view.selections.display_ranges(cx),
2783 vec![
2784 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
2785 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4),
2786 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2),
2787 DisplayPoint::new(4, 1)..DisplayPoint::new(4, 4),
2788 ]
2789 );
2790 });
2791
2792 view.update(cx, |view, cx| {
2793 view.add_selection_above(&AddSelectionAbove, cx);
2794 assert_eq!(
2795 view.selections.display_ranges(cx),
2796 vec![
2797 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
2798 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4),
2799 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2),
2800 ]
2801 );
2802 });
2803
2804 view.update(cx, |view, cx| {
2805 view.change_selections(None, cx, |s| {
2806 s.select_display_ranges([DisplayPoint::new(4, 3)..DisplayPoint::new(1, 1)])
2807 });
2808 });
2809 view.update(cx, |view, cx| {
2810 view.add_selection_above(&AddSelectionAbove, cx);
2811 assert_eq!(
2812 view.selections.display_ranges(cx),
2813 vec![
2814 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 1),
2815 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 1),
2816 DisplayPoint::new(3, 2)..DisplayPoint::new(3, 1),
2817 DisplayPoint::new(4, 3)..DisplayPoint::new(4, 1),
2818 ]
2819 );
2820 });
2821
2822 view.update(cx, |view, cx| {
2823 view.add_selection_below(&AddSelectionBelow, cx);
2824 assert_eq!(
2825 view.selections.display_ranges(cx),
2826 vec![
2827 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 1),
2828 DisplayPoint::new(3, 2)..DisplayPoint::new(3, 1),
2829 DisplayPoint::new(4, 3)..DisplayPoint::new(4, 1),
2830 ]
2831 );
2832 });
2833}
2834
2835#[gpui::test]
2836async fn test_select_next(cx: &mut gpui::TestAppContext) {
2837 let mut cx = EditorTestContext::new(cx);
2838 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
2839
2840 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx));
2841 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
2842
2843 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx));
2844 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
2845
2846 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
2847 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
2848
2849 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
2850 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
2851
2852 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx));
2853 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
2854
2855 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx));
2856 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
2857}
2858
2859#[gpui::test]
2860async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
2861 cx.update(|cx| cx.set_global(Settings::test(cx)));
2862 let language = Arc::new(Language::new(
2863 LanguageConfig::default(),
2864 Some(tree_sitter_rust::language()),
2865 ));
2866
2867 let text = r#"
2868 use mod1::mod2::{mod3, mod4};
2869
2870 fn fn_1(param1: bool, param2: &str) {
2871 let var1 = "text";
2872 }
2873 "#
2874 .unindent();
2875
2876 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
2877 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
2878 let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
2879 view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
2880 .await;
2881
2882 view.update(cx, |view, cx| {
2883 view.change_selections(None, cx, |s| {
2884 s.select_display_ranges([
2885 DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
2886 DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
2887 DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
2888 ]);
2889 });
2890 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
2891 });
2892 assert_eq!(
2893 view.update(cx, |view, cx| { view.selections.display_ranges(cx) }),
2894 &[
2895 DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27),
2896 DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
2897 DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21),
2898 ]
2899 );
2900
2901 view.update(cx, |view, cx| {
2902 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
2903 });
2904 assert_eq!(
2905 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
2906 &[
2907 DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
2908 DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0),
2909 ]
2910 );
2911
2912 view.update(cx, |view, cx| {
2913 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
2914 });
2915 assert_eq!(
2916 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
2917 &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)]
2918 );
2919
2920 // Trying to expand the selected syntax node one more time has no effect.
2921 view.update(cx, |view, cx| {
2922 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
2923 });
2924 assert_eq!(
2925 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
2926 &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)]
2927 );
2928
2929 view.update(cx, |view, cx| {
2930 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
2931 });
2932 assert_eq!(
2933 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
2934 &[
2935 DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
2936 DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0),
2937 ]
2938 );
2939
2940 view.update(cx, |view, cx| {
2941 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
2942 });
2943 assert_eq!(
2944 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
2945 &[
2946 DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27),
2947 DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
2948 DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21),
2949 ]
2950 );
2951
2952 view.update(cx, |view, cx| {
2953 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
2954 });
2955 assert_eq!(
2956 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
2957 &[
2958 DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
2959 DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
2960 DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
2961 ]
2962 );
2963
2964 // Trying to shrink the selected syntax node one more time has no effect.
2965 view.update(cx, |view, cx| {
2966 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
2967 });
2968 assert_eq!(
2969 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
2970 &[
2971 DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
2972 DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
2973 DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
2974 ]
2975 );
2976
2977 // Ensure that we keep expanding the selection if the larger selection starts or ends within
2978 // a fold.
2979 view.update(cx, |view, cx| {
2980 view.fold_ranges(
2981 vec![
2982 Point::new(0, 21)..Point::new(0, 24),
2983 Point::new(3, 20)..Point::new(3, 22),
2984 ],
2985 cx,
2986 );
2987 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
2988 });
2989 assert_eq!(
2990 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
2991 &[
2992 DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
2993 DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
2994 DisplayPoint::new(3, 4)..DisplayPoint::new(3, 23),
2995 ]
2996 );
2997}
2998
2999#[gpui::test]
3000async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
3001 cx.update(|cx| cx.set_global(Settings::test(cx)));
3002 let language = Arc::new(
3003 Language::new(
3004 LanguageConfig {
3005 brackets: vec![
3006 BracketPair {
3007 start: "{".to_string(),
3008 end: "}".to_string(),
3009 close: false,
3010 newline: true,
3011 },
3012 BracketPair {
3013 start: "(".to_string(),
3014 end: ")".to_string(),
3015 close: false,
3016 newline: true,
3017 },
3018 ],
3019 ..Default::default()
3020 },
3021 Some(tree_sitter_rust::language()),
3022 )
3023 .with_indents_query(
3024 r#"
3025 (_ "(" ")" @end) @indent
3026 (_ "{" "}" @end) @indent
3027 "#,
3028 )
3029 .unwrap(),
3030 );
3031
3032 let text = "fn a() {}";
3033
3034 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
3035 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
3036 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
3037 editor
3038 .condition(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
3039 .await;
3040
3041 editor.update(cx, |editor, cx| {
3042 editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
3043 editor.newline(&Newline, cx);
3044 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
3045 assert_eq!(
3046 editor.selections.ranges(cx),
3047 &[
3048 Point::new(1, 4)..Point::new(1, 4),
3049 Point::new(3, 4)..Point::new(3, 4),
3050 Point::new(5, 0)..Point::new(5, 0)
3051 ]
3052 );
3053 });
3054}
3055
3056#[gpui::test]
3057async fn test_autoclose_pairs(cx: &mut gpui::TestAppContext) {
3058 let mut cx = EditorTestContext::new(cx);
3059
3060 let language = Arc::new(Language::new(
3061 LanguageConfig {
3062 brackets: vec![
3063 BracketPair {
3064 start: "{".to_string(),
3065 end: "}".to_string(),
3066 close: true,
3067 newline: true,
3068 },
3069 BracketPair {
3070 start: "(".to_string(),
3071 end: ")".to_string(),
3072 close: true,
3073 newline: true,
3074 },
3075 BracketPair {
3076 start: "/*".to_string(),
3077 end: " */".to_string(),
3078 close: true,
3079 newline: true,
3080 },
3081 BracketPair {
3082 start: "[".to_string(),
3083 end: "]".to_string(),
3084 close: false,
3085 newline: true,
3086 },
3087 BracketPair {
3088 start: "\"".to_string(),
3089 end: "\"".to_string(),
3090 close: true,
3091 newline: false,
3092 },
3093 ],
3094 autoclose_before: "})]".to_string(),
3095 ..Default::default()
3096 },
3097 Some(tree_sitter_rust::language()),
3098 ));
3099
3100 let registry = Arc::new(LanguageRegistry::test());
3101 registry.add(language.clone());
3102 cx.update_buffer(|buffer, cx| {
3103 buffer.set_language_registry(registry);
3104 buffer.set_language(Some(language), cx);
3105 });
3106
3107 cx.set_state(
3108 &r#"
3109 🏀ˇ
3110 εˇ
3111 ❤️ˇ
3112 "#
3113 .unindent(),
3114 );
3115
3116 // autoclose multiple nested brackets at multiple cursors
3117 cx.update_editor(|view, cx| {
3118 view.handle_input("{", cx);
3119 view.handle_input("{", cx);
3120 view.handle_input("{", cx);
3121 });
3122 cx.assert_editor_state(
3123 &"
3124 🏀{{{ˇ}}}
3125 ε{{{ˇ}}}
3126 ❤️{{{ˇ}}}
3127 "
3128 .unindent(),
3129 );
3130
3131 // insert a different closing bracket
3132 cx.update_editor(|view, cx| {
3133 view.handle_input(")", cx);
3134 });
3135 cx.assert_editor_state(
3136 &"
3137 🏀{{{)ˇ}}}
3138 ε{{{)ˇ}}}
3139 ❤️{{{)ˇ}}}
3140 "
3141 .unindent(),
3142 );
3143
3144 // skip over the auto-closed brackets when typing a closing bracket
3145 cx.update_editor(|view, cx| {
3146 view.move_right(&MoveRight, cx);
3147 view.handle_input("}", cx);
3148 view.handle_input("}", cx);
3149 view.handle_input("}", cx);
3150 });
3151 cx.assert_editor_state(
3152 &"
3153 🏀{{{)}}}}ˇ
3154 ε{{{)}}}}ˇ
3155 ❤️{{{)}}}}ˇ
3156 "
3157 .unindent(),
3158 );
3159
3160 // autoclose multi-character pairs
3161 cx.set_state(
3162 &"
3163 ˇ
3164 ˇ
3165 "
3166 .unindent(),
3167 );
3168 cx.update_editor(|view, cx| {
3169 view.handle_input("/", cx);
3170 view.handle_input("*", cx);
3171 });
3172 cx.assert_editor_state(
3173 &"
3174 /*ˇ */
3175 /*ˇ */
3176 "
3177 .unindent(),
3178 );
3179
3180 // one cursor autocloses a multi-character pair, one cursor
3181 // does not autoclose.
3182 cx.set_state(
3183 &"
3184 /ˇ
3185 ˇ
3186 "
3187 .unindent(),
3188 );
3189 cx.update_editor(|view, cx| view.handle_input("*", cx));
3190 cx.assert_editor_state(
3191 &"
3192 /*ˇ */
3193 *ˇ
3194 "
3195 .unindent(),
3196 );
3197
3198 // Don't autoclose if the next character isn't whitespace and isn't
3199 // listed in the language's "autoclose_before" section.
3200 cx.set_state("ˇa b");
3201 cx.update_editor(|view, cx| view.handle_input("{", cx));
3202 cx.assert_editor_state("{ˇa b");
3203
3204 // Don't autoclose if `close` is false for the bracket pair
3205 cx.set_state("ˇ");
3206 cx.update_editor(|view, cx| view.handle_input("[", cx));
3207 cx.assert_editor_state("[ˇ");
3208
3209 // Surround with brackets if text is selected
3210 cx.set_state("«aˇ» b");
3211 cx.update_editor(|view, cx| view.handle_input("{", cx));
3212 cx.assert_editor_state("{«aˇ»} b");
3213
3214 // Autclose pair where the start and end characters are the same
3215 cx.set_state("aˇ");
3216 cx.update_editor(|view, cx| view.handle_input("\"", cx));
3217 cx.assert_editor_state("a\"ˇ\"");
3218 cx.update_editor(|view, cx| view.handle_input("\"", cx));
3219 cx.assert_editor_state("a\"\"ˇ");
3220}
3221
3222#[gpui::test]
3223async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
3224 let mut cx = EditorTestContext::new(cx);
3225
3226 let html_language = Arc::new(
3227 Language::new(
3228 LanguageConfig {
3229 name: "HTML".into(),
3230 brackets: vec![
3231 BracketPair {
3232 start: "<".into(),
3233 end: ">".into(),
3234 close: true,
3235 ..Default::default()
3236 },
3237 BracketPair {
3238 start: "{".into(),
3239 end: "}".into(),
3240 close: true,
3241 ..Default::default()
3242 },
3243 BracketPair {
3244 start: "(".into(),
3245 end: ")".into(),
3246 close: true,
3247 ..Default::default()
3248 },
3249 ],
3250 autoclose_before: "})]>".into(),
3251 ..Default::default()
3252 },
3253 Some(tree_sitter_html::language()),
3254 )
3255 .with_injection_query(
3256 r#"
3257 (script_element
3258 (raw_text) @content
3259 (#set! "language" "javascript"))
3260 "#,
3261 )
3262 .unwrap(),
3263 );
3264
3265 let javascript_language = Arc::new(Language::new(
3266 LanguageConfig {
3267 name: "JavaScript".into(),
3268 brackets: vec![
3269 BracketPair {
3270 start: "/*".into(),
3271 end: " */".into(),
3272 close: true,
3273 ..Default::default()
3274 },
3275 BracketPair {
3276 start: "{".into(),
3277 end: "}".into(),
3278 close: true,
3279 ..Default::default()
3280 },
3281 BracketPair {
3282 start: "(".into(),
3283 end: ")".into(),
3284 close: true,
3285 ..Default::default()
3286 },
3287 ],
3288 autoclose_before: "})]>".into(),
3289 ..Default::default()
3290 },
3291 Some(tree_sitter_javascript::language()),
3292 ));
3293
3294 let registry = Arc::new(LanguageRegistry::test());
3295 registry.add(html_language.clone());
3296 registry.add(javascript_language.clone());
3297
3298 cx.update_buffer(|buffer, cx| {
3299 buffer.set_language_registry(registry);
3300 buffer.set_language(Some(html_language), cx);
3301 });
3302
3303 cx.set_state(
3304 &r#"
3305 <body>ˇ
3306 <script>
3307 var x = 1;ˇ
3308 </script>
3309 </body>ˇ
3310 "#
3311 .unindent(),
3312 );
3313
3314 // Precondition: different languages are active at different locations.
3315 cx.update_editor(|editor, cx| {
3316 let snapshot = editor.snapshot(cx);
3317 let cursors = editor.selections.ranges::<usize>(cx);
3318 let languages = cursors
3319 .iter()
3320 .map(|c| snapshot.language_at(c.start).unwrap().name())
3321 .collect::<Vec<_>>();
3322 assert_eq!(
3323 languages,
3324 &["HTML".into(), "JavaScript".into(), "HTML".into()]
3325 );
3326 });
3327
3328 // Angle brackets autoclose in HTML, but not JavaScript.
3329 cx.update_editor(|editor, cx| {
3330 editor.handle_input("<", cx);
3331 editor.handle_input("a", cx);
3332 });
3333 cx.assert_editor_state(
3334 &r#"
3335 <body><aˇ>
3336 <script>
3337 var x = 1;<aˇ
3338 </script>
3339 </body><aˇ>
3340 "#
3341 .unindent(),
3342 );
3343
3344 // Curly braces and parens autoclose in both HTML and JavaScript.
3345 cx.update_editor(|editor, cx| {
3346 editor.handle_input(" b=", cx);
3347 editor.handle_input("{", cx);
3348 editor.handle_input("c", cx);
3349 editor.handle_input("(", cx);
3350 });
3351 cx.assert_editor_state(
3352 &r#"
3353 <body><a b={c(ˇ)}>
3354 <script>
3355 var x = 1;<a b={c(ˇ)}
3356 </script>
3357 </body><a b={c(ˇ)}>
3358 "#
3359 .unindent(),
3360 );
3361
3362 // Brackets that were already autoclosed are skipped.
3363 cx.update_editor(|editor, cx| {
3364 editor.handle_input(")", cx);
3365 editor.handle_input("d", cx);
3366 editor.handle_input("}", cx);
3367 });
3368 cx.assert_editor_state(
3369 &r#"
3370 <body><a b={c()d}ˇ>
3371 <script>
3372 var x = 1;<a b={c()d}ˇ
3373 </script>
3374 </body><a b={c()d}ˇ>
3375 "#
3376 .unindent(),
3377 );
3378 cx.update_editor(|editor, cx| {
3379 editor.handle_input(">", cx);
3380 });
3381 cx.assert_editor_state(
3382 &r#"
3383 <body><a b={c()d}>ˇ
3384 <script>
3385 var x = 1;<a b={c()d}>ˇ
3386 </script>
3387 </body><a b={c()d}>ˇ
3388 "#
3389 .unindent(),
3390 );
3391
3392 // Reset
3393 cx.set_state(
3394 &r#"
3395 <body>ˇ
3396 <script>
3397 var x = 1;ˇ
3398 </script>
3399 </body>ˇ
3400 "#
3401 .unindent(),
3402 );
3403
3404 cx.update_editor(|editor, cx| {
3405 editor.handle_input("<", cx);
3406 });
3407 cx.assert_editor_state(
3408 &r#"
3409 <body><ˇ>
3410 <script>
3411 var x = 1;<ˇ
3412 </script>
3413 </body><ˇ>
3414 "#
3415 .unindent(),
3416 );
3417
3418 // When backspacing, the closing angle brackets are removed.
3419 cx.update_editor(|editor, cx| {
3420 editor.backspace(&Backspace, cx);
3421 });
3422 cx.assert_editor_state(
3423 &r#"
3424 <body>ˇ
3425 <script>
3426 var x = 1;ˇ
3427 </script>
3428 </body>ˇ
3429 "#
3430 .unindent(),
3431 );
3432
3433 // Block comments autoclose in JavaScript, but not HTML.
3434 cx.update_editor(|editor, cx| {
3435 editor.handle_input("/", cx);
3436 editor.handle_input("*", cx);
3437 });
3438 cx.assert_editor_state(
3439 &r#"
3440 <body>/*ˇ
3441 <script>
3442 var x = 1;/*ˇ */
3443 </script>
3444 </body>/*ˇ
3445 "#
3446 .unindent(),
3447 );
3448}
3449
3450#[gpui::test]
3451async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
3452 cx.update(|cx| cx.set_global(Settings::test(cx)));
3453 let language = Arc::new(Language::new(
3454 LanguageConfig {
3455 brackets: vec![
3456 BracketPair {
3457 start: "{".to_string(),
3458 end: "}".to_string(),
3459 close: true,
3460 newline: true,
3461 },
3462 BracketPair {
3463 start: "/* ".to_string(),
3464 end: "*/".to_string(),
3465 close: true,
3466 ..Default::default()
3467 },
3468 ],
3469 ..Default::default()
3470 },
3471 Some(tree_sitter_rust::language()),
3472 ));
3473
3474 let text = r#"
3475 a
3476 b
3477 c
3478 "#
3479 .unindent();
3480
3481 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
3482 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
3483 let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
3484 view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
3485 .await;
3486
3487 view.update(cx, |view, cx| {
3488 view.change_selections(None, cx, |s| {
3489 s.select_display_ranges([
3490 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
3491 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
3492 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1),
3493 ])
3494 });
3495
3496 view.handle_input("{", cx);
3497 view.handle_input("{", cx);
3498 view.handle_input("{", cx);
3499 assert_eq!(
3500 view.text(cx),
3501 "
3502 {{{a}}}
3503 {{{b}}}
3504 {{{c}}}
3505 "
3506 .unindent()
3507 );
3508 assert_eq!(
3509 view.selections.display_ranges(cx),
3510 [
3511 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 4),
3512 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 4),
3513 DisplayPoint::new(2, 3)..DisplayPoint::new(2, 4)
3514 ]
3515 );
3516
3517 view.undo(&Undo, cx);
3518 view.undo(&Undo, cx);
3519 view.undo(&Undo, cx);
3520 assert_eq!(
3521 view.text(cx),
3522 "
3523 a
3524 b
3525 c
3526 "
3527 .unindent()
3528 );
3529 assert_eq!(
3530 view.selections.display_ranges(cx),
3531 [
3532 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
3533 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
3534 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1)
3535 ]
3536 );
3537
3538 // Ensure inserting the first character of a multi-byte bracket pair
3539 // doesn't surround the selections with the bracket.
3540 view.handle_input("/", cx);
3541 assert_eq!(
3542 view.text(cx),
3543 "
3544 /
3545 /
3546 /
3547 "
3548 .unindent()
3549 );
3550 assert_eq!(
3551 view.selections.display_ranges(cx),
3552 [
3553 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
3554 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
3555 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1)
3556 ]
3557 );
3558
3559 view.undo(&Undo, cx);
3560 assert_eq!(
3561 view.text(cx),
3562 "
3563 a
3564 b
3565 c
3566 "
3567 .unindent()
3568 );
3569 assert_eq!(
3570 view.selections.display_ranges(cx),
3571 [
3572 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
3573 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
3574 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1)
3575 ]
3576 );
3577
3578 // Ensure inserting the last character of a multi-byte bracket pair
3579 // doesn't surround the selections with the bracket.
3580 view.handle_input("*", cx);
3581 assert_eq!(
3582 view.text(cx),
3583 "
3584 *
3585 *
3586 *
3587 "
3588 .unindent()
3589 );
3590 assert_eq!(
3591 view.selections.display_ranges(cx),
3592 [
3593 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
3594 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
3595 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1)
3596 ]
3597 );
3598 });
3599}
3600
3601#[gpui::test]
3602async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
3603 cx.update(|cx| cx.set_global(Settings::test(cx)));
3604 let language = Arc::new(Language::new(
3605 LanguageConfig {
3606 brackets: vec![BracketPair {
3607 start: "{".to_string(),
3608 end: "}".to_string(),
3609 close: true,
3610 newline: true,
3611 }],
3612 autoclose_before: "}".to_string(),
3613 ..Default::default()
3614 },
3615 Some(tree_sitter_rust::language()),
3616 ));
3617
3618 let text = r#"
3619 a
3620 b
3621 c
3622 "#
3623 .unindent();
3624
3625 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
3626 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
3627 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
3628 editor
3629 .condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
3630 .await;
3631
3632 editor.update(cx, |editor, cx| {
3633 editor.change_selections(None, cx, |s| {
3634 s.select_ranges([
3635 Point::new(0, 1)..Point::new(0, 1),
3636 Point::new(1, 1)..Point::new(1, 1),
3637 Point::new(2, 1)..Point::new(2, 1),
3638 ])
3639 });
3640
3641 editor.handle_input("{", cx);
3642 editor.handle_input("{", cx);
3643 editor.handle_input("_", cx);
3644 assert_eq!(
3645 editor.text(cx),
3646 "
3647 a{{_}}
3648 b{{_}}
3649 c{{_}}
3650 "
3651 .unindent()
3652 );
3653 assert_eq!(
3654 editor.selections.ranges::<Point>(cx),
3655 [
3656 Point::new(0, 4)..Point::new(0, 4),
3657 Point::new(1, 4)..Point::new(1, 4),
3658 Point::new(2, 4)..Point::new(2, 4)
3659 ]
3660 );
3661
3662 editor.backspace(&Default::default(), cx);
3663 editor.backspace(&Default::default(), cx);
3664 assert_eq!(
3665 editor.text(cx),
3666 "
3667 a{}
3668 b{}
3669 c{}
3670 "
3671 .unindent()
3672 );
3673 assert_eq!(
3674 editor.selections.ranges::<Point>(cx),
3675 [
3676 Point::new(0, 2)..Point::new(0, 2),
3677 Point::new(1, 2)..Point::new(1, 2),
3678 Point::new(2, 2)..Point::new(2, 2)
3679 ]
3680 );
3681
3682 editor.delete_to_previous_word_start(&Default::default(), cx);
3683 assert_eq!(
3684 editor.text(cx),
3685 "
3686 a
3687 b
3688 c
3689 "
3690 .unindent()
3691 );
3692 assert_eq!(
3693 editor.selections.ranges::<Point>(cx),
3694 [
3695 Point::new(0, 1)..Point::new(0, 1),
3696 Point::new(1, 1)..Point::new(1, 1),
3697 Point::new(2, 1)..Point::new(2, 1)
3698 ]
3699 );
3700 });
3701}
3702
3703#[gpui::test]
3704async fn test_snippets(cx: &mut gpui::TestAppContext) {
3705 cx.update(|cx| cx.set_global(Settings::test(cx)));
3706
3707 let (text, insertion_ranges) = marked_text_ranges(
3708 indoc! {"
3709 a.ˇ b
3710 a.ˇ b
3711 a.ˇ b
3712 "},
3713 false,
3714 );
3715
3716 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
3717 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
3718
3719 editor.update(cx, |editor, cx| {
3720 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
3721
3722 editor
3723 .insert_snippet(&insertion_ranges, snippet, cx)
3724 .unwrap();
3725
3726 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
3727 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
3728 assert_eq!(editor.text(cx), expected_text);
3729 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
3730 }
3731
3732 assert(
3733 editor,
3734 cx,
3735 indoc! {"
3736 a.f(«one», two, «three») b
3737 a.f(«one», two, «three») b
3738 a.f(«one», two, «three») b
3739 "},
3740 );
3741
3742 // Can't move earlier than the first tab stop
3743 assert!(!editor.move_to_prev_snippet_tabstop(cx));
3744 assert(
3745 editor,
3746 cx,
3747 indoc! {"
3748 a.f(«one», two, «three») b
3749 a.f(«one», two, «three») b
3750 a.f(«one», two, «three») b
3751 "},
3752 );
3753
3754 assert!(editor.move_to_next_snippet_tabstop(cx));
3755 assert(
3756 editor,
3757 cx,
3758 indoc! {"
3759 a.f(one, «two», three) b
3760 a.f(one, «two», three) b
3761 a.f(one, «two», three) b
3762 "},
3763 );
3764
3765 editor.move_to_prev_snippet_tabstop(cx);
3766 assert(
3767 editor,
3768 cx,
3769 indoc! {"
3770 a.f(«one», two, «three») b
3771 a.f(«one», two, «three») b
3772 a.f(«one», two, «three») b
3773 "},
3774 );
3775
3776 assert!(editor.move_to_next_snippet_tabstop(cx));
3777 assert(
3778 editor,
3779 cx,
3780 indoc! {"
3781 a.f(one, «two», three) b
3782 a.f(one, «two», three) b
3783 a.f(one, «two», three) b
3784 "},
3785 );
3786 assert!(editor.move_to_next_snippet_tabstop(cx));
3787 assert(
3788 editor,
3789 cx,
3790 indoc! {"
3791 a.f(one, two, three)ˇ b
3792 a.f(one, two, three)ˇ b
3793 a.f(one, two, three)ˇ b
3794 "},
3795 );
3796
3797 // As soon as the last tab stop is reached, snippet state is gone
3798 editor.move_to_prev_snippet_tabstop(cx);
3799 assert(
3800 editor,
3801 cx,
3802 indoc! {"
3803 a.f(one, two, three)ˇ b
3804 a.f(one, two, three)ˇ b
3805 a.f(one, two, three)ˇ b
3806 "},
3807 );
3808 });
3809}
3810
3811#[gpui::test]
3812async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
3813 cx.foreground().forbid_parking();
3814
3815 let mut language = Language::new(
3816 LanguageConfig {
3817 name: "Rust".into(),
3818 path_suffixes: vec!["rs".to_string()],
3819 ..Default::default()
3820 },
3821 Some(tree_sitter_rust::language()),
3822 );
3823 let mut fake_servers = language
3824 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
3825 capabilities: lsp::ServerCapabilities {
3826 document_formatting_provider: Some(lsp::OneOf::Left(true)),
3827 ..Default::default()
3828 },
3829 ..Default::default()
3830 }))
3831 .await;
3832
3833 let fs = FakeFs::new(cx.background());
3834 fs.insert_file("/file.rs", Default::default()).await;
3835
3836 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
3837 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
3838 let buffer = project
3839 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
3840 .await
3841 .unwrap();
3842
3843 cx.foreground().start_waiting();
3844 let fake_server = fake_servers.next().await.unwrap();
3845
3846 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
3847 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
3848 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
3849 assert!(cx.read(|cx| editor.is_dirty(cx)));
3850
3851 let save = cx.update(|cx| editor.save(project.clone(), cx));
3852 fake_server
3853 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
3854 assert_eq!(
3855 params.text_document.uri,
3856 lsp::Url::from_file_path("/file.rs").unwrap()
3857 );
3858 assert_eq!(params.options.tab_size, 4);
3859 Ok(Some(vec![lsp::TextEdit::new(
3860 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
3861 ", ".to_string(),
3862 )]))
3863 })
3864 .next()
3865 .await;
3866 cx.foreground().start_waiting();
3867 save.await.unwrap();
3868 assert_eq!(
3869 editor.read_with(cx, |editor, cx| editor.text(cx)),
3870 "one, two\nthree\n"
3871 );
3872 assert!(!cx.read(|cx| editor.is_dirty(cx)));
3873
3874 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
3875 assert!(cx.read(|cx| editor.is_dirty(cx)));
3876
3877 // Ensure we can still save even if formatting hangs.
3878 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
3879 assert_eq!(
3880 params.text_document.uri,
3881 lsp::Url::from_file_path("/file.rs").unwrap()
3882 );
3883 futures::future::pending::<()>().await;
3884 unreachable!()
3885 });
3886 let save = cx.update(|cx| editor.save(project.clone(), cx));
3887 cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
3888 cx.foreground().start_waiting();
3889 save.await.unwrap();
3890 assert_eq!(
3891 editor.read_with(cx, |editor, cx| editor.text(cx)),
3892 "one\ntwo\nthree\n"
3893 );
3894 assert!(!cx.read(|cx| editor.is_dirty(cx)));
3895
3896 // Set rust language override and assert overriden tabsize is sent to language server
3897 cx.update(|cx| {
3898 cx.update_global::<Settings, _, _>(|settings, _| {
3899 settings.language_overrides.insert(
3900 "Rust".into(),
3901 EditorSettings {
3902 tab_size: Some(8.try_into().unwrap()),
3903 ..Default::default()
3904 },
3905 );
3906 })
3907 });
3908
3909 let save = cx.update(|cx| editor.save(project.clone(), cx));
3910 fake_server
3911 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
3912 assert_eq!(
3913 params.text_document.uri,
3914 lsp::Url::from_file_path("/file.rs").unwrap()
3915 );
3916 assert_eq!(params.options.tab_size, 8);
3917 Ok(Some(vec![]))
3918 })
3919 .next()
3920 .await;
3921 cx.foreground().start_waiting();
3922 save.await.unwrap();
3923}
3924
3925#[gpui::test]
3926async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
3927 cx.foreground().forbid_parking();
3928
3929 let mut language = Language::new(
3930 LanguageConfig {
3931 name: "Rust".into(),
3932 path_suffixes: vec!["rs".to_string()],
3933 ..Default::default()
3934 },
3935 Some(tree_sitter_rust::language()),
3936 );
3937 let mut fake_servers = language
3938 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
3939 capabilities: lsp::ServerCapabilities {
3940 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
3941 ..Default::default()
3942 },
3943 ..Default::default()
3944 }))
3945 .await;
3946
3947 let fs = FakeFs::new(cx.background());
3948 fs.insert_file("/file.rs", Default::default()).await;
3949
3950 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
3951 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
3952 let buffer = project
3953 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
3954 .await
3955 .unwrap();
3956
3957 cx.foreground().start_waiting();
3958 let fake_server = fake_servers.next().await.unwrap();
3959
3960 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
3961 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
3962 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
3963 assert!(cx.read(|cx| editor.is_dirty(cx)));
3964
3965 let save = cx.update(|cx| editor.save(project.clone(), cx));
3966 fake_server
3967 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
3968 assert_eq!(
3969 params.text_document.uri,
3970 lsp::Url::from_file_path("/file.rs").unwrap()
3971 );
3972 assert_eq!(params.options.tab_size, 4);
3973 Ok(Some(vec![lsp::TextEdit::new(
3974 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
3975 ", ".to_string(),
3976 )]))
3977 })
3978 .next()
3979 .await;
3980 cx.foreground().start_waiting();
3981 save.await.unwrap();
3982 assert_eq!(
3983 editor.read_with(cx, |editor, cx| editor.text(cx)),
3984 "one, two\nthree\n"
3985 );
3986 assert!(!cx.read(|cx| editor.is_dirty(cx)));
3987
3988 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
3989 assert!(cx.read(|cx| editor.is_dirty(cx)));
3990
3991 // Ensure we can still save even if formatting hangs.
3992 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
3993 move |params, _| async move {
3994 assert_eq!(
3995 params.text_document.uri,
3996 lsp::Url::from_file_path("/file.rs").unwrap()
3997 );
3998 futures::future::pending::<()>().await;
3999 unreachable!()
4000 },
4001 );
4002 let save = cx.update(|cx| editor.save(project.clone(), cx));
4003 cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
4004 cx.foreground().start_waiting();
4005 save.await.unwrap();
4006 assert_eq!(
4007 editor.read_with(cx, |editor, cx| editor.text(cx)),
4008 "one\ntwo\nthree\n"
4009 );
4010 assert!(!cx.read(|cx| editor.is_dirty(cx)));
4011
4012 // Set rust language override and assert overriden tabsize is sent to language server
4013 cx.update(|cx| {
4014 cx.update_global::<Settings, _, _>(|settings, _| {
4015 settings.language_overrides.insert(
4016 "Rust".into(),
4017 EditorSettings {
4018 tab_size: Some(8.try_into().unwrap()),
4019 ..Default::default()
4020 },
4021 );
4022 })
4023 });
4024
4025 let save = cx.update(|cx| editor.save(project.clone(), cx));
4026 fake_server
4027 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
4028 assert_eq!(
4029 params.text_document.uri,
4030 lsp::Url::from_file_path("/file.rs").unwrap()
4031 );
4032 assert_eq!(params.options.tab_size, 8);
4033 Ok(Some(vec![]))
4034 })
4035 .next()
4036 .await;
4037 cx.foreground().start_waiting();
4038 save.await.unwrap();
4039}
4040
4041#[gpui::test]
4042async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
4043 cx.foreground().forbid_parking();
4044
4045 let mut language = Language::new(
4046 LanguageConfig {
4047 name: "Rust".into(),
4048 path_suffixes: vec!["rs".to_string()],
4049 ..Default::default()
4050 },
4051 Some(tree_sitter_rust::language()),
4052 );
4053 let mut fake_servers = language
4054 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
4055 capabilities: lsp::ServerCapabilities {
4056 document_formatting_provider: Some(lsp::OneOf::Left(true)),
4057 ..Default::default()
4058 },
4059 ..Default::default()
4060 }))
4061 .await;
4062
4063 let fs = FakeFs::new(cx.background());
4064 fs.insert_file("/file.rs", Default::default()).await;
4065
4066 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
4067 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
4068 let buffer = project
4069 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
4070 .await
4071 .unwrap();
4072
4073 cx.foreground().start_waiting();
4074 let fake_server = fake_servers.next().await.unwrap();
4075
4076 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
4077 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
4078 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
4079
4080 let format = editor.update(cx, |editor, cx| editor.perform_format(project.clone(), cx));
4081 fake_server
4082 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
4083 assert_eq!(
4084 params.text_document.uri,
4085 lsp::Url::from_file_path("/file.rs").unwrap()
4086 );
4087 assert_eq!(params.options.tab_size, 4);
4088 Ok(Some(vec![lsp::TextEdit::new(
4089 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
4090 ", ".to_string(),
4091 )]))
4092 })
4093 .next()
4094 .await;
4095 cx.foreground().start_waiting();
4096 format.await.unwrap();
4097 assert_eq!(
4098 editor.read_with(cx, |editor, cx| editor.text(cx)),
4099 "one, two\nthree\n"
4100 );
4101
4102 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
4103 // Ensure we don't lock if formatting hangs.
4104 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
4105 assert_eq!(
4106 params.text_document.uri,
4107 lsp::Url::from_file_path("/file.rs").unwrap()
4108 );
4109 futures::future::pending::<()>().await;
4110 unreachable!()
4111 });
4112 let format = editor.update(cx, |editor, cx| editor.perform_format(project, cx));
4113 cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
4114 cx.foreground().start_waiting();
4115 format.await.unwrap();
4116 assert_eq!(
4117 editor.read_with(cx, |editor, cx| editor.text(cx)),
4118 "one\ntwo\nthree\n"
4119 );
4120}
4121
4122#[gpui::test]
4123async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
4124 cx.foreground().forbid_parking();
4125
4126 let mut cx = EditorLspTestContext::new_rust(
4127 lsp::ServerCapabilities {
4128 document_formatting_provider: Some(lsp::OneOf::Left(true)),
4129 ..Default::default()
4130 },
4131 cx,
4132 )
4133 .await;
4134
4135 cx.set_state(indoc! {"
4136 one.twoˇ
4137 "});
4138
4139 // The format request takes a long time. When it completes, it inserts
4140 // a newline and an indent before the `.`
4141 cx.lsp
4142 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
4143 let executor = cx.background();
4144 async move {
4145 executor.timer(Duration::from_millis(100)).await;
4146 Ok(Some(vec![lsp::TextEdit {
4147 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
4148 new_text: "\n ".into(),
4149 }]))
4150 }
4151 });
4152
4153 // Submit a format request.
4154 let format_1 = cx
4155 .update_editor(|editor, cx| editor.format(&Format, cx))
4156 .unwrap();
4157 cx.foreground().run_until_parked();
4158
4159 // Submit a second format request.
4160 let format_2 = cx
4161 .update_editor(|editor, cx| editor.format(&Format, cx))
4162 .unwrap();
4163 cx.foreground().run_until_parked();
4164
4165 // Wait for both format requests to complete
4166 cx.foreground().advance_clock(Duration::from_millis(200));
4167 cx.foreground().start_waiting();
4168 format_1.await.unwrap();
4169 cx.foreground().start_waiting();
4170 format_2.await.unwrap();
4171
4172 // The formatting edits only happens once.
4173 cx.assert_editor_state(indoc! {"
4174 one
4175 .twoˇ
4176 "});
4177}
4178
4179#[gpui::test]
4180async fn test_completion(cx: &mut gpui::TestAppContext) {
4181 let mut cx = EditorLspTestContext::new_rust(
4182 lsp::ServerCapabilities {
4183 completion_provider: Some(lsp::CompletionOptions {
4184 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
4185 ..Default::default()
4186 }),
4187 ..Default::default()
4188 },
4189 cx,
4190 )
4191 .await;
4192
4193 cx.set_state(indoc! {"
4194 oneˇ
4195 two
4196 three
4197 "});
4198 cx.simulate_keystroke(".");
4199 handle_completion_request(
4200 &mut cx,
4201 indoc! {"
4202 one.|<>
4203 two
4204 three
4205 "},
4206 vec!["first_completion", "second_completion"],
4207 )
4208 .await;
4209 cx.condition(|editor, _| editor.context_menu_visible())
4210 .await;
4211 let apply_additional_edits = cx.update_editor(|editor, cx| {
4212 editor.move_down(&MoveDown, cx);
4213 editor
4214 .confirm_completion(&ConfirmCompletion::default(), cx)
4215 .unwrap()
4216 });
4217 cx.assert_editor_state(indoc! {"
4218 one.second_completionˇ
4219 two
4220 three
4221 "});
4222
4223 handle_resolve_completion_request(
4224 &mut cx,
4225 Some(vec![
4226 (
4227 //This overlaps with the primary completion edit which is
4228 //misbehavior from the LSP spec, test that we filter it out
4229 indoc! {"
4230 one.second_ˇcompletion
4231 two
4232 threeˇ
4233 "},
4234 "overlapping aditional edit",
4235 ),
4236 (
4237 indoc! {"
4238 one.second_completion
4239 two
4240 threeˇ
4241 "},
4242 "\nadditional edit",
4243 ),
4244 ]),
4245 )
4246 .await;
4247 apply_additional_edits.await.unwrap();
4248 cx.assert_editor_state(indoc! {"
4249 one.second_completionˇ
4250 two
4251 three
4252 additional edit
4253 "});
4254
4255 cx.set_state(indoc! {"
4256 one.second_completion
4257 twoˇ
4258 threeˇ
4259 additional edit
4260 "});
4261 cx.simulate_keystroke(" ");
4262 assert!(cx.editor(|e, _| e.context_menu.is_none()));
4263 cx.simulate_keystroke("s");
4264 assert!(cx.editor(|e, _| e.context_menu.is_none()));
4265
4266 cx.assert_editor_state(indoc! {"
4267 one.second_completion
4268 two sˇ
4269 three sˇ
4270 additional edit
4271 "});
4272 handle_completion_request(
4273 &mut cx,
4274 indoc! {"
4275 one.second_completion
4276 two s
4277 three <s|>
4278 additional edit
4279 "},
4280 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
4281 )
4282 .await;
4283 cx.condition(|editor, _| editor.context_menu_visible())
4284 .await;
4285
4286 cx.simulate_keystroke("i");
4287
4288 handle_completion_request(
4289 &mut cx,
4290 indoc! {"
4291 one.second_completion
4292 two si
4293 three <si|>
4294 additional edit
4295 "},
4296 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
4297 )
4298 .await;
4299 cx.condition(|editor, _| editor.context_menu_visible())
4300 .await;
4301
4302 let apply_additional_edits = cx.update_editor(|editor, cx| {
4303 editor
4304 .confirm_completion(&ConfirmCompletion::default(), cx)
4305 .unwrap()
4306 });
4307 cx.assert_editor_state(indoc! {"
4308 one.second_completion
4309 two sixth_completionˇ
4310 three sixth_completionˇ
4311 additional edit
4312 "});
4313
4314 handle_resolve_completion_request(&mut cx, None).await;
4315 apply_additional_edits.await.unwrap();
4316
4317 cx.update(|cx| {
4318 cx.update_global::<Settings, _, _>(|settings, _| {
4319 settings.show_completions_on_input = false;
4320 })
4321 });
4322 cx.set_state("editorˇ");
4323 cx.simulate_keystroke(".");
4324 assert!(cx.editor(|e, _| e.context_menu.is_none()));
4325 cx.simulate_keystroke("c");
4326 cx.simulate_keystroke("l");
4327 cx.simulate_keystroke("o");
4328 cx.assert_editor_state("editor.cloˇ");
4329 assert!(cx.editor(|e, _| e.context_menu.is_none()));
4330 cx.update_editor(|editor, cx| {
4331 editor.show_completions(&ShowCompletions, cx);
4332 });
4333 handle_completion_request(&mut cx, "editor.<clo|>", vec!["close", "clobber"]).await;
4334 cx.condition(|editor, _| editor.context_menu_visible())
4335 .await;
4336 let apply_additional_edits = cx.update_editor(|editor, cx| {
4337 editor
4338 .confirm_completion(&ConfirmCompletion::default(), cx)
4339 .unwrap()
4340 });
4341 cx.assert_editor_state("editor.closeˇ");
4342 handle_resolve_completion_request(&mut cx, None).await;
4343 apply_additional_edits.await.unwrap();
4344
4345 // Handle completion request passing a marked string specifying where the completion
4346 // should be triggered from using '|' character, what range should be replaced, and what completions
4347 // should be returned using '<' and '>' to delimit the range
4348 async fn handle_completion_request<'a>(
4349 cx: &mut EditorLspTestContext<'a>,
4350 marked_string: &str,
4351 completions: Vec<&'static str>,
4352 ) {
4353 let complete_from_marker: TextRangeMarker = '|'.into();
4354 let replace_range_marker: TextRangeMarker = ('<', '>').into();
4355 let (_, mut marked_ranges) = marked_text_ranges_by(
4356 marked_string,
4357 vec![complete_from_marker.clone(), replace_range_marker.clone()],
4358 );
4359
4360 let complete_from_position =
4361 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
4362 let replace_range =
4363 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
4364
4365 cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
4366 let completions = completions.clone();
4367 async move {
4368 assert_eq!(params.text_document_position.text_document.uri, url.clone());
4369 assert_eq!(
4370 params.text_document_position.position,
4371 complete_from_position
4372 );
4373 Ok(Some(lsp::CompletionResponse::Array(
4374 completions
4375 .iter()
4376 .map(|completion_text| lsp::CompletionItem {
4377 label: completion_text.to_string(),
4378 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
4379 range: replace_range,
4380 new_text: completion_text.to_string(),
4381 })),
4382 ..Default::default()
4383 })
4384 .collect(),
4385 )))
4386 }
4387 })
4388 .next()
4389 .await;
4390 }
4391
4392 async fn handle_resolve_completion_request<'a>(
4393 cx: &mut EditorLspTestContext<'a>,
4394 edits: Option<Vec<(&'static str, &'static str)>>,
4395 ) {
4396 let edits = edits.map(|edits| {
4397 edits
4398 .iter()
4399 .map(|(marked_string, new_text)| {
4400 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
4401 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
4402 lsp::TextEdit::new(replace_range, new_text.to_string())
4403 })
4404 .collect::<Vec<_>>()
4405 });
4406
4407 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
4408 let edits = edits.clone();
4409 async move {
4410 Ok(lsp::CompletionItem {
4411 additional_text_edits: edits,
4412 ..Default::default()
4413 })
4414 }
4415 })
4416 .next()
4417 .await;
4418 }
4419}
4420
4421#[gpui::test]
4422async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
4423 cx.update(|cx| cx.set_global(Settings::test(cx)));
4424 let language = Arc::new(Language::new(
4425 LanguageConfig {
4426 line_comment: Some("// ".into()),
4427 ..Default::default()
4428 },
4429 Some(tree_sitter_rust::language()),
4430 ));
4431
4432 let text = "
4433 fn a() {
4434 //b();
4435 // c();
4436 // d();
4437 }
4438 "
4439 .unindent();
4440
4441 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
4442 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
4443 let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
4444
4445 view.update(cx, |editor, cx| {
4446 // If multiple selections intersect a line, the line is only
4447 // toggled once.
4448 editor.change_selections(None, cx, |s| {
4449 s.select_display_ranges([
4450 DisplayPoint::new(1, 3)..DisplayPoint::new(2, 3),
4451 DisplayPoint::new(3, 5)..DisplayPoint::new(3, 6),
4452 ])
4453 });
4454 editor.toggle_comments(&ToggleComments, cx);
4455 assert_eq!(
4456 editor.text(cx),
4457 "
4458 fn a() {
4459 b();
4460 c();
4461 d();
4462 }
4463 "
4464 .unindent()
4465 );
4466
4467 // The comment prefix is inserted at the same column for every line
4468 // in a selection.
4469 editor.change_selections(None, cx, |s| {
4470 s.select_display_ranges([DisplayPoint::new(1, 3)..DisplayPoint::new(3, 6)])
4471 });
4472 editor.toggle_comments(&ToggleComments, cx);
4473 assert_eq!(
4474 editor.text(cx),
4475 "
4476 fn a() {
4477 // b();
4478 // c();
4479 // d();
4480 }
4481 "
4482 .unindent()
4483 );
4484
4485 // If a selection ends at the beginning of a line, that line is not toggled.
4486 editor.change_selections(None, cx, |s| {
4487 s.select_display_ranges([DisplayPoint::new(2, 0)..DisplayPoint::new(3, 0)])
4488 });
4489 editor.toggle_comments(&ToggleComments, cx);
4490 assert_eq!(
4491 editor.text(cx),
4492 "
4493 fn a() {
4494 // b();
4495 c();
4496 // d();
4497 }
4498 "
4499 .unindent()
4500 );
4501 });
4502}
4503
4504#[gpui::test]
4505async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
4506 let mut cx = EditorTestContext::new(cx);
4507
4508 let html_language = Arc::new(
4509 Language::new(
4510 LanguageConfig {
4511 name: "HTML".into(),
4512 block_comment: Some(("<!-- ".into(), " -->".into())),
4513 ..Default::default()
4514 },
4515 Some(tree_sitter_html::language()),
4516 )
4517 .with_injection_query(
4518 r#"
4519 (script_element
4520 (raw_text) @content
4521 (#set! "language" "javascript"))
4522 "#,
4523 )
4524 .unwrap(),
4525 );
4526
4527 let javascript_language = Arc::new(Language::new(
4528 LanguageConfig {
4529 name: "JavaScript".into(),
4530 line_comment: Some("// ".into()),
4531 ..Default::default()
4532 },
4533 Some(tree_sitter_javascript::language()),
4534 ));
4535
4536 let registry = Arc::new(LanguageRegistry::test());
4537 registry.add(html_language.clone());
4538 registry.add(javascript_language.clone());
4539
4540 cx.update_buffer(|buffer, cx| {
4541 buffer.set_language_registry(registry);
4542 buffer.set_language(Some(html_language), cx);
4543 });
4544
4545 // Toggle comments for empty selections
4546 cx.set_state(
4547 &r#"
4548 <p>A</p>ˇ
4549 <p>B</p>ˇ
4550 <p>C</p>ˇ
4551 "#
4552 .unindent(),
4553 );
4554 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments, cx));
4555 cx.assert_editor_state(
4556 &r#"
4557 <!-- <p>A</p>ˇ -->
4558 <!-- <p>B</p>ˇ -->
4559 <!-- <p>C</p>ˇ -->
4560 "#
4561 .unindent(),
4562 );
4563 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments, cx));
4564 cx.assert_editor_state(
4565 &r#"
4566 <p>A</p>ˇ
4567 <p>B</p>ˇ
4568 <p>C</p>ˇ
4569 "#
4570 .unindent(),
4571 );
4572
4573 // Toggle comments for mixture of empty and non-empty selections, where
4574 // multiple selections occupy a given line.
4575 cx.set_state(
4576 &r#"
4577 <p>A«</p>
4578 <p>ˇ»B</p>ˇ
4579 <p>C«</p>
4580 <p>ˇ»D</p>ˇ
4581 "#
4582 .unindent(),
4583 );
4584
4585 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments, cx));
4586 cx.assert_editor_state(
4587 &r#"
4588 <!-- <p>A«</p>
4589 <p>ˇ»B</p>ˇ -->
4590 <!-- <p>C«</p>
4591 <p>ˇ»D</p>ˇ -->
4592 "#
4593 .unindent(),
4594 );
4595 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments, cx));
4596 cx.assert_editor_state(
4597 &r#"
4598 <p>A«</p>
4599 <p>ˇ»B</p>ˇ
4600 <p>C«</p>
4601 <p>ˇ»D</p>ˇ
4602 "#
4603 .unindent(),
4604 );
4605
4606 // Toggle comments when different languages are active for different
4607 // selections.
4608 cx.set_state(
4609 &r#"
4610 ˇ<script>
4611 ˇvar x = new Y();
4612 ˇ</script>
4613 "#
4614 .unindent(),
4615 );
4616 cx.foreground().run_until_parked();
4617 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments, cx));
4618 cx.assert_editor_state(
4619 &r#"
4620 <!-- ˇ<script> -->
4621 // ˇvar x = new Y();
4622 <!-- ˇ</script> -->
4623 "#
4624 .unindent(),
4625 );
4626}
4627
4628#[gpui::test]
4629fn test_editing_disjoint_excerpts(cx: &mut gpui::MutableAppContext) {
4630 cx.set_global(Settings::test(cx));
4631 let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
4632 let multibuffer = cx.add_model(|cx| {
4633 let mut multibuffer = MultiBuffer::new(0);
4634 multibuffer.push_excerpts(
4635 buffer.clone(),
4636 [
4637 ExcerptRange {
4638 context: Point::new(0, 0)..Point::new(0, 4),
4639 primary: None,
4640 },
4641 ExcerptRange {
4642 context: Point::new(1, 0)..Point::new(1, 4),
4643 primary: None,
4644 },
4645 ],
4646 cx,
4647 );
4648 multibuffer
4649 });
4650
4651 assert_eq!(multibuffer.read(cx).read(cx).text(), "aaaa\nbbbb");
4652
4653 let (_, view) = cx.add_window(Default::default(), |cx| build_editor(multibuffer, cx));
4654 view.update(cx, |view, cx| {
4655 assert_eq!(view.text(cx), "aaaa\nbbbb");
4656 view.change_selections(None, cx, |s| {
4657 s.select_ranges([
4658 Point::new(0, 0)..Point::new(0, 0),
4659 Point::new(1, 0)..Point::new(1, 0),
4660 ])
4661 });
4662
4663 view.handle_input("X", cx);
4664 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
4665 assert_eq!(
4666 view.selections.ranges(cx),
4667 [
4668 Point::new(0, 1)..Point::new(0, 1),
4669 Point::new(1, 1)..Point::new(1, 1),
4670 ]
4671 )
4672 });
4673}
4674
4675#[gpui::test]
4676fn test_editing_overlapping_excerpts(cx: &mut gpui::MutableAppContext) {
4677 cx.set_global(Settings::test(cx));
4678 let markers = vec![('[', ']').into(), ('(', ')').into()];
4679 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
4680 indoc! {"
4681 [aaaa
4682 (bbbb]
4683 cccc)",
4684 },
4685 markers.clone(),
4686 );
4687 let excerpt_ranges = markers.into_iter().map(|marker| {
4688 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
4689 ExcerptRange {
4690 context,
4691 primary: None,
4692 }
4693 });
4694 let buffer = cx.add_model(|cx| Buffer::new(0, initial_text, cx));
4695 let multibuffer = cx.add_model(|cx| {
4696 let mut multibuffer = MultiBuffer::new(0);
4697 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
4698 multibuffer
4699 });
4700
4701 let (_, view) = cx.add_window(Default::default(), |cx| build_editor(multibuffer, cx));
4702 view.update(cx, |view, cx| {
4703 let (expected_text, selection_ranges) = marked_text_ranges(
4704 indoc! {"
4705 aaaa
4706 bˇbbb
4707 bˇbbˇb
4708 cccc"
4709 },
4710 true,
4711 );
4712 assert_eq!(view.text(cx), expected_text);
4713 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
4714
4715 view.handle_input("X", cx);
4716
4717 let (expected_text, expected_selections) = marked_text_ranges(
4718 indoc! {"
4719 aaaa
4720 bXˇbbXb
4721 bXˇbbXˇb
4722 cccc"
4723 },
4724 false,
4725 );
4726 assert_eq!(view.text(cx), expected_text);
4727 assert_eq!(view.selections.ranges(cx), expected_selections);
4728
4729 view.newline(&Newline, cx);
4730 let (expected_text, expected_selections) = marked_text_ranges(
4731 indoc! {"
4732 aaaa
4733 bX
4734 ˇbbX
4735 b
4736 bX
4737 ˇbbX
4738 ˇb
4739 cccc"
4740 },
4741 false,
4742 );
4743 assert_eq!(view.text(cx), expected_text);
4744 assert_eq!(view.selections.ranges(cx), expected_selections);
4745 });
4746}
4747
4748#[gpui::test]
4749fn test_refresh_selections(cx: &mut gpui::MutableAppContext) {
4750 cx.set_global(Settings::test(cx));
4751 let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
4752 let mut excerpt1_id = None;
4753 let multibuffer = cx.add_model(|cx| {
4754 let mut multibuffer = MultiBuffer::new(0);
4755 excerpt1_id = multibuffer
4756 .push_excerpts(
4757 buffer.clone(),
4758 [
4759 ExcerptRange {
4760 context: Point::new(0, 0)..Point::new(1, 4),
4761 primary: None,
4762 },
4763 ExcerptRange {
4764 context: Point::new(1, 0)..Point::new(2, 4),
4765 primary: None,
4766 },
4767 ],
4768 cx,
4769 )
4770 .into_iter()
4771 .next();
4772 multibuffer
4773 });
4774 assert_eq!(
4775 multibuffer.read(cx).read(cx).text(),
4776 "aaaa\nbbbb\nbbbb\ncccc"
4777 );
4778 let (_, editor) = cx.add_window(Default::default(), |cx| {
4779 let mut editor = build_editor(multibuffer.clone(), cx);
4780 let snapshot = editor.snapshot(cx);
4781 editor.change_selections(None, cx, |s| {
4782 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
4783 });
4784 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
4785 assert_eq!(
4786 editor.selections.ranges(cx),
4787 [
4788 Point::new(1, 3)..Point::new(1, 3),
4789 Point::new(2, 1)..Point::new(2, 1),
4790 ]
4791 );
4792 editor
4793 });
4794
4795 // Refreshing selections is a no-op when excerpts haven't changed.
4796 editor.update(cx, |editor, cx| {
4797 editor.change_selections(None, cx, |s| s.refresh());
4798 assert_eq!(
4799 editor.selections.ranges(cx),
4800 [
4801 Point::new(1, 3)..Point::new(1, 3),
4802 Point::new(2, 1)..Point::new(2, 1),
4803 ]
4804 );
4805 });
4806
4807 multibuffer.update(cx, |multibuffer, cx| {
4808 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
4809 });
4810 editor.update(cx, |editor, cx| {
4811 // Removing an excerpt causes the first selection to become degenerate.
4812 assert_eq!(
4813 editor.selections.ranges(cx),
4814 [
4815 Point::new(0, 0)..Point::new(0, 0),
4816 Point::new(0, 1)..Point::new(0, 1)
4817 ]
4818 );
4819
4820 // Refreshing selections will relocate the first selection to the original buffer
4821 // location.
4822 editor.change_selections(None, cx, |s| s.refresh());
4823 assert_eq!(
4824 editor.selections.ranges(cx),
4825 [
4826 Point::new(0, 1)..Point::new(0, 1),
4827 Point::new(0, 3)..Point::new(0, 3)
4828 ]
4829 );
4830 assert!(editor.selections.pending_anchor().is_some());
4831 });
4832}
4833
4834#[gpui::test]
4835fn test_refresh_selections_while_selecting_with_mouse(cx: &mut gpui::MutableAppContext) {
4836 cx.set_global(Settings::test(cx));
4837 let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
4838 let mut excerpt1_id = None;
4839 let multibuffer = cx.add_model(|cx| {
4840 let mut multibuffer = MultiBuffer::new(0);
4841 excerpt1_id = multibuffer
4842 .push_excerpts(
4843 buffer.clone(),
4844 [
4845 ExcerptRange {
4846 context: Point::new(0, 0)..Point::new(1, 4),
4847 primary: None,
4848 },
4849 ExcerptRange {
4850 context: Point::new(1, 0)..Point::new(2, 4),
4851 primary: None,
4852 },
4853 ],
4854 cx,
4855 )
4856 .into_iter()
4857 .next();
4858 multibuffer
4859 });
4860 assert_eq!(
4861 multibuffer.read(cx).read(cx).text(),
4862 "aaaa\nbbbb\nbbbb\ncccc"
4863 );
4864 let (_, editor) = cx.add_window(Default::default(), |cx| {
4865 let mut editor = build_editor(multibuffer.clone(), cx);
4866 let snapshot = editor.snapshot(cx);
4867 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
4868 assert_eq!(
4869 editor.selections.ranges(cx),
4870 [Point::new(1, 3)..Point::new(1, 3)]
4871 );
4872 editor
4873 });
4874
4875 multibuffer.update(cx, |multibuffer, cx| {
4876 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
4877 });
4878 editor.update(cx, |editor, cx| {
4879 assert_eq!(
4880 editor.selections.ranges(cx),
4881 [Point::new(0, 0)..Point::new(0, 0)]
4882 );
4883
4884 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
4885 editor.change_selections(None, cx, |s| s.refresh());
4886 assert_eq!(
4887 editor.selections.ranges(cx),
4888 [Point::new(0, 3)..Point::new(0, 3)]
4889 );
4890 assert!(editor.selections.pending_anchor().is_some());
4891 });
4892}
4893
4894#[gpui::test]
4895async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
4896 cx.update(|cx| cx.set_global(Settings::test(cx)));
4897 let language = Arc::new(
4898 Language::new(
4899 LanguageConfig {
4900 brackets: vec![
4901 BracketPair {
4902 start: "{".to_string(),
4903 end: "}".to_string(),
4904 close: true,
4905 newline: true,
4906 },
4907 BracketPair {
4908 start: "/* ".to_string(),
4909 end: " */".to_string(),
4910 close: true,
4911 newline: true,
4912 },
4913 ],
4914 ..Default::default()
4915 },
4916 Some(tree_sitter_rust::language()),
4917 )
4918 .with_indents_query("")
4919 .unwrap(),
4920 );
4921
4922 let text = concat!(
4923 "{ }\n", //
4924 " x\n", //
4925 " /* */\n", //
4926 "x\n", //
4927 "{{} }\n", //
4928 );
4929
4930 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
4931 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
4932 let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
4933 view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
4934 .await;
4935
4936 view.update(cx, |view, cx| {
4937 view.change_selections(None, cx, |s| {
4938 s.select_display_ranges([
4939 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3),
4940 DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
4941 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
4942 ])
4943 });
4944 view.newline(&Newline, cx);
4945
4946 assert_eq!(
4947 view.buffer().read(cx).read(cx).text(),
4948 concat!(
4949 "{ \n", // Suppress rustfmt
4950 "\n", //
4951 "}\n", //
4952 " x\n", //
4953 " /* \n", //
4954 " \n", //
4955 " */\n", //
4956 "x\n", //
4957 "{{} \n", //
4958 "}\n", //
4959 )
4960 );
4961 });
4962}
4963
4964#[gpui::test]
4965fn test_highlighted_ranges(cx: &mut gpui::MutableAppContext) {
4966 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
4967
4968 cx.set_global(Settings::test(cx));
4969 let (_, editor) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
4970
4971 editor.update(cx, |editor, cx| {
4972 struct Type1;
4973 struct Type2;
4974
4975 let buffer = buffer.read(cx).snapshot(cx);
4976
4977 let anchor_range =
4978 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
4979
4980 editor.highlight_background::<Type1>(
4981 vec![
4982 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
4983 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
4984 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
4985 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
4986 ],
4987 |_| Color::red(),
4988 cx,
4989 );
4990 editor.highlight_background::<Type2>(
4991 vec![
4992 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
4993 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
4994 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
4995 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
4996 ],
4997 |_| Color::green(),
4998 cx,
4999 );
5000
5001 let snapshot = editor.snapshot(cx);
5002 let mut highlighted_ranges = editor.background_highlights_in_range(
5003 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
5004 &snapshot,
5005 cx.global::<Settings>().theme.as_ref(),
5006 );
5007 // Enforce a consistent ordering based on color without relying on the ordering of the
5008 // highlight's `TypeId` which is non-deterministic.
5009 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
5010 assert_eq!(
5011 highlighted_ranges,
5012 &[
5013 (
5014 DisplayPoint::new(3, 2)..DisplayPoint::new(3, 5),
5015 Color::green(),
5016 ),
5017 (
5018 DisplayPoint::new(5, 3)..DisplayPoint::new(5, 6),
5019 Color::green(),
5020 ),
5021 (
5022 DisplayPoint::new(4, 2)..DisplayPoint::new(4, 4),
5023 Color::red(),
5024 ),
5025 (
5026 DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
5027 Color::red(),
5028 ),
5029 ]
5030 );
5031 assert_eq!(
5032 editor.background_highlights_in_range(
5033 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
5034 &snapshot,
5035 cx.global::<Settings>().theme.as_ref(),
5036 ),
5037 &[(
5038 DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
5039 Color::red(),
5040 )]
5041 );
5042 });
5043}
5044
5045#[gpui::test]
5046async fn test_following(cx: &mut gpui::TestAppContext) {
5047 Settings::test_async(cx);
5048 let fs = FakeFs::new(cx.background());
5049 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
5050
5051 let buffer = project.update(cx, |project, cx| {
5052 let buffer = project
5053 .create_buffer(&sample_text(16, 8, 'a'), None, cx)
5054 .unwrap();
5055 cx.add_model(|cx| MultiBuffer::singleton(buffer, cx))
5056 });
5057 let (_, leader) = cx.add_window(|cx| build_editor(buffer.clone(), cx));
5058 let (_, follower) = cx.update(|cx| {
5059 cx.add_window(
5060 WindowOptions {
5061 bounds: WindowBounds::Fixed(RectF::from_points(vec2f(0., 0.), vec2f(10., 80.))),
5062 ..Default::default()
5063 },
5064 |cx| build_editor(buffer.clone(), cx),
5065 )
5066 });
5067
5068 let is_still_following = Rc::new(RefCell::new(true));
5069 let pending_update = Rc::new(RefCell::new(None));
5070 follower.update(cx, {
5071 let update = pending_update.clone();
5072 let is_still_following = is_still_following.clone();
5073 |_, cx| {
5074 cx.subscribe(&leader, move |_, leader, event, cx| {
5075 leader
5076 .read(cx)
5077 .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx);
5078 })
5079 .detach();
5080
5081 cx.subscribe(&follower, move |_, _, event, cx| {
5082 if Editor::should_unfollow_on_event(event, cx) {
5083 *is_still_following.borrow_mut() = false;
5084 }
5085 })
5086 .detach();
5087 }
5088 });
5089
5090 // Update the selections only
5091 leader.update(cx, |leader, cx| {
5092 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
5093 });
5094 follower
5095 .update(cx, |follower, cx| {
5096 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
5097 })
5098 .await
5099 .unwrap();
5100 follower.read_with(cx, |follower, cx| {
5101 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
5102 });
5103 assert_eq!(*is_still_following.borrow(), true);
5104
5105 // Update the scroll position only
5106 leader.update(cx, |leader, cx| {
5107 leader.set_scroll_position(vec2f(1.5, 3.5), cx);
5108 });
5109 follower
5110 .update(cx, |follower, cx| {
5111 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
5112 })
5113 .await
5114 .unwrap();
5115 assert_eq!(
5116 follower.update(cx, |follower, cx| follower.scroll_position(cx)),
5117 vec2f(1.5, 3.5)
5118 );
5119 assert_eq!(*is_still_following.borrow(), true);
5120
5121 // Update the selections and scroll position. The follower's scroll position is updated
5122 // via autoscroll, not via the leader's exact scroll position.
5123 leader.update(cx, |leader, cx| {
5124 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
5125 leader.request_autoscroll(Autoscroll::newest(), cx);
5126 leader.set_scroll_position(vec2f(1.5, 3.5), cx);
5127 });
5128 follower
5129 .update(cx, |follower, cx| {
5130 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
5131 })
5132 .await
5133 .unwrap();
5134 follower.update(cx, |follower, cx| {
5135 assert_eq!(follower.scroll_position(cx), vec2f(1.5, 0.0));
5136 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
5137 });
5138 assert_eq!(*is_still_following.borrow(), true);
5139
5140 // Creating a pending selection that precedes another selection
5141 leader.update(cx, |leader, cx| {
5142 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
5143 leader.begin_selection(DisplayPoint::new(0, 0), true, 1, cx);
5144 });
5145 follower
5146 .update(cx, |follower, cx| {
5147 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
5148 })
5149 .await
5150 .unwrap();
5151 follower.read_with(cx, |follower, cx| {
5152 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
5153 });
5154 assert_eq!(*is_still_following.borrow(), true);
5155
5156 // Extend the pending selection so that it surrounds another selection
5157 leader.update(cx, |leader, cx| {
5158 leader.extend_selection(DisplayPoint::new(0, 2), 1, cx);
5159 });
5160 follower
5161 .update(cx, |follower, cx| {
5162 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
5163 })
5164 .await
5165 .unwrap();
5166 follower.read_with(cx, |follower, cx| {
5167 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
5168 });
5169
5170 // Scrolling locally breaks the follow
5171 follower.update(cx, |follower, cx| {
5172 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
5173 follower.set_scroll_anchor(
5174 ScrollAnchor {
5175 top_anchor,
5176 offset: vec2f(0.0, 0.5),
5177 },
5178 cx,
5179 );
5180 });
5181 assert_eq!(*is_still_following.borrow(), false);
5182}
5183
5184#[gpui::test]
5185async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
5186 Settings::test_async(cx);
5187 let fs = FakeFs::new(cx.background());
5188 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
5189 let (_, pane) = cx.add_window(|cx| Pane::new(None, cx));
5190
5191 let leader = pane.update(cx, |_, cx| {
5192 let multibuffer = cx.add_model(|_| MultiBuffer::new(0));
5193 cx.add_view(|cx| build_editor(multibuffer.clone(), cx))
5194 });
5195
5196 // Start following the editor when it has no excerpts.
5197 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
5198 let follower_1 = cx
5199 .update(|cx| {
5200 Editor::from_state_proto(
5201 pane.clone(),
5202 project.clone(),
5203 ViewId {
5204 creator: Default::default(),
5205 id: 0,
5206 },
5207 &mut state_message,
5208 cx,
5209 )
5210 })
5211 .unwrap()
5212 .await
5213 .unwrap();
5214
5215 let update_message = Rc::new(RefCell::new(None));
5216 follower_1.update(cx, {
5217 let update = update_message.clone();
5218 |_, cx| {
5219 cx.subscribe(&leader, move |_, leader, event, cx| {
5220 leader
5221 .read(cx)
5222 .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx);
5223 })
5224 .detach();
5225 }
5226 });
5227
5228 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
5229 (
5230 project
5231 .create_buffer("abc\ndef\nghi\njkl\n", None, cx)
5232 .unwrap(),
5233 project
5234 .create_buffer("mno\npqr\nstu\nvwx\n", None, cx)
5235 .unwrap(),
5236 )
5237 });
5238
5239 // Insert some excerpts.
5240 leader.update(cx, |leader, cx| {
5241 leader.buffer.update(cx, |multibuffer, cx| {
5242 let excerpt_ids = multibuffer.push_excerpts(
5243 buffer_1.clone(),
5244 [
5245 ExcerptRange {
5246 context: 1..6,
5247 primary: None,
5248 },
5249 ExcerptRange {
5250 context: 12..15,
5251 primary: None,
5252 },
5253 ExcerptRange {
5254 context: 0..3,
5255 primary: None,
5256 },
5257 ],
5258 cx,
5259 );
5260 multibuffer.insert_excerpts_after(
5261 excerpt_ids[0],
5262 buffer_2.clone(),
5263 [
5264 ExcerptRange {
5265 context: 8..12,
5266 primary: None,
5267 },
5268 ExcerptRange {
5269 context: 0..6,
5270 primary: None,
5271 },
5272 ],
5273 cx,
5274 );
5275 });
5276 });
5277
5278 // Apply the update of adding the excerpts.
5279 follower_1
5280 .update(cx, |follower, cx| {
5281 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
5282 })
5283 .await
5284 .unwrap();
5285 assert_eq!(
5286 follower_1.read_with(cx, Editor::text),
5287 leader.read_with(cx, Editor::text)
5288 );
5289 update_message.borrow_mut().take();
5290
5291 // Start following separately after it already has excerpts.
5292 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
5293 let follower_2 = cx
5294 .update(|cx| {
5295 Editor::from_state_proto(
5296 pane.clone(),
5297 project.clone(),
5298 ViewId {
5299 creator: Default::default(),
5300 id: 0,
5301 },
5302 &mut state_message,
5303 cx,
5304 )
5305 })
5306 .unwrap()
5307 .await
5308 .unwrap();
5309 assert_eq!(
5310 follower_2.read_with(cx, Editor::text),
5311 leader.read_with(cx, Editor::text)
5312 );
5313
5314 // Remove some excerpts.
5315 leader.update(cx, |leader, cx| {
5316 leader.buffer.update(cx, |multibuffer, cx| {
5317 let excerpt_ids = multibuffer.excerpt_ids();
5318 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
5319 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
5320 });
5321 });
5322
5323 // Apply the update of removing the excerpts.
5324 follower_1
5325 .update(cx, |follower, cx| {
5326 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
5327 })
5328 .await
5329 .unwrap();
5330 follower_2
5331 .update(cx, |follower, cx| {
5332 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
5333 })
5334 .await
5335 .unwrap();
5336 update_message.borrow_mut().take();
5337 assert_eq!(
5338 follower_1.read_with(cx, Editor::text),
5339 leader.read_with(cx, Editor::text)
5340 );
5341}
5342
5343#[test]
5344fn test_combine_syntax_and_fuzzy_match_highlights() {
5345 let string = "abcdefghijklmnop";
5346 let syntax_ranges = [
5347 (
5348 0..3,
5349 HighlightStyle {
5350 color: Some(Color::red()),
5351 ..Default::default()
5352 },
5353 ),
5354 (
5355 4..8,
5356 HighlightStyle {
5357 color: Some(Color::green()),
5358 ..Default::default()
5359 },
5360 ),
5361 ];
5362 let match_indices = [4, 6, 7, 8];
5363 assert_eq!(
5364 combine_syntax_and_fuzzy_match_highlights(
5365 string,
5366 Default::default(),
5367 syntax_ranges.into_iter(),
5368 &match_indices,
5369 ),
5370 &[
5371 (
5372 0..3,
5373 HighlightStyle {
5374 color: Some(Color::red()),
5375 ..Default::default()
5376 },
5377 ),
5378 (
5379 4..5,
5380 HighlightStyle {
5381 color: Some(Color::green()),
5382 weight: Some(fonts::Weight::BOLD),
5383 ..Default::default()
5384 },
5385 ),
5386 (
5387 5..6,
5388 HighlightStyle {
5389 color: Some(Color::green()),
5390 ..Default::default()
5391 },
5392 ),
5393 (
5394 6..8,
5395 HighlightStyle {
5396 color: Some(Color::green()),
5397 weight: Some(fonts::Weight::BOLD),
5398 ..Default::default()
5399 },
5400 ),
5401 (
5402 8..9,
5403 HighlightStyle {
5404 weight: Some(fonts::Weight::BOLD),
5405 ..Default::default()
5406 },
5407 ),
5408 ]
5409 );
5410}
5411
5412#[gpui::test]
5413async fn go_to_hunk(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
5414 let mut cx = EditorTestContext::new(cx);
5415
5416 let diff_base = r#"
5417 use some::mod;
5418
5419 const A: u32 = 42;
5420
5421 fn main() {
5422 println!("hello");
5423
5424 println!("world");
5425 }
5426 "#
5427 .unindent();
5428
5429 // Edits are modified, removed, modified, added
5430 cx.set_state(
5431 &r#"
5432 use some::modified;
5433
5434 ˇ
5435 fn main() {
5436 println!("hello there");
5437
5438 println!("around the");
5439 println!("world");
5440 }
5441 "#
5442 .unindent(),
5443 );
5444
5445 cx.set_diff_base(Some(&diff_base));
5446 deterministic.run_until_parked();
5447
5448 cx.update_editor(|editor, cx| {
5449 //Wrap around the bottom of the buffer
5450 for _ in 0..3 {
5451 editor.go_to_hunk(&GoToHunk, cx);
5452 }
5453 });
5454
5455 cx.assert_editor_state(
5456 &r#"
5457 ˇuse some::modified;
5458
5459
5460 fn main() {
5461 println!("hello there");
5462
5463 println!("around the");
5464 println!("world");
5465 }
5466 "#
5467 .unindent(),
5468 );
5469
5470 cx.update_editor(|editor, cx| {
5471 //Wrap around the top of the buffer
5472 for _ in 0..2 {
5473 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
5474 }
5475 });
5476
5477 cx.assert_editor_state(
5478 &r#"
5479 use some::modified;
5480
5481
5482 fn main() {
5483 ˇ println!("hello there");
5484
5485 println!("around the");
5486 println!("world");
5487 }
5488 "#
5489 .unindent(),
5490 );
5491
5492 cx.update_editor(|editor, cx| {
5493 editor.fold(&Fold, cx);
5494
5495 //Make sure that the fold only gets one hunk
5496 for _ in 0..4 {
5497 editor.go_to_hunk(&GoToHunk, cx);
5498 }
5499 });
5500
5501 cx.assert_editor_state(
5502 &r#"
5503 ˇuse some::modified;
5504
5505
5506 fn main() {
5507 println!("hello there");
5508
5509 println!("around the");
5510 println!("world");
5511 }
5512 "#
5513 .unindent(),
5514 );
5515}
5516
5517#[test]
5518fn test_split_words() {
5519 fn split<'a>(text: &'a str) -> Vec<&'a str> {
5520 split_words(text).collect()
5521 }
5522
5523 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
5524 assert_eq!(split("hello_world"), &["hello_", "world"]);
5525 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
5526 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
5527 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
5528 assert_eq!(split("helloworld"), &["helloworld"]);
5529}
5530
5531#[gpui::test]
5532async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
5533 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
5534 let mut assert = |before, after| {
5535 let _state_context = cx.set_state(before);
5536 cx.update_editor(|editor, cx| {
5537 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
5538 });
5539 cx.assert_editor_state(after);
5540 };
5541
5542 // Outside bracket jumps to outside of matching bracket
5543 assert("console.logˇ(var);", "console.log(var)ˇ;");
5544 assert("console.log(var)ˇ;", "console.logˇ(var);");
5545
5546 // Inside bracket jumps to inside of matching bracket
5547 assert("console.log(ˇvar);", "console.log(varˇ);");
5548 assert("console.log(varˇ);", "console.log(ˇvar);");
5549
5550 // When outside a bracket and inside, favor jumping to the inside bracket
5551 assert(
5552 "console.log('foo', [1, 2, 3]ˇ);",
5553 "console.log(ˇ'foo', [1, 2, 3]);",
5554 );
5555 assert(
5556 "console.log(ˇ'foo', [1, 2, 3]);",
5557 "console.log('foo', [1, 2, 3]ˇ);",
5558 );
5559
5560 // Bias forward if two options are equally likely
5561 assert(
5562 "let result = curried_fun()ˇ();",
5563 "let result = curried_fun()()ˇ;",
5564 );
5565
5566 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
5567 assert(
5568 indoc! {"
5569 function test() {
5570 console.log('test')ˇ
5571 }"},
5572 indoc! {"
5573 function test() {
5574 console.logˇ('test')
5575 }"},
5576 );
5577}
5578
5579fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
5580 let point = DisplayPoint::new(row as u32, column as u32);
5581 point..point
5582}
5583
5584fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
5585 let (text, ranges) = marked_text_ranges(marked_text, true);
5586 assert_eq!(view.text(cx), text);
5587 assert_eq!(
5588 view.selections.ranges(cx),
5589 ranges,
5590 "Assert selections are {}",
5591 marked_text
5592 );
5593}