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