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