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