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