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