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 // Don't autoclose if `close` is false for the bracket pair
3020 cx.set_state("ˇ");
3021 cx.update_editor(|view, cx| view.handle_input("[", cx));
3022 cx.assert_editor_state("[ˇ");
3023
3024 // Surround with brackets if text is selected
3025 cx.set_state("«aˇ» b");
3026 cx.update_editor(|view, cx| view.handle_input("{", cx));
3027 cx.assert_editor_state("{«aˇ»} b");
3028}
3029
3030#[gpui::test]
3031async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
3032 let mut cx = EditorTestContext::new(cx);
3033
3034 let html_language = Arc::new(
3035 Language::new(
3036 LanguageConfig {
3037 name: "HTML".into(),
3038 brackets: vec![
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 BracketPair {
3050 start: "(".into(),
3051 end: ")".into(),
3052 ..Default::default()
3053 },
3054 ],
3055 autoclose_before: "})]>".into(),
3056 ..Default::default()
3057 },
3058 Some(tree_sitter_html::language()),
3059 )
3060 .with_injection_query(
3061 r#"
3062 (script_element
3063 (raw_text) @content
3064 (#set! "language" "javascript"))
3065 "#,
3066 )
3067 .unwrap(),
3068 );
3069
3070 let javascript_language = Arc::new(Language::new(
3071 LanguageConfig {
3072 name: "JavaScript".into(),
3073 brackets: vec![
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 BracketPair {
3085 start: "(".into(),
3086 end: ")".into(),
3087 ..Default::default()
3088 },
3089 ],
3090 autoclose_before: "})]>".into(),
3091 ..Default::default()
3092 },
3093 Some(tree_sitter_javascript::language()),
3094 ));
3095
3096 let registry = Arc::new(LanguageRegistry::test());
3097 registry.add(html_language.clone());
3098 registry.add(javascript_language.clone());
3099
3100 cx.update_buffer(|buffer, cx| {
3101 buffer.set_language_registry(registry);
3102 buffer.set_language(Some(html_language), cx);
3103 });
3104
3105 cx.set_state(
3106 &r#"
3107 <body>ˇ
3108 <script>
3109 var x = 1;ˇ
3110 </script>
3111 </body>ˇ
3112 "#
3113 .unindent(),
3114 );
3115
3116 // Precondition: different languages are active at different locations.
3117 cx.update_editor(|editor, cx| {
3118 let snapshot = editor.snapshot(cx);
3119 let cursors = editor.selections.ranges::<usize>(cx);
3120 let languages = cursors
3121 .iter()
3122 .map(|c| snapshot.language_at(c.start).unwrap().name())
3123 .collect::<Vec<_>>();
3124 assert_eq!(
3125 languages,
3126 &["HTML".into(), "JavaScript".into(), "HTML".into()]
3127 );
3128 });
3129
3130 // Angle brackets autoclose in HTML, but not JavaScript.
3131 cx.update_editor(|editor, cx| {
3132 editor.handle_input("<", cx);
3133 editor.handle_input("a", cx);
3134 });
3135 cx.assert_editor_state(
3136 &r#"
3137 <body><aˇ>
3138 <script>
3139 var x = 1;<aˇ
3140 </script>
3141 </body><aˇ>
3142 "#
3143 .unindent(),
3144 );
3145
3146 // Curly braces and parens autoclose in both HTML and JavaScript.
3147 cx.update_editor(|editor, cx| {
3148 editor.handle_input(" b=", cx);
3149 editor.handle_input("{", cx);
3150 editor.handle_input("c", cx);
3151 editor.handle_input("(", cx);
3152 });
3153 cx.assert_editor_state(
3154 &r#"
3155 <body><a b={c(ˇ)}>
3156 <script>
3157 var x = 1;<a b={c(ˇ)}
3158 </script>
3159 </body><a b={c(ˇ)}>
3160 "#
3161 .unindent(),
3162 );
3163
3164 // Brackets that were already autoclosed are skipped.
3165 cx.update_editor(|editor, cx| {
3166 editor.handle_input(")", cx);
3167 editor.handle_input("d", cx);
3168 editor.handle_input("}", cx);
3169 });
3170 cx.assert_editor_state(
3171 &r#"
3172 <body><a b={c()d}ˇ>
3173 <script>
3174 var x = 1;<a b={c()d}ˇ
3175 </script>
3176 </body><a b={c()d}ˇ>
3177 "#
3178 .unindent(),
3179 );
3180 cx.update_editor(|editor, cx| {
3181 editor.handle_input(">", cx);
3182 });
3183 cx.assert_editor_state(
3184 &r#"
3185 <body><a b={c()d}>ˇ
3186 <script>
3187 var x = 1;<a b={c()d}>ˇ
3188 </script>
3189 </body><a b={c()d}>ˇ
3190 "#
3191 .unindent(),
3192 );
3193
3194 // Reset
3195 cx.set_state(
3196 &r#"
3197 <body>ˇ
3198 <script>
3199 var x = 1;ˇ
3200 </script>
3201 </body>ˇ
3202 "#
3203 .unindent(),
3204 );
3205
3206 cx.update_editor(|editor, cx| {
3207 editor.handle_input("<", cx);
3208 });
3209 cx.assert_editor_state(
3210 &r#"
3211 <body><ˇ>
3212 <script>
3213 var x = 1;<ˇ
3214 </script>
3215 </body><ˇ>
3216 "#
3217 .unindent(),
3218 );
3219
3220 // When backspacing, the closing angle brackets are removed.
3221 cx.update_editor(|editor, cx| {
3222 editor.backspace(&Backspace, cx);
3223 });
3224 cx.assert_editor_state(
3225 &r#"
3226 <body>ˇ
3227 <script>
3228 var x = 1;ˇ
3229 </script>
3230 </body>ˇ
3231 "#
3232 .unindent(),
3233 );
3234
3235 // Block comments autoclose in JavaScript, but not HTML.
3236 cx.update_editor(|editor, cx| {
3237 editor.handle_input("/", cx);
3238 editor.handle_input("*", cx);
3239 });
3240 cx.assert_editor_state(
3241 &r#"
3242 <body>/*ˇ
3243 <script>
3244 var x = 1;/*ˇ */
3245 </script>
3246 </body>/*ˇ
3247 "#
3248 .unindent(),
3249 );
3250}
3251
3252#[gpui::test]
3253async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
3254 cx.update(|cx| cx.set_global(Settings::test(cx)));
3255 let language = Arc::new(Language::new(
3256 LanguageConfig {
3257 brackets: vec![BracketPair {
3258 start: "{".to_string(),
3259 end: "}".to_string(),
3260 close: true,
3261 newline: true,
3262 }],
3263 ..Default::default()
3264 },
3265 Some(tree_sitter_rust::language()),
3266 ));
3267
3268 let text = r#"
3269 a
3270 b
3271 c
3272 "#
3273 .unindent();
3274
3275 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
3276 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
3277 let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
3278 view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
3279 .await;
3280
3281 view.update(cx, |view, cx| {
3282 view.change_selections(None, cx, |s| {
3283 s.select_display_ranges([
3284 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
3285 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
3286 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1),
3287 ])
3288 });
3289
3290 view.handle_input("{", cx);
3291 view.handle_input("{", cx);
3292 view.handle_input("{", cx);
3293 assert_eq!(
3294 view.text(cx),
3295 "
3296 {{{a}}}
3297 {{{b}}}
3298 {{{c}}}
3299 "
3300 .unindent()
3301 );
3302 assert_eq!(
3303 view.selections.display_ranges(cx),
3304 [
3305 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 4),
3306 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 4),
3307 DisplayPoint::new(2, 3)..DisplayPoint::new(2, 4)
3308 ]
3309 );
3310
3311 view.undo(&Undo, cx);
3312 assert_eq!(
3313 view.text(cx),
3314 "
3315 a
3316 b
3317 c
3318 "
3319 .unindent()
3320 );
3321 assert_eq!(
3322 view.selections.display_ranges(cx),
3323 [
3324 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
3325 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
3326 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1)
3327 ]
3328 );
3329 });
3330}
3331
3332#[gpui::test]
3333async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
3334 cx.update(|cx| cx.set_global(Settings::test(cx)));
3335 let language = Arc::new(Language::new(
3336 LanguageConfig {
3337 brackets: vec![BracketPair {
3338 start: "{".to_string(),
3339 end: "}".to_string(),
3340 close: true,
3341 newline: true,
3342 }],
3343 autoclose_before: "}".to_string(),
3344 ..Default::default()
3345 },
3346 Some(tree_sitter_rust::language()),
3347 ));
3348
3349 let text = r#"
3350 a
3351 b
3352 c
3353 "#
3354 .unindent();
3355
3356 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
3357 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
3358 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
3359 editor
3360 .condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
3361 .await;
3362
3363 editor.update(cx, |editor, cx| {
3364 editor.change_selections(None, cx, |s| {
3365 s.select_ranges([
3366 Point::new(0, 1)..Point::new(0, 1),
3367 Point::new(1, 1)..Point::new(1, 1),
3368 Point::new(2, 1)..Point::new(2, 1),
3369 ])
3370 });
3371
3372 editor.handle_input("{", cx);
3373 editor.handle_input("{", cx);
3374 editor.handle_input("_", cx);
3375 assert_eq!(
3376 editor.text(cx),
3377 "
3378 a{{_}}
3379 b{{_}}
3380 c{{_}}
3381 "
3382 .unindent()
3383 );
3384 assert_eq!(
3385 editor.selections.ranges::<Point>(cx),
3386 [
3387 Point::new(0, 4)..Point::new(0, 4),
3388 Point::new(1, 4)..Point::new(1, 4),
3389 Point::new(2, 4)..Point::new(2, 4)
3390 ]
3391 );
3392
3393 editor.backspace(&Default::default(), cx);
3394 editor.backspace(&Default::default(), cx);
3395 assert_eq!(
3396 editor.text(cx),
3397 "
3398 a{}
3399 b{}
3400 c{}
3401 "
3402 .unindent()
3403 );
3404 assert_eq!(
3405 editor.selections.ranges::<Point>(cx),
3406 [
3407 Point::new(0, 2)..Point::new(0, 2),
3408 Point::new(1, 2)..Point::new(1, 2),
3409 Point::new(2, 2)..Point::new(2, 2)
3410 ]
3411 );
3412
3413 editor.delete_to_previous_word_start(&Default::default(), cx);
3414 assert_eq!(
3415 editor.text(cx),
3416 "
3417 a
3418 b
3419 c
3420 "
3421 .unindent()
3422 );
3423 assert_eq!(
3424 editor.selections.ranges::<Point>(cx),
3425 [
3426 Point::new(0, 1)..Point::new(0, 1),
3427 Point::new(1, 1)..Point::new(1, 1),
3428 Point::new(2, 1)..Point::new(2, 1)
3429 ]
3430 );
3431 });
3432}
3433
3434#[gpui::test]
3435async fn test_snippets(cx: &mut gpui::TestAppContext) {
3436 cx.update(|cx| cx.set_global(Settings::test(cx)));
3437
3438 let (text, insertion_ranges) = marked_text_ranges(
3439 indoc! {"
3440 a.ˇ b
3441 a.ˇ b
3442 a.ˇ b
3443 "},
3444 false,
3445 );
3446
3447 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
3448 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
3449
3450 editor.update(cx, |editor, cx| {
3451 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
3452
3453 editor
3454 .insert_snippet(&insertion_ranges, snippet, cx)
3455 .unwrap();
3456
3457 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
3458 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
3459 assert_eq!(editor.text(cx), expected_text);
3460 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
3461 }
3462
3463 assert(
3464 editor,
3465 cx,
3466 indoc! {"
3467 a.f(«one», two, «three») b
3468 a.f(«one», two, «three») b
3469 a.f(«one», two, «three») b
3470 "},
3471 );
3472
3473 // Can't move earlier than the first tab stop
3474 assert!(!editor.move_to_prev_snippet_tabstop(cx));
3475 assert(
3476 editor,
3477 cx,
3478 indoc! {"
3479 a.f(«one», two, «three») b
3480 a.f(«one», two, «three») b
3481 a.f(«one», two, «three») b
3482 "},
3483 );
3484
3485 assert!(editor.move_to_next_snippet_tabstop(cx));
3486 assert(
3487 editor,
3488 cx,
3489 indoc! {"
3490 a.f(one, «two», three) b
3491 a.f(one, «two», three) b
3492 a.f(one, «two», three) b
3493 "},
3494 );
3495
3496 editor.move_to_prev_snippet_tabstop(cx);
3497 assert(
3498 editor,
3499 cx,
3500 indoc! {"
3501 a.f(«one», two, «three») b
3502 a.f(«one», two, «three») b
3503 a.f(«one», two, «three») b
3504 "},
3505 );
3506
3507 assert!(editor.move_to_next_snippet_tabstop(cx));
3508 assert(
3509 editor,
3510 cx,
3511 indoc! {"
3512 a.f(one, «two», three) b
3513 a.f(one, «two», three) b
3514 a.f(one, «two», three) b
3515 "},
3516 );
3517 assert!(editor.move_to_next_snippet_tabstop(cx));
3518 assert(
3519 editor,
3520 cx,
3521 indoc! {"
3522 a.f(one, two, three)ˇ b
3523 a.f(one, two, three)ˇ b
3524 a.f(one, two, three)ˇ b
3525 "},
3526 );
3527
3528 // As soon as the last tab stop is reached, snippet state is gone
3529 editor.move_to_prev_snippet_tabstop(cx);
3530 assert(
3531 editor,
3532 cx,
3533 indoc! {"
3534 a.f(one, two, three)ˇ b
3535 a.f(one, two, three)ˇ b
3536 a.f(one, two, three)ˇ b
3537 "},
3538 );
3539 });
3540}
3541
3542#[gpui::test]
3543async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
3544 cx.foreground().forbid_parking();
3545
3546 let mut language = Language::new(
3547 LanguageConfig {
3548 name: "Rust".into(),
3549 path_suffixes: vec!["rs".to_string()],
3550 ..Default::default()
3551 },
3552 Some(tree_sitter_rust::language()),
3553 );
3554 let mut fake_servers = language
3555 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
3556 capabilities: lsp::ServerCapabilities {
3557 document_formatting_provider: Some(lsp::OneOf::Left(true)),
3558 ..Default::default()
3559 },
3560 ..Default::default()
3561 }))
3562 .await;
3563
3564 let fs = FakeFs::new(cx.background());
3565 fs.insert_file("/file.rs", Default::default()).await;
3566
3567 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
3568 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
3569 let buffer = project
3570 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
3571 .await
3572 .unwrap();
3573
3574 cx.foreground().start_waiting();
3575 let fake_server = fake_servers.next().await.unwrap();
3576
3577 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
3578 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
3579 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
3580 assert!(cx.read(|cx| editor.is_dirty(cx)));
3581
3582 let save = cx.update(|cx| editor.save(project.clone(), cx));
3583 fake_server
3584 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
3585 assert_eq!(
3586 params.text_document.uri,
3587 lsp::Url::from_file_path("/file.rs").unwrap()
3588 );
3589 assert_eq!(params.options.tab_size, 4);
3590 Ok(Some(vec![lsp::TextEdit::new(
3591 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
3592 ", ".to_string(),
3593 )]))
3594 })
3595 .next()
3596 .await;
3597 cx.foreground().start_waiting();
3598 save.await.unwrap();
3599 assert_eq!(
3600 editor.read_with(cx, |editor, cx| editor.text(cx)),
3601 "one, two\nthree\n"
3602 );
3603 assert!(!cx.read(|cx| editor.is_dirty(cx)));
3604
3605 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
3606 assert!(cx.read(|cx| editor.is_dirty(cx)));
3607
3608 // Ensure we can still save even if formatting hangs.
3609 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
3610 assert_eq!(
3611 params.text_document.uri,
3612 lsp::Url::from_file_path("/file.rs").unwrap()
3613 );
3614 futures::future::pending::<()>().await;
3615 unreachable!()
3616 });
3617 let save = cx.update(|cx| editor.save(project.clone(), cx));
3618 cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
3619 cx.foreground().start_waiting();
3620 save.await.unwrap();
3621 assert_eq!(
3622 editor.read_with(cx, |editor, cx| editor.text(cx)),
3623 "one\ntwo\nthree\n"
3624 );
3625 assert!(!cx.read(|cx| editor.is_dirty(cx)));
3626
3627 // Set rust language override and assert overriden tabsize is sent to language server
3628 cx.update(|cx| {
3629 cx.update_global::<Settings, _, _>(|settings, _| {
3630 settings.language_overrides.insert(
3631 "Rust".into(),
3632 EditorSettings {
3633 tab_size: Some(8.try_into().unwrap()),
3634 ..Default::default()
3635 },
3636 );
3637 })
3638 });
3639
3640 let save = cx.update(|cx| editor.save(project.clone(), cx));
3641 fake_server
3642 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
3643 assert_eq!(
3644 params.text_document.uri,
3645 lsp::Url::from_file_path("/file.rs").unwrap()
3646 );
3647 assert_eq!(params.options.tab_size, 8);
3648 Ok(Some(vec![]))
3649 })
3650 .next()
3651 .await;
3652 cx.foreground().start_waiting();
3653 save.await.unwrap();
3654}
3655
3656#[gpui::test]
3657async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
3658 cx.foreground().forbid_parking();
3659
3660 let mut language = Language::new(
3661 LanguageConfig {
3662 name: "Rust".into(),
3663 path_suffixes: vec!["rs".to_string()],
3664 ..Default::default()
3665 },
3666 Some(tree_sitter_rust::language()),
3667 );
3668 let mut fake_servers = language
3669 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
3670 capabilities: lsp::ServerCapabilities {
3671 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
3672 ..Default::default()
3673 },
3674 ..Default::default()
3675 }))
3676 .await;
3677
3678 let fs = FakeFs::new(cx.background());
3679 fs.insert_file("/file.rs", Default::default()).await;
3680
3681 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
3682 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
3683 let buffer = project
3684 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
3685 .await
3686 .unwrap();
3687
3688 cx.foreground().start_waiting();
3689 let fake_server = fake_servers.next().await.unwrap();
3690
3691 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
3692 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
3693 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
3694 assert!(cx.read(|cx| editor.is_dirty(cx)));
3695
3696 let save = cx.update(|cx| editor.save(project.clone(), cx));
3697 fake_server
3698 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
3699 assert_eq!(
3700 params.text_document.uri,
3701 lsp::Url::from_file_path("/file.rs").unwrap()
3702 );
3703 assert_eq!(params.options.tab_size, 4);
3704 Ok(Some(vec![lsp::TextEdit::new(
3705 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
3706 ", ".to_string(),
3707 )]))
3708 })
3709 .next()
3710 .await;
3711 cx.foreground().start_waiting();
3712 save.await.unwrap();
3713 assert_eq!(
3714 editor.read_with(cx, |editor, cx| editor.text(cx)),
3715 "one, two\nthree\n"
3716 );
3717 assert!(!cx.read(|cx| editor.is_dirty(cx)));
3718
3719 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
3720 assert!(cx.read(|cx| editor.is_dirty(cx)));
3721
3722 // Ensure we can still save even if formatting hangs.
3723 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
3724 move |params, _| async move {
3725 assert_eq!(
3726 params.text_document.uri,
3727 lsp::Url::from_file_path("/file.rs").unwrap()
3728 );
3729 futures::future::pending::<()>().await;
3730 unreachable!()
3731 },
3732 );
3733 let save = cx.update(|cx| editor.save(project.clone(), cx));
3734 cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
3735 cx.foreground().start_waiting();
3736 save.await.unwrap();
3737 assert_eq!(
3738 editor.read_with(cx, |editor, cx| editor.text(cx)),
3739 "one\ntwo\nthree\n"
3740 );
3741 assert!(!cx.read(|cx| editor.is_dirty(cx)));
3742
3743 // Set rust language override and assert overriden tabsize is sent to language server
3744 cx.update(|cx| {
3745 cx.update_global::<Settings, _, _>(|settings, _| {
3746 settings.language_overrides.insert(
3747 "Rust".into(),
3748 EditorSettings {
3749 tab_size: Some(8.try_into().unwrap()),
3750 ..Default::default()
3751 },
3752 );
3753 })
3754 });
3755
3756 let save = cx.update(|cx| editor.save(project.clone(), cx));
3757 fake_server
3758 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
3759 assert_eq!(
3760 params.text_document.uri,
3761 lsp::Url::from_file_path("/file.rs").unwrap()
3762 );
3763 assert_eq!(params.options.tab_size, 8);
3764 Ok(Some(vec![]))
3765 })
3766 .next()
3767 .await;
3768 cx.foreground().start_waiting();
3769 save.await.unwrap();
3770}
3771
3772#[gpui::test]
3773async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
3774 cx.foreground().forbid_parking();
3775
3776 let mut language = Language::new(
3777 LanguageConfig {
3778 name: "Rust".into(),
3779 path_suffixes: vec!["rs".to_string()],
3780 ..Default::default()
3781 },
3782 Some(tree_sitter_rust::language()),
3783 );
3784 let mut fake_servers = language
3785 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
3786 capabilities: lsp::ServerCapabilities {
3787 document_formatting_provider: Some(lsp::OneOf::Left(true)),
3788 ..Default::default()
3789 },
3790 ..Default::default()
3791 }))
3792 .await;
3793
3794 let fs = FakeFs::new(cx.background());
3795 fs.insert_file("/file.rs", Default::default()).await;
3796
3797 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
3798 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
3799 let buffer = project
3800 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
3801 .await
3802 .unwrap();
3803
3804 cx.foreground().start_waiting();
3805 let fake_server = fake_servers.next().await.unwrap();
3806
3807 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
3808 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
3809 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
3810
3811 let format = editor.update(cx, |editor, cx| editor.perform_format(project.clone(), cx));
3812 fake_server
3813 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
3814 assert_eq!(
3815 params.text_document.uri,
3816 lsp::Url::from_file_path("/file.rs").unwrap()
3817 );
3818 assert_eq!(params.options.tab_size, 4);
3819 Ok(Some(vec![lsp::TextEdit::new(
3820 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
3821 ", ".to_string(),
3822 )]))
3823 })
3824 .next()
3825 .await;
3826 cx.foreground().start_waiting();
3827 format.await.unwrap();
3828 assert_eq!(
3829 editor.read_with(cx, |editor, cx| editor.text(cx)),
3830 "one, two\nthree\n"
3831 );
3832
3833 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
3834 // Ensure we don't lock if formatting hangs.
3835 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
3836 assert_eq!(
3837 params.text_document.uri,
3838 lsp::Url::from_file_path("/file.rs").unwrap()
3839 );
3840 futures::future::pending::<()>().await;
3841 unreachable!()
3842 });
3843 let format = editor.update(cx, |editor, cx| editor.perform_format(project, cx));
3844 cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
3845 cx.foreground().start_waiting();
3846 format.await.unwrap();
3847 assert_eq!(
3848 editor.read_with(cx, |editor, cx| editor.text(cx)),
3849 "one\ntwo\nthree\n"
3850 );
3851}
3852
3853#[gpui::test]
3854async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
3855 cx.foreground().forbid_parking();
3856
3857 let mut cx = EditorLspTestContext::new_rust(
3858 lsp::ServerCapabilities {
3859 document_formatting_provider: Some(lsp::OneOf::Left(true)),
3860 ..Default::default()
3861 },
3862 cx,
3863 )
3864 .await;
3865
3866 cx.set_state(indoc! {"
3867 one.twoˇ
3868 "});
3869
3870 // The format request takes a long time. When it completes, it inserts
3871 // a newline and an indent before the `.`
3872 cx.lsp
3873 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
3874 let executor = cx.background();
3875 async move {
3876 executor.timer(Duration::from_millis(100)).await;
3877 Ok(Some(vec![lsp::TextEdit {
3878 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
3879 new_text: "\n ".into(),
3880 }]))
3881 }
3882 });
3883
3884 // Submit a format request.
3885 let format_1 = cx
3886 .update_editor(|editor, cx| editor.format(&Format, cx))
3887 .unwrap();
3888 cx.foreground().run_until_parked();
3889
3890 // Submit a second format request.
3891 let format_2 = cx
3892 .update_editor(|editor, cx| editor.format(&Format, cx))
3893 .unwrap();
3894 cx.foreground().run_until_parked();
3895
3896 // Wait for both format requests to complete
3897 cx.foreground().advance_clock(Duration::from_millis(200));
3898 cx.foreground().start_waiting();
3899 format_1.await.unwrap();
3900 cx.foreground().start_waiting();
3901 format_2.await.unwrap();
3902
3903 // The formatting edits only happens once.
3904 cx.assert_editor_state(indoc! {"
3905 one
3906 .twoˇ
3907 "});
3908}
3909
3910#[gpui::test]
3911async fn test_completion(cx: &mut gpui::TestAppContext) {
3912 let mut cx = EditorLspTestContext::new_rust(
3913 lsp::ServerCapabilities {
3914 completion_provider: Some(lsp::CompletionOptions {
3915 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
3916 ..Default::default()
3917 }),
3918 ..Default::default()
3919 },
3920 cx,
3921 )
3922 .await;
3923
3924 cx.set_state(indoc! {"
3925 oneˇ
3926 two
3927 three
3928 "});
3929 cx.simulate_keystroke(".");
3930 handle_completion_request(
3931 &mut cx,
3932 indoc! {"
3933 one.|<>
3934 two
3935 three
3936 "},
3937 vec!["first_completion", "second_completion"],
3938 )
3939 .await;
3940 cx.condition(|editor, _| editor.context_menu_visible())
3941 .await;
3942 let apply_additional_edits = cx.update_editor(|editor, cx| {
3943 editor.move_down(&MoveDown, cx);
3944 editor
3945 .confirm_completion(&ConfirmCompletion::default(), cx)
3946 .unwrap()
3947 });
3948 cx.assert_editor_state(indoc! {"
3949 one.second_completionˇ
3950 two
3951 three
3952 "});
3953
3954 handle_resolve_completion_request(
3955 &mut cx,
3956 Some((
3957 indoc! {"
3958 one.second_completion
3959 two
3960 threeˇ
3961 "},
3962 "\nadditional edit",
3963 )),
3964 )
3965 .await;
3966 apply_additional_edits.await.unwrap();
3967 cx.assert_editor_state(indoc! {"
3968 one.second_completionˇ
3969 two
3970 three
3971 additional edit
3972 "});
3973
3974 cx.set_state(indoc! {"
3975 one.second_completion
3976 twoˇ
3977 threeˇ
3978 additional edit
3979 "});
3980 cx.simulate_keystroke(" ");
3981 assert!(cx.editor(|e, _| e.context_menu.is_none()));
3982 cx.simulate_keystroke("s");
3983 assert!(cx.editor(|e, _| e.context_menu.is_none()));
3984
3985 cx.assert_editor_state(indoc! {"
3986 one.second_completion
3987 two sˇ
3988 three sˇ
3989 additional edit
3990 "});
3991 handle_completion_request(
3992 &mut cx,
3993 indoc! {"
3994 one.second_completion
3995 two s
3996 three <s|>
3997 additional edit
3998 "},
3999 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
4000 )
4001 .await;
4002 cx.condition(|editor, _| editor.context_menu_visible())
4003 .await;
4004
4005 cx.simulate_keystroke("i");
4006
4007 handle_completion_request(
4008 &mut cx,
4009 indoc! {"
4010 one.second_completion
4011 two si
4012 three <si|>
4013 additional edit
4014 "},
4015 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
4016 )
4017 .await;
4018 cx.condition(|editor, _| editor.context_menu_visible())
4019 .await;
4020
4021 let apply_additional_edits = cx.update_editor(|editor, cx| {
4022 editor
4023 .confirm_completion(&ConfirmCompletion::default(), cx)
4024 .unwrap()
4025 });
4026 cx.assert_editor_state(indoc! {"
4027 one.second_completion
4028 two sixth_completionˇ
4029 three sixth_completionˇ
4030 additional edit
4031 "});
4032
4033 handle_resolve_completion_request(&mut cx, None).await;
4034 apply_additional_edits.await.unwrap();
4035
4036 cx.update(|cx| {
4037 cx.update_global::<Settings, _, _>(|settings, _| {
4038 settings.show_completions_on_input = false;
4039 })
4040 });
4041 cx.set_state("editorˇ");
4042 cx.simulate_keystroke(".");
4043 assert!(cx.editor(|e, _| e.context_menu.is_none()));
4044 cx.simulate_keystroke("c");
4045 cx.simulate_keystroke("l");
4046 cx.simulate_keystroke("o");
4047 cx.assert_editor_state("editor.cloˇ");
4048 assert!(cx.editor(|e, _| e.context_menu.is_none()));
4049 cx.update_editor(|editor, cx| {
4050 editor.show_completions(&ShowCompletions, cx);
4051 });
4052 handle_completion_request(&mut cx, "editor.<clo|>", vec!["close", "clobber"]).await;
4053 cx.condition(|editor, _| editor.context_menu_visible())
4054 .await;
4055 let apply_additional_edits = cx.update_editor(|editor, cx| {
4056 editor
4057 .confirm_completion(&ConfirmCompletion::default(), cx)
4058 .unwrap()
4059 });
4060 cx.assert_editor_state("editor.closeˇ");
4061 handle_resolve_completion_request(&mut cx, None).await;
4062 apply_additional_edits.await.unwrap();
4063
4064 // Handle completion request passing a marked string specifying where the completion
4065 // should be triggered from using '|' character, what range should be replaced, and what completions
4066 // should be returned using '<' and '>' to delimit the range
4067 async fn handle_completion_request<'a>(
4068 cx: &mut EditorLspTestContext<'a>,
4069 marked_string: &str,
4070 completions: Vec<&'static str>,
4071 ) {
4072 let complete_from_marker: TextRangeMarker = '|'.into();
4073 let replace_range_marker: TextRangeMarker = ('<', '>').into();
4074 let (_, mut marked_ranges) = marked_text_ranges_by(
4075 marked_string,
4076 vec![complete_from_marker.clone(), replace_range_marker.clone()],
4077 );
4078
4079 let complete_from_position =
4080 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
4081 let replace_range =
4082 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
4083
4084 cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
4085 let completions = completions.clone();
4086 async move {
4087 assert_eq!(params.text_document_position.text_document.uri, url.clone());
4088 assert_eq!(
4089 params.text_document_position.position,
4090 complete_from_position
4091 );
4092 Ok(Some(lsp::CompletionResponse::Array(
4093 completions
4094 .iter()
4095 .map(|completion_text| lsp::CompletionItem {
4096 label: completion_text.to_string(),
4097 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
4098 range: replace_range,
4099 new_text: completion_text.to_string(),
4100 })),
4101 ..Default::default()
4102 })
4103 .collect(),
4104 )))
4105 }
4106 })
4107 .next()
4108 .await;
4109 }
4110
4111 async fn handle_resolve_completion_request<'a>(
4112 cx: &mut EditorLspTestContext<'a>,
4113 edit: Option<(&'static str, &'static str)>,
4114 ) {
4115 let edit = edit.map(|(marked_string, new_text)| {
4116 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
4117 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
4118 vec![lsp::TextEdit::new(replace_range, new_text.to_string())]
4119 });
4120
4121 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
4122 let edit = edit.clone();
4123 async move {
4124 Ok(lsp::CompletionItem {
4125 additional_text_edits: edit,
4126 ..Default::default()
4127 })
4128 }
4129 })
4130 .next()
4131 .await;
4132 }
4133}
4134
4135#[gpui::test]
4136async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
4137 cx.update(|cx| cx.set_global(Settings::test(cx)));
4138 let language = Arc::new(Language::new(
4139 LanguageConfig {
4140 line_comment: Some("// ".into()),
4141 ..Default::default()
4142 },
4143 Some(tree_sitter_rust::language()),
4144 ));
4145
4146 let text = "
4147 fn a() {
4148 //b();
4149 // c();
4150 // d();
4151 }
4152 "
4153 .unindent();
4154
4155 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
4156 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
4157 let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
4158
4159 view.update(cx, |editor, cx| {
4160 // If multiple selections intersect a line, the line is only
4161 // toggled once.
4162 editor.change_selections(None, cx, |s| {
4163 s.select_display_ranges([
4164 DisplayPoint::new(1, 3)..DisplayPoint::new(2, 3),
4165 DisplayPoint::new(3, 5)..DisplayPoint::new(3, 6),
4166 ])
4167 });
4168 editor.toggle_comments(&ToggleComments, cx);
4169 assert_eq!(
4170 editor.text(cx),
4171 "
4172 fn a() {
4173 b();
4174 c();
4175 d();
4176 }
4177 "
4178 .unindent()
4179 );
4180
4181 // The comment prefix is inserted at the same column for every line
4182 // in a selection.
4183 editor.change_selections(None, cx, |s| {
4184 s.select_display_ranges([DisplayPoint::new(1, 3)..DisplayPoint::new(3, 6)])
4185 });
4186 editor.toggle_comments(&ToggleComments, cx);
4187 assert_eq!(
4188 editor.text(cx),
4189 "
4190 fn a() {
4191 // b();
4192 // c();
4193 // d();
4194 }
4195 "
4196 .unindent()
4197 );
4198
4199 // If a selection ends at the beginning of a line, that line is not toggled.
4200 editor.change_selections(None, cx, |s| {
4201 s.select_display_ranges([DisplayPoint::new(2, 0)..DisplayPoint::new(3, 0)])
4202 });
4203 editor.toggle_comments(&ToggleComments, cx);
4204 assert_eq!(
4205 editor.text(cx),
4206 "
4207 fn a() {
4208 // b();
4209 c();
4210 // d();
4211 }
4212 "
4213 .unindent()
4214 );
4215 });
4216}
4217
4218#[gpui::test]
4219async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
4220 let mut cx = EditorTestContext::new(cx);
4221
4222 let html_language = Arc::new(
4223 Language::new(
4224 LanguageConfig {
4225 name: "HTML".into(),
4226 block_comment: Some(("<!-- ".into(), " -->".into())),
4227 ..Default::default()
4228 },
4229 Some(tree_sitter_html::language()),
4230 )
4231 .with_injection_query(
4232 r#"
4233 (script_element
4234 (raw_text) @content
4235 (#set! "language" "javascript"))
4236 "#,
4237 )
4238 .unwrap(),
4239 );
4240
4241 let javascript_language = Arc::new(Language::new(
4242 LanguageConfig {
4243 name: "JavaScript".into(),
4244 line_comment: Some("// ".into()),
4245 ..Default::default()
4246 },
4247 Some(tree_sitter_javascript::language()),
4248 ));
4249
4250 let registry = Arc::new(LanguageRegistry::test());
4251 registry.add(html_language.clone());
4252 registry.add(javascript_language.clone());
4253
4254 cx.update_buffer(|buffer, cx| {
4255 buffer.set_language_registry(registry);
4256 buffer.set_language(Some(html_language), cx);
4257 });
4258
4259 // Toggle comments for empty selections
4260 cx.set_state(
4261 &r#"
4262 <p>A</p>ˇ
4263 <p>B</p>ˇ
4264 <p>C</p>ˇ
4265 "#
4266 .unindent(),
4267 );
4268 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments, cx));
4269 cx.assert_editor_state(
4270 &r#"
4271 <!-- <p>A</p>ˇ -->
4272 <!-- <p>B</p>ˇ -->
4273 <!-- <p>C</p>ˇ -->
4274 "#
4275 .unindent(),
4276 );
4277 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments, cx));
4278 cx.assert_editor_state(
4279 &r#"
4280 <p>A</p>ˇ
4281 <p>B</p>ˇ
4282 <p>C</p>ˇ
4283 "#
4284 .unindent(),
4285 );
4286
4287 // Toggle comments for mixture of empty and non-empty selections, where
4288 // multiple selections occupy a given line.
4289 cx.set_state(
4290 &r#"
4291 <p>A«</p>
4292 <p>ˇ»B</p>ˇ
4293 <p>C«</p>
4294 <p>ˇ»D</p>ˇ
4295 "#
4296 .unindent(),
4297 );
4298
4299 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments, cx));
4300 cx.assert_editor_state(
4301 &r#"
4302 <!-- <p>A«</p>
4303 <p>ˇ»B</p>ˇ -->
4304 <!-- <p>C«</p>
4305 <p>ˇ»D</p>ˇ -->
4306 "#
4307 .unindent(),
4308 );
4309 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments, cx));
4310 cx.assert_editor_state(
4311 &r#"
4312 <p>A«</p>
4313 <p>ˇ»B</p>ˇ
4314 <p>C«</p>
4315 <p>ˇ»D</p>ˇ
4316 "#
4317 .unindent(),
4318 );
4319
4320 // Toggle comments when different languages are active for different
4321 // selections.
4322 cx.set_state(
4323 &r#"
4324 ˇ<script>
4325 ˇvar x = new Y();
4326 ˇ</script>
4327 "#
4328 .unindent(),
4329 );
4330 cx.foreground().run_until_parked();
4331 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments, cx));
4332 cx.assert_editor_state(
4333 &r#"
4334 <!-- ˇ<script> -->
4335 // ˇvar x = new Y();
4336 <!-- ˇ</script> -->
4337 "#
4338 .unindent(),
4339 );
4340}
4341
4342#[gpui::test]
4343fn test_editing_disjoint_excerpts(cx: &mut gpui::MutableAppContext) {
4344 cx.set_global(Settings::test(cx));
4345 let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
4346 let multibuffer = cx.add_model(|cx| {
4347 let mut multibuffer = MultiBuffer::new(0);
4348 multibuffer.push_excerpts(
4349 buffer.clone(),
4350 [
4351 ExcerptRange {
4352 context: Point::new(0, 0)..Point::new(0, 4),
4353 primary: None,
4354 },
4355 ExcerptRange {
4356 context: Point::new(1, 0)..Point::new(1, 4),
4357 primary: None,
4358 },
4359 ],
4360 cx,
4361 );
4362 multibuffer
4363 });
4364
4365 assert_eq!(multibuffer.read(cx).read(cx).text(), "aaaa\nbbbb");
4366
4367 let (_, view) = cx.add_window(Default::default(), |cx| build_editor(multibuffer, cx));
4368 view.update(cx, |view, cx| {
4369 assert_eq!(view.text(cx), "aaaa\nbbbb");
4370 view.change_selections(None, cx, |s| {
4371 s.select_ranges([
4372 Point::new(0, 0)..Point::new(0, 0),
4373 Point::new(1, 0)..Point::new(1, 0),
4374 ])
4375 });
4376
4377 view.handle_input("X", cx);
4378 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
4379 assert_eq!(
4380 view.selections.ranges(cx),
4381 [
4382 Point::new(0, 1)..Point::new(0, 1),
4383 Point::new(1, 1)..Point::new(1, 1),
4384 ]
4385 )
4386 });
4387}
4388
4389#[gpui::test]
4390fn test_editing_overlapping_excerpts(cx: &mut gpui::MutableAppContext) {
4391 cx.set_global(Settings::test(cx));
4392 let markers = vec![('[', ']').into(), ('(', ')').into()];
4393 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
4394 indoc! {"
4395 [aaaa
4396 (bbbb]
4397 cccc)",
4398 },
4399 markers.clone(),
4400 );
4401 let excerpt_ranges = markers.into_iter().map(|marker| {
4402 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
4403 ExcerptRange {
4404 context,
4405 primary: None,
4406 }
4407 });
4408 let buffer = cx.add_model(|cx| Buffer::new(0, initial_text, cx));
4409 let multibuffer = cx.add_model(|cx| {
4410 let mut multibuffer = MultiBuffer::new(0);
4411 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
4412 multibuffer
4413 });
4414
4415 let (_, view) = cx.add_window(Default::default(), |cx| build_editor(multibuffer, cx));
4416 view.update(cx, |view, cx| {
4417 let (expected_text, selection_ranges) = marked_text_ranges(
4418 indoc! {"
4419 aaaa
4420 bˇbbb
4421 bˇbbˇb
4422 cccc"
4423 },
4424 true,
4425 );
4426 assert_eq!(view.text(cx), expected_text);
4427 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
4428
4429 view.handle_input("X", cx);
4430
4431 let (expected_text, expected_selections) = marked_text_ranges(
4432 indoc! {"
4433 aaaa
4434 bXˇbbXb
4435 bXˇbbXˇb
4436 cccc"
4437 },
4438 false,
4439 );
4440 assert_eq!(view.text(cx), expected_text);
4441 assert_eq!(view.selections.ranges(cx), expected_selections);
4442
4443 view.newline(&Newline, cx);
4444 let (expected_text, expected_selections) = marked_text_ranges(
4445 indoc! {"
4446 aaaa
4447 bX
4448 ˇbbX
4449 b
4450 bX
4451 ˇbbX
4452 ˇb
4453 cccc"
4454 },
4455 false,
4456 );
4457 assert_eq!(view.text(cx), expected_text);
4458 assert_eq!(view.selections.ranges(cx), expected_selections);
4459 });
4460}
4461
4462#[gpui::test]
4463fn test_refresh_selections(cx: &mut gpui::MutableAppContext) {
4464 cx.set_global(Settings::test(cx));
4465 let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
4466 let mut excerpt1_id = None;
4467 let multibuffer = cx.add_model(|cx| {
4468 let mut multibuffer = MultiBuffer::new(0);
4469 excerpt1_id = multibuffer
4470 .push_excerpts(
4471 buffer.clone(),
4472 [
4473 ExcerptRange {
4474 context: Point::new(0, 0)..Point::new(1, 4),
4475 primary: None,
4476 },
4477 ExcerptRange {
4478 context: Point::new(1, 0)..Point::new(2, 4),
4479 primary: None,
4480 },
4481 ],
4482 cx,
4483 )
4484 .into_iter()
4485 .next();
4486 multibuffer
4487 });
4488 assert_eq!(
4489 multibuffer.read(cx).read(cx).text(),
4490 "aaaa\nbbbb\nbbbb\ncccc"
4491 );
4492 let (_, editor) = cx.add_window(Default::default(), |cx| {
4493 let mut editor = build_editor(multibuffer.clone(), cx);
4494 let snapshot = editor.snapshot(cx);
4495 editor.change_selections(None, cx, |s| {
4496 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
4497 });
4498 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
4499 assert_eq!(
4500 editor.selections.ranges(cx),
4501 [
4502 Point::new(1, 3)..Point::new(1, 3),
4503 Point::new(2, 1)..Point::new(2, 1),
4504 ]
4505 );
4506 editor
4507 });
4508
4509 // Refreshing selections is a no-op when excerpts haven't changed.
4510 editor.update(cx, |editor, cx| {
4511 editor.change_selections(None, cx, |s| {
4512 s.refresh();
4513 });
4514 assert_eq!(
4515 editor.selections.ranges(cx),
4516 [
4517 Point::new(1, 3)..Point::new(1, 3),
4518 Point::new(2, 1)..Point::new(2, 1),
4519 ]
4520 );
4521 });
4522
4523 multibuffer.update(cx, |multibuffer, cx| {
4524 multibuffer.remove_excerpts([&excerpt1_id.unwrap()], cx);
4525 });
4526 editor.update(cx, |editor, cx| {
4527 // Removing an excerpt causes the first selection to become degenerate.
4528 assert_eq!(
4529 editor.selections.ranges(cx),
4530 [
4531 Point::new(0, 0)..Point::new(0, 0),
4532 Point::new(0, 1)..Point::new(0, 1)
4533 ]
4534 );
4535
4536 // Refreshing selections will relocate the first selection to the original buffer
4537 // location.
4538 editor.change_selections(None, cx, |s| {
4539 s.refresh();
4540 });
4541 assert_eq!(
4542 editor.selections.ranges(cx),
4543 [
4544 Point::new(0, 1)..Point::new(0, 1),
4545 Point::new(0, 3)..Point::new(0, 3)
4546 ]
4547 );
4548 assert!(editor.selections.pending_anchor().is_some());
4549 });
4550}
4551
4552#[gpui::test]
4553fn test_refresh_selections_while_selecting_with_mouse(cx: &mut gpui::MutableAppContext) {
4554 cx.set_global(Settings::test(cx));
4555 let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
4556 let mut excerpt1_id = None;
4557 let multibuffer = cx.add_model(|cx| {
4558 let mut multibuffer = MultiBuffer::new(0);
4559 excerpt1_id = multibuffer
4560 .push_excerpts(
4561 buffer.clone(),
4562 [
4563 ExcerptRange {
4564 context: Point::new(0, 0)..Point::new(1, 4),
4565 primary: None,
4566 },
4567 ExcerptRange {
4568 context: Point::new(1, 0)..Point::new(2, 4),
4569 primary: None,
4570 },
4571 ],
4572 cx,
4573 )
4574 .into_iter()
4575 .next();
4576 multibuffer
4577 });
4578 assert_eq!(
4579 multibuffer.read(cx).read(cx).text(),
4580 "aaaa\nbbbb\nbbbb\ncccc"
4581 );
4582 let (_, editor) = cx.add_window(Default::default(), |cx| {
4583 let mut editor = build_editor(multibuffer.clone(), cx);
4584 let snapshot = editor.snapshot(cx);
4585 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
4586 assert_eq!(
4587 editor.selections.ranges(cx),
4588 [Point::new(1, 3)..Point::new(1, 3)]
4589 );
4590 editor
4591 });
4592
4593 multibuffer.update(cx, |multibuffer, cx| {
4594 multibuffer.remove_excerpts([&excerpt1_id.unwrap()], cx);
4595 });
4596 editor.update(cx, |editor, cx| {
4597 assert_eq!(
4598 editor.selections.ranges(cx),
4599 [Point::new(0, 0)..Point::new(0, 0)]
4600 );
4601
4602 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
4603 editor.change_selections(None, cx, |s| {
4604 s.refresh();
4605 });
4606 assert_eq!(
4607 editor.selections.ranges(cx),
4608 [Point::new(0, 3)..Point::new(0, 3)]
4609 );
4610 assert!(editor.selections.pending_anchor().is_some());
4611 });
4612}
4613
4614#[gpui::test]
4615async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
4616 cx.update(|cx| cx.set_global(Settings::test(cx)));
4617 let language = Arc::new(
4618 Language::new(
4619 LanguageConfig {
4620 brackets: vec![
4621 BracketPair {
4622 start: "{".to_string(),
4623 end: "}".to_string(),
4624 close: true,
4625 newline: true,
4626 },
4627 BracketPair {
4628 start: "/* ".to_string(),
4629 end: " */".to_string(),
4630 close: true,
4631 newline: true,
4632 },
4633 ],
4634 ..Default::default()
4635 },
4636 Some(tree_sitter_rust::language()),
4637 )
4638 .with_indents_query("")
4639 .unwrap(),
4640 );
4641
4642 let text = concat!(
4643 "{ }\n", //
4644 " x\n", //
4645 " /* */\n", //
4646 "x\n", //
4647 "{{} }\n", //
4648 );
4649
4650 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
4651 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
4652 let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
4653 view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
4654 .await;
4655
4656 view.update(cx, |view, cx| {
4657 view.change_selections(None, cx, |s| {
4658 s.select_display_ranges([
4659 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3),
4660 DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
4661 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
4662 ])
4663 });
4664 view.newline(&Newline, cx);
4665
4666 assert_eq!(
4667 view.buffer().read(cx).read(cx).text(),
4668 concat!(
4669 "{ \n", // Suppress rustfmt
4670 "\n", //
4671 "}\n", //
4672 " x\n", //
4673 " /* \n", //
4674 " \n", //
4675 " */\n", //
4676 "x\n", //
4677 "{{} \n", //
4678 "}\n", //
4679 )
4680 );
4681 });
4682}
4683
4684#[gpui::test]
4685fn test_highlighted_ranges(cx: &mut gpui::MutableAppContext) {
4686 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
4687
4688 cx.set_global(Settings::test(cx));
4689 let (_, editor) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
4690
4691 editor.update(cx, |editor, cx| {
4692 struct Type1;
4693 struct Type2;
4694
4695 let buffer = buffer.read(cx).snapshot(cx);
4696
4697 let anchor_range =
4698 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
4699
4700 editor.highlight_background::<Type1>(
4701 vec![
4702 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
4703 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
4704 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
4705 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
4706 ],
4707 |_| Color::red(),
4708 cx,
4709 );
4710 editor.highlight_background::<Type2>(
4711 vec![
4712 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
4713 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
4714 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
4715 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
4716 ],
4717 |_| Color::green(),
4718 cx,
4719 );
4720
4721 let snapshot = editor.snapshot(cx);
4722 let mut highlighted_ranges = editor.background_highlights_in_range(
4723 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
4724 &snapshot,
4725 cx.global::<Settings>().theme.as_ref(),
4726 );
4727 // Enforce a consistent ordering based on color without relying on the ordering of the
4728 // highlight's `TypeId` which is non-deterministic.
4729 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
4730 assert_eq!(
4731 highlighted_ranges,
4732 &[
4733 (
4734 DisplayPoint::new(3, 2)..DisplayPoint::new(3, 5),
4735 Color::green(),
4736 ),
4737 (
4738 DisplayPoint::new(5, 3)..DisplayPoint::new(5, 6),
4739 Color::green(),
4740 ),
4741 (
4742 DisplayPoint::new(4, 2)..DisplayPoint::new(4, 4),
4743 Color::red(),
4744 ),
4745 (
4746 DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
4747 Color::red(),
4748 ),
4749 ]
4750 );
4751 assert_eq!(
4752 editor.background_highlights_in_range(
4753 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
4754 &snapshot,
4755 cx.global::<Settings>().theme.as_ref(),
4756 ),
4757 &[(
4758 DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
4759 Color::red(),
4760 )]
4761 );
4762 });
4763}
4764
4765#[gpui::test]
4766fn test_following(cx: &mut gpui::MutableAppContext) {
4767 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
4768
4769 cx.set_global(Settings::test(cx));
4770
4771 let (_, leader) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
4772 let (_, follower) = cx.add_window(
4773 WindowOptions {
4774 bounds: WindowBounds::Fixed(RectF::from_points(vec2f(0., 0.), vec2f(10., 80.))),
4775 ..Default::default()
4776 },
4777 |cx| build_editor(buffer.clone(), cx),
4778 );
4779
4780 let pending_update = Rc::new(RefCell::new(None));
4781 follower.update(cx, {
4782 let update = pending_update.clone();
4783 |_, cx| {
4784 cx.subscribe(&leader, move |_, leader, event, cx| {
4785 leader
4786 .read(cx)
4787 .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx);
4788 })
4789 .detach();
4790 }
4791 });
4792
4793 // Update the selections only
4794 leader.update(cx, |leader, cx| {
4795 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
4796 });
4797 follower.update(cx, |follower, cx| {
4798 follower
4799 .apply_update_proto(pending_update.borrow_mut().take().unwrap(), cx)
4800 .unwrap();
4801 });
4802 assert_eq!(follower.read(cx).selections.ranges(cx), vec![1..1]);
4803
4804 // Update the scroll position only
4805 leader.update(cx, |leader, cx| {
4806 leader.set_scroll_position(vec2f(1.5, 3.5), cx);
4807 });
4808 follower.update(cx, |follower, cx| {
4809 follower
4810 .apply_update_proto(pending_update.borrow_mut().take().unwrap(), cx)
4811 .unwrap();
4812 });
4813 assert_eq!(
4814 follower.update(cx, |follower, cx| follower.scroll_position(cx)),
4815 vec2f(1.5, 3.5)
4816 );
4817
4818 // Update the selections and scroll position
4819 leader.update(cx, |leader, cx| {
4820 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
4821 leader.request_autoscroll(Autoscroll::Newest, cx);
4822 leader.set_scroll_position(vec2f(1.5, 3.5), cx);
4823 });
4824 follower.update(cx, |follower, cx| {
4825 let initial_scroll_position = follower.scroll_position(cx);
4826 follower
4827 .apply_update_proto(pending_update.borrow_mut().take().unwrap(), cx)
4828 .unwrap();
4829 assert_eq!(follower.scroll_position(cx), initial_scroll_position);
4830 assert!(follower.autoscroll_request.is_some());
4831 });
4832 assert_eq!(follower.read(cx).selections.ranges(cx), vec![0..0]);
4833
4834 // Creating a pending selection that precedes another selection
4835 leader.update(cx, |leader, cx| {
4836 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
4837 leader.begin_selection(DisplayPoint::new(0, 0), true, 1, cx);
4838 });
4839 follower.update(cx, |follower, cx| {
4840 follower
4841 .apply_update_proto(pending_update.borrow_mut().take().unwrap(), cx)
4842 .unwrap();
4843 });
4844 assert_eq!(follower.read(cx).selections.ranges(cx), vec![0..0, 1..1]);
4845
4846 // Extend the pending selection so that it surrounds another selection
4847 leader.update(cx, |leader, cx| {
4848 leader.extend_selection(DisplayPoint::new(0, 2), 1, cx);
4849 });
4850 follower.update(cx, |follower, cx| {
4851 follower
4852 .apply_update_proto(pending_update.borrow_mut().take().unwrap(), cx)
4853 .unwrap();
4854 });
4855 assert_eq!(follower.read(cx).selections.ranges(cx), vec![0..2]);
4856}
4857
4858#[test]
4859fn test_combine_syntax_and_fuzzy_match_highlights() {
4860 let string = "abcdefghijklmnop";
4861 let syntax_ranges = [
4862 (
4863 0..3,
4864 HighlightStyle {
4865 color: Some(Color::red()),
4866 ..Default::default()
4867 },
4868 ),
4869 (
4870 4..8,
4871 HighlightStyle {
4872 color: Some(Color::green()),
4873 ..Default::default()
4874 },
4875 ),
4876 ];
4877 let match_indices = [4, 6, 7, 8];
4878 assert_eq!(
4879 combine_syntax_and_fuzzy_match_highlights(
4880 string,
4881 Default::default(),
4882 syntax_ranges.into_iter(),
4883 &match_indices,
4884 ),
4885 &[
4886 (
4887 0..3,
4888 HighlightStyle {
4889 color: Some(Color::red()),
4890 ..Default::default()
4891 },
4892 ),
4893 (
4894 4..5,
4895 HighlightStyle {
4896 color: Some(Color::green()),
4897 weight: Some(fonts::Weight::BOLD),
4898 ..Default::default()
4899 },
4900 ),
4901 (
4902 5..6,
4903 HighlightStyle {
4904 color: Some(Color::green()),
4905 ..Default::default()
4906 },
4907 ),
4908 (
4909 6..8,
4910 HighlightStyle {
4911 color: Some(Color::green()),
4912 weight: Some(fonts::Weight::BOLD),
4913 ..Default::default()
4914 },
4915 ),
4916 (
4917 8..9,
4918 HighlightStyle {
4919 weight: Some(fonts::Weight::BOLD),
4920 ..Default::default()
4921 },
4922 ),
4923 ]
4924 );
4925}
4926
4927fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
4928 let point = DisplayPoint::new(row as u32, column as u32);
4929 point..point
4930}
4931
4932fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
4933 let (text, ranges) = marked_text_ranges(marked_text, true);
4934 assert_eq!(view.text(cx), text);
4935 assert_eq!(
4936 view.selections.ranges(cx),
4937 ranges,
4938 "Assert selections are {}",
4939 marked_text
4940 );
4941}