1use super::*;
2use crate::{
3 scroll::scroll_amount::ScrollAmount,
4 test::{
5 assert_text_with_selections, build_editor, editor_lsp_test_context::EditorLspTestContext,
6 editor_test_context::EditorTestContext, select_ranges,
7 },
8 JoinLines,
9};
10use futures::StreamExt;
11use gpui::{
12 div, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext, WindowBounds,
13 WindowOptions,
14};
15use indoc::indoc;
16use language::{
17 language_settings::{
18 AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent, PrettierSettings,
19 },
20 BracketPairConfig,
21 Capability::ReadWrite,
22 FakeLspAdapter, IndentGuide, LanguageConfig, LanguageConfigOverride, LanguageMatcher,
23 LanguageName, Override, ParsedMarkdown, Point,
24};
25use language_settings::{Formatter, FormatterList, IndentGuideSettings};
26use multi_buffer::MultiBufferIndentGuide;
27use parking_lot::Mutex;
28use project::FakeFs;
29use project::{
30 lsp_command::SIGNATURE_HELP_HIGHLIGHT_CURRENT,
31 project_settings::{LspSettings, ProjectSettings},
32};
33use serde_json::{self, json};
34use std::sync::atomic;
35use std::sync::atomic::AtomicUsize;
36use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
37use unindent::Unindent;
38use util::{
39 assert_set_eq,
40 test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
41};
42use workspace::{
43 item::{FollowEvent, FollowableItem, Item, ItemHandle},
44 NavigationEntry, ViewId,
45};
46
47#[gpui::test]
48fn test_edit_events(cx: &mut TestAppContext) {
49 init_test(cx, |_| {});
50
51 let buffer = cx.new_model(|cx| {
52 let mut buffer = language::Buffer::local("123456", cx);
53 buffer.set_group_interval(Duration::from_secs(1));
54 buffer
55 });
56
57 let events = Rc::new(RefCell::new(Vec::new()));
58 let editor1 = cx.add_window({
59 let events = events.clone();
60 |cx| {
61 let view = cx.view().clone();
62 cx.subscribe(&view, move |_, _, event: &EditorEvent, _| match event {
63 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
64 EditorEvent::BufferEdited => events.borrow_mut().push(("editor1", "buffer edited")),
65 _ => {}
66 })
67 .detach();
68 Editor::for_buffer(buffer.clone(), None, cx)
69 }
70 });
71
72 let editor2 = cx.add_window({
73 let events = events.clone();
74 |cx| {
75 cx.subscribe(
76 &cx.view().clone(),
77 move |_, _, event: &EditorEvent, _| match event {
78 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
79 EditorEvent::BufferEdited => {
80 events.borrow_mut().push(("editor2", "buffer edited"))
81 }
82 _ => {}
83 },
84 )
85 .detach();
86 Editor::for_buffer(buffer.clone(), None, cx)
87 }
88 });
89
90 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
91
92 // Mutating editor 1 will emit an `Edited` event only for that editor.
93 _ = editor1.update(cx, |editor, cx| editor.insert("X", cx));
94 assert_eq!(
95 mem::take(&mut *events.borrow_mut()),
96 [
97 ("editor1", "edited"),
98 ("editor1", "buffer edited"),
99 ("editor2", "buffer edited"),
100 ]
101 );
102
103 // Mutating editor 2 will emit an `Edited` event only for that editor.
104 _ = editor2.update(cx, |editor, cx| editor.delete(&Delete, cx));
105 assert_eq!(
106 mem::take(&mut *events.borrow_mut()),
107 [
108 ("editor2", "edited"),
109 ("editor1", "buffer edited"),
110 ("editor2", "buffer edited"),
111 ]
112 );
113
114 // Undoing on editor 1 will emit an `Edited` event only for that editor.
115 _ = editor1.update(cx, |editor, cx| editor.undo(&Undo, cx));
116 assert_eq!(
117 mem::take(&mut *events.borrow_mut()),
118 [
119 ("editor1", "edited"),
120 ("editor1", "buffer edited"),
121 ("editor2", "buffer edited"),
122 ]
123 );
124
125 // Redoing on editor 1 will emit an `Edited` event only for that editor.
126 _ = editor1.update(cx, |editor, cx| editor.redo(&Redo, cx));
127 assert_eq!(
128 mem::take(&mut *events.borrow_mut()),
129 [
130 ("editor1", "edited"),
131 ("editor1", "buffer edited"),
132 ("editor2", "buffer edited"),
133 ]
134 );
135
136 // Undoing on editor 2 will emit an `Edited` event only for that editor.
137 _ = editor2.update(cx, |editor, cx| editor.undo(&Undo, cx));
138 assert_eq!(
139 mem::take(&mut *events.borrow_mut()),
140 [
141 ("editor2", "edited"),
142 ("editor1", "buffer edited"),
143 ("editor2", "buffer edited"),
144 ]
145 );
146
147 // Redoing on editor 2 will emit an `Edited` event only for that editor.
148 _ = editor2.update(cx, |editor, cx| editor.redo(&Redo, cx));
149 assert_eq!(
150 mem::take(&mut *events.borrow_mut()),
151 [
152 ("editor2", "edited"),
153 ("editor1", "buffer edited"),
154 ("editor2", "buffer edited"),
155 ]
156 );
157
158 // No event is emitted when the mutation is a no-op.
159 _ = editor2.update(cx, |editor, cx| {
160 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
161
162 editor.backspace(&Backspace, cx);
163 });
164 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
165}
166
167#[gpui::test]
168fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
169 init_test(cx, |_| {});
170
171 let mut now = Instant::now();
172 let buffer = cx.new_model(|cx| language::Buffer::local("123456", cx));
173 let group_interval = buffer.update(cx, |buffer, _| buffer.transaction_group_interval());
174 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
175 let editor = cx.add_window(|cx| build_editor(buffer.clone(), cx));
176
177 _ = editor.update(cx, |editor, cx| {
178 editor.start_transaction_at(now, cx);
179 editor.change_selections(None, cx, |s| s.select_ranges([2..4]));
180
181 editor.insert("cd", cx);
182 editor.end_transaction_at(now, cx);
183 assert_eq!(editor.text(cx), "12cd56");
184 assert_eq!(editor.selections.ranges(cx), vec![4..4]);
185
186 editor.start_transaction_at(now, cx);
187 editor.change_selections(None, cx, |s| s.select_ranges([4..5]));
188 editor.insert("e", cx);
189 editor.end_transaction_at(now, cx);
190 assert_eq!(editor.text(cx), "12cde6");
191 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
192
193 now += group_interval + Duration::from_millis(1);
194 editor.change_selections(None, cx, |s| s.select_ranges([2..2]));
195
196 // Simulate an edit in another editor
197 buffer.update(cx, |buffer, cx| {
198 buffer.start_transaction_at(now, cx);
199 buffer.edit([(0..1, "a")], None, cx);
200 buffer.edit([(1..1, "b")], None, cx);
201 buffer.end_transaction_at(now, cx);
202 });
203
204 assert_eq!(editor.text(cx), "ab2cde6");
205 assert_eq!(editor.selections.ranges(cx), vec![3..3]);
206
207 // Last transaction happened past the group interval in a different editor.
208 // Undo it individually and don't restore selections.
209 editor.undo(&Undo, cx);
210 assert_eq!(editor.text(cx), "12cde6");
211 assert_eq!(editor.selections.ranges(cx), vec![2..2]);
212
213 // First two transactions happened within the group interval in this editor.
214 // Undo them together and restore selections.
215 editor.undo(&Undo, cx);
216 editor.undo(&Undo, cx); // Undo stack is empty here, so this is a no-op.
217 assert_eq!(editor.text(cx), "123456");
218 assert_eq!(editor.selections.ranges(cx), vec![0..0]);
219
220 // Redo the first two transactions together.
221 editor.redo(&Redo, cx);
222 assert_eq!(editor.text(cx), "12cde6");
223 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
224
225 // Redo the last transaction on its own.
226 editor.redo(&Redo, cx);
227 assert_eq!(editor.text(cx), "ab2cde6");
228 assert_eq!(editor.selections.ranges(cx), vec![6..6]);
229
230 // Test empty transactions.
231 editor.start_transaction_at(now, cx);
232 editor.end_transaction_at(now, cx);
233 editor.undo(&Undo, cx);
234 assert_eq!(editor.text(cx), "12cde6");
235 });
236}
237
238#[gpui::test]
239fn test_ime_composition(cx: &mut TestAppContext) {
240 init_test(cx, |_| {});
241
242 let buffer = cx.new_model(|cx| {
243 let mut buffer = language::Buffer::local("abcde", cx);
244 // Ensure automatic grouping doesn't occur.
245 buffer.set_group_interval(Duration::ZERO);
246 buffer
247 });
248
249 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
250 cx.add_window(|cx| {
251 let mut editor = build_editor(buffer.clone(), cx);
252
253 // Start a new IME composition.
254 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
255 editor.replace_and_mark_text_in_range(Some(0..1), "á", None, cx);
256 editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, cx);
257 assert_eq!(editor.text(cx), "äbcde");
258 assert_eq!(
259 editor.marked_text_ranges(cx),
260 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
261 );
262
263 // Finalize IME composition.
264 editor.replace_text_in_range(None, "ā", cx);
265 assert_eq!(editor.text(cx), "ābcde");
266 assert_eq!(editor.marked_text_ranges(cx), None);
267
268 // IME composition edits are grouped and are undone/redone at once.
269 editor.undo(&Default::default(), cx);
270 assert_eq!(editor.text(cx), "abcde");
271 assert_eq!(editor.marked_text_ranges(cx), None);
272 editor.redo(&Default::default(), cx);
273 assert_eq!(editor.text(cx), "ābcde");
274 assert_eq!(editor.marked_text_ranges(cx), None);
275
276 // Start a new IME composition.
277 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
278 assert_eq!(
279 editor.marked_text_ranges(cx),
280 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
281 );
282
283 // Undoing during an IME composition cancels it.
284 editor.undo(&Default::default(), cx);
285 assert_eq!(editor.text(cx), "ābcde");
286 assert_eq!(editor.marked_text_ranges(cx), None);
287
288 // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
289 editor.replace_and_mark_text_in_range(Some(4..999), "è", None, cx);
290 assert_eq!(editor.text(cx), "ābcdè");
291 assert_eq!(
292 editor.marked_text_ranges(cx),
293 Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
294 );
295
296 // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
297 editor.replace_text_in_range(Some(4..999), "ę", cx);
298 assert_eq!(editor.text(cx), "ābcdę");
299 assert_eq!(editor.marked_text_ranges(cx), None);
300
301 // Start a new IME composition with multiple cursors.
302 editor.change_selections(None, cx, |s| {
303 s.select_ranges([
304 OffsetUtf16(1)..OffsetUtf16(1),
305 OffsetUtf16(3)..OffsetUtf16(3),
306 OffsetUtf16(5)..OffsetUtf16(5),
307 ])
308 });
309 editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, cx);
310 assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
311 assert_eq!(
312 editor.marked_text_ranges(cx),
313 Some(vec![
314 OffsetUtf16(0)..OffsetUtf16(3),
315 OffsetUtf16(4)..OffsetUtf16(7),
316 OffsetUtf16(8)..OffsetUtf16(11)
317 ])
318 );
319
320 // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
321 editor.replace_and_mark_text_in_range(Some(1..2), "1", None, cx);
322 assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
323 assert_eq!(
324 editor.marked_text_ranges(cx),
325 Some(vec![
326 OffsetUtf16(1)..OffsetUtf16(2),
327 OffsetUtf16(5)..OffsetUtf16(6),
328 OffsetUtf16(9)..OffsetUtf16(10)
329 ])
330 );
331
332 // Finalize IME composition with multiple cursors.
333 editor.replace_text_in_range(Some(9..10), "2", cx);
334 assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
335 assert_eq!(editor.marked_text_ranges(cx), None);
336
337 editor
338 });
339}
340
341#[gpui::test]
342fn test_selection_with_mouse(cx: &mut TestAppContext) {
343 init_test(cx, |_| {});
344
345 let editor = cx.add_window(|cx| {
346 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
347 build_editor(buffer, cx)
348 });
349
350 _ = editor.update(cx, |view, cx| {
351 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
352 });
353 assert_eq!(
354 editor
355 .update(cx, |view, cx| view.selections.display_ranges(cx))
356 .unwrap(),
357 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
358 );
359
360 _ = editor.update(cx, |view, cx| {
361 view.update_selection(
362 DisplayPoint::new(DisplayRow(3), 3),
363 0,
364 gpui::Point::<f32>::default(),
365 cx,
366 );
367 });
368
369 assert_eq!(
370 editor
371 .update(cx, |view, cx| view.selections.display_ranges(cx))
372 .unwrap(),
373 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
374 );
375
376 _ = editor.update(cx, |view, cx| {
377 view.update_selection(
378 DisplayPoint::new(DisplayRow(1), 1),
379 0,
380 gpui::Point::<f32>::default(),
381 cx,
382 );
383 });
384
385 assert_eq!(
386 editor
387 .update(cx, |view, cx| view.selections.display_ranges(cx))
388 .unwrap(),
389 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
390 );
391
392 _ = editor.update(cx, |view, cx| {
393 view.end_selection(cx);
394 view.update_selection(
395 DisplayPoint::new(DisplayRow(3), 3),
396 0,
397 gpui::Point::<f32>::default(),
398 cx,
399 );
400 });
401
402 assert_eq!(
403 editor
404 .update(cx, |view, cx| view.selections.display_ranges(cx))
405 .unwrap(),
406 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
407 );
408
409 _ = editor.update(cx, |view, cx| {
410 view.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, cx);
411 view.update_selection(
412 DisplayPoint::new(DisplayRow(0), 0),
413 0,
414 gpui::Point::<f32>::default(),
415 cx,
416 );
417 });
418
419 assert_eq!(
420 editor
421 .update(cx, |view, cx| view.selections.display_ranges(cx))
422 .unwrap(),
423 [
424 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
425 DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
426 ]
427 );
428
429 _ = editor.update(cx, |view, cx| {
430 view.end_selection(cx);
431 });
432
433 assert_eq!(
434 editor
435 .update(cx, |view, cx| view.selections.display_ranges(cx))
436 .unwrap(),
437 [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
438 );
439}
440
441#[gpui::test]
442fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
443 init_test(cx, |_| {});
444
445 let editor = cx.add_window(|cx| {
446 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
447 build_editor(buffer, cx)
448 });
449
450 _ = editor.update(cx, |view, cx| {
451 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, cx);
452 });
453
454 _ = editor.update(cx, |view, cx| {
455 view.end_selection(cx);
456 });
457
458 _ = editor.update(cx, |view, cx| {
459 view.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, cx);
460 });
461
462 _ = editor.update(cx, |view, cx| {
463 view.end_selection(cx);
464 });
465
466 assert_eq!(
467 editor
468 .update(cx, |view, cx| view.selections.display_ranges(cx))
469 .unwrap(),
470 [
471 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
472 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
473 ]
474 );
475
476 _ = editor.update(cx, |view, cx| {
477 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, cx);
478 });
479
480 _ = editor.update(cx, |view, cx| {
481 view.end_selection(cx);
482 });
483
484 assert_eq!(
485 editor
486 .update(cx, |view, cx| view.selections.display_ranges(cx))
487 .unwrap(),
488 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
489 );
490}
491
492#[gpui::test]
493fn test_canceling_pending_selection(cx: &mut TestAppContext) {
494 init_test(cx, |_| {});
495
496 let view = cx.add_window(|cx| {
497 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
498 build_editor(buffer, cx)
499 });
500
501 _ = view.update(cx, |view, cx| {
502 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
503 assert_eq!(
504 view.selections.display_ranges(cx),
505 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
506 );
507 });
508
509 _ = view.update(cx, |view, cx| {
510 view.update_selection(
511 DisplayPoint::new(DisplayRow(3), 3),
512 0,
513 gpui::Point::<f32>::default(),
514 cx,
515 );
516 assert_eq!(
517 view.selections.display_ranges(cx),
518 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
519 );
520 });
521
522 _ = view.update(cx, |view, cx| {
523 view.cancel(&Cancel, cx);
524 view.update_selection(
525 DisplayPoint::new(DisplayRow(1), 1),
526 0,
527 gpui::Point::<f32>::default(),
528 cx,
529 );
530 assert_eq!(
531 view.selections.display_ranges(cx),
532 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
533 );
534 });
535}
536
537#[gpui::test]
538fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
539 init_test(cx, |_| {});
540
541 let view = cx.add_window(|cx| {
542 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
543 build_editor(buffer, cx)
544 });
545
546 _ = view.update(cx, |view, cx| {
547 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
548 assert_eq!(
549 view.selections.display_ranges(cx),
550 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
551 );
552
553 view.move_down(&Default::default(), cx);
554 assert_eq!(
555 view.selections.display_ranges(cx),
556 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
557 );
558
559 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
560 assert_eq!(
561 view.selections.display_ranges(cx),
562 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
563 );
564
565 view.move_up(&Default::default(), cx);
566 assert_eq!(
567 view.selections.display_ranges(cx),
568 [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
569 );
570 });
571}
572
573#[gpui::test]
574fn test_clone(cx: &mut TestAppContext) {
575 init_test(cx, |_| {});
576
577 let (text, selection_ranges) = marked_text_ranges(
578 indoc! {"
579 one
580 two
581 threeˇ
582 four
583 fiveˇ
584 "},
585 true,
586 );
587
588 let editor = cx.add_window(|cx| {
589 let buffer = MultiBuffer::build_simple(&text, cx);
590 build_editor(buffer, cx)
591 });
592
593 _ = editor.update(cx, |editor, cx| {
594 editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
595 editor.fold_ranges(
596 [
597 (Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
598 (Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
599 ],
600 true,
601 cx,
602 );
603 });
604
605 let cloned_editor = editor
606 .update(cx, |editor, cx| {
607 cx.open_window(Default::default(), |cx| cx.new_view(|cx| editor.clone(cx)))
608 })
609 .unwrap()
610 .unwrap();
611
612 let snapshot = editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
613 let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
614
615 assert_eq!(
616 cloned_editor
617 .update(cx, |e, cx| e.display_text(cx))
618 .unwrap(),
619 editor.update(cx, |e, cx| e.display_text(cx)).unwrap()
620 );
621 assert_eq!(
622 cloned_snapshot
623 .folds_in_range(0..text.len())
624 .collect::<Vec<_>>(),
625 snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
626 );
627 assert_set_eq!(
628 cloned_editor
629 .update(cx, |editor, cx| editor.selections.ranges::<Point>(cx))
630 .unwrap(),
631 editor
632 .update(cx, |editor, cx| editor.selections.ranges(cx))
633 .unwrap()
634 );
635 assert_set_eq!(
636 cloned_editor
637 .update(cx, |e, cx| e.selections.display_ranges(cx))
638 .unwrap(),
639 editor
640 .update(cx, |e, cx| e.selections.display_ranges(cx))
641 .unwrap()
642 );
643}
644
645#[gpui::test]
646async fn test_navigation_history(cx: &mut TestAppContext) {
647 init_test(cx, |_| {});
648
649 use workspace::item::Item;
650
651 let fs = FakeFs::new(cx.executor());
652 let project = Project::test(fs, [], cx).await;
653 let workspace = cx.add_window(|cx| Workspace::test_new(project, cx));
654 let pane = workspace
655 .update(cx, |workspace, _| workspace.active_pane().clone())
656 .unwrap();
657
658 _ = workspace.update(cx, |_v, cx| {
659 cx.new_view(|cx| {
660 let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
661 let mut editor = build_editor(buffer.clone(), cx);
662 let handle = cx.view();
663 editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(handle)));
664
665 fn pop_history(editor: &mut Editor, cx: &mut WindowContext) -> Option<NavigationEntry> {
666 editor.nav_history.as_mut().unwrap().pop_backward(cx)
667 }
668
669 // Move the cursor a small distance.
670 // Nothing is added to the navigation history.
671 editor.change_selections(None, cx, |s| {
672 s.select_display_ranges([
673 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
674 ])
675 });
676 editor.change_selections(None, cx, |s| {
677 s.select_display_ranges([
678 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
679 ])
680 });
681 assert!(pop_history(&mut editor, cx).is_none());
682
683 // Move the cursor a large distance.
684 // The history can jump back to the previous position.
685 editor.change_selections(None, cx, |s| {
686 s.select_display_ranges([
687 DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
688 ])
689 });
690 let nav_entry = pop_history(&mut editor, cx).unwrap();
691 editor.navigate(nav_entry.data.unwrap(), cx);
692 assert_eq!(nav_entry.item.id(), cx.entity_id());
693 assert_eq!(
694 editor.selections.display_ranges(cx),
695 &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
696 );
697 assert!(pop_history(&mut editor, cx).is_none());
698
699 // Move the cursor a small distance via the mouse.
700 // Nothing is added to the navigation history.
701 editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, cx);
702 editor.end_selection(cx);
703 assert_eq!(
704 editor.selections.display_ranges(cx),
705 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
706 );
707 assert!(pop_history(&mut editor, cx).is_none());
708
709 // Move the cursor a large distance via the mouse.
710 // The history can jump back to the previous position.
711 editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, cx);
712 editor.end_selection(cx);
713 assert_eq!(
714 editor.selections.display_ranges(cx),
715 &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
716 );
717 let nav_entry = pop_history(&mut editor, cx).unwrap();
718 editor.navigate(nav_entry.data.unwrap(), cx);
719 assert_eq!(nav_entry.item.id(), cx.entity_id());
720 assert_eq!(
721 editor.selections.display_ranges(cx),
722 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
723 );
724 assert!(pop_history(&mut editor, cx).is_none());
725
726 // Set scroll position to check later
727 editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), cx);
728 let original_scroll_position = editor.scroll_manager.anchor();
729
730 // Jump to the end of the document and adjust scroll
731 editor.move_to_end(&MoveToEnd, cx);
732 editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), cx);
733 assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
734
735 let nav_entry = pop_history(&mut editor, cx).unwrap();
736 editor.navigate(nav_entry.data.unwrap(), cx);
737 assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
738
739 // Ensure we don't panic when navigation data contains invalid anchors *and* points.
740 let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
741 invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
742 let invalid_point = Point::new(9999, 0);
743 editor.navigate(
744 Box::new(NavigationData {
745 cursor_anchor: invalid_anchor,
746 cursor_position: invalid_point,
747 scroll_anchor: ScrollAnchor {
748 anchor: invalid_anchor,
749 offset: Default::default(),
750 },
751 scroll_top_row: invalid_point.row,
752 }),
753 cx,
754 );
755 assert_eq!(
756 editor.selections.display_ranges(cx),
757 &[editor.max_point(cx)..editor.max_point(cx)]
758 );
759 assert_eq!(
760 editor.scroll_position(cx),
761 gpui::Point::new(0., editor.max_point(cx).row().as_f32())
762 );
763
764 editor
765 })
766 });
767}
768
769#[gpui::test]
770fn test_cancel(cx: &mut TestAppContext) {
771 init_test(cx, |_| {});
772
773 let view = cx.add_window(|cx| {
774 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
775 build_editor(buffer, cx)
776 });
777
778 _ = view.update(cx, |view, cx| {
779 view.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, cx);
780 view.update_selection(
781 DisplayPoint::new(DisplayRow(1), 1),
782 0,
783 gpui::Point::<f32>::default(),
784 cx,
785 );
786 view.end_selection(cx);
787
788 view.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, cx);
789 view.update_selection(
790 DisplayPoint::new(DisplayRow(0), 3),
791 0,
792 gpui::Point::<f32>::default(),
793 cx,
794 );
795 view.end_selection(cx);
796 assert_eq!(
797 view.selections.display_ranges(cx),
798 [
799 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
800 DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
801 ]
802 );
803 });
804
805 _ = view.update(cx, |view, cx| {
806 view.cancel(&Cancel, cx);
807 assert_eq!(
808 view.selections.display_ranges(cx),
809 [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
810 );
811 });
812
813 _ = view.update(cx, |view, cx| {
814 view.cancel(&Cancel, cx);
815 assert_eq!(
816 view.selections.display_ranges(cx),
817 [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
818 );
819 });
820}
821
822#[gpui::test]
823fn test_fold_action(cx: &mut TestAppContext) {
824 init_test(cx, |_| {});
825
826 let view = cx.add_window(|cx| {
827 let buffer = MultiBuffer::build_simple(
828 &"
829 impl Foo {
830 // Hello!
831
832 fn a() {
833 1
834 }
835
836 fn b() {
837 2
838 }
839
840 fn c() {
841 3
842 }
843 }
844 "
845 .unindent(),
846 cx,
847 );
848 build_editor(buffer.clone(), cx)
849 });
850
851 _ = view.update(cx, |view, cx| {
852 view.change_selections(None, cx, |s| {
853 s.select_display_ranges([
854 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
855 ]);
856 });
857 view.fold(&Fold, cx);
858 assert_eq!(
859 view.display_text(cx),
860 "
861 impl Foo {
862 // Hello!
863
864 fn a() {
865 1
866 }
867
868 fn b() {⋯
869 }
870
871 fn c() {⋯
872 }
873 }
874 "
875 .unindent(),
876 );
877
878 view.fold(&Fold, cx);
879 assert_eq!(
880 view.display_text(cx),
881 "
882 impl Foo {⋯
883 }
884 "
885 .unindent(),
886 );
887
888 view.unfold_lines(&UnfoldLines, cx);
889 assert_eq!(
890 view.display_text(cx),
891 "
892 impl Foo {
893 // Hello!
894
895 fn a() {
896 1
897 }
898
899 fn b() {⋯
900 }
901
902 fn c() {⋯
903 }
904 }
905 "
906 .unindent(),
907 );
908
909 view.unfold_lines(&UnfoldLines, cx);
910 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
911 });
912}
913
914#[gpui::test]
915fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
916 init_test(cx, |_| {});
917
918 let view = cx.add_window(|cx| {
919 let buffer = MultiBuffer::build_simple(
920 &"
921 class Foo:
922 # Hello!
923
924 def a():
925 print(1)
926
927 def b():
928 print(2)
929
930 def c():
931 print(3)
932 "
933 .unindent(),
934 cx,
935 );
936 build_editor(buffer.clone(), cx)
937 });
938
939 _ = view.update(cx, |view, cx| {
940 view.change_selections(None, cx, |s| {
941 s.select_display_ranges([
942 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
943 ]);
944 });
945 view.fold(&Fold, cx);
946 assert_eq!(
947 view.display_text(cx),
948 "
949 class Foo:
950 # Hello!
951
952 def a():
953 print(1)
954
955 def b():⋯
956
957 def c():⋯
958 "
959 .unindent(),
960 );
961
962 view.fold(&Fold, cx);
963 assert_eq!(
964 view.display_text(cx),
965 "
966 class Foo:⋯
967 "
968 .unindent(),
969 );
970
971 view.unfold_lines(&UnfoldLines, cx);
972 assert_eq!(
973 view.display_text(cx),
974 "
975 class Foo:
976 # Hello!
977
978 def a():
979 print(1)
980
981 def b():⋯
982
983 def c():⋯
984 "
985 .unindent(),
986 );
987
988 view.unfold_lines(&UnfoldLines, cx);
989 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
990 });
991}
992
993#[gpui::test]
994fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
995 init_test(cx, |_| {});
996
997 let view = cx.add_window(|cx| {
998 let buffer = MultiBuffer::build_simple(
999 &"
1000 class Foo:
1001 # Hello!
1002
1003 def a():
1004 print(1)
1005
1006 def b():
1007 print(2)
1008
1009
1010 def c():
1011 print(3)
1012
1013
1014 "
1015 .unindent(),
1016 cx,
1017 );
1018 build_editor(buffer.clone(), cx)
1019 });
1020
1021 _ = view.update(cx, |view, cx| {
1022 view.change_selections(None, cx, |s| {
1023 s.select_display_ranges([
1024 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
1025 ]);
1026 });
1027 view.fold(&Fold, cx);
1028 assert_eq!(
1029 view.display_text(cx),
1030 "
1031 class Foo:
1032 # Hello!
1033
1034 def a():
1035 print(1)
1036
1037 def b():⋯
1038
1039
1040 def c():⋯
1041
1042
1043 "
1044 .unindent(),
1045 );
1046
1047 view.fold(&Fold, cx);
1048 assert_eq!(
1049 view.display_text(cx),
1050 "
1051 class Foo:⋯
1052
1053
1054 "
1055 .unindent(),
1056 );
1057
1058 view.unfold_lines(&UnfoldLines, cx);
1059 assert_eq!(
1060 view.display_text(cx),
1061 "
1062 class Foo:
1063 # Hello!
1064
1065 def a():
1066 print(1)
1067
1068 def b():⋯
1069
1070
1071 def c():⋯
1072
1073
1074 "
1075 .unindent(),
1076 );
1077
1078 view.unfold_lines(&UnfoldLines, cx);
1079 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
1080 });
1081}
1082
1083#[gpui::test]
1084fn test_fold_at_level(cx: &mut TestAppContext) {
1085 init_test(cx, |_| {});
1086
1087 let view = cx.add_window(|cx| {
1088 let buffer = MultiBuffer::build_simple(
1089 &"
1090 class Foo:
1091 # Hello!
1092
1093 def a():
1094 print(1)
1095
1096 def b():
1097 print(2)
1098
1099
1100 class Bar:
1101 # World!
1102
1103 def a():
1104 print(1)
1105
1106 def b():
1107 print(2)
1108
1109
1110 "
1111 .unindent(),
1112 cx,
1113 );
1114 build_editor(buffer.clone(), cx)
1115 });
1116
1117 _ = view.update(cx, |view, cx| {
1118 view.fold_at_level(&FoldAtLevel { level: 2 }, cx);
1119 assert_eq!(
1120 view.display_text(cx),
1121 "
1122 class Foo:
1123 # Hello!
1124
1125 def a():⋯
1126
1127 def b():⋯
1128
1129
1130 class Bar:
1131 # World!
1132
1133 def a():⋯
1134
1135 def b():⋯
1136
1137
1138 "
1139 .unindent(),
1140 );
1141
1142 view.fold_at_level(&FoldAtLevel { level: 1 }, cx);
1143 assert_eq!(
1144 view.display_text(cx),
1145 "
1146 class Foo:⋯
1147
1148
1149 class Bar:⋯
1150
1151
1152 "
1153 .unindent(),
1154 );
1155
1156 view.unfold_all(&UnfoldAll, cx);
1157 view.fold_at_level(&FoldAtLevel { level: 0 }, cx);
1158 assert_eq!(
1159 view.display_text(cx),
1160 "
1161 class Foo:
1162 # Hello!
1163
1164 def a():
1165 print(1)
1166
1167 def b():
1168 print(2)
1169
1170
1171 class Bar:
1172 # World!
1173
1174 def a():
1175 print(1)
1176
1177 def b():
1178 print(2)
1179
1180
1181 "
1182 .unindent(),
1183 );
1184
1185 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
1186 });
1187}
1188
1189#[gpui::test]
1190fn test_move_cursor(cx: &mut TestAppContext) {
1191 init_test(cx, |_| {});
1192
1193 let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
1194 let view = cx.add_window(|cx| build_editor(buffer.clone(), cx));
1195
1196 buffer.update(cx, |buffer, cx| {
1197 buffer.edit(
1198 vec![
1199 (Point::new(1, 0)..Point::new(1, 0), "\t"),
1200 (Point::new(1, 1)..Point::new(1, 1), "\t"),
1201 ],
1202 None,
1203 cx,
1204 );
1205 });
1206 _ = view.update(cx, |view, cx| {
1207 assert_eq!(
1208 view.selections.display_ranges(cx),
1209 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1210 );
1211
1212 view.move_down(&MoveDown, cx);
1213 assert_eq!(
1214 view.selections.display_ranges(cx),
1215 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1216 );
1217
1218 view.move_right(&MoveRight, cx);
1219 assert_eq!(
1220 view.selections.display_ranges(cx),
1221 &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
1222 );
1223
1224 view.move_left(&MoveLeft, cx);
1225 assert_eq!(
1226 view.selections.display_ranges(cx),
1227 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1228 );
1229
1230 view.move_up(&MoveUp, cx);
1231 assert_eq!(
1232 view.selections.display_ranges(cx),
1233 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1234 );
1235
1236 view.move_to_end(&MoveToEnd, cx);
1237 assert_eq!(
1238 view.selections.display_ranges(cx),
1239 &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
1240 );
1241
1242 view.move_to_beginning(&MoveToBeginning, cx);
1243 assert_eq!(
1244 view.selections.display_ranges(cx),
1245 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1246 );
1247
1248 view.change_selections(None, cx, |s| {
1249 s.select_display_ranges([
1250 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
1251 ]);
1252 });
1253 view.select_to_beginning(&SelectToBeginning, cx);
1254 assert_eq!(
1255 view.selections.display_ranges(cx),
1256 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
1257 );
1258
1259 view.select_to_end(&SelectToEnd, cx);
1260 assert_eq!(
1261 view.selections.display_ranges(cx),
1262 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
1263 );
1264 });
1265}
1266
1267// TODO: Re-enable this test
1268#[cfg(target_os = "macos")]
1269#[gpui::test]
1270fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
1271 init_test(cx, |_| {});
1272
1273 let view = cx.add_window(|cx| {
1274 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε", cx);
1275 build_editor(buffer.clone(), cx)
1276 });
1277
1278 assert_eq!('ⓐ'.len_utf8(), 3);
1279 assert_eq!('α'.len_utf8(), 2);
1280
1281 _ = view.update(cx, |view, cx| {
1282 view.fold_ranges(
1283 vec![
1284 (Point::new(0, 6)..Point::new(0, 12), FoldPlaceholder::test()),
1285 (Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
1286 (Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
1287 ],
1288 true,
1289 cx,
1290 );
1291 assert_eq!(view.display_text(cx), "ⓐⓑ⋯ⓔ\nab⋯e\nαβ⋯ε");
1292
1293 view.move_right(&MoveRight, cx);
1294 assert_eq!(
1295 view.selections.display_ranges(cx),
1296 &[empty_range(0, "ⓐ".len())]
1297 );
1298 view.move_right(&MoveRight, cx);
1299 assert_eq!(
1300 view.selections.display_ranges(cx),
1301 &[empty_range(0, "ⓐⓑ".len())]
1302 );
1303 view.move_right(&MoveRight, cx);
1304 assert_eq!(
1305 view.selections.display_ranges(cx),
1306 &[empty_range(0, "ⓐⓑ⋯".len())]
1307 );
1308
1309 view.move_down(&MoveDown, cx);
1310 assert_eq!(
1311 view.selections.display_ranges(cx),
1312 &[empty_range(1, "ab⋯e".len())]
1313 );
1314 view.move_left(&MoveLeft, cx);
1315 assert_eq!(
1316 view.selections.display_ranges(cx),
1317 &[empty_range(1, "ab⋯".len())]
1318 );
1319 view.move_left(&MoveLeft, cx);
1320 assert_eq!(
1321 view.selections.display_ranges(cx),
1322 &[empty_range(1, "ab".len())]
1323 );
1324 view.move_left(&MoveLeft, cx);
1325 assert_eq!(
1326 view.selections.display_ranges(cx),
1327 &[empty_range(1, "a".len())]
1328 );
1329
1330 view.move_down(&MoveDown, cx);
1331 assert_eq!(
1332 view.selections.display_ranges(cx),
1333 &[empty_range(2, "α".len())]
1334 );
1335 view.move_right(&MoveRight, cx);
1336 assert_eq!(
1337 view.selections.display_ranges(cx),
1338 &[empty_range(2, "αβ".len())]
1339 );
1340 view.move_right(&MoveRight, cx);
1341 assert_eq!(
1342 view.selections.display_ranges(cx),
1343 &[empty_range(2, "αβ⋯".len())]
1344 );
1345 view.move_right(&MoveRight, cx);
1346 assert_eq!(
1347 view.selections.display_ranges(cx),
1348 &[empty_range(2, "αβ⋯ε".len())]
1349 );
1350
1351 view.move_up(&MoveUp, cx);
1352 assert_eq!(
1353 view.selections.display_ranges(cx),
1354 &[empty_range(1, "ab⋯e".len())]
1355 );
1356 view.move_down(&MoveDown, cx);
1357 assert_eq!(
1358 view.selections.display_ranges(cx),
1359 &[empty_range(2, "αβ⋯ε".len())]
1360 );
1361 view.move_up(&MoveUp, cx);
1362 assert_eq!(
1363 view.selections.display_ranges(cx),
1364 &[empty_range(1, "ab⋯e".len())]
1365 );
1366
1367 view.move_up(&MoveUp, cx);
1368 assert_eq!(
1369 view.selections.display_ranges(cx),
1370 &[empty_range(0, "ⓐⓑ".len())]
1371 );
1372 view.move_left(&MoveLeft, cx);
1373 assert_eq!(
1374 view.selections.display_ranges(cx),
1375 &[empty_range(0, "ⓐ".len())]
1376 );
1377 view.move_left(&MoveLeft, cx);
1378 assert_eq!(
1379 view.selections.display_ranges(cx),
1380 &[empty_range(0, "".len())]
1381 );
1382 });
1383}
1384
1385#[gpui::test]
1386fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
1387 init_test(cx, |_| {});
1388
1389 let view = cx.add_window(|cx| {
1390 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
1391 build_editor(buffer.clone(), cx)
1392 });
1393 _ = view.update(cx, |view, cx| {
1394 view.change_selections(None, cx, |s| {
1395 s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
1396 });
1397 view.move_down(&MoveDown, cx);
1398 assert_eq!(
1399 view.selections.display_ranges(cx),
1400 &[empty_range(1, "abcd".len())]
1401 );
1402
1403 view.move_down(&MoveDown, cx);
1404 assert_eq!(
1405 view.selections.display_ranges(cx),
1406 &[empty_range(2, "αβγ".len())]
1407 );
1408
1409 view.move_down(&MoveDown, cx);
1410 assert_eq!(
1411 view.selections.display_ranges(cx),
1412 &[empty_range(3, "abcd".len())]
1413 );
1414
1415 view.move_down(&MoveDown, cx);
1416 assert_eq!(
1417 view.selections.display_ranges(cx),
1418 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1419 );
1420
1421 view.move_up(&MoveUp, cx);
1422 assert_eq!(
1423 view.selections.display_ranges(cx),
1424 &[empty_range(3, "abcd".len())]
1425 );
1426
1427 view.move_up(&MoveUp, cx);
1428 assert_eq!(
1429 view.selections.display_ranges(cx),
1430 &[empty_range(2, "αβγ".len())]
1431 );
1432 });
1433}
1434
1435#[gpui::test]
1436fn test_beginning_end_of_line(cx: &mut TestAppContext) {
1437 init_test(cx, |_| {});
1438 let move_to_beg = MoveToBeginningOfLine {
1439 stop_at_soft_wraps: true,
1440 };
1441
1442 let move_to_end = MoveToEndOfLine {
1443 stop_at_soft_wraps: true,
1444 };
1445
1446 let view = cx.add_window(|cx| {
1447 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1448 build_editor(buffer, cx)
1449 });
1450 _ = view.update(cx, |view, cx| {
1451 view.change_selections(None, cx, |s| {
1452 s.select_display_ranges([
1453 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1454 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1455 ]);
1456 });
1457 });
1458
1459 _ = view.update(cx, |view, cx| {
1460 view.move_to_beginning_of_line(&move_to_beg, cx);
1461 assert_eq!(
1462 view.selections.display_ranges(cx),
1463 &[
1464 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1465 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1466 ]
1467 );
1468 });
1469
1470 _ = view.update(cx, |view, cx| {
1471 view.move_to_beginning_of_line(&move_to_beg, cx);
1472 assert_eq!(
1473 view.selections.display_ranges(cx),
1474 &[
1475 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1476 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1477 ]
1478 );
1479 });
1480
1481 _ = view.update(cx, |view, cx| {
1482 view.move_to_beginning_of_line(&move_to_beg, cx);
1483 assert_eq!(
1484 view.selections.display_ranges(cx),
1485 &[
1486 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1487 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1488 ]
1489 );
1490 });
1491
1492 _ = view.update(cx, |view, cx| {
1493 view.move_to_end_of_line(&move_to_end, cx);
1494 assert_eq!(
1495 view.selections.display_ranges(cx),
1496 &[
1497 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1498 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1499 ]
1500 );
1501 });
1502
1503 // Moving to the end of line again is a no-op.
1504 _ = view.update(cx, |view, cx| {
1505 view.move_to_end_of_line(&move_to_end, cx);
1506 assert_eq!(
1507 view.selections.display_ranges(cx),
1508 &[
1509 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1510 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1511 ]
1512 );
1513 });
1514
1515 _ = view.update(cx, |view, cx| {
1516 view.move_left(&MoveLeft, cx);
1517 view.select_to_beginning_of_line(
1518 &SelectToBeginningOfLine {
1519 stop_at_soft_wraps: true,
1520 },
1521 cx,
1522 );
1523 assert_eq!(
1524 view.selections.display_ranges(cx),
1525 &[
1526 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1527 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1528 ]
1529 );
1530 });
1531
1532 _ = view.update(cx, |view, cx| {
1533 view.select_to_beginning_of_line(
1534 &SelectToBeginningOfLine {
1535 stop_at_soft_wraps: true,
1536 },
1537 cx,
1538 );
1539 assert_eq!(
1540 view.selections.display_ranges(cx),
1541 &[
1542 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1543 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1544 ]
1545 );
1546 });
1547
1548 _ = view.update(cx, |view, cx| {
1549 view.select_to_beginning_of_line(
1550 &SelectToBeginningOfLine {
1551 stop_at_soft_wraps: true,
1552 },
1553 cx,
1554 );
1555 assert_eq!(
1556 view.selections.display_ranges(cx),
1557 &[
1558 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1559 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1560 ]
1561 );
1562 });
1563
1564 _ = view.update(cx, |view, cx| {
1565 view.select_to_end_of_line(
1566 &SelectToEndOfLine {
1567 stop_at_soft_wraps: true,
1568 },
1569 cx,
1570 );
1571 assert_eq!(
1572 view.selections.display_ranges(cx),
1573 &[
1574 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
1575 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
1576 ]
1577 );
1578 });
1579
1580 _ = view.update(cx, |view, cx| {
1581 view.delete_to_end_of_line(&DeleteToEndOfLine, cx);
1582 assert_eq!(view.display_text(cx), "ab\n de");
1583 assert_eq!(
1584 view.selections.display_ranges(cx),
1585 &[
1586 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
1587 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1588 ]
1589 );
1590 });
1591
1592 _ = view.update(cx, |view, cx| {
1593 view.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
1594 assert_eq!(view.display_text(cx), "\n");
1595 assert_eq!(
1596 view.selections.display_ranges(cx),
1597 &[
1598 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1599 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1600 ]
1601 );
1602 });
1603}
1604
1605#[gpui::test]
1606fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
1607 init_test(cx, |_| {});
1608 let move_to_beg = MoveToBeginningOfLine {
1609 stop_at_soft_wraps: false,
1610 };
1611
1612 let move_to_end = MoveToEndOfLine {
1613 stop_at_soft_wraps: false,
1614 };
1615
1616 let view = cx.add_window(|cx| {
1617 let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
1618 build_editor(buffer, cx)
1619 });
1620
1621 _ = view.update(cx, |view, cx| {
1622 view.set_wrap_width(Some(140.0.into()), cx);
1623
1624 // We expect the following lines after wrapping
1625 // ```
1626 // thequickbrownfox
1627 // jumpedoverthelazydo
1628 // gs
1629 // ```
1630 // The final `gs` was soft-wrapped onto a new line.
1631 assert_eq!(
1632 "thequickbrownfox\njumpedoverthelaz\nydogs",
1633 view.display_text(cx),
1634 );
1635
1636 // First, let's assert behavior on the first line, that was not soft-wrapped.
1637 // Start the cursor at the `k` on the first line
1638 view.change_selections(None, cx, |s| {
1639 s.select_display_ranges([
1640 DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
1641 ]);
1642 });
1643
1644 // Moving to the beginning of the line should put us at the beginning of the line.
1645 view.move_to_beginning_of_line(&move_to_beg, cx);
1646 assert_eq!(
1647 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
1648 view.selections.display_ranges(cx)
1649 );
1650
1651 // Moving to the end of the line should put us at the end of the line.
1652 view.move_to_end_of_line(&move_to_end, cx);
1653 assert_eq!(
1654 vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
1655 view.selections.display_ranges(cx)
1656 );
1657
1658 // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
1659 // Start the cursor at the last line (`y` that was wrapped to a new line)
1660 view.change_selections(None, cx, |s| {
1661 s.select_display_ranges([
1662 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
1663 ]);
1664 });
1665
1666 // Moving to the beginning of the line should put us at the start of the second line of
1667 // display text, i.e., the `j`.
1668 view.move_to_beginning_of_line(&move_to_beg, cx);
1669 assert_eq!(
1670 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1671 view.selections.display_ranges(cx)
1672 );
1673
1674 // Moving to the beginning of the line again should be a no-op.
1675 view.move_to_beginning_of_line(&move_to_beg, cx);
1676 assert_eq!(
1677 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1678 view.selections.display_ranges(cx)
1679 );
1680
1681 // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
1682 // next display line.
1683 view.move_to_end_of_line(&move_to_end, cx);
1684 assert_eq!(
1685 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1686 view.selections.display_ranges(cx)
1687 );
1688
1689 // Moving to the end of the line again should be a no-op.
1690 view.move_to_end_of_line(&move_to_end, cx);
1691 assert_eq!(
1692 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1693 view.selections.display_ranges(cx)
1694 );
1695 });
1696}
1697
1698#[gpui::test]
1699fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1700 init_test(cx, |_| {});
1701
1702 let view = cx.add_window(|cx| {
1703 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1704 build_editor(buffer, cx)
1705 });
1706 _ = view.update(cx, |view, cx| {
1707 view.change_selections(None, cx, |s| {
1708 s.select_display_ranges([
1709 DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
1710 DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
1711 ])
1712 });
1713
1714 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1715 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1716
1717 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1718 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", view, cx);
1719
1720 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1721 assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", view, cx);
1722
1723 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1724 assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1725
1726 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1727 assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", view, cx);
1728
1729 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1730 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", view, cx);
1731
1732 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1733 assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1734
1735 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1736 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1737
1738 view.move_right(&MoveRight, cx);
1739 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1740 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1741
1742 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1743 assert_selection_ranges("use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}", view, cx);
1744
1745 view.select_to_next_word_end(&SelectToNextWordEnd, cx);
1746 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1747 });
1748}
1749
1750#[gpui::test]
1751fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1752 init_test(cx, |_| {});
1753
1754 let view = cx.add_window(|cx| {
1755 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1756 build_editor(buffer, cx)
1757 });
1758
1759 _ = view.update(cx, |view, cx| {
1760 view.set_wrap_width(Some(140.0.into()), cx);
1761 assert_eq!(
1762 view.display_text(cx),
1763 "use one::{\n two::three::\n four::five\n};"
1764 );
1765
1766 view.change_selections(None, cx, |s| {
1767 s.select_display_ranges([
1768 DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
1769 ]);
1770 });
1771
1772 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1773 assert_eq!(
1774 view.selections.display_ranges(cx),
1775 &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
1776 );
1777
1778 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1779 assert_eq!(
1780 view.selections.display_ranges(cx),
1781 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1782 );
1783
1784 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1785 assert_eq!(
1786 view.selections.display_ranges(cx),
1787 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1788 );
1789
1790 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1791 assert_eq!(
1792 view.selections.display_ranges(cx),
1793 &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
1794 );
1795
1796 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1797 assert_eq!(
1798 view.selections.display_ranges(cx),
1799 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1800 );
1801
1802 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1803 assert_eq!(
1804 view.selections.display_ranges(cx),
1805 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1806 );
1807 });
1808}
1809
1810#[gpui::test]
1811async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) {
1812 init_test(cx, |_| {});
1813 let mut cx = EditorTestContext::new(cx).await;
1814
1815 let line_height = cx.editor(|editor, cx| {
1816 editor
1817 .style()
1818 .unwrap()
1819 .text
1820 .line_height_in_pixels(cx.rem_size())
1821 });
1822 cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
1823
1824 cx.set_state(
1825 &r#"ˇone
1826 two
1827
1828 three
1829 fourˇ
1830 five
1831
1832 six"#
1833 .unindent(),
1834 );
1835
1836 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1837 cx.assert_editor_state(
1838 &r#"one
1839 two
1840 ˇ
1841 three
1842 four
1843 five
1844 ˇ
1845 six"#
1846 .unindent(),
1847 );
1848
1849 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1850 cx.assert_editor_state(
1851 &r#"one
1852 two
1853
1854 three
1855 four
1856 five
1857 ˇ
1858 sixˇ"#
1859 .unindent(),
1860 );
1861
1862 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1863 cx.assert_editor_state(
1864 &r#"one
1865 two
1866
1867 three
1868 four
1869 five
1870
1871 sixˇ"#
1872 .unindent(),
1873 );
1874
1875 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1876 cx.assert_editor_state(
1877 &r#"one
1878 two
1879
1880 three
1881 four
1882 five
1883 ˇ
1884 six"#
1885 .unindent(),
1886 );
1887
1888 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1889 cx.assert_editor_state(
1890 &r#"one
1891 two
1892 ˇ
1893 three
1894 four
1895 five
1896
1897 six"#
1898 .unindent(),
1899 );
1900
1901 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1902 cx.assert_editor_state(
1903 &r#"ˇone
1904 two
1905
1906 three
1907 four
1908 five
1909
1910 six"#
1911 .unindent(),
1912 );
1913}
1914
1915#[gpui::test]
1916async fn test_scroll_page_up_page_down(cx: &mut gpui::TestAppContext) {
1917 init_test(cx, |_| {});
1918 let mut cx = EditorTestContext::new(cx).await;
1919 let line_height = cx.editor(|editor, cx| {
1920 editor
1921 .style()
1922 .unwrap()
1923 .text
1924 .line_height_in_pixels(cx.rem_size())
1925 });
1926 let window = cx.window;
1927 cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
1928
1929 cx.set_state(
1930 r#"ˇone
1931 two
1932 three
1933 four
1934 five
1935 six
1936 seven
1937 eight
1938 nine
1939 ten
1940 "#,
1941 );
1942
1943 cx.update_editor(|editor, cx| {
1944 assert_eq!(
1945 editor.snapshot(cx).scroll_position(),
1946 gpui::Point::new(0., 0.)
1947 );
1948 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1949 assert_eq!(
1950 editor.snapshot(cx).scroll_position(),
1951 gpui::Point::new(0., 3.)
1952 );
1953 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1954 assert_eq!(
1955 editor.snapshot(cx).scroll_position(),
1956 gpui::Point::new(0., 6.)
1957 );
1958 editor.scroll_screen(&ScrollAmount::Page(-1.), cx);
1959 assert_eq!(
1960 editor.snapshot(cx).scroll_position(),
1961 gpui::Point::new(0., 3.)
1962 );
1963
1964 editor.scroll_screen(&ScrollAmount::Page(-0.5), cx);
1965 assert_eq!(
1966 editor.snapshot(cx).scroll_position(),
1967 gpui::Point::new(0., 1.)
1968 );
1969 editor.scroll_screen(&ScrollAmount::Page(0.5), cx);
1970 assert_eq!(
1971 editor.snapshot(cx).scroll_position(),
1972 gpui::Point::new(0., 3.)
1973 );
1974 });
1975}
1976
1977#[gpui::test]
1978async fn test_autoscroll(cx: &mut gpui::TestAppContext) {
1979 init_test(cx, |_| {});
1980 let mut cx = EditorTestContext::new(cx).await;
1981
1982 let line_height = cx.update_editor(|editor, cx| {
1983 editor.set_vertical_scroll_margin(2, cx);
1984 editor
1985 .style()
1986 .unwrap()
1987 .text
1988 .line_height_in_pixels(cx.rem_size())
1989 });
1990 let window = cx.window;
1991 cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
1992
1993 cx.set_state(
1994 r#"ˇone
1995 two
1996 three
1997 four
1998 five
1999 six
2000 seven
2001 eight
2002 nine
2003 ten
2004 "#,
2005 );
2006 cx.update_editor(|editor, cx| {
2007 assert_eq!(
2008 editor.snapshot(cx).scroll_position(),
2009 gpui::Point::new(0., 0.0)
2010 );
2011 });
2012
2013 // Add a cursor below the visible area. Since both cursors cannot fit
2014 // on screen, the editor autoscrolls to reveal the newest cursor, and
2015 // allows the vertical scroll margin below that cursor.
2016 cx.update_editor(|editor, cx| {
2017 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
2018 selections.select_ranges([
2019 Point::new(0, 0)..Point::new(0, 0),
2020 Point::new(6, 0)..Point::new(6, 0),
2021 ]);
2022 })
2023 });
2024 cx.update_editor(|editor, cx| {
2025 assert_eq!(
2026 editor.snapshot(cx).scroll_position(),
2027 gpui::Point::new(0., 3.0)
2028 );
2029 });
2030
2031 // Move down. The editor cursor scrolls down to track the newest cursor.
2032 cx.update_editor(|editor, cx| {
2033 editor.move_down(&Default::default(), cx);
2034 });
2035 cx.update_editor(|editor, cx| {
2036 assert_eq!(
2037 editor.snapshot(cx).scroll_position(),
2038 gpui::Point::new(0., 4.0)
2039 );
2040 });
2041
2042 // Add a cursor above the visible area. Since both cursors fit on screen,
2043 // the editor scrolls to show both.
2044 cx.update_editor(|editor, cx| {
2045 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
2046 selections.select_ranges([
2047 Point::new(1, 0)..Point::new(1, 0),
2048 Point::new(6, 0)..Point::new(6, 0),
2049 ]);
2050 })
2051 });
2052 cx.update_editor(|editor, cx| {
2053 assert_eq!(
2054 editor.snapshot(cx).scroll_position(),
2055 gpui::Point::new(0., 1.0)
2056 );
2057 });
2058}
2059
2060#[gpui::test]
2061async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
2062 init_test(cx, |_| {});
2063 let mut cx = EditorTestContext::new(cx).await;
2064
2065 let line_height = cx.editor(|editor, cx| {
2066 editor
2067 .style()
2068 .unwrap()
2069 .text
2070 .line_height_in_pixels(cx.rem_size())
2071 });
2072 let window = cx.window;
2073 cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
2074 cx.set_state(
2075 &r#"
2076 ˇone
2077 two
2078 threeˇ
2079 four
2080 five
2081 six
2082 seven
2083 eight
2084 nine
2085 ten
2086 "#
2087 .unindent(),
2088 );
2089
2090 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
2091 cx.assert_editor_state(
2092 &r#"
2093 one
2094 two
2095 three
2096 ˇfour
2097 five
2098 sixˇ
2099 seven
2100 eight
2101 nine
2102 ten
2103 "#
2104 .unindent(),
2105 );
2106
2107 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
2108 cx.assert_editor_state(
2109 &r#"
2110 one
2111 two
2112 three
2113 four
2114 five
2115 six
2116 ˇseven
2117 eight
2118 nineˇ
2119 ten
2120 "#
2121 .unindent(),
2122 );
2123
2124 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2125 cx.assert_editor_state(
2126 &r#"
2127 one
2128 two
2129 three
2130 ˇfour
2131 five
2132 sixˇ
2133 seven
2134 eight
2135 nine
2136 ten
2137 "#
2138 .unindent(),
2139 );
2140
2141 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2142 cx.assert_editor_state(
2143 &r#"
2144 ˇone
2145 two
2146 threeˇ
2147 four
2148 five
2149 six
2150 seven
2151 eight
2152 nine
2153 ten
2154 "#
2155 .unindent(),
2156 );
2157
2158 // Test select collapsing
2159 cx.update_editor(|editor, cx| {
2160 editor.move_page_down(&MovePageDown::default(), cx);
2161 editor.move_page_down(&MovePageDown::default(), cx);
2162 editor.move_page_down(&MovePageDown::default(), cx);
2163 });
2164 cx.assert_editor_state(
2165 &r#"
2166 one
2167 two
2168 three
2169 four
2170 five
2171 six
2172 seven
2173 eight
2174 nine
2175 ˇten
2176 ˇ"#
2177 .unindent(),
2178 );
2179}
2180
2181#[gpui::test]
2182async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
2183 init_test(cx, |_| {});
2184 let mut cx = EditorTestContext::new(cx).await;
2185 cx.set_state("one «two threeˇ» four");
2186 cx.update_editor(|editor, cx| {
2187 editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
2188 assert_eq!(editor.text(cx), " four");
2189 });
2190}
2191
2192#[gpui::test]
2193fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
2194 init_test(cx, |_| {});
2195
2196 let view = cx.add_window(|cx| {
2197 let buffer = MultiBuffer::build_simple("one two three four", cx);
2198 build_editor(buffer.clone(), cx)
2199 });
2200
2201 _ = view.update(cx, |view, cx| {
2202 view.change_selections(None, cx, |s| {
2203 s.select_display_ranges([
2204 // an empty selection - the preceding word fragment is deleted
2205 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2206 // characters selected - they are deleted
2207 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
2208 ])
2209 });
2210 view.delete_to_previous_word_start(
2211 &DeleteToPreviousWordStart {
2212 ignore_newlines: false,
2213 },
2214 cx,
2215 );
2216 assert_eq!(view.buffer.read(cx).read(cx).text(), "e two te four");
2217 });
2218
2219 _ = view.update(cx, |view, cx| {
2220 view.change_selections(None, cx, |s| {
2221 s.select_display_ranges([
2222 // an empty selection - the following word fragment is deleted
2223 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
2224 // characters selected - they are deleted
2225 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
2226 ])
2227 });
2228 view.delete_to_next_word_end(
2229 &DeleteToNextWordEnd {
2230 ignore_newlines: false,
2231 },
2232 cx,
2233 );
2234 assert_eq!(view.buffer.read(cx).read(cx).text(), "e t te our");
2235 });
2236}
2237
2238#[gpui::test]
2239fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
2240 init_test(cx, |_| {});
2241
2242 let view = cx.add_window(|cx| {
2243 let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
2244 build_editor(buffer.clone(), cx)
2245 });
2246 let del_to_prev_word_start = DeleteToPreviousWordStart {
2247 ignore_newlines: false,
2248 };
2249 let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
2250 ignore_newlines: true,
2251 };
2252
2253 _ = view.update(cx, |view, cx| {
2254 view.change_selections(None, cx, |s| {
2255 s.select_display_ranges([
2256 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
2257 ])
2258 });
2259 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2260 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
2261 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2262 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\nthree");
2263 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2264 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\n");
2265 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2266 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2");
2267 view.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, cx);
2268 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n");
2269 view.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, cx);
2270 assert_eq!(view.buffer.read(cx).read(cx).text(), "");
2271 });
2272}
2273
2274#[gpui::test]
2275fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
2276 init_test(cx, |_| {});
2277
2278 let view = cx.add_window(|cx| {
2279 let buffer = MultiBuffer::build_simple("\none\n two\nthree\n four", cx);
2280 build_editor(buffer.clone(), cx)
2281 });
2282 let del_to_next_word_end = DeleteToNextWordEnd {
2283 ignore_newlines: false,
2284 };
2285 let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
2286 ignore_newlines: true,
2287 };
2288
2289 _ = view.update(cx, |view, cx| {
2290 view.change_selections(None, cx, |s| {
2291 s.select_display_ranges([
2292 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
2293 ])
2294 });
2295 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2296 assert_eq!(
2297 view.buffer.read(cx).read(cx).text(),
2298 "one\n two\nthree\n four"
2299 );
2300 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2301 assert_eq!(
2302 view.buffer.read(cx).read(cx).text(),
2303 "\n two\nthree\n four"
2304 );
2305 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2306 assert_eq!(view.buffer.read(cx).read(cx).text(), "two\nthree\n four");
2307 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2308 assert_eq!(view.buffer.read(cx).read(cx).text(), "\nthree\n four");
2309 view.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, cx);
2310 assert_eq!(view.buffer.read(cx).read(cx).text(), "\n four");
2311 view.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, cx);
2312 assert_eq!(view.buffer.read(cx).read(cx).text(), "");
2313 });
2314}
2315
2316#[gpui::test]
2317fn test_newline(cx: &mut TestAppContext) {
2318 init_test(cx, |_| {});
2319
2320 let view = cx.add_window(|cx| {
2321 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
2322 build_editor(buffer.clone(), cx)
2323 });
2324
2325 _ = view.update(cx, |view, cx| {
2326 view.change_selections(None, cx, |s| {
2327 s.select_display_ranges([
2328 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2329 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
2330 DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
2331 ])
2332 });
2333
2334 view.newline(&Newline, cx);
2335 assert_eq!(view.text(cx), "aa\naa\n \n bb\n bb\n");
2336 });
2337}
2338
2339#[gpui::test]
2340fn test_newline_with_old_selections(cx: &mut TestAppContext) {
2341 init_test(cx, |_| {});
2342
2343 let editor = cx.add_window(|cx| {
2344 let buffer = MultiBuffer::build_simple(
2345 "
2346 a
2347 b(
2348 X
2349 )
2350 c(
2351 X
2352 )
2353 "
2354 .unindent()
2355 .as_str(),
2356 cx,
2357 );
2358 let mut editor = build_editor(buffer.clone(), cx);
2359 editor.change_selections(None, cx, |s| {
2360 s.select_ranges([
2361 Point::new(2, 4)..Point::new(2, 5),
2362 Point::new(5, 4)..Point::new(5, 5),
2363 ])
2364 });
2365 editor
2366 });
2367
2368 _ = editor.update(cx, |editor, cx| {
2369 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2370 editor.buffer.update(cx, |buffer, cx| {
2371 buffer.edit(
2372 [
2373 (Point::new(1, 2)..Point::new(3, 0), ""),
2374 (Point::new(4, 2)..Point::new(6, 0), ""),
2375 ],
2376 None,
2377 cx,
2378 );
2379 assert_eq!(
2380 buffer.read(cx).text(),
2381 "
2382 a
2383 b()
2384 c()
2385 "
2386 .unindent()
2387 );
2388 });
2389 assert_eq!(
2390 editor.selections.ranges(cx),
2391 &[
2392 Point::new(1, 2)..Point::new(1, 2),
2393 Point::new(2, 2)..Point::new(2, 2),
2394 ],
2395 );
2396
2397 editor.newline(&Newline, cx);
2398 assert_eq!(
2399 editor.text(cx),
2400 "
2401 a
2402 b(
2403 )
2404 c(
2405 )
2406 "
2407 .unindent()
2408 );
2409
2410 // The selections are moved after the inserted newlines
2411 assert_eq!(
2412 editor.selections.ranges(cx),
2413 &[
2414 Point::new(2, 0)..Point::new(2, 0),
2415 Point::new(4, 0)..Point::new(4, 0),
2416 ],
2417 );
2418 });
2419}
2420
2421#[gpui::test]
2422async fn test_newline_above(cx: &mut gpui::TestAppContext) {
2423 init_test(cx, |settings| {
2424 settings.defaults.tab_size = NonZeroU32::new(4)
2425 });
2426
2427 let language = Arc::new(
2428 Language::new(
2429 LanguageConfig::default(),
2430 Some(tree_sitter_rust::LANGUAGE.into()),
2431 )
2432 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2433 .unwrap(),
2434 );
2435
2436 let mut cx = EditorTestContext::new(cx).await;
2437 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2438 cx.set_state(indoc! {"
2439 const a: ˇA = (
2440 (ˇ
2441 «const_functionˇ»(ˇ),
2442 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2443 )ˇ
2444 ˇ);ˇ
2445 "});
2446
2447 cx.update_editor(|e, cx| e.newline_above(&NewlineAbove, cx));
2448 cx.assert_editor_state(indoc! {"
2449 ˇ
2450 const a: A = (
2451 ˇ
2452 (
2453 ˇ
2454 ˇ
2455 const_function(),
2456 ˇ
2457 ˇ
2458 ˇ
2459 ˇ
2460 something_else,
2461 ˇ
2462 )
2463 ˇ
2464 ˇ
2465 );
2466 "});
2467}
2468
2469#[gpui::test]
2470async fn test_newline_below(cx: &mut gpui::TestAppContext) {
2471 init_test(cx, |settings| {
2472 settings.defaults.tab_size = NonZeroU32::new(4)
2473 });
2474
2475 let language = Arc::new(
2476 Language::new(
2477 LanguageConfig::default(),
2478 Some(tree_sitter_rust::LANGUAGE.into()),
2479 )
2480 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2481 .unwrap(),
2482 );
2483
2484 let mut cx = EditorTestContext::new(cx).await;
2485 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2486 cx.set_state(indoc! {"
2487 const a: ˇA = (
2488 (ˇ
2489 «const_functionˇ»(ˇ),
2490 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2491 )ˇ
2492 ˇ);ˇ
2493 "});
2494
2495 cx.update_editor(|e, cx| e.newline_below(&NewlineBelow, cx));
2496 cx.assert_editor_state(indoc! {"
2497 const a: A = (
2498 ˇ
2499 (
2500 ˇ
2501 const_function(),
2502 ˇ
2503 ˇ
2504 something_else,
2505 ˇ
2506 ˇ
2507 ˇ
2508 ˇ
2509 )
2510 ˇ
2511 );
2512 ˇ
2513 ˇ
2514 "});
2515}
2516
2517#[gpui::test]
2518async fn test_newline_comments(cx: &mut gpui::TestAppContext) {
2519 init_test(cx, |settings| {
2520 settings.defaults.tab_size = NonZeroU32::new(4)
2521 });
2522
2523 let language = Arc::new(Language::new(
2524 LanguageConfig {
2525 line_comments: vec!["//".into()],
2526 ..LanguageConfig::default()
2527 },
2528 None,
2529 ));
2530 {
2531 let mut cx = EditorTestContext::new(cx).await;
2532 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2533 cx.set_state(indoc! {"
2534 // Fooˇ
2535 "});
2536
2537 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2538 cx.assert_editor_state(indoc! {"
2539 // Foo
2540 //ˇ
2541 "});
2542 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
2543 cx.set_state(indoc! {"
2544 ˇ// Foo
2545 "});
2546 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2547 cx.assert_editor_state(indoc! {"
2548
2549 ˇ// Foo
2550 "});
2551 }
2552 // Ensure that comment continuations can be disabled.
2553 update_test_language_settings(cx, |settings| {
2554 settings.defaults.extend_comment_on_newline = Some(false);
2555 });
2556 let mut cx = EditorTestContext::new(cx).await;
2557 cx.set_state(indoc! {"
2558 // Fooˇ
2559 "});
2560 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2561 cx.assert_editor_state(indoc! {"
2562 // Foo
2563 ˇ
2564 "});
2565}
2566
2567#[gpui::test]
2568fn test_insert_with_old_selections(cx: &mut TestAppContext) {
2569 init_test(cx, |_| {});
2570
2571 let editor = cx.add_window(|cx| {
2572 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
2573 let mut editor = build_editor(buffer.clone(), cx);
2574 editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
2575 editor
2576 });
2577
2578 _ = editor.update(cx, |editor, cx| {
2579 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2580 editor.buffer.update(cx, |buffer, cx| {
2581 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
2582 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
2583 });
2584 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
2585
2586 editor.insert("Z", cx);
2587 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
2588
2589 // The selections are moved after the inserted characters
2590 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
2591 });
2592}
2593
2594#[gpui::test]
2595async fn test_tab(cx: &mut gpui::TestAppContext) {
2596 init_test(cx, |settings| {
2597 settings.defaults.tab_size = NonZeroU32::new(3)
2598 });
2599
2600 let mut cx = EditorTestContext::new(cx).await;
2601 cx.set_state(indoc! {"
2602 ˇabˇc
2603 ˇ🏀ˇ🏀ˇefg
2604 dˇ
2605 "});
2606 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2607 cx.assert_editor_state(indoc! {"
2608 ˇab ˇc
2609 ˇ🏀 ˇ🏀 ˇefg
2610 d ˇ
2611 "});
2612
2613 cx.set_state(indoc! {"
2614 a
2615 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2616 "});
2617 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2618 cx.assert_editor_state(indoc! {"
2619 a
2620 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2621 "});
2622}
2623
2624#[gpui::test]
2625async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) {
2626 init_test(cx, |_| {});
2627
2628 let mut cx = EditorTestContext::new(cx).await;
2629 let language = Arc::new(
2630 Language::new(
2631 LanguageConfig::default(),
2632 Some(tree_sitter_rust::LANGUAGE.into()),
2633 )
2634 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2635 .unwrap(),
2636 );
2637 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2638
2639 // cursors that are already at the suggested indent level insert
2640 // a soft tab. cursors that are to the left of the suggested indent
2641 // auto-indent their line.
2642 cx.set_state(indoc! {"
2643 ˇ
2644 const a: B = (
2645 c(
2646 d(
2647 ˇ
2648 )
2649 ˇ
2650 ˇ )
2651 );
2652 "});
2653 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2654 cx.assert_editor_state(indoc! {"
2655 ˇ
2656 const a: B = (
2657 c(
2658 d(
2659 ˇ
2660 )
2661 ˇ
2662 ˇ)
2663 );
2664 "});
2665
2666 // handle auto-indent when there are multiple cursors on the same line
2667 cx.set_state(indoc! {"
2668 const a: B = (
2669 c(
2670 ˇ ˇ
2671 ˇ )
2672 );
2673 "});
2674 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2675 cx.assert_editor_state(indoc! {"
2676 const a: B = (
2677 c(
2678 ˇ
2679 ˇ)
2680 );
2681 "});
2682}
2683
2684#[gpui::test]
2685async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
2686 init_test(cx, |settings| {
2687 settings.defaults.tab_size = NonZeroU32::new(4)
2688 });
2689
2690 let language = Arc::new(
2691 Language::new(
2692 LanguageConfig::default(),
2693 Some(tree_sitter_rust::LANGUAGE.into()),
2694 )
2695 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2696 .unwrap(),
2697 );
2698
2699 let mut cx = EditorTestContext::new(cx).await;
2700 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2701 cx.set_state(indoc! {"
2702 fn a() {
2703 if b {
2704 \t ˇc
2705 }
2706 }
2707 "});
2708
2709 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2710 cx.assert_editor_state(indoc! {"
2711 fn a() {
2712 if b {
2713 ˇc
2714 }
2715 }
2716 "});
2717}
2718
2719#[gpui::test]
2720async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
2721 init_test(cx, |settings| {
2722 settings.defaults.tab_size = NonZeroU32::new(4);
2723 });
2724
2725 let mut cx = EditorTestContext::new(cx).await;
2726
2727 cx.set_state(indoc! {"
2728 «oneˇ» «twoˇ»
2729 three
2730 four
2731 "});
2732 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2733 cx.assert_editor_state(indoc! {"
2734 «oneˇ» «twoˇ»
2735 three
2736 four
2737 "});
2738
2739 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2740 cx.assert_editor_state(indoc! {"
2741 «oneˇ» «twoˇ»
2742 three
2743 four
2744 "});
2745
2746 // select across line ending
2747 cx.set_state(indoc! {"
2748 one two
2749 t«hree
2750 ˇ» four
2751 "});
2752 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2753 cx.assert_editor_state(indoc! {"
2754 one two
2755 t«hree
2756 ˇ» four
2757 "});
2758
2759 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2760 cx.assert_editor_state(indoc! {"
2761 one two
2762 t«hree
2763 ˇ» four
2764 "});
2765
2766 // Ensure that indenting/outdenting works when the cursor is at column 0.
2767 cx.set_state(indoc! {"
2768 one two
2769 ˇthree
2770 four
2771 "});
2772 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2773 cx.assert_editor_state(indoc! {"
2774 one two
2775 ˇthree
2776 four
2777 "});
2778
2779 cx.set_state(indoc! {"
2780 one two
2781 ˇ three
2782 four
2783 "});
2784 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2785 cx.assert_editor_state(indoc! {"
2786 one two
2787 ˇthree
2788 four
2789 "});
2790}
2791
2792#[gpui::test]
2793async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
2794 init_test(cx, |settings| {
2795 settings.defaults.hard_tabs = Some(true);
2796 });
2797
2798 let mut cx = EditorTestContext::new(cx).await;
2799
2800 // select two ranges on one line
2801 cx.set_state(indoc! {"
2802 «oneˇ» «twoˇ»
2803 three
2804 four
2805 "});
2806 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2807 cx.assert_editor_state(indoc! {"
2808 \t«oneˇ» «twoˇ»
2809 three
2810 four
2811 "});
2812 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2813 cx.assert_editor_state(indoc! {"
2814 \t\t«oneˇ» «twoˇ»
2815 three
2816 four
2817 "});
2818 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2819 cx.assert_editor_state(indoc! {"
2820 \t«oneˇ» «twoˇ»
2821 three
2822 four
2823 "});
2824 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2825 cx.assert_editor_state(indoc! {"
2826 «oneˇ» «twoˇ»
2827 three
2828 four
2829 "});
2830
2831 // select across a line ending
2832 cx.set_state(indoc! {"
2833 one two
2834 t«hree
2835 ˇ»four
2836 "});
2837 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2838 cx.assert_editor_state(indoc! {"
2839 one two
2840 \tt«hree
2841 ˇ»four
2842 "});
2843 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2844 cx.assert_editor_state(indoc! {"
2845 one two
2846 \t\tt«hree
2847 ˇ»four
2848 "});
2849 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2850 cx.assert_editor_state(indoc! {"
2851 one two
2852 \tt«hree
2853 ˇ»four
2854 "});
2855 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2856 cx.assert_editor_state(indoc! {"
2857 one two
2858 t«hree
2859 ˇ»four
2860 "});
2861
2862 // Ensure that indenting/outdenting works when the cursor is at column 0.
2863 cx.set_state(indoc! {"
2864 one two
2865 ˇthree
2866 four
2867 "});
2868 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2869 cx.assert_editor_state(indoc! {"
2870 one two
2871 ˇthree
2872 four
2873 "});
2874 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2875 cx.assert_editor_state(indoc! {"
2876 one two
2877 \tˇthree
2878 four
2879 "});
2880 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2881 cx.assert_editor_state(indoc! {"
2882 one two
2883 ˇthree
2884 four
2885 "});
2886}
2887
2888#[gpui::test]
2889fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
2890 init_test(cx, |settings| {
2891 settings.languages.extend([
2892 (
2893 "TOML".into(),
2894 LanguageSettingsContent {
2895 tab_size: NonZeroU32::new(2),
2896 ..Default::default()
2897 },
2898 ),
2899 (
2900 "Rust".into(),
2901 LanguageSettingsContent {
2902 tab_size: NonZeroU32::new(4),
2903 ..Default::default()
2904 },
2905 ),
2906 ]);
2907 });
2908
2909 let toml_language = Arc::new(Language::new(
2910 LanguageConfig {
2911 name: "TOML".into(),
2912 ..Default::default()
2913 },
2914 None,
2915 ));
2916 let rust_language = Arc::new(Language::new(
2917 LanguageConfig {
2918 name: "Rust".into(),
2919 ..Default::default()
2920 },
2921 None,
2922 ));
2923
2924 let toml_buffer =
2925 cx.new_model(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
2926 let rust_buffer = cx.new_model(|cx| {
2927 Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx)
2928 });
2929 let multibuffer = cx.new_model(|cx| {
2930 let mut multibuffer = MultiBuffer::new(ReadWrite);
2931 multibuffer.push_excerpts(
2932 toml_buffer.clone(),
2933 [ExcerptRange {
2934 context: Point::new(0, 0)..Point::new(2, 0),
2935 primary: None,
2936 }],
2937 cx,
2938 );
2939 multibuffer.push_excerpts(
2940 rust_buffer.clone(),
2941 [ExcerptRange {
2942 context: Point::new(0, 0)..Point::new(1, 0),
2943 primary: None,
2944 }],
2945 cx,
2946 );
2947 multibuffer
2948 });
2949
2950 cx.add_window(|cx| {
2951 let mut editor = build_editor(multibuffer, cx);
2952
2953 assert_eq!(
2954 editor.text(cx),
2955 indoc! {"
2956 a = 1
2957 b = 2
2958
2959 const c: usize = 3;
2960 "}
2961 );
2962
2963 select_ranges(
2964 &mut editor,
2965 indoc! {"
2966 «aˇ» = 1
2967 b = 2
2968
2969 «const c:ˇ» usize = 3;
2970 "},
2971 cx,
2972 );
2973
2974 editor.tab(&Tab, cx);
2975 assert_text_with_selections(
2976 &mut editor,
2977 indoc! {"
2978 «aˇ» = 1
2979 b = 2
2980
2981 «const c:ˇ» usize = 3;
2982 "},
2983 cx,
2984 );
2985 editor.tab_prev(&TabPrev, cx);
2986 assert_text_with_selections(
2987 &mut editor,
2988 indoc! {"
2989 «aˇ» = 1
2990 b = 2
2991
2992 «const c:ˇ» usize = 3;
2993 "},
2994 cx,
2995 );
2996
2997 editor
2998 });
2999}
3000
3001#[gpui::test]
3002async fn test_backspace(cx: &mut gpui::TestAppContext) {
3003 init_test(cx, |_| {});
3004
3005 let mut cx = EditorTestContext::new(cx).await;
3006
3007 // Basic backspace
3008 cx.set_state(indoc! {"
3009 onˇe two three
3010 fou«rˇ» five six
3011 seven «ˇeight nine
3012 »ten
3013 "});
3014 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3015 cx.assert_editor_state(indoc! {"
3016 oˇe two three
3017 fouˇ five six
3018 seven ˇten
3019 "});
3020
3021 // Test backspace inside and around indents
3022 cx.set_state(indoc! {"
3023 zero
3024 ˇone
3025 ˇtwo
3026 ˇ ˇ ˇ three
3027 ˇ ˇ four
3028 "});
3029 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3030 cx.assert_editor_state(indoc! {"
3031 zero
3032 ˇone
3033 ˇtwo
3034 ˇ threeˇ four
3035 "});
3036
3037 // Test backspace with line_mode set to true
3038 cx.update_editor(|e, _| e.selections.line_mode = true);
3039 cx.set_state(indoc! {"
3040 The ˇquick ˇbrown
3041 fox jumps over
3042 the lazy dog
3043 ˇThe qu«ick bˇ»rown"});
3044 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3045 cx.assert_editor_state(indoc! {"
3046 ˇfox jumps over
3047 the lazy dogˇ"});
3048}
3049
3050#[gpui::test]
3051async fn test_delete(cx: &mut gpui::TestAppContext) {
3052 init_test(cx, |_| {});
3053
3054 let mut cx = EditorTestContext::new(cx).await;
3055 cx.set_state(indoc! {"
3056 onˇe two three
3057 fou«rˇ» five six
3058 seven «ˇeight nine
3059 »ten
3060 "});
3061 cx.update_editor(|e, cx| e.delete(&Delete, cx));
3062 cx.assert_editor_state(indoc! {"
3063 onˇ two three
3064 fouˇ five six
3065 seven ˇten
3066 "});
3067
3068 // Test backspace with line_mode set to true
3069 cx.update_editor(|e, _| e.selections.line_mode = true);
3070 cx.set_state(indoc! {"
3071 The ˇquick ˇbrown
3072 fox «ˇjum»ps over
3073 the lazy dog
3074 ˇThe qu«ick bˇ»rown"});
3075 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3076 cx.assert_editor_state("ˇthe lazy dogˇ");
3077}
3078
3079#[gpui::test]
3080fn test_delete_line(cx: &mut TestAppContext) {
3081 init_test(cx, |_| {});
3082
3083 let view = cx.add_window(|cx| {
3084 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3085 build_editor(buffer, cx)
3086 });
3087 _ = view.update(cx, |view, cx| {
3088 view.change_selections(None, cx, |s| {
3089 s.select_display_ranges([
3090 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3091 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3092 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3093 ])
3094 });
3095 view.delete_line(&DeleteLine, cx);
3096 assert_eq!(view.display_text(cx), "ghi");
3097 assert_eq!(
3098 view.selections.display_ranges(cx),
3099 vec![
3100 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
3101 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
3102 ]
3103 );
3104 });
3105
3106 let view = cx.add_window(|cx| {
3107 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3108 build_editor(buffer, cx)
3109 });
3110 _ = view.update(cx, |view, cx| {
3111 view.change_selections(None, cx, |s| {
3112 s.select_display_ranges([
3113 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
3114 ])
3115 });
3116 view.delete_line(&DeleteLine, cx);
3117 assert_eq!(view.display_text(cx), "ghi\n");
3118 assert_eq!(
3119 view.selections.display_ranges(cx),
3120 vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
3121 );
3122 });
3123}
3124
3125#[gpui::test]
3126fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
3127 init_test(cx, |_| {});
3128
3129 cx.add_window(|cx| {
3130 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3131 let mut editor = build_editor(buffer.clone(), cx);
3132 let buffer = buffer.read(cx).as_singleton().unwrap();
3133
3134 assert_eq!(
3135 editor.selections.ranges::<Point>(cx),
3136 &[Point::new(0, 0)..Point::new(0, 0)]
3137 );
3138
3139 // When on single line, replace newline at end by space
3140 editor.join_lines(&JoinLines, cx);
3141 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3142 assert_eq!(
3143 editor.selections.ranges::<Point>(cx),
3144 &[Point::new(0, 3)..Point::new(0, 3)]
3145 );
3146
3147 // When multiple lines are selected, remove newlines that are spanned by the selection
3148 editor.change_selections(None, cx, |s| {
3149 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
3150 });
3151 editor.join_lines(&JoinLines, cx);
3152 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
3153 assert_eq!(
3154 editor.selections.ranges::<Point>(cx),
3155 &[Point::new(0, 11)..Point::new(0, 11)]
3156 );
3157
3158 // Undo should be transactional
3159 editor.undo(&Undo, cx);
3160 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3161 assert_eq!(
3162 editor.selections.ranges::<Point>(cx),
3163 &[Point::new(0, 5)..Point::new(2, 2)]
3164 );
3165
3166 // When joining an empty line don't insert a space
3167 editor.change_selections(None, cx, |s| {
3168 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
3169 });
3170 editor.join_lines(&JoinLines, cx);
3171 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
3172 assert_eq!(
3173 editor.selections.ranges::<Point>(cx),
3174 [Point::new(2, 3)..Point::new(2, 3)]
3175 );
3176
3177 // We can remove trailing newlines
3178 editor.join_lines(&JoinLines, cx);
3179 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3180 assert_eq!(
3181 editor.selections.ranges::<Point>(cx),
3182 [Point::new(2, 3)..Point::new(2, 3)]
3183 );
3184
3185 // We don't blow up on the last line
3186 editor.join_lines(&JoinLines, cx);
3187 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3188 assert_eq!(
3189 editor.selections.ranges::<Point>(cx),
3190 [Point::new(2, 3)..Point::new(2, 3)]
3191 );
3192
3193 // reset to test indentation
3194 editor.buffer.update(cx, |buffer, cx| {
3195 buffer.edit(
3196 [
3197 (Point::new(1, 0)..Point::new(1, 2), " "),
3198 (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
3199 ],
3200 None,
3201 cx,
3202 )
3203 });
3204
3205 // We remove any leading spaces
3206 assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
3207 editor.change_selections(None, cx, |s| {
3208 s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
3209 });
3210 editor.join_lines(&JoinLines, cx);
3211 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
3212
3213 // We don't insert a space for a line containing only spaces
3214 editor.join_lines(&JoinLines, cx);
3215 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
3216
3217 // We ignore any leading tabs
3218 editor.join_lines(&JoinLines, cx);
3219 assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
3220
3221 editor
3222 });
3223}
3224
3225#[gpui::test]
3226fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
3227 init_test(cx, |_| {});
3228
3229 cx.add_window(|cx| {
3230 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3231 let mut editor = build_editor(buffer.clone(), cx);
3232 let buffer = buffer.read(cx).as_singleton().unwrap();
3233
3234 editor.change_selections(None, cx, |s| {
3235 s.select_ranges([
3236 Point::new(0, 2)..Point::new(1, 1),
3237 Point::new(1, 2)..Point::new(1, 2),
3238 Point::new(3, 1)..Point::new(3, 2),
3239 ])
3240 });
3241
3242 editor.join_lines(&JoinLines, cx);
3243 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
3244
3245 assert_eq!(
3246 editor.selections.ranges::<Point>(cx),
3247 [
3248 Point::new(0, 7)..Point::new(0, 7),
3249 Point::new(1, 3)..Point::new(1, 3)
3250 ]
3251 );
3252 editor
3253 });
3254}
3255
3256#[gpui::test]
3257async fn test_join_lines_with_git_diff_base(
3258 executor: BackgroundExecutor,
3259 cx: &mut gpui::TestAppContext,
3260) {
3261 init_test(cx, |_| {});
3262
3263 let mut cx = EditorTestContext::new(cx).await;
3264
3265 let diff_base = r#"
3266 Line 0
3267 Line 1
3268 Line 2
3269 Line 3
3270 "#
3271 .unindent();
3272
3273 cx.set_state(
3274 &r#"
3275 ˇLine 0
3276 Line 1
3277 Line 2
3278 Line 3
3279 "#
3280 .unindent(),
3281 );
3282
3283 cx.set_diff_base(Some(&diff_base));
3284 executor.run_until_parked();
3285
3286 // Join lines
3287 cx.update_editor(|editor, cx| {
3288 editor.join_lines(&JoinLines, cx);
3289 });
3290 executor.run_until_parked();
3291
3292 cx.assert_editor_state(
3293 &r#"
3294 Line 0ˇ Line 1
3295 Line 2
3296 Line 3
3297 "#
3298 .unindent(),
3299 );
3300 // Join again
3301 cx.update_editor(|editor, cx| {
3302 editor.join_lines(&JoinLines, cx);
3303 });
3304 executor.run_until_parked();
3305
3306 cx.assert_editor_state(
3307 &r#"
3308 Line 0 Line 1ˇ Line 2
3309 Line 3
3310 "#
3311 .unindent(),
3312 );
3313}
3314
3315#[gpui::test]
3316async fn test_custom_newlines_cause_no_false_positive_diffs(
3317 executor: BackgroundExecutor,
3318 cx: &mut gpui::TestAppContext,
3319) {
3320 init_test(cx, |_| {});
3321 let mut cx = EditorTestContext::new(cx).await;
3322 cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
3323 cx.set_diff_base(Some("Line 0\r\nLine 1\r\nLine 2\r\nLine 3"));
3324 executor.run_until_parked();
3325
3326 cx.update_editor(|editor, cx| {
3327 assert_eq!(
3328 editor
3329 .buffer()
3330 .read(cx)
3331 .snapshot(cx)
3332 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
3333 .collect::<Vec<_>>(),
3334 Vec::new(),
3335 "Should not have any diffs for files with custom newlines"
3336 );
3337 });
3338}
3339
3340#[gpui::test]
3341async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
3342 init_test(cx, |_| {});
3343
3344 let mut cx = EditorTestContext::new(cx).await;
3345
3346 // Test sort_lines_case_insensitive()
3347 cx.set_state(indoc! {"
3348 «z
3349 y
3350 x
3351 Z
3352 Y
3353 Xˇ»
3354 "});
3355 cx.update_editor(|e, cx| e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, cx));
3356 cx.assert_editor_state(indoc! {"
3357 «x
3358 X
3359 y
3360 Y
3361 z
3362 Zˇ»
3363 "});
3364
3365 // Test reverse_lines()
3366 cx.set_state(indoc! {"
3367 «5
3368 4
3369 3
3370 2
3371 1ˇ»
3372 "});
3373 cx.update_editor(|e, cx| e.reverse_lines(&ReverseLines, cx));
3374 cx.assert_editor_state(indoc! {"
3375 «1
3376 2
3377 3
3378 4
3379 5ˇ»
3380 "});
3381
3382 // Skip testing shuffle_line()
3383
3384 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
3385 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
3386
3387 // Don't manipulate when cursor is on single line, but expand the selection
3388 cx.set_state(indoc! {"
3389 ddˇdd
3390 ccc
3391 bb
3392 a
3393 "});
3394 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3395 cx.assert_editor_state(indoc! {"
3396 «ddddˇ»
3397 ccc
3398 bb
3399 a
3400 "});
3401
3402 // Basic manipulate case
3403 // Start selection moves to column 0
3404 // End of selection shrinks to fit shorter line
3405 cx.set_state(indoc! {"
3406 dd«d
3407 ccc
3408 bb
3409 aaaaaˇ»
3410 "});
3411 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3412 cx.assert_editor_state(indoc! {"
3413 «aaaaa
3414 bb
3415 ccc
3416 dddˇ»
3417 "});
3418
3419 // Manipulate case with newlines
3420 cx.set_state(indoc! {"
3421 dd«d
3422 ccc
3423
3424 bb
3425 aaaaa
3426
3427 ˇ»
3428 "});
3429 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3430 cx.assert_editor_state(indoc! {"
3431 «
3432
3433 aaaaa
3434 bb
3435 ccc
3436 dddˇ»
3437
3438 "});
3439
3440 // Adding new line
3441 cx.set_state(indoc! {"
3442 aa«a
3443 bbˇ»b
3444 "});
3445 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added_line")));
3446 cx.assert_editor_state(indoc! {"
3447 «aaa
3448 bbb
3449 added_lineˇ»
3450 "});
3451
3452 // Removing line
3453 cx.set_state(indoc! {"
3454 aa«a
3455 bbbˇ»
3456 "});
3457 cx.update_editor(|e, cx| {
3458 e.manipulate_lines(cx, |lines| {
3459 lines.pop();
3460 })
3461 });
3462 cx.assert_editor_state(indoc! {"
3463 «aaaˇ»
3464 "});
3465
3466 // Removing all lines
3467 cx.set_state(indoc! {"
3468 aa«a
3469 bbbˇ»
3470 "});
3471 cx.update_editor(|e, cx| {
3472 e.manipulate_lines(cx, |lines| {
3473 lines.drain(..);
3474 })
3475 });
3476 cx.assert_editor_state(indoc! {"
3477 ˇ
3478 "});
3479}
3480
3481#[gpui::test]
3482async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
3483 init_test(cx, |_| {});
3484
3485 let mut cx = EditorTestContext::new(cx).await;
3486
3487 // Consider continuous selection as single selection
3488 cx.set_state(indoc! {"
3489 Aaa«aa
3490 cˇ»c«c
3491 bb
3492 aaaˇ»aa
3493 "});
3494 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3495 cx.assert_editor_state(indoc! {"
3496 «Aaaaa
3497 ccc
3498 bb
3499 aaaaaˇ»
3500 "});
3501
3502 cx.set_state(indoc! {"
3503 Aaa«aa
3504 cˇ»c«c
3505 bb
3506 aaaˇ»aa
3507 "});
3508 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3509 cx.assert_editor_state(indoc! {"
3510 «Aaaaa
3511 ccc
3512 bbˇ»
3513 "});
3514
3515 // Consider non continuous selection as distinct dedup operations
3516 cx.set_state(indoc! {"
3517 «aaaaa
3518 bb
3519 aaaaa
3520 aaaaaˇ»
3521
3522 aaa«aaˇ»
3523 "});
3524 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3525 cx.assert_editor_state(indoc! {"
3526 «aaaaa
3527 bbˇ»
3528
3529 «aaaaaˇ»
3530 "});
3531}
3532
3533#[gpui::test]
3534async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
3535 init_test(cx, |_| {});
3536
3537 let mut cx = EditorTestContext::new(cx).await;
3538
3539 cx.set_state(indoc! {"
3540 «Aaa
3541 aAa
3542 Aaaˇ»
3543 "});
3544 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3545 cx.assert_editor_state(indoc! {"
3546 «Aaa
3547 aAaˇ»
3548 "});
3549
3550 cx.set_state(indoc! {"
3551 «Aaa
3552 aAa
3553 aaAˇ»
3554 "});
3555 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3556 cx.assert_editor_state(indoc! {"
3557 «Aaaˇ»
3558 "});
3559}
3560
3561#[gpui::test]
3562async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
3563 init_test(cx, |_| {});
3564
3565 let mut cx = EditorTestContext::new(cx).await;
3566
3567 // Manipulate with multiple selections on a single line
3568 cx.set_state(indoc! {"
3569 dd«dd
3570 cˇ»c«c
3571 bb
3572 aaaˇ»aa
3573 "});
3574 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3575 cx.assert_editor_state(indoc! {"
3576 «aaaaa
3577 bb
3578 ccc
3579 ddddˇ»
3580 "});
3581
3582 // Manipulate with multiple disjoin selections
3583 cx.set_state(indoc! {"
3584 5«
3585 4
3586 3
3587 2
3588 1ˇ»
3589
3590 dd«dd
3591 ccc
3592 bb
3593 aaaˇ»aa
3594 "});
3595 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3596 cx.assert_editor_state(indoc! {"
3597 «1
3598 2
3599 3
3600 4
3601 5ˇ»
3602
3603 «aaaaa
3604 bb
3605 ccc
3606 ddddˇ»
3607 "});
3608
3609 // Adding lines on each selection
3610 cx.set_state(indoc! {"
3611 2«
3612 1ˇ»
3613
3614 bb«bb
3615 aaaˇ»aa
3616 "});
3617 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added line")));
3618 cx.assert_editor_state(indoc! {"
3619 «2
3620 1
3621 added lineˇ»
3622
3623 «bbbb
3624 aaaaa
3625 added lineˇ»
3626 "});
3627
3628 // Removing lines on each selection
3629 cx.set_state(indoc! {"
3630 2«
3631 1ˇ»
3632
3633 bb«bb
3634 aaaˇ»aa
3635 "});
3636 cx.update_editor(|e, cx| {
3637 e.manipulate_lines(cx, |lines| {
3638 lines.pop();
3639 })
3640 });
3641 cx.assert_editor_state(indoc! {"
3642 «2ˇ»
3643
3644 «bbbbˇ»
3645 "});
3646}
3647
3648#[gpui::test]
3649async fn test_manipulate_text(cx: &mut TestAppContext) {
3650 init_test(cx, |_| {});
3651
3652 let mut cx = EditorTestContext::new(cx).await;
3653
3654 // Test convert_to_upper_case()
3655 cx.set_state(indoc! {"
3656 «hello worldˇ»
3657 "});
3658 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3659 cx.assert_editor_state(indoc! {"
3660 «HELLO WORLDˇ»
3661 "});
3662
3663 // Test convert_to_lower_case()
3664 cx.set_state(indoc! {"
3665 «HELLO WORLDˇ»
3666 "});
3667 cx.update_editor(|e, cx| e.convert_to_lower_case(&ConvertToLowerCase, cx));
3668 cx.assert_editor_state(indoc! {"
3669 «hello worldˇ»
3670 "});
3671
3672 // Test multiple line, single selection case
3673 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3674 cx.set_state(indoc! {"
3675 «The quick brown
3676 fox jumps over
3677 the lazy dogˇ»
3678 "});
3679 cx.update_editor(|e, cx| e.convert_to_title_case(&ConvertToTitleCase, cx));
3680 cx.assert_editor_state(indoc! {"
3681 «The Quick Brown
3682 Fox Jumps Over
3683 The Lazy Dogˇ»
3684 "});
3685
3686 // Test multiple line, single selection case
3687 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3688 cx.set_state(indoc! {"
3689 «The quick brown
3690 fox jumps over
3691 the lazy dogˇ»
3692 "});
3693 cx.update_editor(|e, cx| e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, cx));
3694 cx.assert_editor_state(indoc! {"
3695 «TheQuickBrown
3696 FoxJumpsOver
3697 TheLazyDogˇ»
3698 "});
3699
3700 // From here on out, test more complex cases of manipulate_text()
3701
3702 // Test no selection case - should affect words cursors are in
3703 // Cursor at beginning, middle, and end of word
3704 cx.set_state(indoc! {"
3705 ˇhello big beauˇtiful worldˇ
3706 "});
3707 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3708 cx.assert_editor_state(indoc! {"
3709 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3710 "});
3711
3712 // Test multiple selections on a single line and across multiple lines
3713 cx.set_state(indoc! {"
3714 «Theˇ» quick «brown
3715 foxˇ» jumps «overˇ»
3716 the «lazyˇ» dog
3717 "});
3718 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3719 cx.assert_editor_state(indoc! {"
3720 «THEˇ» quick «BROWN
3721 FOXˇ» jumps «OVERˇ»
3722 the «LAZYˇ» dog
3723 "});
3724
3725 // Test case where text length grows
3726 cx.set_state(indoc! {"
3727 «tschüߡ»
3728 "});
3729 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3730 cx.assert_editor_state(indoc! {"
3731 «TSCHÜSSˇ»
3732 "});
3733
3734 // Test to make sure we don't crash when text shrinks
3735 cx.set_state(indoc! {"
3736 aaa_bbbˇ
3737 "});
3738 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3739 cx.assert_editor_state(indoc! {"
3740 «aaaBbbˇ»
3741 "});
3742
3743 // Test to make sure we all aware of the fact that each word can grow and shrink
3744 // Final selections should be aware of this fact
3745 cx.set_state(indoc! {"
3746 aaa_bˇbb bbˇb_ccc ˇccc_ddd
3747 "});
3748 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3749 cx.assert_editor_state(indoc! {"
3750 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
3751 "});
3752
3753 cx.set_state(indoc! {"
3754 «hElLo, WoRld!ˇ»
3755 "});
3756 cx.update_editor(|e, cx| e.convert_to_opposite_case(&ConvertToOppositeCase, cx));
3757 cx.assert_editor_state(indoc! {"
3758 «HeLlO, wOrLD!ˇ»
3759 "});
3760}
3761
3762#[gpui::test]
3763fn test_duplicate_line(cx: &mut TestAppContext) {
3764 init_test(cx, |_| {});
3765
3766 let view = cx.add_window(|cx| {
3767 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3768 build_editor(buffer, cx)
3769 });
3770 _ = view.update(cx, |view, cx| {
3771 view.change_selections(None, cx, |s| {
3772 s.select_display_ranges([
3773 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3774 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3775 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3776 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3777 ])
3778 });
3779 view.duplicate_line_down(&DuplicateLineDown, cx);
3780 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3781 assert_eq!(
3782 view.selections.display_ranges(cx),
3783 vec![
3784 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3785 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
3786 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3787 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3788 ]
3789 );
3790 });
3791
3792 let view = cx.add_window(|cx| {
3793 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3794 build_editor(buffer, cx)
3795 });
3796 _ = view.update(cx, |view, cx| {
3797 view.change_selections(None, cx, |s| {
3798 s.select_display_ranges([
3799 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3800 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3801 ])
3802 });
3803 view.duplicate_line_down(&DuplicateLineDown, cx);
3804 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3805 assert_eq!(
3806 view.selections.display_ranges(cx),
3807 vec![
3808 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
3809 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
3810 ]
3811 );
3812 });
3813
3814 // With `move_upwards` the selections stay in place, except for
3815 // the lines inserted above them
3816 let view = cx.add_window(|cx| {
3817 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3818 build_editor(buffer, cx)
3819 });
3820 _ = view.update(cx, |view, cx| {
3821 view.change_selections(None, cx, |s| {
3822 s.select_display_ranges([
3823 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3824 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3825 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3826 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3827 ])
3828 });
3829 view.duplicate_line_up(&DuplicateLineUp, cx);
3830 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3831 assert_eq!(
3832 view.selections.display_ranges(cx),
3833 vec![
3834 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3835 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3836 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
3837 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3838 ]
3839 );
3840 });
3841
3842 let view = cx.add_window(|cx| {
3843 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3844 build_editor(buffer, cx)
3845 });
3846 _ = view.update(cx, |view, cx| {
3847 view.change_selections(None, cx, |s| {
3848 s.select_display_ranges([
3849 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3850 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3851 ])
3852 });
3853 view.duplicate_line_up(&DuplicateLineUp, cx);
3854 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3855 assert_eq!(
3856 view.selections.display_ranges(cx),
3857 vec![
3858 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3859 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3860 ]
3861 );
3862 });
3863}
3864
3865#[gpui::test]
3866fn test_move_line_up_down(cx: &mut TestAppContext) {
3867 init_test(cx, |_| {});
3868
3869 let view = cx.add_window(|cx| {
3870 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3871 build_editor(buffer, cx)
3872 });
3873 _ = view.update(cx, |view, cx| {
3874 view.fold_ranges(
3875 vec![
3876 (Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
3877 (Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
3878 (Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
3879 ],
3880 true,
3881 cx,
3882 );
3883 view.change_selections(None, cx, |s| {
3884 s.select_display_ranges([
3885 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3886 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3887 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3888 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
3889 ])
3890 });
3891 assert_eq!(
3892 view.display_text(cx),
3893 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
3894 );
3895
3896 view.move_line_up(&MoveLineUp, cx);
3897 assert_eq!(
3898 view.display_text(cx),
3899 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
3900 );
3901 assert_eq!(
3902 view.selections.display_ranges(cx),
3903 vec![
3904 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3905 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3906 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3907 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3908 ]
3909 );
3910 });
3911
3912 _ = view.update(cx, |view, cx| {
3913 view.move_line_down(&MoveLineDown, cx);
3914 assert_eq!(
3915 view.display_text(cx),
3916 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
3917 );
3918 assert_eq!(
3919 view.selections.display_ranges(cx),
3920 vec![
3921 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3922 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3923 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3924 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3925 ]
3926 );
3927 });
3928
3929 _ = view.update(cx, |view, cx| {
3930 view.move_line_down(&MoveLineDown, cx);
3931 assert_eq!(
3932 view.display_text(cx),
3933 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
3934 );
3935 assert_eq!(
3936 view.selections.display_ranges(cx),
3937 vec![
3938 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3939 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3940 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3941 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3942 ]
3943 );
3944 });
3945
3946 _ = view.update(cx, |view, cx| {
3947 view.move_line_up(&MoveLineUp, cx);
3948 assert_eq!(
3949 view.display_text(cx),
3950 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
3951 );
3952 assert_eq!(
3953 view.selections.display_ranges(cx),
3954 vec![
3955 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3956 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3957 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3958 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3959 ]
3960 );
3961 });
3962}
3963
3964#[gpui::test]
3965fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
3966 init_test(cx, |_| {});
3967
3968 let editor = cx.add_window(|cx| {
3969 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3970 build_editor(buffer, cx)
3971 });
3972 _ = editor.update(cx, |editor, cx| {
3973 let snapshot = editor.buffer.read(cx).snapshot(cx);
3974 editor.insert_blocks(
3975 [BlockProperties {
3976 style: BlockStyle::Fixed,
3977 placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
3978 height: 1,
3979 render: Box::new(|_| div().into_any()),
3980 priority: 0,
3981 }],
3982 Some(Autoscroll::fit()),
3983 cx,
3984 );
3985 editor.change_selections(None, cx, |s| {
3986 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
3987 });
3988 editor.move_line_down(&MoveLineDown, cx);
3989 });
3990}
3991
3992#[gpui::test]
3993async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
3994 init_test(cx, |_| {});
3995
3996 let mut cx = EditorTestContext::new(cx).await;
3997 cx.set_state(
3998 &"
3999 ˇzero
4000 one
4001 two
4002 three
4003 four
4004 five
4005 "
4006 .unindent(),
4007 );
4008
4009 // Create a four-line block that replaces three lines of text.
4010 cx.update_editor(|editor, cx| {
4011 let snapshot = editor.snapshot(cx);
4012 let snapshot = &snapshot.buffer_snapshot;
4013 let placement = BlockPlacement::Replace(
4014 snapshot.anchor_after(Point::new(1, 0))..snapshot.anchor_after(Point::new(3, 0)),
4015 );
4016 editor.insert_blocks(
4017 [BlockProperties {
4018 placement,
4019 height: 4,
4020 style: BlockStyle::Sticky,
4021 render: Box::new(|_| gpui::div().into_any_element()),
4022 priority: 0,
4023 }],
4024 None,
4025 cx,
4026 );
4027 });
4028
4029 // Move down so that the cursor touches the block.
4030 cx.update_editor(|editor, cx| {
4031 editor.move_down(&Default::default(), cx);
4032 });
4033 cx.assert_editor_state(
4034 &"
4035 zero
4036 «one
4037 two
4038 threeˇ»
4039 four
4040 five
4041 "
4042 .unindent(),
4043 );
4044
4045 // Move down past the block.
4046 cx.update_editor(|editor, cx| {
4047 editor.move_down(&Default::default(), cx);
4048 });
4049 cx.assert_editor_state(
4050 &"
4051 zero
4052 one
4053 two
4054 three
4055 ˇfour
4056 five
4057 "
4058 .unindent(),
4059 );
4060}
4061
4062#[gpui::test]
4063fn test_transpose(cx: &mut TestAppContext) {
4064 init_test(cx, |_| {});
4065
4066 _ = cx.add_window(|cx| {
4067 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
4068 editor.set_style(EditorStyle::default(), cx);
4069 editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
4070 editor.transpose(&Default::default(), cx);
4071 assert_eq!(editor.text(cx), "bac");
4072 assert_eq!(editor.selections.ranges(cx), [2..2]);
4073
4074 editor.transpose(&Default::default(), cx);
4075 assert_eq!(editor.text(cx), "bca");
4076 assert_eq!(editor.selections.ranges(cx), [3..3]);
4077
4078 editor.transpose(&Default::default(), cx);
4079 assert_eq!(editor.text(cx), "bac");
4080 assert_eq!(editor.selections.ranges(cx), [3..3]);
4081
4082 editor
4083 });
4084
4085 _ = cx.add_window(|cx| {
4086 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
4087 editor.set_style(EditorStyle::default(), cx);
4088 editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
4089 editor.transpose(&Default::default(), cx);
4090 assert_eq!(editor.text(cx), "acb\nde");
4091 assert_eq!(editor.selections.ranges(cx), [3..3]);
4092
4093 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
4094 editor.transpose(&Default::default(), cx);
4095 assert_eq!(editor.text(cx), "acbd\ne");
4096 assert_eq!(editor.selections.ranges(cx), [5..5]);
4097
4098 editor.transpose(&Default::default(), cx);
4099 assert_eq!(editor.text(cx), "acbde\n");
4100 assert_eq!(editor.selections.ranges(cx), [6..6]);
4101
4102 editor.transpose(&Default::default(), cx);
4103 assert_eq!(editor.text(cx), "acbd\ne");
4104 assert_eq!(editor.selections.ranges(cx), [6..6]);
4105
4106 editor
4107 });
4108
4109 _ = cx.add_window(|cx| {
4110 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
4111 editor.set_style(EditorStyle::default(), cx);
4112 editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
4113 editor.transpose(&Default::default(), cx);
4114 assert_eq!(editor.text(cx), "bacd\ne");
4115 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
4116
4117 editor.transpose(&Default::default(), cx);
4118 assert_eq!(editor.text(cx), "bcade\n");
4119 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
4120
4121 editor.transpose(&Default::default(), cx);
4122 assert_eq!(editor.text(cx), "bcda\ne");
4123 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4124
4125 editor.transpose(&Default::default(), cx);
4126 assert_eq!(editor.text(cx), "bcade\n");
4127 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4128
4129 editor.transpose(&Default::default(), cx);
4130 assert_eq!(editor.text(cx), "bcaed\n");
4131 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
4132
4133 editor
4134 });
4135
4136 _ = cx.add_window(|cx| {
4137 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
4138 editor.set_style(EditorStyle::default(), cx);
4139 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
4140 editor.transpose(&Default::default(), cx);
4141 assert_eq!(editor.text(cx), "🏀🍐✋");
4142 assert_eq!(editor.selections.ranges(cx), [8..8]);
4143
4144 editor.transpose(&Default::default(), cx);
4145 assert_eq!(editor.text(cx), "🏀✋🍐");
4146 assert_eq!(editor.selections.ranges(cx), [11..11]);
4147
4148 editor.transpose(&Default::default(), cx);
4149 assert_eq!(editor.text(cx), "🏀🍐✋");
4150 assert_eq!(editor.selections.ranges(cx), [11..11]);
4151
4152 editor
4153 });
4154}
4155
4156#[gpui::test]
4157async fn test_rewrap(cx: &mut TestAppContext) {
4158 init_test(cx, |_| {});
4159
4160 let mut cx = EditorTestContext::new(cx).await;
4161
4162 {
4163 let language = Arc::new(Language::new(
4164 LanguageConfig {
4165 line_comments: vec!["// ".into()],
4166 ..LanguageConfig::default()
4167 },
4168 None,
4169 ));
4170 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4171
4172 let unwrapped_text = indoc! {"
4173 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4174 "};
4175
4176 let wrapped_text = indoc! {"
4177 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4178 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4179 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4180 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4181 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4182 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4183 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4184 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4185 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4186 // porttitor id. Aliquam id accumsan eros.ˇ
4187 "};
4188
4189 cx.set_state(unwrapped_text);
4190 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4191 cx.assert_editor_state(wrapped_text);
4192 }
4193
4194 // Test that rewrapping works inside of a selection
4195 {
4196 let language = Arc::new(Language::new(
4197 LanguageConfig {
4198 line_comments: vec!["// ".into()],
4199 ..LanguageConfig::default()
4200 },
4201 None,
4202 ));
4203 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4204
4205 let unwrapped_text = indoc! {"
4206 «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.ˇ»
4207 "};
4208
4209 let wrapped_text = indoc! {"
4210 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4211 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4212 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4213 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4214 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4215 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4216 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4217 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4218 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4219 // porttitor id. Aliquam id accumsan eros.ˇ
4220 "};
4221
4222 cx.set_state(unwrapped_text);
4223 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4224 cx.assert_editor_state(wrapped_text);
4225 }
4226
4227 // Test that cursors that expand to the same region are collapsed.
4228 {
4229 let language = Arc::new(Language::new(
4230 LanguageConfig {
4231 line_comments: vec!["// ".into()],
4232 ..LanguageConfig::default()
4233 },
4234 None,
4235 ));
4236 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4237
4238 let unwrapped_text = indoc! {"
4239 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4240 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4241 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4242 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4243 "};
4244
4245 let wrapped_text = indoc! {"
4246 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4247 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4248 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4249 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4250 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4251 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4252 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4253 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4254 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4255 // porttitor id. Aliquam id accumsan eros.ˇ
4256 "};
4257
4258 cx.set_state(unwrapped_text);
4259 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4260 cx.assert_editor_state(wrapped_text);
4261 }
4262
4263 // Test that non-contiguous selections are treated separately.
4264 {
4265 let language = Arc::new(Language::new(
4266 LanguageConfig {
4267 line_comments: vec!["// ".into()],
4268 ..LanguageConfig::default()
4269 },
4270 None,
4271 ));
4272 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4273
4274 let unwrapped_text = indoc! {"
4275 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4276 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4277 //
4278 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4279 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4280 "};
4281
4282 let wrapped_text = indoc! {"
4283 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4284 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4285 // auctor, eu lacinia sapien scelerisque.ˇ
4286 //
4287 // Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
4288 // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4289 // blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
4290 // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
4291 // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
4292 // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
4293 // vulputate turpis porttitor id. Aliquam id accumsan eros.ˇ
4294 "};
4295
4296 cx.set_state(unwrapped_text);
4297 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4298 cx.assert_editor_state(wrapped_text);
4299 }
4300
4301 // Test that different comment prefixes are supported.
4302 {
4303 let language = Arc::new(Language::new(
4304 LanguageConfig {
4305 line_comments: vec!["# ".into()],
4306 ..LanguageConfig::default()
4307 },
4308 None,
4309 ));
4310 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4311
4312 let unwrapped_text = indoc! {"
4313 # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4314 "};
4315
4316 let wrapped_text = indoc! {"
4317 # Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4318 # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4319 # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4320 # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4321 # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
4322 # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
4323 # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
4324 # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
4325 # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
4326 # accumsan eros.ˇ
4327 "};
4328
4329 cx.set_state(unwrapped_text);
4330 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4331 cx.assert_editor_state(wrapped_text);
4332 }
4333
4334 // Test that rewrapping is ignored outside of comments in most languages.
4335 {
4336 let language = Arc::new(Language::new(
4337 LanguageConfig {
4338 line_comments: vec!["// ".into(), "/// ".into()],
4339 ..LanguageConfig::default()
4340 },
4341 Some(tree_sitter_rust::LANGUAGE.into()),
4342 ));
4343 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4344
4345 let unwrapped_text = indoc! {"
4346 /// Adds two numbers.
4347 /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4348 fn add(a: u32, b: u32) -> u32 {
4349 a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + bˇ
4350 }
4351 "};
4352
4353 let wrapped_text = indoc! {"
4354 /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
4355 /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4356 fn add(a: u32, b: u32) -> u32 {
4357 a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + bˇ
4358 }
4359 "};
4360
4361 cx.set_state(unwrapped_text);
4362 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4363 cx.assert_editor_state(wrapped_text);
4364 }
4365
4366 // Test that rewrapping works in Markdown and Plain Text languages.
4367 {
4368 let markdown_language = Arc::new(Language::new(
4369 LanguageConfig {
4370 name: "Markdown".into(),
4371 ..LanguageConfig::default()
4372 },
4373 None,
4374 ));
4375 cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
4376
4377 let unwrapped_text = indoc! {"
4378 # Hello
4379
4380 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi.
4381 "};
4382
4383 let wrapped_text = indoc! {"
4384 # Hello
4385
4386 Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4387 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4388 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4389 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4390 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4391 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4392 Integer sit amet scelerisque nisi.ˇ
4393 "};
4394
4395 cx.set_state(unwrapped_text);
4396 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4397 cx.assert_editor_state(wrapped_text);
4398
4399 let plaintext_language = Arc::new(Language::new(
4400 LanguageConfig {
4401 name: "Plain Text".into(),
4402 ..LanguageConfig::default()
4403 },
4404 None,
4405 ));
4406 cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
4407
4408 let unwrapped_text = indoc! {"
4409 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi.
4410 "};
4411
4412 let wrapped_text = indoc! {"
4413 Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4414 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4415 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4416 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4417 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4418 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4419 Integer sit amet scelerisque nisi.ˇ
4420 "};
4421
4422 cx.set_state(unwrapped_text);
4423 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4424 cx.assert_editor_state(wrapped_text);
4425 }
4426
4427 // Test rewrapping unaligned comments in a selection.
4428 {
4429 let language = Arc::new(Language::new(
4430 LanguageConfig {
4431 line_comments: vec!["// ".into(), "/// ".into()],
4432 ..LanguageConfig::default()
4433 },
4434 Some(tree_sitter_rust::LANGUAGE.into()),
4435 ));
4436 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4437
4438 let unwrapped_text = indoc! {"
4439 fn foo() {
4440 if true {
4441 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4442 // Praesent semper egestas tellus id dignissim.ˇ»
4443 do_something();
4444 } else {
4445 //
4446 }
4447 }
4448 "};
4449
4450 let wrapped_text = indoc! {"
4451 fn foo() {
4452 if true {
4453 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4454 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4455 // egestas tellus id dignissim.ˇ
4456 do_something();
4457 } else {
4458 //
4459 }
4460 }
4461 "};
4462
4463 cx.set_state(unwrapped_text);
4464 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4465 cx.assert_editor_state(wrapped_text);
4466
4467 let unwrapped_text = indoc! {"
4468 fn foo() {
4469 if true {
4470 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4471 // Praesent semper egestas tellus id dignissim.»
4472 do_something();
4473 } else {
4474 //
4475 }
4476
4477 }
4478 "};
4479
4480 let wrapped_text = indoc! {"
4481 fn foo() {
4482 if true {
4483 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4484 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4485 // egestas tellus id dignissim.ˇ
4486 do_something();
4487 } else {
4488 //
4489 }
4490
4491 }
4492 "};
4493
4494 cx.set_state(unwrapped_text);
4495 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4496 cx.assert_editor_state(wrapped_text);
4497 }
4498}
4499
4500#[gpui::test]
4501async fn test_clipboard(cx: &mut gpui::TestAppContext) {
4502 init_test(cx, |_| {});
4503
4504 let mut cx = EditorTestContext::new(cx).await;
4505
4506 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
4507 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4508 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
4509
4510 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
4511 cx.set_state("two ˇfour ˇsix ˇ");
4512 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4513 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
4514
4515 // Paste again but with only two cursors. Since the number of cursors doesn't
4516 // match the number of slices in the clipboard, the entire clipboard text
4517 // is pasted at each cursor.
4518 cx.set_state("ˇtwo one✅ four three six five ˇ");
4519 cx.update_editor(|e, cx| {
4520 e.handle_input("( ", cx);
4521 e.paste(&Paste, cx);
4522 e.handle_input(") ", cx);
4523 });
4524 cx.assert_editor_state(
4525 &([
4526 "( one✅ ",
4527 "three ",
4528 "five ) ˇtwo one✅ four three six five ( one✅ ",
4529 "three ",
4530 "five ) ˇ",
4531 ]
4532 .join("\n")),
4533 );
4534
4535 // Cut with three selections, one of which is full-line.
4536 cx.set_state(indoc! {"
4537 1«2ˇ»3
4538 4ˇ567
4539 «8ˇ»9"});
4540 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4541 cx.assert_editor_state(indoc! {"
4542 1ˇ3
4543 ˇ9"});
4544
4545 // Paste with three selections, noticing how the copied selection that was full-line
4546 // gets inserted before the second cursor.
4547 cx.set_state(indoc! {"
4548 1ˇ3
4549 9ˇ
4550 «oˇ»ne"});
4551 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4552 cx.assert_editor_state(indoc! {"
4553 12ˇ3
4554 4567
4555 9ˇ
4556 8ˇne"});
4557
4558 // Copy with a single cursor only, which writes the whole line into the clipboard.
4559 cx.set_state(indoc! {"
4560 The quick brown
4561 fox juˇmps over
4562 the lazy dog"});
4563 cx.update_editor(|e, cx| e.copy(&Copy, cx));
4564 assert_eq!(
4565 cx.read_from_clipboard()
4566 .and_then(|item| item.text().as_deref().map(str::to_string)),
4567 Some("fox jumps over\n".to_string())
4568 );
4569
4570 // Paste with three selections, noticing how the copied full-line selection is inserted
4571 // before the empty selections but replaces the selection that is non-empty.
4572 cx.set_state(indoc! {"
4573 Tˇhe quick brown
4574 «foˇ»x jumps over
4575 tˇhe lazy dog"});
4576 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4577 cx.assert_editor_state(indoc! {"
4578 fox jumps over
4579 Tˇhe quick brown
4580 fox jumps over
4581 ˇx jumps over
4582 fox jumps over
4583 tˇhe lazy dog"});
4584}
4585
4586#[gpui::test]
4587async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
4588 init_test(cx, |_| {});
4589
4590 let mut cx = EditorTestContext::new(cx).await;
4591 let language = Arc::new(Language::new(
4592 LanguageConfig::default(),
4593 Some(tree_sitter_rust::LANGUAGE.into()),
4594 ));
4595 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4596
4597 // Cut an indented block, without the leading whitespace.
4598 cx.set_state(indoc! {"
4599 const a: B = (
4600 c(),
4601 «d(
4602 e,
4603 f
4604 )ˇ»
4605 );
4606 "});
4607 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4608 cx.assert_editor_state(indoc! {"
4609 const a: B = (
4610 c(),
4611 ˇ
4612 );
4613 "});
4614
4615 // Paste it at the same position.
4616 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4617 cx.assert_editor_state(indoc! {"
4618 const a: B = (
4619 c(),
4620 d(
4621 e,
4622 f
4623 )ˇ
4624 );
4625 "});
4626
4627 // Paste it at a line with a lower indent level.
4628 cx.set_state(indoc! {"
4629 ˇ
4630 const a: B = (
4631 c(),
4632 );
4633 "});
4634 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4635 cx.assert_editor_state(indoc! {"
4636 d(
4637 e,
4638 f
4639 )ˇ
4640 const a: B = (
4641 c(),
4642 );
4643 "});
4644
4645 // Cut an indented block, with the leading whitespace.
4646 cx.set_state(indoc! {"
4647 const a: B = (
4648 c(),
4649 « d(
4650 e,
4651 f
4652 )
4653 ˇ»);
4654 "});
4655 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4656 cx.assert_editor_state(indoc! {"
4657 const a: B = (
4658 c(),
4659 ˇ);
4660 "});
4661
4662 // Paste it at the same position.
4663 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4664 cx.assert_editor_state(indoc! {"
4665 const a: B = (
4666 c(),
4667 d(
4668 e,
4669 f
4670 )
4671 ˇ);
4672 "});
4673
4674 // Paste it at a line with a higher indent level.
4675 cx.set_state(indoc! {"
4676 const a: B = (
4677 c(),
4678 d(
4679 e,
4680 fˇ
4681 )
4682 );
4683 "});
4684 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4685 cx.assert_editor_state(indoc! {"
4686 const a: B = (
4687 c(),
4688 d(
4689 e,
4690 f d(
4691 e,
4692 f
4693 )
4694 ˇ
4695 )
4696 );
4697 "});
4698}
4699
4700#[gpui::test]
4701fn test_select_all(cx: &mut TestAppContext) {
4702 init_test(cx, |_| {});
4703
4704 let view = cx.add_window(|cx| {
4705 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
4706 build_editor(buffer, cx)
4707 });
4708 _ = view.update(cx, |view, cx| {
4709 view.select_all(&SelectAll, cx);
4710 assert_eq!(
4711 view.selections.display_ranges(cx),
4712 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
4713 );
4714 });
4715}
4716
4717#[gpui::test]
4718fn test_select_line(cx: &mut TestAppContext) {
4719 init_test(cx, |_| {});
4720
4721 let view = cx.add_window(|cx| {
4722 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
4723 build_editor(buffer, cx)
4724 });
4725 _ = view.update(cx, |view, cx| {
4726 view.change_selections(None, cx, |s| {
4727 s.select_display_ranges([
4728 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4729 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4730 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4731 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
4732 ])
4733 });
4734 view.select_line(&SelectLine, cx);
4735 assert_eq!(
4736 view.selections.display_ranges(cx),
4737 vec![
4738 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
4739 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
4740 ]
4741 );
4742 });
4743
4744 _ = view.update(cx, |view, cx| {
4745 view.select_line(&SelectLine, cx);
4746 assert_eq!(
4747 view.selections.display_ranges(cx),
4748 vec![
4749 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
4750 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
4751 ]
4752 );
4753 });
4754
4755 _ = view.update(cx, |view, cx| {
4756 view.select_line(&SelectLine, cx);
4757 assert_eq!(
4758 view.selections.display_ranges(cx),
4759 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
4760 );
4761 });
4762}
4763
4764#[gpui::test]
4765fn test_split_selection_into_lines(cx: &mut TestAppContext) {
4766 init_test(cx, |_| {});
4767
4768 let view = cx.add_window(|cx| {
4769 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
4770 build_editor(buffer, cx)
4771 });
4772 _ = view.update(cx, |view, cx| {
4773 view.fold_ranges(
4774 vec![
4775 (Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4776 (Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4777 (Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4778 ],
4779 true,
4780 cx,
4781 );
4782 view.change_selections(None, cx, |s| {
4783 s.select_display_ranges([
4784 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4785 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4786 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4787 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
4788 ])
4789 });
4790 assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i");
4791 });
4792
4793 _ = view.update(cx, |view, cx| {
4794 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4795 assert_eq!(
4796 view.display_text(cx),
4797 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
4798 );
4799 assert_eq!(
4800 view.selections.display_ranges(cx),
4801 [
4802 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4803 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4804 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
4805 DisplayPoint::new(DisplayRow(5), 4)..DisplayPoint::new(DisplayRow(5), 4)
4806 ]
4807 );
4808 });
4809
4810 _ = view.update(cx, |view, cx| {
4811 view.change_selections(None, cx, |s| {
4812 s.select_display_ranges([
4813 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
4814 ])
4815 });
4816 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4817 assert_eq!(
4818 view.display_text(cx),
4819 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
4820 );
4821 assert_eq!(
4822 view.selections.display_ranges(cx),
4823 [
4824 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
4825 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
4826 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
4827 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
4828 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
4829 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
4830 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5),
4831 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(7), 0)
4832 ]
4833 );
4834 });
4835}
4836
4837#[gpui::test]
4838async fn test_add_selection_above_below(cx: &mut TestAppContext) {
4839 init_test(cx, |_| {});
4840
4841 let mut cx = EditorTestContext::new(cx).await;
4842
4843 // let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
4844 cx.set_state(indoc!(
4845 r#"abc
4846 defˇghi
4847
4848 jk
4849 nlmo
4850 "#
4851 ));
4852
4853 cx.update_editor(|editor, cx| {
4854 editor.add_selection_above(&Default::default(), cx);
4855 });
4856
4857 cx.assert_editor_state(indoc!(
4858 r#"abcˇ
4859 defˇghi
4860
4861 jk
4862 nlmo
4863 "#
4864 ));
4865
4866 cx.update_editor(|editor, cx| {
4867 editor.add_selection_above(&Default::default(), cx);
4868 });
4869
4870 cx.assert_editor_state(indoc!(
4871 r#"abcˇ
4872 defˇghi
4873
4874 jk
4875 nlmo
4876 "#
4877 ));
4878
4879 cx.update_editor(|view, cx| {
4880 view.add_selection_below(&Default::default(), cx);
4881 });
4882
4883 cx.assert_editor_state(indoc!(
4884 r#"abc
4885 defˇghi
4886
4887 jk
4888 nlmo
4889 "#
4890 ));
4891
4892 cx.update_editor(|view, cx| {
4893 view.undo_selection(&Default::default(), cx);
4894 });
4895
4896 cx.assert_editor_state(indoc!(
4897 r#"abcˇ
4898 defˇghi
4899
4900 jk
4901 nlmo
4902 "#
4903 ));
4904
4905 cx.update_editor(|view, cx| {
4906 view.redo_selection(&Default::default(), cx);
4907 });
4908
4909 cx.assert_editor_state(indoc!(
4910 r#"abc
4911 defˇghi
4912
4913 jk
4914 nlmo
4915 "#
4916 ));
4917
4918 cx.update_editor(|view, cx| {
4919 view.add_selection_below(&Default::default(), cx);
4920 });
4921
4922 cx.assert_editor_state(indoc!(
4923 r#"abc
4924 defˇghi
4925
4926 jk
4927 nlmˇo
4928 "#
4929 ));
4930
4931 cx.update_editor(|view, cx| {
4932 view.add_selection_below(&Default::default(), cx);
4933 });
4934
4935 cx.assert_editor_state(indoc!(
4936 r#"abc
4937 defˇghi
4938
4939 jk
4940 nlmˇo
4941 "#
4942 ));
4943
4944 // change selections
4945 cx.set_state(indoc!(
4946 r#"abc
4947 def«ˇg»hi
4948
4949 jk
4950 nlmo
4951 "#
4952 ));
4953
4954 cx.update_editor(|view, cx| {
4955 view.add_selection_below(&Default::default(), cx);
4956 });
4957
4958 cx.assert_editor_state(indoc!(
4959 r#"abc
4960 def«ˇg»hi
4961
4962 jk
4963 nlm«ˇo»
4964 "#
4965 ));
4966
4967 cx.update_editor(|view, cx| {
4968 view.add_selection_below(&Default::default(), cx);
4969 });
4970
4971 cx.assert_editor_state(indoc!(
4972 r#"abc
4973 def«ˇg»hi
4974
4975 jk
4976 nlm«ˇo»
4977 "#
4978 ));
4979
4980 cx.update_editor(|view, cx| {
4981 view.add_selection_above(&Default::default(), cx);
4982 });
4983
4984 cx.assert_editor_state(indoc!(
4985 r#"abc
4986 def«ˇg»hi
4987
4988 jk
4989 nlmo
4990 "#
4991 ));
4992
4993 cx.update_editor(|view, cx| {
4994 view.add_selection_above(&Default::default(), cx);
4995 });
4996
4997 cx.assert_editor_state(indoc!(
4998 r#"abc
4999 def«ˇg»hi
5000
5001 jk
5002 nlmo
5003 "#
5004 ));
5005
5006 // Change selections again
5007 cx.set_state(indoc!(
5008 r#"a«bc
5009 defgˇ»hi
5010
5011 jk
5012 nlmo
5013 "#
5014 ));
5015
5016 cx.update_editor(|view, cx| {
5017 view.add_selection_below(&Default::default(), cx);
5018 });
5019
5020 cx.assert_editor_state(indoc!(
5021 r#"a«bcˇ»
5022 d«efgˇ»hi
5023
5024 j«kˇ»
5025 nlmo
5026 "#
5027 ));
5028
5029 cx.update_editor(|view, cx| {
5030 view.add_selection_below(&Default::default(), cx);
5031 });
5032 cx.assert_editor_state(indoc!(
5033 r#"a«bcˇ»
5034 d«efgˇ»hi
5035
5036 j«kˇ»
5037 n«lmoˇ»
5038 "#
5039 ));
5040 cx.update_editor(|view, cx| {
5041 view.add_selection_above(&Default::default(), cx);
5042 });
5043
5044 cx.assert_editor_state(indoc!(
5045 r#"a«bcˇ»
5046 d«efgˇ»hi
5047
5048 j«kˇ»
5049 nlmo
5050 "#
5051 ));
5052
5053 // Change selections again
5054 cx.set_state(indoc!(
5055 r#"abc
5056 d«ˇefghi
5057
5058 jk
5059 nlm»o
5060 "#
5061 ));
5062
5063 cx.update_editor(|view, cx| {
5064 view.add_selection_above(&Default::default(), cx);
5065 });
5066
5067 cx.assert_editor_state(indoc!(
5068 r#"a«ˇbc»
5069 d«ˇef»ghi
5070
5071 j«ˇk»
5072 n«ˇlm»o
5073 "#
5074 ));
5075
5076 cx.update_editor(|view, cx| {
5077 view.add_selection_below(&Default::default(), cx);
5078 });
5079
5080 cx.assert_editor_state(indoc!(
5081 r#"abc
5082 d«ˇef»ghi
5083
5084 j«ˇk»
5085 n«ˇlm»o
5086 "#
5087 ));
5088}
5089
5090#[gpui::test]
5091async fn test_select_next(cx: &mut gpui::TestAppContext) {
5092 init_test(cx, |_| {});
5093
5094 let mut cx = EditorTestContext::new(cx).await;
5095 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5096
5097 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5098 .unwrap();
5099 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5100
5101 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5102 .unwrap();
5103 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5104
5105 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5106 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5107
5108 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5109 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5110
5111 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5112 .unwrap();
5113 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5114
5115 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5116 .unwrap();
5117 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5118}
5119
5120#[gpui::test]
5121async fn test_select_all_matches(cx: &mut gpui::TestAppContext) {
5122 init_test(cx, |_| {});
5123
5124 let mut cx = EditorTestContext::new(cx).await;
5125 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5126
5127 cx.update_editor(|e, cx| e.select_all_matches(&SelectAllMatches, cx))
5128 .unwrap();
5129 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5130}
5131
5132#[gpui::test]
5133async fn test_select_next_with_multiple_carets(cx: &mut gpui::TestAppContext) {
5134 init_test(cx, |_| {});
5135
5136 let mut cx = EditorTestContext::new(cx).await;
5137 cx.set_state(
5138 r#"let foo = 2;
5139lˇet foo = 2;
5140let fooˇ = 2;
5141let foo = 2;
5142let foo = ˇ2;"#,
5143 );
5144
5145 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5146 .unwrap();
5147 cx.assert_editor_state(
5148 r#"let foo = 2;
5149«letˇ» foo = 2;
5150let «fooˇ» = 2;
5151let foo = 2;
5152let foo = «2ˇ»;"#,
5153 );
5154
5155 // noop for multiple selections with different contents
5156 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5157 .unwrap();
5158 cx.assert_editor_state(
5159 r#"let foo = 2;
5160«letˇ» foo = 2;
5161let «fooˇ» = 2;
5162let foo = 2;
5163let foo = «2ˇ»;"#,
5164 );
5165}
5166
5167#[gpui::test]
5168async fn test_select_previous_multibuffer(cx: &mut gpui::TestAppContext) {
5169 init_test(cx, |_| {});
5170
5171 let mut cx =
5172 EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
5173
5174 cx.assert_editor_state(indoc! {"
5175 ˇbbb
5176 ccc
5177
5178 bbb
5179 ccc
5180 "});
5181 cx.dispatch_action(SelectPrevious::default());
5182 cx.assert_editor_state(indoc! {"
5183 «bbbˇ»
5184 ccc
5185
5186 bbb
5187 ccc
5188 "});
5189 cx.dispatch_action(SelectPrevious::default());
5190 cx.assert_editor_state(indoc! {"
5191 «bbbˇ»
5192 ccc
5193
5194 «bbbˇ»
5195 ccc
5196 "});
5197}
5198
5199#[gpui::test]
5200async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) {
5201 init_test(cx, |_| {});
5202
5203 let mut cx = EditorTestContext::new(cx).await;
5204 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5205
5206 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5207 .unwrap();
5208 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5209
5210 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5211 .unwrap();
5212 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5213
5214 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5215 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5216
5217 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5218 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5219
5220 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5221 .unwrap();
5222 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
5223
5224 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5225 .unwrap();
5226 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
5227
5228 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5229 .unwrap();
5230 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5231}
5232
5233#[gpui::test]
5234async fn test_select_previous_with_multiple_carets(cx: &mut gpui::TestAppContext) {
5235 init_test(cx, |_| {});
5236
5237 let mut cx = EditorTestContext::new(cx).await;
5238 cx.set_state(
5239 r#"let foo = 2;
5240lˇet foo = 2;
5241let fooˇ = 2;
5242let foo = 2;
5243let foo = ˇ2;"#,
5244 );
5245
5246 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5247 .unwrap();
5248 cx.assert_editor_state(
5249 r#"let foo = 2;
5250«letˇ» foo = 2;
5251let «fooˇ» = 2;
5252let foo = 2;
5253let foo = «2ˇ»;"#,
5254 );
5255
5256 // noop for multiple selections with different contents
5257 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5258 .unwrap();
5259 cx.assert_editor_state(
5260 r#"let foo = 2;
5261«letˇ» foo = 2;
5262let «fooˇ» = 2;
5263let foo = 2;
5264let foo = «2ˇ»;"#,
5265 );
5266}
5267
5268#[gpui::test]
5269async fn test_select_previous_with_single_selection(cx: &mut gpui::TestAppContext) {
5270 init_test(cx, |_| {});
5271
5272 let mut cx = EditorTestContext::new(cx).await;
5273 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
5274
5275 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5276 .unwrap();
5277 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5278
5279 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5280 .unwrap();
5281 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5282
5283 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5284 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5285
5286 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5287 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5288
5289 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5290 .unwrap();
5291 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
5292
5293 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5294 .unwrap();
5295 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5296}
5297
5298#[gpui::test]
5299async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
5300 init_test(cx, |_| {});
5301
5302 let language = Arc::new(Language::new(
5303 LanguageConfig::default(),
5304 Some(tree_sitter_rust::LANGUAGE.into()),
5305 ));
5306
5307 let text = r#"
5308 use mod1::mod2::{mod3, mod4};
5309
5310 fn fn_1(param1: bool, param2: &str) {
5311 let var1 = "text";
5312 }
5313 "#
5314 .unindent();
5315
5316 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5317 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5318 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5319
5320 editor
5321 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5322 .await;
5323
5324 editor.update(cx, |view, cx| {
5325 view.change_selections(None, cx, |s| {
5326 s.select_display_ranges([
5327 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
5328 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
5329 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
5330 ]);
5331 });
5332 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5333 });
5334 editor.update(cx, |editor, cx| {
5335 assert_text_with_selections(
5336 editor,
5337 indoc! {r#"
5338 use mod1::mod2::{mod3, «mod4ˇ»};
5339
5340 fn fn_1«ˇ(param1: bool, param2: &str)» {
5341 let var1 = "«textˇ»";
5342 }
5343 "#},
5344 cx,
5345 );
5346 });
5347
5348 editor.update(cx, |view, cx| {
5349 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5350 });
5351 editor.update(cx, |editor, cx| {
5352 assert_text_with_selections(
5353 editor,
5354 indoc! {r#"
5355 use mod1::mod2::«{mod3, mod4}ˇ»;
5356
5357 «ˇfn fn_1(param1: bool, param2: &str) {
5358 let var1 = "text";
5359 }»
5360 "#},
5361 cx,
5362 );
5363 });
5364
5365 editor.update(cx, |view, cx| {
5366 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5367 });
5368 assert_eq!(
5369 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
5370 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5371 );
5372
5373 // Trying to expand the selected syntax node one more time has no effect.
5374 editor.update(cx, |view, cx| {
5375 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5376 });
5377 assert_eq!(
5378 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
5379 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5380 );
5381
5382 editor.update(cx, |view, cx| {
5383 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5384 });
5385 editor.update(cx, |editor, cx| {
5386 assert_text_with_selections(
5387 editor,
5388 indoc! {r#"
5389 use mod1::mod2::«{mod3, mod4}ˇ»;
5390
5391 «ˇfn fn_1(param1: bool, param2: &str) {
5392 let var1 = "text";
5393 }»
5394 "#},
5395 cx,
5396 );
5397 });
5398
5399 editor.update(cx, |view, cx| {
5400 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5401 });
5402 editor.update(cx, |editor, cx| {
5403 assert_text_with_selections(
5404 editor,
5405 indoc! {r#"
5406 use mod1::mod2::{mod3, «mod4ˇ»};
5407
5408 fn fn_1«ˇ(param1: bool, param2: &str)» {
5409 let var1 = "«textˇ»";
5410 }
5411 "#},
5412 cx,
5413 );
5414 });
5415
5416 editor.update(cx, |view, cx| {
5417 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5418 });
5419 editor.update(cx, |editor, cx| {
5420 assert_text_with_selections(
5421 editor,
5422 indoc! {r#"
5423 use mod1::mod2::{mod3, mo«ˇ»d4};
5424
5425 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5426 let var1 = "te«ˇ»xt";
5427 }
5428 "#},
5429 cx,
5430 );
5431 });
5432
5433 // Trying to shrink the selected syntax node one more time has no effect.
5434 editor.update(cx, |view, cx| {
5435 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5436 });
5437 editor.update(cx, |editor, cx| {
5438 assert_text_with_selections(
5439 editor,
5440 indoc! {r#"
5441 use mod1::mod2::{mod3, mo«ˇ»d4};
5442
5443 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5444 let var1 = "te«ˇ»xt";
5445 }
5446 "#},
5447 cx,
5448 );
5449 });
5450
5451 // Ensure that we keep expanding the selection if the larger selection starts or ends within
5452 // a fold.
5453 editor.update(cx, |view, cx| {
5454 view.fold_ranges(
5455 vec![
5456 (
5457 Point::new(0, 21)..Point::new(0, 24),
5458 FoldPlaceholder::test(),
5459 ),
5460 (
5461 Point::new(3, 20)..Point::new(3, 22),
5462 FoldPlaceholder::test(),
5463 ),
5464 ],
5465 true,
5466 cx,
5467 );
5468 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5469 });
5470 editor.update(cx, |editor, cx| {
5471 assert_text_with_selections(
5472 editor,
5473 indoc! {r#"
5474 use mod1::mod2::«{mod3, mod4}ˇ»;
5475
5476 fn fn_1«ˇ(param1: bool, param2: &str)» {
5477 «let var1 = "text";ˇ»
5478 }
5479 "#},
5480 cx,
5481 );
5482 });
5483}
5484
5485#[gpui::test]
5486async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
5487 init_test(cx, |_| {});
5488
5489 let language = Arc::new(
5490 Language::new(
5491 LanguageConfig {
5492 brackets: BracketPairConfig {
5493 pairs: vec![
5494 BracketPair {
5495 start: "{".to_string(),
5496 end: "}".to_string(),
5497 close: false,
5498 surround: false,
5499 newline: true,
5500 },
5501 BracketPair {
5502 start: "(".to_string(),
5503 end: ")".to_string(),
5504 close: false,
5505 surround: false,
5506 newline: true,
5507 },
5508 ],
5509 ..Default::default()
5510 },
5511 ..Default::default()
5512 },
5513 Some(tree_sitter_rust::LANGUAGE.into()),
5514 )
5515 .with_indents_query(
5516 r#"
5517 (_ "(" ")" @end) @indent
5518 (_ "{" "}" @end) @indent
5519 "#,
5520 )
5521 .unwrap(),
5522 );
5523
5524 let text = "fn a() {}";
5525
5526 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5527 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5528 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5529 editor
5530 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5531 .await;
5532
5533 editor.update(cx, |editor, cx| {
5534 editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
5535 editor.newline(&Newline, cx);
5536 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
5537 assert_eq!(
5538 editor.selections.ranges(cx),
5539 &[
5540 Point::new(1, 4)..Point::new(1, 4),
5541 Point::new(3, 4)..Point::new(3, 4),
5542 Point::new(5, 0)..Point::new(5, 0)
5543 ]
5544 );
5545 });
5546}
5547
5548#[gpui::test]
5549async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
5550 init_test(cx, |_| {});
5551
5552 let mut cx = EditorTestContext::new(cx).await;
5553
5554 let language = Arc::new(Language::new(
5555 LanguageConfig {
5556 brackets: BracketPairConfig {
5557 pairs: vec![
5558 BracketPair {
5559 start: "{".to_string(),
5560 end: "}".to_string(),
5561 close: true,
5562 surround: true,
5563 newline: true,
5564 },
5565 BracketPair {
5566 start: "(".to_string(),
5567 end: ")".to_string(),
5568 close: true,
5569 surround: true,
5570 newline: true,
5571 },
5572 BracketPair {
5573 start: "/*".to_string(),
5574 end: " */".to_string(),
5575 close: true,
5576 surround: true,
5577 newline: true,
5578 },
5579 BracketPair {
5580 start: "[".to_string(),
5581 end: "]".to_string(),
5582 close: false,
5583 surround: false,
5584 newline: true,
5585 },
5586 BracketPair {
5587 start: "\"".to_string(),
5588 end: "\"".to_string(),
5589 close: true,
5590 surround: true,
5591 newline: false,
5592 },
5593 BracketPair {
5594 start: "<".to_string(),
5595 end: ">".to_string(),
5596 close: false,
5597 surround: true,
5598 newline: true,
5599 },
5600 ],
5601 ..Default::default()
5602 },
5603 autoclose_before: "})]".to_string(),
5604 ..Default::default()
5605 },
5606 Some(tree_sitter_rust::LANGUAGE.into()),
5607 ));
5608
5609 cx.language_registry().add(language.clone());
5610 cx.update_buffer(|buffer, cx| {
5611 buffer.set_language(Some(language), cx);
5612 });
5613
5614 cx.set_state(
5615 &r#"
5616 🏀ˇ
5617 εˇ
5618 ❤️ˇ
5619 "#
5620 .unindent(),
5621 );
5622
5623 // autoclose multiple nested brackets at multiple cursors
5624 cx.update_editor(|view, cx| {
5625 view.handle_input("{", cx);
5626 view.handle_input("{", cx);
5627 view.handle_input("{", cx);
5628 });
5629 cx.assert_editor_state(
5630 &"
5631 🏀{{{ˇ}}}
5632 ε{{{ˇ}}}
5633 ❤️{{{ˇ}}}
5634 "
5635 .unindent(),
5636 );
5637
5638 // insert a different closing bracket
5639 cx.update_editor(|view, cx| {
5640 view.handle_input(")", cx);
5641 });
5642 cx.assert_editor_state(
5643 &"
5644 🏀{{{)ˇ}}}
5645 ε{{{)ˇ}}}
5646 ❤️{{{)ˇ}}}
5647 "
5648 .unindent(),
5649 );
5650
5651 // skip over the auto-closed brackets when typing a closing bracket
5652 cx.update_editor(|view, cx| {
5653 view.move_right(&MoveRight, cx);
5654 view.handle_input("}", cx);
5655 view.handle_input("}", cx);
5656 view.handle_input("}", cx);
5657 });
5658 cx.assert_editor_state(
5659 &"
5660 🏀{{{)}}}}ˇ
5661 ε{{{)}}}}ˇ
5662 ❤️{{{)}}}}ˇ
5663 "
5664 .unindent(),
5665 );
5666
5667 // autoclose multi-character pairs
5668 cx.set_state(
5669 &"
5670 ˇ
5671 ˇ
5672 "
5673 .unindent(),
5674 );
5675 cx.update_editor(|view, cx| {
5676 view.handle_input("/", cx);
5677 view.handle_input("*", cx);
5678 });
5679 cx.assert_editor_state(
5680 &"
5681 /*ˇ */
5682 /*ˇ */
5683 "
5684 .unindent(),
5685 );
5686
5687 // one cursor autocloses a multi-character pair, one cursor
5688 // does not autoclose.
5689 cx.set_state(
5690 &"
5691 /ˇ
5692 ˇ
5693 "
5694 .unindent(),
5695 );
5696 cx.update_editor(|view, cx| view.handle_input("*", cx));
5697 cx.assert_editor_state(
5698 &"
5699 /*ˇ */
5700 *ˇ
5701 "
5702 .unindent(),
5703 );
5704
5705 // Don't autoclose if the next character isn't whitespace and isn't
5706 // listed in the language's "autoclose_before" section.
5707 cx.set_state("ˇa b");
5708 cx.update_editor(|view, cx| view.handle_input("{", cx));
5709 cx.assert_editor_state("{ˇa b");
5710
5711 // Don't autoclose if `close` is false for the bracket pair
5712 cx.set_state("ˇ");
5713 cx.update_editor(|view, cx| view.handle_input("[", cx));
5714 cx.assert_editor_state("[ˇ");
5715
5716 // Surround with brackets if text is selected
5717 cx.set_state("«aˇ» b");
5718 cx.update_editor(|view, cx| view.handle_input("{", cx));
5719 cx.assert_editor_state("{«aˇ»} b");
5720
5721 // Autclose pair where the start and end characters are the same
5722 cx.set_state("aˇ");
5723 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5724 cx.assert_editor_state("a\"ˇ\"");
5725 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5726 cx.assert_editor_state("a\"\"ˇ");
5727
5728 // Don't autoclose pair if autoclose is disabled
5729 cx.set_state("ˇ");
5730 cx.update_editor(|view, cx| view.handle_input("<", cx));
5731 cx.assert_editor_state("<ˇ");
5732
5733 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
5734 cx.set_state("«aˇ» b");
5735 cx.update_editor(|view, cx| view.handle_input("<", cx));
5736 cx.assert_editor_state("<«aˇ»> b");
5737}
5738
5739#[gpui::test]
5740async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut gpui::TestAppContext) {
5741 init_test(cx, |settings| {
5742 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
5743 });
5744
5745 let mut cx = EditorTestContext::new(cx).await;
5746
5747 let language = Arc::new(Language::new(
5748 LanguageConfig {
5749 brackets: BracketPairConfig {
5750 pairs: vec![
5751 BracketPair {
5752 start: "{".to_string(),
5753 end: "}".to_string(),
5754 close: true,
5755 surround: true,
5756 newline: true,
5757 },
5758 BracketPair {
5759 start: "(".to_string(),
5760 end: ")".to_string(),
5761 close: true,
5762 surround: true,
5763 newline: true,
5764 },
5765 BracketPair {
5766 start: "[".to_string(),
5767 end: "]".to_string(),
5768 close: false,
5769 surround: false,
5770 newline: true,
5771 },
5772 ],
5773 ..Default::default()
5774 },
5775 autoclose_before: "})]".to_string(),
5776 ..Default::default()
5777 },
5778 Some(tree_sitter_rust::LANGUAGE.into()),
5779 ));
5780
5781 cx.language_registry().add(language.clone());
5782 cx.update_buffer(|buffer, cx| {
5783 buffer.set_language(Some(language), cx);
5784 });
5785
5786 cx.set_state(
5787 &"
5788 ˇ
5789 ˇ
5790 ˇ
5791 "
5792 .unindent(),
5793 );
5794
5795 // ensure only matching closing brackets are skipped over
5796 cx.update_editor(|view, cx| {
5797 view.handle_input("}", cx);
5798 view.move_left(&MoveLeft, cx);
5799 view.handle_input(")", cx);
5800 view.move_left(&MoveLeft, cx);
5801 });
5802 cx.assert_editor_state(
5803 &"
5804 ˇ)}
5805 ˇ)}
5806 ˇ)}
5807 "
5808 .unindent(),
5809 );
5810
5811 // skip-over closing brackets at multiple cursors
5812 cx.update_editor(|view, cx| {
5813 view.handle_input(")", cx);
5814 view.handle_input("}", cx);
5815 });
5816 cx.assert_editor_state(
5817 &"
5818 )}ˇ
5819 )}ˇ
5820 )}ˇ
5821 "
5822 .unindent(),
5823 );
5824
5825 // ignore non-close brackets
5826 cx.update_editor(|view, cx| {
5827 view.handle_input("]", cx);
5828 view.move_left(&MoveLeft, cx);
5829 view.handle_input("]", cx);
5830 });
5831 cx.assert_editor_state(
5832 &"
5833 )}]ˇ]
5834 )}]ˇ]
5835 )}]ˇ]
5836 "
5837 .unindent(),
5838 );
5839}
5840
5841#[gpui::test]
5842async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
5843 init_test(cx, |_| {});
5844
5845 let mut cx = EditorTestContext::new(cx).await;
5846
5847 let html_language = Arc::new(
5848 Language::new(
5849 LanguageConfig {
5850 name: "HTML".into(),
5851 brackets: BracketPairConfig {
5852 pairs: vec![
5853 BracketPair {
5854 start: "<".into(),
5855 end: ">".into(),
5856 close: true,
5857 ..Default::default()
5858 },
5859 BracketPair {
5860 start: "{".into(),
5861 end: "}".into(),
5862 close: true,
5863 ..Default::default()
5864 },
5865 BracketPair {
5866 start: "(".into(),
5867 end: ")".into(),
5868 close: true,
5869 ..Default::default()
5870 },
5871 ],
5872 ..Default::default()
5873 },
5874 autoclose_before: "})]>".into(),
5875 ..Default::default()
5876 },
5877 Some(tree_sitter_html::language()),
5878 )
5879 .with_injection_query(
5880 r#"
5881 (script_element
5882 (raw_text) @content
5883 (#set! "language" "javascript"))
5884 "#,
5885 )
5886 .unwrap(),
5887 );
5888
5889 let javascript_language = Arc::new(Language::new(
5890 LanguageConfig {
5891 name: "JavaScript".into(),
5892 brackets: BracketPairConfig {
5893 pairs: vec![
5894 BracketPair {
5895 start: "/*".into(),
5896 end: " */".into(),
5897 close: true,
5898 ..Default::default()
5899 },
5900 BracketPair {
5901 start: "{".into(),
5902 end: "}".into(),
5903 close: true,
5904 ..Default::default()
5905 },
5906 BracketPair {
5907 start: "(".into(),
5908 end: ")".into(),
5909 close: true,
5910 ..Default::default()
5911 },
5912 ],
5913 ..Default::default()
5914 },
5915 autoclose_before: "})]>".into(),
5916 ..Default::default()
5917 },
5918 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
5919 ));
5920
5921 cx.language_registry().add(html_language.clone());
5922 cx.language_registry().add(javascript_language.clone());
5923
5924 cx.update_buffer(|buffer, cx| {
5925 buffer.set_language(Some(html_language), cx);
5926 });
5927
5928 cx.set_state(
5929 &r#"
5930 <body>ˇ
5931 <script>
5932 var x = 1;ˇ
5933 </script>
5934 </body>ˇ
5935 "#
5936 .unindent(),
5937 );
5938
5939 // Precondition: different languages are active at different locations.
5940 cx.update_editor(|editor, cx| {
5941 let snapshot = editor.snapshot(cx);
5942 let cursors = editor.selections.ranges::<usize>(cx);
5943 let languages = cursors
5944 .iter()
5945 .map(|c| snapshot.language_at(c.start).unwrap().name())
5946 .collect::<Vec<_>>();
5947 assert_eq!(
5948 languages,
5949 &["HTML".into(), "JavaScript".into(), "HTML".into()]
5950 );
5951 });
5952
5953 // Angle brackets autoclose in HTML, but not JavaScript.
5954 cx.update_editor(|editor, cx| {
5955 editor.handle_input("<", cx);
5956 editor.handle_input("a", cx);
5957 });
5958 cx.assert_editor_state(
5959 &r#"
5960 <body><aˇ>
5961 <script>
5962 var x = 1;<aˇ
5963 </script>
5964 </body><aˇ>
5965 "#
5966 .unindent(),
5967 );
5968
5969 // Curly braces and parens autoclose in both HTML and JavaScript.
5970 cx.update_editor(|editor, cx| {
5971 editor.handle_input(" b=", cx);
5972 editor.handle_input("{", cx);
5973 editor.handle_input("c", cx);
5974 editor.handle_input("(", cx);
5975 });
5976 cx.assert_editor_state(
5977 &r#"
5978 <body><a b={c(ˇ)}>
5979 <script>
5980 var x = 1;<a b={c(ˇ)}
5981 </script>
5982 </body><a b={c(ˇ)}>
5983 "#
5984 .unindent(),
5985 );
5986
5987 // Brackets that were already autoclosed are skipped.
5988 cx.update_editor(|editor, cx| {
5989 editor.handle_input(")", cx);
5990 editor.handle_input("d", cx);
5991 editor.handle_input("}", cx);
5992 });
5993 cx.assert_editor_state(
5994 &r#"
5995 <body><a b={c()d}ˇ>
5996 <script>
5997 var x = 1;<a b={c()d}ˇ
5998 </script>
5999 </body><a b={c()d}ˇ>
6000 "#
6001 .unindent(),
6002 );
6003 cx.update_editor(|editor, cx| {
6004 editor.handle_input(">", cx);
6005 });
6006 cx.assert_editor_state(
6007 &r#"
6008 <body><a b={c()d}>ˇ
6009 <script>
6010 var x = 1;<a b={c()d}>ˇ
6011 </script>
6012 </body><a b={c()d}>ˇ
6013 "#
6014 .unindent(),
6015 );
6016
6017 // Reset
6018 cx.set_state(
6019 &r#"
6020 <body>ˇ
6021 <script>
6022 var x = 1;ˇ
6023 </script>
6024 </body>ˇ
6025 "#
6026 .unindent(),
6027 );
6028
6029 cx.update_editor(|editor, cx| {
6030 editor.handle_input("<", cx);
6031 });
6032 cx.assert_editor_state(
6033 &r#"
6034 <body><ˇ>
6035 <script>
6036 var x = 1;<ˇ
6037 </script>
6038 </body><ˇ>
6039 "#
6040 .unindent(),
6041 );
6042
6043 // When backspacing, the closing angle brackets are removed.
6044 cx.update_editor(|editor, cx| {
6045 editor.backspace(&Backspace, cx);
6046 });
6047 cx.assert_editor_state(
6048 &r#"
6049 <body>ˇ
6050 <script>
6051 var x = 1;ˇ
6052 </script>
6053 </body>ˇ
6054 "#
6055 .unindent(),
6056 );
6057
6058 // Block comments autoclose in JavaScript, but not HTML.
6059 cx.update_editor(|editor, cx| {
6060 editor.handle_input("/", cx);
6061 editor.handle_input("*", cx);
6062 });
6063 cx.assert_editor_state(
6064 &r#"
6065 <body>/*ˇ
6066 <script>
6067 var x = 1;/*ˇ */
6068 </script>
6069 </body>/*ˇ
6070 "#
6071 .unindent(),
6072 );
6073}
6074
6075#[gpui::test]
6076async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
6077 init_test(cx, |_| {});
6078
6079 let mut cx = EditorTestContext::new(cx).await;
6080
6081 let rust_language = Arc::new(
6082 Language::new(
6083 LanguageConfig {
6084 name: "Rust".into(),
6085 brackets: serde_json::from_value(json!([
6086 { "start": "{", "end": "}", "close": true, "newline": true },
6087 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
6088 ]))
6089 .unwrap(),
6090 autoclose_before: "})]>".into(),
6091 ..Default::default()
6092 },
6093 Some(tree_sitter_rust::LANGUAGE.into()),
6094 )
6095 .with_override_query("(string_literal) @string")
6096 .unwrap(),
6097 );
6098
6099 cx.language_registry().add(rust_language.clone());
6100 cx.update_buffer(|buffer, cx| {
6101 buffer.set_language(Some(rust_language), cx);
6102 });
6103
6104 cx.set_state(
6105 &r#"
6106 let x = ˇ
6107 "#
6108 .unindent(),
6109 );
6110
6111 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
6112 cx.update_editor(|editor, cx| {
6113 editor.handle_input("\"", cx);
6114 });
6115 cx.assert_editor_state(
6116 &r#"
6117 let x = "ˇ"
6118 "#
6119 .unindent(),
6120 );
6121
6122 // Inserting another quotation mark. The cursor moves across the existing
6123 // automatically-inserted quotation mark.
6124 cx.update_editor(|editor, cx| {
6125 editor.handle_input("\"", cx);
6126 });
6127 cx.assert_editor_state(
6128 &r#"
6129 let x = ""ˇ
6130 "#
6131 .unindent(),
6132 );
6133
6134 // Reset
6135 cx.set_state(
6136 &r#"
6137 let x = ˇ
6138 "#
6139 .unindent(),
6140 );
6141
6142 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
6143 cx.update_editor(|editor, cx| {
6144 editor.handle_input("\"", cx);
6145 editor.handle_input(" ", cx);
6146 editor.move_left(&Default::default(), cx);
6147 editor.handle_input("\\", cx);
6148 editor.handle_input("\"", cx);
6149 });
6150 cx.assert_editor_state(
6151 &r#"
6152 let x = "\"ˇ "
6153 "#
6154 .unindent(),
6155 );
6156
6157 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
6158 // mark. Nothing is inserted.
6159 cx.update_editor(|editor, cx| {
6160 editor.move_right(&Default::default(), cx);
6161 editor.handle_input("\"", cx);
6162 });
6163 cx.assert_editor_state(
6164 &r#"
6165 let x = "\" "ˇ
6166 "#
6167 .unindent(),
6168 );
6169}
6170
6171#[gpui::test]
6172async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
6173 init_test(cx, |_| {});
6174
6175 let language = Arc::new(Language::new(
6176 LanguageConfig {
6177 brackets: BracketPairConfig {
6178 pairs: vec![
6179 BracketPair {
6180 start: "{".to_string(),
6181 end: "}".to_string(),
6182 close: true,
6183 surround: true,
6184 newline: true,
6185 },
6186 BracketPair {
6187 start: "/* ".to_string(),
6188 end: "*/".to_string(),
6189 close: true,
6190 surround: true,
6191 ..Default::default()
6192 },
6193 ],
6194 ..Default::default()
6195 },
6196 ..Default::default()
6197 },
6198 Some(tree_sitter_rust::LANGUAGE.into()),
6199 ));
6200
6201 let text = r#"
6202 a
6203 b
6204 c
6205 "#
6206 .unindent();
6207
6208 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
6209 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6210 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6211 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6212 .await;
6213
6214 view.update(cx, |view, cx| {
6215 view.change_selections(None, cx, |s| {
6216 s.select_display_ranges([
6217 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6218 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6219 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
6220 ])
6221 });
6222
6223 view.handle_input("{", cx);
6224 view.handle_input("{", cx);
6225 view.handle_input("{", cx);
6226 assert_eq!(
6227 view.text(cx),
6228 "
6229 {{{a}}}
6230 {{{b}}}
6231 {{{c}}}
6232 "
6233 .unindent()
6234 );
6235 assert_eq!(
6236 view.selections.display_ranges(cx),
6237 [
6238 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
6239 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
6240 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
6241 ]
6242 );
6243
6244 view.undo(&Undo, cx);
6245 view.undo(&Undo, cx);
6246 view.undo(&Undo, cx);
6247 assert_eq!(
6248 view.text(cx),
6249 "
6250 a
6251 b
6252 c
6253 "
6254 .unindent()
6255 );
6256 assert_eq!(
6257 view.selections.display_ranges(cx),
6258 [
6259 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6260 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6261 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6262 ]
6263 );
6264
6265 // Ensure inserting the first character of a multi-byte bracket pair
6266 // doesn't surround the selections with the bracket.
6267 view.handle_input("/", cx);
6268 assert_eq!(
6269 view.text(cx),
6270 "
6271 /
6272 /
6273 /
6274 "
6275 .unindent()
6276 );
6277 assert_eq!(
6278 view.selections.display_ranges(cx),
6279 [
6280 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6281 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6282 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6283 ]
6284 );
6285
6286 view.undo(&Undo, cx);
6287 assert_eq!(
6288 view.text(cx),
6289 "
6290 a
6291 b
6292 c
6293 "
6294 .unindent()
6295 );
6296 assert_eq!(
6297 view.selections.display_ranges(cx),
6298 [
6299 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6300 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6301 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6302 ]
6303 );
6304
6305 // Ensure inserting the last character of a multi-byte bracket pair
6306 // doesn't surround the selections with the bracket.
6307 view.handle_input("*", cx);
6308 assert_eq!(
6309 view.text(cx),
6310 "
6311 *
6312 *
6313 *
6314 "
6315 .unindent()
6316 );
6317 assert_eq!(
6318 view.selections.display_ranges(cx),
6319 [
6320 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6321 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6322 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6323 ]
6324 );
6325 });
6326}
6327
6328#[gpui::test]
6329async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
6330 init_test(cx, |_| {});
6331
6332 let language = Arc::new(Language::new(
6333 LanguageConfig {
6334 brackets: BracketPairConfig {
6335 pairs: vec![BracketPair {
6336 start: "{".to_string(),
6337 end: "}".to_string(),
6338 close: true,
6339 surround: true,
6340 newline: true,
6341 }],
6342 ..Default::default()
6343 },
6344 autoclose_before: "}".to_string(),
6345 ..Default::default()
6346 },
6347 Some(tree_sitter_rust::LANGUAGE.into()),
6348 ));
6349
6350 let text = r#"
6351 a
6352 b
6353 c
6354 "#
6355 .unindent();
6356
6357 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
6358 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6359 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6360 editor
6361 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6362 .await;
6363
6364 editor.update(cx, |editor, cx| {
6365 editor.change_selections(None, cx, |s| {
6366 s.select_ranges([
6367 Point::new(0, 1)..Point::new(0, 1),
6368 Point::new(1, 1)..Point::new(1, 1),
6369 Point::new(2, 1)..Point::new(2, 1),
6370 ])
6371 });
6372
6373 editor.handle_input("{", cx);
6374 editor.handle_input("{", cx);
6375 editor.handle_input("_", cx);
6376 assert_eq!(
6377 editor.text(cx),
6378 "
6379 a{{_}}
6380 b{{_}}
6381 c{{_}}
6382 "
6383 .unindent()
6384 );
6385 assert_eq!(
6386 editor.selections.ranges::<Point>(cx),
6387 [
6388 Point::new(0, 4)..Point::new(0, 4),
6389 Point::new(1, 4)..Point::new(1, 4),
6390 Point::new(2, 4)..Point::new(2, 4)
6391 ]
6392 );
6393
6394 editor.backspace(&Default::default(), cx);
6395 editor.backspace(&Default::default(), cx);
6396 assert_eq!(
6397 editor.text(cx),
6398 "
6399 a{}
6400 b{}
6401 c{}
6402 "
6403 .unindent()
6404 );
6405 assert_eq!(
6406 editor.selections.ranges::<Point>(cx),
6407 [
6408 Point::new(0, 2)..Point::new(0, 2),
6409 Point::new(1, 2)..Point::new(1, 2),
6410 Point::new(2, 2)..Point::new(2, 2)
6411 ]
6412 );
6413
6414 editor.delete_to_previous_word_start(&Default::default(), cx);
6415 assert_eq!(
6416 editor.text(cx),
6417 "
6418 a
6419 b
6420 c
6421 "
6422 .unindent()
6423 );
6424 assert_eq!(
6425 editor.selections.ranges::<Point>(cx),
6426 [
6427 Point::new(0, 1)..Point::new(0, 1),
6428 Point::new(1, 1)..Point::new(1, 1),
6429 Point::new(2, 1)..Point::new(2, 1)
6430 ]
6431 );
6432 });
6433}
6434
6435#[gpui::test]
6436async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut gpui::TestAppContext) {
6437 init_test(cx, |settings| {
6438 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6439 });
6440
6441 let mut cx = EditorTestContext::new(cx).await;
6442
6443 let language = Arc::new(Language::new(
6444 LanguageConfig {
6445 brackets: BracketPairConfig {
6446 pairs: vec![
6447 BracketPair {
6448 start: "{".to_string(),
6449 end: "}".to_string(),
6450 close: true,
6451 surround: true,
6452 newline: true,
6453 },
6454 BracketPair {
6455 start: "(".to_string(),
6456 end: ")".to_string(),
6457 close: true,
6458 surround: true,
6459 newline: true,
6460 },
6461 BracketPair {
6462 start: "[".to_string(),
6463 end: "]".to_string(),
6464 close: false,
6465 surround: true,
6466 newline: true,
6467 },
6468 ],
6469 ..Default::default()
6470 },
6471 autoclose_before: "})]".to_string(),
6472 ..Default::default()
6473 },
6474 Some(tree_sitter_rust::LANGUAGE.into()),
6475 ));
6476
6477 cx.language_registry().add(language.clone());
6478 cx.update_buffer(|buffer, cx| {
6479 buffer.set_language(Some(language), cx);
6480 });
6481
6482 cx.set_state(
6483 &"
6484 {(ˇ)}
6485 [[ˇ]]
6486 {(ˇ)}
6487 "
6488 .unindent(),
6489 );
6490
6491 cx.update_editor(|view, cx| {
6492 view.backspace(&Default::default(), cx);
6493 view.backspace(&Default::default(), cx);
6494 });
6495
6496 cx.assert_editor_state(
6497 &"
6498 ˇ
6499 ˇ]]
6500 ˇ
6501 "
6502 .unindent(),
6503 );
6504
6505 cx.update_editor(|view, cx| {
6506 view.handle_input("{", cx);
6507 view.handle_input("{", cx);
6508 view.move_right(&MoveRight, cx);
6509 view.move_right(&MoveRight, cx);
6510 view.move_left(&MoveLeft, cx);
6511 view.move_left(&MoveLeft, cx);
6512 view.backspace(&Default::default(), cx);
6513 });
6514
6515 cx.assert_editor_state(
6516 &"
6517 {ˇ}
6518 {ˇ}]]
6519 {ˇ}
6520 "
6521 .unindent(),
6522 );
6523
6524 cx.update_editor(|view, cx| {
6525 view.backspace(&Default::default(), cx);
6526 });
6527
6528 cx.assert_editor_state(
6529 &"
6530 ˇ
6531 ˇ]]
6532 ˇ
6533 "
6534 .unindent(),
6535 );
6536}
6537
6538#[gpui::test]
6539async fn test_auto_replace_emoji_shortcode(cx: &mut gpui::TestAppContext) {
6540 init_test(cx, |_| {});
6541
6542 let language = Arc::new(Language::new(
6543 LanguageConfig::default(),
6544 Some(tree_sitter_rust::LANGUAGE.into()),
6545 ));
6546
6547 let buffer = cx.new_model(|cx| Buffer::local("", cx).with_language(language, cx));
6548 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6549 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6550 editor
6551 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6552 .await;
6553
6554 editor.update(cx, |editor, cx| {
6555 editor.set_auto_replace_emoji_shortcode(true);
6556
6557 editor.handle_input("Hello ", cx);
6558 editor.handle_input(":wave", cx);
6559 assert_eq!(editor.text(cx), "Hello :wave".unindent());
6560
6561 editor.handle_input(":", cx);
6562 assert_eq!(editor.text(cx), "Hello 👋".unindent());
6563
6564 editor.handle_input(" :smile", cx);
6565 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
6566
6567 editor.handle_input(":", cx);
6568 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
6569
6570 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
6571 editor.handle_input(":wave", cx);
6572 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
6573
6574 editor.handle_input(":", cx);
6575 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
6576
6577 editor.handle_input(":1", cx);
6578 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
6579
6580 editor.handle_input(":", cx);
6581 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
6582
6583 // Ensure shortcode does not get replaced when it is part of a word
6584 editor.handle_input(" Test:wave", cx);
6585 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
6586
6587 editor.handle_input(":", cx);
6588 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
6589
6590 editor.set_auto_replace_emoji_shortcode(false);
6591
6592 // Ensure shortcode does not get replaced when auto replace is off
6593 editor.handle_input(" :wave", cx);
6594 assert_eq!(
6595 editor.text(cx),
6596 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
6597 );
6598
6599 editor.handle_input(":", cx);
6600 assert_eq!(
6601 editor.text(cx),
6602 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
6603 );
6604 });
6605}
6606
6607#[gpui::test]
6608async fn test_snippets(cx: &mut gpui::TestAppContext) {
6609 init_test(cx, |_| {});
6610
6611 let (text, insertion_ranges) = marked_text_ranges(
6612 indoc! {"
6613 a.ˇ b
6614 a.ˇ b
6615 a.ˇ b
6616 "},
6617 false,
6618 );
6619
6620 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
6621 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6622
6623 editor.update(cx, |editor, cx| {
6624 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
6625
6626 editor
6627 .insert_snippet(&insertion_ranges, snippet, cx)
6628 .unwrap();
6629
6630 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
6631 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
6632 assert_eq!(editor.text(cx), expected_text);
6633 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
6634 }
6635
6636 assert(
6637 editor,
6638 cx,
6639 indoc! {"
6640 a.f(«one», two, «three») b
6641 a.f(«one», two, «three») b
6642 a.f(«one», two, «three») b
6643 "},
6644 );
6645
6646 // Can't move earlier than the first tab stop
6647 assert!(!editor.move_to_prev_snippet_tabstop(cx));
6648 assert(
6649 editor,
6650 cx,
6651 indoc! {"
6652 a.f(«one», two, «three») b
6653 a.f(«one», two, «three») b
6654 a.f(«one», two, «three») b
6655 "},
6656 );
6657
6658 assert!(editor.move_to_next_snippet_tabstop(cx));
6659 assert(
6660 editor,
6661 cx,
6662 indoc! {"
6663 a.f(one, «two», three) b
6664 a.f(one, «two», three) b
6665 a.f(one, «two», three) b
6666 "},
6667 );
6668
6669 editor.move_to_prev_snippet_tabstop(cx);
6670 assert(
6671 editor,
6672 cx,
6673 indoc! {"
6674 a.f(«one», two, «three») b
6675 a.f(«one», two, «three») b
6676 a.f(«one», two, «three») b
6677 "},
6678 );
6679
6680 assert!(editor.move_to_next_snippet_tabstop(cx));
6681 assert(
6682 editor,
6683 cx,
6684 indoc! {"
6685 a.f(one, «two», three) b
6686 a.f(one, «two», three) b
6687 a.f(one, «two», three) b
6688 "},
6689 );
6690 assert!(editor.move_to_next_snippet_tabstop(cx));
6691 assert(
6692 editor,
6693 cx,
6694 indoc! {"
6695 a.f(one, two, three)ˇ b
6696 a.f(one, two, three)ˇ b
6697 a.f(one, two, three)ˇ b
6698 "},
6699 );
6700
6701 // As soon as the last tab stop is reached, snippet state is gone
6702 editor.move_to_prev_snippet_tabstop(cx);
6703 assert(
6704 editor,
6705 cx,
6706 indoc! {"
6707 a.f(one, two, three)ˇ b
6708 a.f(one, two, three)ˇ b
6709 a.f(one, two, three)ˇ b
6710 "},
6711 );
6712 });
6713}
6714
6715#[gpui::test]
6716async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
6717 init_test(cx, |_| {});
6718
6719 let fs = FakeFs::new(cx.executor());
6720 fs.insert_file("/file.rs", Default::default()).await;
6721
6722 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6723
6724 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6725 language_registry.add(rust_lang());
6726 let mut fake_servers = language_registry.register_fake_lsp(
6727 "Rust",
6728 FakeLspAdapter {
6729 capabilities: lsp::ServerCapabilities {
6730 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6731 ..Default::default()
6732 },
6733 ..Default::default()
6734 },
6735 );
6736
6737 let buffer = project
6738 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6739 .await
6740 .unwrap();
6741
6742 cx.executor().start_waiting();
6743 let fake_server = fake_servers.next().await.unwrap();
6744
6745 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6746 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6747 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6748 assert!(cx.read(|cx| editor.is_dirty(cx)));
6749
6750 let save = editor
6751 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6752 .unwrap();
6753 fake_server
6754 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6755 assert_eq!(
6756 params.text_document.uri,
6757 lsp::Url::from_file_path("/file.rs").unwrap()
6758 );
6759 assert_eq!(params.options.tab_size, 4);
6760 Ok(Some(vec![lsp::TextEdit::new(
6761 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6762 ", ".to_string(),
6763 )]))
6764 })
6765 .next()
6766 .await;
6767 cx.executor().start_waiting();
6768 save.await;
6769
6770 assert_eq!(
6771 editor.update(cx, |editor, cx| editor.text(cx)),
6772 "one, two\nthree\n"
6773 );
6774 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6775
6776 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6777 assert!(cx.read(|cx| editor.is_dirty(cx)));
6778
6779 // Ensure we can still save even if formatting hangs.
6780 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6781 assert_eq!(
6782 params.text_document.uri,
6783 lsp::Url::from_file_path("/file.rs").unwrap()
6784 );
6785 futures::future::pending::<()>().await;
6786 unreachable!()
6787 });
6788 let save = editor
6789 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6790 .unwrap();
6791 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6792 cx.executor().start_waiting();
6793 save.await;
6794 assert_eq!(
6795 editor.update(cx, |editor, cx| editor.text(cx)),
6796 "one\ntwo\nthree\n"
6797 );
6798 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6799
6800 // For non-dirty buffer, no formatting request should be sent
6801 let save = editor
6802 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6803 .unwrap();
6804 let _pending_format_request = fake_server
6805 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
6806 panic!("Should not be invoked on non-dirty buffer");
6807 })
6808 .next();
6809 cx.executor().start_waiting();
6810 save.await;
6811
6812 // Set rust language override and assert overridden tabsize is sent to language server
6813 update_test_language_settings(cx, |settings| {
6814 settings.languages.insert(
6815 "Rust".into(),
6816 LanguageSettingsContent {
6817 tab_size: NonZeroU32::new(8),
6818 ..Default::default()
6819 },
6820 );
6821 });
6822
6823 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
6824 assert!(cx.read(|cx| editor.is_dirty(cx)));
6825 let save = editor
6826 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6827 .unwrap();
6828 fake_server
6829 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6830 assert_eq!(
6831 params.text_document.uri,
6832 lsp::Url::from_file_path("/file.rs").unwrap()
6833 );
6834 assert_eq!(params.options.tab_size, 8);
6835 Ok(Some(vec![]))
6836 })
6837 .next()
6838 .await;
6839 cx.executor().start_waiting();
6840 save.await;
6841}
6842
6843#[gpui::test]
6844async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
6845 init_test(cx, |_| {});
6846
6847 let cols = 4;
6848 let rows = 10;
6849 let sample_text_1 = sample_text(rows, cols, 'a');
6850 assert_eq!(
6851 sample_text_1,
6852 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
6853 );
6854 let sample_text_2 = sample_text(rows, cols, 'l');
6855 assert_eq!(
6856 sample_text_2,
6857 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
6858 );
6859 let sample_text_3 = sample_text(rows, cols, 'v');
6860 assert_eq!(
6861 sample_text_3,
6862 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
6863 );
6864
6865 let fs = FakeFs::new(cx.executor());
6866 fs.insert_tree(
6867 "/a",
6868 json!({
6869 "main.rs": sample_text_1,
6870 "other.rs": sample_text_2,
6871 "lib.rs": sample_text_3,
6872 }),
6873 )
6874 .await;
6875
6876 let project = Project::test(fs, ["/a".as_ref()], cx).await;
6877 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
6878 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
6879
6880 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6881 language_registry.add(rust_lang());
6882 let mut fake_servers = language_registry.register_fake_lsp(
6883 "Rust",
6884 FakeLspAdapter {
6885 capabilities: lsp::ServerCapabilities {
6886 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6887 ..Default::default()
6888 },
6889 ..Default::default()
6890 },
6891 );
6892
6893 let worktree = project.update(cx, |project, cx| {
6894 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
6895 assert_eq!(worktrees.len(), 1);
6896 worktrees.pop().unwrap()
6897 });
6898 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
6899
6900 let buffer_1 = project
6901 .update(cx, |project, cx| {
6902 project.open_buffer((worktree_id, "main.rs"), cx)
6903 })
6904 .await
6905 .unwrap();
6906 let buffer_2 = project
6907 .update(cx, |project, cx| {
6908 project.open_buffer((worktree_id, "other.rs"), cx)
6909 })
6910 .await
6911 .unwrap();
6912 let buffer_3 = project
6913 .update(cx, |project, cx| {
6914 project.open_buffer((worktree_id, "lib.rs"), cx)
6915 })
6916 .await
6917 .unwrap();
6918
6919 let multi_buffer = cx.new_model(|cx| {
6920 let mut multi_buffer = MultiBuffer::new(ReadWrite);
6921 multi_buffer.push_excerpts(
6922 buffer_1.clone(),
6923 [
6924 ExcerptRange {
6925 context: Point::new(0, 0)..Point::new(3, 0),
6926 primary: None,
6927 },
6928 ExcerptRange {
6929 context: Point::new(5, 0)..Point::new(7, 0),
6930 primary: None,
6931 },
6932 ExcerptRange {
6933 context: Point::new(9, 0)..Point::new(10, 4),
6934 primary: None,
6935 },
6936 ],
6937 cx,
6938 );
6939 multi_buffer.push_excerpts(
6940 buffer_2.clone(),
6941 [
6942 ExcerptRange {
6943 context: Point::new(0, 0)..Point::new(3, 0),
6944 primary: None,
6945 },
6946 ExcerptRange {
6947 context: Point::new(5, 0)..Point::new(7, 0),
6948 primary: None,
6949 },
6950 ExcerptRange {
6951 context: Point::new(9, 0)..Point::new(10, 4),
6952 primary: None,
6953 },
6954 ],
6955 cx,
6956 );
6957 multi_buffer.push_excerpts(
6958 buffer_3.clone(),
6959 [
6960 ExcerptRange {
6961 context: Point::new(0, 0)..Point::new(3, 0),
6962 primary: None,
6963 },
6964 ExcerptRange {
6965 context: Point::new(5, 0)..Point::new(7, 0),
6966 primary: None,
6967 },
6968 ExcerptRange {
6969 context: Point::new(9, 0)..Point::new(10, 4),
6970 primary: None,
6971 },
6972 ],
6973 cx,
6974 );
6975 multi_buffer
6976 });
6977 let multi_buffer_editor = cx.new_view(|cx| {
6978 Editor::new(
6979 EditorMode::Full,
6980 multi_buffer,
6981 Some(project.clone()),
6982 true,
6983 cx,
6984 )
6985 });
6986
6987 multi_buffer_editor.update(cx, |editor, cx| {
6988 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
6989 editor.insert("|one|two|three|", cx);
6990 });
6991 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
6992 multi_buffer_editor.update(cx, |editor, cx| {
6993 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
6994 s.select_ranges(Some(60..70))
6995 });
6996 editor.insert("|four|five|six|", cx);
6997 });
6998 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
6999
7000 // First two buffers should be edited, but not the third one.
7001 assert_eq!(
7002 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7003 "a|one|two|three|aa\nbbbb\ncccc\n\nffff\ngggg\n\njjjj\nllll\nmmmm\nnnnn|four|five|six|\nr\n\nuuuu\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}",
7004 );
7005 buffer_1.update(cx, |buffer, _| {
7006 assert!(buffer.is_dirty());
7007 assert_eq!(
7008 buffer.text(),
7009 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
7010 )
7011 });
7012 buffer_2.update(cx, |buffer, _| {
7013 assert!(buffer.is_dirty());
7014 assert_eq!(
7015 buffer.text(),
7016 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
7017 )
7018 });
7019 buffer_3.update(cx, |buffer, _| {
7020 assert!(!buffer.is_dirty());
7021 assert_eq!(buffer.text(), sample_text_3,)
7022 });
7023
7024 cx.executor().start_waiting();
7025 let save = multi_buffer_editor
7026 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7027 .unwrap();
7028
7029 let fake_server = fake_servers.next().await.unwrap();
7030 fake_server
7031 .server
7032 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7033 Ok(Some(vec![lsp::TextEdit::new(
7034 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7035 format!("[{} formatted]", params.text_document.uri),
7036 )]))
7037 })
7038 .detach();
7039 save.await;
7040
7041 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
7042 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
7043 assert_eq!(
7044 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7045 "a|o[file:///a/main.rs formatted]bbbb\ncccc\n\nffff\ngggg\n\njjjj\n\nlll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|\nr\n\nuuuu\n\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}",
7046 );
7047 buffer_1.update(cx, |buffer, _| {
7048 assert!(!buffer.is_dirty());
7049 assert_eq!(
7050 buffer.text(),
7051 "a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n",
7052 )
7053 });
7054 buffer_2.update(cx, |buffer, _| {
7055 assert!(!buffer.is_dirty());
7056 assert_eq!(
7057 buffer.text(),
7058 "lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n",
7059 )
7060 });
7061 buffer_3.update(cx, |buffer, _| {
7062 assert!(!buffer.is_dirty());
7063 assert_eq!(buffer.text(), sample_text_3,)
7064 });
7065}
7066
7067#[gpui::test]
7068async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
7069 init_test(cx, |_| {});
7070
7071 let fs = FakeFs::new(cx.executor());
7072 fs.insert_file("/file.rs", Default::default()).await;
7073
7074 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7075
7076 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7077 language_registry.add(rust_lang());
7078 let mut fake_servers = language_registry.register_fake_lsp(
7079 "Rust",
7080 FakeLspAdapter {
7081 capabilities: lsp::ServerCapabilities {
7082 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
7083 ..Default::default()
7084 },
7085 ..Default::default()
7086 },
7087 );
7088
7089 let buffer = project
7090 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
7091 .await
7092 .unwrap();
7093
7094 cx.executor().start_waiting();
7095 let fake_server = fake_servers.next().await.unwrap();
7096
7097 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
7098 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
7099 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7100 assert!(cx.read(|cx| editor.is_dirty(cx)));
7101
7102 let save = editor
7103 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7104 .unwrap();
7105 fake_server
7106 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7107 assert_eq!(
7108 params.text_document.uri,
7109 lsp::Url::from_file_path("/file.rs").unwrap()
7110 );
7111 assert_eq!(params.options.tab_size, 4);
7112 Ok(Some(vec![lsp::TextEdit::new(
7113 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7114 ", ".to_string(),
7115 )]))
7116 })
7117 .next()
7118 .await;
7119 cx.executor().start_waiting();
7120 save.await;
7121 assert_eq!(
7122 editor.update(cx, |editor, cx| editor.text(cx)),
7123 "one, two\nthree\n"
7124 );
7125 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7126
7127 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7128 assert!(cx.read(|cx| editor.is_dirty(cx)));
7129
7130 // Ensure we can still save even if formatting hangs.
7131 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
7132 move |params, _| async move {
7133 assert_eq!(
7134 params.text_document.uri,
7135 lsp::Url::from_file_path("/file.rs").unwrap()
7136 );
7137 futures::future::pending::<()>().await;
7138 unreachable!()
7139 },
7140 );
7141 let save = editor
7142 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7143 .unwrap();
7144 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7145 cx.executor().start_waiting();
7146 save.await;
7147 assert_eq!(
7148 editor.update(cx, |editor, cx| editor.text(cx)),
7149 "one\ntwo\nthree\n"
7150 );
7151 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7152
7153 // For non-dirty buffer, no formatting request should be sent
7154 let save = editor
7155 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7156 .unwrap();
7157 let _pending_format_request = fake_server
7158 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7159 panic!("Should not be invoked on non-dirty buffer");
7160 })
7161 .next();
7162 cx.executor().start_waiting();
7163 save.await;
7164
7165 // Set Rust language override and assert overridden tabsize is sent to language server
7166 update_test_language_settings(cx, |settings| {
7167 settings.languages.insert(
7168 "Rust".into(),
7169 LanguageSettingsContent {
7170 tab_size: NonZeroU32::new(8),
7171 ..Default::default()
7172 },
7173 );
7174 });
7175
7176 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
7177 assert!(cx.read(|cx| editor.is_dirty(cx)));
7178 let save = editor
7179 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7180 .unwrap();
7181 fake_server
7182 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7183 assert_eq!(
7184 params.text_document.uri,
7185 lsp::Url::from_file_path("/file.rs").unwrap()
7186 );
7187 assert_eq!(params.options.tab_size, 8);
7188 Ok(Some(vec![]))
7189 })
7190 .next()
7191 .await;
7192 cx.executor().start_waiting();
7193 save.await;
7194}
7195
7196#[gpui::test]
7197async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
7198 init_test(cx, |settings| {
7199 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
7200 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
7201 ))
7202 });
7203
7204 let fs = FakeFs::new(cx.executor());
7205 fs.insert_file("/file.rs", Default::default()).await;
7206
7207 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7208
7209 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7210 language_registry.add(Arc::new(Language::new(
7211 LanguageConfig {
7212 name: "Rust".into(),
7213 matcher: LanguageMatcher {
7214 path_suffixes: vec!["rs".to_string()],
7215 ..Default::default()
7216 },
7217 ..LanguageConfig::default()
7218 },
7219 Some(tree_sitter_rust::LANGUAGE.into()),
7220 )));
7221 update_test_language_settings(cx, |settings| {
7222 // Enable Prettier formatting for the same buffer, and ensure
7223 // LSP is called instead of Prettier.
7224 settings.defaults.prettier = Some(PrettierSettings {
7225 allowed: true,
7226 ..PrettierSettings::default()
7227 });
7228 });
7229 let mut fake_servers = language_registry.register_fake_lsp(
7230 "Rust",
7231 FakeLspAdapter {
7232 capabilities: lsp::ServerCapabilities {
7233 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7234 ..Default::default()
7235 },
7236 ..Default::default()
7237 },
7238 );
7239
7240 let buffer = project
7241 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
7242 .await
7243 .unwrap();
7244
7245 cx.executor().start_waiting();
7246 let fake_server = fake_servers.next().await.unwrap();
7247
7248 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
7249 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
7250 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7251
7252 let format = editor
7253 .update(cx, |editor, cx| {
7254 editor.perform_format(
7255 project.clone(),
7256 FormatTrigger::Manual,
7257 FormatTarget::Buffer,
7258 cx,
7259 )
7260 })
7261 .unwrap();
7262 fake_server
7263 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7264 assert_eq!(
7265 params.text_document.uri,
7266 lsp::Url::from_file_path("/file.rs").unwrap()
7267 );
7268 assert_eq!(params.options.tab_size, 4);
7269 Ok(Some(vec![lsp::TextEdit::new(
7270 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7271 ", ".to_string(),
7272 )]))
7273 })
7274 .next()
7275 .await;
7276 cx.executor().start_waiting();
7277 format.await;
7278 assert_eq!(
7279 editor.update(cx, |editor, cx| editor.text(cx)),
7280 "one, two\nthree\n"
7281 );
7282
7283 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7284 // Ensure we don't lock if formatting hangs.
7285 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7286 assert_eq!(
7287 params.text_document.uri,
7288 lsp::Url::from_file_path("/file.rs").unwrap()
7289 );
7290 futures::future::pending::<()>().await;
7291 unreachable!()
7292 });
7293 let format = editor
7294 .update(cx, |editor, cx| {
7295 editor.perform_format(project, FormatTrigger::Manual, FormatTarget::Buffer, cx)
7296 })
7297 .unwrap();
7298 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7299 cx.executor().start_waiting();
7300 format.await;
7301 assert_eq!(
7302 editor.update(cx, |editor, cx| editor.text(cx)),
7303 "one\ntwo\nthree\n"
7304 );
7305}
7306
7307#[gpui::test]
7308async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
7309 init_test(cx, |_| {});
7310
7311 let mut cx = EditorLspTestContext::new_rust(
7312 lsp::ServerCapabilities {
7313 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7314 ..Default::default()
7315 },
7316 cx,
7317 )
7318 .await;
7319
7320 cx.set_state(indoc! {"
7321 one.twoˇ
7322 "});
7323
7324 // The format request takes a long time. When it completes, it inserts
7325 // a newline and an indent before the `.`
7326 cx.lsp
7327 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
7328 let executor = cx.background_executor().clone();
7329 async move {
7330 executor.timer(Duration::from_millis(100)).await;
7331 Ok(Some(vec![lsp::TextEdit {
7332 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
7333 new_text: "\n ".into(),
7334 }]))
7335 }
7336 });
7337
7338 // Submit a format request.
7339 let format_1 = cx
7340 .update_editor(|editor, cx| editor.format(&Format, cx))
7341 .unwrap();
7342 cx.executor().run_until_parked();
7343
7344 // Submit a second format request.
7345 let format_2 = cx
7346 .update_editor(|editor, cx| editor.format(&Format, cx))
7347 .unwrap();
7348 cx.executor().run_until_parked();
7349
7350 // Wait for both format requests to complete
7351 cx.executor().advance_clock(Duration::from_millis(200));
7352 cx.executor().start_waiting();
7353 format_1.await.unwrap();
7354 cx.executor().start_waiting();
7355 format_2.await.unwrap();
7356
7357 // The formatting edits only happens once.
7358 cx.assert_editor_state(indoc! {"
7359 one
7360 .twoˇ
7361 "});
7362}
7363
7364#[gpui::test]
7365async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
7366 init_test(cx, |settings| {
7367 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
7368 });
7369
7370 let mut cx = EditorLspTestContext::new_rust(
7371 lsp::ServerCapabilities {
7372 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7373 ..Default::default()
7374 },
7375 cx,
7376 )
7377 .await;
7378
7379 // Set up a buffer white some trailing whitespace and no trailing newline.
7380 cx.set_state(
7381 &[
7382 "one ", //
7383 "twoˇ", //
7384 "three ", //
7385 "four", //
7386 ]
7387 .join("\n"),
7388 );
7389
7390 // Submit a format request.
7391 let format = cx
7392 .update_editor(|editor, cx| editor.format(&Format, cx))
7393 .unwrap();
7394
7395 // Record which buffer changes have been sent to the language server
7396 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
7397 cx.lsp
7398 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
7399 let buffer_changes = buffer_changes.clone();
7400 move |params, _| {
7401 buffer_changes.lock().extend(
7402 params
7403 .content_changes
7404 .into_iter()
7405 .map(|e| (e.range.unwrap(), e.text)),
7406 );
7407 }
7408 });
7409
7410 // Handle formatting requests to the language server.
7411 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
7412 let buffer_changes = buffer_changes.clone();
7413 move |_, _| {
7414 // When formatting is requested, trailing whitespace has already been stripped,
7415 // and the trailing newline has already been added.
7416 assert_eq!(
7417 &buffer_changes.lock()[1..],
7418 &[
7419 (
7420 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
7421 "".into()
7422 ),
7423 (
7424 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
7425 "".into()
7426 ),
7427 (
7428 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
7429 "\n".into()
7430 ),
7431 ]
7432 );
7433
7434 // Insert blank lines between each line of the buffer.
7435 async move {
7436 Ok(Some(vec![
7437 lsp::TextEdit {
7438 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
7439 new_text: "\n".into(),
7440 },
7441 lsp::TextEdit {
7442 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
7443 new_text: "\n".into(),
7444 },
7445 ]))
7446 }
7447 }
7448 });
7449
7450 // After formatting the buffer, the trailing whitespace is stripped,
7451 // a newline is appended, and the edits provided by the language server
7452 // have been applied.
7453 format.await.unwrap();
7454 cx.assert_editor_state(
7455 &[
7456 "one", //
7457 "", //
7458 "twoˇ", //
7459 "", //
7460 "three", //
7461 "four", //
7462 "", //
7463 ]
7464 .join("\n"),
7465 );
7466
7467 // Undoing the formatting undoes the trailing whitespace removal, the
7468 // trailing newline, and the LSP edits.
7469 cx.update_buffer(|buffer, cx| buffer.undo(cx));
7470 cx.assert_editor_state(
7471 &[
7472 "one ", //
7473 "twoˇ", //
7474 "three ", //
7475 "four", //
7476 ]
7477 .join("\n"),
7478 );
7479}
7480
7481#[gpui::test]
7482async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
7483 cx: &mut gpui::TestAppContext,
7484) {
7485 init_test(cx, |_| {});
7486
7487 cx.update(|cx| {
7488 cx.update_global::<SettingsStore, _>(|settings, cx| {
7489 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7490 settings.auto_signature_help = Some(true);
7491 });
7492 });
7493 });
7494
7495 let mut cx = EditorLspTestContext::new_rust(
7496 lsp::ServerCapabilities {
7497 signature_help_provider: Some(lsp::SignatureHelpOptions {
7498 ..Default::default()
7499 }),
7500 ..Default::default()
7501 },
7502 cx,
7503 )
7504 .await;
7505
7506 let language = Language::new(
7507 LanguageConfig {
7508 name: "Rust".into(),
7509 brackets: BracketPairConfig {
7510 pairs: vec![
7511 BracketPair {
7512 start: "{".to_string(),
7513 end: "}".to_string(),
7514 close: true,
7515 surround: true,
7516 newline: true,
7517 },
7518 BracketPair {
7519 start: "(".to_string(),
7520 end: ")".to_string(),
7521 close: true,
7522 surround: true,
7523 newline: true,
7524 },
7525 BracketPair {
7526 start: "/*".to_string(),
7527 end: " */".to_string(),
7528 close: true,
7529 surround: true,
7530 newline: true,
7531 },
7532 BracketPair {
7533 start: "[".to_string(),
7534 end: "]".to_string(),
7535 close: false,
7536 surround: false,
7537 newline: true,
7538 },
7539 BracketPair {
7540 start: "\"".to_string(),
7541 end: "\"".to_string(),
7542 close: true,
7543 surround: true,
7544 newline: false,
7545 },
7546 BracketPair {
7547 start: "<".to_string(),
7548 end: ">".to_string(),
7549 close: false,
7550 surround: true,
7551 newline: true,
7552 },
7553 ],
7554 ..Default::default()
7555 },
7556 autoclose_before: "})]".to_string(),
7557 ..Default::default()
7558 },
7559 Some(tree_sitter_rust::LANGUAGE.into()),
7560 );
7561 let language = Arc::new(language);
7562
7563 cx.language_registry().add(language.clone());
7564 cx.update_buffer(|buffer, cx| {
7565 buffer.set_language(Some(language), cx);
7566 });
7567
7568 cx.set_state(
7569 &r#"
7570 fn main() {
7571 sampleˇ
7572 }
7573 "#
7574 .unindent(),
7575 );
7576
7577 cx.update_editor(|view, cx| {
7578 view.handle_input("(", cx);
7579 });
7580 cx.assert_editor_state(
7581 &"
7582 fn main() {
7583 sample(ˇ)
7584 }
7585 "
7586 .unindent(),
7587 );
7588
7589 let mocked_response = lsp::SignatureHelp {
7590 signatures: vec![lsp::SignatureInformation {
7591 label: "fn sample(param1: u8, param2: u8)".to_string(),
7592 documentation: None,
7593 parameters: Some(vec![
7594 lsp::ParameterInformation {
7595 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7596 documentation: None,
7597 },
7598 lsp::ParameterInformation {
7599 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7600 documentation: None,
7601 },
7602 ]),
7603 active_parameter: None,
7604 }],
7605 active_signature: Some(0),
7606 active_parameter: Some(0),
7607 };
7608 handle_signature_help_request(&mut cx, mocked_response).await;
7609
7610 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7611 .await;
7612
7613 cx.editor(|editor, _| {
7614 let signature_help_state = editor.signature_help_state.popover().cloned();
7615 assert!(signature_help_state.is_some());
7616 let ParsedMarkdown {
7617 text, highlights, ..
7618 } = signature_help_state.unwrap().parsed_content;
7619 assert_eq!(text, "param1: u8, param2: u8");
7620 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7621 });
7622}
7623
7624#[gpui::test]
7625async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui::TestAppContext) {
7626 init_test(cx, |_| {});
7627
7628 cx.update(|cx| {
7629 cx.update_global::<SettingsStore, _>(|settings, cx| {
7630 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7631 settings.auto_signature_help = Some(false);
7632 settings.show_signature_help_after_edits = Some(false);
7633 });
7634 });
7635 });
7636
7637 let mut cx = EditorLspTestContext::new_rust(
7638 lsp::ServerCapabilities {
7639 signature_help_provider: Some(lsp::SignatureHelpOptions {
7640 ..Default::default()
7641 }),
7642 ..Default::default()
7643 },
7644 cx,
7645 )
7646 .await;
7647
7648 let language = Language::new(
7649 LanguageConfig {
7650 name: "Rust".into(),
7651 brackets: BracketPairConfig {
7652 pairs: vec![
7653 BracketPair {
7654 start: "{".to_string(),
7655 end: "}".to_string(),
7656 close: true,
7657 surround: true,
7658 newline: true,
7659 },
7660 BracketPair {
7661 start: "(".to_string(),
7662 end: ")".to_string(),
7663 close: true,
7664 surround: true,
7665 newline: true,
7666 },
7667 BracketPair {
7668 start: "/*".to_string(),
7669 end: " */".to_string(),
7670 close: true,
7671 surround: true,
7672 newline: true,
7673 },
7674 BracketPair {
7675 start: "[".to_string(),
7676 end: "]".to_string(),
7677 close: false,
7678 surround: false,
7679 newline: true,
7680 },
7681 BracketPair {
7682 start: "\"".to_string(),
7683 end: "\"".to_string(),
7684 close: true,
7685 surround: true,
7686 newline: false,
7687 },
7688 BracketPair {
7689 start: "<".to_string(),
7690 end: ">".to_string(),
7691 close: false,
7692 surround: true,
7693 newline: true,
7694 },
7695 ],
7696 ..Default::default()
7697 },
7698 autoclose_before: "})]".to_string(),
7699 ..Default::default()
7700 },
7701 Some(tree_sitter_rust::LANGUAGE.into()),
7702 );
7703 let language = Arc::new(language);
7704
7705 cx.language_registry().add(language.clone());
7706 cx.update_buffer(|buffer, cx| {
7707 buffer.set_language(Some(language), cx);
7708 });
7709
7710 // Ensure that signature_help is not called when no signature help is enabled.
7711 cx.set_state(
7712 &r#"
7713 fn main() {
7714 sampleˇ
7715 }
7716 "#
7717 .unindent(),
7718 );
7719 cx.update_editor(|view, cx| {
7720 view.handle_input("(", cx);
7721 });
7722 cx.assert_editor_state(
7723 &"
7724 fn main() {
7725 sample(ˇ)
7726 }
7727 "
7728 .unindent(),
7729 );
7730 cx.editor(|editor, _| {
7731 assert!(editor.signature_help_state.task().is_none());
7732 });
7733
7734 let mocked_response = lsp::SignatureHelp {
7735 signatures: vec![lsp::SignatureInformation {
7736 label: "fn sample(param1: u8, param2: u8)".to_string(),
7737 documentation: None,
7738 parameters: Some(vec![
7739 lsp::ParameterInformation {
7740 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7741 documentation: None,
7742 },
7743 lsp::ParameterInformation {
7744 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7745 documentation: None,
7746 },
7747 ]),
7748 active_parameter: None,
7749 }],
7750 active_signature: Some(0),
7751 active_parameter: Some(0),
7752 };
7753
7754 // Ensure that signature_help is called when enabled afte edits
7755 cx.update(|cx| {
7756 cx.update_global::<SettingsStore, _>(|settings, cx| {
7757 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7758 settings.auto_signature_help = Some(false);
7759 settings.show_signature_help_after_edits = Some(true);
7760 });
7761 });
7762 });
7763 cx.set_state(
7764 &r#"
7765 fn main() {
7766 sampleˇ
7767 }
7768 "#
7769 .unindent(),
7770 );
7771 cx.update_editor(|view, cx| {
7772 view.handle_input("(", cx);
7773 });
7774 cx.assert_editor_state(
7775 &"
7776 fn main() {
7777 sample(ˇ)
7778 }
7779 "
7780 .unindent(),
7781 );
7782 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7783 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7784 .await;
7785 cx.update_editor(|editor, _| {
7786 let signature_help_state = editor.signature_help_state.popover().cloned();
7787 assert!(signature_help_state.is_some());
7788 let ParsedMarkdown {
7789 text, highlights, ..
7790 } = signature_help_state.unwrap().parsed_content;
7791 assert_eq!(text, "param1: u8, param2: u8");
7792 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7793 editor.signature_help_state = SignatureHelpState::default();
7794 });
7795
7796 // Ensure that signature_help is called when auto signature help override is enabled
7797 cx.update(|cx| {
7798 cx.update_global::<SettingsStore, _>(|settings, cx| {
7799 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7800 settings.auto_signature_help = Some(true);
7801 settings.show_signature_help_after_edits = Some(false);
7802 });
7803 });
7804 });
7805 cx.set_state(
7806 &r#"
7807 fn main() {
7808 sampleˇ
7809 }
7810 "#
7811 .unindent(),
7812 );
7813 cx.update_editor(|view, cx| {
7814 view.handle_input("(", cx);
7815 });
7816 cx.assert_editor_state(
7817 &"
7818 fn main() {
7819 sample(ˇ)
7820 }
7821 "
7822 .unindent(),
7823 );
7824 handle_signature_help_request(&mut cx, mocked_response).await;
7825 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7826 .await;
7827 cx.editor(|editor, _| {
7828 let signature_help_state = editor.signature_help_state.popover().cloned();
7829 assert!(signature_help_state.is_some());
7830 let ParsedMarkdown {
7831 text, highlights, ..
7832 } = signature_help_state.unwrap().parsed_content;
7833 assert_eq!(text, "param1: u8, param2: u8");
7834 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7835 });
7836}
7837
7838#[gpui::test]
7839async fn test_signature_help(cx: &mut gpui::TestAppContext) {
7840 init_test(cx, |_| {});
7841 cx.update(|cx| {
7842 cx.update_global::<SettingsStore, _>(|settings, cx| {
7843 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7844 settings.auto_signature_help = Some(true);
7845 });
7846 });
7847 });
7848
7849 let mut cx = EditorLspTestContext::new_rust(
7850 lsp::ServerCapabilities {
7851 signature_help_provider: Some(lsp::SignatureHelpOptions {
7852 ..Default::default()
7853 }),
7854 ..Default::default()
7855 },
7856 cx,
7857 )
7858 .await;
7859
7860 // A test that directly calls `show_signature_help`
7861 cx.update_editor(|editor, cx| {
7862 editor.show_signature_help(&ShowSignatureHelp, cx);
7863 });
7864
7865 let mocked_response = lsp::SignatureHelp {
7866 signatures: vec![lsp::SignatureInformation {
7867 label: "fn sample(param1: u8, param2: u8)".to_string(),
7868 documentation: None,
7869 parameters: Some(vec![
7870 lsp::ParameterInformation {
7871 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7872 documentation: None,
7873 },
7874 lsp::ParameterInformation {
7875 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7876 documentation: None,
7877 },
7878 ]),
7879 active_parameter: None,
7880 }],
7881 active_signature: Some(0),
7882 active_parameter: Some(0),
7883 };
7884 handle_signature_help_request(&mut cx, mocked_response).await;
7885
7886 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7887 .await;
7888
7889 cx.editor(|editor, _| {
7890 let signature_help_state = editor.signature_help_state.popover().cloned();
7891 assert!(signature_help_state.is_some());
7892 let ParsedMarkdown {
7893 text, highlights, ..
7894 } = signature_help_state.unwrap().parsed_content;
7895 assert_eq!(text, "param1: u8, param2: u8");
7896 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7897 });
7898
7899 // When exiting outside from inside the brackets, `signature_help` is closed.
7900 cx.set_state(indoc! {"
7901 fn main() {
7902 sample(ˇ);
7903 }
7904
7905 fn sample(param1: u8, param2: u8) {}
7906 "});
7907
7908 cx.update_editor(|editor, cx| {
7909 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
7910 });
7911
7912 let mocked_response = lsp::SignatureHelp {
7913 signatures: Vec::new(),
7914 active_signature: None,
7915 active_parameter: None,
7916 };
7917 handle_signature_help_request(&mut cx, mocked_response).await;
7918
7919 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
7920 .await;
7921
7922 cx.editor(|editor, _| {
7923 assert!(!editor.signature_help_state.is_shown());
7924 });
7925
7926 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
7927 cx.set_state(indoc! {"
7928 fn main() {
7929 sample(ˇ);
7930 }
7931
7932 fn sample(param1: u8, param2: u8) {}
7933 "});
7934
7935 let mocked_response = lsp::SignatureHelp {
7936 signatures: vec![lsp::SignatureInformation {
7937 label: "fn sample(param1: u8, param2: u8)".to_string(),
7938 documentation: None,
7939 parameters: Some(vec![
7940 lsp::ParameterInformation {
7941 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7942 documentation: None,
7943 },
7944 lsp::ParameterInformation {
7945 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7946 documentation: None,
7947 },
7948 ]),
7949 active_parameter: None,
7950 }],
7951 active_signature: Some(0),
7952 active_parameter: Some(0),
7953 };
7954 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7955 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7956 .await;
7957 cx.editor(|editor, _| {
7958 assert!(editor.signature_help_state.is_shown());
7959 });
7960
7961 // Restore the popover with more parameter input
7962 cx.set_state(indoc! {"
7963 fn main() {
7964 sample(param1, param2ˇ);
7965 }
7966
7967 fn sample(param1: u8, param2: u8) {}
7968 "});
7969
7970 let mocked_response = lsp::SignatureHelp {
7971 signatures: vec![lsp::SignatureInformation {
7972 label: "fn sample(param1: u8, param2: u8)".to_string(),
7973 documentation: None,
7974 parameters: Some(vec![
7975 lsp::ParameterInformation {
7976 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7977 documentation: None,
7978 },
7979 lsp::ParameterInformation {
7980 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7981 documentation: None,
7982 },
7983 ]),
7984 active_parameter: None,
7985 }],
7986 active_signature: Some(0),
7987 active_parameter: Some(1),
7988 };
7989 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7990 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7991 .await;
7992
7993 // When selecting a range, the popover is gone.
7994 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
7995 cx.update_editor(|editor, cx| {
7996 editor.change_selections(None, cx, |s| {
7997 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
7998 })
7999 });
8000 cx.assert_editor_state(indoc! {"
8001 fn main() {
8002 sample(param1, «ˇparam2»);
8003 }
8004
8005 fn sample(param1: u8, param2: u8) {}
8006 "});
8007 cx.editor(|editor, _| {
8008 assert!(!editor.signature_help_state.is_shown());
8009 });
8010
8011 // When unselecting again, the popover is back if within the brackets.
8012 cx.update_editor(|editor, cx| {
8013 editor.change_selections(None, cx, |s| {
8014 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8015 })
8016 });
8017 cx.assert_editor_state(indoc! {"
8018 fn main() {
8019 sample(param1, ˇparam2);
8020 }
8021
8022 fn sample(param1: u8, param2: u8) {}
8023 "});
8024 handle_signature_help_request(&mut cx, mocked_response).await;
8025 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8026 .await;
8027 cx.editor(|editor, _| {
8028 assert!(editor.signature_help_state.is_shown());
8029 });
8030
8031 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
8032 cx.update_editor(|editor, cx| {
8033 editor.change_selections(None, cx, |s| {
8034 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
8035 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8036 })
8037 });
8038 cx.assert_editor_state(indoc! {"
8039 fn main() {
8040 sample(param1, ˇparam2);
8041 }
8042
8043 fn sample(param1: u8, param2: u8) {}
8044 "});
8045
8046 let mocked_response = lsp::SignatureHelp {
8047 signatures: vec![lsp::SignatureInformation {
8048 label: "fn sample(param1: u8, param2: u8)".to_string(),
8049 documentation: None,
8050 parameters: Some(vec![
8051 lsp::ParameterInformation {
8052 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8053 documentation: None,
8054 },
8055 lsp::ParameterInformation {
8056 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8057 documentation: None,
8058 },
8059 ]),
8060 active_parameter: None,
8061 }],
8062 active_signature: Some(0),
8063 active_parameter: Some(1),
8064 };
8065 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8066 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8067 .await;
8068 cx.update_editor(|editor, cx| {
8069 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
8070 });
8071 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8072 .await;
8073 cx.update_editor(|editor, cx| {
8074 editor.change_selections(None, cx, |s| {
8075 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8076 })
8077 });
8078 cx.assert_editor_state(indoc! {"
8079 fn main() {
8080 sample(param1, «ˇparam2»);
8081 }
8082
8083 fn sample(param1: u8, param2: u8) {}
8084 "});
8085 cx.update_editor(|editor, cx| {
8086 editor.change_selections(None, cx, |s| {
8087 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8088 })
8089 });
8090 cx.assert_editor_state(indoc! {"
8091 fn main() {
8092 sample(param1, ˇparam2);
8093 }
8094
8095 fn sample(param1: u8, param2: u8) {}
8096 "});
8097 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
8098 .await;
8099}
8100
8101#[gpui::test]
8102async fn test_completion(cx: &mut gpui::TestAppContext) {
8103 init_test(cx, |_| {});
8104
8105 let mut cx = EditorLspTestContext::new_rust(
8106 lsp::ServerCapabilities {
8107 completion_provider: Some(lsp::CompletionOptions {
8108 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
8109 resolve_provider: Some(true),
8110 ..Default::default()
8111 }),
8112 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
8113 ..Default::default()
8114 },
8115 cx,
8116 )
8117 .await;
8118 let counter = Arc::new(AtomicUsize::new(0));
8119
8120 cx.set_state(indoc! {"
8121 oneˇ
8122 two
8123 three
8124 "});
8125 cx.simulate_keystroke(".");
8126 handle_completion_request(
8127 &mut cx,
8128 indoc! {"
8129 one.|<>
8130 two
8131 three
8132 "},
8133 vec!["first_completion", "second_completion"],
8134 counter.clone(),
8135 )
8136 .await;
8137 cx.condition(|editor, _| editor.context_menu_visible())
8138 .await;
8139 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8140
8141 let _handler = handle_signature_help_request(
8142 &mut cx,
8143 lsp::SignatureHelp {
8144 signatures: vec![lsp::SignatureInformation {
8145 label: "test signature".to_string(),
8146 documentation: None,
8147 parameters: Some(vec![lsp::ParameterInformation {
8148 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
8149 documentation: None,
8150 }]),
8151 active_parameter: None,
8152 }],
8153 active_signature: None,
8154 active_parameter: None,
8155 },
8156 );
8157 cx.update_editor(|editor, cx| {
8158 assert!(
8159 !editor.signature_help_state.is_shown(),
8160 "No signature help was called for"
8161 );
8162 editor.show_signature_help(&ShowSignatureHelp, cx);
8163 });
8164 cx.run_until_parked();
8165 cx.update_editor(|editor, _| {
8166 assert!(
8167 !editor.signature_help_state.is_shown(),
8168 "No signature help should be shown when completions menu is open"
8169 );
8170 });
8171
8172 let apply_additional_edits = cx.update_editor(|editor, cx| {
8173 editor.context_menu_next(&Default::default(), cx);
8174 editor
8175 .confirm_completion(&ConfirmCompletion::default(), cx)
8176 .unwrap()
8177 });
8178 cx.assert_editor_state(indoc! {"
8179 one.second_completionˇ
8180 two
8181 three
8182 "});
8183
8184 handle_resolve_completion_request(
8185 &mut cx,
8186 Some(vec![
8187 (
8188 //This overlaps with the primary completion edit which is
8189 //misbehavior from the LSP spec, test that we filter it out
8190 indoc! {"
8191 one.second_ˇcompletion
8192 two
8193 threeˇ
8194 "},
8195 "overlapping additional edit",
8196 ),
8197 (
8198 indoc! {"
8199 one.second_completion
8200 two
8201 threeˇ
8202 "},
8203 "\nadditional edit",
8204 ),
8205 ]),
8206 )
8207 .await;
8208 apply_additional_edits.await.unwrap();
8209 cx.assert_editor_state(indoc! {"
8210 one.second_completionˇ
8211 two
8212 three
8213 additional edit
8214 "});
8215
8216 cx.set_state(indoc! {"
8217 one.second_completion
8218 twoˇ
8219 threeˇ
8220 additional edit
8221 "});
8222 cx.simulate_keystroke(" ");
8223 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8224 cx.simulate_keystroke("s");
8225 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8226
8227 cx.assert_editor_state(indoc! {"
8228 one.second_completion
8229 two sˇ
8230 three sˇ
8231 additional edit
8232 "});
8233 handle_completion_request(
8234 &mut cx,
8235 indoc! {"
8236 one.second_completion
8237 two s
8238 three <s|>
8239 additional edit
8240 "},
8241 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8242 counter.clone(),
8243 )
8244 .await;
8245 cx.condition(|editor, _| editor.context_menu_visible())
8246 .await;
8247 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
8248
8249 cx.simulate_keystroke("i");
8250
8251 handle_completion_request(
8252 &mut cx,
8253 indoc! {"
8254 one.second_completion
8255 two si
8256 three <si|>
8257 additional edit
8258 "},
8259 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8260 counter.clone(),
8261 )
8262 .await;
8263 cx.condition(|editor, _| editor.context_menu_visible())
8264 .await;
8265 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
8266
8267 let apply_additional_edits = cx.update_editor(|editor, cx| {
8268 editor
8269 .confirm_completion(&ConfirmCompletion::default(), cx)
8270 .unwrap()
8271 });
8272 cx.assert_editor_state(indoc! {"
8273 one.second_completion
8274 two sixth_completionˇ
8275 three sixth_completionˇ
8276 additional edit
8277 "});
8278
8279 handle_resolve_completion_request(&mut cx, None).await;
8280 apply_additional_edits.await.unwrap();
8281
8282 cx.update(|cx| {
8283 cx.update_global::<SettingsStore, _>(|settings, cx| {
8284 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8285 settings.show_completions_on_input = Some(false);
8286 });
8287 })
8288 });
8289 cx.set_state("editorˇ");
8290 cx.simulate_keystroke(".");
8291 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8292 cx.simulate_keystroke("c");
8293 cx.simulate_keystroke("l");
8294 cx.simulate_keystroke("o");
8295 cx.assert_editor_state("editor.cloˇ");
8296 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8297 cx.update_editor(|editor, cx| {
8298 editor.show_completions(&ShowCompletions { trigger: None }, cx);
8299 });
8300 handle_completion_request(
8301 &mut cx,
8302 "editor.<clo|>",
8303 vec!["close", "clobber"],
8304 counter.clone(),
8305 )
8306 .await;
8307 cx.condition(|editor, _| editor.context_menu_visible())
8308 .await;
8309 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
8310
8311 let apply_additional_edits = cx.update_editor(|editor, cx| {
8312 editor
8313 .confirm_completion(&ConfirmCompletion::default(), cx)
8314 .unwrap()
8315 });
8316 cx.assert_editor_state("editor.closeˇ");
8317 handle_resolve_completion_request(&mut cx, None).await;
8318 apply_additional_edits.await.unwrap();
8319}
8320
8321#[gpui::test]
8322async fn test_completion_page_up_down_keys(cx: &mut gpui::TestAppContext) {
8323 init_test(cx, |_| {});
8324 let mut cx = EditorLspTestContext::new_rust(
8325 lsp::ServerCapabilities {
8326 completion_provider: Some(lsp::CompletionOptions {
8327 trigger_characters: Some(vec![".".to_string()]),
8328 ..Default::default()
8329 }),
8330 ..Default::default()
8331 },
8332 cx,
8333 )
8334 .await;
8335 cx.lsp
8336 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8337 Ok(Some(lsp::CompletionResponse::Array(vec![
8338 lsp::CompletionItem {
8339 label: "first".into(),
8340 ..Default::default()
8341 },
8342 lsp::CompletionItem {
8343 label: "last".into(),
8344 ..Default::default()
8345 },
8346 ])))
8347 });
8348 cx.set_state("variableˇ");
8349 cx.simulate_keystroke(".");
8350 cx.executor().run_until_parked();
8351
8352 cx.update_editor(|editor, _| {
8353 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8354 assert_eq!(
8355 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8356 &["first", "last"]
8357 );
8358 } else {
8359 panic!("expected completion menu to be open");
8360 }
8361 });
8362
8363 cx.update_editor(|editor, cx| {
8364 editor.move_page_down(&MovePageDown::default(), cx);
8365 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8366 assert!(
8367 menu.selected_item == 1,
8368 "expected PageDown to select the last item from the context menu"
8369 );
8370 } else {
8371 panic!("expected completion menu to stay open after PageDown");
8372 }
8373 });
8374
8375 cx.update_editor(|editor, cx| {
8376 editor.move_page_up(&MovePageUp::default(), cx);
8377 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8378 assert!(
8379 menu.selected_item == 0,
8380 "expected PageUp to select the first item from the context menu"
8381 );
8382 } else {
8383 panic!("expected completion menu to stay open after PageUp");
8384 }
8385 });
8386}
8387
8388#[gpui::test]
8389async fn test_no_duplicated_completion_requests(cx: &mut gpui::TestAppContext) {
8390 init_test(cx, |_| {});
8391
8392 let mut cx = EditorLspTestContext::new_rust(
8393 lsp::ServerCapabilities {
8394 completion_provider: Some(lsp::CompletionOptions {
8395 trigger_characters: Some(vec![".".to_string()]),
8396 resolve_provider: Some(true),
8397 ..Default::default()
8398 }),
8399 ..Default::default()
8400 },
8401 cx,
8402 )
8403 .await;
8404
8405 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
8406 cx.simulate_keystroke(".");
8407 let completion_item = lsp::CompletionItem {
8408 label: "Some".into(),
8409 kind: Some(lsp::CompletionItemKind::SNIPPET),
8410 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
8411 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
8412 kind: lsp::MarkupKind::Markdown,
8413 value: "```rust\nSome(2)\n```".to_string(),
8414 })),
8415 deprecated: Some(false),
8416 sort_text: Some("Some".to_string()),
8417 filter_text: Some("Some".to_string()),
8418 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
8419 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8420 range: lsp::Range {
8421 start: lsp::Position {
8422 line: 0,
8423 character: 22,
8424 },
8425 end: lsp::Position {
8426 line: 0,
8427 character: 22,
8428 },
8429 },
8430 new_text: "Some(2)".to_string(),
8431 })),
8432 additional_text_edits: Some(vec![lsp::TextEdit {
8433 range: lsp::Range {
8434 start: lsp::Position {
8435 line: 0,
8436 character: 20,
8437 },
8438 end: lsp::Position {
8439 line: 0,
8440 character: 22,
8441 },
8442 },
8443 new_text: "".to_string(),
8444 }]),
8445 ..Default::default()
8446 };
8447
8448 let closure_completion_item = completion_item.clone();
8449 let counter = Arc::new(AtomicUsize::new(0));
8450 let counter_clone = counter.clone();
8451 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
8452 let task_completion_item = closure_completion_item.clone();
8453 counter_clone.fetch_add(1, atomic::Ordering::Release);
8454 async move {
8455 Ok(Some(lsp::CompletionResponse::Array(vec![
8456 task_completion_item,
8457 ])))
8458 }
8459 });
8460
8461 cx.condition(|editor, _| editor.context_menu_visible())
8462 .await;
8463 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
8464 assert!(request.next().await.is_some());
8465 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8466
8467 cx.simulate_keystroke("S");
8468 cx.simulate_keystroke("o");
8469 cx.simulate_keystroke("m");
8470 cx.condition(|editor, _| editor.context_menu_visible())
8471 .await;
8472 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
8473 assert!(request.next().await.is_some());
8474 assert!(request.next().await.is_some());
8475 assert!(request.next().await.is_some());
8476 request.close();
8477 assert!(request.next().await.is_none());
8478 assert_eq!(
8479 counter.load(atomic::Ordering::Acquire),
8480 4,
8481 "With the completions menu open, only one LSP request should happen per input"
8482 );
8483}
8484
8485#[gpui::test]
8486async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
8487 init_test(cx, |_| {});
8488 let mut cx = EditorTestContext::new(cx).await;
8489 let language = Arc::new(Language::new(
8490 LanguageConfig {
8491 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
8492 ..Default::default()
8493 },
8494 Some(tree_sitter_rust::LANGUAGE.into()),
8495 ));
8496 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
8497
8498 // If multiple selections intersect a line, the line is only toggled once.
8499 cx.set_state(indoc! {"
8500 fn a() {
8501 «//b();
8502 ˇ»// «c();
8503 //ˇ» d();
8504 }
8505 "});
8506
8507 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8508
8509 cx.assert_editor_state(indoc! {"
8510 fn a() {
8511 «b();
8512 c();
8513 ˇ» d();
8514 }
8515 "});
8516
8517 // The comment prefix is inserted at the same column for every line in a
8518 // selection.
8519 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8520
8521 cx.assert_editor_state(indoc! {"
8522 fn a() {
8523 // «b();
8524 // c();
8525 ˇ»// d();
8526 }
8527 "});
8528
8529 // If a selection ends at the beginning of a line, that line is not toggled.
8530 cx.set_selections_state(indoc! {"
8531 fn a() {
8532 // b();
8533 «// c();
8534 ˇ» // d();
8535 }
8536 "});
8537
8538 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8539
8540 cx.assert_editor_state(indoc! {"
8541 fn a() {
8542 // b();
8543 «c();
8544 ˇ» // d();
8545 }
8546 "});
8547
8548 // If a selection span a single line and is empty, the line is toggled.
8549 cx.set_state(indoc! {"
8550 fn a() {
8551 a();
8552 b();
8553 ˇ
8554 }
8555 "});
8556
8557 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8558
8559 cx.assert_editor_state(indoc! {"
8560 fn a() {
8561 a();
8562 b();
8563 //•ˇ
8564 }
8565 "});
8566
8567 // If a selection span multiple lines, empty lines are not toggled.
8568 cx.set_state(indoc! {"
8569 fn a() {
8570 «a();
8571
8572 c();ˇ»
8573 }
8574 "});
8575
8576 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8577
8578 cx.assert_editor_state(indoc! {"
8579 fn a() {
8580 // «a();
8581
8582 // c();ˇ»
8583 }
8584 "});
8585
8586 // If a selection includes multiple comment prefixes, all lines are uncommented.
8587 cx.set_state(indoc! {"
8588 fn a() {
8589 «// a();
8590 /// b();
8591 //! c();ˇ»
8592 }
8593 "});
8594
8595 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8596
8597 cx.assert_editor_state(indoc! {"
8598 fn a() {
8599 «a();
8600 b();
8601 c();ˇ»
8602 }
8603 "});
8604}
8605
8606#[gpui::test]
8607async fn test_toggle_comment_ignore_indent(cx: &mut gpui::TestAppContext) {
8608 init_test(cx, |_| {});
8609 let mut cx = EditorTestContext::new(cx).await;
8610 let language = Arc::new(Language::new(
8611 LanguageConfig {
8612 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
8613 ..Default::default()
8614 },
8615 Some(tree_sitter_rust::LANGUAGE.into()),
8616 ));
8617 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
8618
8619 let toggle_comments = &ToggleComments {
8620 advance_downwards: false,
8621 ignore_indent: true,
8622 };
8623
8624 // If multiple selections intersect a line, the line is only toggled once.
8625 cx.set_state(indoc! {"
8626 fn a() {
8627 // «b();
8628 // c();
8629 // ˇ» d();
8630 }
8631 "});
8632
8633 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8634
8635 cx.assert_editor_state(indoc! {"
8636 fn a() {
8637 «b();
8638 c();
8639 ˇ» d();
8640 }
8641 "});
8642
8643 // The comment prefix is inserted at the beginning of each line
8644 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8645
8646 cx.assert_editor_state(indoc! {"
8647 fn a() {
8648 // «b();
8649 // c();
8650 // ˇ» d();
8651 }
8652 "});
8653
8654 // If a selection ends at the beginning of a line, that line is not toggled.
8655 cx.set_selections_state(indoc! {"
8656 fn a() {
8657 // b();
8658 // «c();
8659 ˇ»// d();
8660 }
8661 "});
8662
8663 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8664
8665 cx.assert_editor_state(indoc! {"
8666 fn a() {
8667 // b();
8668 «c();
8669 ˇ»// d();
8670 }
8671 "});
8672
8673 // If a selection span a single line and is empty, the line is toggled.
8674 cx.set_state(indoc! {"
8675 fn a() {
8676 a();
8677 b();
8678 ˇ
8679 }
8680 "});
8681
8682 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8683
8684 cx.assert_editor_state(indoc! {"
8685 fn a() {
8686 a();
8687 b();
8688 //ˇ
8689 }
8690 "});
8691
8692 // If a selection span multiple lines, empty lines are not toggled.
8693 cx.set_state(indoc! {"
8694 fn a() {
8695 «a();
8696
8697 c();ˇ»
8698 }
8699 "});
8700
8701 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8702
8703 cx.assert_editor_state(indoc! {"
8704 fn a() {
8705 // «a();
8706
8707 // c();ˇ»
8708 }
8709 "});
8710
8711 // If a selection includes multiple comment prefixes, all lines are uncommented.
8712 cx.set_state(indoc! {"
8713 fn a() {
8714 // «a();
8715 /// b();
8716 //! c();ˇ»
8717 }
8718 "});
8719
8720 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8721
8722 cx.assert_editor_state(indoc! {"
8723 fn a() {
8724 «a();
8725 b();
8726 c();ˇ»
8727 }
8728 "});
8729}
8730
8731#[gpui::test]
8732async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
8733 init_test(cx, |_| {});
8734
8735 let language = Arc::new(Language::new(
8736 LanguageConfig {
8737 line_comments: vec!["// ".into()],
8738 ..Default::default()
8739 },
8740 Some(tree_sitter_rust::LANGUAGE.into()),
8741 ));
8742
8743 let mut cx = EditorTestContext::new(cx).await;
8744
8745 cx.language_registry().add(language.clone());
8746 cx.update_buffer(|buffer, cx| {
8747 buffer.set_language(Some(language), cx);
8748 });
8749
8750 let toggle_comments = &ToggleComments {
8751 advance_downwards: true,
8752 ignore_indent: false,
8753 };
8754
8755 // Single cursor on one line -> advance
8756 // Cursor moves horizontally 3 characters as well on non-blank line
8757 cx.set_state(indoc!(
8758 "fn a() {
8759 ˇdog();
8760 cat();
8761 }"
8762 ));
8763 cx.update_editor(|editor, cx| {
8764 editor.toggle_comments(toggle_comments, cx);
8765 });
8766 cx.assert_editor_state(indoc!(
8767 "fn a() {
8768 // dog();
8769 catˇ();
8770 }"
8771 ));
8772
8773 // Single selection on one line -> don't advance
8774 cx.set_state(indoc!(
8775 "fn a() {
8776 «dog()ˇ»;
8777 cat();
8778 }"
8779 ));
8780 cx.update_editor(|editor, cx| {
8781 editor.toggle_comments(toggle_comments, cx);
8782 });
8783 cx.assert_editor_state(indoc!(
8784 "fn a() {
8785 // «dog()ˇ»;
8786 cat();
8787 }"
8788 ));
8789
8790 // Multiple cursors on one line -> advance
8791 cx.set_state(indoc!(
8792 "fn a() {
8793 ˇdˇog();
8794 cat();
8795 }"
8796 ));
8797 cx.update_editor(|editor, cx| {
8798 editor.toggle_comments(toggle_comments, cx);
8799 });
8800 cx.assert_editor_state(indoc!(
8801 "fn a() {
8802 // dog();
8803 catˇ(ˇ);
8804 }"
8805 ));
8806
8807 // Multiple cursors on one line, with selection -> don't advance
8808 cx.set_state(indoc!(
8809 "fn a() {
8810 ˇdˇog«()ˇ»;
8811 cat();
8812 }"
8813 ));
8814 cx.update_editor(|editor, cx| {
8815 editor.toggle_comments(toggle_comments, cx);
8816 });
8817 cx.assert_editor_state(indoc!(
8818 "fn a() {
8819 // ˇdˇog«()ˇ»;
8820 cat();
8821 }"
8822 ));
8823
8824 // Single cursor on one line -> advance
8825 // Cursor moves to column 0 on blank line
8826 cx.set_state(indoc!(
8827 "fn a() {
8828 ˇdog();
8829
8830 cat();
8831 }"
8832 ));
8833 cx.update_editor(|editor, cx| {
8834 editor.toggle_comments(toggle_comments, cx);
8835 });
8836 cx.assert_editor_state(indoc!(
8837 "fn a() {
8838 // dog();
8839 ˇ
8840 cat();
8841 }"
8842 ));
8843
8844 // Single cursor on one line -> advance
8845 // Cursor starts and ends at column 0
8846 cx.set_state(indoc!(
8847 "fn a() {
8848 ˇ dog();
8849 cat();
8850 }"
8851 ));
8852 cx.update_editor(|editor, cx| {
8853 editor.toggle_comments(toggle_comments, cx);
8854 });
8855 cx.assert_editor_state(indoc!(
8856 "fn a() {
8857 // dog();
8858 ˇ cat();
8859 }"
8860 ));
8861}
8862
8863#[gpui::test]
8864async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
8865 init_test(cx, |_| {});
8866
8867 let mut cx = EditorTestContext::new(cx).await;
8868
8869 let html_language = Arc::new(
8870 Language::new(
8871 LanguageConfig {
8872 name: "HTML".into(),
8873 block_comment: Some(("<!-- ".into(), " -->".into())),
8874 ..Default::default()
8875 },
8876 Some(tree_sitter_html::language()),
8877 )
8878 .with_injection_query(
8879 r#"
8880 (script_element
8881 (raw_text) @content
8882 (#set! "language" "javascript"))
8883 "#,
8884 )
8885 .unwrap(),
8886 );
8887
8888 let javascript_language = Arc::new(Language::new(
8889 LanguageConfig {
8890 name: "JavaScript".into(),
8891 line_comments: vec!["// ".into()],
8892 ..Default::default()
8893 },
8894 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
8895 ));
8896
8897 cx.language_registry().add(html_language.clone());
8898 cx.language_registry().add(javascript_language.clone());
8899 cx.update_buffer(|buffer, cx| {
8900 buffer.set_language(Some(html_language), cx);
8901 });
8902
8903 // Toggle comments for empty selections
8904 cx.set_state(
8905 &r#"
8906 <p>A</p>ˇ
8907 <p>B</p>ˇ
8908 <p>C</p>ˇ
8909 "#
8910 .unindent(),
8911 );
8912 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8913 cx.assert_editor_state(
8914 &r#"
8915 <!-- <p>A</p>ˇ -->
8916 <!-- <p>B</p>ˇ -->
8917 <!-- <p>C</p>ˇ -->
8918 "#
8919 .unindent(),
8920 );
8921 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8922 cx.assert_editor_state(
8923 &r#"
8924 <p>A</p>ˇ
8925 <p>B</p>ˇ
8926 <p>C</p>ˇ
8927 "#
8928 .unindent(),
8929 );
8930
8931 // Toggle comments for mixture of empty and non-empty selections, where
8932 // multiple selections occupy a given line.
8933 cx.set_state(
8934 &r#"
8935 <p>A«</p>
8936 <p>ˇ»B</p>ˇ
8937 <p>C«</p>
8938 <p>ˇ»D</p>ˇ
8939 "#
8940 .unindent(),
8941 );
8942
8943 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8944 cx.assert_editor_state(
8945 &r#"
8946 <!-- <p>A«</p>
8947 <p>ˇ»B</p>ˇ -->
8948 <!-- <p>C«</p>
8949 <p>ˇ»D</p>ˇ -->
8950 "#
8951 .unindent(),
8952 );
8953 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8954 cx.assert_editor_state(
8955 &r#"
8956 <p>A«</p>
8957 <p>ˇ»B</p>ˇ
8958 <p>C«</p>
8959 <p>ˇ»D</p>ˇ
8960 "#
8961 .unindent(),
8962 );
8963
8964 // Toggle comments when different languages are active for different
8965 // selections.
8966 cx.set_state(
8967 &r#"
8968 ˇ<script>
8969 ˇvar x = new Y();
8970 ˇ</script>
8971 "#
8972 .unindent(),
8973 );
8974 cx.executor().run_until_parked();
8975 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8976 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
8977 // Uncommenting and commenting from this position brings in even more wrong artifacts.
8978 cx.assert_editor_state(
8979 &r#"
8980 <!-- ˇ<script> -->
8981 // ˇvar x = new Y();
8982 // ˇ</script>
8983 "#
8984 .unindent(),
8985 );
8986}
8987
8988#[gpui::test]
8989fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
8990 init_test(cx, |_| {});
8991
8992 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
8993 let multibuffer = cx.new_model(|cx| {
8994 let mut multibuffer = MultiBuffer::new(ReadWrite);
8995 multibuffer.push_excerpts(
8996 buffer.clone(),
8997 [
8998 ExcerptRange {
8999 context: Point::new(0, 0)..Point::new(0, 4),
9000 primary: None,
9001 },
9002 ExcerptRange {
9003 context: Point::new(1, 0)..Point::new(1, 4),
9004 primary: None,
9005 },
9006 ],
9007 cx,
9008 );
9009 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
9010 multibuffer
9011 });
9012
9013 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
9014 view.update(cx, |view, cx| {
9015 assert_eq!(view.text(cx), "aaaa\nbbbb");
9016 view.change_selections(None, cx, |s| {
9017 s.select_ranges([
9018 Point::new(0, 0)..Point::new(0, 0),
9019 Point::new(1, 0)..Point::new(1, 0),
9020 ])
9021 });
9022
9023 view.handle_input("X", cx);
9024 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
9025 assert_eq!(
9026 view.selections.ranges(cx),
9027 [
9028 Point::new(0, 1)..Point::new(0, 1),
9029 Point::new(1, 1)..Point::new(1, 1),
9030 ]
9031 );
9032
9033 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
9034 view.change_selections(None, cx, |s| {
9035 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
9036 });
9037 view.backspace(&Default::default(), cx);
9038 assert_eq!(view.text(cx), "Xa\nbbb");
9039 assert_eq!(
9040 view.selections.ranges(cx),
9041 [Point::new(1, 0)..Point::new(1, 0)]
9042 );
9043
9044 view.change_selections(None, cx, |s| {
9045 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
9046 });
9047 view.backspace(&Default::default(), cx);
9048 assert_eq!(view.text(cx), "X\nbb");
9049 assert_eq!(
9050 view.selections.ranges(cx),
9051 [Point::new(0, 1)..Point::new(0, 1)]
9052 );
9053 });
9054}
9055
9056#[gpui::test]
9057fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
9058 init_test(cx, |_| {});
9059
9060 let markers = vec![('[', ']').into(), ('(', ')').into()];
9061 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
9062 indoc! {"
9063 [aaaa
9064 (bbbb]
9065 cccc)",
9066 },
9067 markers.clone(),
9068 );
9069 let excerpt_ranges = markers.into_iter().map(|marker| {
9070 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
9071 ExcerptRange {
9072 context,
9073 primary: None,
9074 }
9075 });
9076 let buffer = cx.new_model(|cx| Buffer::local(initial_text, cx));
9077 let multibuffer = cx.new_model(|cx| {
9078 let mut multibuffer = MultiBuffer::new(ReadWrite);
9079 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
9080 multibuffer
9081 });
9082
9083 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
9084 view.update(cx, |view, cx| {
9085 let (expected_text, selection_ranges) = marked_text_ranges(
9086 indoc! {"
9087 aaaa
9088 bˇbbb
9089 bˇbbˇb
9090 cccc"
9091 },
9092 true,
9093 );
9094 assert_eq!(view.text(cx), expected_text);
9095 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
9096
9097 view.handle_input("X", cx);
9098
9099 let (expected_text, expected_selections) = marked_text_ranges(
9100 indoc! {"
9101 aaaa
9102 bXˇbbXb
9103 bXˇbbXˇb
9104 cccc"
9105 },
9106 false,
9107 );
9108 assert_eq!(view.text(cx), expected_text);
9109 assert_eq!(view.selections.ranges(cx), expected_selections);
9110
9111 view.newline(&Newline, cx);
9112 let (expected_text, expected_selections) = marked_text_ranges(
9113 indoc! {"
9114 aaaa
9115 bX
9116 ˇbbX
9117 b
9118 bX
9119 ˇbbX
9120 ˇb
9121 cccc"
9122 },
9123 false,
9124 );
9125 assert_eq!(view.text(cx), expected_text);
9126 assert_eq!(view.selections.ranges(cx), expected_selections);
9127 });
9128}
9129
9130#[gpui::test]
9131fn test_refresh_selections(cx: &mut TestAppContext) {
9132 init_test(cx, |_| {});
9133
9134 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9135 let mut excerpt1_id = None;
9136 let multibuffer = cx.new_model(|cx| {
9137 let mut multibuffer = MultiBuffer::new(ReadWrite);
9138 excerpt1_id = multibuffer
9139 .push_excerpts(
9140 buffer.clone(),
9141 [
9142 ExcerptRange {
9143 context: Point::new(0, 0)..Point::new(1, 4),
9144 primary: None,
9145 },
9146 ExcerptRange {
9147 context: Point::new(1, 0)..Point::new(2, 4),
9148 primary: None,
9149 },
9150 ],
9151 cx,
9152 )
9153 .into_iter()
9154 .next();
9155 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
9156 multibuffer
9157 });
9158
9159 let editor = cx.add_window(|cx| {
9160 let mut editor = build_editor(multibuffer.clone(), cx);
9161 let snapshot = editor.snapshot(cx);
9162 editor.change_selections(None, cx, |s| {
9163 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
9164 });
9165 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
9166 assert_eq!(
9167 editor.selections.ranges(cx),
9168 [
9169 Point::new(1, 3)..Point::new(1, 3),
9170 Point::new(2, 1)..Point::new(2, 1),
9171 ]
9172 );
9173 editor
9174 });
9175
9176 // Refreshing selections is a no-op when excerpts haven't changed.
9177 _ = editor.update(cx, |editor, cx| {
9178 editor.change_selections(None, cx, |s| s.refresh());
9179 assert_eq!(
9180 editor.selections.ranges(cx),
9181 [
9182 Point::new(1, 3)..Point::new(1, 3),
9183 Point::new(2, 1)..Point::new(2, 1),
9184 ]
9185 );
9186 });
9187
9188 multibuffer.update(cx, |multibuffer, cx| {
9189 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
9190 });
9191 _ = editor.update(cx, |editor, cx| {
9192 // Removing an excerpt causes the first selection to become degenerate.
9193 assert_eq!(
9194 editor.selections.ranges(cx),
9195 [
9196 Point::new(0, 0)..Point::new(0, 0),
9197 Point::new(0, 1)..Point::new(0, 1)
9198 ]
9199 );
9200
9201 // Refreshing selections will relocate the first selection to the original buffer
9202 // location.
9203 editor.change_selections(None, cx, |s| s.refresh());
9204 assert_eq!(
9205 editor.selections.ranges(cx),
9206 [
9207 Point::new(0, 1)..Point::new(0, 1),
9208 Point::new(0, 3)..Point::new(0, 3)
9209 ]
9210 );
9211 assert!(editor.selections.pending_anchor().is_some());
9212 });
9213}
9214
9215#[gpui::test]
9216fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
9217 init_test(cx, |_| {});
9218
9219 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9220 let mut excerpt1_id = None;
9221 let multibuffer = cx.new_model(|cx| {
9222 let mut multibuffer = MultiBuffer::new(ReadWrite);
9223 excerpt1_id = multibuffer
9224 .push_excerpts(
9225 buffer.clone(),
9226 [
9227 ExcerptRange {
9228 context: Point::new(0, 0)..Point::new(1, 4),
9229 primary: None,
9230 },
9231 ExcerptRange {
9232 context: Point::new(1, 0)..Point::new(2, 4),
9233 primary: None,
9234 },
9235 ],
9236 cx,
9237 )
9238 .into_iter()
9239 .next();
9240 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
9241 multibuffer
9242 });
9243
9244 let editor = cx.add_window(|cx| {
9245 let mut editor = build_editor(multibuffer.clone(), cx);
9246 let snapshot = editor.snapshot(cx);
9247 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
9248 assert_eq!(
9249 editor.selections.ranges(cx),
9250 [Point::new(1, 3)..Point::new(1, 3)]
9251 );
9252 editor
9253 });
9254
9255 multibuffer.update(cx, |multibuffer, cx| {
9256 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
9257 });
9258 _ = editor.update(cx, |editor, cx| {
9259 assert_eq!(
9260 editor.selections.ranges(cx),
9261 [Point::new(0, 0)..Point::new(0, 0)]
9262 );
9263
9264 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
9265 editor.change_selections(None, cx, |s| s.refresh());
9266 assert_eq!(
9267 editor.selections.ranges(cx),
9268 [Point::new(0, 3)..Point::new(0, 3)]
9269 );
9270 assert!(editor.selections.pending_anchor().is_some());
9271 });
9272}
9273
9274#[gpui::test]
9275async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
9276 init_test(cx, |_| {});
9277
9278 let language = Arc::new(
9279 Language::new(
9280 LanguageConfig {
9281 brackets: BracketPairConfig {
9282 pairs: vec![
9283 BracketPair {
9284 start: "{".to_string(),
9285 end: "}".to_string(),
9286 close: true,
9287 surround: true,
9288 newline: true,
9289 },
9290 BracketPair {
9291 start: "/* ".to_string(),
9292 end: " */".to_string(),
9293 close: true,
9294 surround: true,
9295 newline: true,
9296 },
9297 ],
9298 ..Default::default()
9299 },
9300 ..Default::default()
9301 },
9302 Some(tree_sitter_rust::LANGUAGE.into()),
9303 )
9304 .with_indents_query("")
9305 .unwrap(),
9306 );
9307
9308 let text = concat!(
9309 "{ }\n", //
9310 " x\n", //
9311 " /* */\n", //
9312 "x\n", //
9313 "{{} }\n", //
9314 );
9315
9316 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
9317 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
9318 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
9319 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
9320 .await;
9321
9322 view.update(cx, |view, cx| {
9323 view.change_selections(None, cx, |s| {
9324 s.select_display_ranges([
9325 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
9326 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
9327 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
9328 ])
9329 });
9330 view.newline(&Newline, cx);
9331
9332 assert_eq!(
9333 view.buffer().read(cx).read(cx).text(),
9334 concat!(
9335 "{ \n", // Suppress rustfmt
9336 "\n", //
9337 "}\n", //
9338 " x\n", //
9339 " /* \n", //
9340 " \n", //
9341 " */\n", //
9342 "x\n", //
9343 "{{} \n", //
9344 "}\n", //
9345 )
9346 );
9347 });
9348}
9349
9350#[gpui::test]
9351fn test_highlighted_ranges(cx: &mut TestAppContext) {
9352 init_test(cx, |_| {});
9353
9354 let editor = cx.add_window(|cx| {
9355 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
9356 build_editor(buffer.clone(), cx)
9357 });
9358
9359 _ = editor.update(cx, |editor, cx| {
9360 struct Type1;
9361 struct Type2;
9362
9363 let buffer = editor.buffer.read(cx).snapshot(cx);
9364
9365 let anchor_range =
9366 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
9367
9368 editor.highlight_background::<Type1>(
9369 &[
9370 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
9371 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
9372 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
9373 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
9374 ],
9375 |_| Hsla::red(),
9376 cx,
9377 );
9378 editor.highlight_background::<Type2>(
9379 &[
9380 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
9381 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
9382 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
9383 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
9384 ],
9385 |_| Hsla::green(),
9386 cx,
9387 );
9388
9389 let snapshot = editor.snapshot(cx);
9390 let mut highlighted_ranges = editor.background_highlights_in_range(
9391 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
9392 &snapshot,
9393 cx.theme().colors(),
9394 );
9395 // Enforce a consistent ordering based on color without relying on the ordering of the
9396 // highlight's `TypeId` which is non-executor.
9397 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
9398 assert_eq!(
9399 highlighted_ranges,
9400 &[
9401 (
9402 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
9403 Hsla::red(),
9404 ),
9405 (
9406 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9407 Hsla::red(),
9408 ),
9409 (
9410 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
9411 Hsla::green(),
9412 ),
9413 (
9414 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
9415 Hsla::green(),
9416 ),
9417 ]
9418 );
9419 assert_eq!(
9420 editor.background_highlights_in_range(
9421 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
9422 &snapshot,
9423 cx.theme().colors(),
9424 ),
9425 &[(
9426 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9427 Hsla::red(),
9428 )]
9429 );
9430 });
9431}
9432
9433#[gpui::test]
9434async fn test_following(cx: &mut gpui::TestAppContext) {
9435 init_test(cx, |_| {});
9436
9437 let fs = FakeFs::new(cx.executor());
9438 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9439
9440 let buffer = project.update(cx, |project, cx| {
9441 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
9442 cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
9443 });
9444 let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
9445 let follower = cx.update(|cx| {
9446 cx.open_window(
9447 WindowOptions {
9448 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
9449 gpui::Point::new(px(0.), px(0.)),
9450 gpui::Point::new(px(10.), px(80.)),
9451 ))),
9452 ..Default::default()
9453 },
9454 |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
9455 )
9456 .unwrap()
9457 });
9458
9459 let is_still_following = Rc::new(RefCell::new(true));
9460 let follower_edit_event_count = Rc::new(RefCell::new(0));
9461 let pending_update = Rc::new(RefCell::new(None));
9462 _ = follower.update(cx, {
9463 let update = pending_update.clone();
9464 let is_still_following = is_still_following.clone();
9465 let follower_edit_event_count = follower_edit_event_count.clone();
9466 |_, cx| {
9467 cx.subscribe(
9468 &leader.root_view(cx).unwrap(),
9469 move |_, leader, event, cx| {
9470 leader
9471 .read(cx)
9472 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9473 },
9474 )
9475 .detach();
9476
9477 cx.subscribe(
9478 &follower.root_view(cx).unwrap(),
9479 move |_, _, event: &EditorEvent, _cx| {
9480 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
9481 *is_still_following.borrow_mut() = false;
9482 }
9483
9484 if let EditorEvent::BufferEdited = event {
9485 *follower_edit_event_count.borrow_mut() += 1;
9486 }
9487 },
9488 )
9489 .detach();
9490 }
9491 });
9492
9493 // Update the selections only
9494 _ = leader.update(cx, |leader, cx| {
9495 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9496 });
9497 follower
9498 .update(cx, |follower, cx| {
9499 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9500 })
9501 .unwrap()
9502 .await
9503 .unwrap();
9504 _ = follower.update(cx, |follower, cx| {
9505 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
9506 });
9507 assert!(*is_still_following.borrow());
9508 assert_eq!(*follower_edit_event_count.borrow(), 0);
9509
9510 // Update the scroll position only
9511 _ = leader.update(cx, |leader, cx| {
9512 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9513 });
9514 follower
9515 .update(cx, |follower, cx| {
9516 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9517 })
9518 .unwrap()
9519 .await
9520 .unwrap();
9521 assert_eq!(
9522 follower
9523 .update(cx, |follower, cx| follower.scroll_position(cx))
9524 .unwrap(),
9525 gpui::Point::new(1.5, 3.5)
9526 );
9527 assert!(*is_still_following.borrow());
9528 assert_eq!(*follower_edit_event_count.borrow(), 0);
9529
9530 // Update the selections and scroll position. The follower's scroll position is updated
9531 // via autoscroll, not via the leader's exact scroll position.
9532 _ = leader.update(cx, |leader, cx| {
9533 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
9534 leader.request_autoscroll(Autoscroll::newest(), cx);
9535 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9536 });
9537 follower
9538 .update(cx, |follower, cx| {
9539 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9540 })
9541 .unwrap()
9542 .await
9543 .unwrap();
9544 _ = follower.update(cx, |follower, cx| {
9545 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
9546 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
9547 });
9548 assert!(*is_still_following.borrow());
9549
9550 // Creating a pending selection that precedes another selection
9551 _ = leader.update(cx, |leader, cx| {
9552 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9553 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, cx);
9554 });
9555 follower
9556 .update(cx, |follower, cx| {
9557 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9558 })
9559 .unwrap()
9560 .await
9561 .unwrap();
9562 _ = follower.update(cx, |follower, cx| {
9563 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
9564 });
9565 assert!(*is_still_following.borrow());
9566
9567 // Extend the pending selection so that it surrounds another selection
9568 _ = leader.update(cx, |leader, cx| {
9569 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, cx);
9570 });
9571 follower
9572 .update(cx, |follower, cx| {
9573 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9574 })
9575 .unwrap()
9576 .await
9577 .unwrap();
9578 _ = follower.update(cx, |follower, cx| {
9579 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
9580 });
9581
9582 // Scrolling locally breaks the follow
9583 _ = follower.update(cx, |follower, cx| {
9584 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
9585 follower.set_scroll_anchor(
9586 ScrollAnchor {
9587 anchor: top_anchor,
9588 offset: gpui::Point::new(0.0, 0.5),
9589 },
9590 cx,
9591 );
9592 });
9593 assert!(!(*is_still_following.borrow()));
9594}
9595
9596#[gpui::test]
9597async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
9598 init_test(cx, |_| {});
9599
9600 let fs = FakeFs::new(cx.executor());
9601 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9602 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9603 let pane = workspace
9604 .update(cx, |workspace, _| workspace.active_pane().clone())
9605 .unwrap();
9606
9607 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
9608
9609 let leader = pane.update(cx, |_, cx| {
9610 let multibuffer = cx.new_model(|_| MultiBuffer::new(ReadWrite));
9611 cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
9612 });
9613
9614 // Start following the editor when it has no excerpts.
9615 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9616 let follower_1 = cx
9617 .update_window(*workspace.deref(), |_, cx| {
9618 Editor::from_state_proto(
9619 workspace.root_view(cx).unwrap(),
9620 ViewId {
9621 creator: Default::default(),
9622 id: 0,
9623 },
9624 &mut state_message,
9625 cx,
9626 )
9627 })
9628 .unwrap()
9629 .unwrap()
9630 .await
9631 .unwrap();
9632
9633 let update_message = Rc::new(RefCell::new(None));
9634 follower_1.update(cx, {
9635 let update = update_message.clone();
9636 |_, cx| {
9637 cx.subscribe(&leader, move |_, leader, event, cx| {
9638 leader
9639 .read(cx)
9640 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9641 })
9642 .detach();
9643 }
9644 });
9645
9646 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
9647 (
9648 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
9649 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
9650 )
9651 });
9652
9653 // Insert some excerpts.
9654 leader.update(cx, |leader, cx| {
9655 leader.buffer.update(cx, |multibuffer, cx| {
9656 let excerpt_ids = multibuffer.push_excerpts(
9657 buffer_1.clone(),
9658 [
9659 ExcerptRange {
9660 context: 1..6,
9661 primary: None,
9662 },
9663 ExcerptRange {
9664 context: 12..15,
9665 primary: None,
9666 },
9667 ExcerptRange {
9668 context: 0..3,
9669 primary: None,
9670 },
9671 ],
9672 cx,
9673 );
9674 multibuffer.insert_excerpts_after(
9675 excerpt_ids[0],
9676 buffer_2.clone(),
9677 [
9678 ExcerptRange {
9679 context: 8..12,
9680 primary: None,
9681 },
9682 ExcerptRange {
9683 context: 0..6,
9684 primary: None,
9685 },
9686 ],
9687 cx,
9688 );
9689 });
9690 });
9691
9692 // Apply the update of adding the excerpts.
9693 follower_1
9694 .update(cx, |follower, cx| {
9695 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9696 })
9697 .await
9698 .unwrap();
9699 assert_eq!(
9700 follower_1.update(cx, |editor, cx| editor.text(cx)),
9701 leader.update(cx, |editor, cx| editor.text(cx))
9702 );
9703 update_message.borrow_mut().take();
9704
9705 // Start following separately after it already has excerpts.
9706 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9707 let follower_2 = cx
9708 .update_window(*workspace.deref(), |_, cx| {
9709 Editor::from_state_proto(
9710 workspace.root_view(cx).unwrap().clone(),
9711 ViewId {
9712 creator: Default::default(),
9713 id: 0,
9714 },
9715 &mut state_message,
9716 cx,
9717 )
9718 })
9719 .unwrap()
9720 .unwrap()
9721 .await
9722 .unwrap();
9723 assert_eq!(
9724 follower_2.update(cx, |editor, cx| editor.text(cx)),
9725 leader.update(cx, |editor, cx| editor.text(cx))
9726 );
9727
9728 // Remove some excerpts.
9729 leader.update(cx, |leader, cx| {
9730 leader.buffer.update(cx, |multibuffer, cx| {
9731 let excerpt_ids = multibuffer.excerpt_ids();
9732 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
9733 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
9734 });
9735 });
9736
9737 // Apply the update of removing the excerpts.
9738 follower_1
9739 .update(cx, |follower, cx| {
9740 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9741 })
9742 .await
9743 .unwrap();
9744 follower_2
9745 .update(cx, |follower, cx| {
9746 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9747 })
9748 .await
9749 .unwrap();
9750 update_message.borrow_mut().take();
9751 assert_eq!(
9752 follower_1.update(cx, |editor, cx| editor.text(cx)),
9753 leader.update(cx, |editor, cx| editor.text(cx))
9754 );
9755}
9756
9757#[gpui::test]
9758async fn go_to_prev_overlapping_diagnostic(
9759 executor: BackgroundExecutor,
9760 cx: &mut gpui::TestAppContext,
9761) {
9762 init_test(cx, |_| {});
9763
9764 let mut cx = EditorTestContext::new(cx).await;
9765 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
9766
9767 cx.set_state(indoc! {"
9768 ˇfn func(abc def: i32) -> u32 {
9769 }
9770 "});
9771
9772 cx.update(|cx| {
9773 project.update(cx, |project, cx| {
9774 project
9775 .update_diagnostics(
9776 LanguageServerId(0),
9777 lsp::PublishDiagnosticsParams {
9778 uri: lsp::Url::from_file_path("/root/file").unwrap(),
9779 version: None,
9780 diagnostics: vec![
9781 lsp::Diagnostic {
9782 range: lsp::Range::new(
9783 lsp::Position::new(0, 11),
9784 lsp::Position::new(0, 12),
9785 ),
9786 severity: Some(lsp::DiagnosticSeverity::ERROR),
9787 ..Default::default()
9788 },
9789 lsp::Diagnostic {
9790 range: lsp::Range::new(
9791 lsp::Position::new(0, 12),
9792 lsp::Position::new(0, 15),
9793 ),
9794 severity: Some(lsp::DiagnosticSeverity::ERROR),
9795 ..Default::default()
9796 },
9797 lsp::Diagnostic {
9798 range: lsp::Range::new(
9799 lsp::Position::new(0, 25),
9800 lsp::Position::new(0, 28),
9801 ),
9802 severity: Some(lsp::DiagnosticSeverity::ERROR),
9803 ..Default::default()
9804 },
9805 ],
9806 },
9807 &[],
9808 cx,
9809 )
9810 .unwrap()
9811 });
9812 });
9813
9814 executor.run_until_parked();
9815
9816 cx.update_editor(|editor, cx| {
9817 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9818 });
9819
9820 cx.assert_editor_state(indoc! {"
9821 fn func(abc def: i32) -> ˇu32 {
9822 }
9823 "});
9824
9825 cx.update_editor(|editor, cx| {
9826 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9827 });
9828
9829 cx.assert_editor_state(indoc! {"
9830 fn func(abc ˇdef: i32) -> u32 {
9831 }
9832 "});
9833
9834 cx.update_editor(|editor, cx| {
9835 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9836 });
9837
9838 cx.assert_editor_state(indoc! {"
9839 fn func(abcˇ def: i32) -> u32 {
9840 }
9841 "});
9842
9843 cx.update_editor(|editor, cx| {
9844 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9845 });
9846
9847 cx.assert_editor_state(indoc! {"
9848 fn func(abc def: i32) -> ˇu32 {
9849 }
9850 "});
9851}
9852
9853#[gpui::test]
9854async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
9855 init_test(cx, |_| {});
9856
9857 let mut cx = EditorTestContext::new(cx).await;
9858
9859 cx.set_state(indoc! {"
9860 fn func(abˇc def: i32) -> u32 {
9861 }
9862 "});
9863 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
9864
9865 cx.update(|cx| {
9866 project.update(cx, |project, cx| {
9867 project.update_diagnostics(
9868 LanguageServerId(0),
9869 lsp::PublishDiagnosticsParams {
9870 uri: lsp::Url::from_file_path("/root/file").unwrap(),
9871 version: None,
9872 diagnostics: vec![lsp::Diagnostic {
9873 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
9874 severity: Some(lsp::DiagnosticSeverity::ERROR),
9875 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
9876 ..Default::default()
9877 }],
9878 },
9879 &[],
9880 cx,
9881 )
9882 })
9883 }).unwrap();
9884 cx.run_until_parked();
9885 cx.update_editor(|editor, cx| hover_popover::hover(editor, &Default::default(), cx));
9886 cx.run_until_parked();
9887 cx.update_editor(|editor, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
9888}
9889
9890#[gpui::test]
9891async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
9892 init_test(cx, |_| {});
9893
9894 let mut cx = EditorTestContext::new(cx).await;
9895
9896 let diff_base = r#"
9897 use some::mod;
9898
9899 const A: u32 = 42;
9900
9901 fn main() {
9902 println!("hello");
9903
9904 println!("world");
9905 }
9906 "#
9907 .unindent();
9908
9909 // Edits are modified, removed, modified, added
9910 cx.set_state(
9911 &r#"
9912 use some::modified;
9913
9914 ˇ
9915 fn main() {
9916 println!("hello there");
9917
9918 println!("around the");
9919 println!("world");
9920 }
9921 "#
9922 .unindent(),
9923 );
9924
9925 cx.set_diff_base(Some(&diff_base));
9926 executor.run_until_parked();
9927
9928 cx.update_editor(|editor, cx| {
9929 //Wrap around the bottom of the buffer
9930 for _ in 0..3 {
9931 editor.go_to_next_hunk(&GoToHunk, cx);
9932 }
9933 });
9934
9935 cx.assert_editor_state(
9936 &r#"
9937 ˇuse some::modified;
9938
9939
9940 fn main() {
9941 println!("hello there");
9942
9943 println!("around the");
9944 println!("world");
9945 }
9946 "#
9947 .unindent(),
9948 );
9949
9950 cx.update_editor(|editor, cx| {
9951 //Wrap around the top of the buffer
9952 for _ in 0..2 {
9953 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9954 }
9955 });
9956
9957 cx.assert_editor_state(
9958 &r#"
9959 use some::modified;
9960
9961
9962 fn main() {
9963 ˇ println!("hello there");
9964
9965 println!("around the");
9966 println!("world");
9967 }
9968 "#
9969 .unindent(),
9970 );
9971
9972 cx.update_editor(|editor, cx| {
9973 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9974 });
9975
9976 cx.assert_editor_state(
9977 &r#"
9978 use some::modified;
9979
9980 ˇ
9981 fn main() {
9982 println!("hello there");
9983
9984 println!("around the");
9985 println!("world");
9986 }
9987 "#
9988 .unindent(),
9989 );
9990
9991 cx.update_editor(|editor, cx| {
9992 for _ in 0..3 {
9993 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9994 }
9995 });
9996
9997 cx.assert_editor_state(
9998 &r#"
9999 use some::modified;
10000
10001
10002 fn main() {
10003 ˇ println!("hello there");
10004
10005 println!("around the");
10006 println!("world");
10007 }
10008 "#
10009 .unindent(),
10010 );
10011
10012 cx.update_editor(|editor, cx| {
10013 editor.fold(&Fold, cx);
10014
10015 //Make sure that the fold only gets one hunk
10016 for _ in 0..4 {
10017 editor.go_to_next_hunk(&GoToHunk, cx);
10018 }
10019 });
10020
10021 cx.assert_editor_state(
10022 &r#"
10023 ˇuse some::modified;
10024
10025
10026 fn main() {
10027 println!("hello there");
10028
10029 println!("around the");
10030 println!("world");
10031 }
10032 "#
10033 .unindent(),
10034 );
10035}
10036
10037#[test]
10038fn test_split_words() {
10039 fn split(text: &str) -> Vec<&str> {
10040 split_words(text).collect()
10041 }
10042
10043 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
10044 assert_eq!(split("hello_world"), &["hello_", "world"]);
10045 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
10046 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
10047 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
10048 assert_eq!(split("helloworld"), &["helloworld"]);
10049
10050 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
10051}
10052
10053#[gpui::test]
10054async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
10055 init_test(cx, |_| {});
10056
10057 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
10058 let mut assert = |before, after| {
10059 let _state_context = cx.set_state(before);
10060 cx.update_editor(|editor, cx| {
10061 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
10062 });
10063 cx.assert_editor_state(after);
10064 };
10065
10066 // Outside bracket jumps to outside of matching bracket
10067 assert("console.logˇ(var);", "console.log(var)ˇ;");
10068 assert("console.log(var)ˇ;", "console.logˇ(var);");
10069
10070 // Inside bracket jumps to inside of matching bracket
10071 assert("console.log(ˇvar);", "console.log(varˇ);");
10072 assert("console.log(varˇ);", "console.log(ˇvar);");
10073
10074 // When outside a bracket and inside, favor jumping to the inside bracket
10075 assert(
10076 "console.log('foo', [1, 2, 3]ˇ);",
10077 "console.log(ˇ'foo', [1, 2, 3]);",
10078 );
10079 assert(
10080 "console.log(ˇ'foo', [1, 2, 3]);",
10081 "console.log('foo', [1, 2, 3]ˇ);",
10082 );
10083
10084 // Bias forward if two options are equally likely
10085 assert(
10086 "let result = curried_fun()ˇ();",
10087 "let result = curried_fun()()ˇ;",
10088 );
10089
10090 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
10091 assert(
10092 indoc! {"
10093 function test() {
10094 console.log('test')ˇ
10095 }"},
10096 indoc! {"
10097 function test() {
10098 console.logˇ('test')
10099 }"},
10100 );
10101}
10102
10103#[gpui::test]
10104async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
10105 init_test(cx, |_| {});
10106
10107 let fs = FakeFs::new(cx.executor());
10108 fs.insert_tree(
10109 "/a",
10110 json!({
10111 "main.rs": "fn main() { let a = 5; }",
10112 "other.rs": "// Test file",
10113 }),
10114 )
10115 .await;
10116 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10117
10118 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10119 language_registry.add(Arc::new(Language::new(
10120 LanguageConfig {
10121 name: "Rust".into(),
10122 matcher: LanguageMatcher {
10123 path_suffixes: vec!["rs".to_string()],
10124 ..Default::default()
10125 },
10126 brackets: BracketPairConfig {
10127 pairs: vec![BracketPair {
10128 start: "{".to_string(),
10129 end: "}".to_string(),
10130 close: true,
10131 surround: true,
10132 newline: true,
10133 }],
10134 disabled_scopes_by_bracket_ix: Vec::new(),
10135 },
10136 ..Default::default()
10137 },
10138 Some(tree_sitter_rust::LANGUAGE.into()),
10139 )));
10140 let mut fake_servers = language_registry.register_fake_lsp(
10141 "Rust",
10142 FakeLspAdapter {
10143 capabilities: lsp::ServerCapabilities {
10144 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
10145 first_trigger_character: "{".to_string(),
10146 more_trigger_character: None,
10147 }),
10148 ..Default::default()
10149 },
10150 ..Default::default()
10151 },
10152 );
10153
10154 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10155
10156 let cx = &mut VisualTestContext::from_window(*workspace, cx);
10157
10158 let worktree_id = workspace
10159 .update(cx, |workspace, cx| {
10160 workspace.project().update(cx, |project, cx| {
10161 project.worktrees(cx).next().unwrap().read(cx).id()
10162 })
10163 })
10164 .unwrap();
10165
10166 let buffer = project
10167 .update(cx, |project, cx| {
10168 project.open_local_buffer("/a/main.rs", cx)
10169 })
10170 .await
10171 .unwrap();
10172 cx.executor().run_until_parked();
10173 cx.executor().start_waiting();
10174 let fake_server = fake_servers.next().await.unwrap();
10175 let editor_handle = workspace
10176 .update(cx, |workspace, cx| {
10177 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
10178 })
10179 .unwrap()
10180 .await
10181 .unwrap()
10182 .downcast::<Editor>()
10183 .unwrap();
10184
10185 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
10186 assert_eq!(
10187 params.text_document_position.text_document.uri,
10188 lsp::Url::from_file_path("/a/main.rs").unwrap(),
10189 );
10190 assert_eq!(
10191 params.text_document_position.position,
10192 lsp::Position::new(0, 21),
10193 );
10194
10195 Ok(Some(vec![lsp::TextEdit {
10196 new_text: "]".to_string(),
10197 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10198 }]))
10199 });
10200
10201 editor_handle.update(cx, |editor, cx| {
10202 editor.focus(cx);
10203 editor.change_selections(None, cx, |s| {
10204 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
10205 });
10206 editor.handle_input("{", cx);
10207 });
10208
10209 cx.executor().run_until_parked();
10210
10211 buffer.update(cx, |buffer, _| {
10212 assert_eq!(
10213 buffer.text(),
10214 "fn main() { let a = {5}; }",
10215 "No extra braces from on type formatting should appear in the buffer"
10216 )
10217 });
10218}
10219
10220#[gpui::test]
10221async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
10222 init_test(cx, |_| {});
10223
10224 let fs = FakeFs::new(cx.executor());
10225 fs.insert_tree(
10226 "/a",
10227 json!({
10228 "main.rs": "fn main() { let a = 5; }",
10229 "other.rs": "// Test file",
10230 }),
10231 )
10232 .await;
10233
10234 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10235
10236 let server_restarts = Arc::new(AtomicUsize::new(0));
10237 let closure_restarts = Arc::clone(&server_restarts);
10238 let language_server_name = "test language server";
10239 let language_name: LanguageName = "Rust".into();
10240
10241 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10242 language_registry.add(Arc::new(Language::new(
10243 LanguageConfig {
10244 name: language_name.clone(),
10245 matcher: LanguageMatcher {
10246 path_suffixes: vec!["rs".to_string()],
10247 ..Default::default()
10248 },
10249 ..Default::default()
10250 },
10251 Some(tree_sitter_rust::LANGUAGE.into()),
10252 )));
10253 let mut fake_servers = language_registry.register_fake_lsp(
10254 "Rust",
10255 FakeLspAdapter {
10256 name: language_server_name,
10257 initialization_options: Some(json!({
10258 "testOptionValue": true
10259 })),
10260 initializer: Some(Box::new(move |fake_server| {
10261 let task_restarts = Arc::clone(&closure_restarts);
10262 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
10263 task_restarts.fetch_add(1, atomic::Ordering::Release);
10264 futures::future::ready(Ok(()))
10265 });
10266 })),
10267 ..Default::default()
10268 },
10269 );
10270
10271 let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10272 let _buffer = project
10273 .update(cx, |project, cx| {
10274 project.open_local_buffer("/a/main.rs", cx)
10275 })
10276 .await
10277 .unwrap();
10278 let _fake_server = fake_servers.next().await.unwrap();
10279 update_test_language_settings(cx, |language_settings| {
10280 language_settings.languages.insert(
10281 language_name.clone(),
10282 LanguageSettingsContent {
10283 tab_size: NonZeroU32::new(8),
10284 ..Default::default()
10285 },
10286 );
10287 });
10288 cx.executor().run_until_parked();
10289 assert_eq!(
10290 server_restarts.load(atomic::Ordering::Acquire),
10291 0,
10292 "Should not restart LSP server on an unrelated change"
10293 );
10294
10295 update_test_project_settings(cx, |project_settings| {
10296 project_settings.lsp.insert(
10297 "Some other server name".into(),
10298 LspSettings {
10299 binary: None,
10300 settings: None,
10301 initialization_options: Some(json!({
10302 "some other init value": false
10303 })),
10304 },
10305 );
10306 });
10307 cx.executor().run_until_parked();
10308 assert_eq!(
10309 server_restarts.load(atomic::Ordering::Acquire),
10310 0,
10311 "Should not restart LSP server on an unrelated LSP settings change"
10312 );
10313
10314 update_test_project_settings(cx, |project_settings| {
10315 project_settings.lsp.insert(
10316 language_server_name.into(),
10317 LspSettings {
10318 binary: None,
10319 settings: None,
10320 initialization_options: Some(json!({
10321 "anotherInitValue": false
10322 })),
10323 },
10324 );
10325 });
10326 cx.executor().run_until_parked();
10327 assert_eq!(
10328 server_restarts.load(atomic::Ordering::Acquire),
10329 1,
10330 "Should restart LSP server on a related LSP settings change"
10331 );
10332
10333 update_test_project_settings(cx, |project_settings| {
10334 project_settings.lsp.insert(
10335 language_server_name.into(),
10336 LspSettings {
10337 binary: None,
10338 settings: None,
10339 initialization_options: Some(json!({
10340 "anotherInitValue": false
10341 })),
10342 },
10343 );
10344 });
10345 cx.executor().run_until_parked();
10346 assert_eq!(
10347 server_restarts.load(atomic::Ordering::Acquire),
10348 1,
10349 "Should not restart LSP server on a related LSP settings change that is the same"
10350 );
10351
10352 update_test_project_settings(cx, |project_settings| {
10353 project_settings.lsp.insert(
10354 language_server_name.into(),
10355 LspSettings {
10356 binary: None,
10357 settings: None,
10358 initialization_options: None,
10359 },
10360 );
10361 });
10362 cx.executor().run_until_parked();
10363 assert_eq!(
10364 server_restarts.load(atomic::Ordering::Acquire),
10365 2,
10366 "Should restart LSP server on another related LSP settings change"
10367 );
10368}
10369
10370#[gpui::test]
10371async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
10372 init_test(cx, |_| {});
10373
10374 let mut cx = EditorLspTestContext::new_rust(
10375 lsp::ServerCapabilities {
10376 completion_provider: Some(lsp::CompletionOptions {
10377 trigger_characters: Some(vec![".".to_string()]),
10378 resolve_provider: Some(true),
10379 ..Default::default()
10380 }),
10381 ..Default::default()
10382 },
10383 cx,
10384 )
10385 .await;
10386
10387 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10388 cx.simulate_keystroke(".");
10389 let completion_item = lsp::CompletionItem {
10390 label: "some".into(),
10391 kind: Some(lsp::CompletionItemKind::SNIPPET),
10392 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
10393 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
10394 kind: lsp::MarkupKind::Markdown,
10395 value: "```rust\nSome(2)\n```".to_string(),
10396 })),
10397 deprecated: Some(false),
10398 sort_text: Some("fffffff2".to_string()),
10399 filter_text: Some("some".to_string()),
10400 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
10401 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10402 range: lsp::Range {
10403 start: lsp::Position {
10404 line: 0,
10405 character: 22,
10406 },
10407 end: lsp::Position {
10408 line: 0,
10409 character: 22,
10410 },
10411 },
10412 new_text: "Some(2)".to_string(),
10413 })),
10414 additional_text_edits: Some(vec![lsp::TextEdit {
10415 range: lsp::Range {
10416 start: lsp::Position {
10417 line: 0,
10418 character: 20,
10419 },
10420 end: lsp::Position {
10421 line: 0,
10422 character: 22,
10423 },
10424 },
10425 new_text: "".to_string(),
10426 }]),
10427 ..Default::default()
10428 };
10429
10430 let closure_completion_item = completion_item.clone();
10431 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10432 let task_completion_item = closure_completion_item.clone();
10433 async move {
10434 Ok(Some(lsp::CompletionResponse::Array(vec![
10435 task_completion_item,
10436 ])))
10437 }
10438 });
10439
10440 request.next().await;
10441
10442 cx.condition(|editor, _| editor.context_menu_visible())
10443 .await;
10444 let apply_additional_edits = cx.update_editor(|editor, cx| {
10445 editor
10446 .confirm_completion(&ConfirmCompletion::default(), cx)
10447 .unwrap()
10448 });
10449 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
10450
10451 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
10452 let task_completion_item = completion_item.clone();
10453 async move { Ok(task_completion_item) }
10454 })
10455 .next()
10456 .await
10457 .unwrap();
10458 apply_additional_edits.await.unwrap();
10459 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
10460}
10461
10462#[gpui::test]
10463async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
10464 init_test(cx, |_| {});
10465
10466 let mut cx = EditorLspTestContext::new(
10467 Language::new(
10468 LanguageConfig {
10469 matcher: LanguageMatcher {
10470 path_suffixes: vec!["jsx".into()],
10471 ..Default::default()
10472 },
10473 overrides: [(
10474 "element".into(),
10475 LanguageConfigOverride {
10476 word_characters: Override::Set(['-'].into_iter().collect()),
10477 ..Default::default()
10478 },
10479 )]
10480 .into_iter()
10481 .collect(),
10482 ..Default::default()
10483 },
10484 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10485 )
10486 .with_override_query("(jsx_self_closing_element) @element")
10487 .unwrap(),
10488 lsp::ServerCapabilities {
10489 completion_provider: Some(lsp::CompletionOptions {
10490 trigger_characters: Some(vec![":".to_string()]),
10491 ..Default::default()
10492 }),
10493 ..Default::default()
10494 },
10495 cx,
10496 )
10497 .await;
10498
10499 cx.lsp
10500 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
10501 Ok(Some(lsp::CompletionResponse::Array(vec![
10502 lsp::CompletionItem {
10503 label: "bg-blue".into(),
10504 ..Default::default()
10505 },
10506 lsp::CompletionItem {
10507 label: "bg-red".into(),
10508 ..Default::default()
10509 },
10510 lsp::CompletionItem {
10511 label: "bg-yellow".into(),
10512 ..Default::default()
10513 },
10514 ])))
10515 });
10516
10517 cx.set_state(r#"<p class="bgˇ" />"#);
10518
10519 // Trigger completion when typing a dash, because the dash is an extra
10520 // word character in the 'element' scope, which contains the cursor.
10521 cx.simulate_keystroke("-");
10522 cx.executor().run_until_parked();
10523 cx.update_editor(|editor, _| {
10524 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10525 assert_eq!(
10526 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10527 &["bg-red", "bg-blue", "bg-yellow"]
10528 );
10529 } else {
10530 panic!("expected completion menu to be open");
10531 }
10532 });
10533
10534 cx.simulate_keystroke("l");
10535 cx.executor().run_until_parked();
10536 cx.update_editor(|editor, _| {
10537 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10538 assert_eq!(
10539 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10540 &["bg-blue", "bg-yellow"]
10541 );
10542 } else {
10543 panic!("expected completion menu to be open");
10544 }
10545 });
10546
10547 // When filtering completions, consider the character after the '-' to
10548 // be the start of a subword.
10549 cx.set_state(r#"<p class="yelˇ" />"#);
10550 cx.simulate_keystroke("l");
10551 cx.executor().run_until_parked();
10552 cx.update_editor(|editor, _| {
10553 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10554 assert_eq!(
10555 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10556 &["bg-yellow"]
10557 );
10558 } else {
10559 panic!("expected completion menu to be open");
10560 }
10561 });
10562}
10563
10564#[gpui::test]
10565async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
10566 init_test(cx, |settings| {
10567 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
10568 FormatterList(vec![Formatter::Prettier].into()),
10569 ))
10570 });
10571
10572 let fs = FakeFs::new(cx.executor());
10573 fs.insert_file("/file.ts", Default::default()).await;
10574
10575 let project = Project::test(fs, ["/file.ts".as_ref()], cx).await;
10576 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10577
10578 language_registry.add(Arc::new(Language::new(
10579 LanguageConfig {
10580 name: "TypeScript".into(),
10581 matcher: LanguageMatcher {
10582 path_suffixes: vec!["ts".to_string()],
10583 ..Default::default()
10584 },
10585 ..Default::default()
10586 },
10587 Some(tree_sitter_rust::LANGUAGE.into()),
10588 )));
10589 update_test_language_settings(cx, |settings| {
10590 settings.defaults.prettier = Some(PrettierSettings {
10591 allowed: true,
10592 ..PrettierSettings::default()
10593 });
10594 });
10595
10596 let test_plugin = "test_plugin";
10597 let _ = language_registry.register_fake_lsp(
10598 "TypeScript",
10599 FakeLspAdapter {
10600 prettier_plugins: vec![test_plugin],
10601 ..Default::default()
10602 },
10603 );
10604
10605 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
10606 let buffer = project
10607 .update(cx, |project, cx| project.open_local_buffer("/file.ts", cx))
10608 .await
10609 .unwrap();
10610
10611 let buffer_text = "one\ntwo\nthree\n";
10612 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
10613 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
10614 editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
10615
10616 editor
10617 .update(cx, |editor, cx| {
10618 editor.perform_format(
10619 project.clone(),
10620 FormatTrigger::Manual,
10621 FormatTarget::Buffer,
10622 cx,
10623 )
10624 })
10625 .unwrap()
10626 .await;
10627 assert_eq!(
10628 editor.update(cx, |editor, cx| editor.text(cx)),
10629 buffer_text.to_string() + prettier_format_suffix,
10630 "Test prettier formatting was not applied to the original buffer text",
10631 );
10632
10633 update_test_language_settings(cx, |settings| {
10634 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
10635 });
10636 let format = editor.update(cx, |editor, cx| {
10637 editor.perform_format(
10638 project.clone(),
10639 FormatTrigger::Manual,
10640 FormatTarget::Buffer,
10641 cx,
10642 )
10643 });
10644 format.await.unwrap();
10645 assert_eq!(
10646 editor.update(cx, |editor, cx| editor.text(cx)),
10647 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
10648 "Autoformatting (via test prettier) was not applied to the original buffer text",
10649 );
10650}
10651
10652#[gpui::test]
10653async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
10654 init_test(cx, |_| {});
10655 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10656 let base_text = indoc! {r#"struct Row;
10657struct Row1;
10658struct Row2;
10659
10660struct Row4;
10661struct Row5;
10662struct Row6;
10663
10664struct Row8;
10665struct Row9;
10666struct Row10;"#};
10667
10668 // When addition hunks are not adjacent to carets, no hunk revert is performed
10669 assert_hunk_revert(
10670 indoc! {r#"struct Row;
10671 struct Row1;
10672 struct Row1.1;
10673 struct Row1.2;
10674 struct Row2;ˇ
10675
10676 struct Row4;
10677 struct Row5;
10678 struct Row6;
10679
10680 struct Row8;
10681 ˇstruct Row9;
10682 struct Row9.1;
10683 struct Row9.2;
10684 struct Row9.3;
10685 struct Row10;"#},
10686 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
10687 indoc! {r#"struct Row;
10688 struct Row1;
10689 struct Row1.1;
10690 struct Row1.2;
10691 struct Row2;ˇ
10692
10693 struct Row4;
10694 struct Row5;
10695 struct Row6;
10696
10697 struct Row8;
10698 ˇstruct Row9;
10699 struct Row9.1;
10700 struct Row9.2;
10701 struct Row9.3;
10702 struct Row10;"#},
10703 base_text,
10704 &mut cx,
10705 );
10706 // Same for selections
10707 assert_hunk_revert(
10708 indoc! {r#"struct Row;
10709 struct Row1;
10710 struct Row2;
10711 struct Row2.1;
10712 struct Row2.2;
10713 «ˇ
10714 struct Row4;
10715 struct» Row5;
10716 «struct Row6;
10717 ˇ»
10718 struct Row9.1;
10719 struct Row9.2;
10720 struct Row9.3;
10721 struct Row8;
10722 struct Row9;
10723 struct Row10;"#},
10724 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
10725 indoc! {r#"struct Row;
10726 struct Row1;
10727 struct Row2;
10728 struct Row2.1;
10729 struct Row2.2;
10730 «ˇ
10731 struct Row4;
10732 struct» Row5;
10733 «struct Row6;
10734 ˇ»
10735 struct Row9.1;
10736 struct Row9.2;
10737 struct Row9.3;
10738 struct Row8;
10739 struct Row9;
10740 struct Row10;"#},
10741 base_text,
10742 &mut cx,
10743 );
10744
10745 // When carets and selections intersect the addition hunks, those are reverted.
10746 // Adjacent carets got merged.
10747 assert_hunk_revert(
10748 indoc! {r#"struct Row;
10749 ˇ// something on the top
10750 struct Row1;
10751 struct Row2;
10752 struct Roˇw3.1;
10753 struct Row2.2;
10754 struct Row2.3;ˇ
10755
10756 struct Row4;
10757 struct ˇRow5.1;
10758 struct Row5.2;
10759 struct «Rowˇ»5.3;
10760 struct Row5;
10761 struct Row6;
10762 ˇ
10763 struct Row9.1;
10764 struct «Rowˇ»9.2;
10765 struct «ˇRow»9.3;
10766 struct Row8;
10767 struct Row9;
10768 «ˇ// something on bottom»
10769 struct Row10;"#},
10770 vec![
10771 DiffHunkStatus::Added,
10772 DiffHunkStatus::Added,
10773 DiffHunkStatus::Added,
10774 DiffHunkStatus::Added,
10775 DiffHunkStatus::Added,
10776 ],
10777 indoc! {r#"struct Row;
10778 ˇstruct Row1;
10779 struct Row2;
10780 ˇ
10781 struct Row4;
10782 ˇstruct Row5;
10783 struct Row6;
10784 ˇ
10785 ˇstruct Row8;
10786 struct Row9;
10787 ˇstruct Row10;"#},
10788 base_text,
10789 &mut cx,
10790 );
10791}
10792
10793#[gpui::test]
10794async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
10795 init_test(cx, |_| {});
10796 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10797 let base_text = indoc! {r#"struct Row;
10798struct Row1;
10799struct Row2;
10800
10801struct Row4;
10802struct Row5;
10803struct Row6;
10804
10805struct Row8;
10806struct Row9;
10807struct Row10;"#};
10808
10809 // Modification hunks behave the same as the addition ones.
10810 assert_hunk_revert(
10811 indoc! {r#"struct Row;
10812 struct Row1;
10813 struct Row33;
10814 ˇ
10815 struct Row4;
10816 struct Row5;
10817 struct Row6;
10818 ˇ
10819 struct Row99;
10820 struct Row9;
10821 struct Row10;"#},
10822 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
10823 indoc! {r#"struct Row;
10824 struct Row1;
10825 struct Row33;
10826 ˇ
10827 struct Row4;
10828 struct Row5;
10829 struct Row6;
10830 ˇ
10831 struct Row99;
10832 struct Row9;
10833 struct Row10;"#},
10834 base_text,
10835 &mut cx,
10836 );
10837 assert_hunk_revert(
10838 indoc! {r#"struct Row;
10839 struct Row1;
10840 struct Row33;
10841 «ˇ
10842 struct Row4;
10843 struct» Row5;
10844 «struct Row6;
10845 ˇ»
10846 struct Row99;
10847 struct Row9;
10848 struct Row10;"#},
10849 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
10850 indoc! {r#"struct Row;
10851 struct Row1;
10852 struct Row33;
10853 «ˇ
10854 struct Row4;
10855 struct» Row5;
10856 «struct Row6;
10857 ˇ»
10858 struct Row99;
10859 struct Row9;
10860 struct Row10;"#},
10861 base_text,
10862 &mut cx,
10863 );
10864
10865 assert_hunk_revert(
10866 indoc! {r#"ˇstruct Row1.1;
10867 struct Row1;
10868 «ˇstr»uct Row22;
10869
10870 struct ˇRow44;
10871 struct Row5;
10872 struct «Rˇ»ow66;ˇ
10873
10874 «struˇ»ct Row88;
10875 struct Row9;
10876 struct Row1011;ˇ"#},
10877 vec![
10878 DiffHunkStatus::Modified,
10879 DiffHunkStatus::Modified,
10880 DiffHunkStatus::Modified,
10881 DiffHunkStatus::Modified,
10882 DiffHunkStatus::Modified,
10883 DiffHunkStatus::Modified,
10884 ],
10885 indoc! {r#"struct Row;
10886 ˇstruct Row1;
10887 struct Row2;
10888 ˇ
10889 struct Row4;
10890 ˇstruct Row5;
10891 struct Row6;
10892 ˇ
10893 struct Row8;
10894 ˇstruct Row9;
10895 struct Row10;ˇ"#},
10896 base_text,
10897 &mut cx,
10898 );
10899}
10900
10901#[gpui::test]
10902async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
10903 init_test(cx, |_| {});
10904 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10905 let base_text = indoc! {r#"struct Row;
10906struct Row1;
10907struct Row2;
10908
10909struct Row4;
10910struct Row5;
10911struct Row6;
10912
10913struct Row8;
10914struct Row9;
10915struct Row10;"#};
10916
10917 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
10918 assert_hunk_revert(
10919 indoc! {r#"struct Row;
10920 struct Row2;
10921
10922 ˇstruct Row4;
10923 struct Row5;
10924 struct Row6;
10925 ˇ
10926 struct Row8;
10927 struct Row10;"#},
10928 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10929 indoc! {r#"struct Row;
10930 struct Row2;
10931
10932 ˇstruct Row4;
10933 struct Row5;
10934 struct Row6;
10935 ˇ
10936 struct Row8;
10937 struct Row10;"#},
10938 base_text,
10939 &mut cx,
10940 );
10941 assert_hunk_revert(
10942 indoc! {r#"struct Row;
10943 struct Row2;
10944
10945 «ˇstruct Row4;
10946 struct» Row5;
10947 «struct Row6;
10948 ˇ»
10949 struct Row8;
10950 struct Row10;"#},
10951 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10952 indoc! {r#"struct Row;
10953 struct Row2;
10954
10955 «ˇstruct Row4;
10956 struct» Row5;
10957 «struct Row6;
10958 ˇ»
10959 struct Row8;
10960 struct Row10;"#},
10961 base_text,
10962 &mut cx,
10963 );
10964
10965 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
10966 assert_hunk_revert(
10967 indoc! {r#"struct Row;
10968 ˇstruct Row2;
10969
10970 struct Row4;
10971 struct Row5;
10972 struct Row6;
10973
10974 struct Row8;ˇ
10975 struct Row10;"#},
10976 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10977 indoc! {r#"struct Row;
10978 struct Row1;
10979 ˇstruct Row2;
10980
10981 struct Row4;
10982 struct Row5;
10983 struct Row6;
10984
10985 struct Row8;ˇ
10986 struct Row9;
10987 struct Row10;"#},
10988 base_text,
10989 &mut cx,
10990 );
10991 assert_hunk_revert(
10992 indoc! {r#"struct Row;
10993 struct Row2«ˇ;
10994 struct Row4;
10995 struct» Row5;
10996 «struct Row6;
10997
10998 struct Row8;ˇ»
10999 struct Row10;"#},
11000 vec![
11001 DiffHunkStatus::Removed,
11002 DiffHunkStatus::Removed,
11003 DiffHunkStatus::Removed,
11004 ],
11005 indoc! {r#"struct Row;
11006 struct Row1;
11007 struct Row2«ˇ;
11008
11009 struct Row4;
11010 struct» Row5;
11011 «struct Row6;
11012
11013 struct Row8;ˇ»
11014 struct Row9;
11015 struct Row10;"#},
11016 base_text,
11017 &mut cx,
11018 );
11019}
11020
11021#[gpui::test]
11022async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
11023 init_test(cx, |_| {});
11024
11025 let cols = 4;
11026 let rows = 10;
11027 let sample_text_1 = sample_text(rows, cols, 'a');
11028 assert_eq!(
11029 sample_text_1,
11030 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11031 );
11032 let sample_text_2 = sample_text(rows, cols, 'l');
11033 assert_eq!(
11034 sample_text_2,
11035 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11036 );
11037 let sample_text_3 = sample_text(rows, cols, 'v');
11038 assert_eq!(
11039 sample_text_3,
11040 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11041 );
11042
11043 fn diff_every_buffer_row(
11044 buffer: &Model<Buffer>,
11045 sample_text: String,
11046 cols: usize,
11047 cx: &mut gpui::TestAppContext,
11048 ) {
11049 // revert first character in each row, creating one large diff hunk per buffer
11050 let is_first_char = |offset: usize| offset % cols == 0;
11051 buffer.update(cx, |buffer, cx| {
11052 buffer.set_text(
11053 sample_text
11054 .chars()
11055 .enumerate()
11056 .map(|(offset, c)| if is_first_char(offset) { 'X' } else { c })
11057 .collect::<String>(),
11058 cx,
11059 );
11060 buffer.set_diff_base(Some(sample_text), cx);
11061 });
11062 cx.executor().run_until_parked();
11063 }
11064
11065 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
11066 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
11067
11068 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
11069 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
11070
11071 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
11072 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
11073
11074 let multibuffer = cx.new_model(|cx| {
11075 let mut multibuffer = MultiBuffer::new(ReadWrite);
11076 multibuffer.push_excerpts(
11077 buffer_1.clone(),
11078 [
11079 ExcerptRange {
11080 context: Point::new(0, 0)..Point::new(3, 0),
11081 primary: None,
11082 },
11083 ExcerptRange {
11084 context: Point::new(5, 0)..Point::new(7, 0),
11085 primary: None,
11086 },
11087 ExcerptRange {
11088 context: Point::new(9, 0)..Point::new(10, 4),
11089 primary: None,
11090 },
11091 ],
11092 cx,
11093 );
11094 multibuffer.push_excerpts(
11095 buffer_2.clone(),
11096 [
11097 ExcerptRange {
11098 context: Point::new(0, 0)..Point::new(3, 0),
11099 primary: None,
11100 },
11101 ExcerptRange {
11102 context: Point::new(5, 0)..Point::new(7, 0),
11103 primary: None,
11104 },
11105 ExcerptRange {
11106 context: Point::new(9, 0)..Point::new(10, 4),
11107 primary: None,
11108 },
11109 ],
11110 cx,
11111 );
11112 multibuffer.push_excerpts(
11113 buffer_3.clone(),
11114 [
11115 ExcerptRange {
11116 context: Point::new(0, 0)..Point::new(3, 0),
11117 primary: None,
11118 },
11119 ExcerptRange {
11120 context: Point::new(5, 0)..Point::new(7, 0),
11121 primary: None,
11122 },
11123 ExcerptRange {
11124 context: Point::new(9, 0)..Point::new(10, 4),
11125 primary: None,
11126 },
11127 ],
11128 cx,
11129 );
11130 multibuffer
11131 });
11132
11133 let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
11134 editor.update(cx, |editor, cx| {
11135 assert_eq!(editor.text(cx), "XaaaXbbbX\nccXc\ndXdd\n\nhXhh\nXiiiXjjjX\n\nXlllXmmmX\nnnXn\noXoo\n\nsXss\nXtttXuuuX\n\nXvvvXwwwX\nxxXx\nyXyy\n\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X\n");
11136 editor.select_all(&SelectAll, cx);
11137 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
11138 });
11139 cx.executor().run_until_parked();
11140 // When all ranges are selected, all buffer hunks are reverted.
11141 editor.update(cx, |editor, cx| {
11142 assert_eq!(editor.text(cx), "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n\n\nllll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu\n\n\nvvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}\n\n");
11143 });
11144 buffer_1.update(cx, |buffer, _| {
11145 assert_eq!(buffer.text(), sample_text_1);
11146 });
11147 buffer_2.update(cx, |buffer, _| {
11148 assert_eq!(buffer.text(), sample_text_2);
11149 });
11150 buffer_3.update(cx, |buffer, _| {
11151 assert_eq!(buffer.text(), sample_text_3);
11152 });
11153
11154 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
11155 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
11156 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
11157 editor.update(cx, |editor, cx| {
11158 editor.change_selections(None, cx, |s| {
11159 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
11160 });
11161 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
11162 });
11163 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
11164 // but not affect buffer_2 and its related excerpts.
11165 editor.update(cx, |editor, cx| {
11166 assert_eq!(
11167 editor.text(cx),
11168 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n\n\nXlllXmmmX\nnnXn\noXoo\nXpppXqqqX\nrrXr\nsXss\nXtttXuuuX\n\n\nXvvvXwwwX\nxxXx\nyXyy\nXzzzX{{{X\n||X|\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X\n\n"
11169 );
11170 });
11171 buffer_1.update(cx, |buffer, _| {
11172 assert_eq!(buffer.text(), sample_text_1);
11173 });
11174 buffer_2.update(cx, |buffer, _| {
11175 assert_eq!(
11176 buffer.text(),
11177 "XlllXmmmX\nnnXn\noXoo\nXpppXqqqX\nrrXr\nsXss\nXtttXuuuX"
11178 );
11179 });
11180 buffer_3.update(cx, |buffer, _| {
11181 assert_eq!(
11182 buffer.text(),
11183 "XvvvXwwwX\nxxXx\nyXyy\nXzzzX{{{X\n||X|\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X"
11184 );
11185 });
11186}
11187
11188#[gpui::test]
11189async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
11190 init_test(cx, |_| {});
11191
11192 let cols = 4;
11193 let rows = 10;
11194 let sample_text_1 = sample_text(rows, cols, 'a');
11195 assert_eq!(
11196 sample_text_1,
11197 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11198 );
11199 let sample_text_2 = sample_text(rows, cols, 'l');
11200 assert_eq!(
11201 sample_text_2,
11202 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11203 );
11204 let sample_text_3 = sample_text(rows, cols, 'v');
11205 assert_eq!(
11206 sample_text_3,
11207 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11208 );
11209
11210 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
11211 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
11212 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
11213
11214 let multi_buffer = cx.new_model(|cx| {
11215 let mut multibuffer = MultiBuffer::new(ReadWrite);
11216 multibuffer.push_excerpts(
11217 buffer_1.clone(),
11218 [
11219 ExcerptRange {
11220 context: Point::new(0, 0)..Point::new(3, 0),
11221 primary: None,
11222 },
11223 ExcerptRange {
11224 context: Point::new(5, 0)..Point::new(7, 0),
11225 primary: None,
11226 },
11227 ExcerptRange {
11228 context: Point::new(9, 0)..Point::new(10, 4),
11229 primary: None,
11230 },
11231 ],
11232 cx,
11233 );
11234 multibuffer.push_excerpts(
11235 buffer_2.clone(),
11236 [
11237 ExcerptRange {
11238 context: Point::new(0, 0)..Point::new(3, 0),
11239 primary: None,
11240 },
11241 ExcerptRange {
11242 context: Point::new(5, 0)..Point::new(7, 0),
11243 primary: None,
11244 },
11245 ExcerptRange {
11246 context: Point::new(9, 0)..Point::new(10, 4),
11247 primary: None,
11248 },
11249 ],
11250 cx,
11251 );
11252 multibuffer.push_excerpts(
11253 buffer_3.clone(),
11254 [
11255 ExcerptRange {
11256 context: Point::new(0, 0)..Point::new(3, 0),
11257 primary: None,
11258 },
11259 ExcerptRange {
11260 context: Point::new(5, 0)..Point::new(7, 0),
11261 primary: None,
11262 },
11263 ExcerptRange {
11264 context: Point::new(9, 0)..Point::new(10, 4),
11265 primary: None,
11266 },
11267 ],
11268 cx,
11269 );
11270 multibuffer
11271 });
11272
11273 let fs = FakeFs::new(cx.executor());
11274 fs.insert_tree(
11275 "/a",
11276 json!({
11277 "main.rs": sample_text_1,
11278 "other.rs": sample_text_2,
11279 "lib.rs": sample_text_3,
11280 }),
11281 )
11282 .await;
11283 let project = Project::test(fs, ["/a".as_ref()], cx).await;
11284 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
11285 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11286 let multi_buffer_editor = cx.new_view(|cx| {
11287 Editor::new(
11288 EditorMode::Full,
11289 multi_buffer,
11290 Some(project.clone()),
11291 true,
11292 cx,
11293 )
11294 });
11295 let multibuffer_item_id = workspace
11296 .update(cx, |workspace, cx| {
11297 assert!(
11298 workspace.active_item(cx).is_none(),
11299 "active item should be None before the first item is added"
11300 );
11301 workspace.add_item_to_active_pane(
11302 Box::new(multi_buffer_editor.clone()),
11303 None,
11304 true,
11305 cx,
11306 );
11307 let active_item = workspace
11308 .active_item(cx)
11309 .expect("should have an active item after adding the multi buffer");
11310 assert!(
11311 !active_item.is_singleton(cx),
11312 "A multi buffer was expected to active after adding"
11313 );
11314 active_item.item_id()
11315 })
11316 .unwrap();
11317 cx.executor().run_until_parked();
11318
11319 multi_buffer_editor.update(cx, |editor, cx| {
11320 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
11321 editor.open_excerpts(&OpenExcerpts, cx);
11322 });
11323 cx.executor().run_until_parked();
11324 let first_item_id = workspace
11325 .update(cx, |workspace, cx| {
11326 let active_item = workspace
11327 .active_item(cx)
11328 .expect("should have an active item after navigating into the 1st buffer");
11329 let first_item_id = active_item.item_id();
11330 assert_ne!(
11331 first_item_id, multibuffer_item_id,
11332 "Should navigate into the 1st buffer and activate it"
11333 );
11334 assert!(
11335 active_item.is_singleton(cx),
11336 "New active item should be a singleton buffer"
11337 );
11338 assert_eq!(
11339 active_item
11340 .act_as::<Editor>(cx)
11341 .expect("should have navigated into an editor for the 1st buffer")
11342 .read(cx)
11343 .text(cx),
11344 sample_text_1
11345 );
11346
11347 workspace
11348 .go_back(workspace.active_pane().downgrade(), cx)
11349 .detach_and_log_err(cx);
11350
11351 first_item_id
11352 })
11353 .unwrap();
11354 cx.executor().run_until_parked();
11355 workspace
11356 .update(cx, |workspace, cx| {
11357 let active_item = workspace
11358 .active_item(cx)
11359 .expect("should have an active item after navigating back");
11360 assert_eq!(
11361 active_item.item_id(),
11362 multibuffer_item_id,
11363 "Should navigate back to the multi buffer"
11364 );
11365 assert!(!active_item.is_singleton(cx));
11366 })
11367 .unwrap();
11368
11369 multi_buffer_editor.update(cx, |editor, cx| {
11370 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
11371 s.select_ranges(Some(39..40))
11372 });
11373 editor.open_excerpts(&OpenExcerpts, cx);
11374 });
11375 cx.executor().run_until_parked();
11376 let second_item_id = workspace
11377 .update(cx, |workspace, cx| {
11378 let active_item = workspace
11379 .active_item(cx)
11380 .expect("should have an active item after navigating into the 2nd buffer");
11381 let second_item_id = active_item.item_id();
11382 assert_ne!(
11383 second_item_id, multibuffer_item_id,
11384 "Should navigate away from the multibuffer"
11385 );
11386 assert_ne!(
11387 second_item_id, first_item_id,
11388 "Should navigate into the 2nd buffer and activate it"
11389 );
11390 assert!(
11391 active_item.is_singleton(cx),
11392 "New active item should be a singleton buffer"
11393 );
11394 assert_eq!(
11395 active_item
11396 .act_as::<Editor>(cx)
11397 .expect("should have navigated into an editor")
11398 .read(cx)
11399 .text(cx),
11400 sample_text_2
11401 );
11402
11403 workspace
11404 .go_back(workspace.active_pane().downgrade(), cx)
11405 .detach_and_log_err(cx);
11406
11407 second_item_id
11408 })
11409 .unwrap();
11410 cx.executor().run_until_parked();
11411 workspace
11412 .update(cx, |workspace, cx| {
11413 let active_item = workspace
11414 .active_item(cx)
11415 .expect("should have an active item after navigating back from the 2nd buffer");
11416 assert_eq!(
11417 active_item.item_id(),
11418 multibuffer_item_id,
11419 "Should navigate back from the 2nd buffer to the multi buffer"
11420 );
11421 assert!(!active_item.is_singleton(cx));
11422 })
11423 .unwrap();
11424
11425 multi_buffer_editor.update(cx, |editor, cx| {
11426 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
11427 s.select_ranges(Some(60..70))
11428 });
11429 editor.open_excerpts(&OpenExcerpts, cx);
11430 });
11431 cx.executor().run_until_parked();
11432 workspace
11433 .update(cx, |workspace, cx| {
11434 let active_item = workspace
11435 .active_item(cx)
11436 .expect("should have an active item after navigating into the 3rd buffer");
11437 let third_item_id = active_item.item_id();
11438 assert_ne!(
11439 third_item_id, multibuffer_item_id,
11440 "Should navigate into the 3rd buffer and activate it"
11441 );
11442 assert_ne!(third_item_id, first_item_id);
11443 assert_ne!(third_item_id, second_item_id);
11444 assert!(
11445 active_item.is_singleton(cx),
11446 "New active item should be a singleton buffer"
11447 );
11448 assert_eq!(
11449 active_item
11450 .act_as::<Editor>(cx)
11451 .expect("should have navigated into an editor")
11452 .read(cx)
11453 .text(cx),
11454 sample_text_3
11455 );
11456
11457 workspace
11458 .go_back(workspace.active_pane().downgrade(), cx)
11459 .detach_and_log_err(cx);
11460 })
11461 .unwrap();
11462 cx.executor().run_until_parked();
11463 workspace
11464 .update(cx, |workspace, cx| {
11465 let active_item = workspace
11466 .active_item(cx)
11467 .expect("should have an active item after navigating back from the 3rd buffer");
11468 assert_eq!(
11469 active_item.item_id(),
11470 multibuffer_item_id,
11471 "Should navigate back from the 3rd buffer to the multi buffer"
11472 );
11473 assert!(!active_item.is_singleton(cx));
11474 })
11475 .unwrap();
11476}
11477
11478#[gpui::test]
11479async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
11480 init_test(cx, |_| {});
11481
11482 let mut cx = EditorTestContext::new(cx).await;
11483
11484 let diff_base = r#"
11485 use some::mod;
11486
11487 const A: u32 = 42;
11488
11489 fn main() {
11490 println!("hello");
11491
11492 println!("world");
11493 }
11494 "#
11495 .unindent();
11496
11497 cx.set_state(
11498 &r#"
11499 use some::modified;
11500
11501 ˇ
11502 fn main() {
11503 println!("hello there");
11504
11505 println!("around the");
11506 println!("world");
11507 }
11508 "#
11509 .unindent(),
11510 );
11511
11512 cx.set_diff_base(Some(&diff_base));
11513 executor.run_until_parked();
11514
11515 cx.update_editor(|editor, cx| {
11516 editor.go_to_next_hunk(&GoToHunk, cx);
11517 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11518 });
11519 executor.run_until_parked();
11520 cx.assert_diff_hunks(
11521 r#"
11522 use some::modified;
11523
11524
11525 fn main() {
11526 - println!("hello");
11527 + println!("hello there");
11528
11529 println!("around the");
11530 println!("world");
11531 }
11532 "#
11533 .unindent(),
11534 );
11535
11536 cx.update_editor(|editor, cx| {
11537 for _ in 0..3 {
11538 editor.go_to_next_hunk(&GoToHunk, cx);
11539 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11540 }
11541 });
11542 executor.run_until_parked();
11543 cx.assert_editor_state(
11544 &r#"
11545 use some::modified;
11546
11547 ˇ
11548 fn main() {
11549 println!("hello there");
11550
11551 println!("around the");
11552 println!("world");
11553 }
11554 "#
11555 .unindent(),
11556 );
11557
11558 cx.assert_diff_hunks(
11559 r#"
11560 - use some::mod;
11561 + use some::modified;
11562
11563 - const A: u32 = 42;
11564
11565 fn main() {
11566 - println!("hello");
11567 + println!("hello there");
11568
11569 + println!("around the");
11570 println!("world");
11571 }
11572 "#
11573 .unindent(),
11574 );
11575
11576 cx.update_editor(|editor, cx| {
11577 editor.cancel(&Cancel, cx);
11578 });
11579
11580 cx.assert_diff_hunks(
11581 r#"
11582 use some::modified;
11583
11584
11585 fn main() {
11586 println!("hello there");
11587
11588 println!("around the");
11589 println!("world");
11590 }
11591 "#
11592 .unindent(),
11593 );
11594}
11595
11596#[gpui::test]
11597async fn test_diff_base_change_with_expanded_diff_hunks(
11598 executor: BackgroundExecutor,
11599 cx: &mut gpui::TestAppContext,
11600) {
11601 init_test(cx, |_| {});
11602
11603 let mut cx = EditorTestContext::new(cx).await;
11604
11605 let diff_base = r#"
11606 use some::mod1;
11607 use some::mod2;
11608
11609 const A: u32 = 42;
11610 const B: u32 = 42;
11611 const C: u32 = 42;
11612
11613 fn main() {
11614 println!("hello");
11615
11616 println!("world");
11617 }
11618 "#
11619 .unindent();
11620
11621 cx.set_state(
11622 &r#"
11623 use some::mod2;
11624
11625 const A: u32 = 42;
11626 const C: u32 = 42;
11627
11628 fn main(ˇ) {
11629 //println!("hello");
11630
11631 println!("world");
11632 //
11633 //
11634 }
11635 "#
11636 .unindent(),
11637 );
11638
11639 cx.set_diff_base(Some(&diff_base));
11640 executor.run_until_parked();
11641
11642 cx.update_editor(|editor, cx| {
11643 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11644 });
11645 executor.run_until_parked();
11646 cx.assert_diff_hunks(
11647 r#"
11648 - use some::mod1;
11649 use some::mod2;
11650
11651 const A: u32 = 42;
11652 - const B: u32 = 42;
11653 const C: u32 = 42;
11654
11655 fn main() {
11656 - println!("hello");
11657 + //println!("hello");
11658
11659 println!("world");
11660 + //
11661 + //
11662 }
11663 "#
11664 .unindent(),
11665 );
11666
11667 cx.set_diff_base(Some("new diff base!"));
11668 executor.run_until_parked();
11669 cx.assert_diff_hunks(
11670 r#"
11671 use some::mod2;
11672
11673 const A: u32 = 42;
11674 const C: u32 = 42;
11675
11676 fn main() {
11677 //println!("hello");
11678
11679 println!("world");
11680 //
11681 //
11682 }
11683 "#
11684 .unindent(),
11685 );
11686
11687 cx.update_editor(|editor, cx| {
11688 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11689 });
11690 executor.run_until_parked();
11691 cx.assert_diff_hunks(
11692 r#"
11693 - new diff base!
11694 + use some::mod2;
11695 +
11696 + const A: u32 = 42;
11697 + const C: u32 = 42;
11698 +
11699 + fn main() {
11700 + //println!("hello");
11701 +
11702 + println!("world");
11703 + //
11704 + //
11705 + }
11706 "#
11707 .unindent(),
11708 );
11709}
11710
11711#[gpui::test]
11712async fn test_fold_unfold_diff_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
11713 init_test(cx, |_| {});
11714
11715 let mut cx = EditorTestContext::new(cx).await;
11716
11717 let diff_base = r#"
11718 use some::mod1;
11719 use some::mod2;
11720
11721 const A: u32 = 42;
11722 const B: u32 = 42;
11723 const C: u32 = 42;
11724
11725 fn main() {
11726 println!("hello");
11727
11728 println!("world");
11729 }
11730
11731 fn another() {
11732 println!("another");
11733 }
11734
11735 fn another2() {
11736 println!("another2");
11737 }
11738 "#
11739 .unindent();
11740
11741 cx.set_state(
11742 &r#"
11743 «use some::mod2;
11744
11745 const A: u32 = 42;
11746 const C: u32 = 42;
11747
11748 fn main() {
11749 //println!("hello");
11750
11751 println!("world");
11752 //
11753 //ˇ»
11754 }
11755
11756 fn another() {
11757 println!("another");
11758 println!("another");
11759 }
11760
11761 println!("another2");
11762 }
11763 "#
11764 .unindent(),
11765 );
11766
11767 cx.set_diff_base(Some(&diff_base));
11768 executor.run_until_parked();
11769
11770 cx.update_editor(|editor, cx| {
11771 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11772 });
11773 executor.run_until_parked();
11774
11775 cx.assert_diff_hunks(
11776 r#"
11777 - use some::mod1;
11778 use some::mod2;
11779
11780 const A: u32 = 42;
11781 - const B: u32 = 42;
11782 const C: u32 = 42;
11783
11784 fn main() {
11785 - println!("hello");
11786 + //println!("hello");
11787
11788 println!("world");
11789 + //
11790 + //
11791 }
11792
11793 fn another() {
11794 println!("another");
11795 + println!("another");
11796 }
11797
11798 - fn another2() {
11799 println!("another2");
11800 }
11801 "#
11802 .unindent(),
11803 );
11804
11805 // Fold across some of the diff hunks. They should no longer appear expanded.
11806 cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx));
11807 cx.executor().run_until_parked();
11808
11809 // Hunks are not shown if their position is within a fold
11810 cx.assert_diff_hunks(
11811 r#"
11812 use some::mod2;
11813
11814 const A: u32 = 42;
11815 const C: u32 = 42;
11816
11817 fn main() {
11818 //println!("hello");
11819
11820 println!("world");
11821 //
11822 //
11823 }
11824
11825 fn another() {
11826 println!("another");
11827 + println!("another");
11828 }
11829
11830 - fn another2() {
11831 println!("another2");
11832 }
11833 "#
11834 .unindent(),
11835 );
11836
11837 cx.update_editor(|editor, cx| {
11838 editor.select_all(&SelectAll, cx);
11839 editor.unfold_lines(&UnfoldLines, cx);
11840 });
11841 cx.executor().run_until_parked();
11842
11843 // The deletions reappear when unfolding.
11844 cx.assert_diff_hunks(
11845 r#"
11846 - use some::mod1;
11847 use some::mod2;
11848
11849 const A: u32 = 42;
11850 - const B: u32 = 42;
11851 const C: u32 = 42;
11852
11853 fn main() {
11854 - println!("hello");
11855 + //println!("hello");
11856
11857 println!("world");
11858 + //
11859 + //
11860 }
11861
11862 fn another() {
11863 println!("another");
11864 + println!("another");
11865 }
11866
11867 - fn another2() {
11868 println!("another2");
11869 }
11870 "#
11871 .unindent(),
11872 );
11873}
11874
11875#[gpui::test]
11876async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
11877 init_test(cx, |_| {});
11878
11879 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
11880 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
11881 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
11882 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
11883 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
11884 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
11885
11886 let buffer_1 = cx.new_model(|cx| {
11887 let mut buffer = Buffer::local(file_1_new.to_string(), cx);
11888 buffer.set_diff_base(Some(file_1_old.into()), cx);
11889 buffer
11890 });
11891 let buffer_2 = cx.new_model(|cx| {
11892 let mut buffer = Buffer::local(file_2_new.to_string(), cx);
11893 buffer.set_diff_base(Some(file_2_old.into()), cx);
11894 buffer
11895 });
11896 let buffer_3 = cx.new_model(|cx| {
11897 let mut buffer = Buffer::local(file_3_new.to_string(), cx);
11898 buffer.set_diff_base(Some(file_3_old.into()), cx);
11899 buffer
11900 });
11901
11902 let multi_buffer = cx.new_model(|cx| {
11903 let mut multibuffer = MultiBuffer::new(ReadWrite);
11904 multibuffer.push_excerpts(
11905 buffer_1.clone(),
11906 [
11907 ExcerptRange {
11908 context: Point::new(0, 0)..Point::new(3, 0),
11909 primary: None,
11910 },
11911 ExcerptRange {
11912 context: Point::new(5, 0)..Point::new(7, 0),
11913 primary: None,
11914 },
11915 ExcerptRange {
11916 context: Point::new(9, 0)..Point::new(10, 3),
11917 primary: None,
11918 },
11919 ],
11920 cx,
11921 );
11922 multibuffer.push_excerpts(
11923 buffer_2.clone(),
11924 [
11925 ExcerptRange {
11926 context: Point::new(0, 0)..Point::new(3, 0),
11927 primary: None,
11928 },
11929 ExcerptRange {
11930 context: Point::new(5, 0)..Point::new(7, 0),
11931 primary: None,
11932 },
11933 ExcerptRange {
11934 context: Point::new(9, 0)..Point::new(10, 3),
11935 primary: None,
11936 },
11937 ],
11938 cx,
11939 );
11940 multibuffer.push_excerpts(
11941 buffer_3.clone(),
11942 [
11943 ExcerptRange {
11944 context: Point::new(0, 0)..Point::new(3, 0),
11945 primary: None,
11946 },
11947 ExcerptRange {
11948 context: Point::new(5, 0)..Point::new(7, 0),
11949 primary: None,
11950 },
11951 ExcerptRange {
11952 context: Point::new(9, 0)..Point::new(10, 3),
11953 primary: None,
11954 },
11955 ],
11956 cx,
11957 );
11958 multibuffer
11959 });
11960
11961 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
11962 let mut cx = EditorTestContext::for_editor(editor, cx).await;
11963 cx.run_until_parked();
11964
11965 cx.assert_editor_state(
11966 &"
11967 ˇaaa
11968 ccc
11969 ddd
11970
11971 ggg
11972 hhh
11973
11974
11975 lll
11976 mmm
11977 NNN
11978
11979 qqq
11980 rrr
11981
11982 uuu
11983 111
11984 222
11985 333
11986
11987 666
11988 777
11989
11990 000
11991 !!!"
11992 .unindent(),
11993 );
11994
11995 cx.update_editor(|editor, cx| {
11996 editor.select_all(&SelectAll, cx);
11997 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11998 });
11999 cx.executor().run_until_parked();
12000
12001 cx.assert_diff_hunks(
12002 "
12003 aaa
12004 - bbb
12005 ccc
12006 ddd
12007
12008 ggg
12009 hhh
12010
12011
12012 lll
12013 mmm
12014 - nnn
12015 + NNN
12016
12017 qqq
12018 rrr
12019
12020 uuu
12021 111
12022 222
12023 333
12024
12025 + 666
12026 777
12027
12028 000
12029 !!!"
12030 .unindent(),
12031 );
12032}
12033
12034#[gpui::test]
12035async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut gpui::TestAppContext) {
12036 init_test(cx, |_| {});
12037
12038 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
12039 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\n";
12040
12041 let buffer = cx.new_model(|cx| {
12042 let mut buffer = Buffer::local(text.to_string(), cx);
12043 buffer.set_diff_base(Some(base.into()), cx);
12044 buffer
12045 });
12046
12047 let multi_buffer = cx.new_model(|cx| {
12048 let mut multibuffer = MultiBuffer::new(ReadWrite);
12049 multibuffer.push_excerpts(
12050 buffer.clone(),
12051 [
12052 ExcerptRange {
12053 context: Point::new(0, 0)..Point::new(2, 0),
12054 primary: None,
12055 },
12056 ExcerptRange {
12057 context: Point::new(5, 0)..Point::new(7, 0),
12058 primary: None,
12059 },
12060 ],
12061 cx,
12062 );
12063 multibuffer
12064 });
12065
12066 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
12067 let mut cx = EditorTestContext::for_editor(editor, cx).await;
12068 cx.run_until_parked();
12069
12070 cx.update_editor(|editor, cx| editor.expand_all_hunk_diffs(&Default::default(), cx));
12071 cx.executor().run_until_parked();
12072
12073 cx.assert_diff_hunks(
12074 "
12075 aaa
12076 - bbb
12077 + BBB
12078
12079 - ddd
12080 - eee
12081 + EEE
12082 fff
12083 "
12084 .unindent(),
12085 );
12086}
12087
12088#[gpui::test]
12089async fn test_edits_around_expanded_insertion_hunks(
12090 executor: BackgroundExecutor,
12091 cx: &mut gpui::TestAppContext,
12092) {
12093 init_test(cx, |_| {});
12094
12095 let mut cx = EditorTestContext::new(cx).await;
12096
12097 let diff_base = r#"
12098 use some::mod1;
12099 use some::mod2;
12100
12101 const A: u32 = 42;
12102
12103 fn main() {
12104 println!("hello");
12105
12106 println!("world");
12107 }
12108 "#
12109 .unindent();
12110 executor.run_until_parked();
12111 cx.set_state(
12112 &r#"
12113 use some::mod1;
12114 use some::mod2;
12115
12116 const A: u32 = 42;
12117 const B: u32 = 42;
12118 const C: u32 = 42;
12119 ˇ
12120
12121 fn main() {
12122 println!("hello");
12123
12124 println!("world");
12125 }
12126 "#
12127 .unindent(),
12128 );
12129
12130 cx.set_diff_base(Some(&diff_base));
12131 executor.run_until_parked();
12132
12133 cx.update_editor(|editor, cx| {
12134 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12135 });
12136 executor.run_until_parked();
12137
12138 cx.assert_diff_hunks(
12139 r#"
12140 use some::mod1;
12141 use some::mod2;
12142
12143 const A: u32 = 42;
12144 + const B: u32 = 42;
12145 + const C: u32 = 42;
12146 +
12147
12148 fn main() {
12149 println!("hello");
12150
12151 println!("world");
12152 }
12153 "#
12154 .unindent(),
12155 );
12156
12157 cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx));
12158 executor.run_until_parked();
12159
12160 cx.assert_diff_hunks(
12161 r#"
12162 use some::mod1;
12163 use some::mod2;
12164
12165 const A: u32 = 42;
12166 + const B: u32 = 42;
12167 + const C: u32 = 42;
12168 + const D: u32 = 42;
12169 +
12170
12171 fn main() {
12172 println!("hello");
12173
12174 println!("world");
12175 }
12176 "#
12177 .unindent(),
12178 );
12179
12180 cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx));
12181 executor.run_until_parked();
12182
12183 cx.assert_diff_hunks(
12184 r#"
12185 use some::mod1;
12186 use some::mod2;
12187
12188 const A: u32 = 42;
12189 + const B: u32 = 42;
12190 + const C: u32 = 42;
12191 + const D: u32 = 42;
12192 + const E: u32 = 42;
12193 +
12194
12195 fn main() {
12196 println!("hello");
12197
12198 println!("world");
12199 }
12200 "#
12201 .unindent(),
12202 );
12203
12204 cx.update_editor(|editor, cx| {
12205 editor.delete_line(&DeleteLine, cx);
12206 });
12207 executor.run_until_parked();
12208
12209 cx.assert_diff_hunks(
12210 r#"
12211 use some::mod1;
12212 use some::mod2;
12213
12214 const A: u32 = 42;
12215 + const B: u32 = 42;
12216 + const C: u32 = 42;
12217 + const D: u32 = 42;
12218 + const E: u32 = 42;
12219
12220 fn main() {
12221 println!("hello");
12222
12223 println!("world");
12224 }
12225 "#
12226 .unindent(),
12227 );
12228
12229 cx.update_editor(|editor, cx| {
12230 editor.move_up(&MoveUp, cx);
12231 editor.delete_line(&DeleteLine, cx);
12232 editor.move_up(&MoveUp, cx);
12233 editor.delete_line(&DeleteLine, cx);
12234 editor.move_up(&MoveUp, cx);
12235 editor.delete_line(&DeleteLine, cx);
12236 });
12237 executor.run_until_parked();
12238 cx.assert_editor_state(
12239 &r#"
12240 use some::mod1;
12241 use some::mod2;
12242
12243 const A: u32 = 42;
12244 const B: u32 = 42;
12245 ˇ
12246 fn main() {
12247 println!("hello");
12248
12249 println!("world");
12250 }
12251 "#
12252 .unindent(),
12253 );
12254
12255 cx.assert_diff_hunks(
12256 r#"
12257 use some::mod1;
12258 use some::mod2;
12259
12260 const A: u32 = 42;
12261 + const B: u32 = 42;
12262
12263 fn main() {
12264 println!("hello");
12265
12266 println!("world");
12267 }
12268 "#
12269 .unindent(),
12270 );
12271
12272 cx.update_editor(|editor, cx| {
12273 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, cx);
12274 editor.delete_line(&DeleteLine, cx);
12275 });
12276 executor.run_until_parked();
12277 cx.assert_diff_hunks(
12278 r#"
12279 use some::mod1;
12280 - use some::mod2;
12281 -
12282 - const A: u32 = 42;
12283
12284 fn main() {
12285 println!("hello");
12286
12287 println!("world");
12288 }
12289 "#
12290 .unindent(),
12291 );
12292}
12293
12294#[gpui::test]
12295async fn test_edits_around_expanded_deletion_hunks(
12296 executor: BackgroundExecutor,
12297 cx: &mut gpui::TestAppContext,
12298) {
12299 init_test(cx, |_| {});
12300
12301 let mut cx = EditorTestContext::new(cx).await;
12302
12303 let diff_base = r#"
12304 use some::mod1;
12305 use some::mod2;
12306
12307 const A: u32 = 42;
12308 const B: u32 = 42;
12309 const C: u32 = 42;
12310
12311
12312 fn main() {
12313 println!("hello");
12314
12315 println!("world");
12316 }
12317 "#
12318 .unindent();
12319 executor.run_until_parked();
12320 cx.set_state(
12321 &r#"
12322 use some::mod1;
12323 use some::mod2;
12324
12325 ˇconst B: u32 = 42;
12326 const C: u32 = 42;
12327
12328
12329 fn main() {
12330 println!("hello");
12331
12332 println!("world");
12333 }
12334 "#
12335 .unindent(),
12336 );
12337
12338 cx.set_diff_base(Some(&diff_base));
12339 executor.run_until_parked();
12340
12341 cx.update_editor(|editor, cx| {
12342 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12343 });
12344 executor.run_until_parked();
12345
12346 cx.assert_diff_hunks(
12347 r#"
12348 use some::mod1;
12349 use some::mod2;
12350
12351 - const A: u32 = 42;
12352 const B: u32 = 42;
12353 const C: u32 = 42;
12354
12355
12356 fn main() {
12357 println!("hello");
12358
12359 println!("world");
12360 }
12361 "#
12362 .unindent(),
12363 );
12364
12365 cx.update_editor(|editor, cx| {
12366 editor.delete_line(&DeleteLine, cx);
12367 });
12368 executor.run_until_parked();
12369 cx.assert_editor_state(
12370 &r#"
12371 use some::mod1;
12372 use some::mod2;
12373
12374 ˇconst C: u32 = 42;
12375
12376
12377 fn main() {
12378 println!("hello");
12379
12380 println!("world");
12381 }
12382 "#
12383 .unindent(),
12384 );
12385 cx.assert_diff_hunks(
12386 r#"
12387 use some::mod1;
12388 use some::mod2;
12389
12390 - const A: u32 = 42;
12391 - const B: u32 = 42;
12392 const C: u32 = 42;
12393
12394
12395 fn main() {
12396 println!("hello");
12397
12398 println!("world");
12399 }
12400 "#
12401 .unindent(),
12402 );
12403
12404 cx.update_editor(|editor, cx| {
12405 editor.delete_line(&DeleteLine, cx);
12406 });
12407 executor.run_until_parked();
12408 cx.assert_editor_state(
12409 &r#"
12410 use some::mod1;
12411 use some::mod2;
12412
12413 ˇ
12414
12415 fn main() {
12416 println!("hello");
12417
12418 println!("world");
12419 }
12420 "#
12421 .unindent(),
12422 );
12423 cx.assert_diff_hunks(
12424 r#"
12425 use some::mod1;
12426 use some::mod2;
12427
12428 - const A: u32 = 42;
12429 - const B: u32 = 42;
12430 - const C: u32 = 42;
12431
12432
12433 fn main() {
12434 println!("hello");
12435
12436 println!("world");
12437 }
12438 "#
12439 .unindent(),
12440 );
12441
12442 cx.update_editor(|editor, cx| {
12443 editor.handle_input("replacement", cx);
12444 });
12445 executor.run_until_parked();
12446 cx.assert_editor_state(
12447 &r#"
12448 use some::mod1;
12449 use some::mod2;
12450
12451 replacementˇ
12452
12453 fn main() {
12454 println!("hello");
12455
12456 println!("world");
12457 }
12458 "#
12459 .unindent(),
12460 );
12461 cx.assert_diff_hunks(
12462 r#"
12463 use some::mod1;
12464 use some::mod2;
12465
12466 - const A: u32 = 42;
12467 - const B: u32 = 42;
12468 - const C: u32 = 42;
12469 -
12470 + replacement
12471
12472 fn main() {
12473 println!("hello");
12474
12475 println!("world");
12476 }
12477 "#
12478 .unindent(),
12479 );
12480}
12481
12482#[gpui::test]
12483async fn test_edit_after_expanded_modification_hunk(
12484 executor: BackgroundExecutor,
12485 cx: &mut gpui::TestAppContext,
12486) {
12487 init_test(cx, |_| {});
12488
12489 let mut cx = EditorTestContext::new(cx).await;
12490
12491 let diff_base = r#"
12492 use some::mod1;
12493 use some::mod2;
12494
12495 const A: u32 = 42;
12496 const B: u32 = 42;
12497 const C: u32 = 42;
12498 const D: u32 = 42;
12499
12500
12501 fn main() {
12502 println!("hello");
12503
12504 println!("world");
12505 }"#
12506 .unindent();
12507
12508 cx.set_state(
12509 &r#"
12510 use some::mod1;
12511 use some::mod2;
12512
12513 const A: u32 = 42;
12514 const B: u32 = 42;
12515 const C: u32 = 43ˇ
12516 const D: u32 = 42;
12517
12518
12519 fn main() {
12520 println!("hello");
12521
12522 println!("world");
12523 }"#
12524 .unindent(),
12525 );
12526
12527 cx.set_diff_base(Some(&diff_base));
12528 executor.run_until_parked();
12529 cx.update_editor(|editor, cx| {
12530 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12531 });
12532 executor.run_until_parked();
12533
12534 cx.assert_diff_hunks(
12535 r#"
12536 use some::mod1;
12537 use some::mod2;
12538
12539 const A: u32 = 42;
12540 const B: u32 = 42;
12541 - const C: u32 = 42;
12542 + const C: u32 = 43
12543 const D: u32 = 42;
12544
12545
12546 fn main() {
12547 println!("hello");
12548
12549 println!("world");
12550 }"#
12551 .unindent(),
12552 );
12553
12554 cx.update_editor(|editor, cx| {
12555 editor.handle_input("\nnew_line\n", cx);
12556 });
12557 executor.run_until_parked();
12558
12559 cx.assert_diff_hunks(
12560 r#"
12561 use some::mod1;
12562 use some::mod2;
12563
12564 const A: u32 = 42;
12565 const B: u32 = 42;
12566 - const C: u32 = 42;
12567 + const C: u32 = 43
12568 + new_line
12569 +
12570 const D: u32 = 42;
12571
12572
12573 fn main() {
12574 println!("hello");
12575
12576 println!("world");
12577 }"#
12578 .unindent(),
12579 );
12580}
12581
12582async fn setup_indent_guides_editor(
12583 text: &str,
12584 cx: &mut gpui::TestAppContext,
12585) -> (BufferId, EditorTestContext) {
12586 init_test(cx, |_| {});
12587
12588 let mut cx = EditorTestContext::new(cx).await;
12589
12590 let buffer_id = cx.update_editor(|editor, cx| {
12591 editor.set_text(text, cx);
12592 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
12593
12594 buffer_ids[0]
12595 });
12596
12597 (buffer_id, cx)
12598}
12599
12600fn assert_indent_guides(
12601 range: Range<u32>,
12602 expected: Vec<IndentGuide>,
12603 active_indices: Option<Vec<usize>>,
12604 cx: &mut EditorTestContext,
12605) {
12606 let indent_guides = cx.update_editor(|editor, cx| {
12607 let snapshot = editor.snapshot(cx).display_snapshot;
12608 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
12609 MultiBufferRow(range.start)..MultiBufferRow(range.end),
12610 true,
12611 &snapshot,
12612 cx,
12613 );
12614
12615 indent_guides.sort_by(|a, b| {
12616 a.depth.cmp(&b.depth).then(
12617 a.start_row
12618 .cmp(&b.start_row)
12619 .then(a.end_row.cmp(&b.end_row)),
12620 )
12621 });
12622 indent_guides
12623 });
12624
12625 if let Some(expected) = active_indices {
12626 let active_indices = cx.update_editor(|editor, cx| {
12627 let snapshot = editor.snapshot(cx).display_snapshot;
12628 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, cx)
12629 });
12630
12631 assert_eq!(
12632 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
12633 expected,
12634 "Active indent guide indices do not match"
12635 );
12636 }
12637
12638 let expected: Vec<_> = expected
12639 .into_iter()
12640 .map(|guide| MultiBufferIndentGuide {
12641 multibuffer_row_range: MultiBufferRow(guide.start_row)..MultiBufferRow(guide.end_row),
12642 buffer: guide,
12643 })
12644 .collect();
12645
12646 assert_eq!(indent_guides, expected, "Indent guides do not match");
12647}
12648
12649fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
12650 IndentGuide {
12651 buffer_id,
12652 start_row,
12653 end_row,
12654 depth,
12655 tab_size: 4,
12656 settings: IndentGuideSettings {
12657 enabled: true,
12658 line_width: 1,
12659 active_line_width: 1,
12660 ..Default::default()
12661 },
12662 }
12663}
12664
12665#[gpui::test]
12666async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
12667 let (buffer_id, mut cx) = setup_indent_guides_editor(
12668 &"
12669 fn main() {
12670 let a = 1;
12671 }"
12672 .unindent(),
12673 cx,
12674 )
12675 .await;
12676
12677 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
12678}
12679
12680#[gpui::test]
12681async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) {
12682 let (buffer_id, mut cx) = setup_indent_guides_editor(
12683 &"
12684 fn main() {
12685 let a = 1;
12686 let b = 2;
12687 }"
12688 .unindent(),
12689 cx,
12690 )
12691 .await;
12692
12693 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
12694}
12695
12696#[gpui::test]
12697async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) {
12698 let (buffer_id, mut cx) = setup_indent_guides_editor(
12699 &"
12700 fn main() {
12701 let a = 1;
12702 if a == 3 {
12703 let b = 2;
12704 } else {
12705 let c = 3;
12706 }
12707 }"
12708 .unindent(),
12709 cx,
12710 )
12711 .await;
12712
12713 assert_indent_guides(
12714 0..8,
12715 vec![
12716 indent_guide(buffer_id, 1, 6, 0),
12717 indent_guide(buffer_id, 3, 3, 1),
12718 indent_guide(buffer_id, 5, 5, 1),
12719 ],
12720 None,
12721 &mut cx,
12722 );
12723}
12724
12725#[gpui::test]
12726async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) {
12727 let (buffer_id, mut cx) = setup_indent_guides_editor(
12728 &"
12729 fn main() {
12730 let a = 1;
12731 let b = 2;
12732 let c = 3;
12733 }"
12734 .unindent(),
12735 cx,
12736 )
12737 .await;
12738
12739 assert_indent_guides(
12740 0..5,
12741 vec![
12742 indent_guide(buffer_id, 1, 3, 0),
12743 indent_guide(buffer_id, 2, 2, 1),
12744 ],
12745 None,
12746 &mut cx,
12747 );
12748}
12749
12750#[gpui::test]
12751async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext) {
12752 let (buffer_id, mut cx) = setup_indent_guides_editor(
12753 &"
12754 fn main() {
12755 let a = 1;
12756
12757 let c = 3;
12758 }"
12759 .unindent(),
12760 cx,
12761 )
12762 .await;
12763
12764 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
12765}
12766
12767#[gpui::test]
12768async fn test_indent_guide_complex(cx: &mut gpui::TestAppContext) {
12769 let (buffer_id, mut cx) = setup_indent_guides_editor(
12770 &"
12771 fn main() {
12772 let a = 1;
12773
12774 let c = 3;
12775
12776 if a == 3 {
12777 let b = 2;
12778 } else {
12779 let c = 3;
12780 }
12781 }"
12782 .unindent(),
12783 cx,
12784 )
12785 .await;
12786
12787 assert_indent_guides(
12788 0..11,
12789 vec![
12790 indent_guide(buffer_id, 1, 9, 0),
12791 indent_guide(buffer_id, 6, 6, 1),
12792 indent_guide(buffer_id, 8, 8, 1),
12793 ],
12794 None,
12795 &mut cx,
12796 );
12797}
12798
12799#[gpui::test]
12800async fn test_indent_guide_starts_off_screen(cx: &mut gpui::TestAppContext) {
12801 let (buffer_id, mut cx) = setup_indent_guides_editor(
12802 &"
12803 fn main() {
12804 let a = 1;
12805
12806 let c = 3;
12807
12808 if a == 3 {
12809 let b = 2;
12810 } else {
12811 let c = 3;
12812 }
12813 }"
12814 .unindent(),
12815 cx,
12816 )
12817 .await;
12818
12819 assert_indent_guides(
12820 1..11,
12821 vec![
12822 indent_guide(buffer_id, 1, 9, 0),
12823 indent_guide(buffer_id, 6, 6, 1),
12824 indent_guide(buffer_id, 8, 8, 1),
12825 ],
12826 None,
12827 &mut cx,
12828 );
12829}
12830
12831#[gpui::test]
12832async fn test_indent_guide_ends_off_screen(cx: &mut gpui::TestAppContext) {
12833 let (buffer_id, mut cx) = setup_indent_guides_editor(
12834 &"
12835 fn main() {
12836 let a = 1;
12837
12838 let c = 3;
12839
12840 if a == 3 {
12841 let b = 2;
12842 } else {
12843 let c = 3;
12844 }
12845 }"
12846 .unindent(),
12847 cx,
12848 )
12849 .await;
12850
12851 assert_indent_guides(
12852 1..10,
12853 vec![
12854 indent_guide(buffer_id, 1, 9, 0),
12855 indent_guide(buffer_id, 6, 6, 1),
12856 indent_guide(buffer_id, 8, 8, 1),
12857 ],
12858 None,
12859 &mut cx,
12860 );
12861}
12862
12863#[gpui::test]
12864async fn test_indent_guide_without_brackets(cx: &mut gpui::TestAppContext) {
12865 let (buffer_id, mut cx) = setup_indent_guides_editor(
12866 &"
12867 block1
12868 block2
12869 block3
12870 block4
12871 block2
12872 block1
12873 block1"
12874 .unindent(),
12875 cx,
12876 )
12877 .await;
12878
12879 assert_indent_guides(
12880 1..10,
12881 vec![
12882 indent_guide(buffer_id, 1, 4, 0),
12883 indent_guide(buffer_id, 2, 3, 1),
12884 indent_guide(buffer_id, 3, 3, 2),
12885 ],
12886 None,
12887 &mut cx,
12888 );
12889}
12890
12891#[gpui::test]
12892async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext) {
12893 let (buffer_id, mut cx) = setup_indent_guides_editor(
12894 &"
12895 block1
12896 block2
12897 block3
12898
12899 block1
12900 block1"
12901 .unindent(),
12902 cx,
12903 )
12904 .await;
12905
12906 assert_indent_guides(
12907 0..6,
12908 vec![
12909 indent_guide(buffer_id, 1, 2, 0),
12910 indent_guide(buffer_id, 2, 2, 1),
12911 ],
12912 None,
12913 &mut cx,
12914 );
12915}
12916
12917#[gpui::test]
12918async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext) {
12919 let (buffer_id, mut cx) = setup_indent_guides_editor(
12920 &"
12921 block1
12922
12923
12924
12925 block2
12926 "
12927 .unindent(),
12928 cx,
12929 )
12930 .await;
12931
12932 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
12933}
12934
12935#[gpui::test]
12936async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) {
12937 let (buffer_id, mut cx) = setup_indent_guides_editor(
12938 &"
12939 def a:
12940 \tb = 3
12941 \tif True:
12942 \t\tc = 4
12943 \t\td = 5
12944 \tprint(b)
12945 "
12946 .unindent(),
12947 cx,
12948 )
12949 .await;
12950
12951 assert_indent_guides(
12952 0..6,
12953 vec![
12954 indent_guide(buffer_id, 1, 6, 0),
12955 indent_guide(buffer_id, 3, 4, 1),
12956 ],
12957 None,
12958 &mut cx,
12959 );
12960}
12961
12962#[gpui::test]
12963async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
12964 let (buffer_id, mut cx) = setup_indent_guides_editor(
12965 &"
12966 fn main() {
12967 let a = 1;
12968 }"
12969 .unindent(),
12970 cx,
12971 )
12972 .await;
12973
12974 cx.update_editor(|editor, cx| {
12975 editor.change_selections(None, cx, |s| {
12976 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
12977 });
12978 });
12979
12980 assert_indent_guides(
12981 0..3,
12982 vec![indent_guide(buffer_id, 1, 1, 0)],
12983 Some(vec![0]),
12984 &mut cx,
12985 );
12986}
12987
12988#[gpui::test]
12989async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppContext) {
12990 let (buffer_id, mut cx) = setup_indent_guides_editor(
12991 &"
12992 fn main() {
12993 if 1 == 2 {
12994 let a = 1;
12995 }
12996 }"
12997 .unindent(),
12998 cx,
12999 )
13000 .await;
13001
13002 cx.update_editor(|editor, cx| {
13003 editor.change_selections(None, cx, |s| {
13004 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13005 });
13006 });
13007
13008 assert_indent_guides(
13009 0..4,
13010 vec![
13011 indent_guide(buffer_id, 1, 3, 0),
13012 indent_guide(buffer_id, 2, 2, 1),
13013 ],
13014 Some(vec![1]),
13015 &mut cx,
13016 );
13017
13018 cx.update_editor(|editor, cx| {
13019 editor.change_selections(None, cx, |s| {
13020 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13021 });
13022 });
13023
13024 assert_indent_guides(
13025 0..4,
13026 vec![
13027 indent_guide(buffer_id, 1, 3, 0),
13028 indent_guide(buffer_id, 2, 2, 1),
13029 ],
13030 Some(vec![1]),
13031 &mut cx,
13032 );
13033
13034 cx.update_editor(|editor, cx| {
13035 editor.change_selections(None, cx, |s| {
13036 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
13037 });
13038 });
13039
13040 assert_indent_guides(
13041 0..4,
13042 vec![
13043 indent_guide(buffer_id, 1, 3, 0),
13044 indent_guide(buffer_id, 2, 2, 1),
13045 ],
13046 Some(vec![0]),
13047 &mut cx,
13048 );
13049}
13050
13051#[gpui::test]
13052async fn test_active_indent_guide_empty_line(cx: &mut gpui::TestAppContext) {
13053 let (buffer_id, mut cx) = setup_indent_guides_editor(
13054 &"
13055 fn main() {
13056 let a = 1;
13057
13058 let b = 2;
13059 }"
13060 .unindent(),
13061 cx,
13062 )
13063 .await;
13064
13065 cx.update_editor(|editor, cx| {
13066 editor.change_selections(None, cx, |s| {
13067 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13068 });
13069 });
13070
13071 assert_indent_guides(
13072 0..5,
13073 vec![indent_guide(buffer_id, 1, 3, 0)],
13074 Some(vec![0]),
13075 &mut cx,
13076 );
13077}
13078
13079#[gpui::test]
13080async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppContext) {
13081 let (buffer_id, mut cx) = setup_indent_guides_editor(
13082 &"
13083 def m:
13084 a = 1
13085 pass"
13086 .unindent(),
13087 cx,
13088 )
13089 .await;
13090
13091 cx.update_editor(|editor, cx| {
13092 editor.change_selections(None, cx, |s| {
13093 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13094 });
13095 });
13096
13097 assert_indent_guides(
13098 0..3,
13099 vec![indent_guide(buffer_id, 1, 2, 0)],
13100 Some(vec![0]),
13101 &mut cx,
13102 );
13103}
13104
13105#[gpui::test]
13106fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
13107 init_test(cx, |_| {});
13108
13109 let editor = cx.add_window(|cx| {
13110 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
13111 build_editor(buffer, cx)
13112 });
13113
13114 let render_args = Arc::new(Mutex::new(None));
13115 let snapshot = editor
13116 .update(cx, |editor, cx| {
13117 let snapshot = editor.buffer().read(cx).snapshot(cx);
13118 let range =
13119 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
13120
13121 struct RenderArgs {
13122 row: MultiBufferRow,
13123 folded: bool,
13124 callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
13125 }
13126
13127 let crease = Crease::new(
13128 range,
13129 FoldPlaceholder::test(),
13130 {
13131 let toggle_callback = render_args.clone();
13132 move |row, folded, callback, _cx| {
13133 *toggle_callback.lock() = Some(RenderArgs {
13134 row,
13135 folded,
13136 callback,
13137 });
13138 div()
13139 }
13140 },
13141 |_row, _folded, _cx| div(),
13142 );
13143
13144 editor.insert_creases(Some(crease), cx);
13145 let snapshot = editor.snapshot(cx);
13146 let _div = snapshot.render_fold_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
13147 snapshot
13148 })
13149 .unwrap();
13150
13151 let render_args = render_args.lock().take().unwrap();
13152 assert_eq!(render_args.row, MultiBufferRow(1));
13153 assert!(!render_args.folded);
13154 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13155
13156 cx.update_window(*editor, |_, cx| (render_args.callback)(true, cx))
13157 .unwrap();
13158 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13159 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
13160
13161 cx.update_window(*editor, |_, cx| (render_args.callback)(false, cx))
13162 .unwrap();
13163 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13164 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13165}
13166
13167#[gpui::test]
13168async fn test_input_text(cx: &mut gpui::TestAppContext) {
13169 init_test(cx, |_| {});
13170 let mut cx = EditorTestContext::new(cx).await;
13171
13172 cx.set_state(
13173 &r#"ˇone
13174 two
13175
13176 three
13177 fourˇ
13178 five
13179
13180 siˇx"#
13181 .unindent(),
13182 );
13183
13184 cx.dispatch_action(HandleInput(String::new()));
13185 cx.assert_editor_state(
13186 &r#"ˇone
13187 two
13188
13189 three
13190 fourˇ
13191 five
13192
13193 siˇx"#
13194 .unindent(),
13195 );
13196
13197 cx.dispatch_action(HandleInput("AAAA".to_string()));
13198 cx.assert_editor_state(
13199 &r#"AAAAˇone
13200 two
13201
13202 three
13203 fourAAAAˇ
13204 five
13205
13206 siAAAAˇx"#
13207 .unindent(),
13208 );
13209}
13210
13211#[gpui::test]
13212async fn test_scroll_cursor_center_top_bottom(cx: &mut gpui::TestAppContext) {
13213 init_test(cx, |_| {});
13214
13215 let mut cx = EditorTestContext::new(cx).await;
13216 cx.set_state(
13217 r#"let foo = 1;
13218let foo = 2;
13219let foo = 3;
13220let fooˇ = 4;
13221let foo = 5;
13222let foo = 6;
13223let foo = 7;
13224let foo = 8;
13225let foo = 9;
13226let foo = 10;
13227let foo = 11;
13228let foo = 12;
13229let foo = 13;
13230let foo = 14;
13231let foo = 15;"#,
13232 );
13233
13234 cx.update_editor(|e, cx| {
13235 assert_eq!(
13236 e.next_scroll_position,
13237 NextScrollCursorCenterTopBottom::Center,
13238 "Default next scroll direction is center",
13239 );
13240
13241 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13242 assert_eq!(
13243 e.next_scroll_position,
13244 NextScrollCursorCenterTopBottom::Top,
13245 "After center, next scroll direction should be top",
13246 );
13247
13248 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13249 assert_eq!(
13250 e.next_scroll_position,
13251 NextScrollCursorCenterTopBottom::Bottom,
13252 "After top, next scroll direction should be bottom",
13253 );
13254
13255 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13256 assert_eq!(
13257 e.next_scroll_position,
13258 NextScrollCursorCenterTopBottom::Center,
13259 "After bottom, scrolling should start over",
13260 );
13261
13262 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13263 assert_eq!(
13264 e.next_scroll_position,
13265 NextScrollCursorCenterTopBottom::Top,
13266 "Scrolling continues if retriggered fast enough"
13267 );
13268 });
13269
13270 cx.executor()
13271 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
13272 cx.executor().run_until_parked();
13273 cx.update_editor(|e, _| {
13274 assert_eq!(
13275 e.next_scroll_position,
13276 NextScrollCursorCenterTopBottom::Center,
13277 "If scrolling is not triggered fast enough, it should reset"
13278 );
13279 });
13280}
13281
13282#[gpui::test]
13283async fn test_goto_definition_with_find_all_references_fallback(cx: &mut gpui::TestAppContext) {
13284 init_test(cx, |_| {});
13285 let mut cx = EditorLspTestContext::new_rust(
13286 lsp::ServerCapabilities {
13287 definition_provider: Some(lsp::OneOf::Left(true)),
13288 references_provider: Some(lsp::OneOf::Left(true)),
13289 ..lsp::ServerCapabilities::default()
13290 },
13291 cx,
13292 )
13293 .await;
13294
13295 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
13296 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
13297 move |params, _| async move {
13298 if empty_go_to_definition {
13299 Ok(None)
13300 } else {
13301 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
13302 uri: params.text_document_position_params.text_document.uri,
13303 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
13304 })))
13305 }
13306 },
13307 );
13308 let references =
13309 cx.lsp
13310 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
13311 Ok(Some(vec![lsp::Location {
13312 uri: params.text_document_position.text_document.uri,
13313 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
13314 }]))
13315 });
13316 (go_to_definition, references)
13317 };
13318
13319 cx.set_state(
13320 &r#"fn one() {
13321 let mut a = ˇtwo();
13322 }
13323
13324 fn two() {}"#
13325 .unindent(),
13326 );
13327 set_up_lsp_handlers(false, &mut cx);
13328 let navigated = cx
13329 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13330 .await
13331 .expect("Failed to navigate to definition");
13332 assert_eq!(
13333 navigated,
13334 Navigated::Yes,
13335 "Should have navigated to definition from the GetDefinition response"
13336 );
13337 cx.assert_editor_state(
13338 &r#"fn one() {
13339 let mut a = two();
13340 }
13341
13342 fn «twoˇ»() {}"#
13343 .unindent(),
13344 );
13345
13346 let editors = cx.update_workspace(|workspace, cx| {
13347 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13348 });
13349 cx.update_editor(|_, test_editor_cx| {
13350 assert_eq!(
13351 editors.len(),
13352 1,
13353 "Initially, only one, test, editor should be open in the workspace"
13354 );
13355 assert_eq!(
13356 test_editor_cx.view(),
13357 editors.last().expect("Asserted len is 1")
13358 );
13359 });
13360
13361 set_up_lsp_handlers(true, &mut cx);
13362 let navigated = cx
13363 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13364 .await
13365 .expect("Failed to navigate to lookup references");
13366 assert_eq!(
13367 navigated,
13368 Navigated::Yes,
13369 "Should have navigated to references as a fallback after empty GoToDefinition response"
13370 );
13371 // We should not change the selections in the existing file,
13372 // if opening another milti buffer with the references
13373 cx.assert_editor_state(
13374 &r#"fn one() {
13375 let mut a = two();
13376 }
13377
13378 fn «twoˇ»() {}"#
13379 .unindent(),
13380 );
13381 let editors = cx.update_workspace(|workspace, cx| {
13382 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13383 });
13384 cx.update_editor(|_, test_editor_cx| {
13385 assert_eq!(
13386 editors.len(),
13387 2,
13388 "After falling back to references search, we open a new editor with the results"
13389 );
13390 let references_fallback_text = editors
13391 .into_iter()
13392 .find(|new_editor| new_editor != test_editor_cx.view())
13393 .expect("Should have one non-test editor now")
13394 .read(test_editor_cx)
13395 .text(test_editor_cx);
13396 assert_eq!(
13397 references_fallback_text, "fn one() {\n let mut a = two();\n}",
13398 "Should use the range from the references response and not the GoToDefinition one"
13399 );
13400 });
13401}
13402
13403#[gpui::test]
13404async fn test_find_enclosing_node_with_task(cx: &mut gpui::TestAppContext) {
13405 init_test(cx, |_| {});
13406
13407 let language = Arc::new(Language::new(
13408 LanguageConfig::default(),
13409 Some(tree_sitter_rust::LANGUAGE.into()),
13410 ));
13411
13412 let text = r#"
13413 #[cfg(test)]
13414 mod tests() {
13415 #[test]
13416 fn runnable_1() {
13417 let a = 1;
13418 }
13419
13420 #[test]
13421 fn runnable_2() {
13422 let a = 1;
13423 let b = 2;
13424 }
13425 }
13426 "#
13427 .unindent();
13428
13429 let fs = FakeFs::new(cx.executor());
13430 fs.insert_file("/file.rs", Default::default()).await;
13431
13432 let project = Project::test(fs, ["/a".as_ref()], cx).await;
13433 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
13434 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
13435 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
13436 let multi_buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx));
13437
13438 let editor = cx.new_view(|cx| {
13439 Editor::new(
13440 EditorMode::Full,
13441 multi_buffer,
13442 Some(project.clone()),
13443 true,
13444 cx,
13445 )
13446 });
13447
13448 editor.update(cx, |editor, cx| {
13449 editor.tasks.insert(
13450 (buffer.read(cx).remote_id(), 3),
13451 RunnableTasks {
13452 templates: vec![],
13453 offset: MultiBufferOffset(43),
13454 column: 0,
13455 extra_variables: HashMap::default(),
13456 context_range: BufferOffset(43)..BufferOffset(85),
13457 },
13458 );
13459 editor.tasks.insert(
13460 (buffer.read(cx).remote_id(), 8),
13461 RunnableTasks {
13462 templates: vec![],
13463 offset: MultiBufferOffset(86),
13464 column: 0,
13465 extra_variables: HashMap::default(),
13466 context_range: BufferOffset(86)..BufferOffset(191),
13467 },
13468 );
13469
13470 // Test finding task when cursor is inside function body
13471 editor.change_selections(None, cx, |s| {
13472 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
13473 });
13474 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
13475 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
13476
13477 // Test finding task when cursor is on function name
13478 editor.change_selections(None, cx, |s| {
13479 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
13480 });
13481 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
13482 assert_eq!(row, 8, "Should find task when cursor is on function name");
13483 });
13484}
13485
13486fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
13487 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
13488 point..point
13489}
13490
13491fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
13492 let (text, ranges) = marked_text_ranges(marked_text, true);
13493 assert_eq!(view.text(cx), text);
13494 assert_eq!(
13495 view.selections.ranges(cx),
13496 ranges,
13497 "Assert selections are {}",
13498 marked_text
13499 );
13500}
13501
13502pub fn handle_signature_help_request(
13503 cx: &mut EditorLspTestContext,
13504 mocked_response: lsp::SignatureHelp,
13505) -> impl Future<Output = ()> {
13506 let mut request =
13507 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
13508 let mocked_response = mocked_response.clone();
13509 async move { Ok(Some(mocked_response)) }
13510 });
13511
13512 async move {
13513 request.next().await;
13514 }
13515}
13516
13517/// Handle completion request passing a marked string specifying where the completion
13518/// should be triggered from using '|' character, what range should be replaced, and what completions
13519/// should be returned using '<' and '>' to delimit the range
13520pub fn handle_completion_request(
13521 cx: &mut EditorLspTestContext,
13522 marked_string: &str,
13523 completions: Vec<&'static str>,
13524 counter: Arc<AtomicUsize>,
13525) -> impl Future<Output = ()> {
13526 let complete_from_marker: TextRangeMarker = '|'.into();
13527 let replace_range_marker: TextRangeMarker = ('<', '>').into();
13528 let (_, mut marked_ranges) = marked_text_ranges_by(
13529 marked_string,
13530 vec![complete_from_marker.clone(), replace_range_marker.clone()],
13531 );
13532
13533 let complete_from_position =
13534 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
13535 let replace_range =
13536 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
13537
13538 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
13539 let completions = completions.clone();
13540 counter.fetch_add(1, atomic::Ordering::Release);
13541 async move {
13542 assert_eq!(params.text_document_position.text_document.uri, url.clone());
13543 assert_eq!(
13544 params.text_document_position.position,
13545 complete_from_position
13546 );
13547 Ok(Some(lsp::CompletionResponse::Array(
13548 completions
13549 .iter()
13550 .map(|completion_text| lsp::CompletionItem {
13551 label: completion_text.to_string(),
13552 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13553 range: replace_range,
13554 new_text: completion_text.to_string(),
13555 })),
13556 ..Default::default()
13557 })
13558 .collect(),
13559 )))
13560 }
13561 });
13562
13563 async move {
13564 request.next().await;
13565 }
13566}
13567
13568fn handle_resolve_completion_request(
13569 cx: &mut EditorLspTestContext,
13570 edits: Option<Vec<(&'static str, &'static str)>>,
13571) -> impl Future<Output = ()> {
13572 let edits = edits.map(|edits| {
13573 edits
13574 .iter()
13575 .map(|(marked_string, new_text)| {
13576 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
13577 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
13578 lsp::TextEdit::new(replace_range, new_text.to_string())
13579 })
13580 .collect::<Vec<_>>()
13581 });
13582
13583 let mut request =
13584 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
13585 let edits = edits.clone();
13586 async move {
13587 Ok(lsp::CompletionItem {
13588 additional_text_edits: edits,
13589 ..Default::default()
13590 })
13591 }
13592 });
13593
13594 async move {
13595 request.next().await;
13596 }
13597}
13598
13599pub(crate) fn update_test_language_settings(
13600 cx: &mut TestAppContext,
13601 f: impl Fn(&mut AllLanguageSettingsContent),
13602) {
13603 cx.update(|cx| {
13604 SettingsStore::update_global(cx, |store, cx| {
13605 store.update_user_settings::<AllLanguageSettings>(cx, f);
13606 });
13607 });
13608}
13609
13610pub(crate) fn update_test_project_settings(
13611 cx: &mut TestAppContext,
13612 f: impl Fn(&mut ProjectSettings),
13613) {
13614 cx.update(|cx| {
13615 SettingsStore::update_global(cx, |store, cx| {
13616 store.update_user_settings::<ProjectSettings>(cx, f);
13617 });
13618 });
13619}
13620
13621pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
13622 cx.update(|cx| {
13623 assets::Assets.load_test_fonts(cx);
13624 let store = SettingsStore::test(cx);
13625 cx.set_global(store);
13626 theme::init(theme::LoadThemes::JustBase, cx);
13627 release_channel::init(SemanticVersion::default(), cx);
13628 client::init_settings(cx);
13629 language::init(cx);
13630 Project::init_settings(cx);
13631 workspace::init_settings(cx);
13632 crate::init(cx);
13633 });
13634
13635 update_test_language_settings(cx, f);
13636}
13637
13638pub(crate) fn rust_lang() -> Arc<Language> {
13639 Arc::new(Language::new(
13640 LanguageConfig {
13641 name: "Rust".into(),
13642 matcher: LanguageMatcher {
13643 path_suffixes: vec!["rs".to_string()],
13644 ..Default::default()
13645 },
13646 ..Default::default()
13647 },
13648 Some(tree_sitter_rust::LANGUAGE.into()),
13649 ))
13650}
13651
13652#[track_caller]
13653fn assert_hunk_revert(
13654 not_reverted_text_with_selections: &str,
13655 expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
13656 expected_reverted_text_with_selections: &str,
13657 base_text: &str,
13658 cx: &mut EditorLspTestContext,
13659) {
13660 cx.set_state(not_reverted_text_with_selections);
13661 cx.update_editor(|editor, cx| {
13662 editor
13663 .buffer()
13664 .read(cx)
13665 .as_singleton()
13666 .unwrap()
13667 .update(cx, |buffer, cx| {
13668 buffer.set_diff_base(Some(base_text.into()), cx);
13669 });
13670 });
13671 cx.executor().run_until_parked();
13672
13673 let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
13674 let snapshot = editor.buffer().read(cx).snapshot(cx);
13675 let reverted_hunk_statuses = snapshot
13676 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
13677 .map(|hunk| hunk_status(&hunk))
13678 .collect::<Vec<_>>();
13679
13680 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
13681 reverted_hunk_statuses
13682 });
13683 cx.executor().run_until_parked();
13684 cx.assert_editor_state(expected_reverted_text_with_selections);
13685 assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
13686}