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![BracketPair {
3456 start: "{".to_string(),
3457 end: "}".to_string(),
3458 close: true,
3459 newline: true,
3460 }],
3461 ..Default::default()
3462 },
3463 Some(tree_sitter_rust::language()),
3464 ));
3465
3466 let text = r#"
3467 a
3468 b
3469 c
3470 "#
3471 .unindent();
3472
3473 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
3474 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
3475 let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
3476 view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
3477 .await;
3478
3479 view.update(cx, |view, cx| {
3480 view.change_selections(None, cx, |s| {
3481 s.select_display_ranges([
3482 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
3483 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
3484 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1),
3485 ])
3486 });
3487
3488 view.handle_input("{", cx);
3489 view.handle_input("{", cx);
3490 view.handle_input("{", cx);
3491 assert_eq!(
3492 view.text(cx),
3493 "
3494 {{{a}}}
3495 {{{b}}}
3496 {{{c}}}
3497 "
3498 .unindent()
3499 );
3500 assert_eq!(
3501 view.selections.display_ranges(cx),
3502 [
3503 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 4),
3504 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 4),
3505 DisplayPoint::new(2, 3)..DisplayPoint::new(2, 4)
3506 ]
3507 );
3508
3509 view.undo(&Undo, cx);
3510 view.undo(&Undo, cx);
3511 view.undo(&Undo, cx);
3512 assert_eq!(
3513 view.text(cx),
3514 "
3515 a
3516 b
3517 c
3518 "
3519 .unindent()
3520 );
3521 assert_eq!(
3522 view.selections.display_ranges(cx),
3523 [
3524 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
3525 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
3526 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1)
3527 ]
3528 );
3529 });
3530}
3531
3532#[gpui::test]
3533async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
3534 cx.update(|cx| cx.set_global(Settings::test(cx)));
3535 let language = Arc::new(Language::new(
3536 LanguageConfig {
3537 brackets: vec![BracketPair {
3538 start: "{".to_string(),
3539 end: "}".to_string(),
3540 close: true,
3541 newline: true,
3542 }],
3543 autoclose_before: "}".to_string(),
3544 ..Default::default()
3545 },
3546 Some(tree_sitter_rust::language()),
3547 ));
3548
3549 let text = r#"
3550 a
3551 b
3552 c
3553 "#
3554 .unindent();
3555
3556 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
3557 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
3558 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
3559 editor
3560 .condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
3561 .await;
3562
3563 editor.update(cx, |editor, cx| {
3564 editor.change_selections(None, cx, |s| {
3565 s.select_ranges([
3566 Point::new(0, 1)..Point::new(0, 1),
3567 Point::new(1, 1)..Point::new(1, 1),
3568 Point::new(2, 1)..Point::new(2, 1),
3569 ])
3570 });
3571
3572 editor.handle_input("{", cx);
3573 editor.handle_input("{", cx);
3574 editor.handle_input("_", cx);
3575 assert_eq!(
3576 editor.text(cx),
3577 "
3578 a{{_}}
3579 b{{_}}
3580 c{{_}}
3581 "
3582 .unindent()
3583 );
3584 assert_eq!(
3585 editor.selections.ranges::<Point>(cx),
3586 [
3587 Point::new(0, 4)..Point::new(0, 4),
3588 Point::new(1, 4)..Point::new(1, 4),
3589 Point::new(2, 4)..Point::new(2, 4)
3590 ]
3591 );
3592
3593 editor.backspace(&Default::default(), cx);
3594 editor.backspace(&Default::default(), cx);
3595 assert_eq!(
3596 editor.text(cx),
3597 "
3598 a{}
3599 b{}
3600 c{}
3601 "
3602 .unindent()
3603 );
3604 assert_eq!(
3605 editor.selections.ranges::<Point>(cx),
3606 [
3607 Point::new(0, 2)..Point::new(0, 2),
3608 Point::new(1, 2)..Point::new(1, 2),
3609 Point::new(2, 2)..Point::new(2, 2)
3610 ]
3611 );
3612
3613 editor.delete_to_previous_word_start(&Default::default(), cx);
3614 assert_eq!(
3615 editor.text(cx),
3616 "
3617 a
3618 b
3619 c
3620 "
3621 .unindent()
3622 );
3623 assert_eq!(
3624 editor.selections.ranges::<Point>(cx),
3625 [
3626 Point::new(0, 1)..Point::new(0, 1),
3627 Point::new(1, 1)..Point::new(1, 1),
3628 Point::new(2, 1)..Point::new(2, 1)
3629 ]
3630 );
3631 });
3632}
3633
3634#[gpui::test]
3635async fn test_snippets(cx: &mut gpui::TestAppContext) {
3636 cx.update(|cx| cx.set_global(Settings::test(cx)));
3637
3638 let (text, insertion_ranges) = marked_text_ranges(
3639 indoc! {"
3640 a.ˇ b
3641 a.ˇ b
3642 a.ˇ b
3643 "},
3644 false,
3645 );
3646
3647 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
3648 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
3649
3650 editor.update(cx, |editor, cx| {
3651 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
3652
3653 editor
3654 .insert_snippet(&insertion_ranges, snippet, cx)
3655 .unwrap();
3656
3657 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
3658 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
3659 assert_eq!(editor.text(cx), expected_text);
3660 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
3661 }
3662
3663 assert(
3664 editor,
3665 cx,
3666 indoc! {"
3667 a.f(«one», two, «three») b
3668 a.f(«one», two, «three») b
3669 a.f(«one», two, «three») b
3670 "},
3671 );
3672
3673 // Can't move earlier than the first tab stop
3674 assert!(!editor.move_to_prev_snippet_tabstop(cx));
3675 assert(
3676 editor,
3677 cx,
3678 indoc! {"
3679 a.f(«one», two, «three») b
3680 a.f(«one», two, «three») b
3681 a.f(«one», two, «three») b
3682 "},
3683 );
3684
3685 assert!(editor.move_to_next_snippet_tabstop(cx));
3686 assert(
3687 editor,
3688 cx,
3689 indoc! {"
3690 a.f(one, «two», three) b
3691 a.f(one, «two», three) b
3692 a.f(one, «two», three) b
3693 "},
3694 );
3695
3696 editor.move_to_prev_snippet_tabstop(cx);
3697 assert(
3698 editor,
3699 cx,
3700 indoc! {"
3701 a.f(«one», two, «three») b
3702 a.f(«one», two, «three») b
3703 a.f(«one», two, «three») b
3704 "},
3705 );
3706
3707 assert!(editor.move_to_next_snippet_tabstop(cx));
3708 assert(
3709 editor,
3710 cx,
3711 indoc! {"
3712 a.f(one, «two», three) b
3713 a.f(one, «two», three) b
3714 a.f(one, «two», three) b
3715 "},
3716 );
3717 assert!(editor.move_to_next_snippet_tabstop(cx));
3718 assert(
3719 editor,
3720 cx,
3721 indoc! {"
3722 a.f(one, two, three)ˇ b
3723 a.f(one, two, three)ˇ b
3724 a.f(one, two, three)ˇ b
3725 "},
3726 );
3727
3728 // As soon as the last tab stop is reached, snippet state is gone
3729 editor.move_to_prev_snippet_tabstop(cx);
3730 assert(
3731 editor,
3732 cx,
3733 indoc! {"
3734 a.f(one, two, three)ˇ b
3735 a.f(one, two, three)ˇ b
3736 a.f(one, two, three)ˇ b
3737 "},
3738 );
3739 });
3740}
3741
3742#[gpui::test]
3743async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
3744 cx.foreground().forbid_parking();
3745
3746 let mut language = Language::new(
3747 LanguageConfig {
3748 name: "Rust".into(),
3749 path_suffixes: vec!["rs".to_string()],
3750 ..Default::default()
3751 },
3752 Some(tree_sitter_rust::language()),
3753 );
3754 let mut fake_servers = language
3755 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
3756 capabilities: lsp::ServerCapabilities {
3757 document_formatting_provider: Some(lsp::OneOf::Left(true)),
3758 ..Default::default()
3759 },
3760 ..Default::default()
3761 }))
3762 .await;
3763
3764 let fs = FakeFs::new(cx.background());
3765 fs.insert_file("/file.rs", Default::default()).await;
3766
3767 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
3768 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
3769 let buffer = project
3770 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
3771 .await
3772 .unwrap();
3773
3774 cx.foreground().start_waiting();
3775 let fake_server = fake_servers.next().await.unwrap();
3776
3777 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
3778 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
3779 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
3780 assert!(cx.read(|cx| editor.is_dirty(cx)));
3781
3782 let save = cx.update(|cx| editor.save(project.clone(), cx));
3783 fake_server
3784 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
3785 assert_eq!(
3786 params.text_document.uri,
3787 lsp::Url::from_file_path("/file.rs").unwrap()
3788 );
3789 assert_eq!(params.options.tab_size, 4);
3790 Ok(Some(vec![lsp::TextEdit::new(
3791 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
3792 ", ".to_string(),
3793 )]))
3794 })
3795 .next()
3796 .await;
3797 cx.foreground().start_waiting();
3798 save.await.unwrap();
3799 assert_eq!(
3800 editor.read_with(cx, |editor, cx| editor.text(cx)),
3801 "one, two\nthree\n"
3802 );
3803 assert!(!cx.read(|cx| editor.is_dirty(cx)));
3804
3805 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
3806 assert!(cx.read(|cx| editor.is_dirty(cx)));
3807
3808 // Ensure we can still save even if formatting hangs.
3809 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
3810 assert_eq!(
3811 params.text_document.uri,
3812 lsp::Url::from_file_path("/file.rs").unwrap()
3813 );
3814 futures::future::pending::<()>().await;
3815 unreachable!()
3816 });
3817 let save = cx.update(|cx| editor.save(project.clone(), cx));
3818 cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
3819 cx.foreground().start_waiting();
3820 save.await.unwrap();
3821 assert_eq!(
3822 editor.read_with(cx, |editor, cx| editor.text(cx)),
3823 "one\ntwo\nthree\n"
3824 );
3825 assert!(!cx.read(|cx| editor.is_dirty(cx)));
3826
3827 // Set rust language override and assert overriden tabsize is sent to language server
3828 cx.update(|cx| {
3829 cx.update_global::<Settings, _, _>(|settings, _| {
3830 settings.language_overrides.insert(
3831 "Rust".into(),
3832 EditorSettings {
3833 tab_size: Some(8.try_into().unwrap()),
3834 ..Default::default()
3835 },
3836 );
3837 })
3838 });
3839
3840 let save = cx.update(|cx| editor.save(project.clone(), cx));
3841 fake_server
3842 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
3843 assert_eq!(
3844 params.text_document.uri,
3845 lsp::Url::from_file_path("/file.rs").unwrap()
3846 );
3847 assert_eq!(params.options.tab_size, 8);
3848 Ok(Some(vec![]))
3849 })
3850 .next()
3851 .await;
3852 cx.foreground().start_waiting();
3853 save.await.unwrap();
3854}
3855
3856#[gpui::test]
3857async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
3858 cx.foreground().forbid_parking();
3859
3860 let mut language = Language::new(
3861 LanguageConfig {
3862 name: "Rust".into(),
3863 path_suffixes: vec!["rs".to_string()],
3864 ..Default::default()
3865 },
3866 Some(tree_sitter_rust::language()),
3867 );
3868 let mut fake_servers = language
3869 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
3870 capabilities: lsp::ServerCapabilities {
3871 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
3872 ..Default::default()
3873 },
3874 ..Default::default()
3875 }))
3876 .await;
3877
3878 let fs = FakeFs::new(cx.background());
3879 fs.insert_file("/file.rs", Default::default()).await;
3880
3881 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
3882 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
3883 let buffer = project
3884 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
3885 .await
3886 .unwrap();
3887
3888 cx.foreground().start_waiting();
3889 let fake_server = fake_servers.next().await.unwrap();
3890
3891 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
3892 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
3893 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
3894 assert!(cx.read(|cx| editor.is_dirty(cx)));
3895
3896 let save = cx.update(|cx| editor.save(project.clone(), cx));
3897 fake_server
3898 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
3899 assert_eq!(
3900 params.text_document.uri,
3901 lsp::Url::from_file_path("/file.rs").unwrap()
3902 );
3903 assert_eq!(params.options.tab_size, 4);
3904 Ok(Some(vec![lsp::TextEdit::new(
3905 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
3906 ", ".to_string(),
3907 )]))
3908 })
3909 .next()
3910 .await;
3911 cx.foreground().start_waiting();
3912 save.await.unwrap();
3913 assert_eq!(
3914 editor.read_with(cx, |editor, cx| editor.text(cx)),
3915 "one, two\nthree\n"
3916 );
3917 assert!(!cx.read(|cx| editor.is_dirty(cx)));
3918
3919 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
3920 assert!(cx.read(|cx| editor.is_dirty(cx)));
3921
3922 // Ensure we can still save even if formatting hangs.
3923 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
3924 move |params, _| async move {
3925 assert_eq!(
3926 params.text_document.uri,
3927 lsp::Url::from_file_path("/file.rs").unwrap()
3928 );
3929 futures::future::pending::<()>().await;
3930 unreachable!()
3931 },
3932 );
3933 let save = cx.update(|cx| editor.save(project.clone(), cx));
3934 cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
3935 cx.foreground().start_waiting();
3936 save.await.unwrap();
3937 assert_eq!(
3938 editor.read_with(cx, |editor, cx| editor.text(cx)),
3939 "one\ntwo\nthree\n"
3940 );
3941 assert!(!cx.read(|cx| editor.is_dirty(cx)));
3942
3943 // Set rust language override and assert overriden tabsize is sent to language server
3944 cx.update(|cx| {
3945 cx.update_global::<Settings, _, _>(|settings, _| {
3946 settings.language_overrides.insert(
3947 "Rust".into(),
3948 EditorSettings {
3949 tab_size: Some(8.try_into().unwrap()),
3950 ..Default::default()
3951 },
3952 );
3953 })
3954 });
3955
3956 let save = cx.update(|cx| editor.save(project.clone(), cx));
3957 fake_server
3958 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
3959 assert_eq!(
3960 params.text_document.uri,
3961 lsp::Url::from_file_path("/file.rs").unwrap()
3962 );
3963 assert_eq!(params.options.tab_size, 8);
3964 Ok(Some(vec![]))
3965 })
3966 .next()
3967 .await;
3968 cx.foreground().start_waiting();
3969 save.await.unwrap();
3970}
3971
3972#[gpui::test]
3973async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
3974 cx.foreground().forbid_parking();
3975
3976 let mut language = Language::new(
3977 LanguageConfig {
3978 name: "Rust".into(),
3979 path_suffixes: vec!["rs".to_string()],
3980 ..Default::default()
3981 },
3982 Some(tree_sitter_rust::language()),
3983 );
3984 let mut fake_servers = language
3985 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
3986 capabilities: lsp::ServerCapabilities {
3987 document_formatting_provider: Some(lsp::OneOf::Left(true)),
3988 ..Default::default()
3989 },
3990 ..Default::default()
3991 }))
3992 .await;
3993
3994 let fs = FakeFs::new(cx.background());
3995 fs.insert_file("/file.rs", Default::default()).await;
3996
3997 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
3998 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
3999 let buffer = project
4000 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
4001 .await
4002 .unwrap();
4003
4004 cx.foreground().start_waiting();
4005 let fake_server = fake_servers.next().await.unwrap();
4006
4007 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
4008 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
4009 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
4010
4011 let format = editor.update(cx, |editor, cx| editor.perform_format(project.clone(), cx));
4012 fake_server
4013 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
4014 assert_eq!(
4015 params.text_document.uri,
4016 lsp::Url::from_file_path("/file.rs").unwrap()
4017 );
4018 assert_eq!(params.options.tab_size, 4);
4019 Ok(Some(vec![lsp::TextEdit::new(
4020 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
4021 ", ".to_string(),
4022 )]))
4023 })
4024 .next()
4025 .await;
4026 cx.foreground().start_waiting();
4027 format.await.unwrap();
4028 assert_eq!(
4029 editor.read_with(cx, |editor, cx| editor.text(cx)),
4030 "one, two\nthree\n"
4031 );
4032
4033 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
4034 // Ensure we don't lock if formatting hangs.
4035 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
4036 assert_eq!(
4037 params.text_document.uri,
4038 lsp::Url::from_file_path("/file.rs").unwrap()
4039 );
4040 futures::future::pending::<()>().await;
4041 unreachable!()
4042 });
4043 let format = editor.update(cx, |editor, cx| editor.perform_format(project, cx));
4044 cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
4045 cx.foreground().start_waiting();
4046 format.await.unwrap();
4047 assert_eq!(
4048 editor.read_with(cx, |editor, cx| editor.text(cx)),
4049 "one\ntwo\nthree\n"
4050 );
4051}
4052
4053#[gpui::test]
4054async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
4055 cx.foreground().forbid_parking();
4056
4057 let mut cx = EditorLspTestContext::new_rust(
4058 lsp::ServerCapabilities {
4059 document_formatting_provider: Some(lsp::OneOf::Left(true)),
4060 ..Default::default()
4061 },
4062 cx,
4063 )
4064 .await;
4065
4066 cx.set_state(indoc! {"
4067 one.twoˇ
4068 "});
4069
4070 // The format request takes a long time. When it completes, it inserts
4071 // a newline and an indent before the `.`
4072 cx.lsp
4073 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
4074 let executor = cx.background();
4075 async move {
4076 executor.timer(Duration::from_millis(100)).await;
4077 Ok(Some(vec![lsp::TextEdit {
4078 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
4079 new_text: "\n ".into(),
4080 }]))
4081 }
4082 });
4083
4084 // Submit a format request.
4085 let format_1 = cx
4086 .update_editor(|editor, cx| editor.format(&Format, cx))
4087 .unwrap();
4088 cx.foreground().run_until_parked();
4089
4090 // Submit a second format request.
4091 let format_2 = cx
4092 .update_editor(|editor, cx| editor.format(&Format, cx))
4093 .unwrap();
4094 cx.foreground().run_until_parked();
4095
4096 // Wait for both format requests to complete
4097 cx.foreground().advance_clock(Duration::from_millis(200));
4098 cx.foreground().start_waiting();
4099 format_1.await.unwrap();
4100 cx.foreground().start_waiting();
4101 format_2.await.unwrap();
4102
4103 // The formatting edits only happens once.
4104 cx.assert_editor_state(indoc! {"
4105 one
4106 .twoˇ
4107 "});
4108}
4109
4110#[gpui::test]
4111async fn test_completion(cx: &mut gpui::TestAppContext) {
4112 let mut cx = EditorLspTestContext::new_rust(
4113 lsp::ServerCapabilities {
4114 completion_provider: Some(lsp::CompletionOptions {
4115 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
4116 ..Default::default()
4117 }),
4118 ..Default::default()
4119 },
4120 cx,
4121 )
4122 .await;
4123
4124 cx.set_state(indoc! {"
4125 oneˇ
4126 two
4127 three
4128 "});
4129 cx.simulate_keystroke(".");
4130 handle_completion_request(
4131 &mut cx,
4132 indoc! {"
4133 one.|<>
4134 two
4135 three
4136 "},
4137 vec!["first_completion", "second_completion"],
4138 )
4139 .await;
4140 cx.condition(|editor, _| editor.context_menu_visible())
4141 .await;
4142 let apply_additional_edits = cx.update_editor(|editor, cx| {
4143 editor.move_down(&MoveDown, cx);
4144 editor
4145 .confirm_completion(&ConfirmCompletion::default(), cx)
4146 .unwrap()
4147 });
4148 cx.assert_editor_state(indoc! {"
4149 one.second_completionˇ
4150 two
4151 three
4152 "});
4153
4154 handle_resolve_completion_request(
4155 &mut cx,
4156 Some(vec![
4157 (
4158 //This overlaps with the primary completion edit which is
4159 //misbehavior from the LSP spec, test that we filter it out
4160 indoc! {"
4161 one.second_ˇcompletion
4162 two
4163 threeˇ
4164 "},
4165 "overlapping aditional edit",
4166 ),
4167 (
4168 indoc! {"
4169 one.second_completion
4170 two
4171 threeˇ
4172 "},
4173 "\nadditional edit",
4174 ),
4175 ]),
4176 )
4177 .await;
4178 apply_additional_edits.await.unwrap();
4179 cx.assert_editor_state(indoc! {"
4180 one.second_completionˇ
4181 two
4182 three
4183 additional edit
4184 "});
4185
4186 cx.set_state(indoc! {"
4187 one.second_completion
4188 twoˇ
4189 threeˇ
4190 additional edit
4191 "});
4192 cx.simulate_keystroke(" ");
4193 assert!(cx.editor(|e, _| e.context_menu.is_none()));
4194 cx.simulate_keystroke("s");
4195 assert!(cx.editor(|e, _| e.context_menu.is_none()));
4196
4197 cx.assert_editor_state(indoc! {"
4198 one.second_completion
4199 two sˇ
4200 three sˇ
4201 additional edit
4202 "});
4203 handle_completion_request(
4204 &mut cx,
4205 indoc! {"
4206 one.second_completion
4207 two s
4208 three <s|>
4209 additional edit
4210 "},
4211 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
4212 )
4213 .await;
4214 cx.condition(|editor, _| editor.context_menu_visible())
4215 .await;
4216
4217 cx.simulate_keystroke("i");
4218
4219 handle_completion_request(
4220 &mut cx,
4221 indoc! {"
4222 one.second_completion
4223 two si
4224 three <si|>
4225 additional edit
4226 "},
4227 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
4228 )
4229 .await;
4230 cx.condition(|editor, _| editor.context_menu_visible())
4231 .await;
4232
4233 let apply_additional_edits = cx.update_editor(|editor, cx| {
4234 editor
4235 .confirm_completion(&ConfirmCompletion::default(), cx)
4236 .unwrap()
4237 });
4238 cx.assert_editor_state(indoc! {"
4239 one.second_completion
4240 two sixth_completionˇ
4241 three sixth_completionˇ
4242 additional edit
4243 "});
4244
4245 handle_resolve_completion_request(&mut cx, None).await;
4246 apply_additional_edits.await.unwrap();
4247
4248 cx.update(|cx| {
4249 cx.update_global::<Settings, _, _>(|settings, _| {
4250 settings.show_completions_on_input = false;
4251 })
4252 });
4253 cx.set_state("editorˇ");
4254 cx.simulate_keystroke(".");
4255 assert!(cx.editor(|e, _| e.context_menu.is_none()));
4256 cx.simulate_keystroke("c");
4257 cx.simulate_keystroke("l");
4258 cx.simulate_keystroke("o");
4259 cx.assert_editor_state("editor.cloˇ");
4260 assert!(cx.editor(|e, _| e.context_menu.is_none()));
4261 cx.update_editor(|editor, cx| {
4262 editor.show_completions(&ShowCompletions, cx);
4263 });
4264 handle_completion_request(&mut cx, "editor.<clo|>", vec!["close", "clobber"]).await;
4265 cx.condition(|editor, _| editor.context_menu_visible())
4266 .await;
4267 let apply_additional_edits = cx.update_editor(|editor, cx| {
4268 editor
4269 .confirm_completion(&ConfirmCompletion::default(), cx)
4270 .unwrap()
4271 });
4272 cx.assert_editor_state("editor.closeˇ");
4273 handle_resolve_completion_request(&mut cx, None).await;
4274 apply_additional_edits.await.unwrap();
4275
4276 // Handle completion request passing a marked string specifying where the completion
4277 // should be triggered from using '|' character, what range should be replaced, and what completions
4278 // should be returned using '<' and '>' to delimit the range
4279 async fn handle_completion_request<'a>(
4280 cx: &mut EditorLspTestContext<'a>,
4281 marked_string: &str,
4282 completions: Vec<&'static str>,
4283 ) {
4284 let complete_from_marker: TextRangeMarker = '|'.into();
4285 let replace_range_marker: TextRangeMarker = ('<', '>').into();
4286 let (_, mut marked_ranges) = marked_text_ranges_by(
4287 marked_string,
4288 vec![complete_from_marker.clone(), replace_range_marker.clone()],
4289 );
4290
4291 let complete_from_position =
4292 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
4293 let replace_range =
4294 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
4295
4296 cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
4297 let completions = completions.clone();
4298 async move {
4299 assert_eq!(params.text_document_position.text_document.uri, url.clone());
4300 assert_eq!(
4301 params.text_document_position.position,
4302 complete_from_position
4303 );
4304 Ok(Some(lsp::CompletionResponse::Array(
4305 completions
4306 .iter()
4307 .map(|completion_text| lsp::CompletionItem {
4308 label: completion_text.to_string(),
4309 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
4310 range: replace_range,
4311 new_text: completion_text.to_string(),
4312 })),
4313 ..Default::default()
4314 })
4315 .collect(),
4316 )))
4317 }
4318 })
4319 .next()
4320 .await;
4321 }
4322
4323 async fn handle_resolve_completion_request<'a>(
4324 cx: &mut EditorLspTestContext<'a>,
4325 edits: Option<Vec<(&'static str, &'static str)>>,
4326 ) {
4327 let edits = edits.map(|edits| {
4328 edits
4329 .iter()
4330 .map(|(marked_string, new_text)| {
4331 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
4332 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
4333 lsp::TextEdit::new(replace_range, new_text.to_string())
4334 })
4335 .collect::<Vec<_>>()
4336 });
4337
4338 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
4339 let edits = edits.clone();
4340 async move {
4341 Ok(lsp::CompletionItem {
4342 additional_text_edits: edits,
4343 ..Default::default()
4344 })
4345 }
4346 })
4347 .next()
4348 .await;
4349 }
4350}
4351
4352#[gpui::test]
4353async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
4354 cx.update(|cx| cx.set_global(Settings::test(cx)));
4355 let language = Arc::new(Language::new(
4356 LanguageConfig {
4357 line_comment: Some("// ".into()),
4358 ..Default::default()
4359 },
4360 Some(tree_sitter_rust::language()),
4361 ));
4362
4363 let text = "
4364 fn a() {
4365 //b();
4366 // c();
4367 // d();
4368 }
4369 "
4370 .unindent();
4371
4372 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
4373 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
4374 let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
4375
4376 view.update(cx, |editor, cx| {
4377 // If multiple selections intersect a line, the line is only
4378 // toggled once.
4379 editor.change_selections(None, cx, |s| {
4380 s.select_display_ranges([
4381 DisplayPoint::new(1, 3)..DisplayPoint::new(2, 3),
4382 DisplayPoint::new(3, 5)..DisplayPoint::new(3, 6),
4383 ])
4384 });
4385 editor.toggle_comments(&ToggleComments, cx);
4386 assert_eq!(
4387 editor.text(cx),
4388 "
4389 fn a() {
4390 b();
4391 c();
4392 d();
4393 }
4394 "
4395 .unindent()
4396 );
4397
4398 // The comment prefix is inserted at the same column for every line
4399 // in a selection.
4400 editor.change_selections(None, cx, |s| {
4401 s.select_display_ranges([DisplayPoint::new(1, 3)..DisplayPoint::new(3, 6)])
4402 });
4403 editor.toggle_comments(&ToggleComments, cx);
4404 assert_eq!(
4405 editor.text(cx),
4406 "
4407 fn a() {
4408 // b();
4409 // c();
4410 // d();
4411 }
4412 "
4413 .unindent()
4414 );
4415
4416 // If a selection ends at the beginning of a line, that line is not toggled.
4417 editor.change_selections(None, cx, |s| {
4418 s.select_display_ranges([DisplayPoint::new(2, 0)..DisplayPoint::new(3, 0)])
4419 });
4420 editor.toggle_comments(&ToggleComments, cx);
4421 assert_eq!(
4422 editor.text(cx),
4423 "
4424 fn a() {
4425 // b();
4426 c();
4427 // d();
4428 }
4429 "
4430 .unindent()
4431 );
4432 });
4433}
4434
4435#[gpui::test]
4436async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
4437 let mut cx = EditorTestContext::new(cx);
4438
4439 let html_language = Arc::new(
4440 Language::new(
4441 LanguageConfig {
4442 name: "HTML".into(),
4443 block_comment: Some(("<!-- ".into(), " -->".into())),
4444 ..Default::default()
4445 },
4446 Some(tree_sitter_html::language()),
4447 )
4448 .with_injection_query(
4449 r#"
4450 (script_element
4451 (raw_text) @content
4452 (#set! "language" "javascript"))
4453 "#,
4454 )
4455 .unwrap(),
4456 );
4457
4458 let javascript_language = Arc::new(Language::new(
4459 LanguageConfig {
4460 name: "JavaScript".into(),
4461 line_comment: Some("// ".into()),
4462 ..Default::default()
4463 },
4464 Some(tree_sitter_javascript::language()),
4465 ));
4466
4467 let registry = Arc::new(LanguageRegistry::test());
4468 registry.add(html_language.clone());
4469 registry.add(javascript_language.clone());
4470
4471 cx.update_buffer(|buffer, cx| {
4472 buffer.set_language_registry(registry);
4473 buffer.set_language(Some(html_language), cx);
4474 });
4475
4476 // Toggle comments for empty selections
4477 cx.set_state(
4478 &r#"
4479 <p>A</p>ˇ
4480 <p>B</p>ˇ
4481 <p>C</p>ˇ
4482 "#
4483 .unindent(),
4484 );
4485 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments, cx));
4486 cx.assert_editor_state(
4487 &r#"
4488 <!-- <p>A</p>ˇ -->
4489 <!-- <p>B</p>ˇ -->
4490 <!-- <p>C</p>ˇ -->
4491 "#
4492 .unindent(),
4493 );
4494 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments, cx));
4495 cx.assert_editor_state(
4496 &r#"
4497 <p>A</p>ˇ
4498 <p>B</p>ˇ
4499 <p>C</p>ˇ
4500 "#
4501 .unindent(),
4502 );
4503
4504 // Toggle comments for mixture of empty and non-empty selections, where
4505 // multiple selections occupy a given line.
4506 cx.set_state(
4507 &r#"
4508 <p>A«</p>
4509 <p>ˇ»B</p>ˇ
4510 <p>C«</p>
4511 <p>ˇ»D</p>ˇ
4512 "#
4513 .unindent(),
4514 );
4515
4516 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments, cx));
4517 cx.assert_editor_state(
4518 &r#"
4519 <!-- <p>A«</p>
4520 <p>ˇ»B</p>ˇ -->
4521 <!-- <p>C«</p>
4522 <p>ˇ»D</p>ˇ -->
4523 "#
4524 .unindent(),
4525 );
4526 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments, cx));
4527 cx.assert_editor_state(
4528 &r#"
4529 <p>A«</p>
4530 <p>ˇ»B</p>ˇ
4531 <p>C«</p>
4532 <p>ˇ»D</p>ˇ
4533 "#
4534 .unindent(),
4535 );
4536
4537 // Toggle comments when different languages are active for different
4538 // selections.
4539 cx.set_state(
4540 &r#"
4541 ˇ<script>
4542 ˇvar x = new Y();
4543 ˇ</script>
4544 "#
4545 .unindent(),
4546 );
4547 cx.foreground().run_until_parked();
4548 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments, cx));
4549 cx.assert_editor_state(
4550 &r#"
4551 <!-- ˇ<script> -->
4552 // ˇvar x = new Y();
4553 <!-- ˇ</script> -->
4554 "#
4555 .unindent(),
4556 );
4557}
4558
4559#[gpui::test]
4560fn test_editing_disjoint_excerpts(cx: &mut gpui::MutableAppContext) {
4561 cx.set_global(Settings::test(cx));
4562 let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
4563 let multibuffer = cx.add_model(|cx| {
4564 let mut multibuffer = MultiBuffer::new(0);
4565 multibuffer.push_excerpts(
4566 buffer.clone(),
4567 [
4568 ExcerptRange {
4569 context: Point::new(0, 0)..Point::new(0, 4),
4570 primary: None,
4571 },
4572 ExcerptRange {
4573 context: Point::new(1, 0)..Point::new(1, 4),
4574 primary: None,
4575 },
4576 ],
4577 cx,
4578 );
4579 multibuffer
4580 });
4581
4582 assert_eq!(multibuffer.read(cx).read(cx).text(), "aaaa\nbbbb");
4583
4584 let (_, view) = cx.add_window(Default::default(), |cx| build_editor(multibuffer, cx));
4585 view.update(cx, |view, cx| {
4586 assert_eq!(view.text(cx), "aaaa\nbbbb");
4587 view.change_selections(None, cx, |s| {
4588 s.select_ranges([
4589 Point::new(0, 0)..Point::new(0, 0),
4590 Point::new(1, 0)..Point::new(1, 0),
4591 ])
4592 });
4593
4594 view.handle_input("X", cx);
4595 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
4596 assert_eq!(
4597 view.selections.ranges(cx),
4598 [
4599 Point::new(0, 1)..Point::new(0, 1),
4600 Point::new(1, 1)..Point::new(1, 1),
4601 ]
4602 )
4603 });
4604}
4605
4606#[gpui::test]
4607fn test_editing_overlapping_excerpts(cx: &mut gpui::MutableAppContext) {
4608 cx.set_global(Settings::test(cx));
4609 let markers = vec![('[', ']').into(), ('(', ')').into()];
4610 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
4611 indoc! {"
4612 [aaaa
4613 (bbbb]
4614 cccc)",
4615 },
4616 markers.clone(),
4617 );
4618 let excerpt_ranges = markers.into_iter().map(|marker| {
4619 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
4620 ExcerptRange {
4621 context,
4622 primary: None,
4623 }
4624 });
4625 let buffer = cx.add_model(|cx| Buffer::new(0, initial_text, cx));
4626 let multibuffer = cx.add_model(|cx| {
4627 let mut multibuffer = MultiBuffer::new(0);
4628 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
4629 multibuffer
4630 });
4631
4632 let (_, view) = cx.add_window(Default::default(), |cx| build_editor(multibuffer, cx));
4633 view.update(cx, |view, cx| {
4634 let (expected_text, selection_ranges) = marked_text_ranges(
4635 indoc! {"
4636 aaaa
4637 bˇbbb
4638 bˇbbˇb
4639 cccc"
4640 },
4641 true,
4642 );
4643 assert_eq!(view.text(cx), expected_text);
4644 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
4645
4646 view.handle_input("X", cx);
4647
4648 let (expected_text, expected_selections) = marked_text_ranges(
4649 indoc! {"
4650 aaaa
4651 bXˇbbXb
4652 bXˇbbXˇb
4653 cccc"
4654 },
4655 false,
4656 );
4657 assert_eq!(view.text(cx), expected_text);
4658 assert_eq!(view.selections.ranges(cx), expected_selections);
4659
4660 view.newline(&Newline, cx);
4661 let (expected_text, expected_selections) = marked_text_ranges(
4662 indoc! {"
4663 aaaa
4664 bX
4665 ˇbbX
4666 b
4667 bX
4668 ˇbbX
4669 ˇb
4670 cccc"
4671 },
4672 false,
4673 );
4674 assert_eq!(view.text(cx), expected_text);
4675 assert_eq!(view.selections.ranges(cx), expected_selections);
4676 });
4677}
4678
4679#[gpui::test]
4680fn test_refresh_selections(cx: &mut gpui::MutableAppContext) {
4681 cx.set_global(Settings::test(cx));
4682 let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
4683 let mut excerpt1_id = None;
4684 let multibuffer = cx.add_model(|cx| {
4685 let mut multibuffer = MultiBuffer::new(0);
4686 excerpt1_id = multibuffer
4687 .push_excerpts(
4688 buffer.clone(),
4689 [
4690 ExcerptRange {
4691 context: Point::new(0, 0)..Point::new(1, 4),
4692 primary: None,
4693 },
4694 ExcerptRange {
4695 context: Point::new(1, 0)..Point::new(2, 4),
4696 primary: None,
4697 },
4698 ],
4699 cx,
4700 )
4701 .into_iter()
4702 .next();
4703 multibuffer
4704 });
4705 assert_eq!(
4706 multibuffer.read(cx).read(cx).text(),
4707 "aaaa\nbbbb\nbbbb\ncccc"
4708 );
4709 let (_, editor) = cx.add_window(Default::default(), |cx| {
4710 let mut editor = build_editor(multibuffer.clone(), cx);
4711 let snapshot = editor.snapshot(cx);
4712 editor.change_selections(None, cx, |s| {
4713 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
4714 });
4715 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
4716 assert_eq!(
4717 editor.selections.ranges(cx),
4718 [
4719 Point::new(1, 3)..Point::new(1, 3),
4720 Point::new(2, 1)..Point::new(2, 1),
4721 ]
4722 );
4723 editor
4724 });
4725
4726 // Refreshing selections is a no-op when excerpts haven't changed.
4727 editor.update(cx, |editor, cx| {
4728 editor.change_selections(None, cx, |s| s.refresh());
4729 assert_eq!(
4730 editor.selections.ranges(cx),
4731 [
4732 Point::new(1, 3)..Point::new(1, 3),
4733 Point::new(2, 1)..Point::new(2, 1),
4734 ]
4735 );
4736 });
4737
4738 multibuffer.update(cx, |multibuffer, cx| {
4739 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
4740 });
4741 editor.update(cx, |editor, cx| {
4742 // Removing an excerpt causes the first selection to become degenerate.
4743 assert_eq!(
4744 editor.selections.ranges(cx),
4745 [
4746 Point::new(0, 0)..Point::new(0, 0),
4747 Point::new(0, 1)..Point::new(0, 1)
4748 ]
4749 );
4750
4751 // Refreshing selections will relocate the first selection to the original buffer
4752 // location.
4753 editor.change_selections(None, cx, |s| s.refresh());
4754 assert_eq!(
4755 editor.selections.ranges(cx),
4756 [
4757 Point::new(0, 1)..Point::new(0, 1),
4758 Point::new(0, 3)..Point::new(0, 3)
4759 ]
4760 );
4761 assert!(editor.selections.pending_anchor().is_some());
4762 });
4763}
4764
4765#[gpui::test]
4766fn test_refresh_selections_while_selecting_with_mouse(cx: &mut gpui::MutableAppContext) {
4767 cx.set_global(Settings::test(cx));
4768 let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
4769 let mut excerpt1_id = None;
4770 let multibuffer = cx.add_model(|cx| {
4771 let mut multibuffer = MultiBuffer::new(0);
4772 excerpt1_id = multibuffer
4773 .push_excerpts(
4774 buffer.clone(),
4775 [
4776 ExcerptRange {
4777 context: Point::new(0, 0)..Point::new(1, 4),
4778 primary: None,
4779 },
4780 ExcerptRange {
4781 context: Point::new(1, 0)..Point::new(2, 4),
4782 primary: None,
4783 },
4784 ],
4785 cx,
4786 )
4787 .into_iter()
4788 .next();
4789 multibuffer
4790 });
4791 assert_eq!(
4792 multibuffer.read(cx).read(cx).text(),
4793 "aaaa\nbbbb\nbbbb\ncccc"
4794 );
4795 let (_, editor) = cx.add_window(Default::default(), |cx| {
4796 let mut editor = build_editor(multibuffer.clone(), cx);
4797 let snapshot = editor.snapshot(cx);
4798 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
4799 assert_eq!(
4800 editor.selections.ranges(cx),
4801 [Point::new(1, 3)..Point::new(1, 3)]
4802 );
4803 editor
4804 });
4805
4806 multibuffer.update(cx, |multibuffer, cx| {
4807 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
4808 });
4809 editor.update(cx, |editor, cx| {
4810 assert_eq!(
4811 editor.selections.ranges(cx),
4812 [Point::new(0, 0)..Point::new(0, 0)]
4813 );
4814
4815 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
4816 editor.change_selections(None, cx, |s| s.refresh());
4817 assert_eq!(
4818 editor.selections.ranges(cx),
4819 [Point::new(0, 3)..Point::new(0, 3)]
4820 );
4821 assert!(editor.selections.pending_anchor().is_some());
4822 });
4823}
4824
4825#[gpui::test]
4826async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
4827 cx.update(|cx| cx.set_global(Settings::test(cx)));
4828 let language = Arc::new(
4829 Language::new(
4830 LanguageConfig {
4831 brackets: vec![
4832 BracketPair {
4833 start: "{".to_string(),
4834 end: "}".to_string(),
4835 close: true,
4836 newline: true,
4837 },
4838 BracketPair {
4839 start: "/* ".to_string(),
4840 end: " */".to_string(),
4841 close: true,
4842 newline: true,
4843 },
4844 ],
4845 ..Default::default()
4846 },
4847 Some(tree_sitter_rust::language()),
4848 )
4849 .with_indents_query("")
4850 .unwrap(),
4851 );
4852
4853 let text = concat!(
4854 "{ }\n", //
4855 " x\n", //
4856 " /* */\n", //
4857 "x\n", //
4858 "{{} }\n", //
4859 );
4860
4861 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
4862 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
4863 let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
4864 view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
4865 .await;
4866
4867 view.update(cx, |view, cx| {
4868 view.change_selections(None, cx, |s| {
4869 s.select_display_ranges([
4870 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3),
4871 DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
4872 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
4873 ])
4874 });
4875 view.newline(&Newline, cx);
4876
4877 assert_eq!(
4878 view.buffer().read(cx).read(cx).text(),
4879 concat!(
4880 "{ \n", // Suppress rustfmt
4881 "\n", //
4882 "}\n", //
4883 " x\n", //
4884 " /* \n", //
4885 " \n", //
4886 " */\n", //
4887 "x\n", //
4888 "{{} \n", //
4889 "}\n", //
4890 )
4891 );
4892 });
4893}
4894
4895#[gpui::test]
4896fn test_highlighted_ranges(cx: &mut gpui::MutableAppContext) {
4897 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
4898
4899 cx.set_global(Settings::test(cx));
4900 let (_, editor) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
4901
4902 editor.update(cx, |editor, cx| {
4903 struct Type1;
4904 struct Type2;
4905
4906 let buffer = buffer.read(cx).snapshot(cx);
4907
4908 let anchor_range =
4909 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
4910
4911 editor.highlight_background::<Type1>(
4912 vec![
4913 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
4914 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
4915 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
4916 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
4917 ],
4918 |_| Color::red(),
4919 cx,
4920 );
4921 editor.highlight_background::<Type2>(
4922 vec![
4923 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
4924 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
4925 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
4926 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
4927 ],
4928 |_| Color::green(),
4929 cx,
4930 );
4931
4932 let snapshot = editor.snapshot(cx);
4933 let mut highlighted_ranges = editor.background_highlights_in_range(
4934 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
4935 &snapshot,
4936 cx.global::<Settings>().theme.as_ref(),
4937 );
4938 // Enforce a consistent ordering based on color without relying on the ordering of the
4939 // highlight's `TypeId` which is non-deterministic.
4940 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
4941 assert_eq!(
4942 highlighted_ranges,
4943 &[
4944 (
4945 DisplayPoint::new(3, 2)..DisplayPoint::new(3, 5),
4946 Color::green(),
4947 ),
4948 (
4949 DisplayPoint::new(5, 3)..DisplayPoint::new(5, 6),
4950 Color::green(),
4951 ),
4952 (
4953 DisplayPoint::new(4, 2)..DisplayPoint::new(4, 4),
4954 Color::red(),
4955 ),
4956 (
4957 DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
4958 Color::red(),
4959 ),
4960 ]
4961 );
4962 assert_eq!(
4963 editor.background_highlights_in_range(
4964 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
4965 &snapshot,
4966 cx.global::<Settings>().theme.as_ref(),
4967 ),
4968 &[(
4969 DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
4970 Color::red(),
4971 )]
4972 );
4973 });
4974}
4975
4976#[gpui::test]
4977async fn test_following(cx: &mut gpui::TestAppContext) {
4978 Settings::test_async(cx);
4979 let fs = FakeFs::new(cx.background());
4980 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
4981
4982 let buffer = project.update(cx, |project, cx| {
4983 let buffer = project
4984 .create_buffer(&sample_text(16, 8, 'a'), None, cx)
4985 .unwrap();
4986 cx.add_model(|cx| MultiBuffer::singleton(buffer, cx))
4987 });
4988 let (_, leader) = cx.add_window(|cx| build_editor(buffer.clone(), cx));
4989 let (_, follower) = cx.update(|cx| {
4990 cx.add_window(
4991 WindowOptions {
4992 bounds: WindowBounds::Fixed(RectF::from_points(vec2f(0., 0.), vec2f(10., 80.))),
4993 ..Default::default()
4994 },
4995 |cx| build_editor(buffer.clone(), cx),
4996 )
4997 });
4998
4999 let is_still_following = Rc::new(RefCell::new(true));
5000 let pending_update = Rc::new(RefCell::new(None));
5001 follower.update(cx, {
5002 let update = pending_update.clone();
5003 let is_still_following = is_still_following.clone();
5004 |_, cx| {
5005 cx.subscribe(&leader, move |_, leader, event, cx| {
5006 leader
5007 .read(cx)
5008 .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx);
5009 })
5010 .detach();
5011
5012 cx.subscribe(&follower, move |_, _, event, cx| {
5013 if Editor::should_unfollow_on_event(event, cx) {
5014 *is_still_following.borrow_mut() = false;
5015 }
5016 })
5017 .detach();
5018 }
5019 });
5020
5021 // Update the selections only
5022 leader.update(cx, |leader, cx| {
5023 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
5024 });
5025 follower
5026 .update(cx, |follower, cx| {
5027 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
5028 })
5029 .await
5030 .unwrap();
5031 follower.read_with(cx, |follower, cx| {
5032 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
5033 });
5034 assert_eq!(*is_still_following.borrow(), true);
5035
5036 // Update the scroll position only
5037 leader.update(cx, |leader, cx| {
5038 leader.set_scroll_position(vec2f(1.5, 3.5), cx);
5039 });
5040 follower
5041 .update(cx, |follower, cx| {
5042 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
5043 })
5044 .await
5045 .unwrap();
5046 assert_eq!(
5047 follower.update(cx, |follower, cx| follower.scroll_position(cx)),
5048 vec2f(1.5, 3.5)
5049 );
5050 assert_eq!(*is_still_following.borrow(), true);
5051
5052 // Update the selections and scroll position. The follower's scroll position is updated
5053 // via autoscroll, not via the leader's exact scroll position.
5054 leader.update(cx, |leader, cx| {
5055 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
5056 leader.request_autoscroll(Autoscroll::newest(), cx);
5057 leader.set_scroll_position(vec2f(1.5, 3.5), cx);
5058 });
5059 follower
5060 .update(cx, |follower, cx| {
5061 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
5062 })
5063 .await
5064 .unwrap();
5065 follower.update(cx, |follower, cx| {
5066 assert_eq!(follower.scroll_position(cx), vec2f(1.5, 0.0));
5067 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
5068 });
5069 assert_eq!(*is_still_following.borrow(), true);
5070
5071 // Creating a pending selection that precedes another selection
5072 leader.update(cx, |leader, cx| {
5073 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
5074 leader.begin_selection(DisplayPoint::new(0, 0), true, 1, cx);
5075 });
5076 follower
5077 .update(cx, |follower, cx| {
5078 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
5079 })
5080 .await
5081 .unwrap();
5082 follower.read_with(cx, |follower, cx| {
5083 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
5084 });
5085 assert_eq!(*is_still_following.borrow(), true);
5086
5087 // Extend the pending selection so that it surrounds another selection
5088 leader.update(cx, |leader, cx| {
5089 leader.extend_selection(DisplayPoint::new(0, 2), 1, cx);
5090 });
5091 follower
5092 .update(cx, |follower, cx| {
5093 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
5094 })
5095 .await
5096 .unwrap();
5097 follower.read_with(cx, |follower, cx| {
5098 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
5099 });
5100
5101 // Scrolling locally breaks the follow
5102 follower.update(cx, |follower, cx| {
5103 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
5104 follower.set_scroll_anchor(
5105 ScrollAnchor {
5106 top_anchor,
5107 offset: vec2f(0.0, 0.5),
5108 },
5109 cx,
5110 );
5111 });
5112 assert_eq!(*is_still_following.borrow(), false);
5113}
5114
5115#[gpui::test]
5116async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
5117 Settings::test_async(cx);
5118 let fs = FakeFs::new(cx.background());
5119 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
5120 let (_, pane) = cx.add_window(|cx| Pane::new(None, cx));
5121
5122 let leader = pane.update(cx, |_, cx| {
5123 let multibuffer = cx.add_model(|_| MultiBuffer::new(0));
5124 cx.add_view(|cx| build_editor(multibuffer.clone(), cx))
5125 });
5126
5127 // Start following the editor when it has no excerpts.
5128 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
5129 let follower_1 = cx
5130 .update(|cx| {
5131 Editor::from_state_proto(
5132 pane.clone(),
5133 project.clone(),
5134 ViewId {
5135 creator: Default::default(),
5136 id: 0,
5137 },
5138 &mut state_message,
5139 cx,
5140 )
5141 })
5142 .unwrap()
5143 .await
5144 .unwrap();
5145
5146 let update_message = Rc::new(RefCell::new(None));
5147 follower_1.update(cx, {
5148 let update = update_message.clone();
5149 |_, cx| {
5150 cx.subscribe(&leader, move |_, leader, event, cx| {
5151 leader
5152 .read(cx)
5153 .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx);
5154 })
5155 .detach();
5156 }
5157 });
5158
5159 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
5160 (
5161 project
5162 .create_buffer("abc\ndef\nghi\njkl\n", None, cx)
5163 .unwrap(),
5164 project
5165 .create_buffer("mno\npqr\nstu\nvwx\n", None, cx)
5166 .unwrap(),
5167 )
5168 });
5169
5170 // Insert some excerpts.
5171 leader.update(cx, |leader, cx| {
5172 leader.buffer.update(cx, |multibuffer, cx| {
5173 let excerpt_ids = multibuffer.push_excerpts(
5174 buffer_1.clone(),
5175 [
5176 ExcerptRange {
5177 context: 1..6,
5178 primary: None,
5179 },
5180 ExcerptRange {
5181 context: 12..15,
5182 primary: None,
5183 },
5184 ExcerptRange {
5185 context: 0..3,
5186 primary: None,
5187 },
5188 ],
5189 cx,
5190 );
5191 multibuffer.insert_excerpts_after(
5192 excerpt_ids[0],
5193 buffer_2.clone(),
5194 [
5195 ExcerptRange {
5196 context: 8..12,
5197 primary: None,
5198 },
5199 ExcerptRange {
5200 context: 0..6,
5201 primary: None,
5202 },
5203 ],
5204 cx,
5205 );
5206 });
5207 });
5208
5209 // Apply the update of adding the excerpts.
5210 follower_1
5211 .update(cx, |follower, cx| {
5212 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
5213 })
5214 .await
5215 .unwrap();
5216 assert_eq!(
5217 follower_1.read_with(cx, Editor::text),
5218 leader.read_with(cx, Editor::text)
5219 );
5220 update_message.borrow_mut().take();
5221
5222 // Start following separately after it already has excerpts.
5223 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
5224 let follower_2 = cx
5225 .update(|cx| {
5226 Editor::from_state_proto(
5227 pane.clone(),
5228 project.clone(),
5229 ViewId {
5230 creator: Default::default(),
5231 id: 0,
5232 },
5233 &mut state_message,
5234 cx,
5235 )
5236 })
5237 .unwrap()
5238 .await
5239 .unwrap();
5240 assert_eq!(
5241 follower_2.read_with(cx, Editor::text),
5242 leader.read_with(cx, Editor::text)
5243 );
5244
5245 // Remove some excerpts.
5246 leader.update(cx, |leader, cx| {
5247 leader.buffer.update(cx, |multibuffer, cx| {
5248 let excerpt_ids = multibuffer.excerpt_ids();
5249 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
5250 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
5251 });
5252 });
5253
5254 // Apply the update of removing the excerpts.
5255 follower_1
5256 .update(cx, |follower, cx| {
5257 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
5258 })
5259 .await
5260 .unwrap();
5261 follower_2
5262 .update(cx, |follower, cx| {
5263 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
5264 })
5265 .await
5266 .unwrap();
5267 update_message.borrow_mut().take();
5268 assert_eq!(
5269 follower_1.read_with(cx, Editor::text),
5270 leader.read_with(cx, Editor::text)
5271 );
5272}
5273
5274#[test]
5275fn test_combine_syntax_and_fuzzy_match_highlights() {
5276 let string = "abcdefghijklmnop";
5277 let syntax_ranges = [
5278 (
5279 0..3,
5280 HighlightStyle {
5281 color: Some(Color::red()),
5282 ..Default::default()
5283 },
5284 ),
5285 (
5286 4..8,
5287 HighlightStyle {
5288 color: Some(Color::green()),
5289 ..Default::default()
5290 },
5291 ),
5292 ];
5293 let match_indices = [4, 6, 7, 8];
5294 assert_eq!(
5295 combine_syntax_and_fuzzy_match_highlights(
5296 string,
5297 Default::default(),
5298 syntax_ranges.into_iter(),
5299 &match_indices,
5300 ),
5301 &[
5302 (
5303 0..3,
5304 HighlightStyle {
5305 color: Some(Color::red()),
5306 ..Default::default()
5307 },
5308 ),
5309 (
5310 4..5,
5311 HighlightStyle {
5312 color: Some(Color::green()),
5313 weight: Some(fonts::Weight::BOLD),
5314 ..Default::default()
5315 },
5316 ),
5317 (
5318 5..6,
5319 HighlightStyle {
5320 color: Some(Color::green()),
5321 ..Default::default()
5322 },
5323 ),
5324 (
5325 6..8,
5326 HighlightStyle {
5327 color: Some(Color::green()),
5328 weight: Some(fonts::Weight::BOLD),
5329 ..Default::default()
5330 },
5331 ),
5332 (
5333 8..9,
5334 HighlightStyle {
5335 weight: Some(fonts::Weight::BOLD),
5336 ..Default::default()
5337 },
5338 ),
5339 ]
5340 );
5341}
5342
5343#[gpui::test]
5344async fn go_to_hunk(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
5345 let mut cx = EditorTestContext::new(cx);
5346
5347 let diff_base = r#"
5348 use some::mod;
5349
5350 const A: u32 = 42;
5351
5352 fn main() {
5353 println!("hello");
5354
5355 println!("world");
5356 }
5357 "#
5358 .unindent();
5359
5360 // Edits are modified, removed, modified, added
5361 cx.set_state(
5362 &r#"
5363 use some::modified;
5364
5365 ˇ
5366 fn main() {
5367 println!("hello there");
5368
5369 println!("around the");
5370 println!("world");
5371 }
5372 "#
5373 .unindent(),
5374 );
5375
5376 cx.set_diff_base(Some(&diff_base));
5377 deterministic.run_until_parked();
5378
5379 cx.update_editor(|editor, cx| {
5380 //Wrap around the bottom of the buffer
5381 for _ in 0..3 {
5382 editor.go_to_hunk(&GoToHunk, cx);
5383 }
5384 });
5385
5386 cx.assert_editor_state(
5387 &r#"
5388 ˇuse some::modified;
5389
5390
5391 fn main() {
5392 println!("hello there");
5393
5394 println!("around the");
5395 println!("world");
5396 }
5397 "#
5398 .unindent(),
5399 );
5400
5401 cx.update_editor(|editor, cx| {
5402 //Wrap around the top of the buffer
5403 for _ in 0..2 {
5404 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
5405 }
5406 });
5407
5408 cx.assert_editor_state(
5409 &r#"
5410 use some::modified;
5411
5412
5413 fn main() {
5414 ˇ println!("hello there");
5415
5416 println!("around the");
5417 println!("world");
5418 }
5419 "#
5420 .unindent(),
5421 );
5422
5423 cx.update_editor(|editor, cx| {
5424 editor.fold(&Fold, cx);
5425
5426 //Make sure that the fold only gets one hunk
5427 for _ in 0..4 {
5428 editor.go_to_hunk(&GoToHunk, cx);
5429 }
5430 });
5431
5432 cx.assert_editor_state(
5433 &r#"
5434 ˇuse some::modified;
5435
5436
5437 fn main() {
5438 println!("hello there");
5439
5440 println!("around the");
5441 println!("world");
5442 }
5443 "#
5444 .unindent(),
5445 );
5446}
5447
5448#[test]
5449fn test_split_words() {
5450 fn split<'a>(text: &'a str) -> Vec<&'a str> {
5451 split_words(text).collect()
5452 }
5453
5454 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
5455 assert_eq!(split("hello_world"), &["hello_", "world"]);
5456 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
5457 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
5458 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
5459 assert_eq!(split("helloworld"), &["helloworld"]);
5460}
5461
5462fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
5463 let point = DisplayPoint::new(row as u32, column as u32);
5464 point..point
5465}
5466
5467fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
5468 let (text, ranges) = marked_text_ranges(marked_text, true);
5469 assert_eq!(view.text(cx), text);
5470 assert_eq!(
5471 view.selections.ranges(cx),
5472 ranges,
5473 "Assert selections are {}",
5474 marked_text
5475 );
5476}