1use super::*;
2use crate::{
3 scroll::scroll_amount::ScrollAmount,
4 test::{
5 assert_text_with_selections, build_editor, editor_lsp_test_context::EditorLspTestContext,
6 editor_test_context::EditorTestContext, select_ranges,
7 },
8 JoinLines,
9};
10use drag_and_drop::DragAndDrop;
11use futures::StreamExt;
12use gpui::{
13 executor::Deterministic,
14 geometry::{rect::RectF, vector::vec2f},
15 platform::{WindowBounds, WindowOptions},
16 serde_json::{self, json},
17 TestAppContext,
18};
19use indoc::indoc;
20use language::{
21 language_settings::{AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent},
22 BracketPairConfig, FakeLspAdapter, LanguageConfig, LanguageRegistry, Point,
23};
24use parking_lot::Mutex;
25use project::project_settings::{LspSettings, ProjectSettings};
26use project::FakeFs;
27use std::sync::atomic;
28use std::sync::atomic::AtomicUsize;
29use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
30use unindent::Unindent;
31use util::{
32 assert_set_eq,
33 test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
34};
35use workspace::{
36 item::{FollowableItem, Item, ItemHandle},
37 NavigationEntry, ViewId,
38};
39
40#[gpui::test]
41fn test_edit_events(cx: &mut TestAppContext) {
42 init_test(cx, |_| {});
43
44 let buffer = cx.add_model(|cx| {
45 let mut buffer = language::Buffer::new(0, "123456", cx);
46 buffer.set_group_interval(Duration::from_secs(1));
47 buffer
48 });
49
50 let events = Rc::new(RefCell::new(Vec::new()));
51 let (_, editor1) = cx.add_window({
52 let events = events.clone();
53 |cx| {
54 cx.subscribe(&cx.handle(), move |_, _, event, _| {
55 if matches!(
56 event,
57 Event::Edited | Event::BufferEdited | Event::DirtyChanged
58 ) {
59 events.borrow_mut().push(("editor1", event.clone()));
60 }
61 })
62 .detach();
63 Editor::for_buffer(buffer.clone(), None, cx)
64 }
65 });
66 let (_, editor2) = cx.add_window({
67 let events = events.clone();
68 |cx| {
69 cx.subscribe(&cx.handle(), move |_, _, event, _| {
70 if matches!(
71 event,
72 Event::Edited | Event::BufferEdited | Event::DirtyChanged
73 ) {
74 events.borrow_mut().push(("editor2", event.clone()));
75 }
76 })
77 .detach();
78 Editor::for_buffer(buffer.clone(), None, cx)
79 }
80 });
81 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
82
83 // Mutating editor 1 will emit an `Edited` event only for that editor.
84 editor1.update(cx, |editor, cx| editor.insert("X", cx));
85 assert_eq!(
86 mem::take(&mut *events.borrow_mut()),
87 [
88 ("editor1", Event::Edited),
89 ("editor1", Event::BufferEdited),
90 ("editor2", Event::BufferEdited),
91 ("editor1", Event::DirtyChanged),
92 ("editor2", Event::DirtyChanged)
93 ]
94 );
95
96 // Mutating editor 2 will emit an `Edited` event only for that editor.
97 editor2.update(cx, |editor, cx| editor.delete(&Delete, cx));
98 assert_eq!(
99 mem::take(&mut *events.borrow_mut()),
100 [
101 ("editor2", Event::Edited),
102 ("editor1", Event::BufferEdited),
103 ("editor2", Event::BufferEdited),
104 ]
105 );
106
107 // Undoing on editor 1 will emit an `Edited` event only for that editor.
108 editor1.update(cx, |editor, cx| editor.undo(&Undo, cx));
109 assert_eq!(
110 mem::take(&mut *events.borrow_mut()),
111 [
112 ("editor1", Event::Edited),
113 ("editor1", Event::BufferEdited),
114 ("editor2", Event::BufferEdited),
115 ("editor1", Event::DirtyChanged),
116 ("editor2", Event::DirtyChanged),
117 ]
118 );
119
120 // Redoing on editor 1 will emit an `Edited` event only for that editor.
121 editor1.update(cx, |editor, cx| editor.redo(&Redo, cx));
122 assert_eq!(
123 mem::take(&mut *events.borrow_mut()),
124 [
125 ("editor1", Event::Edited),
126 ("editor1", Event::BufferEdited),
127 ("editor2", Event::BufferEdited),
128 ("editor1", Event::DirtyChanged),
129 ("editor2", Event::DirtyChanged),
130 ]
131 );
132
133 // Undoing on editor 2 will emit an `Edited` event only for that editor.
134 editor2.update(cx, |editor, cx| editor.undo(&Undo, cx));
135 assert_eq!(
136 mem::take(&mut *events.borrow_mut()),
137 [
138 ("editor2", Event::Edited),
139 ("editor1", Event::BufferEdited),
140 ("editor2", Event::BufferEdited),
141 ("editor1", Event::DirtyChanged),
142 ("editor2", Event::DirtyChanged),
143 ]
144 );
145
146 // Redoing on editor 2 will emit an `Edited` event only for that editor.
147 editor2.update(cx, |editor, cx| editor.redo(&Redo, cx));
148 assert_eq!(
149 mem::take(&mut *events.borrow_mut()),
150 [
151 ("editor2", Event::Edited),
152 ("editor1", Event::BufferEdited),
153 ("editor2", Event::BufferEdited),
154 ("editor1", Event::DirtyChanged),
155 ("editor2", Event::DirtyChanged),
156 ]
157 );
158
159 // No event is emitted when the mutation is a no-op.
160 editor2.update(cx, |editor, cx| {
161 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
162
163 editor.backspace(&Backspace, cx);
164 });
165 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
166}
167
168#[gpui::test]
169fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
170 init_test(cx, |_| {});
171
172 let mut now = Instant::now();
173 let buffer = cx.add_model(|cx| language::Buffer::new(0, "123456", cx));
174 let group_interval = buffer.read_with(cx, |buffer, _| buffer.transaction_group_interval());
175 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
176 let (_, editor) = cx.add_window(|cx| build_editor(buffer.clone(), cx));
177
178 editor.update(cx, |editor, cx| {
179 editor.start_transaction_at(now, cx);
180 editor.change_selections(None, cx, |s| s.select_ranges([2..4]));
181
182 editor.insert("cd", cx);
183 editor.end_transaction_at(now, cx);
184 assert_eq!(editor.text(cx), "12cd56");
185 assert_eq!(editor.selections.ranges(cx), vec![4..4]);
186
187 editor.start_transaction_at(now, cx);
188 editor.change_selections(None, cx, |s| s.select_ranges([4..5]));
189 editor.insert("e", cx);
190 editor.end_transaction_at(now, cx);
191 assert_eq!(editor.text(cx), "12cde6");
192 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
193
194 now += group_interval + Duration::from_millis(1);
195 editor.change_selections(None, cx, |s| s.select_ranges([2..2]));
196
197 // Simulate an edit in another editor
198 buffer.update(cx, |buffer, cx| {
199 buffer.start_transaction_at(now, cx);
200 buffer.edit([(0..1, "a")], None, cx);
201 buffer.edit([(1..1, "b")], None, cx);
202 buffer.end_transaction_at(now, cx);
203 });
204
205 assert_eq!(editor.text(cx), "ab2cde6");
206 assert_eq!(editor.selections.ranges(cx), vec![3..3]);
207
208 // Last transaction happened past the group interval in a different editor.
209 // Undo it individually and don't restore selections.
210 editor.undo(&Undo, cx);
211 assert_eq!(editor.text(cx), "12cde6");
212 assert_eq!(editor.selections.ranges(cx), vec![2..2]);
213
214 // First two transactions happened within the group interval in this editor.
215 // Undo them together and restore selections.
216 editor.undo(&Undo, cx);
217 editor.undo(&Undo, cx); // Undo stack is empty here, so this is a no-op.
218 assert_eq!(editor.text(cx), "123456");
219 assert_eq!(editor.selections.ranges(cx), vec![0..0]);
220
221 // Redo the first two transactions together.
222 editor.redo(&Redo, cx);
223 assert_eq!(editor.text(cx), "12cde6");
224 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
225
226 // Redo the last transaction on its own.
227 editor.redo(&Redo, cx);
228 assert_eq!(editor.text(cx), "ab2cde6");
229 assert_eq!(editor.selections.ranges(cx), vec![6..6]);
230
231 // Test empty transactions.
232 editor.start_transaction_at(now, cx);
233 editor.end_transaction_at(now, cx);
234 editor.undo(&Undo, cx);
235 assert_eq!(editor.text(cx), "12cde6");
236 });
237}
238
239#[gpui::test]
240fn test_ime_composition(cx: &mut TestAppContext) {
241 init_test(cx, |_| {});
242
243 let buffer = cx.add_model(|cx| {
244 let mut buffer = language::Buffer::new(0, "abcde", cx);
245 // Ensure automatic grouping doesn't occur.
246 buffer.set_group_interval(Duration::ZERO);
247 buffer
248 });
249
250 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
251 cx.add_window(|cx| {
252 let mut editor = build_editor(buffer.clone(), cx);
253
254 // Start a new IME composition.
255 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
256 editor.replace_and_mark_text_in_range(Some(0..1), "á", None, cx);
257 editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, cx);
258 assert_eq!(editor.text(cx), "äbcde");
259 assert_eq!(
260 editor.marked_text_ranges(cx),
261 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
262 );
263
264 // Finalize IME composition.
265 editor.replace_text_in_range(None, "ā", cx);
266 assert_eq!(editor.text(cx), "ābcde");
267 assert_eq!(editor.marked_text_ranges(cx), None);
268
269 // IME composition edits are grouped and are undone/redone at once.
270 editor.undo(&Default::default(), cx);
271 assert_eq!(editor.text(cx), "abcde");
272 assert_eq!(editor.marked_text_ranges(cx), None);
273 editor.redo(&Default::default(), cx);
274 assert_eq!(editor.text(cx), "ābcde");
275 assert_eq!(editor.marked_text_ranges(cx), None);
276
277 // Start a new IME composition.
278 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
279 assert_eq!(
280 editor.marked_text_ranges(cx),
281 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
282 );
283
284 // Undoing during an IME composition cancels it.
285 editor.undo(&Default::default(), cx);
286 assert_eq!(editor.text(cx), "ābcde");
287 assert_eq!(editor.marked_text_ranges(cx), None);
288
289 // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
290 editor.replace_and_mark_text_in_range(Some(4..999), "è", None, cx);
291 assert_eq!(editor.text(cx), "ābcdè");
292 assert_eq!(
293 editor.marked_text_ranges(cx),
294 Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
295 );
296
297 // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
298 editor.replace_text_in_range(Some(4..999), "ę", cx);
299 assert_eq!(editor.text(cx), "ābcdę");
300 assert_eq!(editor.marked_text_ranges(cx), None);
301
302 // Start a new IME composition with multiple cursors.
303 editor.change_selections(None, cx, |s| {
304 s.select_ranges([
305 OffsetUtf16(1)..OffsetUtf16(1),
306 OffsetUtf16(3)..OffsetUtf16(3),
307 OffsetUtf16(5)..OffsetUtf16(5),
308 ])
309 });
310 editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, cx);
311 assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
312 assert_eq!(
313 editor.marked_text_ranges(cx),
314 Some(vec![
315 OffsetUtf16(0)..OffsetUtf16(3),
316 OffsetUtf16(4)..OffsetUtf16(7),
317 OffsetUtf16(8)..OffsetUtf16(11)
318 ])
319 );
320
321 // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
322 editor.replace_and_mark_text_in_range(Some(1..2), "1", None, cx);
323 assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
324 assert_eq!(
325 editor.marked_text_ranges(cx),
326 Some(vec![
327 OffsetUtf16(1)..OffsetUtf16(2),
328 OffsetUtf16(5)..OffsetUtf16(6),
329 OffsetUtf16(9)..OffsetUtf16(10)
330 ])
331 );
332
333 // Finalize IME composition with multiple cursors.
334 editor.replace_text_in_range(Some(9..10), "2", cx);
335 assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
336 assert_eq!(editor.marked_text_ranges(cx), None);
337
338 editor
339 });
340}
341
342#[gpui::test]
343fn test_selection_with_mouse(cx: &mut TestAppContext) {
344 init_test(cx, |_| {});
345
346 let (_, editor) = cx.add_window(|cx| {
347 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
348 build_editor(buffer, cx)
349 });
350 editor.update(cx, |view, cx| {
351 view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx);
352 });
353 assert_eq!(
354 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
355 [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)]
356 );
357
358 editor.update(cx, |view, cx| {
359 view.update_selection(DisplayPoint::new(3, 3), 0, Vector2F::zero(), cx);
360 });
361
362 assert_eq!(
363 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
364 [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
365 );
366
367 editor.update(cx, |view, cx| {
368 view.update_selection(DisplayPoint::new(1, 1), 0, Vector2F::zero(), cx);
369 });
370
371 assert_eq!(
372 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
373 [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
374 );
375
376 editor.update(cx, |view, cx| {
377 view.end_selection(cx);
378 view.update_selection(DisplayPoint::new(3, 3), 0, Vector2F::zero(), cx);
379 });
380
381 assert_eq!(
382 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
383 [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
384 );
385
386 editor.update(cx, |view, cx| {
387 view.begin_selection(DisplayPoint::new(3, 3), true, 1, cx);
388 view.update_selection(DisplayPoint::new(0, 0), 0, Vector2F::zero(), cx);
389 });
390
391 assert_eq!(
392 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
393 [
394 DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1),
395 DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)
396 ]
397 );
398
399 editor.update(cx, |view, cx| {
400 view.end_selection(cx);
401 });
402
403 assert_eq!(
404 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
405 [DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)]
406 );
407}
408
409#[gpui::test]
410fn test_canceling_pending_selection(cx: &mut TestAppContext) {
411 init_test(cx, |_| {});
412
413 let (_, view) = cx.add_window(|cx| {
414 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
415 build_editor(buffer, cx)
416 });
417
418 view.update(cx, |view, cx| {
419 view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx);
420 assert_eq!(
421 view.selections.display_ranges(cx),
422 [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)]
423 );
424 });
425
426 view.update(cx, |view, cx| {
427 view.update_selection(DisplayPoint::new(3, 3), 0, Vector2F::zero(), cx);
428 assert_eq!(
429 view.selections.display_ranges(cx),
430 [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
431 );
432 });
433
434 view.update(cx, |view, cx| {
435 view.cancel(&Cancel, cx);
436 view.update_selection(DisplayPoint::new(1, 1), 0, Vector2F::zero(), cx);
437 assert_eq!(
438 view.selections.display_ranges(cx),
439 [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
440 );
441 });
442}
443
444#[gpui::test]
445fn test_clone(cx: &mut TestAppContext) {
446 init_test(cx, |_| {});
447
448 let (text, selection_ranges) = marked_text_ranges(
449 indoc! {"
450 one
451 two
452 threeˇ
453 four
454 fiveˇ
455 "},
456 true,
457 );
458
459 let (_, editor) = cx.add_window(|cx| {
460 let buffer = MultiBuffer::build_simple(&text, cx);
461 build_editor(buffer, cx)
462 });
463
464 editor.update(cx, |editor, cx| {
465 editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
466 editor.fold_ranges(
467 [
468 Point::new(1, 0)..Point::new(2, 0),
469 Point::new(3, 0)..Point::new(4, 0),
470 ],
471 true,
472 cx,
473 );
474 });
475
476 let (_, cloned_editor) = editor.update(cx, |editor, cx| {
477 cx.add_window(Default::default(), |cx| editor.clone(cx))
478 });
479
480 let snapshot = editor.update(cx, |e, cx| e.snapshot(cx));
481 let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx));
482
483 assert_eq!(
484 cloned_editor.update(cx, |e, cx| e.display_text(cx)),
485 editor.update(cx, |e, cx| e.display_text(cx))
486 );
487 assert_eq!(
488 cloned_snapshot
489 .folds_in_range(0..text.len())
490 .collect::<Vec<_>>(),
491 snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
492 );
493 assert_set_eq!(
494 cloned_editor.read_with(cx, |editor, cx| editor.selections.ranges::<Point>(cx)),
495 editor.read_with(cx, |editor, cx| editor.selections.ranges(cx))
496 );
497 assert_set_eq!(
498 cloned_editor.update(cx, |e, cx| e.selections.display_ranges(cx)),
499 editor.update(cx, |e, cx| e.selections.display_ranges(cx))
500 );
501}
502
503#[gpui::test]
504async fn test_navigation_history(cx: &mut TestAppContext) {
505 init_test(cx, |_| {});
506
507 cx.set_global(DragAndDrop::<Workspace>::default());
508 use workspace::item::Item;
509
510 let fs = FakeFs::new(cx.background());
511 let project = Project::test(fs, [], cx).await;
512 let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
513 let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
514 cx.add_view(window_id, |cx| {
515 let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
516 let mut editor = build_editor(buffer.clone(), cx);
517 let handle = cx.handle();
518 editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
519
520 fn pop_history(editor: &mut Editor, cx: &mut WindowContext) -> Option<NavigationEntry> {
521 editor.nav_history.as_mut().unwrap().pop_backward(cx)
522 }
523
524 // Move the cursor a small distance.
525 // Nothing is added to the navigation history.
526 editor.change_selections(None, cx, |s| {
527 s.select_display_ranges([DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)])
528 });
529 editor.change_selections(None, cx, |s| {
530 s.select_display_ranges([DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)])
531 });
532 assert!(pop_history(&mut editor, cx).is_none());
533
534 // Move the cursor a large distance.
535 // The history can jump back to the previous position.
536 editor.change_selections(None, cx, |s| {
537 s.select_display_ranges([DisplayPoint::new(13, 0)..DisplayPoint::new(13, 3)])
538 });
539 let nav_entry = pop_history(&mut editor, cx).unwrap();
540 editor.navigate(nav_entry.data.unwrap(), cx);
541 assert_eq!(nav_entry.item.id(), cx.view_id());
542 assert_eq!(
543 editor.selections.display_ranges(cx),
544 &[DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)]
545 );
546 assert!(pop_history(&mut editor, cx).is_none());
547
548 // Move the cursor a small distance via the mouse.
549 // Nothing is added to the navigation history.
550 editor.begin_selection(DisplayPoint::new(5, 0), false, 1, cx);
551 editor.end_selection(cx);
552 assert_eq!(
553 editor.selections.display_ranges(cx),
554 &[DisplayPoint::new(5, 0)..DisplayPoint::new(5, 0)]
555 );
556 assert!(pop_history(&mut editor, cx).is_none());
557
558 // Move the cursor a large distance via the mouse.
559 // The history can jump back to the previous position.
560 editor.begin_selection(DisplayPoint::new(15, 0), false, 1, cx);
561 editor.end_selection(cx);
562 assert_eq!(
563 editor.selections.display_ranges(cx),
564 &[DisplayPoint::new(15, 0)..DisplayPoint::new(15, 0)]
565 );
566 let nav_entry = pop_history(&mut editor, cx).unwrap();
567 editor.navigate(nav_entry.data.unwrap(), cx);
568 assert_eq!(nav_entry.item.id(), cx.view_id());
569 assert_eq!(
570 editor.selections.display_ranges(cx),
571 &[DisplayPoint::new(5, 0)..DisplayPoint::new(5, 0)]
572 );
573 assert!(pop_history(&mut editor, cx).is_none());
574
575 // Set scroll position to check later
576 editor.set_scroll_position(Vector2F::new(5.5, 5.5), cx);
577 let original_scroll_position = editor.scroll_manager.anchor();
578
579 // Jump to the end of the document and adjust scroll
580 editor.move_to_end(&MoveToEnd, cx);
581 editor.set_scroll_position(Vector2F::new(-2.5, -0.5), cx);
582 assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
583
584 let nav_entry = pop_history(&mut editor, cx).unwrap();
585 editor.navigate(nav_entry.data.unwrap(), cx);
586 assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
587
588 // Ensure we don't panic when navigation data contains invalid anchors *and* points.
589 let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
590 invalid_anchor.text_anchor.buffer_id = Some(999);
591 let invalid_point = Point::new(9999, 0);
592 editor.navigate(
593 Box::new(NavigationData {
594 cursor_anchor: invalid_anchor,
595 cursor_position: invalid_point,
596 scroll_anchor: ScrollAnchor {
597 anchor: invalid_anchor,
598 offset: Default::default(),
599 },
600 scroll_top_row: invalid_point.row,
601 }),
602 cx,
603 );
604 assert_eq!(
605 editor.selections.display_ranges(cx),
606 &[editor.max_point(cx)..editor.max_point(cx)]
607 );
608 assert_eq!(
609 editor.scroll_position(cx),
610 vec2f(0., editor.max_point(cx).row() as f32)
611 );
612
613 editor
614 });
615}
616
617#[gpui::test]
618fn test_cancel(cx: &mut TestAppContext) {
619 init_test(cx, |_| {});
620
621 let (_, view) = cx.add_window(|cx| {
622 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
623 build_editor(buffer, cx)
624 });
625
626 view.update(cx, |view, cx| {
627 view.begin_selection(DisplayPoint::new(3, 4), false, 1, cx);
628 view.update_selection(DisplayPoint::new(1, 1), 0, Vector2F::zero(), cx);
629 view.end_selection(cx);
630
631 view.begin_selection(DisplayPoint::new(0, 1), true, 1, cx);
632 view.update_selection(DisplayPoint::new(0, 3), 0, Vector2F::zero(), cx);
633 view.end_selection(cx);
634 assert_eq!(
635 view.selections.display_ranges(cx),
636 [
637 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
638 DisplayPoint::new(3, 4)..DisplayPoint::new(1, 1),
639 ]
640 );
641 });
642
643 view.update(cx, |view, cx| {
644 view.cancel(&Cancel, cx);
645 assert_eq!(
646 view.selections.display_ranges(cx),
647 [DisplayPoint::new(3, 4)..DisplayPoint::new(1, 1)]
648 );
649 });
650
651 view.update(cx, |view, cx| {
652 view.cancel(&Cancel, cx);
653 assert_eq!(
654 view.selections.display_ranges(cx),
655 [DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1)]
656 );
657 });
658}
659
660#[gpui::test]
661fn test_fold_action(cx: &mut TestAppContext) {
662 init_test(cx, |_| {});
663
664 let (_, view) = cx.add_window(|cx| {
665 let buffer = MultiBuffer::build_simple(
666 &"
667 impl Foo {
668 // Hello!
669
670 fn a() {
671 1
672 }
673
674 fn b() {
675 2
676 }
677
678 fn c() {
679 3
680 }
681 }
682 "
683 .unindent(),
684 cx,
685 );
686 build_editor(buffer.clone(), cx)
687 });
688
689 view.update(cx, |view, cx| {
690 view.change_selections(None, cx, |s| {
691 s.select_display_ranges([DisplayPoint::new(8, 0)..DisplayPoint::new(12, 0)]);
692 });
693 view.fold(&Fold, cx);
694 assert_eq!(
695 view.display_text(cx),
696 "
697 impl Foo {
698 // Hello!
699
700 fn a() {
701 1
702 }
703
704 fn b() {⋯
705 }
706
707 fn c() {⋯
708 }
709 }
710 "
711 .unindent(),
712 );
713
714 view.fold(&Fold, cx);
715 assert_eq!(
716 view.display_text(cx),
717 "
718 impl Foo {⋯
719 }
720 "
721 .unindent(),
722 );
723
724 view.unfold_lines(&UnfoldLines, cx);
725 assert_eq!(
726 view.display_text(cx),
727 "
728 impl Foo {
729 // Hello!
730
731 fn a() {
732 1
733 }
734
735 fn b() {⋯
736 }
737
738 fn c() {⋯
739 }
740 }
741 "
742 .unindent(),
743 );
744
745 view.unfold_lines(&UnfoldLines, cx);
746 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
747 });
748}
749
750#[gpui::test]
751fn test_move_cursor(cx: &mut TestAppContext) {
752 init_test(cx, |_| {});
753
754 let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
755 let (_, view) = cx.add_window(|cx| build_editor(buffer.clone(), cx));
756
757 buffer.update(cx, |buffer, cx| {
758 buffer.edit(
759 vec![
760 (Point::new(1, 0)..Point::new(1, 0), "\t"),
761 (Point::new(1, 1)..Point::new(1, 1), "\t"),
762 ],
763 None,
764 cx,
765 );
766 });
767 view.update(cx, |view, cx| {
768 assert_eq!(
769 view.selections.display_ranges(cx),
770 &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
771 );
772
773 view.move_down(&MoveDown, cx);
774 assert_eq!(
775 view.selections.display_ranges(cx),
776 &[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)]
777 );
778
779 view.move_right(&MoveRight, cx);
780 assert_eq!(
781 view.selections.display_ranges(cx),
782 &[DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4)]
783 );
784
785 view.move_left(&MoveLeft, cx);
786 assert_eq!(
787 view.selections.display_ranges(cx),
788 &[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)]
789 );
790
791 view.move_up(&MoveUp, cx);
792 assert_eq!(
793 view.selections.display_ranges(cx),
794 &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
795 );
796
797 view.move_to_end(&MoveToEnd, cx);
798 assert_eq!(
799 view.selections.display_ranges(cx),
800 &[DisplayPoint::new(5, 6)..DisplayPoint::new(5, 6)]
801 );
802
803 view.move_to_beginning(&MoveToBeginning, cx);
804 assert_eq!(
805 view.selections.display_ranges(cx),
806 &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
807 );
808
809 view.change_selections(None, cx, |s| {
810 s.select_display_ranges([DisplayPoint::new(0, 1)..DisplayPoint::new(0, 2)]);
811 });
812 view.select_to_beginning(&SelectToBeginning, cx);
813 assert_eq!(
814 view.selections.display_ranges(cx),
815 &[DisplayPoint::new(0, 1)..DisplayPoint::new(0, 0)]
816 );
817
818 view.select_to_end(&SelectToEnd, cx);
819 assert_eq!(
820 view.selections.display_ranges(cx),
821 &[DisplayPoint::new(0, 1)..DisplayPoint::new(5, 6)]
822 );
823 });
824}
825
826#[gpui::test]
827fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
828 init_test(cx, |_| {});
829
830 let (_, view) = cx.add_window(|cx| {
831 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε\n", cx);
832 build_editor(buffer.clone(), cx)
833 });
834
835 assert_eq!('ⓐ'.len_utf8(), 3);
836 assert_eq!('α'.len_utf8(), 2);
837
838 view.update(cx, |view, cx| {
839 view.fold_ranges(
840 vec![
841 Point::new(0, 6)..Point::new(0, 12),
842 Point::new(1, 2)..Point::new(1, 4),
843 Point::new(2, 4)..Point::new(2, 8),
844 ],
845 true,
846 cx,
847 );
848 assert_eq!(view.display_text(cx), "ⓐⓑ⋯ⓔ\nab⋯e\nαβ⋯ε\n");
849
850 view.move_right(&MoveRight, cx);
851 assert_eq!(
852 view.selections.display_ranges(cx),
853 &[empty_range(0, "ⓐ".len())]
854 );
855 view.move_right(&MoveRight, cx);
856 assert_eq!(
857 view.selections.display_ranges(cx),
858 &[empty_range(0, "ⓐⓑ".len())]
859 );
860 view.move_right(&MoveRight, cx);
861 assert_eq!(
862 view.selections.display_ranges(cx),
863 &[empty_range(0, "ⓐⓑ⋯".len())]
864 );
865
866 view.move_down(&MoveDown, cx);
867 assert_eq!(
868 view.selections.display_ranges(cx),
869 &[empty_range(1, "ab⋯".len())]
870 );
871 view.move_left(&MoveLeft, cx);
872 assert_eq!(
873 view.selections.display_ranges(cx),
874 &[empty_range(1, "ab".len())]
875 );
876 view.move_left(&MoveLeft, cx);
877 assert_eq!(
878 view.selections.display_ranges(cx),
879 &[empty_range(1, "a".len())]
880 );
881
882 view.move_down(&MoveDown, cx);
883 assert_eq!(
884 view.selections.display_ranges(cx),
885 &[empty_range(2, "α".len())]
886 );
887 view.move_right(&MoveRight, cx);
888 assert_eq!(
889 view.selections.display_ranges(cx),
890 &[empty_range(2, "αβ".len())]
891 );
892 view.move_right(&MoveRight, cx);
893 assert_eq!(
894 view.selections.display_ranges(cx),
895 &[empty_range(2, "αβ⋯".len())]
896 );
897 view.move_right(&MoveRight, cx);
898 assert_eq!(
899 view.selections.display_ranges(cx),
900 &[empty_range(2, "αβ⋯ε".len())]
901 );
902
903 view.move_up(&MoveUp, cx);
904 assert_eq!(
905 view.selections.display_ranges(cx),
906 &[empty_range(1, "ab⋯e".len())]
907 );
908 view.move_up(&MoveUp, cx);
909 assert_eq!(
910 view.selections.display_ranges(cx),
911 &[empty_range(0, "ⓐⓑ⋯ⓔ".len())]
912 );
913 view.move_left(&MoveLeft, cx);
914 assert_eq!(
915 view.selections.display_ranges(cx),
916 &[empty_range(0, "ⓐⓑ⋯".len())]
917 );
918 view.move_left(&MoveLeft, cx);
919 assert_eq!(
920 view.selections.display_ranges(cx),
921 &[empty_range(0, "ⓐⓑ".len())]
922 );
923 view.move_left(&MoveLeft, cx);
924 assert_eq!(
925 view.selections.display_ranges(cx),
926 &[empty_range(0, "ⓐ".len())]
927 );
928 });
929}
930
931#[gpui::test]
932fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
933 init_test(cx, |_| {});
934
935 let (_, view) = cx.add_window(|cx| {
936 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
937 build_editor(buffer.clone(), cx)
938 });
939 view.update(cx, |view, cx| {
940 view.change_selections(None, cx, |s| {
941 s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
942 });
943 view.move_down(&MoveDown, cx);
944 assert_eq!(
945 view.selections.display_ranges(cx),
946 &[empty_range(1, "abcd".len())]
947 );
948
949 view.move_down(&MoveDown, cx);
950 assert_eq!(
951 view.selections.display_ranges(cx),
952 &[empty_range(2, "αβγ".len())]
953 );
954
955 view.move_down(&MoveDown, cx);
956 assert_eq!(
957 view.selections.display_ranges(cx),
958 &[empty_range(3, "abcd".len())]
959 );
960
961 view.move_down(&MoveDown, cx);
962 assert_eq!(
963 view.selections.display_ranges(cx),
964 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
965 );
966
967 view.move_up(&MoveUp, cx);
968 assert_eq!(
969 view.selections.display_ranges(cx),
970 &[empty_range(3, "abcd".len())]
971 );
972
973 view.move_up(&MoveUp, cx);
974 assert_eq!(
975 view.selections.display_ranges(cx),
976 &[empty_range(2, "αβγ".len())]
977 );
978 });
979}
980
981#[gpui::test]
982fn test_beginning_end_of_line(cx: &mut TestAppContext) {
983 init_test(cx, |_| {});
984
985 let (_, view) = cx.add_window(|cx| {
986 let buffer = MultiBuffer::build_simple("abc\n def", cx);
987 build_editor(buffer, cx)
988 });
989 view.update(cx, |view, cx| {
990 view.change_selections(None, cx, |s| {
991 s.select_display_ranges([
992 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
993 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4),
994 ]);
995 });
996 });
997
998 view.update(cx, |view, cx| {
999 view.move_to_beginning_of_line(&MoveToBeginningOfLine, cx);
1000 assert_eq!(
1001 view.selections.display_ranges(cx),
1002 &[
1003 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
1004 DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
1005 ]
1006 );
1007 });
1008
1009 view.update(cx, |view, cx| {
1010 view.move_to_beginning_of_line(&MoveToBeginningOfLine, cx);
1011 assert_eq!(
1012 view.selections.display_ranges(cx),
1013 &[
1014 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
1015 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
1016 ]
1017 );
1018 });
1019
1020 view.update(cx, |view, cx| {
1021 view.move_to_beginning_of_line(&MoveToBeginningOfLine, cx);
1022 assert_eq!(
1023 view.selections.display_ranges(cx),
1024 &[
1025 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
1026 DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
1027 ]
1028 );
1029 });
1030
1031 view.update(cx, |view, cx| {
1032 view.move_to_end_of_line(&MoveToEndOfLine, cx);
1033 assert_eq!(
1034 view.selections.display_ranges(cx),
1035 &[
1036 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
1037 DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
1038 ]
1039 );
1040 });
1041
1042 // Moving to the end of line again is a no-op.
1043 view.update(cx, |view, cx| {
1044 view.move_to_end_of_line(&MoveToEndOfLine, cx);
1045 assert_eq!(
1046 view.selections.display_ranges(cx),
1047 &[
1048 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
1049 DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
1050 ]
1051 );
1052 });
1053
1054 view.update(cx, |view, cx| {
1055 view.move_left(&MoveLeft, cx);
1056 view.select_to_beginning_of_line(
1057 &SelectToBeginningOfLine {
1058 stop_at_soft_wraps: true,
1059 },
1060 cx,
1061 );
1062 assert_eq!(
1063 view.selections.display_ranges(cx),
1064 &[
1065 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0),
1066 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 2),
1067 ]
1068 );
1069 });
1070
1071 view.update(cx, |view, cx| {
1072 view.select_to_beginning_of_line(
1073 &SelectToBeginningOfLine {
1074 stop_at_soft_wraps: true,
1075 },
1076 cx,
1077 );
1078 assert_eq!(
1079 view.selections.display_ranges(cx),
1080 &[
1081 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0),
1082 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 0),
1083 ]
1084 );
1085 });
1086
1087 view.update(cx, |view, cx| {
1088 view.select_to_beginning_of_line(
1089 &SelectToBeginningOfLine {
1090 stop_at_soft_wraps: true,
1091 },
1092 cx,
1093 );
1094 assert_eq!(
1095 view.selections.display_ranges(cx),
1096 &[
1097 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0),
1098 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 2),
1099 ]
1100 );
1101 });
1102
1103 view.update(cx, |view, cx| {
1104 view.select_to_end_of_line(
1105 &SelectToEndOfLine {
1106 stop_at_soft_wraps: true,
1107 },
1108 cx,
1109 );
1110 assert_eq!(
1111 view.selections.display_ranges(cx),
1112 &[
1113 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3),
1114 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 5),
1115 ]
1116 );
1117 });
1118
1119 view.update(cx, |view, cx| {
1120 view.delete_to_end_of_line(&DeleteToEndOfLine, cx);
1121 assert_eq!(view.display_text(cx), "ab\n de");
1122 assert_eq!(
1123 view.selections.display_ranges(cx),
1124 &[
1125 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
1126 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4),
1127 ]
1128 );
1129 });
1130
1131 view.update(cx, |view, cx| {
1132 view.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
1133 assert_eq!(view.display_text(cx), "\n");
1134 assert_eq!(
1135 view.selections.display_ranges(cx),
1136 &[
1137 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
1138 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
1139 ]
1140 );
1141 });
1142}
1143
1144#[gpui::test]
1145fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1146 init_test(cx, |_| {});
1147
1148 let (_, view) = cx.add_window(|cx| {
1149 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1150 build_editor(buffer, cx)
1151 });
1152 view.update(cx, |view, cx| {
1153 view.change_selections(None, cx, |s| {
1154 s.select_display_ranges([
1155 DisplayPoint::new(0, 11)..DisplayPoint::new(0, 11),
1156 DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4),
1157 ])
1158 });
1159
1160 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1161 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1162
1163 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1164 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", view, cx);
1165
1166 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1167 assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", view, cx);
1168
1169 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1170 assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1171
1172 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1173 assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", view, cx);
1174
1175 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1176 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", view, cx);
1177
1178 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1179 assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1180
1181 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1182 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1183
1184 view.move_right(&MoveRight, cx);
1185 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1186 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1187
1188 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1189 assert_selection_ranges("use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}", view, cx);
1190
1191 view.select_to_next_word_end(&SelectToNextWordEnd, cx);
1192 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1193 });
1194}
1195
1196#[gpui::test]
1197fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1198 init_test(cx, |_| {});
1199
1200 let (_, view) = cx.add_window(|cx| {
1201 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1202 build_editor(buffer, cx)
1203 });
1204
1205 view.update(cx, |view, cx| {
1206 view.set_wrap_width(Some(140.), cx);
1207 assert_eq!(
1208 view.display_text(cx),
1209 "use one::{\n two::three::\n four::five\n};"
1210 );
1211
1212 view.change_selections(None, cx, |s| {
1213 s.select_display_ranges([DisplayPoint::new(1, 7)..DisplayPoint::new(1, 7)]);
1214 });
1215
1216 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1217 assert_eq!(
1218 view.selections.display_ranges(cx),
1219 &[DisplayPoint::new(1, 9)..DisplayPoint::new(1, 9)]
1220 );
1221
1222 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1223 assert_eq!(
1224 view.selections.display_ranges(cx),
1225 &[DisplayPoint::new(1, 14)..DisplayPoint::new(1, 14)]
1226 );
1227
1228 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1229 assert_eq!(
1230 view.selections.display_ranges(cx),
1231 &[DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4)]
1232 );
1233
1234 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1235 assert_eq!(
1236 view.selections.display_ranges(cx),
1237 &[DisplayPoint::new(2, 8)..DisplayPoint::new(2, 8)]
1238 );
1239
1240 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1241 assert_eq!(
1242 view.selections.display_ranges(cx),
1243 &[DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4)]
1244 );
1245
1246 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1247 assert_eq!(
1248 view.selections.display_ranges(cx),
1249 &[DisplayPoint::new(1, 14)..DisplayPoint::new(1, 14)]
1250 );
1251 });
1252}
1253
1254#[gpui::test]
1255async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) {
1256 init_test(cx, |_| {});
1257 let mut cx = EditorTestContext::new(cx).await;
1258
1259 let line_height = cx.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache()));
1260 cx.simulate_window_resize(cx.window_id, vec2f(100., 4. * line_height));
1261
1262 cx.set_state(
1263 &r#"ˇone
1264 two
1265
1266 three
1267 fourˇ
1268 five
1269
1270 six"#
1271 .unindent(),
1272 );
1273
1274 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1275 cx.assert_editor_state(
1276 &r#"one
1277 two
1278 ˇ
1279 three
1280 four
1281 five
1282 ˇ
1283 six"#
1284 .unindent(),
1285 );
1286
1287 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1288 cx.assert_editor_state(
1289 &r#"one
1290 two
1291
1292 three
1293 four
1294 five
1295 ˇ
1296 sixˇ"#
1297 .unindent(),
1298 );
1299
1300 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1301 cx.assert_editor_state(
1302 &r#"ˇone
1303 two
1304
1305 three
1306 four
1307 five
1308
1309 sixˇ"#
1310 .unindent(),
1311 );
1312
1313 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1314 cx.assert_editor_state(
1315 &r#"ˇone
1316 two
1317 ˇ
1318 three
1319 four
1320 five
1321
1322 six"#
1323 .unindent(),
1324 );
1325
1326 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1327 cx.assert_editor_state(
1328 &r#"ˇone
1329 two
1330
1331 three
1332 four
1333 five
1334
1335 sixˇ"#
1336 .unindent(),
1337 );
1338
1339 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1340 cx.assert_editor_state(
1341 &r#"one
1342 two
1343
1344 three
1345 four
1346 five
1347 ˇ
1348 sixˇ"#
1349 .unindent(),
1350 );
1351
1352 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1353 cx.assert_editor_state(
1354 &r#"one
1355 two
1356 ˇ
1357 three
1358 four
1359 five
1360 ˇ
1361 six"#
1362 .unindent(),
1363 );
1364}
1365
1366#[gpui::test]
1367async fn test_scroll_page_up_page_down(cx: &mut gpui::TestAppContext) {
1368 init_test(cx, |_| {});
1369 let mut cx = EditorTestContext::new(cx).await;
1370 let line_height = cx.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache()));
1371 cx.simulate_window_resize(cx.window_id, vec2f(1000., 4. * line_height + 0.5));
1372
1373 cx.set_state(
1374 &r#"ˇone
1375 two
1376 three
1377 four
1378 five
1379 six
1380 seven
1381 eight
1382 nine
1383 ten
1384 "#,
1385 );
1386
1387 cx.update_editor(|editor, cx| {
1388 assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 0.));
1389 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1390 assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 3.));
1391 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1392 assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 6.));
1393 editor.scroll_screen(&ScrollAmount::Page(-1.), cx);
1394 assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 3.));
1395
1396 editor.scroll_screen(&ScrollAmount::Page(-0.5), cx);
1397 assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 2.));
1398 editor.scroll_screen(&ScrollAmount::Page(0.5), cx);
1399 assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 3.));
1400 });
1401}
1402
1403#[gpui::test]
1404async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
1405 init_test(cx, |_| {});
1406 let mut cx = EditorTestContext::new(cx).await;
1407
1408 let line_height = cx.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache()));
1409 cx.simulate_window_resize(cx.window_id, vec2f(100., 4. * line_height));
1410
1411 cx.set_state(
1412 &r#"
1413 ˇone
1414 two
1415 threeˇ
1416 four
1417 five
1418 six
1419 seven
1420 eight
1421 nine
1422 ten
1423 "#
1424 .unindent(),
1425 );
1426
1427 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
1428 cx.assert_editor_state(
1429 &r#"
1430 one
1431 two
1432 three
1433 ˇfour
1434 five
1435 sixˇ
1436 seven
1437 eight
1438 nine
1439 ten
1440 "#
1441 .unindent(),
1442 );
1443
1444 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
1445 cx.assert_editor_state(
1446 &r#"
1447 one
1448 two
1449 three
1450 four
1451 five
1452 six
1453 ˇseven
1454 eight
1455 nineˇ
1456 ten
1457 "#
1458 .unindent(),
1459 );
1460
1461 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
1462 cx.assert_editor_state(
1463 &r#"
1464 one
1465 two
1466 three
1467 ˇfour
1468 five
1469 sixˇ
1470 seven
1471 eight
1472 nine
1473 ten
1474 "#
1475 .unindent(),
1476 );
1477
1478 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
1479 cx.assert_editor_state(
1480 &r#"
1481 ˇone
1482 two
1483 threeˇ
1484 four
1485 five
1486 six
1487 seven
1488 eight
1489 nine
1490 ten
1491 "#
1492 .unindent(),
1493 );
1494
1495 // Test select collapsing
1496 cx.update_editor(|editor, cx| {
1497 editor.move_page_down(&MovePageDown::default(), cx);
1498 editor.move_page_down(&MovePageDown::default(), cx);
1499 editor.move_page_down(&MovePageDown::default(), cx);
1500 });
1501 cx.assert_editor_state(
1502 &r#"
1503 one
1504 two
1505 three
1506 four
1507 five
1508 six
1509 seven
1510 eight
1511 nine
1512 ˇten
1513 ˇ"#
1514 .unindent(),
1515 );
1516}
1517
1518#[gpui::test]
1519async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
1520 init_test(cx, |_| {});
1521 let mut cx = EditorTestContext::new(cx).await;
1522 cx.set_state("one «two threeˇ» four");
1523 cx.update_editor(|editor, cx| {
1524 editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
1525 assert_eq!(editor.text(cx), " four");
1526 });
1527}
1528
1529#[gpui::test]
1530fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
1531 init_test(cx, |_| {});
1532
1533 let (_, view) = cx.add_window(|cx| {
1534 let buffer = MultiBuffer::build_simple("one two three four", cx);
1535 build_editor(buffer.clone(), cx)
1536 });
1537
1538 view.update(cx, |view, cx| {
1539 view.change_selections(None, cx, |s| {
1540 s.select_display_ranges([
1541 // an empty selection - the preceding word fragment is deleted
1542 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
1543 // characters selected - they are deleted
1544 DisplayPoint::new(0, 9)..DisplayPoint::new(0, 12),
1545 ])
1546 });
1547 view.delete_to_previous_word_start(&DeleteToPreviousWordStart, cx);
1548 assert_eq!(view.buffer.read(cx).read(cx).text(), "e two te four");
1549 });
1550
1551 view.update(cx, |view, cx| {
1552 view.change_selections(None, cx, |s| {
1553 s.select_display_ranges([
1554 // an empty selection - the following word fragment is deleted
1555 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
1556 // characters selected - they are deleted
1557 DisplayPoint::new(0, 9)..DisplayPoint::new(0, 10),
1558 ])
1559 });
1560 view.delete_to_next_word_end(&DeleteToNextWordEnd, cx);
1561 assert_eq!(view.buffer.read(cx).read(cx).text(), "e t te our");
1562 });
1563}
1564
1565#[gpui::test]
1566fn test_newline(cx: &mut TestAppContext) {
1567 init_test(cx, |_| {});
1568
1569 let (_, view) = cx.add_window(|cx| {
1570 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
1571 build_editor(buffer.clone(), cx)
1572 });
1573
1574 view.update(cx, |view, cx| {
1575 view.change_selections(None, cx, |s| {
1576 s.select_display_ranges([
1577 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
1578 DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
1579 DisplayPoint::new(1, 6)..DisplayPoint::new(1, 6),
1580 ])
1581 });
1582
1583 view.newline(&Newline, cx);
1584 assert_eq!(view.text(cx), "aa\naa\n \n bb\n bb\n");
1585 });
1586}
1587
1588#[gpui::test]
1589fn test_newline_with_old_selections(cx: &mut TestAppContext) {
1590 init_test(cx, |_| {});
1591
1592 let (_, editor) = cx.add_window(|cx| {
1593 let buffer = MultiBuffer::build_simple(
1594 "
1595 a
1596 b(
1597 X
1598 )
1599 c(
1600 X
1601 )
1602 "
1603 .unindent()
1604 .as_str(),
1605 cx,
1606 );
1607 let mut editor = build_editor(buffer.clone(), cx);
1608 editor.change_selections(None, cx, |s| {
1609 s.select_ranges([
1610 Point::new(2, 4)..Point::new(2, 5),
1611 Point::new(5, 4)..Point::new(5, 5),
1612 ])
1613 });
1614 editor
1615 });
1616
1617 editor.update(cx, |editor, cx| {
1618 // Edit the buffer directly, deleting ranges surrounding the editor's selections
1619 editor.buffer.update(cx, |buffer, cx| {
1620 buffer.edit(
1621 [
1622 (Point::new(1, 2)..Point::new(3, 0), ""),
1623 (Point::new(4, 2)..Point::new(6, 0), ""),
1624 ],
1625 None,
1626 cx,
1627 );
1628 assert_eq!(
1629 buffer.read(cx).text(),
1630 "
1631 a
1632 b()
1633 c()
1634 "
1635 .unindent()
1636 );
1637 });
1638 assert_eq!(
1639 editor.selections.ranges(cx),
1640 &[
1641 Point::new(1, 2)..Point::new(1, 2),
1642 Point::new(2, 2)..Point::new(2, 2),
1643 ],
1644 );
1645
1646 editor.newline(&Newline, cx);
1647 assert_eq!(
1648 editor.text(cx),
1649 "
1650 a
1651 b(
1652 )
1653 c(
1654 )
1655 "
1656 .unindent()
1657 );
1658
1659 // The selections are moved after the inserted newlines
1660 assert_eq!(
1661 editor.selections.ranges(cx),
1662 &[
1663 Point::new(2, 0)..Point::new(2, 0),
1664 Point::new(4, 0)..Point::new(4, 0),
1665 ],
1666 );
1667 });
1668}
1669
1670#[gpui::test]
1671async fn test_newline_above(cx: &mut gpui::TestAppContext) {
1672 init_test(cx, |settings| {
1673 settings.defaults.tab_size = NonZeroU32::new(4)
1674 });
1675
1676 let language = Arc::new(
1677 Language::new(
1678 LanguageConfig::default(),
1679 Some(tree_sitter_rust::language()),
1680 )
1681 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
1682 .unwrap(),
1683 );
1684
1685 let mut cx = EditorTestContext::new(cx).await;
1686 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
1687 cx.set_state(indoc! {"
1688 const a: ˇA = (
1689 (ˇ
1690 «const_functionˇ»(ˇ),
1691 so«mˇ»et«hˇ»ing_ˇelse,ˇ
1692 )ˇ
1693 ˇ);ˇ
1694 "});
1695
1696 cx.update_editor(|e, cx| e.newline_above(&NewlineAbove, cx));
1697 cx.assert_editor_state(indoc! {"
1698 ˇ
1699 const a: A = (
1700 ˇ
1701 (
1702 ˇ
1703 ˇ
1704 const_function(),
1705 ˇ
1706 ˇ
1707 ˇ
1708 ˇ
1709 something_else,
1710 ˇ
1711 )
1712 ˇ
1713 ˇ
1714 );
1715 "});
1716}
1717
1718#[gpui::test]
1719async fn test_newline_below(cx: &mut gpui::TestAppContext) {
1720 init_test(cx, |settings| {
1721 settings.defaults.tab_size = NonZeroU32::new(4)
1722 });
1723
1724 let language = Arc::new(
1725 Language::new(
1726 LanguageConfig::default(),
1727 Some(tree_sitter_rust::language()),
1728 )
1729 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
1730 .unwrap(),
1731 );
1732
1733 let mut cx = EditorTestContext::new(cx).await;
1734 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
1735 cx.set_state(indoc! {"
1736 const a: ˇA = (
1737 (ˇ
1738 «const_functionˇ»(ˇ),
1739 so«mˇ»et«hˇ»ing_ˇelse,ˇ
1740 )ˇ
1741 ˇ);ˇ
1742 "});
1743
1744 cx.update_editor(|e, cx| e.newline_below(&NewlineBelow, cx));
1745 cx.assert_editor_state(indoc! {"
1746 const a: A = (
1747 ˇ
1748 (
1749 ˇ
1750 const_function(),
1751 ˇ
1752 ˇ
1753 something_else,
1754 ˇ
1755 ˇ
1756 ˇ
1757 ˇ
1758 )
1759 ˇ
1760 );
1761 ˇ
1762 ˇ
1763 "});
1764}
1765
1766#[gpui::test]
1767async fn test_newline_comments(cx: &mut gpui::TestAppContext) {
1768 init_test(cx, |settings| {
1769 settings.defaults.tab_size = NonZeroU32::new(4)
1770 });
1771
1772 let language = Arc::new(Language::new(
1773 LanguageConfig {
1774 line_comment: Some("//".into()),
1775 ..LanguageConfig::default()
1776 },
1777 None,
1778 ));
1779 {
1780 let mut cx = EditorTestContext::new(cx).await;
1781 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
1782 cx.set_state(indoc! {"
1783 // Fooˇ
1784 "});
1785
1786 cx.update_editor(|e, cx| e.newline(&Newline, cx));
1787 cx.assert_editor_state(indoc! {"
1788 // Foo
1789 //ˇ
1790 "});
1791 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
1792 cx.set_state(indoc! {"
1793 ˇ// Foo
1794 "});
1795 cx.update_editor(|e, cx| e.newline(&Newline, cx));
1796 cx.assert_editor_state(indoc! {"
1797
1798 ˇ// Foo
1799 "});
1800 }
1801 // Ensure that comment continuations can be disabled.
1802 update_test_language_settings(cx, |settings| {
1803 settings.defaults.extend_comment_on_newline = Some(false);
1804 });
1805 let mut cx = EditorTestContext::new(cx).await;
1806 cx.set_state(indoc! {"
1807 // Fooˇ
1808 "});
1809 cx.update_editor(|e, cx| e.newline(&Newline, cx));
1810 cx.assert_editor_state(indoc! {"
1811 // Foo
1812 ˇ
1813 "});
1814}
1815
1816#[gpui::test]
1817fn test_insert_with_old_selections(cx: &mut TestAppContext) {
1818 init_test(cx, |_| {});
1819
1820 let (_, editor) = cx.add_window(|cx| {
1821 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
1822 let mut editor = build_editor(buffer.clone(), cx);
1823 editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
1824 editor
1825 });
1826
1827 editor.update(cx, |editor, cx| {
1828 // Edit the buffer directly, deleting ranges surrounding the editor's selections
1829 editor.buffer.update(cx, |buffer, cx| {
1830 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
1831 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
1832 });
1833 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
1834
1835 editor.insert("Z", cx);
1836 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
1837
1838 // The selections are moved after the inserted characters
1839 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
1840 });
1841}
1842
1843#[gpui::test]
1844async fn test_tab(cx: &mut gpui::TestAppContext) {
1845 init_test(cx, |settings| {
1846 settings.defaults.tab_size = NonZeroU32::new(3)
1847 });
1848
1849 let mut cx = EditorTestContext::new(cx).await;
1850 cx.set_state(indoc! {"
1851 ˇabˇc
1852 ˇ🏀ˇ🏀ˇefg
1853 dˇ
1854 "});
1855 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1856 cx.assert_editor_state(indoc! {"
1857 ˇab ˇc
1858 ˇ🏀 ˇ🏀 ˇefg
1859 d ˇ
1860 "});
1861
1862 cx.set_state(indoc! {"
1863 a
1864 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
1865 "});
1866 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1867 cx.assert_editor_state(indoc! {"
1868 a
1869 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
1870 "});
1871}
1872
1873#[gpui::test]
1874async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) {
1875 init_test(cx, |_| {});
1876
1877 let mut cx = EditorTestContext::new(cx).await;
1878 let language = Arc::new(
1879 Language::new(
1880 LanguageConfig::default(),
1881 Some(tree_sitter_rust::language()),
1882 )
1883 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
1884 .unwrap(),
1885 );
1886 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
1887
1888 // cursors that are already at the suggested indent level insert
1889 // a soft tab. cursors that are to the left of the suggested indent
1890 // auto-indent their line.
1891 cx.set_state(indoc! {"
1892 ˇ
1893 const a: B = (
1894 c(
1895 d(
1896 ˇ
1897 )
1898 ˇ
1899 ˇ )
1900 );
1901 "});
1902 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1903 cx.assert_editor_state(indoc! {"
1904 ˇ
1905 const a: B = (
1906 c(
1907 d(
1908 ˇ
1909 )
1910 ˇ
1911 ˇ)
1912 );
1913 "});
1914
1915 // handle auto-indent when there are multiple cursors on the same line
1916 cx.set_state(indoc! {"
1917 const a: B = (
1918 c(
1919 ˇ ˇ
1920 ˇ )
1921 );
1922 "});
1923 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1924 cx.assert_editor_state(indoc! {"
1925 const a: B = (
1926 c(
1927 ˇ
1928 ˇ)
1929 );
1930 "});
1931}
1932
1933#[gpui::test]
1934async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
1935 init_test(cx, |settings| {
1936 settings.defaults.tab_size = NonZeroU32::new(4)
1937 });
1938
1939 let language = Arc::new(
1940 Language::new(
1941 LanguageConfig::default(),
1942 Some(tree_sitter_rust::language()),
1943 )
1944 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
1945 .unwrap(),
1946 );
1947
1948 let mut cx = EditorTestContext::new(cx).await;
1949 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
1950 cx.set_state(indoc! {"
1951 fn a() {
1952 if b {
1953 \t ˇc
1954 }
1955 }
1956 "});
1957
1958 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1959 cx.assert_editor_state(indoc! {"
1960 fn a() {
1961 if b {
1962 ˇc
1963 }
1964 }
1965 "});
1966}
1967
1968#[gpui::test]
1969async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
1970 init_test(cx, |settings| {
1971 settings.defaults.tab_size = NonZeroU32::new(4);
1972 });
1973
1974 let mut cx = EditorTestContext::new(cx).await;
1975
1976 cx.set_state(indoc! {"
1977 «oneˇ» «twoˇ»
1978 three
1979 four
1980 "});
1981 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1982 cx.assert_editor_state(indoc! {"
1983 «oneˇ» «twoˇ»
1984 three
1985 four
1986 "});
1987
1988 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
1989 cx.assert_editor_state(indoc! {"
1990 «oneˇ» «twoˇ»
1991 three
1992 four
1993 "});
1994
1995 // select across line ending
1996 cx.set_state(indoc! {"
1997 one two
1998 t«hree
1999 ˇ» four
2000 "});
2001 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2002 cx.assert_editor_state(indoc! {"
2003 one two
2004 t«hree
2005 ˇ» four
2006 "});
2007
2008 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2009 cx.assert_editor_state(indoc! {"
2010 one two
2011 t«hree
2012 ˇ» four
2013 "});
2014
2015 // Ensure that indenting/outdenting works when the cursor is at column 0.
2016 cx.set_state(indoc! {"
2017 one two
2018 ˇthree
2019 four
2020 "});
2021 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2022 cx.assert_editor_state(indoc! {"
2023 one two
2024 ˇthree
2025 four
2026 "});
2027
2028 cx.set_state(indoc! {"
2029 one two
2030 ˇ three
2031 four
2032 "});
2033 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2034 cx.assert_editor_state(indoc! {"
2035 one two
2036 ˇthree
2037 four
2038 "});
2039}
2040
2041#[gpui::test]
2042async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
2043 init_test(cx, |settings| {
2044 settings.defaults.hard_tabs = Some(true);
2045 });
2046
2047 let mut cx = EditorTestContext::new(cx).await;
2048
2049 // select two ranges on one line
2050 cx.set_state(indoc! {"
2051 «oneˇ» «twoˇ»
2052 three
2053 four
2054 "});
2055 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2056 cx.assert_editor_state(indoc! {"
2057 \t«oneˇ» «twoˇ»
2058 three
2059 four
2060 "});
2061 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2062 cx.assert_editor_state(indoc! {"
2063 \t\t«oneˇ» «twoˇ»
2064 three
2065 four
2066 "});
2067 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2068 cx.assert_editor_state(indoc! {"
2069 \t«oneˇ» «twoˇ»
2070 three
2071 four
2072 "});
2073 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2074 cx.assert_editor_state(indoc! {"
2075 «oneˇ» «twoˇ»
2076 three
2077 four
2078 "});
2079
2080 // select across a line ending
2081 cx.set_state(indoc! {"
2082 one two
2083 t«hree
2084 ˇ»four
2085 "});
2086 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2087 cx.assert_editor_state(indoc! {"
2088 one two
2089 \tt«hree
2090 ˇ»four
2091 "});
2092 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2093 cx.assert_editor_state(indoc! {"
2094 one two
2095 \t\tt«hree
2096 ˇ»four
2097 "});
2098 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2099 cx.assert_editor_state(indoc! {"
2100 one two
2101 \tt«hree
2102 ˇ»four
2103 "});
2104 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2105 cx.assert_editor_state(indoc! {"
2106 one two
2107 t«hree
2108 ˇ»four
2109 "});
2110
2111 // Ensure that indenting/outdenting works when the cursor is at column 0.
2112 cx.set_state(indoc! {"
2113 one two
2114 ˇthree
2115 four
2116 "});
2117 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2118 cx.assert_editor_state(indoc! {"
2119 one two
2120 ˇthree
2121 four
2122 "});
2123 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2124 cx.assert_editor_state(indoc! {"
2125 one two
2126 \tˇthree
2127 four
2128 "});
2129 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2130 cx.assert_editor_state(indoc! {"
2131 one two
2132 ˇthree
2133 four
2134 "});
2135}
2136
2137#[gpui::test]
2138fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
2139 init_test(cx, |settings| {
2140 settings.languages.extend([
2141 (
2142 "TOML".into(),
2143 LanguageSettingsContent {
2144 tab_size: NonZeroU32::new(2),
2145 ..Default::default()
2146 },
2147 ),
2148 (
2149 "Rust".into(),
2150 LanguageSettingsContent {
2151 tab_size: NonZeroU32::new(4),
2152 ..Default::default()
2153 },
2154 ),
2155 ]);
2156 });
2157
2158 let toml_language = Arc::new(Language::new(
2159 LanguageConfig {
2160 name: "TOML".into(),
2161 ..Default::default()
2162 },
2163 None,
2164 ));
2165 let rust_language = Arc::new(Language::new(
2166 LanguageConfig {
2167 name: "Rust".into(),
2168 ..Default::default()
2169 },
2170 None,
2171 ));
2172
2173 let toml_buffer =
2174 cx.add_model(|cx| Buffer::new(0, "a = 1\nb = 2\n", cx).with_language(toml_language, cx));
2175 let rust_buffer = cx.add_model(|cx| {
2176 Buffer::new(0, "const c: usize = 3;\n", cx).with_language(rust_language, cx)
2177 });
2178 let multibuffer = cx.add_model(|cx| {
2179 let mut multibuffer = MultiBuffer::new(0);
2180 multibuffer.push_excerpts(
2181 toml_buffer.clone(),
2182 [ExcerptRange {
2183 context: Point::new(0, 0)..Point::new(2, 0),
2184 primary: None,
2185 }],
2186 cx,
2187 );
2188 multibuffer.push_excerpts(
2189 rust_buffer.clone(),
2190 [ExcerptRange {
2191 context: Point::new(0, 0)..Point::new(1, 0),
2192 primary: None,
2193 }],
2194 cx,
2195 );
2196 multibuffer
2197 });
2198
2199 cx.add_window(|cx| {
2200 let mut editor = build_editor(multibuffer, cx);
2201
2202 assert_eq!(
2203 editor.text(cx),
2204 indoc! {"
2205 a = 1
2206 b = 2
2207
2208 const c: usize = 3;
2209 "}
2210 );
2211
2212 select_ranges(
2213 &mut editor,
2214 indoc! {"
2215 «aˇ» = 1
2216 b = 2
2217
2218 «const c:ˇ» usize = 3;
2219 "},
2220 cx,
2221 );
2222
2223 editor.tab(&Tab, cx);
2224 assert_text_with_selections(
2225 &mut editor,
2226 indoc! {"
2227 «aˇ» = 1
2228 b = 2
2229
2230 «const c:ˇ» usize = 3;
2231 "},
2232 cx,
2233 );
2234 editor.tab_prev(&TabPrev, cx);
2235 assert_text_with_selections(
2236 &mut editor,
2237 indoc! {"
2238 «aˇ» = 1
2239 b = 2
2240
2241 «const c:ˇ» usize = 3;
2242 "},
2243 cx,
2244 );
2245
2246 editor
2247 });
2248}
2249
2250#[gpui::test]
2251async fn test_backspace(cx: &mut gpui::TestAppContext) {
2252 init_test(cx, |_| {});
2253
2254 let mut cx = EditorTestContext::new(cx).await;
2255
2256 // Basic backspace
2257 cx.set_state(indoc! {"
2258 onˇe two three
2259 fou«rˇ» five six
2260 seven «ˇeight nine
2261 »ten
2262 "});
2263 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2264 cx.assert_editor_state(indoc! {"
2265 oˇe two three
2266 fouˇ five six
2267 seven ˇten
2268 "});
2269
2270 // Test backspace inside and around indents
2271 cx.set_state(indoc! {"
2272 zero
2273 ˇone
2274 ˇtwo
2275 ˇ ˇ ˇ three
2276 ˇ ˇ four
2277 "});
2278 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2279 cx.assert_editor_state(indoc! {"
2280 zero
2281 ˇone
2282 ˇtwo
2283 ˇ threeˇ four
2284 "});
2285
2286 // Test backspace with line_mode set to true
2287 cx.update_editor(|e, _| e.selections.line_mode = true);
2288 cx.set_state(indoc! {"
2289 The ˇquick ˇbrown
2290 fox jumps over
2291 the lazy dog
2292 ˇThe qu«ick bˇ»rown"});
2293 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2294 cx.assert_editor_state(indoc! {"
2295 ˇfox jumps over
2296 the lazy dogˇ"});
2297}
2298
2299#[gpui::test]
2300async fn test_delete(cx: &mut gpui::TestAppContext) {
2301 init_test(cx, |_| {});
2302
2303 let mut cx = EditorTestContext::new(cx).await;
2304 cx.set_state(indoc! {"
2305 onˇe two three
2306 fou«rˇ» five six
2307 seven «ˇeight nine
2308 »ten
2309 "});
2310 cx.update_editor(|e, cx| e.delete(&Delete, cx));
2311 cx.assert_editor_state(indoc! {"
2312 onˇ two three
2313 fouˇ five six
2314 seven ˇten
2315 "});
2316
2317 // Test backspace with line_mode set to true
2318 cx.update_editor(|e, _| e.selections.line_mode = true);
2319 cx.set_state(indoc! {"
2320 The ˇquick ˇbrown
2321 fox «ˇjum»ps over
2322 the lazy dog
2323 ˇThe qu«ick bˇ»rown"});
2324 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2325 cx.assert_editor_state("ˇthe lazy dogˇ");
2326}
2327
2328#[gpui::test]
2329fn test_delete_line(cx: &mut TestAppContext) {
2330 init_test(cx, |_| {});
2331
2332 let (_, view) = cx.add_window(|cx| {
2333 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2334 build_editor(buffer, cx)
2335 });
2336 view.update(cx, |view, cx| {
2337 view.change_selections(None, cx, |s| {
2338 s.select_display_ranges([
2339 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
2340 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
2341 DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
2342 ])
2343 });
2344 view.delete_line(&DeleteLine, cx);
2345 assert_eq!(view.display_text(cx), "ghi");
2346 assert_eq!(
2347 view.selections.display_ranges(cx),
2348 vec![
2349 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
2350 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)
2351 ]
2352 );
2353 });
2354
2355 let (_, view) = cx.add_window(|cx| {
2356 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2357 build_editor(buffer, cx)
2358 });
2359 view.update(cx, |view, cx| {
2360 view.change_selections(None, cx, |s| {
2361 s.select_display_ranges([DisplayPoint::new(2, 0)..DisplayPoint::new(0, 1)])
2362 });
2363 view.delete_line(&DeleteLine, cx);
2364 assert_eq!(view.display_text(cx), "ghi\n");
2365 assert_eq!(
2366 view.selections.display_ranges(cx),
2367 vec![DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)]
2368 );
2369 });
2370}
2371
2372#[gpui::test]
2373fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
2374 init_test(cx, |_| {});
2375
2376 cx.add_window(|cx| {
2377 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
2378 let mut editor = build_editor(buffer.clone(), cx);
2379 let buffer = buffer.read(cx).as_singleton().unwrap();
2380
2381 assert_eq!(
2382 editor.selections.ranges::<Point>(cx),
2383 &[Point::new(0, 0)..Point::new(0, 0)]
2384 );
2385
2386 // When on single line, replace newline at end by space
2387 editor.join_lines(&JoinLines, cx);
2388 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
2389 assert_eq!(
2390 editor.selections.ranges::<Point>(cx),
2391 &[Point::new(0, 3)..Point::new(0, 3)]
2392 );
2393
2394 // When multiple lines are selected, remove newlines that are spanned by the selection
2395 editor.change_selections(None, cx, |s| {
2396 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
2397 });
2398 editor.join_lines(&JoinLines, cx);
2399 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
2400 assert_eq!(
2401 editor.selections.ranges::<Point>(cx),
2402 &[Point::new(0, 11)..Point::new(0, 11)]
2403 );
2404
2405 // Undo should be transactional
2406 editor.undo(&Undo, cx);
2407 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
2408 assert_eq!(
2409 editor.selections.ranges::<Point>(cx),
2410 &[Point::new(0, 5)..Point::new(2, 2)]
2411 );
2412
2413 // When joining an empty line don't insert a space
2414 editor.change_selections(None, cx, |s| {
2415 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
2416 });
2417 editor.join_lines(&JoinLines, cx);
2418 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
2419 assert_eq!(
2420 editor.selections.ranges::<Point>(cx),
2421 [Point::new(2, 3)..Point::new(2, 3)]
2422 );
2423
2424 // We can remove trailing newlines
2425 editor.join_lines(&JoinLines, cx);
2426 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
2427 assert_eq!(
2428 editor.selections.ranges::<Point>(cx),
2429 [Point::new(2, 3)..Point::new(2, 3)]
2430 );
2431
2432 // We don't blow up on the last line
2433 editor.join_lines(&JoinLines, cx);
2434 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
2435 assert_eq!(
2436 editor.selections.ranges::<Point>(cx),
2437 [Point::new(2, 3)..Point::new(2, 3)]
2438 );
2439
2440 // reset to test indentation
2441 editor.buffer.update(cx, |buffer, cx| {
2442 buffer.edit(
2443 [
2444 (Point::new(1, 0)..Point::new(1, 2), " "),
2445 (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
2446 ],
2447 None,
2448 cx,
2449 )
2450 });
2451
2452 // We remove any leading spaces
2453 assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
2454 editor.change_selections(None, cx, |s| {
2455 s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
2456 });
2457 editor.join_lines(&JoinLines, cx);
2458 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
2459
2460 // We don't insert a space for a line containing only spaces
2461 editor.join_lines(&JoinLines, cx);
2462 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
2463
2464 // We ignore any leading tabs
2465 editor.join_lines(&JoinLines, cx);
2466 assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
2467
2468 editor
2469 });
2470}
2471
2472#[gpui::test]
2473fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
2474 init_test(cx, |_| {});
2475
2476 cx.add_window(|cx| {
2477 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
2478 let mut editor = build_editor(buffer.clone(), cx);
2479 let buffer = buffer.read(cx).as_singleton().unwrap();
2480
2481 editor.change_selections(None, cx, |s| {
2482 s.select_ranges([
2483 Point::new(0, 2)..Point::new(1, 1),
2484 Point::new(1, 2)..Point::new(1, 2),
2485 Point::new(3, 1)..Point::new(3, 2),
2486 ])
2487 });
2488
2489 editor.join_lines(&JoinLines, cx);
2490 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
2491
2492 assert_eq!(
2493 editor.selections.ranges::<Point>(cx),
2494 [
2495 Point::new(0, 7)..Point::new(0, 7),
2496 Point::new(1, 3)..Point::new(1, 3)
2497 ]
2498 );
2499 editor
2500 });
2501}
2502
2503#[gpui::test]
2504async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
2505 init_test(cx, |_| {});
2506
2507 let mut cx = EditorTestContext::new(cx).await;
2508
2509 // Test sort_lines_case_insensitive()
2510 cx.set_state(indoc! {"
2511 «z
2512 y
2513 x
2514 Z
2515 Y
2516 Xˇ»
2517 "});
2518 cx.update_editor(|e, cx| e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, cx));
2519 cx.assert_editor_state(indoc! {"
2520 «x
2521 X
2522 y
2523 Y
2524 z
2525 Zˇ»
2526 "});
2527
2528 // Test reverse_lines()
2529 cx.set_state(indoc! {"
2530 «5
2531 4
2532 3
2533 2
2534 1ˇ»
2535 "});
2536 cx.update_editor(|e, cx| e.reverse_lines(&ReverseLines, cx));
2537 cx.assert_editor_state(indoc! {"
2538 «1
2539 2
2540 3
2541 4
2542 5ˇ»
2543 "});
2544
2545 // Skip testing shuffle_line()
2546
2547 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
2548 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
2549
2550 // Don't manipulate when cursor is on single line, but expand the selection
2551 cx.set_state(indoc! {"
2552 ddˇdd
2553 ccc
2554 bb
2555 a
2556 "});
2557 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
2558 cx.assert_editor_state(indoc! {"
2559 «ddddˇ»
2560 ccc
2561 bb
2562 a
2563 "});
2564
2565 // Basic manipulate case
2566 // Start selection moves to column 0
2567 // End of selection shrinks to fit shorter line
2568 cx.set_state(indoc! {"
2569 dd«d
2570 ccc
2571 bb
2572 aaaaaˇ»
2573 "});
2574 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
2575 cx.assert_editor_state(indoc! {"
2576 «aaaaa
2577 bb
2578 ccc
2579 dddˇ»
2580 "});
2581
2582 // Manipulate case with newlines
2583 cx.set_state(indoc! {"
2584 dd«d
2585 ccc
2586
2587 bb
2588 aaaaa
2589
2590 ˇ»
2591 "});
2592 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
2593 cx.assert_editor_state(indoc! {"
2594 «
2595
2596 aaaaa
2597 bb
2598 ccc
2599 dddˇ»
2600
2601 "});
2602}
2603
2604#[gpui::test]
2605async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
2606 init_test(cx, |_| {});
2607
2608 let mut cx = EditorTestContext::new(cx).await;
2609
2610 // Manipulate with multiple selections on a single line
2611 cx.set_state(indoc! {"
2612 dd«dd
2613 cˇ»c«c
2614 bb
2615 aaaˇ»aa
2616 "});
2617 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
2618 cx.assert_editor_state(indoc! {"
2619 «aaaaa
2620 bb
2621 ccc
2622 ddddˇ»
2623 "});
2624
2625 // Manipulate with multiple disjoin selections
2626 cx.set_state(indoc! {"
2627 5«
2628 4
2629 3
2630 2
2631 1ˇ»
2632
2633 dd«dd
2634 ccc
2635 bb
2636 aaaˇ»aa
2637 "});
2638 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
2639 cx.assert_editor_state(indoc! {"
2640 «1
2641 2
2642 3
2643 4
2644 5ˇ»
2645
2646 «aaaaa
2647 bb
2648 ccc
2649 ddddˇ»
2650 "});
2651}
2652
2653#[gpui::test]
2654fn test_duplicate_line(cx: &mut TestAppContext) {
2655 init_test(cx, |_| {});
2656
2657 let (_, view) = cx.add_window(|cx| {
2658 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2659 build_editor(buffer, cx)
2660 });
2661 view.update(cx, |view, cx| {
2662 view.change_selections(None, cx, |s| {
2663 s.select_display_ranges([
2664 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
2665 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
2666 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
2667 DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
2668 ])
2669 });
2670 view.duplicate_line(&DuplicateLine, cx);
2671 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
2672 assert_eq!(
2673 view.selections.display_ranges(cx),
2674 vec![
2675 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
2676 DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
2677 DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
2678 DisplayPoint::new(6, 0)..DisplayPoint::new(6, 0),
2679 ]
2680 );
2681 });
2682
2683 let (_, view) = cx.add_window(|cx| {
2684 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2685 build_editor(buffer, cx)
2686 });
2687 view.update(cx, |view, cx| {
2688 view.change_selections(None, cx, |s| {
2689 s.select_display_ranges([
2690 DisplayPoint::new(0, 1)..DisplayPoint::new(1, 1),
2691 DisplayPoint::new(1, 2)..DisplayPoint::new(2, 1),
2692 ])
2693 });
2694 view.duplicate_line(&DuplicateLine, cx);
2695 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
2696 assert_eq!(
2697 view.selections.display_ranges(cx),
2698 vec![
2699 DisplayPoint::new(3, 1)..DisplayPoint::new(4, 1),
2700 DisplayPoint::new(4, 2)..DisplayPoint::new(5, 1),
2701 ]
2702 );
2703 });
2704}
2705
2706#[gpui::test]
2707fn test_move_line_up_down(cx: &mut TestAppContext) {
2708 init_test(cx, |_| {});
2709
2710 let (_, view) = cx.add_window(|cx| {
2711 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
2712 build_editor(buffer, cx)
2713 });
2714 view.update(cx, |view, cx| {
2715 view.fold_ranges(
2716 vec![
2717 Point::new(0, 2)..Point::new(1, 2),
2718 Point::new(2, 3)..Point::new(4, 1),
2719 Point::new(7, 0)..Point::new(8, 4),
2720 ],
2721 true,
2722 cx,
2723 );
2724 view.change_selections(None, cx, |s| {
2725 s.select_display_ranges([
2726 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
2727 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
2728 DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
2729 DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2),
2730 ])
2731 });
2732 assert_eq!(
2733 view.display_text(cx),
2734 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
2735 );
2736
2737 view.move_line_up(&MoveLineUp, cx);
2738 assert_eq!(
2739 view.display_text(cx),
2740 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
2741 );
2742 assert_eq!(
2743 view.selections.display_ranges(cx),
2744 vec![
2745 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
2746 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
2747 DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3),
2748 DisplayPoint::new(4, 0)..DisplayPoint::new(4, 2)
2749 ]
2750 );
2751 });
2752
2753 view.update(cx, |view, cx| {
2754 view.move_line_down(&MoveLineDown, cx);
2755 assert_eq!(
2756 view.display_text(cx),
2757 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
2758 );
2759 assert_eq!(
2760 view.selections.display_ranges(cx),
2761 vec![
2762 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
2763 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
2764 DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
2765 DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2)
2766 ]
2767 );
2768 });
2769
2770 view.update(cx, |view, cx| {
2771 view.move_line_down(&MoveLineDown, cx);
2772 assert_eq!(
2773 view.display_text(cx),
2774 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
2775 );
2776 assert_eq!(
2777 view.selections.display_ranges(cx),
2778 vec![
2779 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
2780 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
2781 DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
2782 DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2)
2783 ]
2784 );
2785 });
2786
2787 view.update(cx, |view, cx| {
2788 view.move_line_up(&MoveLineUp, cx);
2789 assert_eq!(
2790 view.display_text(cx),
2791 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
2792 );
2793 assert_eq!(
2794 view.selections.display_ranges(cx),
2795 vec![
2796 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
2797 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
2798 DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3),
2799 DisplayPoint::new(4, 0)..DisplayPoint::new(4, 2)
2800 ]
2801 );
2802 });
2803}
2804
2805#[gpui::test]
2806fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
2807 init_test(cx, |_| {});
2808
2809 let (_, editor) = cx.add_window(|cx| {
2810 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
2811 build_editor(buffer, cx)
2812 });
2813 editor.update(cx, |editor, cx| {
2814 let snapshot = editor.buffer.read(cx).snapshot(cx);
2815 editor.insert_blocks(
2816 [BlockProperties {
2817 style: BlockStyle::Fixed,
2818 position: snapshot.anchor_after(Point::new(2, 0)),
2819 disposition: BlockDisposition::Below,
2820 height: 1,
2821 render: Arc::new(|_| Empty::new().into_any()),
2822 }],
2823 Some(Autoscroll::fit()),
2824 cx,
2825 );
2826 editor.change_selections(None, cx, |s| {
2827 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
2828 });
2829 editor.move_line_down(&MoveLineDown, cx);
2830 });
2831}
2832
2833#[gpui::test]
2834fn test_transpose(cx: &mut TestAppContext) {
2835 init_test(cx, |_| {});
2836
2837 _ = cx
2838 .add_window(|cx| {
2839 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
2840
2841 editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
2842 editor.transpose(&Default::default(), cx);
2843 assert_eq!(editor.text(cx), "bac");
2844 assert_eq!(editor.selections.ranges(cx), [2..2]);
2845
2846 editor.transpose(&Default::default(), cx);
2847 assert_eq!(editor.text(cx), "bca");
2848 assert_eq!(editor.selections.ranges(cx), [3..3]);
2849
2850 editor.transpose(&Default::default(), cx);
2851 assert_eq!(editor.text(cx), "bac");
2852 assert_eq!(editor.selections.ranges(cx), [3..3]);
2853
2854 editor
2855 })
2856 .1;
2857
2858 _ = cx
2859 .add_window(|cx| {
2860 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
2861
2862 editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
2863 editor.transpose(&Default::default(), cx);
2864 assert_eq!(editor.text(cx), "acb\nde");
2865 assert_eq!(editor.selections.ranges(cx), [3..3]);
2866
2867 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
2868 editor.transpose(&Default::default(), cx);
2869 assert_eq!(editor.text(cx), "acbd\ne");
2870 assert_eq!(editor.selections.ranges(cx), [5..5]);
2871
2872 editor.transpose(&Default::default(), cx);
2873 assert_eq!(editor.text(cx), "acbde\n");
2874 assert_eq!(editor.selections.ranges(cx), [6..6]);
2875
2876 editor.transpose(&Default::default(), cx);
2877 assert_eq!(editor.text(cx), "acbd\ne");
2878 assert_eq!(editor.selections.ranges(cx), [6..6]);
2879
2880 editor
2881 })
2882 .1;
2883
2884 _ = cx
2885 .add_window(|cx| {
2886 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
2887
2888 editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
2889 editor.transpose(&Default::default(), cx);
2890 assert_eq!(editor.text(cx), "bacd\ne");
2891 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
2892
2893 editor.transpose(&Default::default(), cx);
2894 assert_eq!(editor.text(cx), "bcade\n");
2895 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
2896
2897 editor.transpose(&Default::default(), cx);
2898 assert_eq!(editor.text(cx), "bcda\ne");
2899 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
2900
2901 editor.transpose(&Default::default(), cx);
2902 assert_eq!(editor.text(cx), "bcade\n");
2903 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
2904
2905 editor.transpose(&Default::default(), cx);
2906 assert_eq!(editor.text(cx), "bcaed\n");
2907 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
2908
2909 editor
2910 })
2911 .1;
2912
2913 _ = cx
2914 .add_window(|cx| {
2915 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
2916
2917 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
2918 editor.transpose(&Default::default(), cx);
2919 assert_eq!(editor.text(cx), "🏀🍐✋");
2920 assert_eq!(editor.selections.ranges(cx), [8..8]);
2921
2922 editor.transpose(&Default::default(), cx);
2923 assert_eq!(editor.text(cx), "🏀✋🍐");
2924 assert_eq!(editor.selections.ranges(cx), [11..11]);
2925
2926 editor.transpose(&Default::default(), cx);
2927 assert_eq!(editor.text(cx), "🏀🍐✋");
2928 assert_eq!(editor.selections.ranges(cx), [11..11]);
2929
2930 editor
2931 })
2932 .1;
2933}
2934
2935#[gpui::test]
2936async fn test_clipboard(cx: &mut gpui::TestAppContext) {
2937 init_test(cx, |_| {});
2938
2939 let mut cx = EditorTestContext::new(cx).await;
2940
2941 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
2942 cx.update_editor(|e, cx| e.cut(&Cut, cx));
2943 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
2944
2945 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
2946 cx.set_state("two ˇfour ˇsix ˇ");
2947 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2948 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
2949
2950 // Paste again but with only two cursors. Since the number of cursors doesn't
2951 // match the number of slices in the clipboard, the entire clipboard text
2952 // is pasted at each cursor.
2953 cx.set_state("ˇtwo one✅ four three six five ˇ");
2954 cx.update_editor(|e, cx| {
2955 e.handle_input("( ", cx);
2956 e.paste(&Paste, cx);
2957 e.handle_input(") ", cx);
2958 });
2959 cx.assert_editor_state(
2960 &([
2961 "( one✅ ",
2962 "three ",
2963 "five ) ˇtwo one✅ four three six five ( one✅ ",
2964 "three ",
2965 "five ) ˇ",
2966 ]
2967 .join("\n")),
2968 );
2969
2970 // Cut with three selections, one of which is full-line.
2971 cx.set_state(indoc! {"
2972 1«2ˇ»3
2973 4ˇ567
2974 «8ˇ»9"});
2975 cx.update_editor(|e, cx| e.cut(&Cut, cx));
2976 cx.assert_editor_state(indoc! {"
2977 1ˇ3
2978 ˇ9"});
2979
2980 // Paste with three selections, noticing how the copied selection that was full-line
2981 // gets inserted before the second cursor.
2982 cx.set_state(indoc! {"
2983 1ˇ3
2984 9ˇ
2985 «oˇ»ne"});
2986 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2987 cx.assert_editor_state(indoc! {"
2988 12ˇ3
2989 4567
2990 9ˇ
2991 8ˇne"});
2992
2993 // Copy with a single cursor only, which writes the whole line into the clipboard.
2994 cx.set_state(indoc! {"
2995 The quick brown
2996 fox juˇmps over
2997 the lazy dog"});
2998 cx.update_editor(|e, cx| e.copy(&Copy, cx));
2999 cx.cx.assert_clipboard_content(Some("fox jumps over\n"));
3000
3001 // Paste with three selections, noticing how the copied full-line selection is inserted
3002 // before the empty selections but replaces the selection that is non-empty.
3003 cx.set_state(indoc! {"
3004 Tˇhe quick brown
3005 «foˇ»x jumps over
3006 tˇhe lazy dog"});
3007 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3008 cx.assert_editor_state(indoc! {"
3009 fox jumps over
3010 Tˇhe quick brown
3011 fox jumps over
3012 ˇx jumps over
3013 fox jumps over
3014 tˇhe lazy dog"});
3015}
3016
3017#[gpui::test]
3018async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
3019 init_test(cx, |_| {});
3020
3021 let mut cx = EditorTestContext::new(cx).await;
3022 let language = Arc::new(Language::new(
3023 LanguageConfig::default(),
3024 Some(tree_sitter_rust::language()),
3025 ));
3026 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
3027
3028 // Cut an indented block, without the leading whitespace.
3029 cx.set_state(indoc! {"
3030 const a: B = (
3031 c(),
3032 «d(
3033 e,
3034 f
3035 )ˇ»
3036 );
3037 "});
3038 cx.update_editor(|e, cx| e.cut(&Cut, cx));
3039 cx.assert_editor_state(indoc! {"
3040 const a: B = (
3041 c(),
3042 ˇ
3043 );
3044 "});
3045
3046 // Paste it at the same position.
3047 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3048 cx.assert_editor_state(indoc! {"
3049 const a: B = (
3050 c(),
3051 d(
3052 e,
3053 f
3054 )ˇ
3055 );
3056 "});
3057
3058 // Paste it at a line with a lower indent level.
3059 cx.set_state(indoc! {"
3060 ˇ
3061 const a: B = (
3062 c(),
3063 );
3064 "});
3065 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3066 cx.assert_editor_state(indoc! {"
3067 d(
3068 e,
3069 f
3070 )ˇ
3071 const a: B = (
3072 c(),
3073 );
3074 "});
3075
3076 // Cut an indented block, with the leading whitespace.
3077 cx.set_state(indoc! {"
3078 const a: B = (
3079 c(),
3080 « d(
3081 e,
3082 f
3083 )
3084 ˇ»);
3085 "});
3086 cx.update_editor(|e, cx| e.cut(&Cut, cx));
3087 cx.assert_editor_state(indoc! {"
3088 const a: B = (
3089 c(),
3090 ˇ);
3091 "});
3092
3093 // Paste it at the same position.
3094 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3095 cx.assert_editor_state(indoc! {"
3096 const a: B = (
3097 c(),
3098 d(
3099 e,
3100 f
3101 )
3102 ˇ);
3103 "});
3104
3105 // Paste it at a line with a higher indent level.
3106 cx.set_state(indoc! {"
3107 const a: B = (
3108 c(),
3109 d(
3110 e,
3111 fˇ
3112 )
3113 );
3114 "});
3115 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3116 cx.assert_editor_state(indoc! {"
3117 const a: B = (
3118 c(),
3119 d(
3120 e,
3121 f d(
3122 e,
3123 f
3124 )
3125 ˇ
3126 )
3127 );
3128 "});
3129}
3130
3131#[gpui::test]
3132fn test_select_all(cx: &mut TestAppContext) {
3133 init_test(cx, |_| {});
3134
3135 let (_, view) = cx.add_window(|cx| {
3136 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
3137 build_editor(buffer, cx)
3138 });
3139 view.update(cx, |view, cx| {
3140 view.select_all(&SelectAll, cx);
3141 assert_eq!(
3142 view.selections.display_ranges(cx),
3143 &[DisplayPoint::new(0, 0)..DisplayPoint::new(2, 3)]
3144 );
3145 });
3146}
3147
3148#[gpui::test]
3149fn test_select_line(cx: &mut TestAppContext) {
3150 init_test(cx, |_| {});
3151
3152 let (_, view) = cx.add_window(|cx| {
3153 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
3154 build_editor(buffer, cx)
3155 });
3156 view.update(cx, |view, cx| {
3157 view.change_selections(None, cx, |s| {
3158 s.select_display_ranges([
3159 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
3160 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
3161 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
3162 DisplayPoint::new(4, 2)..DisplayPoint::new(4, 2),
3163 ])
3164 });
3165 view.select_line(&SelectLine, cx);
3166 assert_eq!(
3167 view.selections.display_ranges(cx),
3168 vec![
3169 DisplayPoint::new(0, 0)..DisplayPoint::new(2, 0),
3170 DisplayPoint::new(4, 0)..DisplayPoint::new(5, 0),
3171 ]
3172 );
3173 });
3174
3175 view.update(cx, |view, cx| {
3176 view.select_line(&SelectLine, cx);
3177 assert_eq!(
3178 view.selections.display_ranges(cx),
3179 vec![
3180 DisplayPoint::new(0, 0)..DisplayPoint::new(3, 0),
3181 DisplayPoint::new(4, 0)..DisplayPoint::new(5, 5),
3182 ]
3183 );
3184 });
3185
3186 view.update(cx, |view, cx| {
3187 view.select_line(&SelectLine, cx);
3188 assert_eq!(
3189 view.selections.display_ranges(cx),
3190 vec![DisplayPoint::new(0, 0)..DisplayPoint::new(5, 5)]
3191 );
3192 });
3193}
3194
3195#[gpui::test]
3196fn test_split_selection_into_lines(cx: &mut TestAppContext) {
3197 init_test(cx, |_| {});
3198
3199 let (_, view) = cx.add_window(|cx| {
3200 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
3201 build_editor(buffer, cx)
3202 });
3203 view.update(cx, |view, cx| {
3204 view.fold_ranges(
3205 vec![
3206 Point::new(0, 2)..Point::new(1, 2),
3207 Point::new(2, 3)..Point::new(4, 1),
3208 Point::new(7, 0)..Point::new(8, 4),
3209 ],
3210 true,
3211 cx,
3212 );
3213 view.change_selections(None, cx, |s| {
3214 s.select_display_ranges([
3215 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
3216 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
3217 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
3218 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
3219 ])
3220 });
3221 assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i");
3222 });
3223
3224 view.update(cx, |view, cx| {
3225 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
3226 assert_eq!(
3227 view.display_text(cx),
3228 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
3229 );
3230 assert_eq!(
3231 view.selections.display_ranges(cx),
3232 [
3233 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
3234 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
3235 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 0),
3236 DisplayPoint::new(5, 4)..DisplayPoint::new(5, 4)
3237 ]
3238 );
3239 });
3240
3241 view.update(cx, |view, cx| {
3242 view.change_selections(None, cx, |s| {
3243 s.select_display_ranges([DisplayPoint::new(5, 0)..DisplayPoint::new(0, 1)])
3244 });
3245 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
3246 assert_eq!(
3247 view.display_text(cx),
3248 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
3249 );
3250 assert_eq!(
3251 view.selections.display_ranges(cx),
3252 [
3253 DisplayPoint::new(0, 5)..DisplayPoint::new(0, 5),
3254 DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
3255 DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
3256 DisplayPoint::new(3, 5)..DisplayPoint::new(3, 5),
3257 DisplayPoint::new(4, 5)..DisplayPoint::new(4, 5),
3258 DisplayPoint::new(5, 5)..DisplayPoint::new(5, 5),
3259 DisplayPoint::new(6, 5)..DisplayPoint::new(6, 5),
3260 DisplayPoint::new(7, 0)..DisplayPoint::new(7, 0)
3261 ]
3262 );
3263 });
3264}
3265
3266#[gpui::test]
3267fn test_add_selection_above_below(cx: &mut TestAppContext) {
3268 init_test(cx, |_| {});
3269
3270 let (_, view) = cx.add_window(|cx| {
3271 let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
3272 build_editor(buffer, cx)
3273 });
3274
3275 view.update(cx, |view, cx| {
3276 view.change_selections(None, cx, |s| {
3277 s.select_display_ranges([DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)])
3278 });
3279 });
3280 view.update(cx, |view, cx| {
3281 view.add_selection_above(&AddSelectionAbove, cx);
3282 assert_eq!(
3283 view.selections.display_ranges(cx),
3284 vec![
3285 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
3286 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)
3287 ]
3288 );
3289 });
3290
3291 view.update(cx, |view, cx| {
3292 view.add_selection_above(&AddSelectionAbove, cx);
3293 assert_eq!(
3294 view.selections.display_ranges(cx),
3295 vec![
3296 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
3297 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)
3298 ]
3299 );
3300 });
3301
3302 view.update(cx, |view, cx| {
3303 view.add_selection_below(&AddSelectionBelow, cx);
3304 assert_eq!(
3305 view.selections.display_ranges(cx),
3306 vec![DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)]
3307 );
3308
3309 view.undo_selection(&UndoSelection, cx);
3310 assert_eq!(
3311 view.selections.display_ranges(cx),
3312 vec![
3313 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
3314 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)
3315 ]
3316 );
3317
3318 view.redo_selection(&RedoSelection, cx);
3319 assert_eq!(
3320 view.selections.display_ranges(cx),
3321 vec![DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)]
3322 );
3323 });
3324
3325 view.update(cx, |view, cx| {
3326 view.add_selection_below(&AddSelectionBelow, cx);
3327 assert_eq!(
3328 view.selections.display_ranges(cx),
3329 vec![
3330 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3),
3331 DisplayPoint::new(4, 3)..DisplayPoint::new(4, 3)
3332 ]
3333 );
3334 });
3335
3336 view.update(cx, |view, cx| {
3337 view.add_selection_below(&AddSelectionBelow, cx);
3338 assert_eq!(
3339 view.selections.display_ranges(cx),
3340 vec![
3341 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3),
3342 DisplayPoint::new(4, 3)..DisplayPoint::new(4, 3)
3343 ]
3344 );
3345 });
3346
3347 view.update(cx, |view, cx| {
3348 view.change_selections(None, cx, |s| {
3349 s.select_display_ranges([DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)])
3350 });
3351 });
3352 view.update(cx, |view, cx| {
3353 view.add_selection_below(&AddSelectionBelow, cx);
3354 assert_eq!(
3355 view.selections.display_ranges(cx),
3356 vec![
3357 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3),
3358 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 3)
3359 ]
3360 );
3361 });
3362
3363 view.update(cx, |view, cx| {
3364 view.add_selection_below(&AddSelectionBelow, cx);
3365 assert_eq!(
3366 view.selections.display_ranges(cx),
3367 vec![
3368 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3),
3369 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 3)
3370 ]
3371 );
3372 });
3373
3374 view.update(cx, |view, cx| {
3375 view.add_selection_above(&AddSelectionAbove, cx);
3376 assert_eq!(
3377 view.selections.display_ranges(cx),
3378 vec![DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)]
3379 );
3380 });
3381
3382 view.update(cx, |view, cx| {
3383 view.add_selection_above(&AddSelectionAbove, cx);
3384 assert_eq!(
3385 view.selections.display_ranges(cx),
3386 vec![DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)]
3387 );
3388 });
3389
3390 view.update(cx, |view, cx| {
3391 view.change_selections(None, cx, |s| {
3392 s.select_display_ranges([DisplayPoint::new(0, 1)..DisplayPoint::new(1, 4)])
3393 });
3394 view.add_selection_below(&AddSelectionBelow, cx);
3395 assert_eq!(
3396 view.selections.display_ranges(cx),
3397 vec![
3398 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
3399 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4),
3400 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2),
3401 ]
3402 );
3403 });
3404
3405 view.update(cx, |view, cx| {
3406 view.add_selection_below(&AddSelectionBelow, cx);
3407 assert_eq!(
3408 view.selections.display_ranges(cx),
3409 vec![
3410 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
3411 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4),
3412 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2),
3413 DisplayPoint::new(4, 1)..DisplayPoint::new(4, 4),
3414 ]
3415 );
3416 });
3417
3418 view.update(cx, |view, cx| {
3419 view.add_selection_above(&AddSelectionAbove, cx);
3420 assert_eq!(
3421 view.selections.display_ranges(cx),
3422 vec![
3423 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
3424 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4),
3425 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2),
3426 ]
3427 );
3428 });
3429
3430 view.update(cx, |view, cx| {
3431 view.change_selections(None, cx, |s| {
3432 s.select_display_ranges([DisplayPoint::new(4, 3)..DisplayPoint::new(1, 1)])
3433 });
3434 });
3435 view.update(cx, |view, cx| {
3436 view.add_selection_above(&AddSelectionAbove, cx);
3437 assert_eq!(
3438 view.selections.display_ranges(cx),
3439 vec![
3440 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 1),
3441 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 1),
3442 DisplayPoint::new(3, 2)..DisplayPoint::new(3, 1),
3443 DisplayPoint::new(4, 3)..DisplayPoint::new(4, 1),
3444 ]
3445 );
3446 });
3447
3448 view.update(cx, |view, cx| {
3449 view.add_selection_below(&AddSelectionBelow, cx);
3450 assert_eq!(
3451 view.selections.display_ranges(cx),
3452 vec![
3453 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 1),
3454 DisplayPoint::new(3, 2)..DisplayPoint::new(3, 1),
3455 DisplayPoint::new(4, 3)..DisplayPoint::new(4, 1),
3456 ]
3457 );
3458 });
3459}
3460
3461#[gpui::test]
3462async fn test_select_next(cx: &mut gpui::TestAppContext) {
3463 init_test(cx, |_| {});
3464
3465 let mut cx = EditorTestContext::new(cx).await;
3466 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
3467
3468 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx));
3469 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
3470
3471 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx));
3472 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
3473
3474 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
3475 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
3476
3477 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
3478 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
3479
3480 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx));
3481 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
3482
3483 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx));
3484 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
3485}
3486
3487#[gpui::test]
3488async fn test_select_previous(cx: &mut gpui::TestAppContext) {
3489 init_test(cx, |_| {});
3490 {
3491 // `Select previous` without a selection (selects wordwise)
3492 let mut cx = EditorTestContext::new(cx).await;
3493 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
3494
3495 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
3496 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
3497
3498 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
3499 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
3500
3501 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
3502 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
3503
3504 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
3505 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
3506
3507 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
3508 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
3509
3510 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
3511 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
3512 }
3513 {
3514 // `Select previous` with a selection
3515 let mut cx = EditorTestContext::new(cx).await;
3516 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
3517
3518 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
3519 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
3520
3521 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
3522 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
3523
3524 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
3525 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
3526
3527 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
3528 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
3529
3530 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
3531 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
3532
3533 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
3534 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
3535 }
3536}
3537
3538#[gpui::test]
3539async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
3540 init_test(cx, |_| {});
3541
3542 let language = Arc::new(Language::new(
3543 LanguageConfig::default(),
3544 Some(tree_sitter_rust::language()),
3545 ));
3546
3547 let text = r#"
3548 use mod1::mod2::{mod3, mod4};
3549
3550 fn fn_1(param1: bool, param2: &str) {
3551 let var1 = "text";
3552 }
3553 "#
3554 .unindent();
3555
3556 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
3557 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
3558 let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
3559 view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
3560 .await;
3561
3562 view.update(cx, |view, cx| {
3563 view.change_selections(None, cx, |s| {
3564 s.select_display_ranges([
3565 DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
3566 DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
3567 DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
3568 ]);
3569 });
3570 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
3571 });
3572 assert_eq!(
3573 view.update(cx, |view, cx| { view.selections.display_ranges(cx) }),
3574 &[
3575 DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27),
3576 DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
3577 DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21),
3578 ]
3579 );
3580
3581 view.update(cx, |view, cx| {
3582 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
3583 });
3584 assert_eq!(
3585 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3586 &[
3587 DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
3588 DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0),
3589 ]
3590 );
3591
3592 view.update(cx, |view, cx| {
3593 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
3594 });
3595 assert_eq!(
3596 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3597 &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)]
3598 );
3599
3600 // Trying to expand the selected syntax node one more time has no effect.
3601 view.update(cx, |view, cx| {
3602 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
3603 });
3604 assert_eq!(
3605 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3606 &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)]
3607 );
3608
3609 view.update(cx, |view, cx| {
3610 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
3611 });
3612 assert_eq!(
3613 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3614 &[
3615 DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
3616 DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0),
3617 ]
3618 );
3619
3620 view.update(cx, |view, cx| {
3621 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
3622 });
3623 assert_eq!(
3624 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3625 &[
3626 DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27),
3627 DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
3628 DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21),
3629 ]
3630 );
3631
3632 view.update(cx, |view, cx| {
3633 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
3634 });
3635 assert_eq!(
3636 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3637 &[
3638 DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
3639 DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
3640 DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
3641 ]
3642 );
3643
3644 // Trying to shrink the selected syntax node one more time has no effect.
3645 view.update(cx, |view, cx| {
3646 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
3647 });
3648 assert_eq!(
3649 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3650 &[
3651 DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
3652 DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
3653 DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
3654 ]
3655 );
3656
3657 // Ensure that we keep expanding the selection if the larger selection starts or ends within
3658 // a fold.
3659 view.update(cx, |view, cx| {
3660 view.fold_ranges(
3661 vec![
3662 Point::new(0, 21)..Point::new(0, 24),
3663 Point::new(3, 20)..Point::new(3, 22),
3664 ],
3665 true,
3666 cx,
3667 );
3668 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
3669 });
3670 assert_eq!(
3671 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3672 &[
3673 DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
3674 DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
3675 DisplayPoint::new(3, 4)..DisplayPoint::new(3, 23),
3676 ]
3677 );
3678}
3679
3680#[gpui::test]
3681async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
3682 init_test(cx, |_| {});
3683
3684 let language = Arc::new(
3685 Language::new(
3686 LanguageConfig {
3687 brackets: BracketPairConfig {
3688 pairs: vec![
3689 BracketPair {
3690 start: "{".to_string(),
3691 end: "}".to_string(),
3692 close: false,
3693 newline: true,
3694 },
3695 BracketPair {
3696 start: "(".to_string(),
3697 end: ")".to_string(),
3698 close: false,
3699 newline: true,
3700 },
3701 ],
3702 ..Default::default()
3703 },
3704 ..Default::default()
3705 },
3706 Some(tree_sitter_rust::language()),
3707 )
3708 .with_indents_query(
3709 r#"
3710 (_ "(" ")" @end) @indent
3711 (_ "{" "}" @end) @indent
3712 "#,
3713 )
3714 .unwrap(),
3715 );
3716
3717 let text = "fn a() {}";
3718
3719 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
3720 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
3721 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
3722 editor
3723 .condition(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
3724 .await;
3725
3726 editor.update(cx, |editor, cx| {
3727 editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
3728 editor.newline(&Newline, cx);
3729 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
3730 assert_eq!(
3731 editor.selections.ranges(cx),
3732 &[
3733 Point::new(1, 4)..Point::new(1, 4),
3734 Point::new(3, 4)..Point::new(3, 4),
3735 Point::new(5, 0)..Point::new(5, 0)
3736 ]
3737 );
3738 });
3739}
3740
3741#[gpui::test]
3742async fn test_autoclose_pairs(cx: &mut gpui::TestAppContext) {
3743 init_test(cx, |_| {});
3744
3745 let mut cx = EditorTestContext::new(cx).await;
3746
3747 let language = Arc::new(Language::new(
3748 LanguageConfig {
3749 brackets: BracketPairConfig {
3750 pairs: vec![
3751 BracketPair {
3752 start: "{".to_string(),
3753 end: "}".to_string(),
3754 close: true,
3755 newline: true,
3756 },
3757 BracketPair {
3758 start: "(".to_string(),
3759 end: ")".to_string(),
3760 close: true,
3761 newline: true,
3762 },
3763 BracketPair {
3764 start: "/*".to_string(),
3765 end: " */".to_string(),
3766 close: true,
3767 newline: true,
3768 },
3769 BracketPair {
3770 start: "[".to_string(),
3771 end: "]".to_string(),
3772 close: false,
3773 newline: true,
3774 },
3775 BracketPair {
3776 start: "\"".to_string(),
3777 end: "\"".to_string(),
3778 close: true,
3779 newline: false,
3780 },
3781 ],
3782 ..Default::default()
3783 },
3784 autoclose_before: "})]".to_string(),
3785 ..Default::default()
3786 },
3787 Some(tree_sitter_rust::language()),
3788 ));
3789
3790 let registry = Arc::new(LanguageRegistry::test());
3791 registry.add(language.clone());
3792 cx.update_buffer(|buffer, cx| {
3793 buffer.set_language_registry(registry);
3794 buffer.set_language(Some(language), cx);
3795 });
3796
3797 cx.set_state(
3798 &r#"
3799 🏀ˇ
3800 εˇ
3801 ❤️ˇ
3802 "#
3803 .unindent(),
3804 );
3805
3806 // autoclose multiple nested brackets at multiple cursors
3807 cx.update_editor(|view, cx| {
3808 view.handle_input("{", cx);
3809 view.handle_input("{", cx);
3810 view.handle_input("{", cx);
3811 });
3812 cx.assert_editor_state(
3813 &"
3814 🏀{{{ˇ}}}
3815 ε{{{ˇ}}}
3816 ❤️{{{ˇ}}}
3817 "
3818 .unindent(),
3819 );
3820
3821 // insert a different closing bracket
3822 cx.update_editor(|view, cx| {
3823 view.handle_input(")", cx);
3824 });
3825 cx.assert_editor_state(
3826 &"
3827 🏀{{{)ˇ}}}
3828 ε{{{)ˇ}}}
3829 ❤️{{{)ˇ}}}
3830 "
3831 .unindent(),
3832 );
3833
3834 // skip over the auto-closed brackets when typing a closing bracket
3835 cx.update_editor(|view, cx| {
3836 view.move_right(&MoveRight, cx);
3837 view.handle_input("}", cx);
3838 view.handle_input("}", cx);
3839 view.handle_input("}", cx);
3840 });
3841 cx.assert_editor_state(
3842 &"
3843 🏀{{{)}}}}ˇ
3844 ε{{{)}}}}ˇ
3845 ❤️{{{)}}}}ˇ
3846 "
3847 .unindent(),
3848 );
3849
3850 // autoclose multi-character pairs
3851 cx.set_state(
3852 &"
3853 ˇ
3854 ˇ
3855 "
3856 .unindent(),
3857 );
3858 cx.update_editor(|view, cx| {
3859 view.handle_input("/", cx);
3860 view.handle_input("*", cx);
3861 });
3862 cx.assert_editor_state(
3863 &"
3864 /*ˇ */
3865 /*ˇ */
3866 "
3867 .unindent(),
3868 );
3869
3870 // one cursor autocloses a multi-character pair, one cursor
3871 // does not autoclose.
3872 cx.set_state(
3873 &"
3874 /ˇ
3875 ˇ
3876 "
3877 .unindent(),
3878 );
3879 cx.update_editor(|view, cx| view.handle_input("*", cx));
3880 cx.assert_editor_state(
3881 &"
3882 /*ˇ */
3883 *ˇ
3884 "
3885 .unindent(),
3886 );
3887
3888 // Don't autoclose if the next character isn't whitespace and isn't
3889 // listed in the language's "autoclose_before" section.
3890 cx.set_state("ˇa b");
3891 cx.update_editor(|view, cx| view.handle_input("{", cx));
3892 cx.assert_editor_state("{ˇa b");
3893
3894 // Don't autoclose if `close` is false for the bracket pair
3895 cx.set_state("ˇ");
3896 cx.update_editor(|view, cx| view.handle_input("[", cx));
3897 cx.assert_editor_state("[ˇ");
3898
3899 // Surround with brackets if text is selected
3900 cx.set_state("«aˇ» b");
3901 cx.update_editor(|view, cx| view.handle_input("{", cx));
3902 cx.assert_editor_state("{«aˇ»} b");
3903
3904 // Autclose pair where the start and end characters are the same
3905 cx.set_state("aˇ");
3906 cx.update_editor(|view, cx| view.handle_input("\"", cx));
3907 cx.assert_editor_state("a\"ˇ\"");
3908 cx.update_editor(|view, cx| view.handle_input("\"", cx));
3909 cx.assert_editor_state("a\"\"ˇ");
3910}
3911
3912#[gpui::test]
3913async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
3914 init_test(cx, |_| {});
3915
3916 let mut cx = EditorTestContext::new(cx).await;
3917
3918 let html_language = Arc::new(
3919 Language::new(
3920 LanguageConfig {
3921 name: "HTML".into(),
3922 brackets: BracketPairConfig {
3923 pairs: vec![
3924 BracketPair {
3925 start: "<".into(),
3926 end: ">".into(),
3927 close: true,
3928 ..Default::default()
3929 },
3930 BracketPair {
3931 start: "{".into(),
3932 end: "}".into(),
3933 close: true,
3934 ..Default::default()
3935 },
3936 BracketPair {
3937 start: "(".into(),
3938 end: ")".into(),
3939 close: true,
3940 ..Default::default()
3941 },
3942 ],
3943 ..Default::default()
3944 },
3945 autoclose_before: "})]>".into(),
3946 ..Default::default()
3947 },
3948 Some(tree_sitter_html::language()),
3949 )
3950 .with_injection_query(
3951 r#"
3952 (script_element
3953 (raw_text) @content
3954 (#set! "language" "javascript"))
3955 "#,
3956 )
3957 .unwrap(),
3958 );
3959
3960 let javascript_language = Arc::new(Language::new(
3961 LanguageConfig {
3962 name: "JavaScript".into(),
3963 brackets: BracketPairConfig {
3964 pairs: vec![
3965 BracketPair {
3966 start: "/*".into(),
3967 end: " */".into(),
3968 close: true,
3969 ..Default::default()
3970 },
3971 BracketPair {
3972 start: "{".into(),
3973 end: "}".into(),
3974 close: true,
3975 ..Default::default()
3976 },
3977 BracketPair {
3978 start: "(".into(),
3979 end: ")".into(),
3980 close: true,
3981 ..Default::default()
3982 },
3983 ],
3984 ..Default::default()
3985 },
3986 autoclose_before: "})]>".into(),
3987 ..Default::default()
3988 },
3989 Some(tree_sitter_typescript::language_tsx()),
3990 ));
3991
3992 let registry = Arc::new(LanguageRegistry::test());
3993 registry.add(html_language.clone());
3994 registry.add(javascript_language.clone());
3995
3996 cx.update_buffer(|buffer, cx| {
3997 buffer.set_language_registry(registry);
3998 buffer.set_language(Some(html_language), cx);
3999 });
4000
4001 cx.set_state(
4002 &r#"
4003 <body>ˇ
4004 <script>
4005 var x = 1;ˇ
4006 </script>
4007 </body>ˇ
4008 "#
4009 .unindent(),
4010 );
4011
4012 // Precondition: different languages are active at different locations.
4013 cx.update_editor(|editor, cx| {
4014 let snapshot = editor.snapshot(cx);
4015 let cursors = editor.selections.ranges::<usize>(cx);
4016 let languages = cursors
4017 .iter()
4018 .map(|c| snapshot.language_at(c.start).unwrap().name())
4019 .collect::<Vec<_>>();
4020 assert_eq!(
4021 languages,
4022 &["HTML".into(), "JavaScript".into(), "HTML".into()]
4023 );
4024 });
4025
4026 // Angle brackets autoclose in HTML, but not JavaScript.
4027 cx.update_editor(|editor, cx| {
4028 editor.handle_input("<", cx);
4029 editor.handle_input("a", cx);
4030 });
4031 cx.assert_editor_state(
4032 &r#"
4033 <body><aˇ>
4034 <script>
4035 var x = 1;<aˇ
4036 </script>
4037 </body><aˇ>
4038 "#
4039 .unindent(),
4040 );
4041
4042 // Curly braces and parens autoclose in both HTML and JavaScript.
4043 cx.update_editor(|editor, cx| {
4044 editor.handle_input(" b=", cx);
4045 editor.handle_input("{", cx);
4046 editor.handle_input("c", cx);
4047 editor.handle_input("(", cx);
4048 });
4049 cx.assert_editor_state(
4050 &r#"
4051 <body><a b={c(ˇ)}>
4052 <script>
4053 var x = 1;<a b={c(ˇ)}
4054 </script>
4055 </body><a b={c(ˇ)}>
4056 "#
4057 .unindent(),
4058 );
4059
4060 // Brackets that were already autoclosed are skipped.
4061 cx.update_editor(|editor, cx| {
4062 editor.handle_input(")", cx);
4063 editor.handle_input("d", cx);
4064 editor.handle_input("}", cx);
4065 });
4066 cx.assert_editor_state(
4067 &r#"
4068 <body><a b={c()d}ˇ>
4069 <script>
4070 var x = 1;<a b={c()d}ˇ
4071 </script>
4072 </body><a b={c()d}ˇ>
4073 "#
4074 .unindent(),
4075 );
4076 cx.update_editor(|editor, cx| {
4077 editor.handle_input(">", cx);
4078 });
4079 cx.assert_editor_state(
4080 &r#"
4081 <body><a b={c()d}>ˇ
4082 <script>
4083 var x = 1;<a b={c()d}>ˇ
4084 </script>
4085 </body><a b={c()d}>ˇ
4086 "#
4087 .unindent(),
4088 );
4089
4090 // Reset
4091 cx.set_state(
4092 &r#"
4093 <body>ˇ
4094 <script>
4095 var x = 1;ˇ
4096 </script>
4097 </body>ˇ
4098 "#
4099 .unindent(),
4100 );
4101
4102 cx.update_editor(|editor, cx| {
4103 editor.handle_input("<", cx);
4104 });
4105 cx.assert_editor_state(
4106 &r#"
4107 <body><ˇ>
4108 <script>
4109 var x = 1;<ˇ
4110 </script>
4111 </body><ˇ>
4112 "#
4113 .unindent(),
4114 );
4115
4116 // When backspacing, the closing angle brackets are removed.
4117 cx.update_editor(|editor, cx| {
4118 editor.backspace(&Backspace, cx);
4119 });
4120 cx.assert_editor_state(
4121 &r#"
4122 <body>ˇ
4123 <script>
4124 var x = 1;ˇ
4125 </script>
4126 </body>ˇ
4127 "#
4128 .unindent(),
4129 );
4130
4131 // Block comments autoclose in JavaScript, but not HTML.
4132 cx.update_editor(|editor, cx| {
4133 editor.handle_input("/", cx);
4134 editor.handle_input("*", cx);
4135 });
4136 cx.assert_editor_state(
4137 &r#"
4138 <body>/*ˇ
4139 <script>
4140 var x = 1;/*ˇ */
4141 </script>
4142 </body>/*ˇ
4143 "#
4144 .unindent(),
4145 );
4146}
4147
4148#[gpui::test]
4149async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
4150 init_test(cx, |_| {});
4151
4152 let mut cx = EditorTestContext::new(cx).await;
4153
4154 let rust_language = Arc::new(
4155 Language::new(
4156 LanguageConfig {
4157 name: "Rust".into(),
4158 brackets: serde_json::from_value(json!([
4159 { "start": "{", "end": "}", "close": true, "newline": true },
4160 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
4161 ]))
4162 .unwrap(),
4163 autoclose_before: "})]>".into(),
4164 ..Default::default()
4165 },
4166 Some(tree_sitter_rust::language()),
4167 )
4168 .with_override_query("(string_literal) @string")
4169 .unwrap(),
4170 );
4171
4172 let registry = Arc::new(LanguageRegistry::test());
4173 registry.add(rust_language.clone());
4174
4175 cx.update_buffer(|buffer, cx| {
4176 buffer.set_language_registry(registry);
4177 buffer.set_language(Some(rust_language), cx);
4178 });
4179
4180 cx.set_state(
4181 &r#"
4182 let x = ˇ
4183 "#
4184 .unindent(),
4185 );
4186
4187 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
4188 cx.update_editor(|editor, cx| {
4189 editor.handle_input("\"", cx);
4190 });
4191 cx.assert_editor_state(
4192 &r#"
4193 let x = "ˇ"
4194 "#
4195 .unindent(),
4196 );
4197
4198 // Inserting another quotation mark. The cursor moves across the existing
4199 // automatically-inserted quotation mark.
4200 cx.update_editor(|editor, cx| {
4201 editor.handle_input("\"", cx);
4202 });
4203 cx.assert_editor_state(
4204 &r#"
4205 let x = ""ˇ
4206 "#
4207 .unindent(),
4208 );
4209
4210 // Reset
4211 cx.set_state(
4212 &r#"
4213 let x = ˇ
4214 "#
4215 .unindent(),
4216 );
4217
4218 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
4219 cx.update_editor(|editor, cx| {
4220 editor.handle_input("\"", cx);
4221 editor.handle_input(" ", cx);
4222 editor.move_left(&Default::default(), cx);
4223 editor.handle_input("\\", cx);
4224 editor.handle_input("\"", cx);
4225 });
4226 cx.assert_editor_state(
4227 &r#"
4228 let x = "\"ˇ "
4229 "#
4230 .unindent(),
4231 );
4232
4233 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
4234 // mark. Nothing is inserted.
4235 cx.update_editor(|editor, cx| {
4236 editor.move_right(&Default::default(), cx);
4237 editor.handle_input("\"", cx);
4238 });
4239 cx.assert_editor_state(
4240 &r#"
4241 let x = "\" "ˇ
4242 "#
4243 .unindent(),
4244 );
4245}
4246
4247#[gpui::test]
4248async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
4249 init_test(cx, |_| {});
4250
4251 let language = Arc::new(Language::new(
4252 LanguageConfig {
4253 brackets: BracketPairConfig {
4254 pairs: vec![
4255 BracketPair {
4256 start: "{".to_string(),
4257 end: "}".to_string(),
4258 close: true,
4259 newline: true,
4260 },
4261 BracketPair {
4262 start: "/* ".to_string(),
4263 end: "*/".to_string(),
4264 close: true,
4265 ..Default::default()
4266 },
4267 ],
4268 ..Default::default()
4269 },
4270 ..Default::default()
4271 },
4272 Some(tree_sitter_rust::language()),
4273 ));
4274
4275 let text = r#"
4276 a
4277 b
4278 c
4279 "#
4280 .unindent();
4281
4282 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
4283 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
4284 let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
4285 view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
4286 .await;
4287
4288 view.update(cx, |view, cx| {
4289 view.change_selections(None, cx, |s| {
4290 s.select_display_ranges([
4291 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
4292 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
4293 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1),
4294 ])
4295 });
4296
4297 view.handle_input("{", cx);
4298 view.handle_input("{", cx);
4299 view.handle_input("{", cx);
4300 assert_eq!(
4301 view.text(cx),
4302 "
4303 {{{a}}}
4304 {{{b}}}
4305 {{{c}}}
4306 "
4307 .unindent()
4308 );
4309 assert_eq!(
4310 view.selections.display_ranges(cx),
4311 [
4312 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 4),
4313 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 4),
4314 DisplayPoint::new(2, 3)..DisplayPoint::new(2, 4)
4315 ]
4316 );
4317
4318 view.undo(&Undo, cx);
4319 view.undo(&Undo, cx);
4320 view.undo(&Undo, cx);
4321 assert_eq!(
4322 view.text(cx),
4323 "
4324 a
4325 b
4326 c
4327 "
4328 .unindent()
4329 );
4330 assert_eq!(
4331 view.selections.display_ranges(cx),
4332 [
4333 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
4334 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
4335 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1)
4336 ]
4337 );
4338
4339 // Ensure inserting the first character of a multi-byte bracket pair
4340 // doesn't surround the selections with the bracket.
4341 view.handle_input("/", cx);
4342 assert_eq!(
4343 view.text(cx),
4344 "
4345 /
4346 /
4347 /
4348 "
4349 .unindent()
4350 );
4351 assert_eq!(
4352 view.selections.display_ranges(cx),
4353 [
4354 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
4355 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
4356 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1)
4357 ]
4358 );
4359
4360 view.undo(&Undo, cx);
4361 assert_eq!(
4362 view.text(cx),
4363 "
4364 a
4365 b
4366 c
4367 "
4368 .unindent()
4369 );
4370 assert_eq!(
4371 view.selections.display_ranges(cx),
4372 [
4373 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
4374 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
4375 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1)
4376 ]
4377 );
4378
4379 // Ensure inserting the last character of a multi-byte bracket pair
4380 // doesn't surround the selections with the bracket.
4381 view.handle_input("*", cx);
4382 assert_eq!(
4383 view.text(cx),
4384 "
4385 *
4386 *
4387 *
4388 "
4389 .unindent()
4390 );
4391 assert_eq!(
4392 view.selections.display_ranges(cx),
4393 [
4394 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
4395 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
4396 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1)
4397 ]
4398 );
4399 });
4400}
4401
4402#[gpui::test]
4403async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
4404 init_test(cx, |_| {});
4405
4406 let language = Arc::new(Language::new(
4407 LanguageConfig {
4408 brackets: BracketPairConfig {
4409 pairs: vec![BracketPair {
4410 start: "{".to_string(),
4411 end: "}".to_string(),
4412 close: true,
4413 newline: true,
4414 }],
4415 ..Default::default()
4416 },
4417 autoclose_before: "}".to_string(),
4418 ..Default::default()
4419 },
4420 Some(tree_sitter_rust::language()),
4421 ));
4422
4423 let text = r#"
4424 a
4425 b
4426 c
4427 "#
4428 .unindent();
4429
4430 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
4431 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
4432 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
4433 editor
4434 .condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
4435 .await;
4436
4437 editor.update(cx, |editor, cx| {
4438 editor.change_selections(None, cx, |s| {
4439 s.select_ranges([
4440 Point::new(0, 1)..Point::new(0, 1),
4441 Point::new(1, 1)..Point::new(1, 1),
4442 Point::new(2, 1)..Point::new(2, 1),
4443 ])
4444 });
4445
4446 editor.handle_input("{", cx);
4447 editor.handle_input("{", cx);
4448 editor.handle_input("_", cx);
4449 assert_eq!(
4450 editor.text(cx),
4451 "
4452 a{{_}}
4453 b{{_}}
4454 c{{_}}
4455 "
4456 .unindent()
4457 );
4458 assert_eq!(
4459 editor.selections.ranges::<Point>(cx),
4460 [
4461 Point::new(0, 4)..Point::new(0, 4),
4462 Point::new(1, 4)..Point::new(1, 4),
4463 Point::new(2, 4)..Point::new(2, 4)
4464 ]
4465 );
4466
4467 editor.backspace(&Default::default(), cx);
4468 editor.backspace(&Default::default(), cx);
4469 assert_eq!(
4470 editor.text(cx),
4471 "
4472 a{}
4473 b{}
4474 c{}
4475 "
4476 .unindent()
4477 );
4478 assert_eq!(
4479 editor.selections.ranges::<Point>(cx),
4480 [
4481 Point::new(0, 2)..Point::new(0, 2),
4482 Point::new(1, 2)..Point::new(1, 2),
4483 Point::new(2, 2)..Point::new(2, 2)
4484 ]
4485 );
4486
4487 editor.delete_to_previous_word_start(&Default::default(), cx);
4488 assert_eq!(
4489 editor.text(cx),
4490 "
4491 a
4492 b
4493 c
4494 "
4495 .unindent()
4496 );
4497 assert_eq!(
4498 editor.selections.ranges::<Point>(cx),
4499 [
4500 Point::new(0, 1)..Point::new(0, 1),
4501 Point::new(1, 1)..Point::new(1, 1),
4502 Point::new(2, 1)..Point::new(2, 1)
4503 ]
4504 );
4505 });
4506}
4507
4508#[gpui::test]
4509async fn test_snippets(cx: &mut gpui::TestAppContext) {
4510 init_test(cx, |_| {});
4511
4512 let (text, insertion_ranges) = marked_text_ranges(
4513 indoc! {"
4514 a.ˇ b
4515 a.ˇ b
4516 a.ˇ b
4517 "},
4518 false,
4519 );
4520
4521 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
4522 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
4523
4524 editor.update(cx, |editor, cx| {
4525 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
4526
4527 editor
4528 .insert_snippet(&insertion_ranges, snippet, cx)
4529 .unwrap();
4530
4531 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
4532 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
4533 assert_eq!(editor.text(cx), expected_text);
4534 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
4535 }
4536
4537 assert(
4538 editor,
4539 cx,
4540 indoc! {"
4541 a.f(«one», two, «three») b
4542 a.f(«one», two, «three») b
4543 a.f(«one», two, «three») b
4544 "},
4545 );
4546
4547 // Can't move earlier than the first tab stop
4548 assert!(!editor.move_to_prev_snippet_tabstop(cx));
4549 assert(
4550 editor,
4551 cx,
4552 indoc! {"
4553 a.f(«one», two, «three») b
4554 a.f(«one», two, «three») b
4555 a.f(«one», two, «three») b
4556 "},
4557 );
4558
4559 assert!(editor.move_to_next_snippet_tabstop(cx));
4560 assert(
4561 editor,
4562 cx,
4563 indoc! {"
4564 a.f(one, «two», three) b
4565 a.f(one, «two», three) b
4566 a.f(one, «two», three) b
4567 "},
4568 );
4569
4570 editor.move_to_prev_snippet_tabstop(cx);
4571 assert(
4572 editor,
4573 cx,
4574 indoc! {"
4575 a.f(«one», two, «three») b
4576 a.f(«one», two, «three») b
4577 a.f(«one», two, «three») b
4578 "},
4579 );
4580
4581 assert!(editor.move_to_next_snippet_tabstop(cx));
4582 assert(
4583 editor,
4584 cx,
4585 indoc! {"
4586 a.f(one, «two», three) b
4587 a.f(one, «two», three) b
4588 a.f(one, «two», three) b
4589 "},
4590 );
4591 assert!(editor.move_to_next_snippet_tabstop(cx));
4592 assert(
4593 editor,
4594 cx,
4595 indoc! {"
4596 a.f(one, two, three)ˇ b
4597 a.f(one, two, three)ˇ b
4598 a.f(one, two, three)ˇ b
4599 "},
4600 );
4601
4602 // As soon as the last tab stop is reached, snippet state is gone
4603 editor.move_to_prev_snippet_tabstop(cx);
4604 assert(
4605 editor,
4606 cx,
4607 indoc! {"
4608 a.f(one, two, three)ˇ b
4609 a.f(one, two, three)ˇ b
4610 a.f(one, two, three)ˇ b
4611 "},
4612 );
4613 });
4614}
4615
4616#[gpui::test]
4617async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
4618 init_test(cx, |_| {});
4619
4620 let mut language = Language::new(
4621 LanguageConfig {
4622 name: "Rust".into(),
4623 path_suffixes: vec!["rs".to_string()],
4624 ..Default::default()
4625 },
4626 Some(tree_sitter_rust::language()),
4627 );
4628 let mut fake_servers = language
4629 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
4630 capabilities: lsp::ServerCapabilities {
4631 document_formatting_provider: Some(lsp::OneOf::Left(true)),
4632 ..Default::default()
4633 },
4634 ..Default::default()
4635 }))
4636 .await;
4637
4638 let fs = FakeFs::new(cx.background());
4639 fs.insert_file("/file.rs", Default::default()).await;
4640
4641 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
4642 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
4643 let buffer = project
4644 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
4645 .await
4646 .unwrap();
4647
4648 cx.foreground().start_waiting();
4649 let fake_server = fake_servers.next().await.unwrap();
4650
4651 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
4652 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
4653 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
4654 assert!(cx.read(|cx| editor.is_dirty(cx)));
4655
4656 let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
4657 fake_server
4658 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
4659 assert_eq!(
4660 params.text_document.uri,
4661 lsp::Url::from_file_path("/file.rs").unwrap()
4662 );
4663 assert_eq!(params.options.tab_size, 4);
4664 Ok(Some(vec![lsp::TextEdit::new(
4665 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
4666 ", ".to_string(),
4667 )]))
4668 })
4669 .next()
4670 .await;
4671 cx.foreground().start_waiting();
4672 save.await.unwrap();
4673 assert_eq!(
4674 editor.read_with(cx, |editor, cx| editor.text(cx)),
4675 "one, two\nthree\n"
4676 );
4677 assert!(!cx.read(|cx| editor.is_dirty(cx)));
4678
4679 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
4680 assert!(cx.read(|cx| editor.is_dirty(cx)));
4681
4682 // Ensure we can still save even if formatting hangs.
4683 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
4684 assert_eq!(
4685 params.text_document.uri,
4686 lsp::Url::from_file_path("/file.rs").unwrap()
4687 );
4688 futures::future::pending::<()>().await;
4689 unreachable!()
4690 });
4691 let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
4692 cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
4693 cx.foreground().start_waiting();
4694 save.await.unwrap();
4695 assert_eq!(
4696 editor.read_with(cx, |editor, cx| editor.text(cx)),
4697 "one\ntwo\nthree\n"
4698 );
4699 assert!(!cx.read(|cx| editor.is_dirty(cx)));
4700
4701 // Set rust language override and assert overridden tabsize is sent to language server
4702 update_test_language_settings(cx, |settings| {
4703 settings.languages.insert(
4704 "Rust".into(),
4705 LanguageSettingsContent {
4706 tab_size: NonZeroU32::new(8),
4707 ..Default::default()
4708 },
4709 );
4710 });
4711
4712 let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
4713 fake_server
4714 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
4715 assert_eq!(
4716 params.text_document.uri,
4717 lsp::Url::from_file_path("/file.rs").unwrap()
4718 );
4719 assert_eq!(params.options.tab_size, 8);
4720 Ok(Some(vec![]))
4721 })
4722 .next()
4723 .await;
4724 cx.foreground().start_waiting();
4725 save.await.unwrap();
4726}
4727
4728#[gpui::test]
4729async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
4730 init_test(cx, |_| {});
4731
4732 let mut language = Language::new(
4733 LanguageConfig {
4734 name: "Rust".into(),
4735 path_suffixes: vec!["rs".to_string()],
4736 ..Default::default()
4737 },
4738 Some(tree_sitter_rust::language()),
4739 );
4740 let mut fake_servers = language
4741 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
4742 capabilities: lsp::ServerCapabilities {
4743 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
4744 ..Default::default()
4745 },
4746 ..Default::default()
4747 }))
4748 .await;
4749
4750 let fs = FakeFs::new(cx.background());
4751 fs.insert_file("/file.rs", Default::default()).await;
4752
4753 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
4754 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
4755 let buffer = project
4756 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
4757 .await
4758 .unwrap();
4759
4760 cx.foreground().start_waiting();
4761 let fake_server = fake_servers.next().await.unwrap();
4762
4763 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
4764 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
4765 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
4766 assert!(cx.read(|cx| editor.is_dirty(cx)));
4767
4768 let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
4769 fake_server
4770 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
4771 assert_eq!(
4772 params.text_document.uri,
4773 lsp::Url::from_file_path("/file.rs").unwrap()
4774 );
4775 assert_eq!(params.options.tab_size, 4);
4776 Ok(Some(vec![lsp::TextEdit::new(
4777 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
4778 ", ".to_string(),
4779 )]))
4780 })
4781 .next()
4782 .await;
4783 cx.foreground().start_waiting();
4784 save.await.unwrap();
4785 assert_eq!(
4786 editor.read_with(cx, |editor, cx| editor.text(cx)),
4787 "one, two\nthree\n"
4788 );
4789 assert!(!cx.read(|cx| editor.is_dirty(cx)));
4790
4791 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
4792 assert!(cx.read(|cx| editor.is_dirty(cx)));
4793
4794 // Ensure we can still save even if formatting hangs.
4795 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
4796 move |params, _| async move {
4797 assert_eq!(
4798 params.text_document.uri,
4799 lsp::Url::from_file_path("/file.rs").unwrap()
4800 );
4801 futures::future::pending::<()>().await;
4802 unreachable!()
4803 },
4804 );
4805 let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
4806 cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
4807 cx.foreground().start_waiting();
4808 save.await.unwrap();
4809 assert_eq!(
4810 editor.read_with(cx, |editor, cx| editor.text(cx)),
4811 "one\ntwo\nthree\n"
4812 );
4813 assert!(!cx.read(|cx| editor.is_dirty(cx)));
4814
4815 // Set rust language override and assert overridden tabsize is sent to language server
4816 update_test_language_settings(cx, |settings| {
4817 settings.languages.insert(
4818 "Rust".into(),
4819 LanguageSettingsContent {
4820 tab_size: NonZeroU32::new(8),
4821 ..Default::default()
4822 },
4823 );
4824 });
4825
4826 let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
4827 fake_server
4828 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
4829 assert_eq!(
4830 params.text_document.uri,
4831 lsp::Url::from_file_path("/file.rs").unwrap()
4832 );
4833 assert_eq!(params.options.tab_size, 8);
4834 Ok(Some(vec![]))
4835 })
4836 .next()
4837 .await;
4838 cx.foreground().start_waiting();
4839 save.await.unwrap();
4840}
4841
4842#[gpui::test]
4843async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
4844 init_test(cx, |_| {});
4845
4846 let mut language = Language::new(
4847 LanguageConfig {
4848 name: "Rust".into(),
4849 path_suffixes: vec!["rs".to_string()],
4850 ..Default::default()
4851 },
4852 Some(tree_sitter_rust::language()),
4853 );
4854 let mut fake_servers = language
4855 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
4856 capabilities: lsp::ServerCapabilities {
4857 document_formatting_provider: Some(lsp::OneOf::Left(true)),
4858 ..Default::default()
4859 },
4860 ..Default::default()
4861 }))
4862 .await;
4863
4864 let fs = FakeFs::new(cx.background());
4865 fs.insert_file("/file.rs", Default::default()).await;
4866
4867 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
4868 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
4869 let buffer = project
4870 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
4871 .await
4872 .unwrap();
4873
4874 cx.foreground().start_waiting();
4875 let fake_server = fake_servers.next().await.unwrap();
4876
4877 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
4878 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
4879 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
4880
4881 let format = editor.update(cx, |editor, cx| {
4882 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
4883 });
4884 fake_server
4885 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
4886 assert_eq!(
4887 params.text_document.uri,
4888 lsp::Url::from_file_path("/file.rs").unwrap()
4889 );
4890 assert_eq!(params.options.tab_size, 4);
4891 Ok(Some(vec![lsp::TextEdit::new(
4892 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
4893 ", ".to_string(),
4894 )]))
4895 })
4896 .next()
4897 .await;
4898 cx.foreground().start_waiting();
4899 format.await.unwrap();
4900 assert_eq!(
4901 editor.read_with(cx, |editor, cx| editor.text(cx)),
4902 "one, two\nthree\n"
4903 );
4904
4905 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
4906 // Ensure we don't lock if formatting hangs.
4907 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
4908 assert_eq!(
4909 params.text_document.uri,
4910 lsp::Url::from_file_path("/file.rs").unwrap()
4911 );
4912 futures::future::pending::<()>().await;
4913 unreachable!()
4914 });
4915 let format = editor.update(cx, |editor, cx| {
4916 editor.perform_format(project, FormatTrigger::Manual, cx)
4917 });
4918 cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
4919 cx.foreground().start_waiting();
4920 format.await.unwrap();
4921 assert_eq!(
4922 editor.read_with(cx, |editor, cx| editor.text(cx)),
4923 "one\ntwo\nthree\n"
4924 );
4925}
4926
4927#[gpui::test]
4928async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
4929 init_test(cx, |_| {});
4930
4931 let mut cx = EditorLspTestContext::new_rust(
4932 lsp::ServerCapabilities {
4933 document_formatting_provider: Some(lsp::OneOf::Left(true)),
4934 ..Default::default()
4935 },
4936 cx,
4937 )
4938 .await;
4939
4940 cx.set_state(indoc! {"
4941 one.twoˇ
4942 "});
4943
4944 // The format request takes a long time. When it completes, it inserts
4945 // a newline and an indent before the `.`
4946 cx.lsp
4947 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
4948 let executor = cx.background();
4949 async move {
4950 executor.timer(Duration::from_millis(100)).await;
4951 Ok(Some(vec![lsp::TextEdit {
4952 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
4953 new_text: "\n ".into(),
4954 }]))
4955 }
4956 });
4957
4958 // Submit a format request.
4959 let format_1 = cx
4960 .update_editor(|editor, cx| editor.format(&Format, cx))
4961 .unwrap();
4962 cx.foreground().run_until_parked();
4963
4964 // Submit a second format request.
4965 let format_2 = cx
4966 .update_editor(|editor, cx| editor.format(&Format, cx))
4967 .unwrap();
4968 cx.foreground().run_until_parked();
4969
4970 // Wait for both format requests to complete
4971 cx.foreground().advance_clock(Duration::from_millis(200));
4972 cx.foreground().start_waiting();
4973 format_1.await.unwrap();
4974 cx.foreground().start_waiting();
4975 format_2.await.unwrap();
4976
4977 // The formatting edits only happens once.
4978 cx.assert_editor_state(indoc! {"
4979 one
4980 .twoˇ
4981 "});
4982}
4983
4984#[gpui::test]
4985async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
4986 init_test(cx, |_| {});
4987
4988 let mut cx = EditorLspTestContext::new_rust(
4989 lsp::ServerCapabilities {
4990 document_formatting_provider: Some(lsp::OneOf::Left(true)),
4991 ..Default::default()
4992 },
4993 cx,
4994 )
4995 .await;
4996
4997 // Set up a buffer white some trailing whitespace and no trailing newline.
4998 cx.set_state(
4999 &[
5000 "one ", //
5001 "twoˇ", //
5002 "three ", //
5003 "four", //
5004 ]
5005 .join("\n"),
5006 );
5007
5008 // Submit a format request.
5009 let format = cx
5010 .update_editor(|editor, cx| editor.format(&Format, cx))
5011 .unwrap();
5012
5013 // Record which buffer changes have been sent to the language server
5014 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
5015 cx.lsp
5016 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
5017 let buffer_changes = buffer_changes.clone();
5018 move |params, _| {
5019 buffer_changes.lock().extend(
5020 params
5021 .content_changes
5022 .into_iter()
5023 .map(|e| (e.range.unwrap(), e.text)),
5024 );
5025 }
5026 });
5027
5028 // Handle formatting requests to the language server.
5029 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
5030 let buffer_changes = buffer_changes.clone();
5031 move |_, _| {
5032 // When formatting is requested, trailing whitespace has already been stripped,
5033 // and the trailing newline has already been added.
5034 assert_eq!(
5035 &buffer_changes.lock()[1..],
5036 &[
5037 (
5038 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
5039 "".into()
5040 ),
5041 (
5042 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
5043 "".into()
5044 ),
5045 (
5046 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
5047 "\n".into()
5048 ),
5049 ]
5050 );
5051
5052 // Insert blank lines between each line of the buffer.
5053 async move {
5054 Ok(Some(vec![
5055 lsp::TextEdit {
5056 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
5057 new_text: "\n".into(),
5058 },
5059 lsp::TextEdit {
5060 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
5061 new_text: "\n".into(),
5062 },
5063 ]))
5064 }
5065 }
5066 });
5067
5068 // After formatting the buffer, the trailing whitespace is stripped,
5069 // a newline is appended, and the edits provided by the language server
5070 // have been applied.
5071 format.await.unwrap();
5072 cx.assert_editor_state(
5073 &[
5074 "one", //
5075 "", //
5076 "twoˇ", //
5077 "", //
5078 "three", //
5079 "four", //
5080 "", //
5081 ]
5082 .join("\n"),
5083 );
5084
5085 // Undoing the formatting undoes the trailing whitespace removal, the
5086 // trailing newline, and the LSP edits.
5087 cx.update_buffer(|buffer, cx| buffer.undo(cx));
5088 cx.assert_editor_state(
5089 &[
5090 "one ", //
5091 "twoˇ", //
5092 "three ", //
5093 "four", //
5094 ]
5095 .join("\n"),
5096 );
5097}
5098
5099#[gpui::test]
5100async fn test_completion(cx: &mut gpui::TestAppContext) {
5101 init_test(cx, |_| {});
5102
5103 let mut cx = EditorLspTestContext::new_rust(
5104 lsp::ServerCapabilities {
5105 completion_provider: Some(lsp::CompletionOptions {
5106 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
5107 ..Default::default()
5108 }),
5109 ..Default::default()
5110 },
5111 cx,
5112 )
5113 .await;
5114
5115 cx.set_state(indoc! {"
5116 oneˇ
5117 two
5118 three
5119 "});
5120 cx.simulate_keystroke(".");
5121 handle_completion_request(
5122 &mut cx,
5123 indoc! {"
5124 one.|<>
5125 two
5126 three
5127 "},
5128 vec!["first_completion", "second_completion"],
5129 )
5130 .await;
5131 cx.condition(|editor, _| editor.context_menu_visible())
5132 .await;
5133 let apply_additional_edits = cx.update_editor(|editor, cx| {
5134 editor.move_down(&MoveDown, cx);
5135 editor
5136 .confirm_completion(&ConfirmCompletion::default(), cx)
5137 .unwrap()
5138 });
5139 cx.assert_editor_state(indoc! {"
5140 one.second_completionˇ
5141 two
5142 three
5143 "});
5144
5145 handle_resolve_completion_request(
5146 &mut cx,
5147 Some(vec![
5148 (
5149 //This overlaps with the primary completion edit which is
5150 //misbehavior from the LSP spec, test that we filter it out
5151 indoc! {"
5152 one.second_ˇcompletion
5153 two
5154 threeˇ
5155 "},
5156 "overlapping additional edit",
5157 ),
5158 (
5159 indoc! {"
5160 one.second_completion
5161 two
5162 threeˇ
5163 "},
5164 "\nadditional edit",
5165 ),
5166 ]),
5167 )
5168 .await;
5169 apply_additional_edits.await.unwrap();
5170 cx.assert_editor_state(indoc! {"
5171 one.second_completionˇ
5172 two
5173 three
5174 additional edit
5175 "});
5176
5177 cx.set_state(indoc! {"
5178 one.second_completion
5179 twoˇ
5180 threeˇ
5181 additional edit
5182 "});
5183 cx.simulate_keystroke(" ");
5184 assert!(cx.editor(|e, _| e.context_menu.is_none()));
5185 cx.simulate_keystroke("s");
5186 assert!(cx.editor(|e, _| e.context_menu.is_none()));
5187
5188 cx.assert_editor_state(indoc! {"
5189 one.second_completion
5190 two sˇ
5191 three sˇ
5192 additional edit
5193 "});
5194 handle_completion_request(
5195 &mut cx,
5196 indoc! {"
5197 one.second_completion
5198 two s
5199 three <s|>
5200 additional edit
5201 "},
5202 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
5203 )
5204 .await;
5205 cx.condition(|editor, _| editor.context_menu_visible())
5206 .await;
5207
5208 cx.simulate_keystroke("i");
5209
5210 handle_completion_request(
5211 &mut cx,
5212 indoc! {"
5213 one.second_completion
5214 two si
5215 three <si|>
5216 additional edit
5217 "},
5218 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
5219 )
5220 .await;
5221 cx.condition(|editor, _| editor.context_menu_visible())
5222 .await;
5223
5224 let apply_additional_edits = cx.update_editor(|editor, cx| {
5225 editor
5226 .confirm_completion(&ConfirmCompletion::default(), cx)
5227 .unwrap()
5228 });
5229 cx.assert_editor_state(indoc! {"
5230 one.second_completion
5231 two sixth_completionˇ
5232 three sixth_completionˇ
5233 additional edit
5234 "});
5235
5236 handle_resolve_completion_request(&mut cx, None).await;
5237 apply_additional_edits.await.unwrap();
5238
5239 cx.update(|cx| {
5240 cx.update_global::<SettingsStore, _, _>(|settings, cx| {
5241 settings.update_user_settings::<EditorSettings>(cx, |settings| {
5242 settings.show_completions_on_input = Some(false);
5243 });
5244 })
5245 });
5246 cx.set_state("editorˇ");
5247 cx.simulate_keystroke(".");
5248 assert!(cx.editor(|e, _| e.context_menu.is_none()));
5249 cx.simulate_keystroke("c");
5250 cx.simulate_keystroke("l");
5251 cx.simulate_keystroke("o");
5252 cx.assert_editor_state("editor.cloˇ");
5253 assert!(cx.editor(|e, _| e.context_menu.is_none()));
5254 cx.update_editor(|editor, cx| {
5255 editor.show_completions(&ShowCompletions, cx);
5256 });
5257 handle_completion_request(&mut cx, "editor.<clo|>", vec!["close", "clobber"]).await;
5258 cx.condition(|editor, _| editor.context_menu_visible())
5259 .await;
5260 let apply_additional_edits = cx.update_editor(|editor, cx| {
5261 editor
5262 .confirm_completion(&ConfirmCompletion::default(), cx)
5263 .unwrap()
5264 });
5265 cx.assert_editor_state("editor.closeˇ");
5266 handle_resolve_completion_request(&mut cx, None).await;
5267 apply_additional_edits.await.unwrap();
5268}
5269
5270#[gpui::test]
5271async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
5272 init_test(cx, |_| {});
5273 let mut cx = EditorTestContext::new(cx).await;
5274 let language = Arc::new(Language::new(
5275 LanguageConfig {
5276 line_comment: Some("// ".into()),
5277 ..Default::default()
5278 },
5279 Some(tree_sitter_rust::language()),
5280 ));
5281 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
5282
5283 // If multiple selections intersect a line, the line is only toggled once.
5284 cx.set_state(indoc! {"
5285 fn a() {
5286 «//b();
5287 ˇ»// «c();
5288 //ˇ» d();
5289 }
5290 "});
5291
5292 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5293
5294 cx.assert_editor_state(indoc! {"
5295 fn a() {
5296 «b();
5297 c();
5298 ˇ» d();
5299 }
5300 "});
5301
5302 // The comment prefix is inserted at the same column for every line in a
5303 // selection.
5304 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5305
5306 cx.assert_editor_state(indoc! {"
5307 fn a() {
5308 // «b();
5309 // c();
5310 ˇ»// d();
5311 }
5312 "});
5313
5314 // If a selection ends at the beginning of a line, that line is not toggled.
5315 cx.set_selections_state(indoc! {"
5316 fn a() {
5317 // b();
5318 «// c();
5319 ˇ» // d();
5320 }
5321 "});
5322
5323 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5324
5325 cx.assert_editor_state(indoc! {"
5326 fn a() {
5327 // b();
5328 «c();
5329 ˇ» // d();
5330 }
5331 "});
5332
5333 // If a selection span a single line and is empty, the line is toggled.
5334 cx.set_state(indoc! {"
5335 fn a() {
5336 a();
5337 b();
5338 ˇ
5339 }
5340 "});
5341
5342 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5343
5344 cx.assert_editor_state(indoc! {"
5345 fn a() {
5346 a();
5347 b();
5348 //•ˇ
5349 }
5350 "});
5351
5352 // If a selection span multiple lines, empty lines are not toggled.
5353 cx.set_state(indoc! {"
5354 fn a() {
5355 «a();
5356
5357 c();ˇ»
5358 }
5359 "});
5360
5361 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5362
5363 cx.assert_editor_state(indoc! {"
5364 fn a() {
5365 // «a();
5366
5367 // c();ˇ»
5368 }
5369 "});
5370}
5371
5372#[gpui::test]
5373async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
5374 init_test(cx, |_| {});
5375
5376 let language = Arc::new(Language::new(
5377 LanguageConfig {
5378 line_comment: Some("// ".into()),
5379 ..Default::default()
5380 },
5381 Some(tree_sitter_rust::language()),
5382 ));
5383
5384 let registry = Arc::new(LanguageRegistry::test());
5385 registry.add(language.clone());
5386
5387 let mut cx = EditorTestContext::new(cx).await;
5388 cx.update_buffer(|buffer, cx| {
5389 buffer.set_language_registry(registry);
5390 buffer.set_language(Some(language), cx);
5391 });
5392
5393 let toggle_comments = &ToggleComments {
5394 advance_downwards: true,
5395 };
5396
5397 // Single cursor on one line -> advance
5398 // Cursor moves horizontally 3 characters as well on non-blank line
5399 cx.set_state(indoc!(
5400 "fn a() {
5401 ˇdog();
5402 cat();
5403 }"
5404 ));
5405 cx.update_editor(|editor, cx| {
5406 editor.toggle_comments(toggle_comments, cx);
5407 });
5408 cx.assert_editor_state(indoc!(
5409 "fn a() {
5410 // dog();
5411 catˇ();
5412 }"
5413 ));
5414
5415 // Single selection on one line -> don't advance
5416 cx.set_state(indoc!(
5417 "fn a() {
5418 «dog()ˇ»;
5419 cat();
5420 }"
5421 ));
5422 cx.update_editor(|editor, cx| {
5423 editor.toggle_comments(toggle_comments, cx);
5424 });
5425 cx.assert_editor_state(indoc!(
5426 "fn a() {
5427 // «dog()ˇ»;
5428 cat();
5429 }"
5430 ));
5431
5432 // Multiple cursors on one line -> advance
5433 cx.set_state(indoc!(
5434 "fn a() {
5435 ˇdˇog();
5436 cat();
5437 }"
5438 ));
5439 cx.update_editor(|editor, cx| {
5440 editor.toggle_comments(toggle_comments, cx);
5441 });
5442 cx.assert_editor_state(indoc!(
5443 "fn a() {
5444 // dog();
5445 catˇ(ˇ);
5446 }"
5447 ));
5448
5449 // Multiple cursors on one line, with selection -> don't advance
5450 cx.set_state(indoc!(
5451 "fn a() {
5452 ˇdˇog«()ˇ»;
5453 cat();
5454 }"
5455 ));
5456 cx.update_editor(|editor, cx| {
5457 editor.toggle_comments(toggle_comments, cx);
5458 });
5459 cx.assert_editor_state(indoc!(
5460 "fn a() {
5461 // ˇdˇog«()ˇ»;
5462 cat();
5463 }"
5464 ));
5465
5466 // Single cursor on one line -> advance
5467 // Cursor moves to column 0 on blank line
5468 cx.set_state(indoc!(
5469 "fn a() {
5470 ˇdog();
5471
5472 cat();
5473 }"
5474 ));
5475 cx.update_editor(|editor, cx| {
5476 editor.toggle_comments(toggle_comments, cx);
5477 });
5478 cx.assert_editor_state(indoc!(
5479 "fn a() {
5480 // dog();
5481 ˇ
5482 cat();
5483 }"
5484 ));
5485
5486 // Single cursor on one line -> advance
5487 // Cursor starts and ends at column 0
5488 cx.set_state(indoc!(
5489 "fn a() {
5490 ˇ dog();
5491 cat();
5492 }"
5493 ));
5494 cx.update_editor(|editor, cx| {
5495 editor.toggle_comments(toggle_comments, cx);
5496 });
5497 cx.assert_editor_state(indoc!(
5498 "fn a() {
5499 // dog();
5500 ˇ cat();
5501 }"
5502 ));
5503}
5504
5505#[gpui::test]
5506async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
5507 init_test(cx, |_| {});
5508
5509 let mut cx = EditorTestContext::new(cx).await;
5510
5511 let html_language = Arc::new(
5512 Language::new(
5513 LanguageConfig {
5514 name: "HTML".into(),
5515 block_comment: Some(("<!-- ".into(), " -->".into())),
5516 ..Default::default()
5517 },
5518 Some(tree_sitter_html::language()),
5519 )
5520 .with_injection_query(
5521 r#"
5522 (script_element
5523 (raw_text) @content
5524 (#set! "language" "javascript"))
5525 "#,
5526 )
5527 .unwrap(),
5528 );
5529
5530 let javascript_language = Arc::new(Language::new(
5531 LanguageConfig {
5532 name: "JavaScript".into(),
5533 line_comment: Some("// ".into()),
5534 ..Default::default()
5535 },
5536 Some(tree_sitter_typescript::language_tsx()),
5537 ));
5538
5539 let registry = Arc::new(LanguageRegistry::test());
5540 registry.add(html_language.clone());
5541 registry.add(javascript_language.clone());
5542
5543 cx.update_buffer(|buffer, cx| {
5544 buffer.set_language_registry(registry);
5545 buffer.set_language(Some(html_language), cx);
5546 });
5547
5548 // Toggle comments for empty selections
5549 cx.set_state(
5550 &r#"
5551 <p>A</p>ˇ
5552 <p>B</p>ˇ
5553 <p>C</p>ˇ
5554 "#
5555 .unindent(),
5556 );
5557 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5558 cx.assert_editor_state(
5559 &r#"
5560 <!-- <p>A</p>ˇ -->
5561 <!-- <p>B</p>ˇ -->
5562 <!-- <p>C</p>ˇ -->
5563 "#
5564 .unindent(),
5565 );
5566 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5567 cx.assert_editor_state(
5568 &r#"
5569 <p>A</p>ˇ
5570 <p>B</p>ˇ
5571 <p>C</p>ˇ
5572 "#
5573 .unindent(),
5574 );
5575
5576 // Toggle comments for mixture of empty and non-empty selections, where
5577 // multiple selections occupy a given line.
5578 cx.set_state(
5579 &r#"
5580 <p>A«</p>
5581 <p>ˇ»B</p>ˇ
5582 <p>C«</p>
5583 <p>ˇ»D</p>ˇ
5584 "#
5585 .unindent(),
5586 );
5587
5588 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5589 cx.assert_editor_state(
5590 &r#"
5591 <!-- <p>A«</p>
5592 <p>ˇ»B</p>ˇ -->
5593 <!-- <p>C«</p>
5594 <p>ˇ»D</p>ˇ -->
5595 "#
5596 .unindent(),
5597 );
5598 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5599 cx.assert_editor_state(
5600 &r#"
5601 <p>A«</p>
5602 <p>ˇ»B</p>ˇ
5603 <p>C«</p>
5604 <p>ˇ»D</p>ˇ
5605 "#
5606 .unindent(),
5607 );
5608
5609 // Toggle comments when different languages are active for different
5610 // selections.
5611 cx.set_state(
5612 &r#"
5613 ˇ<script>
5614 ˇvar x = new Y();
5615 ˇ</script>
5616 "#
5617 .unindent(),
5618 );
5619 cx.foreground().run_until_parked();
5620 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5621 cx.assert_editor_state(
5622 &r#"
5623 <!-- ˇ<script> -->
5624 // ˇvar x = new Y();
5625 <!-- ˇ</script> -->
5626 "#
5627 .unindent(),
5628 );
5629}
5630
5631#[gpui::test]
5632fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
5633 init_test(cx, |_| {});
5634
5635 let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
5636 let multibuffer = cx.add_model(|cx| {
5637 let mut multibuffer = MultiBuffer::new(0);
5638 multibuffer.push_excerpts(
5639 buffer.clone(),
5640 [
5641 ExcerptRange {
5642 context: Point::new(0, 0)..Point::new(0, 4),
5643 primary: None,
5644 },
5645 ExcerptRange {
5646 context: Point::new(1, 0)..Point::new(1, 4),
5647 primary: None,
5648 },
5649 ],
5650 cx,
5651 );
5652 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
5653 multibuffer
5654 });
5655
5656 let (_, view) = cx.add_window(|cx| build_editor(multibuffer, cx));
5657 view.update(cx, |view, cx| {
5658 assert_eq!(view.text(cx), "aaaa\nbbbb");
5659 view.change_selections(None, cx, |s| {
5660 s.select_ranges([
5661 Point::new(0, 0)..Point::new(0, 0),
5662 Point::new(1, 0)..Point::new(1, 0),
5663 ])
5664 });
5665
5666 view.handle_input("X", cx);
5667 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
5668 assert_eq!(
5669 view.selections.ranges(cx),
5670 [
5671 Point::new(0, 1)..Point::new(0, 1),
5672 Point::new(1, 1)..Point::new(1, 1),
5673 ]
5674 );
5675
5676 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
5677 view.change_selections(None, cx, |s| {
5678 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
5679 });
5680 view.backspace(&Default::default(), cx);
5681 assert_eq!(view.text(cx), "Xa\nbbb");
5682 assert_eq!(
5683 view.selections.ranges(cx),
5684 [Point::new(1, 0)..Point::new(1, 0)]
5685 );
5686
5687 view.change_selections(None, cx, |s| {
5688 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
5689 });
5690 view.backspace(&Default::default(), cx);
5691 assert_eq!(view.text(cx), "X\nbb");
5692 assert_eq!(
5693 view.selections.ranges(cx),
5694 [Point::new(0, 1)..Point::new(0, 1)]
5695 );
5696 });
5697}
5698
5699#[gpui::test]
5700fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
5701 init_test(cx, |_| {});
5702
5703 let markers = vec![('[', ']').into(), ('(', ')').into()];
5704 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
5705 indoc! {"
5706 [aaaa
5707 (bbbb]
5708 cccc)",
5709 },
5710 markers.clone(),
5711 );
5712 let excerpt_ranges = markers.into_iter().map(|marker| {
5713 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
5714 ExcerptRange {
5715 context,
5716 primary: None,
5717 }
5718 });
5719 let buffer = cx.add_model(|cx| Buffer::new(0, initial_text, cx));
5720 let multibuffer = cx.add_model(|cx| {
5721 let mut multibuffer = MultiBuffer::new(0);
5722 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
5723 multibuffer
5724 });
5725
5726 let (_, view) = cx.add_window(|cx| build_editor(multibuffer, cx));
5727 view.update(cx, |view, cx| {
5728 let (expected_text, selection_ranges) = marked_text_ranges(
5729 indoc! {"
5730 aaaa
5731 bˇbbb
5732 bˇbbˇb
5733 cccc"
5734 },
5735 true,
5736 );
5737 assert_eq!(view.text(cx), expected_text);
5738 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
5739
5740 view.handle_input("X", cx);
5741
5742 let (expected_text, expected_selections) = marked_text_ranges(
5743 indoc! {"
5744 aaaa
5745 bXˇbbXb
5746 bXˇbbXˇb
5747 cccc"
5748 },
5749 false,
5750 );
5751 assert_eq!(view.text(cx), expected_text);
5752 assert_eq!(view.selections.ranges(cx), expected_selections);
5753
5754 view.newline(&Newline, cx);
5755 let (expected_text, expected_selections) = marked_text_ranges(
5756 indoc! {"
5757 aaaa
5758 bX
5759 ˇbbX
5760 b
5761 bX
5762 ˇbbX
5763 ˇb
5764 cccc"
5765 },
5766 false,
5767 );
5768 assert_eq!(view.text(cx), expected_text);
5769 assert_eq!(view.selections.ranges(cx), expected_selections);
5770 });
5771}
5772
5773#[gpui::test]
5774fn test_refresh_selections(cx: &mut TestAppContext) {
5775 init_test(cx, |_| {});
5776
5777 let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
5778 let mut excerpt1_id = None;
5779 let multibuffer = cx.add_model(|cx| {
5780 let mut multibuffer = MultiBuffer::new(0);
5781 excerpt1_id = multibuffer
5782 .push_excerpts(
5783 buffer.clone(),
5784 [
5785 ExcerptRange {
5786 context: Point::new(0, 0)..Point::new(1, 4),
5787 primary: None,
5788 },
5789 ExcerptRange {
5790 context: Point::new(1, 0)..Point::new(2, 4),
5791 primary: None,
5792 },
5793 ],
5794 cx,
5795 )
5796 .into_iter()
5797 .next();
5798 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
5799 multibuffer
5800 });
5801
5802 let (_, editor) = cx.add_window(|cx| {
5803 let mut editor = build_editor(multibuffer.clone(), cx);
5804 let snapshot = editor.snapshot(cx);
5805 editor.change_selections(None, cx, |s| {
5806 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
5807 });
5808 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
5809 assert_eq!(
5810 editor.selections.ranges(cx),
5811 [
5812 Point::new(1, 3)..Point::new(1, 3),
5813 Point::new(2, 1)..Point::new(2, 1),
5814 ]
5815 );
5816 editor
5817 });
5818
5819 // Refreshing selections is a no-op when excerpts haven't changed.
5820 editor.update(cx, |editor, cx| {
5821 editor.change_selections(None, cx, |s| s.refresh());
5822 assert_eq!(
5823 editor.selections.ranges(cx),
5824 [
5825 Point::new(1, 3)..Point::new(1, 3),
5826 Point::new(2, 1)..Point::new(2, 1),
5827 ]
5828 );
5829 });
5830
5831 multibuffer.update(cx, |multibuffer, cx| {
5832 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
5833 });
5834 editor.update(cx, |editor, cx| {
5835 // Removing an excerpt causes the first selection to become degenerate.
5836 assert_eq!(
5837 editor.selections.ranges(cx),
5838 [
5839 Point::new(0, 0)..Point::new(0, 0),
5840 Point::new(0, 1)..Point::new(0, 1)
5841 ]
5842 );
5843
5844 // Refreshing selections will relocate the first selection to the original buffer
5845 // location.
5846 editor.change_selections(None, cx, |s| s.refresh());
5847 assert_eq!(
5848 editor.selections.ranges(cx),
5849 [
5850 Point::new(0, 1)..Point::new(0, 1),
5851 Point::new(0, 3)..Point::new(0, 3)
5852 ]
5853 );
5854 assert!(editor.selections.pending_anchor().is_some());
5855 });
5856}
5857
5858#[gpui::test]
5859fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
5860 init_test(cx, |_| {});
5861
5862 let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
5863 let mut excerpt1_id = None;
5864 let multibuffer = cx.add_model(|cx| {
5865 let mut multibuffer = MultiBuffer::new(0);
5866 excerpt1_id = multibuffer
5867 .push_excerpts(
5868 buffer.clone(),
5869 [
5870 ExcerptRange {
5871 context: Point::new(0, 0)..Point::new(1, 4),
5872 primary: None,
5873 },
5874 ExcerptRange {
5875 context: Point::new(1, 0)..Point::new(2, 4),
5876 primary: None,
5877 },
5878 ],
5879 cx,
5880 )
5881 .into_iter()
5882 .next();
5883 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
5884 multibuffer
5885 });
5886
5887 let (_, editor) = cx.add_window(|cx| {
5888 let mut editor = build_editor(multibuffer.clone(), cx);
5889 let snapshot = editor.snapshot(cx);
5890 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
5891 assert_eq!(
5892 editor.selections.ranges(cx),
5893 [Point::new(1, 3)..Point::new(1, 3)]
5894 );
5895 editor
5896 });
5897
5898 multibuffer.update(cx, |multibuffer, cx| {
5899 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
5900 });
5901 editor.update(cx, |editor, cx| {
5902 assert_eq!(
5903 editor.selections.ranges(cx),
5904 [Point::new(0, 0)..Point::new(0, 0)]
5905 );
5906
5907 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
5908 editor.change_selections(None, cx, |s| s.refresh());
5909 assert_eq!(
5910 editor.selections.ranges(cx),
5911 [Point::new(0, 3)..Point::new(0, 3)]
5912 );
5913 assert!(editor.selections.pending_anchor().is_some());
5914 });
5915}
5916
5917#[gpui::test]
5918async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
5919 init_test(cx, |_| {});
5920
5921 let language = Arc::new(
5922 Language::new(
5923 LanguageConfig {
5924 brackets: BracketPairConfig {
5925 pairs: vec![
5926 BracketPair {
5927 start: "{".to_string(),
5928 end: "}".to_string(),
5929 close: true,
5930 newline: true,
5931 },
5932 BracketPair {
5933 start: "/* ".to_string(),
5934 end: " */".to_string(),
5935 close: true,
5936 newline: true,
5937 },
5938 ],
5939 ..Default::default()
5940 },
5941 ..Default::default()
5942 },
5943 Some(tree_sitter_rust::language()),
5944 )
5945 .with_indents_query("")
5946 .unwrap(),
5947 );
5948
5949 let text = concat!(
5950 "{ }\n", //
5951 " x\n", //
5952 " /* */\n", //
5953 "x\n", //
5954 "{{} }\n", //
5955 );
5956
5957 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
5958 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
5959 let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
5960 view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5961 .await;
5962
5963 view.update(cx, |view, cx| {
5964 view.change_selections(None, cx, |s| {
5965 s.select_display_ranges([
5966 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3),
5967 DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
5968 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
5969 ])
5970 });
5971 view.newline(&Newline, cx);
5972
5973 assert_eq!(
5974 view.buffer().read(cx).read(cx).text(),
5975 concat!(
5976 "{ \n", // Suppress rustfmt
5977 "\n", //
5978 "}\n", //
5979 " x\n", //
5980 " /* \n", //
5981 " \n", //
5982 " */\n", //
5983 "x\n", //
5984 "{{} \n", //
5985 "}\n", //
5986 )
5987 );
5988 });
5989}
5990
5991#[gpui::test]
5992fn test_highlighted_ranges(cx: &mut TestAppContext) {
5993 init_test(cx, |_| {});
5994
5995 let (_, editor) = cx.add_window(|cx| {
5996 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
5997 build_editor(buffer.clone(), cx)
5998 });
5999
6000 editor.update(cx, |editor, cx| {
6001 struct Type1;
6002 struct Type2;
6003
6004 let buffer = editor.buffer.read(cx).snapshot(cx);
6005
6006 let anchor_range =
6007 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
6008
6009 editor.highlight_background::<Type1>(
6010 vec![
6011 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
6012 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
6013 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
6014 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
6015 ],
6016 |_| Color::red(),
6017 cx,
6018 );
6019 editor.highlight_background::<Type2>(
6020 vec![
6021 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
6022 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
6023 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
6024 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
6025 ],
6026 |_| Color::green(),
6027 cx,
6028 );
6029
6030 let snapshot = editor.snapshot(cx);
6031 let mut highlighted_ranges = editor.background_highlights_in_range(
6032 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
6033 &snapshot,
6034 theme::current(cx).as_ref(),
6035 );
6036 // Enforce a consistent ordering based on color without relying on the ordering of the
6037 // highlight's `TypeId` which is non-deterministic.
6038 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
6039 assert_eq!(
6040 highlighted_ranges,
6041 &[
6042 (
6043 DisplayPoint::new(3, 2)..DisplayPoint::new(3, 5),
6044 Color::green(),
6045 ),
6046 (
6047 DisplayPoint::new(5, 3)..DisplayPoint::new(5, 6),
6048 Color::green(),
6049 ),
6050 (
6051 DisplayPoint::new(4, 2)..DisplayPoint::new(4, 4),
6052 Color::red(),
6053 ),
6054 (
6055 DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
6056 Color::red(),
6057 ),
6058 ]
6059 );
6060 assert_eq!(
6061 editor.background_highlights_in_range(
6062 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
6063 &snapshot,
6064 theme::current(cx).as_ref(),
6065 ),
6066 &[(
6067 DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
6068 Color::red(),
6069 )]
6070 );
6071 });
6072}
6073
6074#[gpui::test]
6075async fn test_following(cx: &mut gpui::TestAppContext) {
6076 init_test(cx, |_| {});
6077
6078 let fs = FakeFs::new(cx.background());
6079 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6080
6081 let buffer = project.update(cx, |project, cx| {
6082 let buffer = project
6083 .create_buffer(&sample_text(16, 8, 'a'), None, cx)
6084 .unwrap();
6085 cx.add_model(|cx| MultiBuffer::singleton(buffer, cx))
6086 });
6087 let (_, leader) = cx.add_window(|cx| build_editor(buffer.clone(), cx));
6088 let (_, follower) = cx.update(|cx| {
6089 cx.add_window(
6090 WindowOptions {
6091 bounds: WindowBounds::Fixed(RectF::from_points(vec2f(0., 0.), vec2f(10., 80.))),
6092 ..Default::default()
6093 },
6094 |cx| build_editor(buffer.clone(), cx),
6095 )
6096 });
6097
6098 let is_still_following = Rc::new(RefCell::new(true));
6099 let follower_edit_event_count = Rc::new(RefCell::new(0));
6100 let pending_update = Rc::new(RefCell::new(None));
6101 follower.update(cx, {
6102 let update = pending_update.clone();
6103 let is_still_following = is_still_following.clone();
6104 let follower_edit_event_count = follower_edit_event_count.clone();
6105 |_, cx| {
6106 cx.subscribe(&leader, move |_, leader, event, cx| {
6107 leader
6108 .read(cx)
6109 .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx);
6110 })
6111 .detach();
6112
6113 cx.subscribe(&follower, move |_, _, event, cx| {
6114 if Editor::should_unfollow_on_event(event, cx) {
6115 *is_still_following.borrow_mut() = false;
6116 }
6117 if let Event::BufferEdited = event {
6118 *follower_edit_event_count.borrow_mut() += 1;
6119 }
6120 })
6121 .detach();
6122 }
6123 });
6124
6125 // Update the selections only
6126 leader.update(cx, |leader, cx| {
6127 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
6128 });
6129 follower
6130 .update(cx, |follower, cx| {
6131 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6132 })
6133 .await
6134 .unwrap();
6135 follower.read_with(cx, |follower, cx| {
6136 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
6137 });
6138 assert_eq!(*is_still_following.borrow(), true);
6139 assert_eq!(*follower_edit_event_count.borrow(), 0);
6140
6141 // Update the scroll position only
6142 leader.update(cx, |leader, cx| {
6143 leader.set_scroll_position(vec2f(1.5, 3.5), cx);
6144 });
6145 follower
6146 .update(cx, |follower, cx| {
6147 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6148 })
6149 .await
6150 .unwrap();
6151 assert_eq!(
6152 follower.update(cx, |follower, cx| follower.scroll_position(cx)),
6153 vec2f(1.5, 3.5)
6154 );
6155 assert_eq!(*is_still_following.borrow(), true);
6156 assert_eq!(*follower_edit_event_count.borrow(), 0);
6157
6158 // Update the selections and scroll position. The follower's scroll position is updated
6159 // via autoscroll, not via the leader's exact scroll position.
6160 leader.update(cx, |leader, cx| {
6161 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
6162 leader.request_autoscroll(Autoscroll::newest(), cx);
6163 leader.set_scroll_position(vec2f(1.5, 3.5), cx);
6164 });
6165 follower
6166 .update(cx, |follower, cx| {
6167 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6168 })
6169 .await
6170 .unwrap();
6171 follower.update(cx, |follower, cx| {
6172 assert_eq!(follower.scroll_position(cx), vec2f(1.5, 0.0));
6173 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
6174 });
6175 assert_eq!(*is_still_following.borrow(), true);
6176
6177 // Creating a pending selection that precedes another selection
6178 leader.update(cx, |leader, cx| {
6179 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
6180 leader.begin_selection(DisplayPoint::new(0, 0), true, 1, cx);
6181 });
6182 follower
6183 .update(cx, |follower, cx| {
6184 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6185 })
6186 .await
6187 .unwrap();
6188 follower.read_with(cx, |follower, cx| {
6189 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
6190 });
6191 assert_eq!(*is_still_following.borrow(), true);
6192
6193 // Extend the pending selection so that it surrounds another selection
6194 leader.update(cx, |leader, cx| {
6195 leader.extend_selection(DisplayPoint::new(0, 2), 1, cx);
6196 });
6197 follower
6198 .update(cx, |follower, cx| {
6199 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6200 })
6201 .await
6202 .unwrap();
6203 follower.read_with(cx, |follower, cx| {
6204 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
6205 });
6206
6207 // Scrolling locally breaks the follow
6208 follower.update(cx, |follower, cx| {
6209 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
6210 follower.set_scroll_anchor(
6211 ScrollAnchor {
6212 anchor: top_anchor,
6213 offset: vec2f(0.0, 0.5),
6214 },
6215 cx,
6216 );
6217 });
6218 assert_eq!(*is_still_following.borrow(), false);
6219}
6220
6221#[gpui::test]
6222async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
6223 init_test(cx, |_| {});
6224
6225 let fs = FakeFs::new(cx.background());
6226 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6227 let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
6228 let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
6229
6230 let leader = pane.update(cx, |_, cx| {
6231 let multibuffer = cx.add_model(|_| MultiBuffer::new(0));
6232 cx.add_view(|cx| build_editor(multibuffer.clone(), cx))
6233 });
6234
6235 // Start following the editor when it has no excerpts.
6236 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
6237 let follower_1 = cx
6238 .update(|cx| {
6239 Editor::from_state_proto(
6240 pane.clone(),
6241 project.clone(),
6242 ViewId {
6243 creator: Default::default(),
6244 id: 0,
6245 },
6246 &mut state_message,
6247 cx,
6248 )
6249 })
6250 .unwrap()
6251 .await
6252 .unwrap();
6253
6254 let update_message = Rc::new(RefCell::new(None));
6255 follower_1.update(cx, {
6256 let update = update_message.clone();
6257 |_, cx| {
6258 cx.subscribe(&leader, move |_, leader, event, cx| {
6259 leader
6260 .read(cx)
6261 .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx);
6262 })
6263 .detach();
6264 }
6265 });
6266
6267 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
6268 (
6269 project
6270 .create_buffer("abc\ndef\nghi\njkl\n", None, cx)
6271 .unwrap(),
6272 project
6273 .create_buffer("mno\npqr\nstu\nvwx\n", None, cx)
6274 .unwrap(),
6275 )
6276 });
6277
6278 // Insert some excerpts.
6279 leader.update(cx, |leader, cx| {
6280 leader.buffer.update(cx, |multibuffer, cx| {
6281 let excerpt_ids = multibuffer.push_excerpts(
6282 buffer_1.clone(),
6283 [
6284 ExcerptRange {
6285 context: 1..6,
6286 primary: None,
6287 },
6288 ExcerptRange {
6289 context: 12..15,
6290 primary: None,
6291 },
6292 ExcerptRange {
6293 context: 0..3,
6294 primary: None,
6295 },
6296 ],
6297 cx,
6298 );
6299 multibuffer.insert_excerpts_after(
6300 excerpt_ids[0],
6301 buffer_2.clone(),
6302 [
6303 ExcerptRange {
6304 context: 8..12,
6305 primary: None,
6306 },
6307 ExcerptRange {
6308 context: 0..6,
6309 primary: None,
6310 },
6311 ],
6312 cx,
6313 );
6314 });
6315 });
6316
6317 // Apply the update of adding the excerpts.
6318 follower_1
6319 .update(cx, |follower, cx| {
6320 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
6321 })
6322 .await
6323 .unwrap();
6324 assert_eq!(
6325 follower_1.read_with(cx, |editor, cx| editor.text(cx)),
6326 leader.read_with(cx, |editor, cx| editor.text(cx))
6327 );
6328 update_message.borrow_mut().take();
6329
6330 // Start following separately after it already has excerpts.
6331 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
6332 let follower_2 = cx
6333 .update(|cx| {
6334 Editor::from_state_proto(
6335 pane.clone(),
6336 project.clone(),
6337 ViewId {
6338 creator: Default::default(),
6339 id: 0,
6340 },
6341 &mut state_message,
6342 cx,
6343 )
6344 })
6345 .unwrap()
6346 .await
6347 .unwrap();
6348 assert_eq!(
6349 follower_2.read_with(cx, |editor, cx| editor.text(cx)),
6350 leader.read_with(cx, |editor, cx| editor.text(cx))
6351 );
6352
6353 // Remove some excerpts.
6354 leader.update(cx, |leader, cx| {
6355 leader.buffer.update(cx, |multibuffer, cx| {
6356 let excerpt_ids = multibuffer.excerpt_ids();
6357 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
6358 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
6359 });
6360 });
6361
6362 // Apply the update of removing the excerpts.
6363 follower_1
6364 .update(cx, |follower, cx| {
6365 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
6366 })
6367 .await
6368 .unwrap();
6369 follower_2
6370 .update(cx, |follower, cx| {
6371 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
6372 })
6373 .await
6374 .unwrap();
6375 update_message.borrow_mut().take();
6376 assert_eq!(
6377 follower_1.read_with(cx, |editor, cx| editor.text(cx)),
6378 leader.read_with(cx, |editor, cx| editor.text(cx))
6379 );
6380}
6381
6382#[test]
6383fn test_combine_syntax_and_fuzzy_match_highlights() {
6384 let string = "abcdefghijklmnop";
6385 let syntax_ranges = [
6386 (
6387 0..3,
6388 HighlightStyle {
6389 color: Some(Color::red()),
6390 ..Default::default()
6391 },
6392 ),
6393 (
6394 4..8,
6395 HighlightStyle {
6396 color: Some(Color::green()),
6397 ..Default::default()
6398 },
6399 ),
6400 ];
6401 let match_indices = [4, 6, 7, 8];
6402 assert_eq!(
6403 combine_syntax_and_fuzzy_match_highlights(
6404 string,
6405 Default::default(),
6406 syntax_ranges.into_iter(),
6407 &match_indices,
6408 ),
6409 &[
6410 (
6411 0..3,
6412 HighlightStyle {
6413 color: Some(Color::red()),
6414 ..Default::default()
6415 },
6416 ),
6417 (
6418 4..5,
6419 HighlightStyle {
6420 color: Some(Color::green()),
6421 weight: Some(fonts::Weight::BOLD),
6422 ..Default::default()
6423 },
6424 ),
6425 (
6426 5..6,
6427 HighlightStyle {
6428 color: Some(Color::green()),
6429 ..Default::default()
6430 },
6431 ),
6432 (
6433 6..8,
6434 HighlightStyle {
6435 color: Some(Color::green()),
6436 weight: Some(fonts::Weight::BOLD),
6437 ..Default::default()
6438 },
6439 ),
6440 (
6441 8..9,
6442 HighlightStyle {
6443 weight: Some(fonts::Weight::BOLD),
6444 ..Default::default()
6445 },
6446 ),
6447 ]
6448 );
6449}
6450
6451#[gpui::test]
6452async fn go_to_hunk(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
6453 init_test(cx, |_| {});
6454
6455 let mut cx = EditorTestContext::new(cx).await;
6456
6457 let diff_base = r#"
6458 use some::mod;
6459
6460 const A: u32 = 42;
6461
6462 fn main() {
6463 println!("hello");
6464
6465 println!("world");
6466 }
6467 "#
6468 .unindent();
6469
6470 // Edits are modified, removed, modified, added
6471 cx.set_state(
6472 &r#"
6473 use some::modified;
6474
6475 ˇ
6476 fn main() {
6477 println!("hello there");
6478
6479 println!("around the");
6480 println!("world");
6481 }
6482 "#
6483 .unindent(),
6484 );
6485
6486 cx.set_diff_base(Some(&diff_base));
6487 deterministic.run_until_parked();
6488
6489 cx.update_editor(|editor, cx| {
6490 //Wrap around the bottom of the buffer
6491 for _ in 0..3 {
6492 editor.go_to_hunk(&GoToHunk, cx);
6493 }
6494 });
6495
6496 cx.assert_editor_state(
6497 &r#"
6498 ˇuse some::modified;
6499
6500
6501 fn main() {
6502 println!("hello there");
6503
6504 println!("around the");
6505 println!("world");
6506 }
6507 "#
6508 .unindent(),
6509 );
6510
6511 cx.update_editor(|editor, cx| {
6512 //Wrap around the top of the buffer
6513 for _ in 0..2 {
6514 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
6515 }
6516 });
6517
6518 cx.assert_editor_state(
6519 &r#"
6520 use some::modified;
6521
6522
6523 fn main() {
6524 ˇ println!("hello there");
6525
6526 println!("around the");
6527 println!("world");
6528 }
6529 "#
6530 .unindent(),
6531 );
6532
6533 cx.update_editor(|editor, cx| {
6534 editor.fold(&Fold, cx);
6535
6536 //Make sure that the fold only gets one hunk
6537 for _ in 0..4 {
6538 editor.go_to_hunk(&GoToHunk, cx);
6539 }
6540 });
6541
6542 cx.assert_editor_state(
6543 &r#"
6544 ˇuse some::modified;
6545
6546
6547 fn main() {
6548 println!("hello there");
6549
6550 println!("around the");
6551 println!("world");
6552 }
6553 "#
6554 .unindent(),
6555 );
6556}
6557
6558#[test]
6559fn test_split_words() {
6560 fn split<'a>(text: &'a str) -> Vec<&'a str> {
6561 split_words(text).collect()
6562 }
6563
6564 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
6565 assert_eq!(split("hello_world"), &["hello_", "world"]);
6566 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
6567 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
6568 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
6569 assert_eq!(split("helloworld"), &["helloworld"]);
6570}
6571
6572#[gpui::test]
6573async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
6574 init_test(cx, |_| {});
6575
6576 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
6577 let mut assert = |before, after| {
6578 let _state_context = cx.set_state(before);
6579 cx.update_editor(|editor, cx| {
6580 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
6581 });
6582 cx.assert_editor_state(after);
6583 };
6584
6585 // Outside bracket jumps to outside of matching bracket
6586 assert("console.logˇ(var);", "console.log(var)ˇ;");
6587 assert("console.log(var)ˇ;", "console.logˇ(var);");
6588
6589 // Inside bracket jumps to inside of matching bracket
6590 assert("console.log(ˇvar);", "console.log(varˇ);");
6591 assert("console.log(varˇ);", "console.log(ˇvar);");
6592
6593 // When outside a bracket and inside, favor jumping to the inside bracket
6594 assert(
6595 "console.log('foo', [1, 2, 3]ˇ);",
6596 "console.log(ˇ'foo', [1, 2, 3]);",
6597 );
6598 assert(
6599 "console.log(ˇ'foo', [1, 2, 3]);",
6600 "console.log('foo', [1, 2, 3]ˇ);",
6601 );
6602
6603 // Bias forward if two options are equally likely
6604 assert(
6605 "let result = curried_fun()ˇ();",
6606 "let result = curried_fun()()ˇ;",
6607 );
6608
6609 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
6610 assert(
6611 indoc! {"
6612 function test() {
6613 console.log('test')ˇ
6614 }"},
6615 indoc! {"
6616 function test() {
6617 console.logˇ('test')
6618 }"},
6619 );
6620}
6621
6622#[gpui::test(iterations = 10)]
6623async fn test_copilot(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
6624 init_test(cx, |_| {});
6625
6626 let (copilot, copilot_lsp) = Copilot::fake(cx);
6627 cx.update(|cx| cx.set_global(copilot));
6628 let mut cx = EditorLspTestContext::new_rust(
6629 lsp::ServerCapabilities {
6630 completion_provider: Some(lsp::CompletionOptions {
6631 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
6632 ..Default::default()
6633 }),
6634 ..Default::default()
6635 },
6636 cx,
6637 )
6638 .await;
6639
6640 // When inserting, ensure autocompletion is favored over Copilot suggestions.
6641 cx.set_state(indoc! {"
6642 oneˇ
6643 two
6644 three
6645 "});
6646 cx.simulate_keystroke(".");
6647 let _ = handle_completion_request(
6648 &mut cx,
6649 indoc! {"
6650 one.|<>
6651 two
6652 three
6653 "},
6654 vec!["completion_a", "completion_b"],
6655 );
6656 handle_copilot_completion_request(
6657 &copilot_lsp,
6658 vec![copilot::request::Completion {
6659 text: "one.copilot1".into(),
6660 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
6661 ..Default::default()
6662 }],
6663 vec![],
6664 );
6665 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6666 cx.update_editor(|editor, cx| {
6667 assert!(editor.context_menu_visible());
6668 assert!(!editor.has_active_copilot_suggestion(cx));
6669
6670 // Confirming a completion inserts it and hides the context menu, without showing
6671 // the copilot suggestion afterwards.
6672 editor
6673 .confirm_completion(&Default::default(), cx)
6674 .unwrap()
6675 .detach();
6676 assert!(!editor.context_menu_visible());
6677 assert!(!editor.has_active_copilot_suggestion(cx));
6678 assert_eq!(editor.text(cx), "one.completion_a\ntwo\nthree\n");
6679 assert_eq!(editor.display_text(cx), "one.completion_a\ntwo\nthree\n");
6680 });
6681
6682 // Ensure Copilot suggestions are shown right away if no autocompletion is available.
6683 cx.set_state(indoc! {"
6684 oneˇ
6685 two
6686 three
6687 "});
6688 cx.simulate_keystroke(".");
6689 let _ = handle_completion_request(
6690 &mut cx,
6691 indoc! {"
6692 one.|<>
6693 two
6694 three
6695 "},
6696 vec![],
6697 );
6698 handle_copilot_completion_request(
6699 &copilot_lsp,
6700 vec![copilot::request::Completion {
6701 text: "one.copilot1".into(),
6702 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
6703 ..Default::default()
6704 }],
6705 vec![],
6706 );
6707 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6708 cx.update_editor(|editor, cx| {
6709 assert!(!editor.context_menu_visible());
6710 assert!(editor.has_active_copilot_suggestion(cx));
6711 assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
6712 assert_eq!(editor.text(cx), "one.\ntwo\nthree\n");
6713 });
6714
6715 // Reset editor, and ensure autocompletion is still favored over Copilot suggestions.
6716 cx.set_state(indoc! {"
6717 oneˇ
6718 two
6719 three
6720 "});
6721 cx.simulate_keystroke(".");
6722 let _ = handle_completion_request(
6723 &mut cx,
6724 indoc! {"
6725 one.|<>
6726 two
6727 three
6728 "},
6729 vec!["completion_a", "completion_b"],
6730 );
6731 handle_copilot_completion_request(
6732 &copilot_lsp,
6733 vec![copilot::request::Completion {
6734 text: "one.copilot1".into(),
6735 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
6736 ..Default::default()
6737 }],
6738 vec![],
6739 );
6740 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6741 cx.update_editor(|editor, cx| {
6742 assert!(editor.context_menu_visible());
6743 assert!(!editor.has_active_copilot_suggestion(cx));
6744
6745 // When hiding the context menu, the Copilot suggestion becomes visible.
6746 editor.hide_context_menu(cx);
6747 assert!(!editor.context_menu_visible());
6748 assert!(editor.has_active_copilot_suggestion(cx));
6749 assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
6750 assert_eq!(editor.text(cx), "one.\ntwo\nthree\n");
6751 });
6752
6753 // Ensure existing completion is interpolated when inserting again.
6754 cx.simulate_keystroke("c");
6755 deterministic.run_until_parked();
6756 cx.update_editor(|editor, cx| {
6757 assert!(!editor.context_menu_visible());
6758 assert!(editor.has_active_copilot_suggestion(cx));
6759 assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
6760 assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
6761 });
6762
6763 // After debouncing, new Copilot completions should be requested.
6764 handle_copilot_completion_request(
6765 &copilot_lsp,
6766 vec![copilot::request::Completion {
6767 text: "one.copilot2".into(),
6768 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 5)),
6769 ..Default::default()
6770 }],
6771 vec![],
6772 );
6773 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6774 cx.update_editor(|editor, cx| {
6775 assert!(!editor.context_menu_visible());
6776 assert!(editor.has_active_copilot_suggestion(cx));
6777 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
6778 assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
6779
6780 // Canceling should remove the active Copilot suggestion.
6781 editor.cancel(&Default::default(), cx);
6782 assert!(!editor.has_active_copilot_suggestion(cx));
6783 assert_eq!(editor.display_text(cx), "one.c\ntwo\nthree\n");
6784 assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
6785
6786 // After canceling, tabbing shouldn't insert the previously shown suggestion.
6787 editor.tab(&Default::default(), cx);
6788 assert!(!editor.has_active_copilot_suggestion(cx));
6789 assert_eq!(editor.display_text(cx), "one.c \ntwo\nthree\n");
6790 assert_eq!(editor.text(cx), "one.c \ntwo\nthree\n");
6791
6792 // When undoing the previously active suggestion is shown again.
6793 editor.undo(&Default::default(), cx);
6794 assert!(editor.has_active_copilot_suggestion(cx));
6795 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
6796 assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
6797 });
6798
6799 // If an edit occurs outside of this editor, the suggestion is still correctly interpolated.
6800 cx.update_buffer(|buffer, cx| buffer.edit([(5..5, "o")], None, cx));
6801 cx.update_editor(|editor, cx| {
6802 assert!(editor.has_active_copilot_suggestion(cx));
6803 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
6804 assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
6805
6806 // Tabbing when there is an active suggestion inserts it.
6807 editor.tab(&Default::default(), cx);
6808 assert!(!editor.has_active_copilot_suggestion(cx));
6809 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
6810 assert_eq!(editor.text(cx), "one.copilot2\ntwo\nthree\n");
6811
6812 // When undoing the previously active suggestion is shown again.
6813 editor.undo(&Default::default(), cx);
6814 assert!(editor.has_active_copilot_suggestion(cx));
6815 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
6816 assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
6817
6818 // Hide suggestion.
6819 editor.cancel(&Default::default(), cx);
6820 assert!(!editor.has_active_copilot_suggestion(cx));
6821 assert_eq!(editor.display_text(cx), "one.co\ntwo\nthree\n");
6822 assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
6823 });
6824
6825 // If an edit occurs outside of this editor but no suggestion is being shown,
6826 // we won't make it visible.
6827 cx.update_buffer(|buffer, cx| buffer.edit([(6..6, "p")], None, cx));
6828 cx.update_editor(|editor, cx| {
6829 assert!(!editor.has_active_copilot_suggestion(cx));
6830 assert_eq!(editor.display_text(cx), "one.cop\ntwo\nthree\n");
6831 assert_eq!(editor.text(cx), "one.cop\ntwo\nthree\n");
6832 });
6833
6834 // Reset the editor to verify how suggestions behave when tabbing on leading indentation.
6835 cx.update_editor(|editor, cx| {
6836 editor.set_text("fn foo() {\n \n}", cx);
6837 editor.change_selections(None, cx, |s| {
6838 s.select_ranges([Point::new(1, 2)..Point::new(1, 2)])
6839 });
6840 });
6841 handle_copilot_completion_request(
6842 &copilot_lsp,
6843 vec![copilot::request::Completion {
6844 text: " let x = 4;".into(),
6845 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 2)),
6846 ..Default::default()
6847 }],
6848 vec![],
6849 );
6850
6851 cx.update_editor(|editor, cx| editor.next_copilot_suggestion(&Default::default(), cx));
6852 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6853 cx.update_editor(|editor, cx| {
6854 assert!(editor.has_active_copilot_suggestion(cx));
6855 assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
6856 assert_eq!(editor.text(cx), "fn foo() {\n \n}");
6857
6858 // Tabbing inside of leading whitespace inserts indentation without accepting the suggestion.
6859 editor.tab(&Default::default(), cx);
6860 assert!(editor.has_active_copilot_suggestion(cx));
6861 assert_eq!(editor.text(cx), "fn foo() {\n \n}");
6862 assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
6863
6864 // Tabbing again accepts the suggestion.
6865 editor.tab(&Default::default(), cx);
6866 assert!(!editor.has_active_copilot_suggestion(cx));
6867 assert_eq!(editor.text(cx), "fn foo() {\n let x = 4;\n}");
6868 assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
6869 });
6870}
6871
6872#[gpui::test]
6873async fn test_copilot_completion_invalidation(
6874 deterministic: Arc<Deterministic>,
6875 cx: &mut gpui::TestAppContext,
6876) {
6877 init_test(cx, |_| {});
6878
6879 let (copilot, copilot_lsp) = Copilot::fake(cx);
6880 cx.update(|cx| cx.set_global(copilot));
6881 let mut cx = EditorLspTestContext::new_rust(
6882 lsp::ServerCapabilities {
6883 completion_provider: Some(lsp::CompletionOptions {
6884 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
6885 ..Default::default()
6886 }),
6887 ..Default::default()
6888 },
6889 cx,
6890 )
6891 .await;
6892
6893 cx.set_state(indoc! {"
6894 one
6895 twˇ
6896 three
6897 "});
6898
6899 handle_copilot_completion_request(
6900 &copilot_lsp,
6901 vec![copilot::request::Completion {
6902 text: "two.foo()".into(),
6903 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 2)),
6904 ..Default::default()
6905 }],
6906 vec![],
6907 );
6908 cx.update_editor(|editor, cx| editor.next_copilot_suggestion(&Default::default(), cx));
6909 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6910 cx.update_editor(|editor, cx| {
6911 assert!(editor.has_active_copilot_suggestion(cx));
6912 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
6913 assert_eq!(editor.text(cx), "one\ntw\nthree\n");
6914
6915 editor.backspace(&Default::default(), cx);
6916 assert!(editor.has_active_copilot_suggestion(cx));
6917 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
6918 assert_eq!(editor.text(cx), "one\nt\nthree\n");
6919
6920 editor.backspace(&Default::default(), cx);
6921 assert!(editor.has_active_copilot_suggestion(cx));
6922 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
6923 assert_eq!(editor.text(cx), "one\n\nthree\n");
6924
6925 // Deleting across the original suggestion range invalidates it.
6926 editor.backspace(&Default::default(), cx);
6927 assert!(!editor.has_active_copilot_suggestion(cx));
6928 assert_eq!(editor.display_text(cx), "one\nthree\n");
6929 assert_eq!(editor.text(cx), "one\nthree\n");
6930
6931 // Undoing the deletion restores the suggestion.
6932 editor.undo(&Default::default(), cx);
6933 assert!(editor.has_active_copilot_suggestion(cx));
6934 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
6935 assert_eq!(editor.text(cx), "one\n\nthree\n");
6936 });
6937}
6938
6939#[gpui::test]
6940async fn test_copilot_multibuffer(
6941 deterministic: Arc<Deterministic>,
6942 cx: &mut gpui::TestAppContext,
6943) {
6944 init_test(cx, |_| {});
6945
6946 let (copilot, copilot_lsp) = Copilot::fake(cx);
6947 cx.update(|cx| cx.set_global(copilot));
6948
6949 let buffer_1 = cx.add_model(|cx| Buffer::new(0, "a = 1\nb = 2\n", cx));
6950 let buffer_2 = cx.add_model(|cx| Buffer::new(0, "c = 3\nd = 4\n", cx));
6951 let multibuffer = cx.add_model(|cx| {
6952 let mut multibuffer = MultiBuffer::new(0);
6953 multibuffer.push_excerpts(
6954 buffer_1.clone(),
6955 [ExcerptRange {
6956 context: Point::new(0, 0)..Point::new(2, 0),
6957 primary: None,
6958 }],
6959 cx,
6960 );
6961 multibuffer.push_excerpts(
6962 buffer_2.clone(),
6963 [ExcerptRange {
6964 context: Point::new(0, 0)..Point::new(2, 0),
6965 primary: None,
6966 }],
6967 cx,
6968 );
6969 multibuffer
6970 });
6971 let (_, editor) = cx.add_window(|cx| build_editor(multibuffer, cx));
6972
6973 handle_copilot_completion_request(
6974 &copilot_lsp,
6975 vec![copilot::request::Completion {
6976 text: "b = 2 + a".into(),
6977 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 5)),
6978 ..Default::default()
6979 }],
6980 vec![],
6981 );
6982 editor.update(cx, |editor, cx| {
6983 // Ensure copilot suggestions are shown for the first excerpt.
6984 editor.change_selections(None, cx, |s| {
6985 s.select_ranges([Point::new(1, 5)..Point::new(1, 5)])
6986 });
6987 editor.next_copilot_suggestion(&Default::default(), cx);
6988 });
6989 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6990 editor.update(cx, |editor, cx| {
6991 assert!(editor.has_active_copilot_suggestion(cx));
6992 assert_eq!(
6993 editor.display_text(cx),
6994 "\n\na = 1\nb = 2 + a\n\n\n\nc = 3\nd = 4\n"
6995 );
6996 assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4\n");
6997 });
6998
6999 handle_copilot_completion_request(
7000 &copilot_lsp,
7001 vec![copilot::request::Completion {
7002 text: "d = 4 + c".into(),
7003 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 6)),
7004 ..Default::default()
7005 }],
7006 vec![],
7007 );
7008 editor.update(cx, |editor, cx| {
7009 // Move to another excerpt, ensuring the suggestion gets cleared.
7010 editor.change_selections(None, cx, |s| {
7011 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
7012 });
7013 assert!(!editor.has_active_copilot_suggestion(cx));
7014 assert_eq!(
7015 editor.display_text(cx),
7016 "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4\n"
7017 );
7018 assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4\n");
7019
7020 // Type a character, ensuring we don't even try to interpolate the previous suggestion.
7021 editor.handle_input(" ", cx);
7022 assert!(!editor.has_active_copilot_suggestion(cx));
7023 assert_eq!(
7024 editor.display_text(cx),
7025 "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4 \n"
7026 );
7027 assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4 \n");
7028 });
7029
7030 // Ensure the new suggestion is displayed when the debounce timeout expires.
7031 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7032 editor.update(cx, |editor, cx| {
7033 assert!(editor.has_active_copilot_suggestion(cx));
7034 assert_eq!(
7035 editor.display_text(cx),
7036 "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4 + c\n"
7037 );
7038 assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4 \n");
7039 });
7040}
7041
7042#[gpui::test]
7043async fn test_copilot_disabled_globs(
7044 deterministic: Arc<Deterministic>,
7045 cx: &mut gpui::TestAppContext,
7046) {
7047 init_test(cx, |settings| {
7048 settings
7049 .copilot
7050 .get_or_insert(Default::default())
7051 .disabled_globs = Some(vec![".env*".to_string()]);
7052 });
7053
7054 let (copilot, copilot_lsp) = Copilot::fake(cx);
7055 cx.update(|cx| cx.set_global(copilot));
7056
7057 let fs = FakeFs::new(cx.background());
7058 fs.insert_tree(
7059 "/test",
7060 json!({
7061 ".env": "SECRET=something\n",
7062 "README.md": "hello\n"
7063 }),
7064 )
7065 .await;
7066 let project = Project::test(fs, ["/test".as_ref()], cx).await;
7067
7068 let private_buffer = project
7069 .update(cx, |project, cx| {
7070 project.open_local_buffer("/test/.env", cx)
7071 })
7072 .await
7073 .unwrap();
7074 let public_buffer = project
7075 .update(cx, |project, cx| {
7076 project.open_local_buffer("/test/README.md", cx)
7077 })
7078 .await
7079 .unwrap();
7080
7081 let multibuffer = cx.add_model(|cx| {
7082 let mut multibuffer = MultiBuffer::new(0);
7083 multibuffer.push_excerpts(
7084 private_buffer.clone(),
7085 [ExcerptRange {
7086 context: Point::new(0, 0)..Point::new(1, 0),
7087 primary: None,
7088 }],
7089 cx,
7090 );
7091 multibuffer.push_excerpts(
7092 public_buffer.clone(),
7093 [ExcerptRange {
7094 context: Point::new(0, 0)..Point::new(1, 0),
7095 primary: None,
7096 }],
7097 cx,
7098 );
7099 multibuffer
7100 });
7101 let (_, editor) = cx.add_window(|cx| build_editor(multibuffer, cx));
7102
7103 let mut copilot_requests = copilot_lsp
7104 .handle_request::<copilot::request::GetCompletions, _, _>(move |_params, _cx| async move {
7105 Ok(copilot::request::GetCompletionsResult {
7106 completions: vec![copilot::request::Completion {
7107 text: "next line".into(),
7108 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
7109 ..Default::default()
7110 }],
7111 })
7112 });
7113
7114 editor.update(cx, |editor, cx| {
7115 editor.change_selections(None, cx, |selections| {
7116 selections.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
7117 });
7118 editor.next_copilot_suggestion(&Default::default(), cx);
7119 });
7120
7121 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7122 assert!(copilot_requests.try_next().is_err());
7123
7124 editor.update(cx, |editor, cx| {
7125 editor.change_selections(None, cx, |s| {
7126 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
7127 });
7128 editor.next_copilot_suggestion(&Default::default(), cx);
7129 });
7130
7131 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7132 assert!(copilot_requests.try_next().is_ok());
7133}
7134
7135#[gpui::test]
7136async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
7137 init_test(cx, |_| {});
7138
7139 let mut language = Language::new(
7140 LanguageConfig {
7141 name: "Rust".into(),
7142 path_suffixes: vec!["rs".to_string()],
7143 brackets: BracketPairConfig {
7144 pairs: vec![BracketPair {
7145 start: "{".to_string(),
7146 end: "}".to_string(),
7147 close: true,
7148 newline: true,
7149 }],
7150 disabled_scopes_by_bracket_ix: Vec::new(),
7151 },
7152 ..Default::default()
7153 },
7154 Some(tree_sitter_rust::language()),
7155 );
7156 let mut fake_servers = language
7157 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
7158 capabilities: lsp::ServerCapabilities {
7159 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
7160 first_trigger_character: "{".to_string(),
7161 more_trigger_character: None,
7162 }),
7163 ..Default::default()
7164 },
7165 ..Default::default()
7166 }))
7167 .await;
7168
7169 let fs = FakeFs::new(cx.background());
7170 fs.insert_tree(
7171 "/a",
7172 json!({
7173 "main.rs": "fn main() { let a = 5; }",
7174 "other.rs": "// Test file",
7175 }),
7176 )
7177 .await;
7178 let project = Project::test(fs, ["/a".as_ref()], cx).await;
7179 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
7180 let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
7181 let worktree_id = workspace.update(cx, |workspace, cx| {
7182 workspace.project().read_with(cx, |project, cx| {
7183 project.worktrees(cx).next().unwrap().read(cx).id()
7184 })
7185 });
7186
7187 let buffer = project
7188 .update(cx, |project, cx| {
7189 project.open_local_buffer("/a/main.rs", cx)
7190 })
7191 .await
7192 .unwrap();
7193 cx.foreground().run_until_parked();
7194 cx.foreground().start_waiting();
7195 let fake_server = fake_servers.next().await.unwrap();
7196 let editor_handle = workspace
7197 .update(cx, |workspace, cx| {
7198 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
7199 })
7200 .await
7201 .unwrap()
7202 .downcast::<Editor>()
7203 .unwrap();
7204
7205 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
7206 assert_eq!(
7207 params.text_document_position.text_document.uri,
7208 lsp::Url::from_file_path("/a/main.rs").unwrap(),
7209 );
7210 assert_eq!(
7211 params.text_document_position.position,
7212 lsp::Position::new(0, 21),
7213 );
7214
7215 Ok(Some(vec![lsp::TextEdit {
7216 new_text: "]".to_string(),
7217 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
7218 }]))
7219 });
7220
7221 editor_handle.update(cx, |editor, cx| {
7222 cx.focus(&editor_handle);
7223 editor.change_selections(None, cx, |s| {
7224 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
7225 });
7226 editor.handle_input("{", cx);
7227 });
7228
7229 cx.foreground().run_until_parked();
7230
7231 buffer.read_with(cx, |buffer, _| {
7232 assert_eq!(
7233 buffer.text(),
7234 "fn main() { let a = {5}; }",
7235 "No extra braces from on type formatting should appear in the buffer"
7236 )
7237 });
7238}
7239
7240#[gpui::test]
7241async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
7242 init_test(cx, |_| {});
7243
7244 let language_name: Arc<str> = "Rust".into();
7245 let mut language = Language::new(
7246 LanguageConfig {
7247 name: Arc::clone(&language_name),
7248 path_suffixes: vec!["rs".to_string()],
7249 ..Default::default()
7250 },
7251 Some(tree_sitter_rust::language()),
7252 );
7253
7254 let server_restarts = Arc::new(AtomicUsize::new(0));
7255 let closure_restarts = Arc::clone(&server_restarts);
7256 let language_server_name = "test language server";
7257 let mut fake_servers = language
7258 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
7259 name: language_server_name,
7260 initialization_options: Some(json!({
7261 "testOptionValue": true
7262 })),
7263 initializer: Some(Box::new(move |fake_server| {
7264 let task_restarts = Arc::clone(&closure_restarts);
7265 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
7266 task_restarts.fetch_add(1, atomic::Ordering::Release);
7267 futures::future::ready(Ok(()))
7268 });
7269 })),
7270 ..Default::default()
7271 }))
7272 .await;
7273
7274 let fs = FakeFs::new(cx.background());
7275 fs.insert_tree(
7276 "/a",
7277 json!({
7278 "main.rs": "fn main() { let a = 5; }",
7279 "other.rs": "// Test file",
7280 }),
7281 )
7282 .await;
7283 let project = Project::test(fs, ["/a".as_ref()], cx).await;
7284 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
7285 let (_, _workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
7286 let _buffer = project
7287 .update(cx, |project, cx| {
7288 project.open_local_buffer("/a/main.rs", cx)
7289 })
7290 .await
7291 .unwrap();
7292 let _fake_server = fake_servers.next().await.unwrap();
7293 update_test_language_settings(cx, |language_settings| {
7294 language_settings.languages.insert(
7295 Arc::clone(&language_name),
7296 LanguageSettingsContent {
7297 tab_size: NonZeroU32::new(8),
7298 ..Default::default()
7299 },
7300 );
7301 });
7302 cx.foreground().run_until_parked();
7303 assert_eq!(
7304 server_restarts.load(atomic::Ordering::Acquire),
7305 0,
7306 "Should not restart LSP server on an unrelated change"
7307 );
7308
7309 update_test_project_settings(cx, |project_settings| {
7310 project_settings.lsp.insert(
7311 "Some other server name".into(),
7312 LspSettings {
7313 initialization_options: Some(json!({
7314 "some other init value": false
7315 })),
7316 },
7317 );
7318 });
7319 cx.foreground().run_until_parked();
7320 assert_eq!(
7321 server_restarts.load(atomic::Ordering::Acquire),
7322 0,
7323 "Should not restart LSP server on an unrelated LSP settings change"
7324 );
7325
7326 update_test_project_settings(cx, |project_settings| {
7327 project_settings.lsp.insert(
7328 language_server_name.into(),
7329 LspSettings {
7330 initialization_options: Some(json!({
7331 "anotherInitValue": false
7332 })),
7333 },
7334 );
7335 });
7336 cx.foreground().run_until_parked();
7337 assert_eq!(
7338 server_restarts.load(atomic::Ordering::Acquire),
7339 1,
7340 "Should restart LSP server on a related LSP settings change"
7341 );
7342
7343 update_test_project_settings(cx, |project_settings| {
7344 project_settings.lsp.insert(
7345 language_server_name.into(),
7346 LspSettings {
7347 initialization_options: Some(json!({
7348 "anotherInitValue": false
7349 })),
7350 },
7351 );
7352 });
7353 cx.foreground().run_until_parked();
7354 assert_eq!(
7355 server_restarts.load(atomic::Ordering::Acquire),
7356 1,
7357 "Should not restart LSP server on a related LSP settings change that is the same"
7358 );
7359
7360 update_test_project_settings(cx, |project_settings| {
7361 project_settings.lsp.insert(
7362 language_server_name.into(),
7363 LspSettings {
7364 initialization_options: None,
7365 },
7366 );
7367 });
7368 cx.foreground().run_until_parked();
7369 assert_eq!(
7370 server_restarts.load(atomic::Ordering::Acquire),
7371 2,
7372 "Should restart LSP server on another related LSP settings change"
7373 );
7374}
7375
7376#[gpui::test]
7377async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
7378 init_test(cx, |_| {});
7379
7380 let mut cx = EditorLspTestContext::new_rust(
7381 lsp::ServerCapabilities {
7382 completion_provider: Some(lsp::CompletionOptions {
7383 trigger_characters: Some(vec![".".to_string()]),
7384 ..Default::default()
7385 }),
7386 ..Default::default()
7387 },
7388 cx,
7389 )
7390 .await;
7391
7392 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
7393 cx.simulate_keystroke(".");
7394 let completion_item = lsp::CompletionItem {
7395 label: "some".into(),
7396 kind: Some(lsp::CompletionItemKind::SNIPPET),
7397 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
7398 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
7399 kind: lsp::MarkupKind::Markdown,
7400 value: "```rust\nSome(2)\n```".to_string(),
7401 })),
7402 deprecated: Some(false),
7403 sort_text: Some("fffffff2".to_string()),
7404 filter_text: Some("some".to_string()),
7405 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
7406 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
7407 range: lsp::Range {
7408 start: lsp::Position {
7409 line: 0,
7410 character: 22,
7411 },
7412 end: lsp::Position {
7413 line: 0,
7414 character: 22,
7415 },
7416 },
7417 new_text: "Some(2)".to_string(),
7418 })),
7419 additional_text_edits: Some(vec![lsp::TextEdit {
7420 range: lsp::Range {
7421 start: lsp::Position {
7422 line: 0,
7423 character: 20,
7424 },
7425 end: lsp::Position {
7426 line: 0,
7427 character: 22,
7428 },
7429 },
7430 new_text: "".to_string(),
7431 }]),
7432 ..Default::default()
7433 };
7434
7435 let closure_completion_item = completion_item.clone();
7436 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
7437 let task_completion_item = closure_completion_item.clone();
7438 async move {
7439 Ok(Some(lsp::CompletionResponse::Array(vec![
7440 task_completion_item,
7441 ])))
7442 }
7443 });
7444
7445 request.next().await;
7446
7447 cx.condition(|editor, _| editor.context_menu_visible())
7448 .await;
7449 let apply_additional_edits = cx.update_editor(|editor, cx| {
7450 editor
7451 .confirm_completion(&ConfirmCompletion::default(), cx)
7452 .unwrap()
7453 });
7454 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
7455
7456 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
7457 let task_completion_item = completion_item.clone();
7458 async move { Ok(task_completion_item) }
7459 })
7460 .next()
7461 .await
7462 .unwrap();
7463 apply_additional_edits.await.unwrap();
7464 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
7465}
7466
7467fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
7468 let point = DisplayPoint::new(row as u32, column as u32);
7469 point..point
7470}
7471
7472fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
7473 let (text, ranges) = marked_text_ranges(marked_text, true);
7474 assert_eq!(view.text(cx), text);
7475 assert_eq!(
7476 view.selections.ranges(cx),
7477 ranges,
7478 "Assert selections are {}",
7479 marked_text
7480 );
7481}
7482
7483/// Handle completion request passing a marked string specifying where the completion
7484/// should be triggered from using '|' character, what range should be replaced, and what completions
7485/// should be returned using '<' and '>' to delimit the range
7486fn handle_completion_request<'a>(
7487 cx: &mut EditorLspTestContext<'a>,
7488 marked_string: &str,
7489 completions: Vec<&'static str>,
7490) -> impl Future<Output = ()> {
7491 let complete_from_marker: TextRangeMarker = '|'.into();
7492 let replace_range_marker: TextRangeMarker = ('<', '>').into();
7493 let (_, mut marked_ranges) = marked_text_ranges_by(
7494 marked_string,
7495 vec![complete_from_marker.clone(), replace_range_marker.clone()],
7496 );
7497
7498 let complete_from_position =
7499 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
7500 let replace_range =
7501 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
7502
7503 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
7504 let completions = completions.clone();
7505 async move {
7506 assert_eq!(params.text_document_position.text_document.uri, url.clone());
7507 assert_eq!(
7508 params.text_document_position.position,
7509 complete_from_position
7510 );
7511 Ok(Some(lsp::CompletionResponse::Array(
7512 completions
7513 .iter()
7514 .map(|completion_text| lsp::CompletionItem {
7515 label: completion_text.to_string(),
7516 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
7517 range: replace_range,
7518 new_text: completion_text.to_string(),
7519 })),
7520 ..Default::default()
7521 })
7522 .collect(),
7523 )))
7524 }
7525 });
7526
7527 async move {
7528 request.next().await;
7529 }
7530}
7531
7532fn handle_resolve_completion_request<'a>(
7533 cx: &mut EditorLspTestContext<'a>,
7534 edits: Option<Vec<(&'static str, &'static str)>>,
7535) -> impl Future<Output = ()> {
7536 let edits = edits.map(|edits| {
7537 edits
7538 .iter()
7539 .map(|(marked_string, new_text)| {
7540 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
7541 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
7542 lsp::TextEdit::new(replace_range, new_text.to_string())
7543 })
7544 .collect::<Vec<_>>()
7545 });
7546
7547 let mut request =
7548 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
7549 let edits = edits.clone();
7550 async move {
7551 Ok(lsp::CompletionItem {
7552 additional_text_edits: edits,
7553 ..Default::default()
7554 })
7555 }
7556 });
7557
7558 async move {
7559 request.next().await;
7560 }
7561}
7562
7563fn handle_copilot_completion_request(
7564 lsp: &lsp::FakeLanguageServer,
7565 completions: Vec<copilot::request::Completion>,
7566 completions_cycling: Vec<copilot::request::Completion>,
7567) {
7568 lsp.handle_request::<copilot::request::GetCompletions, _, _>(move |_params, _cx| {
7569 let completions = completions.clone();
7570 async move {
7571 Ok(copilot::request::GetCompletionsResult {
7572 completions: completions.clone(),
7573 })
7574 }
7575 });
7576 lsp.handle_request::<copilot::request::GetCompletionsCycling, _, _>(move |_params, _cx| {
7577 let completions_cycling = completions_cycling.clone();
7578 async move {
7579 Ok(copilot::request::GetCompletionsResult {
7580 completions: completions_cycling.clone(),
7581 })
7582 }
7583 });
7584}
7585
7586pub(crate) fn update_test_language_settings(
7587 cx: &mut TestAppContext,
7588 f: impl Fn(&mut AllLanguageSettingsContent),
7589) {
7590 cx.update(|cx| {
7591 cx.update_global::<SettingsStore, _, _>(|store, cx| {
7592 store.update_user_settings::<AllLanguageSettings>(cx, f);
7593 });
7594 });
7595}
7596
7597pub(crate) fn update_test_project_settings(
7598 cx: &mut TestAppContext,
7599 f: impl Fn(&mut ProjectSettings),
7600) {
7601 cx.update(|cx| {
7602 cx.update_global::<SettingsStore, _, _>(|store, cx| {
7603 store.update_user_settings::<ProjectSettings>(cx, f);
7604 });
7605 });
7606}
7607
7608pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
7609 cx.foreground().forbid_parking();
7610
7611 cx.update(|cx| {
7612 cx.set_global(SettingsStore::test(cx));
7613 theme::init((), cx);
7614 client::init_settings(cx);
7615 language::init(cx);
7616 Project::init_settings(cx);
7617 workspace::init_settings(cx);
7618 crate::init(cx);
7619 });
7620
7621 update_test_language_settings(cx, f);
7622}