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