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, BackgroundExecutor, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext,
13 WindowBounds, 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::{buffer_store::BufferChangeSet, 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::{self, AtomicBool, AtomicUsize};
35use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
36use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
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 group_interval = Duration::from_millis(1);
173 let buffer = cx.new_model(|cx| {
174 let mut buf = language::Buffer::local("123456", cx);
175 buf.set_group_interval(group_interval);
176 buf
177 });
178 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
179 let editor = cx.add_window(|cx| build_editor(buffer.clone(), cx));
180
181 _ = editor.update(cx, |editor, cx| {
182 editor.start_transaction_at(now, cx);
183 editor.change_selections(None, cx, |s| s.select_ranges([2..4]));
184
185 editor.insert("cd", cx);
186 editor.end_transaction_at(now, cx);
187 assert_eq!(editor.text(cx), "12cd56");
188 assert_eq!(editor.selections.ranges(cx), vec![4..4]);
189
190 editor.start_transaction_at(now, cx);
191 editor.change_selections(None, cx, |s| s.select_ranges([4..5]));
192 editor.insert("e", cx);
193 editor.end_transaction_at(now, cx);
194 assert_eq!(editor.text(cx), "12cde6");
195 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
196
197 now += group_interval + Duration::from_millis(1);
198 editor.change_selections(None, cx, |s| s.select_ranges([2..2]));
199
200 // Simulate an edit in another editor
201 buffer.update(cx, |buffer, cx| {
202 buffer.start_transaction_at(now, cx);
203 buffer.edit([(0..1, "a")], None, cx);
204 buffer.edit([(1..1, "b")], None, cx);
205 buffer.end_transaction_at(now, cx);
206 });
207
208 assert_eq!(editor.text(cx), "ab2cde6");
209 assert_eq!(editor.selections.ranges(cx), vec![3..3]);
210
211 // Last transaction happened past the group interval in a different editor.
212 // Undo it individually and don't restore selections.
213 editor.undo(&Undo, cx);
214 assert_eq!(editor.text(cx), "12cde6");
215 assert_eq!(editor.selections.ranges(cx), vec![2..2]);
216
217 // First two transactions happened within the group interval in this editor.
218 // Undo them together and restore selections.
219 editor.undo(&Undo, cx);
220 editor.undo(&Undo, cx); // Undo stack is empty here, so this is a no-op.
221 assert_eq!(editor.text(cx), "123456");
222 assert_eq!(editor.selections.ranges(cx), vec![0..0]);
223
224 // Redo the first two transactions together.
225 editor.redo(&Redo, cx);
226 assert_eq!(editor.text(cx), "12cde6");
227 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
228
229 // Redo the last transaction on its own.
230 editor.redo(&Redo, cx);
231 assert_eq!(editor.text(cx), "ab2cde6");
232 assert_eq!(editor.selections.ranges(cx), vec![6..6]);
233
234 // Test empty transactions.
235 editor.start_transaction_at(now, cx);
236 editor.end_transaction_at(now, cx);
237 editor.undo(&Undo, cx);
238 assert_eq!(editor.text(cx), "12cde6");
239 });
240}
241
242#[gpui::test]
243fn test_ime_composition(cx: &mut TestAppContext) {
244 init_test(cx, |_| {});
245
246 let buffer = cx.new_model(|cx| {
247 let mut buffer = language::Buffer::local("abcde", cx);
248 // Ensure automatic grouping doesn't occur.
249 buffer.set_group_interval(Duration::ZERO);
250 buffer
251 });
252
253 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
254 cx.add_window(|cx| {
255 let mut editor = build_editor(buffer.clone(), cx);
256
257 // Start a new IME composition.
258 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
259 editor.replace_and_mark_text_in_range(Some(0..1), "á", None, cx);
260 editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, cx);
261 assert_eq!(editor.text(cx), "äbcde");
262 assert_eq!(
263 editor.marked_text_ranges(cx),
264 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
265 );
266
267 // Finalize IME composition.
268 editor.replace_text_in_range(None, "ā", cx);
269 assert_eq!(editor.text(cx), "ābcde");
270 assert_eq!(editor.marked_text_ranges(cx), None);
271
272 // IME composition edits are grouped and are undone/redone at once.
273 editor.undo(&Default::default(), cx);
274 assert_eq!(editor.text(cx), "abcde");
275 assert_eq!(editor.marked_text_ranges(cx), None);
276 editor.redo(&Default::default(), cx);
277 assert_eq!(editor.text(cx), "ābcde");
278 assert_eq!(editor.marked_text_ranges(cx), None);
279
280 // Start a new IME composition.
281 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
282 assert_eq!(
283 editor.marked_text_ranges(cx),
284 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
285 );
286
287 // Undoing during an IME composition cancels it.
288 editor.undo(&Default::default(), cx);
289 assert_eq!(editor.text(cx), "ābcde");
290 assert_eq!(editor.marked_text_ranges(cx), None);
291
292 // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
293 editor.replace_and_mark_text_in_range(Some(4..999), "è", None, cx);
294 assert_eq!(editor.text(cx), "ābcdè");
295 assert_eq!(
296 editor.marked_text_ranges(cx),
297 Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
298 );
299
300 // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
301 editor.replace_text_in_range(Some(4..999), "ę", cx);
302 assert_eq!(editor.text(cx), "ābcdę");
303 assert_eq!(editor.marked_text_ranges(cx), None);
304
305 // Start a new IME composition with multiple cursors.
306 editor.change_selections(None, cx, |s| {
307 s.select_ranges([
308 OffsetUtf16(1)..OffsetUtf16(1),
309 OffsetUtf16(3)..OffsetUtf16(3),
310 OffsetUtf16(5)..OffsetUtf16(5),
311 ])
312 });
313 editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, cx);
314 assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
315 assert_eq!(
316 editor.marked_text_ranges(cx),
317 Some(vec![
318 OffsetUtf16(0)..OffsetUtf16(3),
319 OffsetUtf16(4)..OffsetUtf16(7),
320 OffsetUtf16(8)..OffsetUtf16(11)
321 ])
322 );
323
324 // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
325 editor.replace_and_mark_text_in_range(Some(1..2), "1", None, cx);
326 assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
327 assert_eq!(
328 editor.marked_text_ranges(cx),
329 Some(vec![
330 OffsetUtf16(1)..OffsetUtf16(2),
331 OffsetUtf16(5)..OffsetUtf16(6),
332 OffsetUtf16(9)..OffsetUtf16(10)
333 ])
334 );
335
336 // Finalize IME composition with multiple cursors.
337 editor.replace_text_in_range(Some(9..10), "2", cx);
338 assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
339 assert_eq!(editor.marked_text_ranges(cx), None);
340
341 editor
342 });
343}
344
345#[gpui::test]
346fn test_selection_with_mouse(cx: &mut TestAppContext) {
347 init_test(cx, |_| {});
348
349 let editor = cx.add_window(|cx| {
350 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
351 build_editor(buffer, cx)
352 });
353
354 _ = editor.update(cx, |view, cx| {
355 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
356 });
357 assert_eq!(
358 editor
359 .update(cx, |view, cx| view.selections.display_ranges(cx))
360 .unwrap(),
361 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
362 );
363
364 _ = editor.update(cx, |view, cx| {
365 view.update_selection(
366 DisplayPoint::new(DisplayRow(3), 3),
367 0,
368 gpui::Point::<f32>::default(),
369 cx,
370 );
371 });
372
373 assert_eq!(
374 editor
375 .update(cx, |view, cx| view.selections.display_ranges(cx))
376 .unwrap(),
377 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
378 );
379
380 _ = editor.update(cx, |view, cx| {
381 view.update_selection(
382 DisplayPoint::new(DisplayRow(1), 1),
383 0,
384 gpui::Point::<f32>::default(),
385 cx,
386 );
387 });
388
389 assert_eq!(
390 editor
391 .update(cx, |view, cx| view.selections.display_ranges(cx))
392 .unwrap(),
393 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
394 );
395
396 _ = editor.update(cx, |view, cx| {
397 view.end_selection(cx);
398 view.update_selection(
399 DisplayPoint::new(DisplayRow(3), 3),
400 0,
401 gpui::Point::<f32>::default(),
402 cx,
403 );
404 });
405
406 assert_eq!(
407 editor
408 .update(cx, |view, cx| view.selections.display_ranges(cx))
409 .unwrap(),
410 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
411 );
412
413 _ = editor.update(cx, |view, cx| {
414 view.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, cx);
415 view.update_selection(
416 DisplayPoint::new(DisplayRow(0), 0),
417 0,
418 gpui::Point::<f32>::default(),
419 cx,
420 );
421 });
422
423 assert_eq!(
424 editor
425 .update(cx, |view, cx| view.selections.display_ranges(cx))
426 .unwrap(),
427 [
428 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
429 DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
430 ]
431 );
432
433 _ = editor.update(cx, |view, cx| {
434 view.end_selection(cx);
435 });
436
437 assert_eq!(
438 editor
439 .update(cx, |view, cx| view.selections.display_ranges(cx))
440 .unwrap(),
441 [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
442 );
443}
444
445#[gpui::test]
446fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
447 init_test(cx, |_| {});
448
449 let editor = cx.add_window(|cx| {
450 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
451 build_editor(buffer, cx)
452 });
453
454 _ = editor.update(cx, |view, cx| {
455 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, cx);
456 });
457
458 _ = editor.update(cx, |view, cx| {
459 view.end_selection(cx);
460 });
461
462 _ = editor.update(cx, |view, cx| {
463 view.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, cx);
464 });
465
466 _ = editor.update(cx, |view, cx| {
467 view.end_selection(cx);
468 });
469
470 assert_eq!(
471 editor
472 .update(cx, |view, cx| view.selections.display_ranges(cx))
473 .unwrap(),
474 [
475 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
476 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
477 ]
478 );
479
480 _ = editor.update(cx, |view, cx| {
481 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, cx);
482 });
483
484 _ = editor.update(cx, |view, cx| {
485 view.end_selection(cx);
486 });
487
488 assert_eq!(
489 editor
490 .update(cx, |view, cx| view.selections.display_ranges(cx))
491 .unwrap(),
492 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
493 );
494}
495
496#[gpui::test]
497fn test_canceling_pending_selection(cx: &mut TestAppContext) {
498 init_test(cx, |_| {});
499
500 let view = cx.add_window(|cx| {
501 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
502 build_editor(buffer, cx)
503 });
504
505 _ = view.update(cx, |view, cx| {
506 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
507 assert_eq!(
508 view.selections.display_ranges(cx),
509 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
510 );
511 });
512
513 _ = view.update(cx, |view, cx| {
514 view.update_selection(
515 DisplayPoint::new(DisplayRow(3), 3),
516 0,
517 gpui::Point::<f32>::default(),
518 cx,
519 );
520 assert_eq!(
521 view.selections.display_ranges(cx),
522 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
523 );
524 });
525
526 _ = view.update(cx, |view, cx| {
527 view.cancel(&Cancel, cx);
528 view.update_selection(
529 DisplayPoint::new(DisplayRow(1), 1),
530 0,
531 gpui::Point::<f32>::default(),
532 cx,
533 );
534 assert_eq!(
535 view.selections.display_ranges(cx),
536 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
537 );
538 });
539}
540
541#[gpui::test]
542fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
543 init_test(cx, |_| {});
544
545 let view = cx.add_window(|cx| {
546 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
547 build_editor(buffer, cx)
548 });
549
550 _ = view.update(cx, |view, cx| {
551 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
552 assert_eq!(
553 view.selections.display_ranges(cx),
554 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
555 );
556
557 view.move_down(&Default::default(), cx);
558 assert_eq!(
559 view.selections.display_ranges(cx),
560 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
561 );
562
563 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
564 assert_eq!(
565 view.selections.display_ranges(cx),
566 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
567 );
568
569 view.move_up(&Default::default(), cx);
570 assert_eq!(
571 view.selections.display_ranges(cx),
572 [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
573 );
574 });
575}
576
577#[gpui::test]
578fn test_clone(cx: &mut TestAppContext) {
579 init_test(cx, |_| {});
580
581 let (text, selection_ranges) = marked_text_ranges(
582 indoc! {"
583 one
584 two
585 threeˇ
586 four
587 fiveˇ
588 "},
589 true,
590 );
591
592 let editor = cx.add_window(|cx| {
593 let buffer = MultiBuffer::build_simple(&text, cx);
594 build_editor(buffer, cx)
595 });
596
597 _ = editor.update(cx, |editor, cx| {
598 editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
599 editor.fold_creases(
600 vec![
601 Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
602 Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
603 ],
604 true,
605 cx,
606 );
607 });
608
609 let cloned_editor = editor
610 .update(cx, |editor, cx| {
611 cx.open_window(Default::default(), |cx| cx.new_view(|cx| editor.clone(cx)))
612 })
613 .unwrap()
614 .unwrap();
615
616 let snapshot = editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
617 let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
618
619 assert_eq!(
620 cloned_editor
621 .update(cx, |e, cx| e.display_text(cx))
622 .unwrap(),
623 editor.update(cx, |e, cx| e.display_text(cx)).unwrap()
624 );
625 assert_eq!(
626 cloned_snapshot
627 .folds_in_range(0..text.len())
628 .collect::<Vec<_>>(),
629 snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
630 );
631 assert_set_eq!(
632 cloned_editor
633 .update(cx, |editor, cx| editor.selections.ranges::<Point>(cx))
634 .unwrap(),
635 editor
636 .update(cx, |editor, cx| editor.selections.ranges(cx))
637 .unwrap()
638 );
639 assert_set_eq!(
640 cloned_editor
641 .update(cx, |e, cx| e.selections.display_ranges(cx))
642 .unwrap(),
643 editor
644 .update(cx, |e, cx| e.selections.display_ranges(cx))
645 .unwrap()
646 );
647}
648
649#[gpui::test]
650async fn test_navigation_history(cx: &mut TestAppContext) {
651 init_test(cx, |_| {});
652
653 use workspace::item::Item;
654
655 let fs = FakeFs::new(cx.executor());
656 let project = Project::test(fs, [], cx).await;
657 let workspace = cx.add_window(|cx| Workspace::test_new(project, cx));
658 let pane = workspace
659 .update(cx, |workspace, _| workspace.active_pane().clone())
660 .unwrap();
661
662 _ = workspace.update(cx, |_v, cx| {
663 cx.new_view(|cx| {
664 let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
665 let mut editor = build_editor(buffer.clone(), cx);
666 let handle = cx.view();
667 editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(handle)));
668
669 fn pop_history(editor: &mut Editor, cx: &mut WindowContext) -> Option<NavigationEntry> {
670 editor.nav_history.as_mut().unwrap().pop_backward(cx)
671 }
672
673 // Move the cursor a small distance.
674 // Nothing is added to the navigation history.
675 editor.change_selections(None, cx, |s| {
676 s.select_display_ranges([
677 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
678 ])
679 });
680 editor.change_selections(None, cx, |s| {
681 s.select_display_ranges([
682 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
683 ])
684 });
685 assert!(pop_history(&mut editor, cx).is_none());
686
687 // Move the cursor a large distance.
688 // The history can jump back to the previous position.
689 editor.change_selections(None, cx, |s| {
690 s.select_display_ranges([
691 DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
692 ])
693 });
694 let nav_entry = pop_history(&mut editor, cx).unwrap();
695 editor.navigate(nav_entry.data.unwrap(), cx);
696 assert_eq!(nav_entry.item.id(), cx.entity_id());
697 assert_eq!(
698 editor.selections.display_ranges(cx),
699 &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
700 );
701 assert!(pop_history(&mut editor, cx).is_none());
702
703 // Move the cursor a small distance via the mouse.
704 // Nothing is added to the navigation history.
705 editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, cx);
706 editor.end_selection(cx);
707 assert_eq!(
708 editor.selections.display_ranges(cx),
709 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
710 );
711 assert!(pop_history(&mut editor, cx).is_none());
712
713 // Move the cursor a large distance via the mouse.
714 // The history can jump back to the previous position.
715 editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, cx);
716 editor.end_selection(cx);
717 assert_eq!(
718 editor.selections.display_ranges(cx),
719 &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
720 );
721 let nav_entry = pop_history(&mut editor, cx).unwrap();
722 editor.navigate(nav_entry.data.unwrap(), cx);
723 assert_eq!(nav_entry.item.id(), cx.entity_id());
724 assert_eq!(
725 editor.selections.display_ranges(cx),
726 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
727 );
728 assert!(pop_history(&mut editor, cx).is_none());
729
730 // Set scroll position to check later
731 editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), cx);
732 let original_scroll_position = editor.scroll_manager.anchor();
733
734 // Jump to the end of the document and adjust scroll
735 editor.move_to_end(&MoveToEnd, cx);
736 editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), cx);
737 assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
738
739 let nav_entry = pop_history(&mut editor, cx).unwrap();
740 editor.navigate(nav_entry.data.unwrap(), cx);
741 assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
742
743 // Ensure we don't panic when navigation data contains invalid anchors *and* points.
744 let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
745 invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
746 let invalid_point = Point::new(9999, 0);
747 editor.navigate(
748 Box::new(NavigationData {
749 cursor_anchor: invalid_anchor,
750 cursor_position: invalid_point,
751 scroll_anchor: ScrollAnchor {
752 anchor: invalid_anchor,
753 offset: Default::default(),
754 },
755 scroll_top_row: invalid_point.row,
756 }),
757 cx,
758 );
759 assert_eq!(
760 editor.selections.display_ranges(cx),
761 &[editor.max_point(cx)..editor.max_point(cx)]
762 );
763 assert_eq!(
764 editor.scroll_position(cx),
765 gpui::Point::new(0., editor.max_point(cx).row().as_f32())
766 );
767
768 editor
769 })
770 });
771}
772
773#[gpui::test]
774fn test_cancel(cx: &mut TestAppContext) {
775 init_test(cx, |_| {});
776
777 let view = cx.add_window(|cx| {
778 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
779 build_editor(buffer, cx)
780 });
781
782 _ = view.update(cx, |view, cx| {
783 view.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, cx);
784 view.update_selection(
785 DisplayPoint::new(DisplayRow(1), 1),
786 0,
787 gpui::Point::<f32>::default(),
788 cx,
789 );
790 view.end_selection(cx);
791
792 view.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, cx);
793 view.update_selection(
794 DisplayPoint::new(DisplayRow(0), 3),
795 0,
796 gpui::Point::<f32>::default(),
797 cx,
798 );
799 view.end_selection(cx);
800 assert_eq!(
801 view.selections.display_ranges(cx),
802 [
803 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
804 DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
805 ]
806 );
807 });
808
809 _ = view.update(cx, |view, cx| {
810 view.cancel(&Cancel, cx);
811 assert_eq!(
812 view.selections.display_ranges(cx),
813 [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
814 );
815 });
816
817 _ = view.update(cx, |view, cx| {
818 view.cancel(&Cancel, cx);
819 assert_eq!(
820 view.selections.display_ranges(cx),
821 [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
822 );
823 });
824}
825
826#[gpui::test]
827fn test_fold_action(cx: &mut TestAppContext) {
828 init_test(cx, |_| {});
829
830 let view = cx.add_window(|cx| {
831 let buffer = MultiBuffer::build_simple(
832 &"
833 impl Foo {
834 // Hello!
835
836 fn a() {
837 1
838 }
839
840 fn b() {
841 2
842 }
843
844 fn c() {
845 3
846 }
847 }
848 "
849 .unindent(),
850 cx,
851 );
852 build_editor(buffer.clone(), cx)
853 });
854
855 _ = view.update(cx, |view, cx| {
856 view.change_selections(None, cx, |s| {
857 s.select_display_ranges([
858 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
859 ]);
860 });
861 view.fold(&Fold, cx);
862 assert_eq!(
863 view.display_text(cx),
864 "
865 impl Foo {
866 // Hello!
867
868 fn a() {
869 1
870 }
871
872 fn b() {⋯
873 }
874
875 fn c() {⋯
876 }
877 }
878 "
879 .unindent(),
880 );
881
882 view.fold(&Fold, cx);
883 assert_eq!(
884 view.display_text(cx),
885 "
886 impl Foo {⋯
887 }
888 "
889 .unindent(),
890 );
891
892 view.unfold_lines(&UnfoldLines, cx);
893 assert_eq!(
894 view.display_text(cx),
895 "
896 impl Foo {
897 // Hello!
898
899 fn a() {
900 1
901 }
902
903 fn b() {⋯
904 }
905
906 fn c() {⋯
907 }
908 }
909 "
910 .unindent(),
911 );
912
913 view.unfold_lines(&UnfoldLines, cx);
914 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
915 });
916}
917
918#[gpui::test]
919fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
920 init_test(cx, |_| {});
921
922 let view = cx.add_window(|cx| {
923 let buffer = MultiBuffer::build_simple(
924 &"
925 class Foo:
926 # Hello!
927
928 def a():
929 print(1)
930
931 def b():
932 print(2)
933
934 def c():
935 print(3)
936 "
937 .unindent(),
938 cx,
939 );
940 build_editor(buffer.clone(), cx)
941 });
942
943 _ = view.update(cx, |view, cx| {
944 view.change_selections(None, cx, |s| {
945 s.select_display_ranges([
946 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
947 ]);
948 });
949 view.fold(&Fold, cx);
950 assert_eq!(
951 view.display_text(cx),
952 "
953 class Foo:
954 # Hello!
955
956 def a():
957 print(1)
958
959 def b():⋯
960
961 def c():⋯
962 "
963 .unindent(),
964 );
965
966 view.fold(&Fold, cx);
967 assert_eq!(
968 view.display_text(cx),
969 "
970 class Foo:⋯
971 "
972 .unindent(),
973 );
974
975 view.unfold_lines(&UnfoldLines, cx);
976 assert_eq!(
977 view.display_text(cx),
978 "
979 class Foo:
980 # Hello!
981
982 def a():
983 print(1)
984
985 def b():⋯
986
987 def c():⋯
988 "
989 .unindent(),
990 );
991
992 view.unfold_lines(&UnfoldLines, cx);
993 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
994 });
995}
996
997#[gpui::test]
998fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
999 init_test(cx, |_| {});
1000
1001 let view = cx.add_window(|cx| {
1002 let buffer = MultiBuffer::build_simple(
1003 &"
1004 class Foo:
1005 # Hello!
1006
1007 def a():
1008 print(1)
1009
1010 def b():
1011 print(2)
1012
1013
1014 def c():
1015 print(3)
1016
1017
1018 "
1019 .unindent(),
1020 cx,
1021 );
1022 build_editor(buffer.clone(), cx)
1023 });
1024
1025 _ = view.update(cx, |view, cx| {
1026 view.change_selections(None, cx, |s| {
1027 s.select_display_ranges([
1028 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
1029 ]);
1030 });
1031 view.fold(&Fold, cx);
1032 assert_eq!(
1033 view.display_text(cx),
1034 "
1035 class Foo:
1036 # Hello!
1037
1038 def a():
1039 print(1)
1040
1041 def b():⋯
1042
1043
1044 def c():⋯
1045
1046
1047 "
1048 .unindent(),
1049 );
1050
1051 view.fold(&Fold, cx);
1052 assert_eq!(
1053 view.display_text(cx),
1054 "
1055 class Foo:⋯
1056
1057
1058 "
1059 .unindent(),
1060 );
1061
1062 view.unfold_lines(&UnfoldLines, cx);
1063 assert_eq!(
1064 view.display_text(cx),
1065 "
1066 class Foo:
1067 # Hello!
1068
1069 def a():
1070 print(1)
1071
1072 def b():⋯
1073
1074
1075 def c():⋯
1076
1077
1078 "
1079 .unindent(),
1080 );
1081
1082 view.unfold_lines(&UnfoldLines, cx);
1083 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
1084 });
1085}
1086
1087#[gpui::test]
1088fn test_fold_at_level(cx: &mut TestAppContext) {
1089 init_test(cx, |_| {});
1090
1091 let view = cx.add_window(|cx| {
1092 let buffer = MultiBuffer::build_simple(
1093 &"
1094 class Foo:
1095 # Hello!
1096
1097 def a():
1098 print(1)
1099
1100 def b():
1101 print(2)
1102
1103
1104 class Bar:
1105 # World!
1106
1107 def a():
1108 print(1)
1109
1110 def b():
1111 print(2)
1112
1113
1114 "
1115 .unindent(),
1116 cx,
1117 );
1118 build_editor(buffer.clone(), cx)
1119 });
1120
1121 _ = view.update(cx, |view, cx| {
1122 view.fold_at_level(&FoldAtLevel { level: 2 }, cx);
1123 assert_eq!(
1124 view.display_text(cx),
1125 "
1126 class Foo:
1127 # Hello!
1128
1129 def a():⋯
1130
1131 def b():⋯
1132
1133
1134 class Bar:
1135 # World!
1136
1137 def a():⋯
1138
1139 def b():⋯
1140
1141
1142 "
1143 .unindent(),
1144 );
1145
1146 view.fold_at_level(&FoldAtLevel { level: 1 }, cx);
1147 assert_eq!(
1148 view.display_text(cx),
1149 "
1150 class Foo:⋯
1151
1152
1153 class Bar:⋯
1154
1155
1156 "
1157 .unindent(),
1158 );
1159
1160 view.unfold_all(&UnfoldAll, cx);
1161 view.fold_at_level(&FoldAtLevel { level: 0 }, cx);
1162 assert_eq!(
1163 view.display_text(cx),
1164 "
1165 class Foo:
1166 # Hello!
1167
1168 def a():
1169 print(1)
1170
1171 def b():
1172 print(2)
1173
1174
1175 class Bar:
1176 # World!
1177
1178 def a():
1179 print(1)
1180
1181 def b():
1182 print(2)
1183
1184
1185 "
1186 .unindent(),
1187 );
1188
1189 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
1190 });
1191}
1192
1193#[gpui::test]
1194fn test_move_cursor(cx: &mut TestAppContext) {
1195 init_test(cx, |_| {});
1196
1197 let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
1198 let view = cx.add_window(|cx| build_editor(buffer.clone(), cx));
1199
1200 buffer.update(cx, |buffer, cx| {
1201 buffer.edit(
1202 vec![
1203 (Point::new(1, 0)..Point::new(1, 0), "\t"),
1204 (Point::new(1, 1)..Point::new(1, 1), "\t"),
1205 ],
1206 None,
1207 cx,
1208 );
1209 });
1210 _ = view.update(cx, |view, cx| {
1211 assert_eq!(
1212 view.selections.display_ranges(cx),
1213 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1214 );
1215
1216 view.move_down(&MoveDown, cx);
1217 assert_eq!(
1218 view.selections.display_ranges(cx),
1219 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1220 );
1221
1222 view.move_right(&MoveRight, cx);
1223 assert_eq!(
1224 view.selections.display_ranges(cx),
1225 &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
1226 );
1227
1228 view.move_left(&MoveLeft, cx);
1229 assert_eq!(
1230 view.selections.display_ranges(cx),
1231 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1232 );
1233
1234 view.move_up(&MoveUp, cx);
1235 assert_eq!(
1236 view.selections.display_ranges(cx),
1237 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1238 );
1239
1240 view.move_to_end(&MoveToEnd, cx);
1241 assert_eq!(
1242 view.selections.display_ranges(cx),
1243 &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
1244 );
1245
1246 view.move_to_beginning(&MoveToBeginning, cx);
1247 assert_eq!(
1248 view.selections.display_ranges(cx),
1249 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1250 );
1251
1252 view.change_selections(None, cx, |s| {
1253 s.select_display_ranges([
1254 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
1255 ]);
1256 });
1257 view.select_to_beginning(&SelectToBeginning, cx);
1258 assert_eq!(
1259 view.selections.display_ranges(cx),
1260 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
1261 );
1262
1263 view.select_to_end(&SelectToEnd, cx);
1264 assert_eq!(
1265 view.selections.display_ranges(cx),
1266 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
1267 );
1268 });
1269}
1270
1271// TODO: Re-enable this test
1272#[cfg(target_os = "macos")]
1273#[gpui::test]
1274fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
1275 init_test(cx, |_| {});
1276
1277 let view = cx.add_window(|cx| {
1278 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε", cx);
1279 build_editor(buffer.clone(), cx)
1280 });
1281
1282 assert_eq!('ⓐ'.len_utf8(), 3);
1283 assert_eq!('α'.len_utf8(), 2);
1284
1285 _ = view.update(cx, |view, cx| {
1286 view.fold_creases(
1287 vec![
1288 Crease::simple(Point::new(0, 6)..Point::new(0, 12), FoldPlaceholder::test()),
1289 Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
1290 Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
1291 ],
1292 true,
1293 cx,
1294 );
1295 assert_eq!(view.display_text(cx), "ⓐⓑ⋯ⓔ\nab⋯e\nαβ⋯ε");
1296
1297 view.move_right(&MoveRight, cx);
1298 assert_eq!(
1299 view.selections.display_ranges(cx),
1300 &[empty_range(0, "ⓐ".len())]
1301 );
1302 view.move_right(&MoveRight, cx);
1303 assert_eq!(
1304 view.selections.display_ranges(cx),
1305 &[empty_range(0, "ⓐⓑ".len())]
1306 );
1307 view.move_right(&MoveRight, cx);
1308 assert_eq!(
1309 view.selections.display_ranges(cx),
1310 &[empty_range(0, "ⓐⓑ⋯".len())]
1311 );
1312
1313 view.move_down(&MoveDown, cx);
1314 assert_eq!(
1315 view.selections.display_ranges(cx),
1316 &[empty_range(1, "ab⋯e".len())]
1317 );
1318 view.move_left(&MoveLeft, cx);
1319 assert_eq!(
1320 view.selections.display_ranges(cx),
1321 &[empty_range(1, "ab⋯".len())]
1322 );
1323 view.move_left(&MoveLeft, cx);
1324 assert_eq!(
1325 view.selections.display_ranges(cx),
1326 &[empty_range(1, "ab".len())]
1327 );
1328 view.move_left(&MoveLeft, cx);
1329 assert_eq!(
1330 view.selections.display_ranges(cx),
1331 &[empty_range(1, "a".len())]
1332 );
1333
1334 view.move_down(&MoveDown, cx);
1335 assert_eq!(
1336 view.selections.display_ranges(cx),
1337 &[empty_range(2, "α".len())]
1338 );
1339 view.move_right(&MoveRight, cx);
1340 assert_eq!(
1341 view.selections.display_ranges(cx),
1342 &[empty_range(2, "αβ".len())]
1343 );
1344 view.move_right(&MoveRight, cx);
1345 assert_eq!(
1346 view.selections.display_ranges(cx),
1347 &[empty_range(2, "αβ⋯".len())]
1348 );
1349 view.move_right(&MoveRight, cx);
1350 assert_eq!(
1351 view.selections.display_ranges(cx),
1352 &[empty_range(2, "αβ⋯ε".len())]
1353 );
1354
1355 view.move_up(&MoveUp, cx);
1356 assert_eq!(
1357 view.selections.display_ranges(cx),
1358 &[empty_range(1, "ab⋯e".len())]
1359 );
1360 view.move_down(&MoveDown, cx);
1361 assert_eq!(
1362 view.selections.display_ranges(cx),
1363 &[empty_range(2, "αβ⋯ε".len())]
1364 );
1365 view.move_up(&MoveUp, cx);
1366 assert_eq!(
1367 view.selections.display_ranges(cx),
1368 &[empty_range(1, "ab⋯e".len())]
1369 );
1370
1371 view.move_up(&MoveUp, cx);
1372 assert_eq!(
1373 view.selections.display_ranges(cx),
1374 &[empty_range(0, "ⓐⓑ".len())]
1375 );
1376 view.move_left(&MoveLeft, cx);
1377 assert_eq!(
1378 view.selections.display_ranges(cx),
1379 &[empty_range(0, "ⓐ".len())]
1380 );
1381 view.move_left(&MoveLeft, cx);
1382 assert_eq!(
1383 view.selections.display_ranges(cx),
1384 &[empty_range(0, "".len())]
1385 );
1386 });
1387}
1388
1389#[gpui::test]
1390fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
1391 init_test(cx, |_| {});
1392
1393 let view = cx.add_window(|cx| {
1394 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
1395 build_editor(buffer.clone(), cx)
1396 });
1397 _ = view.update(cx, |view, cx| {
1398 view.change_selections(None, cx, |s| {
1399 s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
1400 });
1401
1402 // moving above start of document should move selection to start of document,
1403 // but the next move down should still be at the original goal_x
1404 view.move_up(&MoveUp, cx);
1405 assert_eq!(
1406 view.selections.display_ranges(cx),
1407 &[empty_range(0, "".len())]
1408 );
1409
1410 view.move_down(&MoveDown, cx);
1411 assert_eq!(
1412 view.selections.display_ranges(cx),
1413 &[empty_range(1, "abcd".len())]
1414 );
1415
1416 view.move_down(&MoveDown, cx);
1417 assert_eq!(
1418 view.selections.display_ranges(cx),
1419 &[empty_range(2, "αβγ".len())]
1420 );
1421
1422 view.move_down(&MoveDown, cx);
1423 assert_eq!(
1424 view.selections.display_ranges(cx),
1425 &[empty_range(3, "abcd".len())]
1426 );
1427
1428 view.move_down(&MoveDown, cx);
1429 assert_eq!(
1430 view.selections.display_ranges(cx),
1431 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1432 );
1433
1434 // moving past end of document should not change goal_x
1435 view.move_down(&MoveDown, cx);
1436 assert_eq!(
1437 view.selections.display_ranges(cx),
1438 &[empty_range(5, "".len())]
1439 );
1440
1441 view.move_down(&MoveDown, cx);
1442 assert_eq!(
1443 view.selections.display_ranges(cx),
1444 &[empty_range(5, "".len())]
1445 );
1446
1447 view.move_up(&MoveUp, cx);
1448 assert_eq!(
1449 view.selections.display_ranges(cx),
1450 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1451 );
1452
1453 view.move_up(&MoveUp, cx);
1454 assert_eq!(
1455 view.selections.display_ranges(cx),
1456 &[empty_range(3, "abcd".len())]
1457 );
1458
1459 view.move_up(&MoveUp, cx);
1460 assert_eq!(
1461 view.selections.display_ranges(cx),
1462 &[empty_range(2, "αβγ".len())]
1463 );
1464 });
1465}
1466
1467#[gpui::test]
1468fn test_beginning_end_of_line(cx: &mut TestAppContext) {
1469 init_test(cx, |_| {});
1470 let move_to_beg = MoveToBeginningOfLine {
1471 stop_at_soft_wraps: true,
1472 };
1473
1474 let move_to_end = MoveToEndOfLine {
1475 stop_at_soft_wraps: true,
1476 };
1477
1478 let view = cx.add_window(|cx| {
1479 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1480 build_editor(buffer, cx)
1481 });
1482 _ = view.update(cx, |view, cx| {
1483 view.change_selections(None, cx, |s| {
1484 s.select_display_ranges([
1485 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1486 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1487 ]);
1488 });
1489 });
1490
1491 _ = view.update(cx, |view, cx| {
1492 view.move_to_beginning_of_line(&move_to_beg, cx);
1493 assert_eq!(
1494 view.selections.display_ranges(cx),
1495 &[
1496 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1497 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1498 ]
1499 );
1500 });
1501
1502 _ = view.update(cx, |view, cx| {
1503 view.move_to_beginning_of_line(&move_to_beg, cx);
1504 assert_eq!(
1505 view.selections.display_ranges(cx),
1506 &[
1507 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1508 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1509 ]
1510 );
1511 });
1512
1513 _ = view.update(cx, |view, cx| {
1514 view.move_to_beginning_of_line(&move_to_beg, cx);
1515 assert_eq!(
1516 view.selections.display_ranges(cx),
1517 &[
1518 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1519 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1520 ]
1521 );
1522 });
1523
1524 _ = view.update(cx, |view, cx| {
1525 view.move_to_end_of_line(&move_to_end, cx);
1526 assert_eq!(
1527 view.selections.display_ranges(cx),
1528 &[
1529 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1530 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1531 ]
1532 );
1533 });
1534
1535 // Moving to the end of line again is a no-op.
1536 _ = view.update(cx, |view, cx| {
1537 view.move_to_end_of_line(&move_to_end, cx);
1538 assert_eq!(
1539 view.selections.display_ranges(cx),
1540 &[
1541 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1542 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1543 ]
1544 );
1545 });
1546
1547 _ = view.update(cx, |view, cx| {
1548 view.move_left(&MoveLeft, 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_beginning_of_line(
1566 &SelectToBeginningOfLine {
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), 0),
1575 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1576 ]
1577 );
1578 });
1579
1580 _ = view.update(cx, |view, cx| {
1581 view.select_to_beginning_of_line(
1582 &SelectToBeginningOfLine {
1583 stop_at_soft_wraps: true,
1584 },
1585 cx,
1586 );
1587 assert_eq!(
1588 view.selections.display_ranges(cx),
1589 &[
1590 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1591 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1592 ]
1593 );
1594 });
1595
1596 _ = view.update(cx, |view, cx| {
1597 view.select_to_end_of_line(
1598 &SelectToEndOfLine {
1599 stop_at_soft_wraps: true,
1600 },
1601 cx,
1602 );
1603 assert_eq!(
1604 view.selections.display_ranges(cx),
1605 &[
1606 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
1607 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
1608 ]
1609 );
1610 });
1611
1612 _ = view.update(cx, |view, cx| {
1613 view.delete_to_end_of_line(&DeleteToEndOfLine, cx);
1614 assert_eq!(view.display_text(cx), "ab\n de");
1615 assert_eq!(
1616 view.selections.display_ranges(cx),
1617 &[
1618 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
1619 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1620 ]
1621 );
1622 });
1623
1624 _ = view.update(cx, |view, cx| {
1625 view.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
1626 assert_eq!(view.display_text(cx), "\n");
1627 assert_eq!(
1628 view.selections.display_ranges(cx),
1629 &[
1630 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1631 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1632 ]
1633 );
1634 });
1635}
1636
1637#[gpui::test]
1638fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
1639 init_test(cx, |_| {});
1640 let move_to_beg = MoveToBeginningOfLine {
1641 stop_at_soft_wraps: false,
1642 };
1643
1644 let move_to_end = MoveToEndOfLine {
1645 stop_at_soft_wraps: false,
1646 };
1647
1648 let view = cx.add_window(|cx| {
1649 let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
1650 build_editor(buffer, cx)
1651 });
1652
1653 _ = view.update(cx, |view, cx| {
1654 view.set_wrap_width(Some(140.0.into()), cx);
1655
1656 // We expect the following lines after wrapping
1657 // ```
1658 // thequickbrownfox
1659 // jumpedoverthelazydo
1660 // gs
1661 // ```
1662 // The final `gs` was soft-wrapped onto a new line.
1663 assert_eq!(
1664 "thequickbrownfox\njumpedoverthelaz\nydogs",
1665 view.display_text(cx),
1666 );
1667
1668 // First, let's assert behavior on the first line, that was not soft-wrapped.
1669 // Start the cursor at the `k` on the first line
1670 view.change_selections(None, cx, |s| {
1671 s.select_display_ranges([
1672 DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
1673 ]);
1674 });
1675
1676 // Moving to the beginning of the line should put us at the beginning of the line.
1677 view.move_to_beginning_of_line(&move_to_beg, cx);
1678 assert_eq!(
1679 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
1680 view.selections.display_ranges(cx)
1681 );
1682
1683 // Moving to the end of the line should put us at the end of the line.
1684 view.move_to_end_of_line(&move_to_end, cx);
1685 assert_eq!(
1686 vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
1687 view.selections.display_ranges(cx)
1688 );
1689
1690 // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
1691 // Start the cursor at the last line (`y` that was wrapped to a new line)
1692 view.change_selections(None, cx, |s| {
1693 s.select_display_ranges([
1694 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
1695 ]);
1696 });
1697
1698 // Moving to the beginning of the line should put us at the start of the second line of
1699 // display text, i.e., the `j`.
1700 view.move_to_beginning_of_line(&move_to_beg, cx);
1701 assert_eq!(
1702 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1703 view.selections.display_ranges(cx)
1704 );
1705
1706 // Moving to the beginning of the line again should be a no-op.
1707 view.move_to_beginning_of_line(&move_to_beg, cx);
1708 assert_eq!(
1709 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1710 view.selections.display_ranges(cx)
1711 );
1712
1713 // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
1714 // next display line.
1715 view.move_to_end_of_line(&move_to_end, cx);
1716 assert_eq!(
1717 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1718 view.selections.display_ranges(cx)
1719 );
1720
1721 // Moving to the end of the line again should be a no-op.
1722 view.move_to_end_of_line(&move_to_end, cx);
1723 assert_eq!(
1724 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1725 view.selections.display_ranges(cx)
1726 );
1727 });
1728}
1729
1730#[gpui::test]
1731fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1732 init_test(cx, |_| {});
1733
1734 let view = cx.add_window(|cx| {
1735 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1736 build_editor(buffer, cx)
1737 });
1738 _ = view.update(cx, |view, cx| {
1739 view.change_selections(None, cx, |s| {
1740 s.select_display_ranges([
1741 DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
1742 DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
1743 ])
1744 });
1745
1746 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1747 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1748
1749 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1750 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", view, cx);
1751
1752 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1753 assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", view, cx);
1754
1755 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1756 assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1757
1758 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1759 assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", view, cx);
1760
1761 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1762 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", view, cx);
1763
1764 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1765 assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1766
1767 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1768 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1769
1770 view.move_right(&MoveRight, cx);
1771 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1772 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1773
1774 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1775 assert_selection_ranges("use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}", view, cx);
1776
1777 view.select_to_next_word_end(&SelectToNextWordEnd, cx);
1778 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1779 });
1780}
1781
1782#[gpui::test]
1783fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1784 init_test(cx, |_| {});
1785
1786 let view = cx.add_window(|cx| {
1787 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1788 build_editor(buffer, cx)
1789 });
1790
1791 _ = view.update(cx, |view, cx| {
1792 view.set_wrap_width(Some(140.0.into()), cx);
1793 assert_eq!(
1794 view.display_text(cx),
1795 "use one::{\n two::three::\n four::five\n};"
1796 );
1797
1798 view.change_selections(None, cx, |s| {
1799 s.select_display_ranges([
1800 DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
1801 ]);
1802 });
1803
1804 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1805 assert_eq!(
1806 view.selections.display_ranges(cx),
1807 &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
1808 );
1809
1810 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1811 assert_eq!(
1812 view.selections.display_ranges(cx),
1813 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1814 );
1815
1816 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1817 assert_eq!(
1818 view.selections.display_ranges(cx),
1819 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1820 );
1821
1822 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1823 assert_eq!(
1824 view.selections.display_ranges(cx),
1825 &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
1826 );
1827
1828 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1829 assert_eq!(
1830 view.selections.display_ranges(cx),
1831 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1832 );
1833
1834 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1835 assert_eq!(
1836 view.selections.display_ranges(cx),
1837 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1838 );
1839 });
1840}
1841
1842#[gpui::test]
1843async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) {
1844 init_test(cx, |_| {});
1845 let mut cx = EditorTestContext::new(cx).await;
1846
1847 let line_height = cx.editor(|editor, cx| {
1848 editor
1849 .style()
1850 .unwrap()
1851 .text
1852 .line_height_in_pixels(cx.rem_size())
1853 });
1854 cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
1855
1856 cx.set_state(
1857 &r#"ˇone
1858 two
1859
1860 three
1861 fourˇ
1862 five
1863
1864 six"#
1865 .unindent(),
1866 );
1867
1868 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1869 cx.assert_editor_state(
1870 &r#"one
1871 two
1872 ˇ
1873 three
1874 four
1875 five
1876 ˇ
1877 six"#
1878 .unindent(),
1879 );
1880
1881 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1882 cx.assert_editor_state(
1883 &r#"one
1884 two
1885
1886 three
1887 four
1888 five
1889 ˇ
1890 sixˇ"#
1891 .unindent(),
1892 );
1893
1894 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1895 cx.assert_editor_state(
1896 &r#"one
1897 two
1898
1899 three
1900 four
1901 five
1902
1903 sixˇ"#
1904 .unindent(),
1905 );
1906
1907 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1908 cx.assert_editor_state(
1909 &r#"one
1910 two
1911
1912 three
1913 four
1914 five
1915 ˇ
1916 six"#
1917 .unindent(),
1918 );
1919
1920 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1921 cx.assert_editor_state(
1922 &r#"one
1923 two
1924 ˇ
1925 three
1926 four
1927 five
1928
1929 six"#
1930 .unindent(),
1931 );
1932
1933 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1934 cx.assert_editor_state(
1935 &r#"ˇone
1936 two
1937
1938 three
1939 four
1940 five
1941
1942 six"#
1943 .unindent(),
1944 );
1945}
1946
1947#[gpui::test]
1948async fn test_scroll_page_up_page_down(cx: &mut gpui::TestAppContext) {
1949 init_test(cx, |_| {});
1950 let mut cx = EditorTestContext::new(cx).await;
1951 let line_height = cx.editor(|editor, cx| {
1952 editor
1953 .style()
1954 .unwrap()
1955 .text
1956 .line_height_in_pixels(cx.rem_size())
1957 });
1958 let window = cx.window;
1959 cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
1960
1961 cx.set_state(
1962 r#"ˇone
1963 two
1964 three
1965 four
1966 five
1967 six
1968 seven
1969 eight
1970 nine
1971 ten
1972 "#,
1973 );
1974
1975 cx.update_editor(|editor, cx| {
1976 assert_eq!(
1977 editor.snapshot(cx).scroll_position(),
1978 gpui::Point::new(0., 0.)
1979 );
1980 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1981 assert_eq!(
1982 editor.snapshot(cx).scroll_position(),
1983 gpui::Point::new(0., 3.)
1984 );
1985 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1986 assert_eq!(
1987 editor.snapshot(cx).scroll_position(),
1988 gpui::Point::new(0., 6.)
1989 );
1990 editor.scroll_screen(&ScrollAmount::Page(-1.), cx);
1991 assert_eq!(
1992 editor.snapshot(cx).scroll_position(),
1993 gpui::Point::new(0., 3.)
1994 );
1995
1996 editor.scroll_screen(&ScrollAmount::Page(-0.5), cx);
1997 assert_eq!(
1998 editor.snapshot(cx).scroll_position(),
1999 gpui::Point::new(0., 1.)
2000 );
2001 editor.scroll_screen(&ScrollAmount::Page(0.5), cx);
2002 assert_eq!(
2003 editor.snapshot(cx).scroll_position(),
2004 gpui::Point::new(0., 3.)
2005 );
2006 });
2007}
2008
2009#[gpui::test]
2010async fn test_autoscroll(cx: &mut gpui::TestAppContext) {
2011 init_test(cx, |_| {});
2012 let mut cx = EditorTestContext::new(cx).await;
2013
2014 let line_height = cx.update_editor(|editor, cx| {
2015 editor.set_vertical_scroll_margin(2, cx);
2016 editor
2017 .style()
2018 .unwrap()
2019 .text
2020 .line_height_in_pixels(cx.rem_size())
2021 });
2022 let window = cx.window;
2023 cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
2024
2025 cx.set_state(
2026 r#"ˇone
2027 two
2028 three
2029 four
2030 five
2031 six
2032 seven
2033 eight
2034 nine
2035 ten
2036 "#,
2037 );
2038 cx.update_editor(|editor, cx| {
2039 assert_eq!(
2040 editor.snapshot(cx).scroll_position(),
2041 gpui::Point::new(0., 0.0)
2042 );
2043 });
2044
2045 // Add a cursor below the visible area. Since both cursors cannot fit
2046 // on screen, the editor autoscrolls to reveal the newest cursor, and
2047 // allows the vertical scroll margin below that cursor.
2048 cx.update_editor(|editor, cx| {
2049 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
2050 selections.select_ranges([
2051 Point::new(0, 0)..Point::new(0, 0),
2052 Point::new(6, 0)..Point::new(6, 0),
2053 ]);
2054 })
2055 });
2056 cx.update_editor(|editor, cx| {
2057 assert_eq!(
2058 editor.snapshot(cx).scroll_position(),
2059 gpui::Point::new(0., 3.0)
2060 );
2061 });
2062
2063 // Move down. The editor cursor scrolls down to track the newest cursor.
2064 cx.update_editor(|editor, cx| {
2065 editor.move_down(&Default::default(), cx);
2066 });
2067 cx.update_editor(|editor, cx| {
2068 assert_eq!(
2069 editor.snapshot(cx).scroll_position(),
2070 gpui::Point::new(0., 4.0)
2071 );
2072 });
2073
2074 // Add a cursor above the visible area. Since both cursors fit on screen,
2075 // the editor scrolls to show both.
2076 cx.update_editor(|editor, cx| {
2077 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
2078 selections.select_ranges([
2079 Point::new(1, 0)..Point::new(1, 0),
2080 Point::new(6, 0)..Point::new(6, 0),
2081 ]);
2082 })
2083 });
2084 cx.update_editor(|editor, cx| {
2085 assert_eq!(
2086 editor.snapshot(cx).scroll_position(),
2087 gpui::Point::new(0., 1.0)
2088 );
2089 });
2090}
2091
2092#[gpui::test]
2093async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
2094 init_test(cx, |_| {});
2095 let mut cx = EditorTestContext::new(cx).await;
2096
2097 let line_height = cx.editor(|editor, cx| {
2098 editor
2099 .style()
2100 .unwrap()
2101 .text
2102 .line_height_in_pixels(cx.rem_size())
2103 });
2104 let window = cx.window;
2105 cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
2106 cx.set_state(
2107 &r#"
2108 ˇone
2109 two
2110 threeˇ
2111 four
2112 five
2113 six
2114 seven
2115 eight
2116 nine
2117 ten
2118 "#
2119 .unindent(),
2120 );
2121
2122 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
2123 cx.assert_editor_state(
2124 &r#"
2125 one
2126 two
2127 three
2128 ˇfour
2129 five
2130 sixˇ
2131 seven
2132 eight
2133 nine
2134 ten
2135 "#
2136 .unindent(),
2137 );
2138
2139 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
2140 cx.assert_editor_state(
2141 &r#"
2142 one
2143 two
2144 three
2145 four
2146 five
2147 six
2148 ˇseven
2149 eight
2150 nineˇ
2151 ten
2152 "#
2153 .unindent(),
2154 );
2155
2156 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2157 cx.assert_editor_state(
2158 &r#"
2159 one
2160 two
2161 three
2162 ˇfour
2163 five
2164 sixˇ
2165 seven
2166 eight
2167 nine
2168 ten
2169 "#
2170 .unindent(),
2171 );
2172
2173 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2174 cx.assert_editor_state(
2175 &r#"
2176 ˇone
2177 two
2178 threeˇ
2179 four
2180 five
2181 six
2182 seven
2183 eight
2184 nine
2185 ten
2186 "#
2187 .unindent(),
2188 );
2189
2190 // Test select collapsing
2191 cx.update_editor(|editor, cx| {
2192 editor.move_page_down(&MovePageDown::default(), cx);
2193 editor.move_page_down(&MovePageDown::default(), cx);
2194 editor.move_page_down(&MovePageDown::default(), cx);
2195 });
2196 cx.assert_editor_state(
2197 &r#"
2198 one
2199 two
2200 three
2201 four
2202 five
2203 six
2204 seven
2205 eight
2206 nine
2207 ˇten
2208 ˇ"#
2209 .unindent(),
2210 );
2211}
2212
2213#[gpui::test]
2214async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
2215 init_test(cx, |_| {});
2216 let mut cx = EditorTestContext::new(cx).await;
2217 cx.set_state("one «two threeˇ» four");
2218 cx.update_editor(|editor, cx| {
2219 editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
2220 assert_eq!(editor.text(cx), " four");
2221 });
2222}
2223
2224#[gpui::test]
2225fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
2226 init_test(cx, |_| {});
2227
2228 let view = cx.add_window(|cx| {
2229 let buffer = MultiBuffer::build_simple("one two three four", cx);
2230 build_editor(buffer.clone(), cx)
2231 });
2232
2233 _ = view.update(cx, |view, cx| {
2234 view.change_selections(None, cx, |s| {
2235 s.select_display_ranges([
2236 // an empty selection - the preceding word fragment is deleted
2237 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2238 // characters selected - they are deleted
2239 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
2240 ])
2241 });
2242 view.delete_to_previous_word_start(
2243 &DeleteToPreviousWordStart {
2244 ignore_newlines: false,
2245 },
2246 cx,
2247 );
2248 assert_eq!(view.buffer.read(cx).read(cx).text(), "e two te four");
2249 });
2250
2251 _ = view.update(cx, |view, cx| {
2252 view.change_selections(None, cx, |s| {
2253 s.select_display_ranges([
2254 // an empty selection - the following word fragment is deleted
2255 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
2256 // characters selected - they are deleted
2257 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
2258 ])
2259 });
2260 view.delete_to_next_word_end(
2261 &DeleteToNextWordEnd {
2262 ignore_newlines: false,
2263 },
2264 cx,
2265 );
2266 assert_eq!(view.buffer.read(cx).read(cx).text(), "e t te our");
2267 });
2268}
2269
2270#[gpui::test]
2271fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
2272 init_test(cx, |_| {});
2273
2274 let view = cx.add_window(|cx| {
2275 let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
2276 build_editor(buffer.clone(), cx)
2277 });
2278 let del_to_prev_word_start = DeleteToPreviousWordStart {
2279 ignore_newlines: false,
2280 };
2281 let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
2282 ignore_newlines: true,
2283 };
2284
2285 _ = view.update(cx, |view, cx| {
2286 view.change_selections(None, cx, |s| {
2287 s.select_display_ranges([
2288 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
2289 ])
2290 });
2291 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2292 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
2293 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2294 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\nthree");
2295 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2296 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\n");
2297 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2298 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2");
2299 view.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, cx);
2300 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n");
2301 view.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, cx);
2302 assert_eq!(view.buffer.read(cx).read(cx).text(), "");
2303 });
2304}
2305
2306#[gpui::test]
2307fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
2308 init_test(cx, |_| {});
2309
2310 let view = cx.add_window(|cx| {
2311 let buffer = MultiBuffer::build_simple("\none\n two\nthree\n four", cx);
2312 build_editor(buffer.clone(), cx)
2313 });
2314 let del_to_next_word_end = DeleteToNextWordEnd {
2315 ignore_newlines: false,
2316 };
2317 let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
2318 ignore_newlines: true,
2319 };
2320
2321 _ = view.update(cx, |view, cx| {
2322 view.change_selections(None, cx, |s| {
2323 s.select_display_ranges([
2324 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
2325 ])
2326 });
2327 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2328 assert_eq!(
2329 view.buffer.read(cx).read(cx).text(),
2330 "one\n two\nthree\n four"
2331 );
2332 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2333 assert_eq!(
2334 view.buffer.read(cx).read(cx).text(),
2335 "\n two\nthree\n four"
2336 );
2337 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2338 assert_eq!(view.buffer.read(cx).read(cx).text(), "two\nthree\n four");
2339 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2340 assert_eq!(view.buffer.read(cx).read(cx).text(), "\nthree\n four");
2341 view.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, cx);
2342 assert_eq!(view.buffer.read(cx).read(cx).text(), "\n four");
2343 view.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, cx);
2344 assert_eq!(view.buffer.read(cx).read(cx).text(), "");
2345 });
2346}
2347
2348#[gpui::test]
2349fn test_newline(cx: &mut TestAppContext) {
2350 init_test(cx, |_| {});
2351
2352 let view = cx.add_window(|cx| {
2353 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
2354 build_editor(buffer.clone(), cx)
2355 });
2356
2357 _ = view.update(cx, |view, cx| {
2358 view.change_selections(None, cx, |s| {
2359 s.select_display_ranges([
2360 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2361 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
2362 DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
2363 ])
2364 });
2365
2366 view.newline(&Newline, cx);
2367 assert_eq!(view.text(cx), "aa\naa\n \n bb\n bb\n");
2368 });
2369}
2370
2371#[gpui::test]
2372fn test_newline_with_old_selections(cx: &mut TestAppContext) {
2373 init_test(cx, |_| {});
2374
2375 let editor = cx.add_window(|cx| {
2376 let buffer = MultiBuffer::build_simple(
2377 "
2378 a
2379 b(
2380 X
2381 )
2382 c(
2383 X
2384 )
2385 "
2386 .unindent()
2387 .as_str(),
2388 cx,
2389 );
2390 let mut editor = build_editor(buffer.clone(), cx);
2391 editor.change_selections(None, cx, |s| {
2392 s.select_ranges([
2393 Point::new(2, 4)..Point::new(2, 5),
2394 Point::new(5, 4)..Point::new(5, 5),
2395 ])
2396 });
2397 editor
2398 });
2399
2400 _ = editor.update(cx, |editor, cx| {
2401 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2402 editor.buffer.update(cx, |buffer, cx| {
2403 buffer.edit(
2404 [
2405 (Point::new(1, 2)..Point::new(3, 0), ""),
2406 (Point::new(4, 2)..Point::new(6, 0), ""),
2407 ],
2408 None,
2409 cx,
2410 );
2411 assert_eq!(
2412 buffer.read(cx).text(),
2413 "
2414 a
2415 b()
2416 c()
2417 "
2418 .unindent()
2419 );
2420 });
2421 assert_eq!(
2422 editor.selections.ranges(cx),
2423 &[
2424 Point::new(1, 2)..Point::new(1, 2),
2425 Point::new(2, 2)..Point::new(2, 2),
2426 ],
2427 );
2428
2429 editor.newline(&Newline, cx);
2430 assert_eq!(
2431 editor.text(cx),
2432 "
2433 a
2434 b(
2435 )
2436 c(
2437 )
2438 "
2439 .unindent()
2440 );
2441
2442 // The selections are moved after the inserted newlines
2443 assert_eq!(
2444 editor.selections.ranges(cx),
2445 &[
2446 Point::new(2, 0)..Point::new(2, 0),
2447 Point::new(4, 0)..Point::new(4, 0),
2448 ],
2449 );
2450 });
2451}
2452
2453#[gpui::test]
2454async fn test_newline_above(cx: &mut gpui::TestAppContext) {
2455 init_test(cx, |settings| {
2456 settings.defaults.tab_size = NonZeroU32::new(4)
2457 });
2458
2459 let language = Arc::new(
2460 Language::new(
2461 LanguageConfig::default(),
2462 Some(tree_sitter_rust::LANGUAGE.into()),
2463 )
2464 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2465 .unwrap(),
2466 );
2467
2468 let mut cx = EditorTestContext::new(cx).await;
2469 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2470 cx.set_state(indoc! {"
2471 const a: ˇA = (
2472 (ˇ
2473 «const_functionˇ»(ˇ),
2474 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2475 )ˇ
2476 ˇ);ˇ
2477 "});
2478
2479 cx.update_editor(|e, cx| e.newline_above(&NewlineAbove, cx));
2480 cx.assert_editor_state(indoc! {"
2481 ˇ
2482 const a: A = (
2483 ˇ
2484 (
2485 ˇ
2486 ˇ
2487 const_function(),
2488 ˇ
2489 ˇ
2490 ˇ
2491 ˇ
2492 something_else,
2493 ˇ
2494 )
2495 ˇ
2496 ˇ
2497 );
2498 "});
2499}
2500
2501#[gpui::test]
2502async fn test_newline_below(cx: &mut gpui::TestAppContext) {
2503 init_test(cx, |settings| {
2504 settings.defaults.tab_size = NonZeroU32::new(4)
2505 });
2506
2507 let language = Arc::new(
2508 Language::new(
2509 LanguageConfig::default(),
2510 Some(tree_sitter_rust::LANGUAGE.into()),
2511 )
2512 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2513 .unwrap(),
2514 );
2515
2516 let mut cx = EditorTestContext::new(cx).await;
2517 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2518 cx.set_state(indoc! {"
2519 const a: ˇA = (
2520 (ˇ
2521 «const_functionˇ»(ˇ),
2522 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2523 )ˇ
2524 ˇ);ˇ
2525 "});
2526
2527 cx.update_editor(|e, cx| e.newline_below(&NewlineBelow, cx));
2528 cx.assert_editor_state(indoc! {"
2529 const a: A = (
2530 ˇ
2531 (
2532 ˇ
2533 const_function(),
2534 ˇ
2535 ˇ
2536 something_else,
2537 ˇ
2538 ˇ
2539 ˇ
2540 ˇ
2541 )
2542 ˇ
2543 );
2544 ˇ
2545 ˇ
2546 "});
2547}
2548
2549#[gpui::test]
2550async fn test_newline_comments(cx: &mut gpui::TestAppContext) {
2551 init_test(cx, |settings| {
2552 settings.defaults.tab_size = NonZeroU32::new(4)
2553 });
2554
2555 let language = Arc::new(Language::new(
2556 LanguageConfig {
2557 line_comments: vec!["//".into()],
2558 ..LanguageConfig::default()
2559 },
2560 None,
2561 ));
2562 {
2563 let mut cx = EditorTestContext::new(cx).await;
2564 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2565 cx.set_state(indoc! {"
2566 // Fooˇ
2567 "});
2568
2569 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2570 cx.assert_editor_state(indoc! {"
2571 // Foo
2572 //ˇ
2573 "});
2574 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
2575 cx.set_state(indoc! {"
2576 ˇ// Foo
2577 "});
2578 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2579 cx.assert_editor_state(indoc! {"
2580
2581 ˇ// Foo
2582 "});
2583 }
2584 // Ensure that comment continuations can be disabled.
2585 update_test_language_settings(cx, |settings| {
2586 settings.defaults.extend_comment_on_newline = Some(false);
2587 });
2588 let mut cx = EditorTestContext::new(cx).await;
2589 cx.set_state(indoc! {"
2590 // Fooˇ
2591 "});
2592 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2593 cx.assert_editor_state(indoc! {"
2594 // Foo
2595 ˇ
2596 "});
2597}
2598
2599#[gpui::test]
2600fn test_insert_with_old_selections(cx: &mut TestAppContext) {
2601 init_test(cx, |_| {});
2602
2603 let editor = cx.add_window(|cx| {
2604 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
2605 let mut editor = build_editor(buffer.clone(), cx);
2606 editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
2607 editor
2608 });
2609
2610 _ = editor.update(cx, |editor, cx| {
2611 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2612 editor.buffer.update(cx, |buffer, cx| {
2613 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
2614 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
2615 });
2616 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
2617
2618 editor.insert("Z", cx);
2619 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
2620
2621 // The selections are moved after the inserted characters
2622 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
2623 });
2624}
2625
2626#[gpui::test]
2627async fn test_tab(cx: &mut gpui::TestAppContext) {
2628 init_test(cx, |settings| {
2629 settings.defaults.tab_size = NonZeroU32::new(3)
2630 });
2631
2632 let mut cx = EditorTestContext::new(cx).await;
2633 cx.set_state(indoc! {"
2634 ˇabˇc
2635 ˇ🏀ˇ🏀ˇefg
2636 dˇ
2637 "});
2638 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2639 cx.assert_editor_state(indoc! {"
2640 ˇab ˇc
2641 ˇ🏀 ˇ🏀 ˇefg
2642 d ˇ
2643 "});
2644
2645 cx.set_state(indoc! {"
2646 a
2647 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2648 "});
2649 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2650 cx.assert_editor_state(indoc! {"
2651 a
2652 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2653 "});
2654}
2655
2656#[gpui::test]
2657async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) {
2658 init_test(cx, |_| {});
2659
2660 let mut cx = EditorTestContext::new(cx).await;
2661 let language = Arc::new(
2662 Language::new(
2663 LanguageConfig::default(),
2664 Some(tree_sitter_rust::LANGUAGE.into()),
2665 )
2666 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2667 .unwrap(),
2668 );
2669 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2670
2671 // cursors that are already at the suggested indent level insert
2672 // a soft tab. cursors that are to the left of the suggested indent
2673 // auto-indent their line.
2674 cx.set_state(indoc! {"
2675 ˇ
2676 const a: B = (
2677 c(
2678 d(
2679 ˇ
2680 )
2681 ˇ
2682 ˇ )
2683 );
2684 "});
2685 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2686 cx.assert_editor_state(indoc! {"
2687 ˇ
2688 const a: B = (
2689 c(
2690 d(
2691 ˇ
2692 )
2693 ˇ
2694 ˇ)
2695 );
2696 "});
2697
2698 // handle auto-indent when there are multiple cursors on the same line
2699 cx.set_state(indoc! {"
2700 const a: B = (
2701 c(
2702 ˇ ˇ
2703 ˇ )
2704 );
2705 "});
2706 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2707 cx.assert_editor_state(indoc! {"
2708 const a: B = (
2709 c(
2710 ˇ
2711 ˇ)
2712 );
2713 "});
2714}
2715
2716#[gpui::test]
2717async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
2718 init_test(cx, |settings| {
2719 settings.defaults.tab_size = NonZeroU32::new(4)
2720 });
2721
2722 let language = Arc::new(
2723 Language::new(
2724 LanguageConfig::default(),
2725 Some(tree_sitter_rust::LANGUAGE.into()),
2726 )
2727 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2728 .unwrap(),
2729 );
2730
2731 let mut cx = EditorTestContext::new(cx).await;
2732 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2733 cx.set_state(indoc! {"
2734 fn a() {
2735 if b {
2736 \t ˇc
2737 }
2738 }
2739 "});
2740
2741 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2742 cx.assert_editor_state(indoc! {"
2743 fn a() {
2744 if b {
2745 ˇc
2746 }
2747 }
2748 "});
2749}
2750
2751#[gpui::test]
2752async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
2753 init_test(cx, |settings| {
2754 settings.defaults.tab_size = NonZeroU32::new(4);
2755 });
2756
2757 let mut cx = EditorTestContext::new(cx).await;
2758
2759 cx.set_state(indoc! {"
2760 «oneˇ» «twoˇ»
2761 three
2762 four
2763 "});
2764 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2765 cx.assert_editor_state(indoc! {"
2766 «oneˇ» «twoˇ»
2767 three
2768 four
2769 "});
2770
2771 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2772 cx.assert_editor_state(indoc! {"
2773 «oneˇ» «twoˇ»
2774 three
2775 four
2776 "});
2777
2778 // select across line ending
2779 cx.set_state(indoc! {"
2780 one two
2781 t«hree
2782 ˇ» four
2783 "});
2784 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2785 cx.assert_editor_state(indoc! {"
2786 one two
2787 t«hree
2788 ˇ» four
2789 "});
2790
2791 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2792 cx.assert_editor_state(indoc! {"
2793 one two
2794 t«hree
2795 ˇ» four
2796 "});
2797
2798 // Ensure that indenting/outdenting works when the cursor is at column 0.
2799 cx.set_state(indoc! {"
2800 one two
2801 ˇthree
2802 four
2803 "});
2804 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2805 cx.assert_editor_state(indoc! {"
2806 one two
2807 ˇthree
2808 four
2809 "});
2810
2811 cx.set_state(indoc! {"
2812 one two
2813 ˇ three
2814 four
2815 "});
2816 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2817 cx.assert_editor_state(indoc! {"
2818 one two
2819 ˇthree
2820 four
2821 "});
2822}
2823
2824#[gpui::test]
2825async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
2826 init_test(cx, |settings| {
2827 settings.defaults.hard_tabs = Some(true);
2828 });
2829
2830 let mut cx = EditorTestContext::new(cx).await;
2831
2832 // select two ranges on one line
2833 cx.set_state(indoc! {"
2834 «oneˇ» «twoˇ»
2835 three
2836 four
2837 "});
2838 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2839 cx.assert_editor_state(indoc! {"
2840 \t«oneˇ» «twoˇ»
2841 three
2842 four
2843 "});
2844 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2845 cx.assert_editor_state(indoc! {"
2846 \t\t«oneˇ» «twoˇ»
2847 three
2848 four
2849 "});
2850 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2851 cx.assert_editor_state(indoc! {"
2852 \t«oneˇ» «twoˇ»
2853 three
2854 four
2855 "});
2856 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2857 cx.assert_editor_state(indoc! {"
2858 «oneˇ» «twoˇ»
2859 three
2860 four
2861 "});
2862
2863 // select across a line ending
2864 cx.set_state(indoc! {"
2865 one two
2866 t«hree
2867 ˇ»four
2868 "});
2869 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2870 cx.assert_editor_state(indoc! {"
2871 one two
2872 \tt«hree
2873 ˇ»four
2874 "});
2875 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2876 cx.assert_editor_state(indoc! {"
2877 one two
2878 \t\tt«hree
2879 ˇ»four
2880 "});
2881 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2882 cx.assert_editor_state(indoc! {"
2883 one two
2884 \tt«hree
2885 ˇ»four
2886 "});
2887 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2888 cx.assert_editor_state(indoc! {"
2889 one two
2890 t«hree
2891 ˇ»four
2892 "});
2893
2894 // Ensure that indenting/outdenting works when the cursor is at column 0.
2895 cx.set_state(indoc! {"
2896 one two
2897 ˇthree
2898 four
2899 "});
2900 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2901 cx.assert_editor_state(indoc! {"
2902 one two
2903 ˇthree
2904 four
2905 "});
2906 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2907 cx.assert_editor_state(indoc! {"
2908 one two
2909 \tˇthree
2910 four
2911 "});
2912 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2913 cx.assert_editor_state(indoc! {"
2914 one two
2915 ˇthree
2916 four
2917 "});
2918}
2919
2920#[gpui::test]
2921fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
2922 init_test(cx, |settings| {
2923 settings.languages.extend([
2924 (
2925 "TOML".into(),
2926 LanguageSettingsContent {
2927 tab_size: NonZeroU32::new(2),
2928 ..Default::default()
2929 },
2930 ),
2931 (
2932 "Rust".into(),
2933 LanguageSettingsContent {
2934 tab_size: NonZeroU32::new(4),
2935 ..Default::default()
2936 },
2937 ),
2938 ]);
2939 });
2940
2941 let toml_language = Arc::new(Language::new(
2942 LanguageConfig {
2943 name: "TOML".into(),
2944 ..Default::default()
2945 },
2946 None,
2947 ));
2948 let rust_language = Arc::new(Language::new(
2949 LanguageConfig {
2950 name: "Rust".into(),
2951 ..Default::default()
2952 },
2953 None,
2954 ));
2955
2956 let toml_buffer =
2957 cx.new_model(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
2958 let rust_buffer = cx.new_model(|cx| {
2959 Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx)
2960 });
2961 let multibuffer = cx.new_model(|cx| {
2962 let mut multibuffer = MultiBuffer::new(ReadWrite);
2963 multibuffer.push_excerpts(
2964 toml_buffer.clone(),
2965 [ExcerptRange {
2966 context: Point::new(0, 0)..Point::new(2, 0),
2967 primary: None,
2968 }],
2969 cx,
2970 );
2971 multibuffer.push_excerpts(
2972 rust_buffer.clone(),
2973 [ExcerptRange {
2974 context: Point::new(0, 0)..Point::new(1, 0),
2975 primary: None,
2976 }],
2977 cx,
2978 );
2979 multibuffer
2980 });
2981
2982 cx.add_window(|cx| {
2983 let mut editor = build_editor(multibuffer, cx);
2984
2985 assert_eq!(
2986 editor.text(cx),
2987 indoc! {"
2988 a = 1
2989 b = 2
2990
2991 const c: usize = 3;
2992 "}
2993 );
2994
2995 select_ranges(
2996 &mut editor,
2997 indoc! {"
2998 «aˇ» = 1
2999 b = 2
3000
3001 «const c:ˇ» usize = 3;
3002 "},
3003 cx,
3004 );
3005
3006 editor.tab(&Tab, cx);
3007 assert_text_with_selections(
3008 &mut editor,
3009 indoc! {"
3010 «aˇ» = 1
3011 b = 2
3012
3013 «const c:ˇ» usize = 3;
3014 "},
3015 cx,
3016 );
3017 editor.tab_prev(&TabPrev, cx);
3018 assert_text_with_selections(
3019 &mut editor,
3020 indoc! {"
3021 «aˇ» = 1
3022 b = 2
3023
3024 «const c:ˇ» usize = 3;
3025 "},
3026 cx,
3027 );
3028
3029 editor
3030 });
3031}
3032
3033#[gpui::test]
3034async fn test_backspace(cx: &mut gpui::TestAppContext) {
3035 init_test(cx, |_| {});
3036
3037 let mut cx = EditorTestContext::new(cx).await;
3038
3039 // Basic backspace
3040 cx.set_state(indoc! {"
3041 onˇe two three
3042 fou«rˇ» five six
3043 seven «ˇeight nine
3044 »ten
3045 "});
3046 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3047 cx.assert_editor_state(indoc! {"
3048 oˇe two three
3049 fouˇ five six
3050 seven ˇten
3051 "});
3052
3053 // Test backspace inside and around indents
3054 cx.set_state(indoc! {"
3055 zero
3056 ˇone
3057 ˇtwo
3058 ˇ ˇ ˇ three
3059 ˇ ˇ four
3060 "});
3061 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3062 cx.assert_editor_state(indoc! {"
3063 zero
3064 ˇone
3065 ˇtwo
3066 ˇ threeˇ four
3067 "});
3068
3069 // Test backspace with line_mode set to true
3070 cx.update_editor(|e, _| e.selections.line_mode = true);
3071 cx.set_state(indoc! {"
3072 The ˇquick ˇbrown
3073 fox jumps over
3074 the lazy dog
3075 ˇThe qu«ick bˇ»rown"});
3076 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3077 cx.assert_editor_state(indoc! {"
3078 ˇfox jumps over
3079 the lazy dogˇ"});
3080}
3081
3082#[gpui::test]
3083async fn test_delete(cx: &mut gpui::TestAppContext) {
3084 init_test(cx, |_| {});
3085
3086 let mut cx = EditorTestContext::new(cx).await;
3087 cx.set_state(indoc! {"
3088 onˇe two three
3089 fou«rˇ» five six
3090 seven «ˇeight nine
3091 »ten
3092 "});
3093 cx.update_editor(|e, cx| e.delete(&Delete, cx));
3094 cx.assert_editor_state(indoc! {"
3095 onˇ two three
3096 fouˇ five six
3097 seven ˇten
3098 "});
3099
3100 // Test backspace with line_mode set to true
3101 cx.update_editor(|e, _| e.selections.line_mode = true);
3102 cx.set_state(indoc! {"
3103 The ˇquick ˇbrown
3104 fox «ˇjum»ps over
3105 the lazy dog
3106 ˇThe qu«ick bˇ»rown"});
3107 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3108 cx.assert_editor_state("ˇthe lazy dogˇ");
3109}
3110
3111#[gpui::test]
3112fn test_delete_line(cx: &mut TestAppContext) {
3113 init_test(cx, |_| {});
3114
3115 let view = cx.add_window(|cx| {
3116 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3117 build_editor(buffer, cx)
3118 });
3119 _ = view.update(cx, |view, cx| {
3120 view.change_selections(None, cx, |s| {
3121 s.select_display_ranges([
3122 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3123 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3124 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3125 ])
3126 });
3127 view.delete_line(&DeleteLine, cx);
3128 assert_eq!(view.display_text(cx), "ghi");
3129 assert_eq!(
3130 view.selections.display_ranges(cx),
3131 vec![
3132 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
3133 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
3134 ]
3135 );
3136 });
3137
3138 let view = cx.add_window(|cx| {
3139 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3140 build_editor(buffer, cx)
3141 });
3142 _ = view.update(cx, |view, cx| {
3143 view.change_selections(None, cx, |s| {
3144 s.select_display_ranges([
3145 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
3146 ])
3147 });
3148 view.delete_line(&DeleteLine, cx);
3149 assert_eq!(view.display_text(cx), "ghi\n");
3150 assert_eq!(
3151 view.selections.display_ranges(cx),
3152 vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
3153 );
3154 });
3155}
3156
3157#[gpui::test]
3158fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
3159 init_test(cx, |_| {});
3160
3161 cx.add_window(|cx| {
3162 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3163 let mut editor = build_editor(buffer.clone(), cx);
3164 let buffer = buffer.read(cx).as_singleton().unwrap();
3165
3166 assert_eq!(
3167 editor.selections.ranges::<Point>(cx),
3168 &[Point::new(0, 0)..Point::new(0, 0)]
3169 );
3170
3171 // When on single line, replace newline at end by space
3172 editor.join_lines(&JoinLines, cx);
3173 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3174 assert_eq!(
3175 editor.selections.ranges::<Point>(cx),
3176 &[Point::new(0, 3)..Point::new(0, 3)]
3177 );
3178
3179 // When multiple lines are selected, remove newlines that are spanned by the selection
3180 editor.change_selections(None, cx, |s| {
3181 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
3182 });
3183 editor.join_lines(&JoinLines, cx);
3184 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
3185 assert_eq!(
3186 editor.selections.ranges::<Point>(cx),
3187 &[Point::new(0, 11)..Point::new(0, 11)]
3188 );
3189
3190 // Undo should be transactional
3191 editor.undo(&Undo, cx);
3192 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3193 assert_eq!(
3194 editor.selections.ranges::<Point>(cx),
3195 &[Point::new(0, 5)..Point::new(2, 2)]
3196 );
3197
3198 // When joining an empty line don't insert a space
3199 editor.change_selections(None, cx, |s| {
3200 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
3201 });
3202 editor.join_lines(&JoinLines, cx);
3203 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
3204 assert_eq!(
3205 editor.selections.ranges::<Point>(cx),
3206 [Point::new(2, 3)..Point::new(2, 3)]
3207 );
3208
3209 // We can remove trailing newlines
3210 editor.join_lines(&JoinLines, cx);
3211 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3212 assert_eq!(
3213 editor.selections.ranges::<Point>(cx),
3214 [Point::new(2, 3)..Point::new(2, 3)]
3215 );
3216
3217 // We don't blow up on the last line
3218 editor.join_lines(&JoinLines, cx);
3219 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3220 assert_eq!(
3221 editor.selections.ranges::<Point>(cx),
3222 [Point::new(2, 3)..Point::new(2, 3)]
3223 );
3224
3225 // reset to test indentation
3226 editor.buffer.update(cx, |buffer, cx| {
3227 buffer.edit(
3228 [
3229 (Point::new(1, 0)..Point::new(1, 2), " "),
3230 (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
3231 ],
3232 None,
3233 cx,
3234 )
3235 });
3236
3237 // We remove any leading spaces
3238 assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
3239 editor.change_selections(None, cx, |s| {
3240 s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
3241 });
3242 editor.join_lines(&JoinLines, cx);
3243 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
3244
3245 // We don't insert a space for a line containing only spaces
3246 editor.join_lines(&JoinLines, cx);
3247 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
3248
3249 // We ignore any leading tabs
3250 editor.join_lines(&JoinLines, cx);
3251 assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
3252
3253 editor
3254 });
3255}
3256
3257#[gpui::test]
3258fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
3259 init_test(cx, |_| {});
3260
3261 cx.add_window(|cx| {
3262 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3263 let mut editor = build_editor(buffer.clone(), cx);
3264 let buffer = buffer.read(cx).as_singleton().unwrap();
3265
3266 editor.change_selections(None, cx, |s| {
3267 s.select_ranges([
3268 Point::new(0, 2)..Point::new(1, 1),
3269 Point::new(1, 2)..Point::new(1, 2),
3270 Point::new(3, 1)..Point::new(3, 2),
3271 ])
3272 });
3273
3274 editor.join_lines(&JoinLines, cx);
3275 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
3276
3277 assert_eq!(
3278 editor.selections.ranges::<Point>(cx),
3279 [
3280 Point::new(0, 7)..Point::new(0, 7),
3281 Point::new(1, 3)..Point::new(1, 3)
3282 ]
3283 );
3284 editor
3285 });
3286}
3287
3288#[gpui::test]
3289async fn test_join_lines_with_git_diff_base(
3290 executor: BackgroundExecutor,
3291 cx: &mut gpui::TestAppContext,
3292) {
3293 init_test(cx, |_| {});
3294
3295 let mut cx = EditorTestContext::new(cx).await;
3296
3297 let diff_base = r#"
3298 Line 0
3299 Line 1
3300 Line 2
3301 Line 3
3302 "#
3303 .unindent();
3304
3305 cx.set_state(
3306 &r#"
3307 ˇLine 0
3308 Line 1
3309 Line 2
3310 Line 3
3311 "#
3312 .unindent(),
3313 );
3314
3315 cx.set_diff_base(&diff_base);
3316 executor.run_until_parked();
3317
3318 // Join lines
3319 cx.update_editor(|editor, cx| {
3320 editor.join_lines(&JoinLines, cx);
3321 });
3322 executor.run_until_parked();
3323
3324 cx.assert_editor_state(
3325 &r#"
3326 Line 0ˇ Line 1
3327 Line 2
3328 Line 3
3329 "#
3330 .unindent(),
3331 );
3332 // Join again
3333 cx.update_editor(|editor, cx| {
3334 editor.join_lines(&JoinLines, cx);
3335 });
3336 executor.run_until_parked();
3337
3338 cx.assert_editor_state(
3339 &r#"
3340 Line 0 Line 1ˇ Line 2
3341 Line 3
3342 "#
3343 .unindent(),
3344 );
3345}
3346
3347#[gpui::test]
3348async fn test_custom_newlines_cause_no_false_positive_diffs(
3349 executor: BackgroundExecutor,
3350 cx: &mut gpui::TestAppContext,
3351) {
3352 init_test(cx, |_| {});
3353 let mut cx = EditorTestContext::new(cx).await;
3354 cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
3355 cx.set_diff_base("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
3356 executor.run_until_parked();
3357
3358 cx.update_editor(|editor, cx| {
3359 let snapshot = editor.snapshot(cx);
3360 assert_eq!(
3361 snapshot
3362 .diff_map
3363 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len(), &snapshot.buffer_snapshot)
3364 .collect::<Vec<_>>(),
3365 Vec::new(),
3366 "Should not have any diffs for files with custom newlines"
3367 );
3368 });
3369}
3370
3371#[gpui::test]
3372async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
3373 init_test(cx, |_| {});
3374
3375 let mut cx = EditorTestContext::new(cx).await;
3376
3377 // Test sort_lines_case_insensitive()
3378 cx.set_state(indoc! {"
3379 «z
3380 y
3381 x
3382 Z
3383 Y
3384 Xˇ»
3385 "});
3386 cx.update_editor(|e, cx| e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, cx));
3387 cx.assert_editor_state(indoc! {"
3388 «x
3389 X
3390 y
3391 Y
3392 z
3393 Zˇ»
3394 "});
3395
3396 // Test reverse_lines()
3397 cx.set_state(indoc! {"
3398 «5
3399 4
3400 3
3401 2
3402 1ˇ»
3403 "});
3404 cx.update_editor(|e, cx| e.reverse_lines(&ReverseLines, cx));
3405 cx.assert_editor_state(indoc! {"
3406 «1
3407 2
3408 3
3409 4
3410 5ˇ»
3411 "});
3412
3413 // Skip testing shuffle_line()
3414
3415 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
3416 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
3417
3418 // Don't manipulate when cursor is on single line, but expand the selection
3419 cx.set_state(indoc! {"
3420 ddˇdd
3421 ccc
3422 bb
3423 a
3424 "});
3425 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3426 cx.assert_editor_state(indoc! {"
3427 «ddddˇ»
3428 ccc
3429 bb
3430 a
3431 "});
3432
3433 // Basic manipulate case
3434 // Start selection moves to column 0
3435 // End of selection shrinks to fit shorter line
3436 cx.set_state(indoc! {"
3437 dd«d
3438 ccc
3439 bb
3440 aaaaaˇ»
3441 "});
3442 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3443 cx.assert_editor_state(indoc! {"
3444 «aaaaa
3445 bb
3446 ccc
3447 dddˇ»
3448 "});
3449
3450 // Manipulate case with newlines
3451 cx.set_state(indoc! {"
3452 dd«d
3453 ccc
3454
3455 bb
3456 aaaaa
3457
3458 ˇ»
3459 "});
3460 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3461 cx.assert_editor_state(indoc! {"
3462 «
3463
3464 aaaaa
3465 bb
3466 ccc
3467 dddˇ»
3468
3469 "});
3470
3471 // Adding new line
3472 cx.set_state(indoc! {"
3473 aa«a
3474 bbˇ»b
3475 "});
3476 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added_line")));
3477 cx.assert_editor_state(indoc! {"
3478 «aaa
3479 bbb
3480 added_lineˇ»
3481 "});
3482
3483 // Removing line
3484 cx.set_state(indoc! {"
3485 aa«a
3486 bbbˇ»
3487 "});
3488 cx.update_editor(|e, cx| {
3489 e.manipulate_lines(cx, |lines| {
3490 lines.pop();
3491 })
3492 });
3493 cx.assert_editor_state(indoc! {"
3494 «aaaˇ»
3495 "});
3496
3497 // Removing all lines
3498 cx.set_state(indoc! {"
3499 aa«a
3500 bbbˇ»
3501 "});
3502 cx.update_editor(|e, cx| {
3503 e.manipulate_lines(cx, |lines| {
3504 lines.drain(..);
3505 })
3506 });
3507 cx.assert_editor_state(indoc! {"
3508 ˇ
3509 "});
3510}
3511
3512#[gpui::test]
3513async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
3514 init_test(cx, |_| {});
3515
3516 let mut cx = EditorTestContext::new(cx).await;
3517
3518 // Consider continuous selection as single selection
3519 cx.set_state(indoc! {"
3520 Aaa«aa
3521 cˇ»c«c
3522 bb
3523 aaaˇ»aa
3524 "});
3525 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3526 cx.assert_editor_state(indoc! {"
3527 «Aaaaa
3528 ccc
3529 bb
3530 aaaaaˇ»
3531 "});
3532
3533 cx.set_state(indoc! {"
3534 Aaa«aa
3535 cˇ»c«c
3536 bb
3537 aaaˇ»aa
3538 "});
3539 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3540 cx.assert_editor_state(indoc! {"
3541 «Aaaaa
3542 ccc
3543 bbˇ»
3544 "});
3545
3546 // Consider non continuous selection as distinct dedup operations
3547 cx.set_state(indoc! {"
3548 «aaaaa
3549 bb
3550 aaaaa
3551 aaaaaˇ»
3552
3553 aaa«aaˇ»
3554 "});
3555 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3556 cx.assert_editor_state(indoc! {"
3557 «aaaaa
3558 bbˇ»
3559
3560 «aaaaaˇ»
3561 "});
3562}
3563
3564#[gpui::test]
3565async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
3566 init_test(cx, |_| {});
3567
3568 let mut cx = EditorTestContext::new(cx).await;
3569
3570 cx.set_state(indoc! {"
3571 «Aaa
3572 aAa
3573 Aaaˇ»
3574 "});
3575 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3576 cx.assert_editor_state(indoc! {"
3577 «Aaa
3578 aAaˇ»
3579 "});
3580
3581 cx.set_state(indoc! {"
3582 «Aaa
3583 aAa
3584 aaAˇ»
3585 "});
3586 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3587 cx.assert_editor_state(indoc! {"
3588 «Aaaˇ»
3589 "});
3590}
3591
3592#[gpui::test]
3593async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
3594 init_test(cx, |_| {});
3595
3596 let mut cx = EditorTestContext::new(cx).await;
3597
3598 // Manipulate with multiple selections on a single line
3599 cx.set_state(indoc! {"
3600 dd«dd
3601 cˇ»c«c
3602 bb
3603 aaaˇ»aa
3604 "});
3605 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3606 cx.assert_editor_state(indoc! {"
3607 «aaaaa
3608 bb
3609 ccc
3610 ddddˇ»
3611 "});
3612
3613 // Manipulate with multiple disjoin selections
3614 cx.set_state(indoc! {"
3615 5«
3616 4
3617 3
3618 2
3619 1ˇ»
3620
3621 dd«dd
3622 ccc
3623 bb
3624 aaaˇ»aa
3625 "});
3626 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3627 cx.assert_editor_state(indoc! {"
3628 «1
3629 2
3630 3
3631 4
3632 5ˇ»
3633
3634 «aaaaa
3635 bb
3636 ccc
3637 ddddˇ»
3638 "});
3639
3640 // Adding lines on each selection
3641 cx.set_state(indoc! {"
3642 2«
3643 1ˇ»
3644
3645 bb«bb
3646 aaaˇ»aa
3647 "});
3648 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added line")));
3649 cx.assert_editor_state(indoc! {"
3650 «2
3651 1
3652 added lineˇ»
3653
3654 «bbbb
3655 aaaaa
3656 added lineˇ»
3657 "});
3658
3659 // Removing lines on each selection
3660 cx.set_state(indoc! {"
3661 2«
3662 1ˇ»
3663
3664 bb«bb
3665 aaaˇ»aa
3666 "});
3667 cx.update_editor(|e, cx| {
3668 e.manipulate_lines(cx, |lines| {
3669 lines.pop();
3670 })
3671 });
3672 cx.assert_editor_state(indoc! {"
3673 «2ˇ»
3674
3675 «bbbbˇ»
3676 "});
3677}
3678
3679#[gpui::test]
3680async fn test_manipulate_text(cx: &mut TestAppContext) {
3681 init_test(cx, |_| {});
3682
3683 let mut cx = EditorTestContext::new(cx).await;
3684
3685 // Test convert_to_upper_case()
3686 cx.set_state(indoc! {"
3687 «hello worldˇ»
3688 "});
3689 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3690 cx.assert_editor_state(indoc! {"
3691 «HELLO WORLDˇ»
3692 "});
3693
3694 // Test convert_to_lower_case()
3695 cx.set_state(indoc! {"
3696 «HELLO WORLDˇ»
3697 "});
3698 cx.update_editor(|e, cx| e.convert_to_lower_case(&ConvertToLowerCase, cx));
3699 cx.assert_editor_state(indoc! {"
3700 «hello worldˇ»
3701 "});
3702
3703 // Test multiple line, single selection case
3704 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3705 cx.set_state(indoc! {"
3706 «The quick brown
3707 fox jumps over
3708 the lazy dogˇ»
3709 "});
3710 cx.update_editor(|e, cx| e.convert_to_title_case(&ConvertToTitleCase, cx));
3711 cx.assert_editor_state(indoc! {"
3712 «The Quick Brown
3713 Fox Jumps Over
3714 The Lazy Dogˇ»
3715 "});
3716
3717 // Test multiple line, single selection case
3718 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3719 cx.set_state(indoc! {"
3720 «The quick brown
3721 fox jumps over
3722 the lazy dogˇ»
3723 "});
3724 cx.update_editor(|e, cx| e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, cx));
3725 cx.assert_editor_state(indoc! {"
3726 «TheQuickBrown
3727 FoxJumpsOver
3728 TheLazyDogˇ»
3729 "});
3730
3731 // From here on out, test more complex cases of manipulate_text()
3732
3733 // Test no selection case - should affect words cursors are in
3734 // Cursor at beginning, middle, and end of word
3735 cx.set_state(indoc! {"
3736 ˇhello big beauˇtiful worldˇ
3737 "});
3738 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3739 cx.assert_editor_state(indoc! {"
3740 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3741 "});
3742
3743 // Test multiple selections on a single line and across multiple lines
3744 cx.set_state(indoc! {"
3745 «Theˇ» quick «brown
3746 foxˇ» jumps «overˇ»
3747 the «lazyˇ» dog
3748 "});
3749 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3750 cx.assert_editor_state(indoc! {"
3751 «THEˇ» quick «BROWN
3752 FOXˇ» jumps «OVERˇ»
3753 the «LAZYˇ» dog
3754 "});
3755
3756 // Test case where text length grows
3757 cx.set_state(indoc! {"
3758 «tschüߡ»
3759 "});
3760 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3761 cx.assert_editor_state(indoc! {"
3762 «TSCHÜSSˇ»
3763 "});
3764
3765 // Test to make sure we don't crash when text shrinks
3766 cx.set_state(indoc! {"
3767 aaa_bbbˇ
3768 "});
3769 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3770 cx.assert_editor_state(indoc! {"
3771 «aaaBbbˇ»
3772 "});
3773
3774 // Test to make sure we all aware of the fact that each word can grow and shrink
3775 // Final selections should be aware of this fact
3776 cx.set_state(indoc! {"
3777 aaa_bˇbb bbˇb_ccc ˇccc_ddd
3778 "});
3779 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3780 cx.assert_editor_state(indoc! {"
3781 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
3782 "});
3783
3784 cx.set_state(indoc! {"
3785 «hElLo, WoRld!ˇ»
3786 "});
3787 cx.update_editor(|e, cx| e.convert_to_opposite_case(&ConvertToOppositeCase, cx));
3788 cx.assert_editor_state(indoc! {"
3789 «HeLlO, wOrLD!ˇ»
3790 "});
3791}
3792
3793#[gpui::test]
3794fn test_duplicate_line(cx: &mut TestAppContext) {
3795 init_test(cx, |_| {});
3796
3797 let view = cx.add_window(|cx| {
3798 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3799 build_editor(buffer, cx)
3800 });
3801 _ = view.update(cx, |view, cx| {
3802 view.change_selections(None, cx, |s| {
3803 s.select_display_ranges([
3804 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3805 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3806 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3807 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3808 ])
3809 });
3810 view.duplicate_line_down(&DuplicateLineDown, cx);
3811 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3812 assert_eq!(
3813 view.selections.display_ranges(cx),
3814 vec![
3815 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3816 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
3817 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3818 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3819 ]
3820 );
3821 });
3822
3823 let view = cx.add_window(|cx| {
3824 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3825 build_editor(buffer, cx)
3826 });
3827 _ = view.update(cx, |view, cx| {
3828 view.change_selections(None, cx, |s| {
3829 s.select_display_ranges([
3830 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3831 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3832 ])
3833 });
3834 view.duplicate_line_down(&DuplicateLineDown, cx);
3835 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3836 assert_eq!(
3837 view.selections.display_ranges(cx),
3838 vec![
3839 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
3840 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
3841 ]
3842 );
3843 });
3844
3845 // With `move_upwards` the selections stay in place, except for
3846 // the lines inserted above them
3847 let view = cx.add_window(|cx| {
3848 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3849 build_editor(buffer, cx)
3850 });
3851 _ = view.update(cx, |view, cx| {
3852 view.change_selections(None, cx, |s| {
3853 s.select_display_ranges([
3854 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3855 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3856 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3857 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3858 ])
3859 });
3860 view.duplicate_line_up(&DuplicateLineUp, cx);
3861 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3862 assert_eq!(
3863 view.selections.display_ranges(cx),
3864 vec![
3865 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3866 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3867 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
3868 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3869 ]
3870 );
3871 });
3872
3873 let view = cx.add_window(|cx| {
3874 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3875 build_editor(buffer, cx)
3876 });
3877 _ = view.update(cx, |view, cx| {
3878 view.change_selections(None, cx, |s| {
3879 s.select_display_ranges([
3880 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3881 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3882 ])
3883 });
3884 view.duplicate_line_up(&DuplicateLineUp, cx);
3885 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3886 assert_eq!(
3887 view.selections.display_ranges(cx),
3888 vec![
3889 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3890 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3891 ]
3892 );
3893 });
3894
3895 let view = cx.add_window(|cx| {
3896 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3897 build_editor(buffer, cx)
3898 });
3899 _ = view.update(cx, |view, cx| {
3900 view.change_selections(None, cx, |s| {
3901 s.select_display_ranges([
3902 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3903 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3904 ])
3905 });
3906 view.duplicate_selection(&DuplicateSelection, cx);
3907 assert_eq!(view.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
3908 assert_eq!(
3909 view.selections.display_ranges(cx),
3910 vec![
3911 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3912 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
3913 ]
3914 );
3915 });
3916}
3917
3918#[gpui::test]
3919fn test_move_line_up_down(cx: &mut TestAppContext) {
3920 init_test(cx, |_| {});
3921
3922 let view = cx.add_window(|cx| {
3923 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3924 build_editor(buffer, cx)
3925 });
3926 _ = view.update(cx, |view, cx| {
3927 view.fold_creases(
3928 vec![
3929 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
3930 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
3931 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
3932 ],
3933 true,
3934 cx,
3935 );
3936 view.change_selections(None, cx, |s| {
3937 s.select_display_ranges([
3938 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 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 assert_eq!(
3945 view.display_text(cx),
3946 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
3947 );
3948
3949 view.move_line_up(&MoveLineUp, cx);
3950 assert_eq!(
3951 view.display_text(cx),
3952 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
3953 );
3954 assert_eq!(
3955 view.selections.display_ranges(cx),
3956 vec![
3957 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3958 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3959 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3960 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3961 ]
3962 );
3963 });
3964
3965 _ = view.update(cx, |view, cx| {
3966 view.move_line_down(&MoveLineDown, cx);
3967 assert_eq!(
3968 view.display_text(cx),
3969 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
3970 );
3971 assert_eq!(
3972 view.selections.display_ranges(cx),
3973 vec![
3974 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3975 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3976 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3977 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3978 ]
3979 );
3980 });
3981
3982 _ = view.update(cx, |view, cx| {
3983 view.move_line_down(&MoveLineDown, cx);
3984 assert_eq!(
3985 view.display_text(cx),
3986 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
3987 );
3988 assert_eq!(
3989 view.selections.display_ranges(cx),
3990 vec![
3991 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3992 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3993 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3994 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3995 ]
3996 );
3997 });
3998
3999 _ = view.update(cx, |view, cx| {
4000 view.move_line_up(&MoveLineUp, cx);
4001 assert_eq!(
4002 view.display_text(cx),
4003 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
4004 );
4005 assert_eq!(
4006 view.selections.display_ranges(cx),
4007 vec![
4008 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
4009 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4010 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
4011 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
4012 ]
4013 );
4014 });
4015}
4016
4017#[gpui::test]
4018fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
4019 init_test(cx, |_| {});
4020
4021 let editor = cx.add_window(|cx| {
4022 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4023 build_editor(buffer, cx)
4024 });
4025 _ = editor.update(cx, |editor, cx| {
4026 let snapshot = editor.buffer.read(cx).snapshot(cx);
4027 editor.insert_blocks(
4028 [BlockProperties {
4029 style: BlockStyle::Fixed,
4030 placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
4031 height: 1,
4032 render: Arc::new(|_| div().into_any()),
4033 priority: 0,
4034 }],
4035 Some(Autoscroll::fit()),
4036 cx,
4037 );
4038 editor.change_selections(None, cx, |s| {
4039 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
4040 });
4041 editor.move_line_down(&MoveLineDown, cx);
4042 });
4043}
4044
4045#[gpui::test]
4046async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
4047 init_test(cx, |_| {});
4048
4049 let mut cx = EditorTestContext::new(cx).await;
4050 cx.set_state(
4051 &"
4052 ˇzero
4053 one
4054 two
4055 three
4056 four
4057 five
4058 "
4059 .unindent(),
4060 );
4061
4062 // Create a four-line block that replaces three lines of text.
4063 cx.update_editor(|editor, cx| {
4064 let snapshot = editor.snapshot(cx);
4065 let snapshot = &snapshot.buffer_snapshot;
4066 let placement = BlockPlacement::Replace(
4067 snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
4068 );
4069 editor.insert_blocks(
4070 [BlockProperties {
4071 placement,
4072 height: 4,
4073 style: BlockStyle::Sticky,
4074 render: Arc::new(|_| gpui::div().into_any_element()),
4075 priority: 0,
4076 }],
4077 None,
4078 cx,
4079 );
4080 });
4081
4082 // Move down so that the cursor touches the block.
4083 cx.update_editor(|editor, cx| {
4084 editor.move_down(&Default::default(), cx);
4085 });
4086 cx.assert_editor_state(
4087 &"
4088 zero
4089 «one
4090 two
4091 threeˇ»
4092 four
4093 five
4094 "
4095 .unindent(),
4096 );
4097
4098 // Move down past the block.
4099 cx.update_editor(|editor, cx| {
4100 editor.move_down(&Default::default(), cx);
4101 });
4102 cx.assert_editor_state(
4103 &"
4104 zero
4105 one
4106 two
4107 three
4108 ˇfour
4109 five
4110 "
4111 .unindent(),
4112 );
4113}
4114
4115#[gpui::test]
4116fn test_transpose(cx: &mut TestAppContext) {
4117 init_test(cx, |_| {});
4118
4119 _ = cx.add_window(|cx| {
4120 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
4121 editor.set_style(EditorStyle::default(), cx);
4122 editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
4123 editor.transpose(&Default::default(), cx);
4124 assert_eq!(editor.text(cx), "bac");
4125 assert_eq!(editor.selections.ranges(cx), [2..2]);
4126
4127 editor.transpose(&Default::default(), cx);
4128 assert_eq!(editor.text(cx), "bca");
4129 assert_eq!(editor.selections.ranges(cx), [3..3]);
4130
4131 editor.transpose(&Default::default(), cx);
4132 assert_eq!(editor.text(cx), "bac");
4133 assert_eq!(editor.selections.ranges(cx), [3..3]);
4134
4135 editor
4136 });
4137
4138 _ = cx.add_window(|cx| {
4139 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
4140 editor.set_style(EditorStyle::default(), cx);
4141 editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
4142 editor.transpose(&Default::default(), cx);
4143 assert_eq!(editor.text(cx), "acb\nde");
4144 assert_eq!(editor.selections.ranges(cx), [3..3]);
4145
4146 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
4147 editor.transpose(&Default::default(), cx);
4148 assert_eq!(editor.text(cx), "acbd\ne");
4149 assert_eq!(editor.selections.ranges(cx), [5..5]);
4150
4151 editor.transpose(&Default::default(), cx);
4152 assert_eq!(editor.text(cx), "acbde\n");
4153 assert_eq!(editor.selections.ranges(cx), [6..6]);
4154
4155 editor.transpose(&Default::default(), cx);
4156 assert_eq!(editor.text(cx), "acbd\ne");
4157 assert_eq!(editor.selections.ranges(cx), [6..6]);
4158
4159 editor
4160 });
4161
4162 _ = cx.add_window(|cx| {
4163 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
4164 editor.set_style(EditorStyle::default(), cx);
4165 editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
4166 editor.transpose(&Default::default(), cx);
4167 assert_eq!(editor.text(cx), "bacd\ne");
4168 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
4169
4170 editor.transpose(&Default::default(), cx);
4171 assert_eq!(editor.text(cx), "bcade\n");
4172 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
4173
4174 editor.transpose(&Default::default(), cx);
4175 assert_eq!(editor.text(cx), "bcda\ne");
4176 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4177
4178 editor.transpose(&Default::default(), cx);
4179 assert_eq!(editor.text(cx), "bcade\n");
4180 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4181
4182 editor.transpose(&Default::default(), cx);
4183 assert_eq!(editor.text(cx), "bcaed\n");
4184 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
4185
4186 editor
4187 });
4188
4189 _ = cx.add_window(|cx| {
4190 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
4191 editor.set_style(EditorStyle::default(), cx);
4192 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
4193 editor.transpose(&Default::default(), cx);
4194 assert_eq!(editor.text(cx), "🏀🍐✋");
4195 assert_eq!(editor.selections.ranges(cx), [8..8]);
4196
4197 editor.transpose(&Default::default(), cx);
4198 assert_eq!(editor.text(cx), "🏀✋🍐");
4199 assert_eq!(editor.selections.ranges(cx), [11..11]);
4200
4201 editor.transpose(&Default::default(), cx);
4202 assert_eq!(editor.text(cx), "🏀🍐✋");
4203 assert_eq!(editor.selections.ranges(cx), [11..11]);
4204
4205 editor
4206 });
4207}
4208
4209#[gpui::test]
4210async fn test_rewrap(cx: &mut TestAppContext) {
4211 init_test(cx, |_| {});
4212
4213 let mut cx = EditorTestContext::new(cx).await;
4214
4215 let language_with_c_comments = Arc::new(Language::new(
4216 LanguageConfig {
4217 line_comments: vec!["// ".into()],
4218 ..LanguageConfig::default()
4219 },
4220 None,
4221 ));
4222 let language_with_pound_comments = Arc::new(Language::new(
4223 LanguageConfig {
4224 line_comments: vec!["# ".into()],
4225 ..LanguageConfig::default()
4226 },
4227 None,
4228 ));
4229 let markdown_language = Arc::new(Language::new(
4230 LanguageConfig {
4231 name: "Markdown".into(),
4232 ..LanguageConfig::default()
4233 },
4234 None,
4235 ));
4236 let language_with_doc_comments = Arc::new(Language::new(
4237 LanguageConfig {
4238 line_comments: vec!["// ".into(), "/// ".into()],
4239 ..LanguageConfig::default()
4240 },
4241 Some(tree_sitter_rust::LANGUAGE.into()),
4242 ));
4243
4244 let plaintext_language = Arc::new(Language::new(
4245 LanguageConfig {
4246 name: "Plain Text".into(),
4247 ..LanguageConfig::default()
4248 },
4249 None,
4250 ));
4251
4252 assert_rewrap(
4253 indoc! {"
4254 // ˇ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.
4255 "},
4256 indoc! {"
4257 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4258 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4259 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4260 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4261 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4262 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4263 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4264 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4265 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4266 // porttitor id. Aliquam id accumsan eros.
4267 "},
4268 language_with_c_comments.clone(),
4269 &mut cx,
4270 );
4271
4272 // Test that rewrapping works inside of a selection
4273 assert_rewrap(
4274 indoc! {"
4275 «// 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.ˇ»
4276 "},
4277 indoc! {"
4278 «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4279 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4280 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4281 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4282 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4283 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4284 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4285 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4286 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4287 // porttitor id. Aliquam id accumsan eros.ˇ»
4288 "},
4289 language_with_c_comments.clone(),
4290 &mut cx,
4291 );
4292
4293 // Test that cursors that expand to the same region are collapsed.
4294 assert_rewrap(
4295 indoc! {"
4296 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4297 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4298 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4299 // ˇ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.
4300 "},
4301 indoc! {"
4302 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4303 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4304 // auctor, eu lacinia sapien scelerisque. ˇVivamus sit amet neque et quam
4305 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4306 // Pellentesque odio lectus, iaculis ac volutpat et, ˇblandit quis urna. Sed
4307 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4308 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4309 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4310 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4311 // porttitor id. Aliquam id accumsan eros.
4312 "},
4313 language_with_c_comments.clone(),
4314 &mut cx,
4315 );
4316
4317 // Test that non-contiguous selections are treated separately.
4318 assert_rewrap(
4319 indoc! {"
4320 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4321 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4322 //
4323 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4324 // ˇ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.
4325 "},
4326 indoc! {"
4327 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4328 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4329 // auctor, eu lacinia sapien scelerisque.
4330 //
4331 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
4332 // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4333 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
4334 // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
4335 // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
4336 // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
4337 // vulputate turpis porttitor id. Aliquam id accumsan eros.
4338 "},
4339 language_with_c_comments.clone(),
4340 &mut cx,
4341 );
4342
4343 // Test that different comment prefixes are supported.
4344 assert_rewrap(
4345 indoc! {"
4346 # ˇ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.
4347 "},
4348 indoc! {"
4349 # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4350 # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4351 # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4352 # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4353 # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
4354 # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
4355 # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
4356 # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
4357 # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
4358 # accumsan eros.
4359 "},
4360 language_with_pound_comments.clone(),
4361 &mut cx,
4362 );
4363
4364 // Test that rewrapping is ignored outside of comments in most languages.
4365 assert_rewrap(
4366 indoc! {"
4367 /// Adds two numbers.
4368 /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4369 fn add(a: u32, b: u32) -> u32 {
4370 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ˇ
4371 }
4372 "},
4373 indoc! {"
4374 /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
4375 /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4376 fn add(a: u32, b: u32) -> u32 {
4377 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ˇ
4378 }
4379 "},
4380 language_with_doc_comments.clone(),
4381 &mut cx,
4382 );
4383
4384 // Test that rewrapping works in Markdown and Plain Text languages.
4385 assert_rewrap(
4386 indoc! {"
4387 # Hello
4388
4389 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.
4390 "},
4391 indoc! {"
4392 # Hello
4393
4394 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4395 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4396 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4397 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4398 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4399 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4400 Integer sit amet scelerisque nisi.
4401 "},
4402 markdown_language,
4403 &mut cx,
4404 );
4405
4406 assert_rewrap(
4407 indoc! {"
4408 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.
4409 "},
4410 indoc! {"
4411 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4412 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4413 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4414 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4415 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4416 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4417 Integer sit amet scelerisque nisi.
4418 "},
4419 plaintext_language,
4420 &mut cx,
4421 );
4422
4423 // Test rewrapping unaligned comments in a selection.
4424 assert_rewrap(
4425 indoc! {"
4426 fn foo() {
4427 if true {
4428 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4429 // Praesent semper egestas tellus id dignissim.ˇ»
4430 do_something();
4431 } else {
4432 //
4433 }
4434 }
4435 "},
4436 indoc! {"
4437 fn foo() {
4438 if true {
4439 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4440 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4441 // egestas tellus id dignissim.ˇ»
4442 do_something();
4443 } else {
4444 //
4445 }
4446 }
4447 "},
4448 language_with_doc_comments.clone(),
4449 &mut cx,
4450 );
4451
4452 assert_rewrap(
4453 indoc! {"
4454 fn foo() {
4455 if true {
4456 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4457 // Praesent semper egestas tellus id dignissim.»
4458 do_something();
4459 } else {
4460 //
4461 }
4462
4463 }
4464 "},
4465 indoc! {"
4466 fn foo() {
4467 if true {
4468 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4469 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4470 // egestas tellus id dignissim.»
4471 do_something();
4472 } else {
4473 //
4474 }
4475
4476 }
4477 "},
4478 language_with_doc_comments.clone(),
4479 &mut cx,
4480 );
4481
4482 #[track_caller]
4483 fn assert_rewrap(
4484 unwrapped_text: &str,
4485 wrapped_text: &str,
4486 language: Arc<Language>,
4487 cx: &mut EditorTestContext,
4488 ) {
4489 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4490 cx.set_state(unwrapped_text);
4491 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4492 cx.assert_editor_state(wrapped_text);
4493 }
4494}
4495
4496#[gpui::test]
4497async fn test_clipboard(cx: &mut gpui::TestAppContext) {
4498 init_test(cx, |_| {});
4499
4500 let mut cx = EditorTestContext::new(cx).await;
4501
4502 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
4503 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4504 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
4505
4506 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
4507 cx.set_state("two ˇfour ˇsix ˇ");
4508 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4509 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
4510
4511 // Paste again but with only two cursors. Since the number of cursors doesn't
4512 // match the number of slices in the clipboard, the entire clipboard text
4513 // is pasted at each cursor.
4514 cx.set_state("ˇtwo one✅ four three six five ˇ");
4515 cx.update_editor(|e, cx| {
4516 e.handle_input("( ", cx);
4517 e.paste(&Paste, cx);
4518 e.handle_input(") ", cx);
4519 });
4520 cx.assert_editor_state(
4521 &([
4522 "( one✅ ",
4523 "three ",
4524 "five ) ˇtwo one✅ four three six five ( one✅ ",
4525 "three ",
4526 "five ) ˇ",
4527 ]
4528 .join("\n")),
4529 );
4530
4531 // Cut with three selections, one of which is full-line.
4532 cx.set_state(indoc! {"
4533 1«2ˇ»3
4534 4ˇ567
4535 «8ˇ»9"});
4536 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4537 cx.assert_editor_state(indoc! {"
4538 1ˇ3
4539 ˇ9"});
4540
4541 // Paste with three selections, noticing how the copied selection that was full-line
4542 // gets inserted before the second cursor.
4543 cx.set_state(indoc! {"
4544 1ˇ3
4545 9ˇ
4546 «oˇ»ne"});
4547 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4548 cx.assert_editor_state(indoc! {"
4549 12ˇ3
4550 4567
4551 9ˇ
4552 8ˇne"});
4553
4554 // Copy with a single cursor only, which writes the whole line into the clipboard.
4555 cx.set_state(indoc! {"
4556 The quick brown
4557 fox juˇmps over
4558 the lazy dog"});
4559 cx.update_editor(|e, cx| e.copy(&Copy, cx));
4560 assert_eq!(
4561 cx.read_from_clipboard()
4562 .and_then(|item| item.text().as_deref().map(str::to_string)),
4563 Some("fox jumps over\n".to_string())
4564 );
4565
4566 // Paste with three selections, noticing how the copied full-line selection is inserted
4567 // before the empty selections but replaces the selection that is non-empty.
4568 cx.set_state(indoc! {"
4569 Tˇhe quick brown
4570 «foˇ»x jumps over
4571 tˇhe lazy dog"});
4572 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4573 cx.assert_editor_state(indoc! {"
4574 fox jumps over
4575 Tˇhe quick brown
4576 fox jumps over
4577 ˇx jumps over
4578 fox jumps over
4579 tˇhe lazy dog"});
4580}
4581
4582#[gpui::test]
4583async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
4584 init_test(cx, |_| {});
4585
4586 let mut cx = EditorTestContext::new(cx).await;
4587 let language = Arc::new(Language::new(
4588 LanguageConfig::default(),
4589 Some(tree_sitter_rust::LANGUAGE.into()),
4590 ));
4591 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4592
4593 // Cut an indented block, without the leading whitespace.
4594 cx.set_state(indoc! {"
4595 const a: B = (
4596 c(),
4597 «d(
4598 e,
4599 f
4600 )ˇ»
4601 );
4602 "});
4603 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4604 cx.assert_editor_state(indoc! {"
4605 const a: B = (
4606 c(),
4607 ˇ
4608 );
4609 "});
4610
4611 // Paste it at the same position.
4612 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4613 cx.assert_editor_state(indoc! {"
4614 const a: B = (
4615 c(),
4616 d(
4617 e,
4618 f
4619 )ˇ
4620 );
4621 "});
4622
4623 // Paste it at a line with a lower indent level.
4624 cx.set_state(indoc! {"
4625 ˇ
4626 const a: B = (
4627 c(),
4628 );
4629 "});
4630 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4631 cx.assert_editor_state(indoc! {"
4632 d(
4633 e,
4634 f
4635 )ˇ
4636 const a: B = (
4637 c(),
4638 );
4639 "});
4640
4641 // Cut an indented block, with the leading whitespace.
4642 cx.set_state(indoc! {"
4643 const a: B = (
4644 c(),
4645 « d(
4646 e,
4647 f
4648 )
4649 ˇ»);
4650 "});
4651 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4652 cx.assert_editor_state(indoc! {"
4653 const a: B = (
4654 c(),
4655 ˇ);
4656 "});
4657
4658 // Paste it at the same position.
4659 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4660 cx.assert_editor_state(indoc! {"
4661 const a: B = (
4662 c(),
4663 d(
4664 e,
4665 f
4666 )
4667 ˇ);
4668 "});
4669
4670 // Paste it at a line with a higher indent level.
4671 cx.set_state(indoc! {"
4672 const a: B = (
4673 c(),
4674 d(
4675 e,
4676 fˇ
4677 )
4678 );
4679 "});
4680 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4681 cx.assert_editor_state(indoc! {"
4682 const a: B = (
4683 c(),
4684 d(
4685 e,
4686 f d(
4687 e,
4688 f
4689 )
4690 ˇ
4691 )
4692 );
4693 "});
4694}
4695
4696#[gpui::test]
4697fn test_select_all(cx: &mut TestAppContext) {
4698 init_test(cx, |_| {});
4699
4700 let view = cx.add_window(|cx| {
4701 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
4702 build_editor(buffer, cx)
4703 });
4704 _ = view.update(cx, |view, cx| {
4705 view.select_all(&SelectAll, cx);
4706 assert_eq!(
4707 view.selections.display_ranges(cx),
4708 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
4709 );
4710 });
4711}
4712
4713#[gpui::test]
4714fn test_select_line(cx: &mut TestAppContext) {
4715 init_test(cx, |_| {});
4716
4717 let view = cx.add_window(|cx| {
4718 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
4719 build_editor(buffer, cx)
4720 });
4721 _ = view.update(cx, |view, cx| {
4722 view.change_selections(None, cx, |s| {
4723 s.select_display_ranges([
4724 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4725 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4726 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4727 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
4728 ])
4729 });
4730 view.select_line(&SelectLine, cx);
4731 assert_eq!(
4732 view.selections.display_ranges(cx),
4733 vec![
4734 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
4735 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
4736 ]
4737 );
4738 });
4739
4740 _ = view.update(cx, |view, cx| {
4741 view.select_line(&SelectLine, cx);
4742 assert_eq!(
4743 view.selections.display_ranges(cx),
4744 vec![
4745 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
4746 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
4747 ]
4748 );
4749 });
4750
4751 _ = view.update(cx, |view, cx| {
4752 view.select_line(&SelectLine, cx);
4753 assert_eq!(
4754 view.selections.display_ranges(cx),
4755 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
4756 );
4757 });
4758}
4759
4760#[gpui::test]
4761fn test_split_selection_into_lines(cx: &mut TestAppContext) {
4762 init_test(cx, |_| {});
4763
4764 let view = cx.add_window(|cx| {
4765 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
4766 build_editor(buffer, cx)
4767 });
4768 _ = view.update(cx, |view, cx| {
4769 view.fold_creases(
4770 vec![
4771 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4772 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4773 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4774 ],
4775 true,
4776 cx,
4777 );
4778 view.change_selections(None, cx, |s| {
4779 s.select_display_ranges([
4780 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4781 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4782 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4783 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
4784 ])
4785 });
4786 assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i");
4787 });
4788
4789 _ = view.update(cx, |view, cx| {
4790 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4791 assert_eq!(
4792 view.display_text(cx),
4793 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
4794 );
4795 assert_eq!(
4796 view.selections.display_ranges(cx),
4797 [
4798 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4799 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4800 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
4801 DisplayPoint::new(DisplayRow(5), 4)..DisplayPoint::new(DisplayRow(5), 4)
4802 ]
4803 );
4804 });
4805
4806 _ = view.update(cx, |view, cx| {
4807 view.change_selections(None, cx, |s| {
4808 s.select_display_ranges([
4809 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
4810 ])
4811 });
4812 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4813 assert_eq!(
4814 view.display_text(cx),
4815 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
4816 );
4817 assert_eq!(
4818 view.selections.display_ranges(cx),
4819 [
4820 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
4821 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
4822 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
4823 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
4824 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
4825 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
4826 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5),
4827 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(7), 0)
4828 ]
4829 );
4830 });
4831}
4832
4833#[gpui::test]
4834async fn test_add_selection_above_below(cx: &mut TestAppContext) {
4835 init_test(cx, |_| {});
4836
4837 let mut cx = EditorTestContext::new(cx).await;
4838
4839 // let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
4840 cx.set_state(indoc!(
4841 r#"abc
4842 defˇghi
4843
4844 jk
4845 nlmo
4846 "#
4847 ));
4848
4849 cx.update_editor(|editor, cx| {
4850 editor.add_selection_above(&Default::default(), cx);
4851 });
4852
4853 cx.assert_editor_state(indoc!(
4854 r#"abcˇ
4855 defˇghi
4856
4857 jk
4858 nlmo
4859 "#
4860 ));
4861
4862 cx.update_editor(|editor, cx| {
4863 editor.add_selection_above(&Default::default(), cx);
4864 });
4865
4866 cx.assert_editor_state(indoc!(
4867 r#"abcˇ
4868 defˇghi
4869
4870 jk
4871 nlmo
4872 "#
4873 ));
4874
4875 cx.update_editor(|view, cx| {
4876 view.add_selection_below(&Default::default(), cx);
4877 });
4878
4879 cx.assert_editor_state(indoc!(
4880 r#"abc
4881 defˇghi
4882
4883 jk
4884 nlmo
4885 "#
4886 ));
4887
4888 cx.update_editor(|view, cx| {
4889 view.undo_selection(&Default::default(), cx);
4890 });
4891
4892 cx.assert_editor_state(indoc!(
4893 r#"abcˇ
4894 defˇghi
4895
4896 jk
4897 nlmo
4898 "#
4899 ));
4900
4901 cx.update_editor(|view, cx| {
4902 view.redo_selection(&Default::default(), cx);
4903 });
4904
4905 cx.assert_editor_state(indoc!(
4906 r#"abc
4907 defˇghi
4908
4909 jk
4910 nlmo
4911 "#
4912 ));
4913
4914 cx.update_editor(|view, cx| {
4915 view.add_selection_below(&Default::default(), cx);
4916 });
4917
4918 cx.assert_editor_state(indoc!(
4919 r#"abc
4920 defˇghi
4921
4922 jk
4923 nlmˇo
4924 "#
4925 ));
4926
4927 cx.update_editor(|view, cx| {
4928 view.add_selection_below(&Default::default(), cx);
4929 });
4930
4931 cx.assert_editor_state(indoc!(
4932 r#"abc
4933 defˇghi
4934
4935 jk
4936 nlmˇo
4937 "#
4938 ));
4939
4940 // change selections
4941 cx.set_state(indoc!(
4942 r#"abc
4943 def«ˇg»hi
4944
4945 jk
4946 nlmo
4947 "#
4948 ));
4949
4950 cx.update_editor(|view, cx| {
4951 view.add_selection_below(&Default::default(), cx);
4952 });
4953
4954 cx.assert_editor_state(indoc!(
4955 r#"abc
4956 def«ˇg»hi
4957
4958 jk
4959 nlm«ˇo»
4960 "#
4961 ));
4962
4963 cx.update_editor(|view, cx| {
4964 view.add_selection_below(&Default::default(), cx);
4965 });
4966
4967 cx.assert_editor_state(indoc!(
4968 r#"abc
4969 def«ˇg»hi
4970
4971 jk
4972 nlm«ˇo»
4973 "#
4974 ));
4975
4976 cx.update_editor(|view, cx| {
4977 view.add_selection_above(&Default::default(), cx);
4978 });
4979
4980 cx.assert_editor_state(indoc!(
4981 r#"abc
4982 def«ˇg»hi
4983
4984 jk
4985 nlmo
4986 "#
4987 ));
4988
4989 cx.update_editor(|view, cx| {
4990 view.add_selection_above(&Default::default(), cx);
4991 });
4992
4993 cx.assert_editor_state(indoc!(
4994 r#"abc
4995 def«ˇg»hi
4996
4997 jk
4998 nlmo
4999 "#
5000 ));
5001
5002 // Change selections again
5003 cx.set_state(indoc!(
5004 r#"a«bc
5005 defgˇ»hi
5006
5007 jk
5008 nlmo
5009 "#
5010 ));
5011
5012 cx.update_editor(|view, cx| {
5013 view.add_selection_below(&Default::default(), cx);
5014 });
5015
5016 cx.assert_editor_state(indoc!(
5017 r#"a«bcˇ»
5018 d«efgˇ»hi
5019
5020 j«kˇ»
5021 nlmo
5022 "#
5023 ));
5024
5025 cx.update_editor(|view, cx| {
5026 view.add_selection_below(&Default::default(), cx);
5027 });
5028 cx.assert_editor_state(indoc!(
5029 r#"a«bcˇ»
5030 d«efgˇ»hi
5031
5032 j«kˇ»
5033 n«lmoˇ»
5034 "#
5035 ));
5036 cx.update_editor(|view, cx| {
5037 view.add_selection_above(&Default::default(), cx);
5038 });
5039
5040 cx.assert_editor_state(indoc!(
5041 r#"a«bcˇ»
5042 d«efgˇ»hi
5043
5044 j«kˇ»
5045 nlmo
5046 "#
5047 ));
5048
5049 // Change selections again
5050 cx.set_state(indoc!(
5051 r#"abc
5052 d«ˇefghi
5053
5054 jk
5055 nlm»o
5056 "#
5057 ));
5058
5059 cx.update_editor(|view, cx| {
5060 view.add_selection_above(&Default::default(), cx);
5061 });
5062
5063 cx.assert_editor_state(indoc!(
5064 r#"a«ˇbc»
5065 d«ˇef»ghi
5066
5067 j«ˇk»
5068 n«ˇlm»o
5069 "#
5070 ));
5071
5072 cx.update_editor(|view, cx| {
5073 view.add_selection_below(&Default::default(), cx);
5074 });
5075
5076 cx.assert_editor_state(indoc!(
5077 r#"abc
5078 d«ˇef»ghi
5079
5080 j«ˇk»
5081 n«ˇlm»o
5082 "#
5083 ));
5084}
5085
5086#[gpui::test]
5087async fn test_select_next(cx: &mut gpui::TestAppContext) {
5088 init_test(cx, |_| {});
5089
5090 let mut cx = EditorTestContext::new(cx).await;
5091 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5092
5093 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5094 .unwrap();
5095 cx.assert_editor_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(|view, cx| view.undo_selection(&UndoSelection, cx));
5102 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5103
5104 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5105 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5106
5107 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5108 .unwrap();
5109 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
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
5116#[gpui::test]
5117async fn test_select_all_matches(cx: &mut gpui::TestAppContext) {
5118 init_test(cx, |_| {});
5119
5120 let mut cx = EditorTestContext::new(cx).await;
5121 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5122
5123 cx.update_editor(|e, cx| e.select_all_matches(&SelectAllMatches, cx))
5124 .unwrap();
5125 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5126}
5127
5128#[gpui::test]
5129async fn test_select_next_with_multiple_carets(cx: &mut gpui::TestAppContext) {
5130 init_test(cx, |_| {});
5131
5132 let mut cx = EditorTestContext::new(cx).await;
5133 cx.set_state(
5134 r#"let foo = 2;
5135lˇet foo = 2;
5136let fooˇ = 2;
5137let foo = 2;
5138let foo = ˇ2;"#,
5139 );
5140
5141 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5142 .unwrap();
5143 cx.assert_editor_state(
5144 r#"let foo = 2;
5145«letˇ» foo = 2;
5146let «fooˇ» = 2;
5147let foo = 2;
5148let foo = «2ˇ»;"#,
5149 );
5150
5151 // noop for multiple selections with different contents
5152 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5153 .unwrap();
5154 cx.assert_editor_state(
5155 r#"let foo = 2;
5156«letˇ» foo = 2;
5157let «fooˇ» = 2;
5158let foo = 2;
5159let foo = «2ˇ»;"#,
5160 );
5161}
5162
5163#[gpui::test]
5164async fn test_select_previous_multibuffer(cx: &mut gpui::TestAppContext) {
5165 init_test(cx, |_| {});
5166
5167 let mut cx =
5168 EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
5169
5170 cx.assert_editor_state(indoc! {"
5171 ˇbbb
5172 ccc
5173
5174 bbb
5175 ccc
5176 "});
5177 cx.dispatch_action(SelectPrevious::default());
5178 cx.assert_editor_state(indoc! {"
5179 «bbbˇ»
5180 ccc
5181
5182 bbb
5183 ccc
5184 "});
5185 cx.dispatch_action(SelectPrevious::default());
5186 cx.assert_editor_state(indoc! {"
5187 «bbbˇ»
5188 ccc
5189
5190 «bbbˇ»
5191 ccc
5192 "});
5193}
5194
5195#[gpui::test]
5196async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) {
5197 init_test(cx, |_| {});
5198
5199 let mut cx = EditorTestContext::new(cx).await;
5200 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5201
5202 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5203 .unwrap();
5204 cx.assert_editor_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(|view, cx| view.undo_selection(&UndoSelection, cx));
5211 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5212
5213 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5214 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5215
5216 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5217 .unwrap();
5218 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
5219
5220 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5221 .unwrap();
5222 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\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
5229#[gpui::test]
5230async fn test_select_previous_with_multiple_carets(cx: &mut gpui::TestAppContext) {
5231 init_test(cx, |_| {});
5232
5233 let mut cx = EditorTestContext::new(cx).await;
5234 cx.set_state(
5235 r#"let foo = 2;
5236lˇet foo = 2;
5237let fooˇ = 2;
5238let foo = 2;
5239let foo = ˇ2;"#,
5240 );
5241
5242 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5243 .unwrap();
5244 cx.assert_editor_state(
5245 r#"let foo = 2;
5246«letˇ» foo = 2;
5247let «fooˇ» = 2;
5248let foo = 2;
5249let foo = «2ˇ»;"#,
5250 );
5251
5252 // noop for multiple selections with different contents
5253 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5254 .unwrap();
5255 cx.assert_editor_state(
5256 r#"let foo = 2;
5257«letˇ» foo = 2;
5258let «fooˇ» = 2;
5259let foo = 2;
5260let foo = «2ˇ»;"#,
5261 );
5262}
5263
5264#[gpui::test]
5265async fn test_select_previous_with_single_selection(cx: &mut gpui::TestAppContext) {
5266 init_test(cx, |_| {});
5267
5268 let mut cx = EditorTestContext::new(cx).await;
5269 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
5270
5271 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5272 .unwrap();
5273 cx.assert_editor_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\n«abcˇ»");
5278
5279 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5280 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5281
5282 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5283 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5284
5285 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5286 .unwrap();
5287 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\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
5294#[gpui::test]
5295async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
5296 init_test(cx, |_| {});
5297
5298 let language = Arc::new(Language::new(
5299 LanguageConfig::default(),
5300 Some(tree_sitter_rust::LANGUAGE.into()),
5301 ));
5302
5303 let text = r#"
5304 use mod1::mod2::{mod3, mod4};
5305
5306 fn fn_1(param1: bool, param2: &str) {
5307 let var1 = "text";
5308 }
5309 "#
5310 .unindent();
5311
5312 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5313 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5314 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5315
5316 editor
5317 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5318 .await;
5319
5320 editor.update(cx, |view, cx| {
5321 view.change_selections(None, cx, |s| {
5322 s.select_display_ranges([
5323 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
5324 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
5325 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
5326 ]);
5327 });
5328 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5329 });
5330 editor.update(cx, |editor, cx| {
5331 assert_text_with_selections(
5332 editor,
5333 indoc! {r#"
5334 use mod1::mod2::{mod3, «mod4ˇ»};
5335
5336 fn fn_1«ˇ(param1: bool, param2: &str)» {
5337 let var1 = "«textˇ»";
5338 }
5339 "#},
5340 cx,
5341 );
5342 });
5343
5344 editor.update(cx, |view, cx| {
5345 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5346 });
5347 editor.update(cx, |editor, cx| {
5348 assert_text_with_selections(
5349 editor,
5350 indoc! {r#"
5351 use mod1::mod2::«{mod3, mod4}ˇ»;
5352
5353 «ˇfn fn_1(param1: bool, param2: &str) {
5354 let var1 = "text";
5355 }»
5356 "#},
5357 cx,
5358 );
5359 });
5360
5361 editor.update(cx, |view, cx| {
5362 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5363 });
5364 assert_eq!(
5365 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
5366 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5367 );
5368
5369 // Trying to expand the selected syntax node one more time has no effect.
5370 editor.update(cx, |view, cx| {
5371 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5372 });
5373 assert_eq!(
5374 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
5375 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5376 );
5377
5378 editor.update(cx, |view, cx| {
5379 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5380 });
5381 editor.update(cx, |editor, cx| {
5382 assert_text_with_selections(
5383 editor,
5384 indoc! {r#"
5385 use mod1::mod2::«{mod3, mod4}ˇ»;
5386
5387 «ˇfn fn_1(param1: bool, param2: &str) {
5388 let var1 = "text";
5389 }»
5390 "#},
5391 cx,
5392 );
5393 });
5394
5395 editor.update(cx, |view, cx| {
5396 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5397 });
5398 editor.update(cx, |editor, cx| {
5399 assert_text_with_selections(
5400 editor,
5401 indoc! {r#"
5402 use mod1::mod2::{mod3, «mod4ˇ»};
5403
5404 fn fn_1«ˇ(param1: bool, param2: &str)» {
5405 let var1 = "«textˇ»";
5406 }
5407 "#},
5408 cx,
5409 );
5410 });
5411
5412 editor.update(cx, |view, cx| {
5413 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5414 });
5415 editor.update(cx, |editor, cx| {
5416 assert_text_with_selections(
5417 editor,
5418 indoc! {r#"
5419 use mod1::mod2::{mod3, mo«ˇ»d4};
5420
5421 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5422 let var1 = "te«ˇ»xt";
5423 }
5424 "#},
5425 cx,
5426 );
5427 });
5428
5429 // Trying to shrink the selected syntax node one more time has no effect.
5430 editor.update(cx, |view, cx| {
5431 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5432 });
5433 editor.update(cx, |editor, cx| {
5434 assert_text_with_selections(
5435 editor,
5436 indoc! {r#"
5437 use mod1::mod2::{mod3, mo«ˇ»d4};
5438
5439 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5440 let var1 = "te«ˇ»xt";
5441 }
5442 "#},
5443 cx,
5444 );
5445 });
5446
5447 // Ensure that we keep expanding the selection if the larger selection starts or ends within
5448 // a fold.
5449 editor.update(cx, |view, cx| {
5450 view.fold_creases(
5451 vec![
5452 Crease::simple(
5453 Point::new(0, 21)..Point::new(0, 24),
5454 FoldPlaceholder::test(),
5455 ),
5456 Crease::simple(
5457 Point::new(3, 20)..Point::new(3, 22),
5458 FoldPlaceholder::test(),
5459 ),
5460 ],
5461 true,
5462 cx,
5463 );
5464 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5465 });
5466 editor.update(cx, |editor, cx| {
5467 assert_text_with_selections(
5468 editor,
5469 indoc! {r#"
5470 use mod1::mod2::«{mod3, mod4}ˇ»;
5471
5472 fn fn_1«ˇ(param1: bool, param2: &str)» {
5473 «let var1 = "text";ˇ»
5474 }
5475 "#},
5476 cx,
5477 );
5478 });
5479}
5480
5481#[gpui::test]
5482async fn test_autoindent(cx: &mut gpui::TestAppContext) {
5483 init_test(cx, |_| {});
5484
5485 let language = Arc::new(
5486 Language::new(
5487 LanguageConfig {
5488 brackets: BracketPairConfig {
5489 pairs: vec![
5490 BracketPair {
5491 start: "{".to_string(),
5492 end: "}".to_string(),
5493 close: false,
5494 surround: false,
5495 newline: true,
5496 },
5497 BracketPair {
5498 start: "(".to_string(),
5499 end: ")".to_string(),
5500 close: false,
5501 surround: false,
5502 newline: true,
5503 },
5504 ],
5505 ..Default::default()
5506 },
5507 ..Default::default()
5508 },
5509 Some(tree_sitter_rust::LANGUAGE.into()),
5510 )
5511 .with_indents_query(
5512 r#"
5513 (_ "(" ")" @end) @indent
5514 (_ "{" "}" @end) @indent
5515 "#,
5516 )
5517 .unwrap(),
5518 );
5519
5520 let text = "fn a() {}";
5521
5522 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5523 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5524 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5525 editor
5526 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5527 .await;
5528
5529 editor.update(cx, |editor, cx| {
5530 editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
5531 editor.newline(&Newline, cx);
5532 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
5533 assert_eq!(
5534 editor.selections.ranges(cx),
5535 &[
5536 Point::new(1, 4)..Point::new(1, 4),
5537 Point::new(3, 4)..Point::new(3, 4),
5538 Point::new(5, 0)..Point::new(5, 0)
5539 ]
5540 );
5541 });
5542}
5543
5544#[gpui::test]
5545async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
5546 init_test(cx, |_| {});
5547
5548 {
5549 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
5550 cx.set_state(indoc! {"
5551 impl A {
5552
5553 fn b() {}
5554
5555 «fn c() {
5556
5557 }ˇ»
5558 }
5559 "});
5560
5561 cx.update_editor(|editor, cx| {
5562 editor.autoindent(&Default::default(), cx);
5563 });
5564
5565 cx.assert_editor_state(indoc! {"
5566 impl A {
5567
5568 fn b() {}
5569
5570 «fn c() {
5571
5572 }ˇ»
5573 }
5574 "});
5575 }
5576
5577 {
5578 let mut cx = EditorTestContext::new_multibuffer(
5579 cx,
5580 [indoc! { "
5581 impl A {
5582 «
5583 // a
5584 fn b(){}
5585 »
5586 «
5587 }
5588 fn c(){}
5589 »
5590 "}],
5591 );
5592
5593 let buffer = cx.update_editor(|editor, cx| {
5594 let buffer = editor.buffer().update(cx, |buffer, _| {
5595 buffer.all_buffers().iter().next().unwrap().clone()
5596 });
5597 buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
5598 buffer
5599 });
5600
5601 cx.run_until_parked();
5602 cx.update_editor(|editor, cx| {
5603 editor.select_all(&Default::default(), cx);
5604 editor.autoindent(&Default::default(), cx)
5605 });
5606 cx.run_until_parked();
5607
5608 cx.update(|cx| {
5609 pretty_assertions::assert_eq!(
5610 buffer.read(cx).text(),
5611 indoc! { "
5612 impl A {
5613
5614 // a
5615 fn b(){}
5616
5617
5618 }
5619 fn c(){}
5620
5621 " }
5622 )
5623 });
5624 }
5625}
5626
5627#[gpui::test]
5628async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
5629 init_test(cx, |_| {});
5630
5631 let mut cx = EditorTestContext::new(cx).await;
5632
5633 let language = Arc::new(Language::new(
5634 LanguageConfig {
5635 brackets: BracketPairConfig {
5636 pairs: vec![
5637 BracketPair {
5638 start: "{".to_string(),
5639 end: "}".to_string(),
5640 close: true,
5641 surround: true,
5642 newline: true,
5643 },
5644 BracketPair {
5645 start: "(".to_string(),
5646 end: ")".to_string(),
5647 close: true,
5648 surround: true,
5649 newline: true,
5650 },
5651 BracketPair {
5652 start: "/*".to_string(),
5653 end: " */".to_string(),
5654 close: true,
5655 surround: true,
5656 newline: true,
5657 },
5658 BracketPair {
5659 start: "[".to_string(),
5660 end: "]".to_string(),
5661 close: false,
5662 surround: false,
5663 newline: true,
5664 },
5665 BracketPair {
5666 start: "\"".to_string(),
5667 end: "\"".to_string(),
5668 close: true,
5669 surround: true,
5670 newline: false,
5671 },
5672 BracketPair {
5673 start: "<".to_string(),
5674 end: ">".to_string(),
5675 close: false,
5676 surround: true,
5677 newline: true,
5678 },
5679 ],
5680 ..Default::default()
5681 },
5682 autoclose_before: "})]".to_string(),
5683 ..Default::default()
5684 },
5685 Some(tree_sitter_rust::LANGUAGE.into()),
5686 ));
5687
5688 cx.language_registry().add(language.clone());
5689 cx.update_buffer(|buffer, cx| {
5690 buffer.set_language(Some(language), cx);
5691 });
5692
5693 cx.set_state(
5694 &r#"
5695 🏀ˇ
5696 εˇ
5697 ❤️ˇ
5698 "#
5699 .unindent(),
5700 );
5701
5702 // autoclose multiple nested brackets at multiple cursors
5703 cx.update_editor(|view, cx| {
5704 view.handle_input("{", cx);
5705 view.handle_input("{", cx);
5706 view.handle_input("{", cx);
5707 });
5708 cx.assert_editor_state(
5709 &"
5710 🏀{{{ˇ}}}
5711 ε{{{ˇ}}}
5712 ❤️{{{ˇ}}}
5713 "
5714 .unindent(),
5715 );
5716
5717 // insert a different closing bracket
5718 cx.update_editor(|view, cx| {
5719 view.handle_input(")", cx);
5720 });
5721 cx.assert_editor_state(
5722 &"
5723 🏀{{{)ˇ}}}
5724 ε{{{)ˇ}}}
5725 ❤️{{{)ˇ}}}
5726 "
5727 .unindent(),
5728 );
5729
5730 // skip over the auto-closed brackets when typing a closing bracket
5731 cx.update_editor(|view, cx| {
5732 view.move_right(&MoveRight, cx);
5733 view.handle_input("}", cx);
5734 view.handle_input("}", cx);
5735 view.handle_input("}", cx);
5736 });
5737 cx.assert_editor_state(
5738 &"
5739 🏀{{{)}}}}ˇ
5740 ε{{{)}}}}ˇ
5741 ❤️{{{)}}}}ˇ
5742 "
5743 .unindent(),
5744 );
5745
5746 // autoclose multi-character pairs
5747 cx.set_state(
5748 &"
5749 ˇ
5750 ˇ
5751 "
5752 .unindent(),
5753 );
5754 cx.update_editor(|view, cx| {
5755 view.handle_input("/", cx);
5756 view.handle_input("*", cx);
5757 });
5758 cx.assert_editor_state(
5759 &"
5760 /*ˇ */
5761 /*ˇ */
5762 "
5763 .unindent(),
5764 );
5765
5766 // one cursor autocloses a multi-character pair, one cursor
5767 // does not autoclose.
5768 cx.set_state(
5769 &"
5770 /ˇ
5771 ˇ
5772 "
5773 .unindent(),
5774 );
5775 cx.update_editor(|view, cx| view.handle_input("*", cx));
5776 cx.assert_editor_state(
5777 &"
5778 /*ˇ */
5779 *ˇ
5780 "
5781 .unindent(),
5782 );
5783
5784 // Don't autoclose if the next character isn't whitespace and isn't
5785 // listed in the language's "autoclose_before" section.
5786 cx.set_state("ˇa b");
5787 cx.update_editor(|view, cx| view.handle_input("{", cx));
5788 cx.assert_editor_state("{ˇa b");
5789
5790 // Don't autoclose if `close` is false for the bracket pair
5791 cx.set_state("ˇ");
5792 cx.update_editor(|view, cx| view.handle_input("[", cx));
5793 cx.assert_editor_state("[ˇ");
5794
5795 // Surround with brackets if text is selected
5796 cx.set_state("«aˇ» b");
5797 cx.update_editor(|view, cx| view.handle_input("{", cx));
5798 cx.assert_editor_state("{«aˇ»} b");
5799
5800 // Autclose pair where the start and end characters are the same
5801 cx.set_state("aˇ");
5802 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5803 cx.assert_editor_state("a\"ˇ\"");
5804 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5805 cx.assert_editor_state("a\"\"ˇ");
5806
5807 // Don't autoclose pair if autoclose is disabled
5808 cx.set_state("ˇ");
5809 cx.update_editor(|view, cx| view.handle_input("<", cx));
5810 cx.assert_editor_state("<ˇ");
5811
5812 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
5813 cx.set_state("«aˇ» b");
5814 cx.update_editor(|view, cx| view.handle_input("<", cx));
5815 cx.assert_editor_state("<«aˇ»> b");
5816}
5817
5818#[gpui::test]
5819async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut gpui::TestAppContext) {
5820 init_test(cx, |settings| {
5821 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
5822 });
5823
5824 let mut cx = EditorTestContext::new(cx).await;
5825
5826 let language = Arc::new(Language::new(
5827 LanguageConfig {
5828 brackets: BracketPairConfig {
5829 pairs: vec![
5830 BracketPair {
5831 start: "{".to_string(),
5832 end: "}".to_string(),
5833 close: true,
5834 surround: true,
5835 newline: true,
5836 },
5837 BracketPair {
5838 start: "(".to_string(),
5839 end: ")".to_string(),
5840 close: true,
5841 surround: true,
5842 newline: true,
5843 },
5844 BracketPair {
5845 start: "[".to_string(),
5846 end: "]".to_string(),
5847 close: false,
5848 surround: false,
5849 newline: true,
5850 },
5851 ],
5852 ..Default::default()
5853 },
5854 autoclose_before: "})]".to_string(),
5855 ..Default::default()
5856 },
5857 Some(tree_sitter_rust::LANGUAGE.into()),
5858 ));
5859
5860 cx.language_registry().add(language.clone());
5861 cx.update_buffer(|buffer, cx| {
5862 buffer.set_language(Some(language), cx);
5863 });
5864
5865 cx.set_state(
5866 &"
5867 ˇ
5868 ˇ
5869 ˇ
5870 "
5871 .unindent(),
5872 );
5873
5874 // ensure only matching closing brackets are skipped over
5875 cx.update_editor(|view, cx| {
5876 view.handle_input("}", cx);
5877 view.move_left(&MoveLeft, cx);
5878 view.handle_input(")", cx);
5879 view.move_left(&MoveLeft, cx);
5880 });
5881 cx.assert_editor_state(
5882 &"
5883 ˇ)}
5884 ˇ)}
5885 ˇ)}
5886 "
5887 .unindent(),
5888 );
5889
5890 // skip-over closing brackets at multiple cursors
5891 cx.update_editor(|view, cx| {
5892 view.handle_input(")", cx);
5893 view.handle_input("}", cx);
5894 });
5895 cx.assert_editor_state(
5896 &"
5897 )}ˇ
5898 )}ˇ
5899 )}ˇ
5900 "
5901 .unindent(),
5902 );
5903
5904 // ignore non-close brackets
5905 cx.update_editor(|view, cx| {
5906 view.handle_input("]", cx);
5907 view.move_left(&MoveLeft, cx);
5908 view.handle_input("]", cx);
5909 });
5910 cx.assert_editor_state(
5911 &"
5912 )}]ˇ]
5913 )}]ˇ]
5914 )}]ˇ]
5915 "
5916 .unindent(),
5917 );
5918}
5919
5920#[gpui::test]
5921async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
5922 init_test(cx, |_| {});
5923
5924 let mut cx = EditorTestContext::new(cx).await;
5925
5926 let html_language = Arc::new(
5927 Language::new(
5928 LanguageConfig {
5929 name: "HTML".into(),
5930 brackets: BracketPairConfig {
5931 pairs: vec![
5932 BracketPair {
5933 start: "<".into(),
5934 end: ">".into(),
5935 close: true,
5936 ..Default::default()
5937 },
5938 BracketPair {
5939 start: "{".into(),
5940 end: "}".into(),
5941 close: true,
5942 ..Default::default()
5943 },
5944 BracketPair {
5945 start: "(".into(),
5946 end: ")".into(),
5947 close: true,
5948 ..Default::default()
5949 },
5950 ],
5951 ..Default::default()
5952 },
5953 autoclose_before: "})]>".into(),
5954 ..Default::default()
5955 },
5956 Some(tree_sitter_html::language()),
5957 )
5958 .with_injection_query(
5959 r#"
5960 (script_element
5961 (raw_text) @content
5962 (#set! "language" "javascript"))
5963 "#,
5964 )
5965 .unwrap(),
5966 );
5967
5968 let javascript_language = Arc::new(Language::new(
5969 LanguageConfig {
5970 name: "JavaScript".into(),
5971 brackets: BracketPairConfig {
5972 pairs: vec![
5973 BracketPair {
5974 start: "/*".into(),
5975 end: " */".into(),
5976 close: true,
5977 ..Default::default()
5978 },
5979 BracketPair {
5980 start: "{".into(),
5981 end: "}".into(),
5982 close: true,
5983 ..Default::default()
5984 },
5985 BracketPair {
5986 start: "(".into(),
5987 end: ")".into(),
5988 close: true,
5989 ..Default::default()
5990 },
5991 ],
5992 ..Default::default()
5993 },
5994 autoclose_before: "})]>".into(),
5995 ..Default::default()
5996 },
5997 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
5998 ));
5999
6000 cx.language_registry().add(html_language.clone());
6001 cx.language_registry().add(javascript_language.clone());
6002
6003 cx.update_buffer(|buffer, cx| {
6004 buffer.set_language(Some(html_language), cx);
6005 });
6006
6007 cx.set_state(
6008 &r#"
6009 <body>ˇ
6010 <script>
6011 var x = 1;ˇ
6012 </script>
6013 </body>ˇ
6014 "#
6015 .unindent(),
6016 );
6017
6018 // Precondition: different languages are active at different locations.
6019 cx.update_editor(|editor, cx| {
6020 let snapshot = editor.snapshot(cx);
6021 let cursors = editor.selections.ranges::<usize>(cx);
6022 let languages = cursors
6023 .iter()
6024 .map(|c| snapshot.language_at(c.start).unwrap().name())
6025 .collect::<Vec<_>>();
6026 assert_eq!(
6027 languages,
6028 &["HTML".into(), "JavaScript".into(), "HTML".into()]
6029 );
6030 });
6031
6032 // Angle brackets autoclose in HTML, but not JavaScript.
6033 cx.update_editor(|editor, cx| {
6034 editor.handle_input("<", cx);
6035 editor.handle_input("a", cx);
6036 });
6037 cx.assert_editor_state(
6038 &r#"
6039 <body><aˇ>
6040 <script>
6041 var x = 1;<aˇ
6042 </script>
6043 </body><aˇ>
6044 "#
6045 .unindent(),
6046 );
6047
6048 // Curly braces and parens autoclose in both HTML and JavaScript.
6049 cx.update_editor(|editor, cx| {
6050 editor.handle_input(" b=", cx);
6051 editor.handle_input("{", cx);
6052 editor.handle_input("c", cx);
6053 editor.handle_input("(", cx);
6054 });
6055 cx.assert_editor_state(
6056 &r#"
6057 <body><a b={c(ˇ)}>
6058 <script>
6059 var x = 1;<a b={c(ˇ)}
6060 </script>
6061 </body><a b={c(ˇ)}>
6062 "#
6063 .unindent(),
6064 );
6065
6066 // Brackets that were already autoclosed are skipped.
6067 cx.update_editor(|editor, cx| {
6068 editor.handle_input(")", cx);
6069 editor.handle_input("d", cx);
6070 editor.handle_input("}", cx);
6071 });
6072 cx.assert_editor_state(
6073 &r#"
6074 <body><a b={c()d}ˇ>
6075 <script>
6076 var x = 1;<a b={c()d}ˇ
6077 </script>
6078 </body><a b={c()d}ˇ>
6079 "#
6080 .unindent(),
6081 );
6082 cx.update_editor(|editor, cx| {
6083 editor.handle_input(">", cx);
6084 });
6085 cx.assert_editor_state(
6086 &r#"
6087 <body><a b={c()d}>ˇ
6088 <script>
6089 var x = 1;<a b={c()d}>ˇ
6090 </script>
6091 </body><a b={c()d}>ˇ
6092 "#
6093 .unindent(),
6094 );
6095
6096 // Reset
6097 cx.set_state(
6098 &r#"
6099 <body>ˇ
6100 <script>
6101 var x = 1;ˇ
6102 </script>
6103 </body>ˇ
6104 "#
6105 .unindent(),
6106 );
6107
6108 cx.update_editor(|editor, cx| {
6109 editor.handle_input("<", cx);
6110 });
6111 cx.assert_editor_state(
6112 &r#"
6113 <body><ˇ>
6114 <script>
6115 var x = 1;<ˇ
6116 </script>
6117 </body><ˇ>
6118 "#
6119 .unindent(),
6120 );
6121
6122 // When backspacing, the closing angle brackets are removed.
6123 cx.update_editor(|editor, cx| {
6124 editor.backspace(&Backspace, cx);
6125 });
6126 cx.assert_editor_state(
6127 &r#"
6128 <body>ˇ
6129 <script>
6130 var x = 1;ˇ
6131 </script>
6132 </body>ˇ
6133 "#
6134 .unindent(),
6135 );
6136
6137 // Block comments autoclose in JavaScript, but not HTML.
6138 cx.update_editor(|editor, cx| {
6139 editor.handle_input("/", cx);
6140 editor.handle_input("*", cx);
6141 });
6142 cx.assert_editor_state(
6143 &r#"
6144 <body>/*ˇ
6145 <script>
6146 var x = 1;/*ˇ */
6147 </script>
6148 </body>/*ˇ
6149 "#
6150 .unindent(),
6151 );
6152}
6153
6154#[gpui::test]
6155async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
6156 init_test(cx, |_| {});
6157
6158 let mut cx = EditorTestContext::new(cx).await;
6159
6160 let rust_language = Arc::new(
6161 Language::new(
6162 LanguageConfig {
6163 name: "Rust".into(),
6164 brackets: serde_json::from_value(json!([
6165 { "start": "{", "end": "}", "close": true, "newline": true },
6166 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
6167 ]))
6168 .unwrap(),
6169 autoclose_before: "})]>".into(),
6170 ..Default::default()
6171 },
6172 Some(tree_sitter_rust::LANGUAGE.into()),
6173 )
6174 .with_override_query("(string_literal) @string")
6175 .unwrap(),
6176 );
6177
6178 cx.language_registry().add(rust_language.clone());
6179 cx.update_buffer(|buffer, cx| {
6180 buffer.set_language(Some(rust_language), cx);
6181 });
6182
6183 cx.set_state(
6184 &r#"
6185 let x = ˇ
6186 "#
6187 .unindent(),
6188 );
6189
6190 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
6191 cx.update_editor(|editor, cx| {
6192 editor.handle_input("\"", cx);
6193 });
6194 cx.assert_editor_state(
6195 &r#"
6196 let x = "ˇ"
6197 "#
6198 .unindent(),
6199 );
6200
6201 // Inserting another quotation mark. The cursor moves across the existing
6202 // automatically-inserted quotation mark.
6203 cx.update_editor(|editor, cx| {
6204 editor.handle_input("\"", cx);
6205 });
6206 cx.assert_editor_state(
6207 &r#"
6208 let x = ""ˇ
6209 "#
6210 .unindent(),
6211 );
6212
6213 // Reset
6214 cx.set_state(
6215 &r#"
6216 let x = ˇ
6217 "#
6218 .unindent(),
6219 );
6220
6221 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
6222 cx.update_editor(|editor, cx| {
6223 editor.handle_input("\"", cx);
6224 editor.handle_input(" ", cx);
6225 editor.move_left(&Default::default(), cx);
6226 editor.handle_input("\\", cx);
6227 editor.handle_input("\"", cx);
6228 });
6229 cx.assert_editor_state(
6230 &r#"
6231 let x = "\"ˇ "
6232 "#
6233 .unindent(),
6234 );
6235
6236 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
6237 // mark. Nothing is inserted.
6238 cx.update_editor(|editor, cx| {
6239 editor.move_right(&Default::default(), cx);
6240 editor.handle_input("\"", cx);
6241 });
6242 cx.assert_editor_state(
6243 &r#"
6244 let x = "\" "ˇ
6245 "#
6246 .unindent(),
6247 );
6248}
6249
6250#[gpui::test]
6251async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
6252 init_test(cx, |_| {});
6253
6254 let language = Arc::new(Language::new(
6255 LanguageConfig {
6256 brackets: BracketPairConfig {
6257 pairs: vec![
6258 BracketPair {
6259 start: "{".to_string(),
6260 end: "}".to_string(),
6261 close: true,
6262 surround: true,
6263 newline: true,
6264 },
6265 BracketPair {
6266 start: "/* ".to_string(),
6267 end: "*/".to_string(),
6268 close: true,
6269 surround: true,
6270 ..Default::default()
6271 },
6272 ],
6273 ..Default::default()
6274 },
6275 ..Default::default()
6276 },
6277 Some(tree_sitter_rust::LANGUAGE.into()),
6278 ));
6279
6280 let text = r#"
6281 a
6282 b
6283 c
6284 "#
6285 .unindent();
6286
6287 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
6288 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6289 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6290 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6291 .await;
6292
6293 view.update(cx, |view, cx| {
6294 view.change_selections(None, cx, |s| {
6295 s.select_display_ranges([
6296 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6297 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6298 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
6299 ])
6300 });
6301
6302 view.handle_input("{", cx);
6303 view.handle_input("{", cx);
6304 view.handle_input("{", cx);
6305 assert_eq!(
6306 view.text(cx),
6307 "
6308 {{{a}}}
6309 {{{b}}}
6310 {{{c}}}
6311 "
6312 .unindent()
6313 );
6314 assert_eq!(
6315 view.selections.display_ranges(cx),
6316 [
6317 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
6318 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
6319 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
6320 ]
6321 );
6322
6323 view.undo(&Undo, cx);
6324 view.undo(&Undo, cx);
6325 view.undo(&Undo, cx);
6326 assert_eq!(
6327 view.text(cx),
6328 "
6329 a
6330 b
6331 c
6332 "
6333 .unindent()
6334 );
6335 assert_eq!(
6336 view.selections.display_ranges(cx),
6337 [
6338 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6339 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6340 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6341 ]
6342 );
6343
6344 // Ensure inserting the first character of a multi-byte bracket pair
6345 // doesn't surround the selections with the bracket.
6346 view.handle_input("/", cx);
6347 assert_eq!(
6348 view.text(cx),
6349 "
6350 /
6351 /
6352 /
6353 "
6354 .unindent()
6355 );
6356 assert_eq!(
6357 view.selections.display_ranges(cx),
6358 [
6359 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6360 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6361 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6362 ]
6363 );
6364
6365 view.undo(&Undo, cx);
6366 assert_eq!(
6367 view.text(cx),
6368 "
6369 a
6370 b
6371 c
6372 "
6373 .unindent()
6374 );
6375 assert_eq!(
6376 view.selections.display_ranges(cx),
6377 [
6378 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6379 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6380 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6381 ]
6382 );
6383
6384 // Ensure inserting the last character of a multi-byte bracket pair
6385 // doesn't surround the selections with the bracket.
6386 view.handle_input("*", cx);
6387 assert_eq!(
6388 view.text(cx),
6389 "
6390 *
6391 *
6392 *
6393 "
6394 .unindent()
6395 );
6396 assert_eq!(
6397 view.selections.display_ranges(cx),
6398 [
6399 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6400 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6401 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6402 ]
6403 );
6404 });
6405}
6406
6407#[gpui::test]
6408async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
6409 init_test(cx, |_| {});
6410
6411 let language = Arc::new(Language::new(
6412 LanguageConfig {
6413 brackets: BracketPairConfig {
6414 pairs: vec![BracketPair {
6415 start: "{".to_string(),
6416 end: "}".to_string(),
6417 close: true,
6418 surround: true,
6419 newline: true,
6420 }],
6421 ..Default::default()
6422 },
6423 autoclose_before: "}".to_string(),
6424 ..Default::default()
6425 },
6426 Some(tree_sitter_rust::LANGUAGE.into()),
6427 ));
6428
6429 let text = r#"
6430 a
6431 b
6432 c
6433 "#
6434 .unindent();
6435
6436 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
6437 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6438 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6439 editor
6440 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6441 .await;
6442
6443 editor.update(cx, |editor, cx| {
6444 editor.change_selections(None, cx, |s| {
6445 s.select_ranges([
6446 Point::new(0, 1)..Point::new(0, 1),
6447 Point::new(1, 1)..Point::new(1, 1),
6448 Point::new(2, 1)..Point::new(2, 1),
6449 ])
6450 });
6451
6452 editor.handle_input("{", cx);
6453 editor.handle_input("{", cx);
6454 editor.handle_input("_", cx);
6455 assert_eq!(
6456 editor.text(cx),
6457 "
6458 a{{_}}
6459 b{{_}}
6460 c{{_}}
6461 "
6462 .unindent()
6463 );
6464 assert_eq!(
6465 editor.selections.ranges::<Point>(cx),
6466 [
6467 Point::new(0, 4)..Point::new(0, 4),
6468 Point::new(1, 4)..Point::new(1, 4),
6469 Point::new(2, 4)..Point::new(2, 4)
6470 ]
6471 );
6472
6473 editor.backspace(&Default::default(), cx);
6474 editor.backspace(&Default::default(), cx);
6475 assert_eq!(
6476 editor.text(cx),
6477 "
6478 a{}
6479 b{}
6480 c{}
6481 "
6482 .unindent()
6483 );
6484 assert_eq!(
6485 editor.selections.ranges::<Point>(cx),
6486 [
6487 Point::new(0, 2)..Point::new(0, 2),
6488 Point::new(1, 2)..Point::new(1, 2),
6489 Point::new(2, 2)..Point::new(2, 2)
6490 ]
6491 );
6492
6493 editor.delete_to_previous_word_start(&Default::default(), cx);
6494 assert_eq!(
6495 editor.text(cx),
6496 "
6497 a
6498 b
6499 c
6500 "
6501 .unindent()
6502 );
6503 assert_eq!(
6504 editor.selections.ranges::<Point>(cx),
6505 [
6506 Point::new(0, 1)..Point::new(0, 1),
6507 Point::new(1, 1)..Point::new(1, 1),
6508 Point::new(2, 1)..Point::new(2, 1)
6509 ]
6510 );
6511 });
6512}
6513
6514#[gpui::test]
6515async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut gpui::TestAppContext) {
6516 init_test(cx, |settings| {
6517 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6518 });
6519
6520 let mut cx = EditorTestContext::new(cx).await;
6521
6522 let language = Arc::new(Language::new(
6523 LanguageConfig {
6524 brackets: BracketPairConfig {
6525 pairs: vec![
6526 BracketPair {
6527 start: "{".to_string(),
6528 end: "}".to_string(),
6529 close: true,
6530 surround: true,
6531 newline: true,
6532 },
6533 BracketPair {
6534 start: "(".to_string(),
6535 end: ")".to_string(),
6536 close: true,
6537 surround: true,
6538 newline: true,
6539 },
6540 BracketPair {
6541 start: "[".to_string(),
6542 end: "]".to_string(),
6543 close: false,
6544 surround: true,
6545 newline: true,
6546 },
6547 ],
6548 ..Default::default()
6549 },
6550 autoclose_before: "})]".to_string(),
6551 ..Default::default()
6552 },
6553 Some(tree_sitter_rust::LANGUAGE.into()),
6554 ));
6555
6556 cx.language_registry().add(language.clone());
6557 cx.update_buffer(|buffer, cx| {
6558 buffer.set_language(Some(language), cx);
6559 });
6560
6561 cx.set_state(
6562 &"
6563 {(ˇ)}
6564 [[ˇ]]
6565 {(ˇ)}
6566 "
6567 .unindent(),
6568 );
6569
6570 cx.update_editor(|view, cx| {
6571 view.backspace(&Default::default(), cx);
6572 view.backspace(&Default::default(), cx);
6573 });
6574
6575 cx.assert_editor_state(
6576 &"
6577 ˇ
6578 ˇ]]
6579 ˇ
6580 "
6581 .unindent(),
6582 );
6583
6584 cx.update_editor(|view, cx| {
6585 view.handle_input("{", cx);
6586 view.handle_input("{", cx);
6587 view.move_right(&MoveRight, cx);
6588 view.move_right(&MoveRight, cx);
6589 view.move_left(&MoveLeft, cx);
6590 view.move_left(&MoveLeft, cx);
6591 view.backspace(&Default::default(), cx);
6592 });
6593
6594 cx.assert_editor_state(
6595 &"
6596 {ˇ}
6597 {ˇ}]]
6598 {ˇ}
6599 "
6600 .unindent(),
6601 );
6602
6603 cx.update_editor(|view, cx| {
6604 view.backspace(&Default::default(), cx);
6605 });
6606
6607 cx.assert_editor_state(
6608 &"
6609 ˇ
6610 ˇ]]
6611 ˇ
6612 "
6613 .unindent(),
6614 );
6615}
6616
6617#[gpui::test]
6618async fn test_auto_replace_emoji_shortcode(cx: &mut gpui::TestAppContext) {
6619 init_test(cx, |_| {});
6620
6621 let language = Arc::new(Language::new(
6622 LanguageConfig::default(),
6623 Some(tree_sitter_rust::LANGUAGE.into()),
6624 ));
6625
6626 let buffer = cx.new_model(|cx| Buffer::local("", cx).with_language(language, cx));
6627 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6628 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6629 editor
6630 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6631 .await;
6632
6633 editor.update(cx, |editor, cx| {
6634 editor.set_auto_replace_emoji_shortcode(true);
6635
6636 editor.handle_input("Hello ", cx);
6637 editor.handle_input(":wave", cx);
6638 assert_eq!(editor.text(cx), "Hello :wave".unindent());
6639
6640 editor.handle_input(":", cx);
6641 assert_eq!(editor.text(cx), "Hello 👋".unindent());
6642
6643 editor.handle_input(" :smile", cx);
6644 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
6645
6646 editor.handle_input(":", cx);
6647 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
6648
6649 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
6650 editor.handle_input(":wave", cx);
6651 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
6652
6653 editor.handle_input(":", cx);
6654 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
6655
6656 editor.handle_input(":1", cx);
6657 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
6658
6659 editor.handle_input(":", cx);
6660 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
6661
6662 // Ensure shortcode does not get replaced when it is part of a word
6663 editor.handle_input(" Test:wave", cx);
6664 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
6665
6666 editor.handle_input(":", cx);
6667 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
6668
6669 editor.set_auto_replace_emoji_shortcode(false);
6670
6671 // Ensure shortcode does not get replaced when auto replace is off
6672 editor.handle_input(" :wave", cx);
6673 assert_eq!(
6674 editor.text(cx),
6675 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
6676 );
6677
6678 editor.handle_input(":", cx);
6679 assert_eq!(
6680 editor.text(cx),
6681 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
6682 );
6683 });
6684}
6685
6686#[gpui::test]
6687async fn test_snippet_placeholder_choices(cx: &mut gpui::TestAppContext) {
6688 init_test(cx, |_| {});
6689
6690 let (text, insertion_ranges) = marked_text_ranges(
6691 indoc! {"
6692 ˇ
6693 "},
6694 false,
6695 );
6696
6697 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
6698 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6699
6700 _ = editor.update(cx, |editor, cx| {
6701 let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
6702
6703 editor
6704 .insert_snippet(&insertion_ranges, snippet, cx)
6705 .unwrap();
6706
6707 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
6708 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
6709 assert_eq!(editor.text(cx), expected_text);
6710 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
6711 }
6712
6713 assert(
6714 editor,
6715 cx,
6716 indoc! {"
6717 type «» =•
6718 "},
6719 );
6720
6721 assert!(editor.context_menu_visible(), "There should be a matches");
6722 });
6723}
6724
6725#[gpui::test]
6726async fn test_snippets(cx: &mut gpui::TestAppContext) {
6727 init_test(cx, |_| {});
6728
6729 let (text, insertion_ranges) = marked_text_ranges(
6730 indoc! {"
6731 a.ˇ b
6732 a.ˇ b
6733 a.ˇ b
6734 "},
6735 false,
6736 );
6737
6738 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
6739 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6740
6741 editor.update(cx, |editor, cx| {
6742 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
6743
6744 editor
6745 .insert_snippet(&insertion_ranges, snippet, cx)
6746 .unwrap();
6747
6748 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
6749 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
6750 assert_eq!(editor.text(cx), expected_text);
6751 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
6752 }
6753
6754 assert(
6755 editor,
6756 cx,
6757 indoc! {"
6758 a.f(«one», two, «three») b
6759 a.f(«one», two, «three») b
6760 a.f(«one», two, «three») b
6761 "},
6762 );
6763
6764 // Can't move earlier than the first tab stop
6765 assert!(!editor.move_to_prev_snippet_tabstop(cx));
6766 assert(
6767 editor,
6768 cx,
6769 indoc! {"
6770 a.f(«one», two, «three») b
6771 a.f(«one», two, «three») b
6772 a.f(«one», two, «three») b
6773 "},
6774 );
6775
6776 assert!(editor.move_to_next_snippet_tabstop(cx));
6777 assert(
6778 editor,
6779 cx,
6780 indoc! {"
6781 a.f(one, «two», three) b
6782 a.f(one, «two», three) b
6783 a.f(one, «two», three) b
6784 "},
6785 );
6786
6787 editor.move_to_prev_snippet_tabstop(cx);
6788 assert(
6789 editor,
6790 cx,
6791 indoc! {"
6792 a.f(«one», two, «three») b
6793 a.f(«one», two, «three») b
6794 a.f(«one», two, «three») b
6795 "},
6796 );
6797
6798 assert!(editor.move_to_next_snippet_tabstop(cx));
6799 assert(
6800 editor,
6801 cx,
6802 indoc! {"
6803 a.f(one, «two», three) b
6804 a.f(one, «two», three) b
6805 a.f(one, «two», three) b
6806 "},
6807 );
6808 assert!(editor.move_to_next_snippet_tabstop(cx));
6809 assert(
6810 editor,
6811 cx,
6812 indoc! {"
6813 a.f(one, two, three)ˇ b
6814 a.f(one, two, three)ˇ b
6815 a.f(one, two, three)ˇ b
6816 "},
6817 );
6818
6819 // As soon as the last tab stop is reached, snippet state is gone
6820 editor.move_to_prev_snippet_tabstop(cx);
6821 assert(
6822 editor,
6823 cx,
6824 indoc! {"
6825 a.f(one, two, three)ˇ b
6826 a.f(one, two, three)ˇ b
6827 a.f(one, two, three)ˇ b
6828 "},
6829 );
6830 });
6831}
6832
6833#[gpui::test]
6834async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
6835 init_test(cx, |_| {});
6836
6837 let fs = FakeFs::new(cx.executor());
6838 fs.insert_file("/file.rs", Default::default()).await;
6839
6840 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6841
6842 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6843 language_registry.add(rust_lang());
6844 let mut fake_servers = language_registry.register_fake_lsp(
6845 "Rust",
6846 FakeLspAdapter {
6847 capabilities: lsp::ServerCapabilities {
6848 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6849 ..Default::default()
6850 },
6851 ..Default::default()
6852 },
6853 );
6854
6855 let buffer = project
6856 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6857 .await
6858 .unwrap();
6859
6860 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6861 let (editor, cx) =
6862 cx.add_window_view(|cx| build_editor_with_project(project.clone(), buffer, cx));
6863 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6864 assert!(cx.read(|cx| editor.is_dirty(cx)));
6865
6866 cx.executor().start_waiting();
6867 let fake_server = fake_servers.next().await.unwrap();
6868
6869 let save = editor
6870 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6871 .unwrap();
6872 fake_server
6873 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6874 assert_eq!(
6875 params.text_document.uri,
6876 lsp::Url::from_file_path("/file.rs").unwrap()
6877 );
6878 assert_eq!(params.options.tab_size, 4);
6879 Ok(Some(vec![lsp::TextEdit::new(
6880 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6881 ", ".to_string(),
6882 )]))
6883 })
6884 .next()
6885 .await;
6886 cx.executor().start_waiting();
6887 save.await;
6888
6889 assert_eq!(
6890 editor.update(cx, |editor, cx| editor.text(cx)),
6891 "one, two\nthree\n"
6892 );
6893 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6894
6895 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6896 assert!(cx.read(|cx| editor.is_dirty(cx)));
6897
6898 // Ensure we can still save even if formatting hangs.
6899 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6900 assert_eq!(
6901 params.text_document.uri,
6902 lsp::Url::from_file_path("/file.rs").unwrap()
6903 );
6904 futures::future::pending::<()>().await;
6905 unreachable!()
6906 });
6907 let save = editor
6908 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6909 .unwrap();
6910 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6911 cx.executor().start_waiting();
6912 save.await;
6913 assert_eq!(
6914 editor.update(cx, |editor, cx| editor.text(cx)),
6915 "one\ntwo\nthree\n"
6916 );
6917 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6918
6919 // For non-dirty buffer, no formatting request should be sent
6920 let save = editor
6921 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6922 .unwrap();
6923 let _pending_format_request = fake_server
6924 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
6925 panic!("Should not be invoked on non-dirty buffer");
6926 })
6927 .next();
6928 cx.executor().start_waiting();
6929 save.await;
6930
6931 // Set rust language override and assert overridden tabsize is sent to language server
6932 update_test_language_settings(cx, |settings| {
6933 settings.languages.insert(
6934 "Rust".into(),
6935 LanguageSettingsContent {
6936 tab_size: NonZeroU32::new(8),
6937 ..Default::default()
6938 },
6939 );
6940 });
6941
6942 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
6943 assert!(cx.read(|cx| editor.is_dirty(cx)));
6944 let save = editor
6945 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6946 .unwrap();
6947 fake_server
6948 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6949 assert_eq!(
6950 params.text_document.uri,
6951 lsp::Url::from_file_path("/file.rs").unwrap()
6952 );
6953 assert_eq!(params.options.tab_size, 8);
6954 Ok(Some(vec![]))
6955 })
6956 .next()
6957 .await;
6958 cx.executor().start_waiting();
6959 save.await;
6960}
6961
6962#[gpui::test]
6963async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
6964 init_test(cx, |_| {});
6965
6966 let cols = 4;
6967 let rows = 10;
6968 let sample_text_1 = sample_text(rows, cols, 'a');
6969 assert_eq!(
6970 sample_text_1,
6971 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
6972 );
6973 let sample_text_2 = sample_text(rows, cols, 'l');
6974 assert_eq!(
6975 sample_text_2,
6976 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
6977 );
6978 let sample_text_3 = sample_text(rows, cols, 'v');
6979 assert_eq!(
6980 sample_text_3,
6981 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
6982 );
6983
6984 let fs = FakeFs::new(cx.executor());
6985 fs.insert_tree(
6986 "/a",
6987 json!({
6988 "main.rs": sample_text_1,
6989 "other.rs": sample_text_2,
6990 "lib.rs": sample_text_3,
6991 }),
6992 )
6993 .await;
6994
6995 let project = Project::test(fs, ["/a".as_ref()], cx).await;
6996 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
6997 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
6998
6999 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7000 language_registry.add(rust_lang());
7001 let mut fake_servers = language_registry.register_fake_lsp(
7002 "Rust",
7003 FakeLspAdapter {
7004 capabilities: lsp::ServerCapabilities {
7005 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7006 ..Default::default()
7007 },
7008 ..Default::default()
7009 },
7010 );
7011
7012 let worktree = project.update(cx, |project, cx| {
7013 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
7014 assert_eq!(worktrees.len(), 1);
7015 worktrees.pop().unwrap()
7016 });
7017 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
7018
7019 let buffer_1 = project
7020 .update(cx, |project, cx| {
7021 project.open_buffer((worktree_id, "main.rs"), cx)
7022 })
7023 .await
7024 .unwrap();
7025 let buffer_2 = project
7026 .update(cx, |project, cx| {
7027 project.open_buffer((worktree_id, "other.rs"), cx)
7028 })
7029 .await
7030 .unwrap();
7031 let buffer_3 = project
7032 .update(cx, |project, cx| {
7033 project.open_buffer((worktree_id, "lib.rs"), cx)
7034 })
7035 .await
7036 .unwrap();
7037
7038 let multi_buffer = cx.new_model(|cx| {
7039 let mut multi_buffer = MultiBuffer::new(ReadWrite);
7040 multi_buffer.push_excerpts(
7041 buffer_1.clone(),
7042 [
7043 ExcerptRange {
7044 context: Point::new(0, 0)..Point::new(3, 0),
7045 primary: None,
7046 },
7047 ExcerptRange {
7048 context: Point::new(5, 0)..Point::new(7, 0),
7049 primary: None,
7050 },
7051 ExcerptRange {
7052 context: Point::new(9, 0)..Point::new(10, 4),
7053 primary: None,
7054 },
7055 ],
7056 cx,
7057 );
7058 multi_buffer.push_excerpts(
7059 buffer_2.clone(),
7060 [
7061 ExcerptRange {
7062 context: Point::new(0, 0)..Point::new(3, 0),
7063 primary: None,
7064 },
7065 ExcerptRange {
7066 context: Point::new(5, 0)..Point::new(7, 0),
7067 primary: None,
7068 },
7069 ExcerptRange {
7070 context: Point::new(9, 0)..Point::new(10, 4),
7071 primary: None,
7072 },
7073 ],
7074 cx,
7075 );
7076 multi_buffer.push_excerpts(
7077 buffer_3.clone(),
7078 [
7079 ExcerptRange {
7080 context: Point::new(0, 0)..Point::new(3, 0),
7081 primary: None,
7082 },
7083 ExcerptRange {
7084 context: Point::new(5, 0)..Point::new(7, 0),
7085 primary: None,
7086 },
7087 ExcerptRange {
7088 context: Point::new(9, 0)..Point::new(10, 4),
7089 primary: None,
7090 },
7091 ],
7092 cx,
7093 );
7094 multi_buffer
7095 });
7096 let multi_buffer_editor = cx.new_view(|cx| {
7097 Editor::new(
7098 EditorMode::Full,
7099 multi_buffer,
7100 Some(project.clone()),
7101 true,
7102 cx,
7103 )
7104 });
7105
7106 multi_buffer_editor.update(cx, |editor, cx| {
7107 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
7108 editor.insert("|one|two|three|", cx);
7109 });
7110 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7111 multi_buffer_editor.update(cx, |editor, cx| {
7112 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
7113 s.select_ranges(Some(60..70))
7114 });
7115 editor.insert("|four|five|six|", cx);
7116 });
7117 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7118
7119 // First two buffers should be edited, but not the third one.
7120 assert_eq!(
7121 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7122 "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}",
7123 );
7124 buffer_1.update(cx, |buffer, _| {
7125 assert!(buffer.is_dirty());
7126 assert_eq!(
7127 buffer.text(),
7128 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
7129 )
7130 });
7131 buffer_2.update(cx, |buffer, _| {
7132 assert!(buffer.is_dirty());
7133 assert_eq!(
7134 buffer.text(),
7135 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
7136 )
7137 });
7138 buffer_3.update(cx, |buffer, _| {
7139 assert!(!buffer.is_dirty());
7140 assert_eq!(buffer.text(), sample_text_3,)
7141 });
7142 cx.executor().run_until_parked();
7143
7144 cx.executor().start_waiting();
7145 let save = multi_buffer_editor
7146 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7147 .unwrap();
7148
7149 let fake_server = fake_servers.next().await.unwrap();
7150 fake_server
7151 .server
7152 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7153 Ok(Some(vec![lsp::TextEdit::new(
7154 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7155 format!("[{} formatted]", params.text_document.uri),
7156 )]))
7157 })
7158 .detach();
7159 save.await;
7160
7161 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
7162 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
7163 assert_eq!(
7164 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7165 "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}",
7166 );
7167 buffer_1.update(cx, |buffer, _| {
7168 assert!(!buffer.is_dirty());
7169 assert_eq!(
7170 buffer.text(),
7171 "a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n",
7172 )
7173 });
7174 buffer_2.update(cx, |buffer, _| {
7175 assert!(!buffer.is_dirty());
7176 assert_eq!(
7177 buffer.text(),
7178 "lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n",
7179 )
7180 });
7181 buffer_3.update(cx, |buffer, _| {
7182 assert!(!buffer.is_dirty());
7183 assert_eq!(buffer.text(), sample_text_3,)
7184 });
7185}
7186
7187#[gpui::test]
7188async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
7189 init_test(cx, |_| {});
7190
7191 let fs = FakeFs::new(cx.executor());
7192 fs.insert_file("/file.rs", Default::default()).await;
7193
7194 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7195
7196 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7197 language_registry.add(rust_lang());
7198 let mut fake_servers = language_registry.register_fake_lsp(
7199 "Rust",
7200 FakeLspAdapter {
7201 capabilities: lsp::ServerCapabilities {
7202 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
7203 ..Default::default()
7204 },
7205 ..Default::default()
7206 },
7207 );
7208
7209 let buffer = project
7210 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
7211 .await
7212 .unwrap();
7213
7214 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
7215 let (editor, cx) =
7216 cx.add_window_view(|cx| build_editor_with_project(project.clone(), buffer, cx));
7217 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7218 assert!(cx.read(|cx| editor.is_dirty(cx)));
7219
7220 cx.executor().start_waiting();
7221 let fake_server = fake_servers.next().await.unwrap();
7222
7223 let save = editor
7224 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7225 .unwrap();
7226 fake_server
7227 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7228 assert_eq!(
7229 params.text_document.uri,
7230 lsp::Url::from_file_path("/file.rs").unwrap()
7231 );
7232 assert_eq!(params.options.tab_size, 4);
7233 Ok(Some(vec![lsp::TextEdit::new(
7234 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7235 ", ".to_string(),
7236 )]))
7237 })
7238 .next()
7239 .await;
7240 cx.executor().start_waiting();
7241 save.await;
7242 assert_eq!(
7243 editor.update(cx, |editor, cx| editor.text(cx)),
7244 "one, two\nthree\n"
7245 );
7246 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7247
7248 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7249 assert!(cx.read(|cx| editor.is_dirty(cx)));
7250
7251 // Ensure we can still save even if formatting hangs.
7252 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
7253 move |params, _| async move {
7254 assert_eq!(
7255 params.text_document.uri,
7256 lsp::Url::from_file_path("/file.rs").unwrap()
7257 );
7258 futures::future::pending::<()>().await;
7259 unreachable!()
7260 },
7261 );
7262 let save = editor
7263 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7264 .unwrap();
7265 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7266 cx.executor().start_waiting();
7267 save.await;
7268 assert_eq!(
7269 editor.update(cx, |editor, cx| editor.text(cx)),
7270 "one\ntwo\nthree\n"
7271 );
7272 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7273
7274 // For non-dirty buffer, no formatting request should be sent
7275 let save = editor
7276 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7277 .unwrap();
7278 let _pending_format_request = fake_server
7279 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7280 panic!("Should not be invoked on non-dirty buffer");
7281 })
7282 .next();
7283 cx.executor().start_waiting();
7284 save.await;
7285
7286 // Set Rust language override and assert overridden tabsize is sent to language server
7287 update_test_language_settings(cx, |settings| {
7288 settings.languages.insert(
7289 "Rust".into(),
7290 LanguageSettingsContent {
7291 tab_size: NonZeroU32::new(8),
7292 ..Default::default()
7293 },
7294 );
7295 });
7296
7297 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
7298 assert!(cx.read(|cx| editor.is_dirty(cx)));
7299 let save = editor
7300 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7301 .unwrap();
7302 fake_server
7303 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7304 assert_eq!(
7305 params.text_document.uri,
7306 lsp::Url::from_file_path("/file.rs").unwrap()
7307 );
7308 assert_eq!(params.options.tab_size, 8);
7309 Ok(Some(vec![]))
7310 })
7311 .next()
7312 .await;
7313 cx.executor().start_waiting();
7314 save.await;
7315}
7316
7317#[gpui::test]
7318async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
7319 init_test(cx, |settings| {
7320 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
7321 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
7322 ))
7323 });
7324
7325 let fs = FakeFs::new(cx.executor());
7326 fs.insert_file("/file.rs", Default::default()).await;
7327
7328 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7329
7330 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7331 language_registry.add(Arc::new(Language::new(
7332 LanguageConfig {
7333 name: "Rust".into(),
7334 matcher: LanguageMatcher {
7335 path_suffixes: vec!["rs".to_string()],
7336 ..Default::default()
7337 },
7338 ..LanguageConfig::default()
7339 },
7340 Some(tree_sitter_rust::LANGUAGE.into()),
7341 )));
7342 update_test_language_settings(cx, |settings| {
7343 // Enable Prettier formatting for the same buffer, and ensure
7344 // LSP is called instead of Prettier.
7345 settings.defaults.prettier = Some(PrettierSettings {
7346 allowed: true,
7347 ..PrettierSettings::default()
7348 });
7349 });
7350 let mut fake_servers = language_registry.register_fake_lsp(
7351 "Rust",
7352 FakeLspAdapter {
7353 capabilities: lsp::ServerCapabilities {
7354 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7355 ..Default::default()
7356 },
7357 ..Default::default()
7358 },
7359 );
7360
7361 let buffer = project
7362 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
7363 .await
7364 .unwrap();
7365
7366 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
7367 let (editor, cx) =
7368 cx.add_window_view(|cx| build_editor_with_project(project.clone(), buffer, cx));
7369 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7370
7371 cx.executor().start_waiting();
7372 let fake_server = fake_servers.next().await.unwrap();
7373
7374 let format = editor
7375 .update(cx, |editor, cx| {
7376 editor.perform_format(
7377 project.clone(),
7378 FormatTrigger::Manual,
7379 FormatTarget::Buffer,
7380 cx,
7381 )
7382 })
7383 .unwrap();
7384 fake_server
7385 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7386 assert_eq!(
7387 params.text_document.uri,
7388 lsp::Url::from_file_path("/file.rs").unwrap()
7389 );
7390 assert_eq!(params.options.tab_size, 4);
7391 Ok(Some(vec![lsp::TextEdit::new(
7392 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7393 ", ".to_string(),
7394 )]))
7395 })
7396 .next()
7397 .await;
7398 cx.executor().start_waiting();
7399 format.await;
7400 assert_eq!(
7401 editor.update(cx, |editor, cx| editor.text(cx)),
7402 "one, two\nthree\n"
7403 );
7404
7405 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7406 // Ensure we don't lock if formatting hangs.
7407 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7408 assert_eq!(
7409 params.text_document.uri,
7410 lsp::Url::from_file_path("/file.rs").unwrap()
7411 );
7412 futures::future::pending::<()>().await;
7413 unreachable!()
7414 });
7415 let format = editor
7416 .update(cx, |editor, cx| {
7417 editor.perform_format(project, FormatTrigger::Manual, FormatTarget::Buffer, cx)
7418 })
7419 .unwrap();
7420 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7421 cx.executor().start_waiting();
7422 format.await;
7423 assert_eq!(
7424 editor.update(cx, |editor, cx| editor.text(cx)),
7425 "one\ntwo\nthree\n"
7426 );
7427}
7428
7429#[gpui::test]
7430async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
7431 init_test(cx, |_| {});
7432
7433 let mut cx = EditorLspTestContext::new_rust(
7434 lsp::ServerCapabilities {
7435 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7436 ..Default::default()
7437 },
7438 cx,
7439 )
7440 .await;
7441
7442 cx.set_state(indoc! {"
7443 one.twoˇ
7444 "});
7445
7446 // The format request takes a long time. When it completes, it inserts
7447 // a newline and an indent before the `.`
7448 cx.lsp
7449 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
7450 let executor = cx.background_executor().clone();
7451 async move {
7452 executor.timer(Duration::from_millis(100)).await;
7453 Ok(Some(vec![lsp::TextEdit {
7454 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
7455 new_text: "\n ".into(),
7456 }]))
7457 }
7458 });
7459
7460 // Submit a format request.
7461 let format_1 = cx
7462 .update_editor(|editor, cx| editor.format(&Format, cx))
7463 .unwrap();
7464 cx.executor().run_until_parked();
7465
7466 // Submit a second format request.
7467 let format_2 = cx
7468 .update_editor(|editor, cx| editor.format(&Format, cx))
7469 .unwrap();
7470 cx.executor().run_until_parked();
7471
7472 // Wait for both format requests to complete
7473 cx.executor().advance_clock(Duration::from_millis(200));
7474 cx.executor().start_waiting();
7475 format_1.await.unwrap();
7476 cx.executor().start_waiting();
7477 format_2.await.unwrap();
7478
7479 // The formatting edits only happens once.
7480 cx.assert_editor_state(indoc! {"
7481 one
7482 .twoˇ
7483 "});
7484}
7485
7486#[gpui::test]
7487async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
7488 init_test(cx, |settings| {
7489 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
7490 });
7491
7492 let mut cx = EditorLspTestContext::new_rust(
7493 lsp::ServerCapabilities {
7494 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7495 ..Default::default()
7496 },
7497 cx,
7498 )
7499 .await;
7500
7501 // Set up a buffer white some trailing whitespace and no trailing newline.
7502 cx.set_state(
7503 &[
7504 "one ", //
7505 "twoˇ", //
7506 "three ", //
7507 "four", //
7508 ]
7509 .join("\n"),
7510 );
7511
7512 // Submit a format request.
7513 let format = cx
7514 .update_editor(|editor, cx| editor.format(&Format, cx))
7515 .unwrap();
7516
7517 // Record which buffer changes have been sent to the language server
7518 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
7519 cx.lsp
7520 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
7521 let buffer_changes = buffer_changes.clone();
7522 move |params, _| {
7523 buffer_changes.lock().extend(
7524 params
7525 .content_changes
7526 .into_iter()
7527 .map(|e| (e.range.unwrap(), e.text)),
7528 );
7529 }
7530 });
7531
7532 // Handle formatting requests to the language server.
7533 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
7534 let buffer_changes = buffer_changes.clone();
7535 move |_, _| {
7536 // When formatting is requested, trailing whitespace has already been stripped,
7537 // and the trailing newline has already been added.
7538 assert_eq!(
7539 &buffer_changes.lock()[1..],
7540 &[
7541 (
7542 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
7543 "".into()
7544 ),
7545 (
7546 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
7547 "".into()
7548 ),
7549 (
7550 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
7551 "\n".into()
7552 ),
7553 ]
7554 );
7555
7556 // Insert blank lines between each line of the buffer.
7557 async move {
7558 Ok(Some(vec![
7559 lsp::TextEdit {
7560 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
7561 new_text: "\n".into(),
7562 },
7563 lsp::TextEdit {
7564 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
7565 new_text: "\n".into(),
7566 },
7567 ]))
7568 }
7569 }
7570 });
7571
7572 // After formatting the buffer, the trailing whitespace is stripped,
7573 // a newline is appended, and the edits provided by the language server
7574 // have been applied.
7575 format.await.unwrap();
7576 cx.assert_editor_state(
7577 &[
7578 "one", //
7579 "", //
7580 "twoˇ", //
7581 "", //
7582 "three", //
7583 "four", //
7584 "", //
7585 ]
7586 .join("\n"),
7587 );
7588
7589 // Undoing the formatting undoes the trailing whitespace removal, the
7590 // trailing newline, and the LSP edits.
7591 cx.update_buffer(|buffer, cx| buffer.undo(cx));
7592 cx.assert_editor_state(
7593 &[
7594 "one ", //
7595 "twoˇ", //
7596 "three ", //
7597 "four", //
7598 ]
7599 .join("\n"),
7600 );
7601}
7602
7603#[gpui::test]
7604async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
7605 cx: &mut gpui::TestAppContext,
7606) {
7607 init_test(cx, |_| {});
7608
7609 cx.update(|cx| {
7610 cx.update_global::<SettingsStore, _>(|settings, cx| {
7611 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7612 settings.auto_signature_help = Some(true);
7613 });
7614 });
7615 });
7616
7617 let mut cx = EditorLspTestContext::new_rust(
7618 lsp::ServerCapabilities {
7619 signature_help_provider: Some(lsp::SignatureHelpOptions {
7620 ..Default::default()
7621 }),
7622 ..Default::default()
7623 },
7624 cx,
7625 )
7626 .await;
7627
7628 let language = Language::new(
7629 LanguageConfig {
7630 name: "Rust".into(),
7631 brackets: BracketPairConfig {
7632 pairs: vec![
7633 BracketPair {
7634 start: "{".to_string(),
7635 end: "}".to_string(),
7636 close: true,
7637 surround: true,
7638 newline: true,
7639 },
7640 BracketPair {
7641 start: "(".to_string(),
7642 end: ")".to_string(),
7643 close: true,
7644 surround: true,
7645 newline: true,
7646 },
7647 BracketPair {
7648 start: "/*".to_string(),
7649 end: " */".to_string(),
7650 close: true,
7651 surround: true,
7652 newline: true,
7653 },
7654 BracketPair {
7655 start: "[".to_string(),
7656 end: "]".to_string(),
7657 close: false,
7658 surround: false,
7659 newline: true,
7660 },
7661 BracketPair {
7662 start: "\"".to_string(),
7663 end: "\"".to_string(),
7664 close: true,
7665 surround: true,
7666 newline: false,
7667 },
7668 BracketPair {
7669 start: "<".to_string(),
7670 end: ">".to_string(),
7671 close: false,
7672 surround: true,
7673 newline: true,
7674 },
7675 ],
7676 ..Default::default()
7677 },
7678 autoclose_before: "})]".to_string(),
7679 ..Default::default()
7680 },
7681 Some(tree_sitter_rust::LANGUAGE.into()),
7682 );
7683 let language = Arc::new(language);
7684
7685 cx.language_registry().add(language.clone());
7686 cx.update_buffer(|buffer, cx| {
7687 buffer.set_language(Some(language), cx);
7688 });
7689
7690 cx.set_state(
7691 &r#"
7692 fn main() {
7693 sampleˇ
7694 }
7695 "#
7696 .unindent(),
7697 );
7698
7699 cx.update_editor(|view, cx| {
7700 view.handle_input("(", cx);
7701 });
7702 cx.assert_editor_state(
7703 &"
7704 fn main() {
7705 sample(ˇ)
7706 }
7707 "
7708 .unindent(),
7709 );
7710
7711 let mocked_response = lsp::SignatureHelp {
7712 signatures: vec![lsp::SignatureInformation {
7713 label: "fn sample(param1: u8, param2: u8)".to_string(),
7714 documentation: None,
7715 parameters: Some(vec![
7716 lsp::ParameterInformation {
7717 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7718 documentation: None,
7719 },
7720 lsp::ParameterInformation {
7721 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7722 documentation: None,
7723 },
7724 ]),
7725 active_parameter: None,
7726 }],
7727 active_signature: Some(0),
7728 active_parameter: Some(0),
7729 };
7730 handle_signature_help_request(&mut cx, mocked_response).await;
7731
7732 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7733 .await;
7734
7735 cx.editor(|editor, _| {
7736 let signature_help_state = editor.signature_help_state.popover().cloned();
7737 assert!(signature_help_state.is_some());
7738 let ParsedMarkdown {
7739 text, highlights, ..
7740 } = signature_help_state.unwrap().parsed_content;
7741 assert_eq!(text, "param1: u8, param2: u8");
7742 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7743 });
7744}
7745
7746#[gpui::test]
7747async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui::TestAppContext) {
7748 init_test(cx, |_| {});
7749
7750 cx.update(|cx| {
7751 cx.update_global::<SettingsStore, _>(|settings, cx| {
7752 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7753 settings.auto_signature_help = Some(false);
7754 settings.show_signature_help_after_edits = Some(false);
7755 });
7756 });
7757 });
7758
7759 let mut cx = EditorLspTestContext::new_rust(
7760 lsp::ServerCapabilities {
7761 signature_help_provider: Some(lsp::SignatureHelpOptions {
7762 ..Default::default()
7763 }),
7764 ..Default::default()
7765 },
7766 cx,
7767 )
7768 .await;
7769
7770 let language = Language::new(
7771 LanguageConfig {
7772 name: "Rust".into(),
7773 brackets: BracketPairConfig {
7774 pairs: vec![
7775 BracketPair {
7776 start: "{".to_string(),
7777 end: "}".to_string(),
7778 close: true,
7779 surround: true,
7780 newline: true,
7781 },
7782 BracketPair {
7783 start: "(".to_string(),
7784 end: ")".to_string(),
7785 close: true,
7786 surround: true,
7787 newline: true,
7788 },
7789 BracketPair {
7790 start: "/*".to_string(),
7791 end: " */".to_string(),
7792 close: true,
7793 surround: true,
7794 newline: true,
7795 },
7796 BracketPair {
7797 start: "[".to_string(),
7798 end: "]".to_string(),
7799 close: false,
7800 surround: false,
7801 newline: true,
7802 },
7803 BracketPair {
7804 start: "\"".to_string(),
7805 end: "\"".to_string(),
7806 close: true,
7807 surround: true,
7808 newline: false,
7809 },
7810 BracketPair {
7811 start: "<".to_string(),
7812 end: ">".to_string(),
7813 close: false,
7814 surround: true,
7815 newline: true,
7816 },
7817 ],
7818 ..Default::default()
7819 },
7820 autoclose_before: "})]".to_string(),
7821 ..Default::default()
7822 },
7823 Some(tree_sitter_rust::LANGUAGE.into()),
7824 );
7825 let language = Arc::new(language);
7826
7827 cx.language_registry().add(language.clone());
7828 cx.update_buffer(|buffer, cx| {
7829 buffer.set_language(Some(language), cx);
7830 });
7831
7832 // Ensure that signature_help is not called when no signature help is enabled.
7833 cx.set_state(
7834 &r#"
7835 fn main() {
7836 sampleˇ
7837 }
7838 "#
7839 .unindent(),
7840 );
7841 cx.update_editor(|view, cx| {
7842 view.handle_input("(", cx);
7843 });
7844 cx.assert_editor_state(
7845 &"
7846 fn main() {
7847 sample(ˇ)
7848 }
7849 "
7850 .unindent(),
7851 );
7852 cx.editor(|editor, _| {
7853 assert!(editor.signature_help_state.task().is_none());
7854 });
7855
7856 let mocked_response = lsp::SignatureHelp {
7857 signatures: vec![lsp::SignatureInformation {
7858 label: "fn sample(param1: u8, param2: u8)".to_string(),
7859 documentation: None,
7860 parameters: Some(vec![
7861 lsp::ParameterInformation {
7862 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7863 documentation: None,
7864 },
7865 lsp::ParameterInformation {
7866 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7867 documentation: None,
7868 },
7869 ]),
7870 active_parameter: None,
7871 }],
7872 active_signature: Some(0),
7873 active_parameter: Some(0),
7874 };
7875
7876 // Ensure that signature_help is called when enabled afte edits
7877 cx.update(|cx| {
7878 cx.update_global::<SettingsStore, _>(|settings, cx| {
7879 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7880 settings.auto_signature_help = Some(false);
7881 settings.show_signature_help_after_edits = Some(true);
7882 });
7883 });
7884 });
7885 cx.set_state(
7886 &r#"
7887 fn main() {
7888 sampleˇ
7889 }
7890 "#
7891 .unindent(),
7892 );
7893 cx.update_editor(|view, cx| {
7894 view.handle_input("(", cx);
7895 });
7896 cx.assert_editor_state(
7897 &"
7898 fn main() {
7899 sample(ˇ)
7900 }
7901 "
7902 .unindent(),
7903 );
7904 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7905 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7906 .await;
7907 cx.update_editor(|editor, _| {
7908 let signature_help_state = editor.signature_help_state.popover().cloned();
7909 assert!(signature_help_state.is_some());
7910 let ParsedMarkdown {
7911 text, highlights, ..
7912 } = signature_help_state.unwrap().parsed_content;
7913 assert_eq!(text, "param1: u8, param2: u8");
7914 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7915 editor.signature_help_state = SignatureHelpState::default();
7916 });
7917
7918 // Ensure that signature_help is called when auto signature help override is enabled
7919 cx.update(|cx| {
7920 cx.update_global::<SettingsStore, _>(|settings, cx| {
7921 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7922 settings.auto_signature_help = Some(true);
7923 settings.show_signature_help_after_edits = Some(false);
7924 });
7925 });
7926 });
7927 cx.set_state(
7928 &r#"
7929 fn main() {
7930 sampleˇ
7931 }
7932 "#
7933 .unindent(),
7934 );
7935 cx.update_editor(|view, cx| {
7936 view.handle_input("(", cx);
7937 });
7938 cx.assert_editor_state(
7939 &"
7940 fn main() {
7941 sample(ˇ)
7942 }
7943 "
7944 .unindent(),
7945 );
7946 handle_signature_help_request(&mut cx, mocked_response).await;
7947 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7948 .await;
7949 cx.editor(|editor, _| {
7950 let signature_help_state = editor.signature_help_state.popover().cloned();
7951 assert!(signature_help_state.is_some());
7952 let ParsedMarkdown {
7953 text, highlights, ..
7954 } = signature_help_state.unwrap().parsed_content;
7955 assert_eq!(text, "param1: u8, param2: u8");
7956 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7957 });
7958}
7959
7960#[gpui::test]
7961async fn test_signature_help(cx: &mut gpui::TestAppContext) {
7962 init_test(cx, |_| {});
7963 cx.update(|cx| {
7964 cx.update_global::<SettingsStore, _>(|settings, cx| {
7965 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7966 settings.auto_signature_help = Some(true);
7967 });
7968 });
7969 });
7970
7971 let mut cx = EditorLspTestContext::new_rust(
7972 lsp::ServerCapabilities {
7973 signature_help_provider: Some(lsp::SignatureHelpOptions {
7974 ..Default::default()
7975 }),
7976 ..Default::default()
7977 },
7978 cx,
7979 )
7980 .await;
7981
7982 // A test that directly calls `show_signature_help`
7983 cx.update_editor(|editor, cx| {
7984 editor.show_signature_help(&ShowSignatureHelp, cx);
7985 });
7986
7987 let mocked_response = lsp::SignatureHelp {
7988 signatures: vec![lsp::SignatureInformation {
7989 label: "fn sample(param1: u8, param2: u8)".to_string(),
7990 documentation: None,
7991 parameters: Some(vec![
7992 lsp::ParameterInformation {
7993 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7994 documentation: None,
7995 },
7996 lsp::ParameterInformation {
7997 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7998 documentation: None,
7999 },
8000 ]),
8001 active_parameter: None,
8002 }],
8003 active_signature: Some(0),
8004 active_parameter: Some(0),
8005 };
8006 handle_signature_help_request(&mut cx, mocked_response).await;
8007
8008 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8009 .await;
8010
8011 cx.editor(|editor, _| {
8012 let signature_help_state = editor.signature_help_state.popover().cloned();
8013 assert!(signature_help_state.is_some());
8014 let ParsedMarkdown {
8015 text, highlights, ..
8016 } = signature_help_state.unwrap().parsed_content;
8017 assert_eq!(text, "param1: u8, param2: u8");
8018 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
8019 });
8020
8021 // When exiting outside from inside the brackets, `signature_help` is closed.
8022 cx.set_state(indoc! {"
8023 fn main() {
8024 sample(ˇ);
8025 }
8026
8027 fn sample(param1: u8, param2: u8) {}
8028 "});
8029
8030 cx.update_editor(|editor, cx| {
8031 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
8032 });
8033
8034 let mocked_response = lsp::SignatureHelp {
8035 signatures: Vec::new(),
8036 active_signature: None,
8037 active_parameter: None,
8038 };
8039 handle_signature_help_request(&mut cx, mocked_response).await;
8040
8041 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8042 .await;
8043
8044 cx.editor(|editor, _| {
8045 assert!(!editor.signature_help_state.is_shown());
8046 });
8047
8048 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
8049 cx.set_state(indoc! {"
8050 fn main() {
8051 sample(ˇ);
8052 }
8053
8054 fn sample(param1: u8, param2: u8) {}
8055 "});
8056
8057 let mocked_response = lsp::SignatureHelp {
8058 signatures: vec![lsp::SignatureInformation {
8059 label: "fn sample(param1: u8, param2: u8)".to_string(),
8060 documentation: None,
8061 parameters: Some(vec![
8062 lsp::ParameterInformation {
8063 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8064 documentation: None,
8065 },
8066 lsp::ParameterInformation {
8067 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8068 documentation: None,
8069 },
8070 ]),
8071 active_parameter: None,
8072 }],
8073 active_signature: Some(0),
8074 active_parameter: Some(0),
8075 };
8076 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8077 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8078 .await;
8079 cx.editor(|editor, _| {
8080 assert!(editor.signature_help_state.is_shown());
8081 });
8082
8083 // Restore the popover with more parameter input
8084 cx.set_state(indoc! {"
8085 fn main() {
8086 sample(param1, param2ˇ);
8087 }
8088
8089 fn sample(param1: u8, param2: u8) {}
8090 "});
8091
8092 let mocked_response = lsp::SignatureHelp {
8093 signatures: vec![lsp::SignatureInformation {
8094 label: "fn sample(param1: u8, param2: u8)".to_string(),
8095 documentation: None,
8096 parameters: Some(vec![
8097 lsp::ParameterInformation {
8098 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8099 documentation: None,
8100 },
8101 lsp::ParameterInformation {
8102 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8103 documentation: None,
8104 },
8105 ]),
8106 active_parameter: None,
8107 }],
8108 active_signature: Some(0),
8109 active_parameter: Some(1),
8110 };
8111 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8112 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8113 .await;
8114
8115 // When selecting a range, the popover is gone.
8116 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
8117 cx.update_editor(|editor, cx| {
8118 editor.change_selections(None, cx, |s| {
8119 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8120 })
8121 });
8122 cx.assert_editor_state(indoc! {"
8123 fn main() {
8124 sample(param1, «ˇparam2»);
8125 }
8126
8127 fn sample(param1: u8, param2: u8) {}
8128 "});
8129 cx.editor(|editor, _| {
8130 assert!(!editor.signature_help_state.is_shown());
8131 });
8132
8133 // When unselecting again, the popover is back if within the brackets.
8134 cx.update_editor(|editor, cx| {
8135 editor.change_selections(None, cx, |s| {
8136 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8137 })
8138 });
8139 cx.assert_editor_state(indoc! {"
8140 fn main() {
8141 sample(param1, ˇparam2);
8142 }
8143
8144 fn sample(param1: u8, param2: u8) {}
8145 "});
8146 handle_signature_help_request(&mut cx, mocked_response).await;
8147 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8148 .await;
8149 cx.editor(|editor, _| {
8150 assert!(editor.signature_help_state.is_shown());
8151 });
8152
8153 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
8154 cx.update_editor(|editor, cx| {
8155 editor.change_selections(None, cx, |s| {
8156 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
8157 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8158 })
8159 });
8160 cx.assert_editor_state(indoc! {"
8161 fn main() {
8162 sample(param1, ˇparam2);
8163 }
8164
8165 fn sample(param1: u8, param2: u8) {}
8166 "});
8167
8168 let mocked_response = lsp::SignatureHelp {
8169 signatures: vec![lsp::SignatureInformation {
8170 label: "fn sample(param1: u8, param2: u8)".to_string(),
8171 documentation: None,
8172 parameters: Some(vec![
8173 lsp::ParameterInformation {
8174 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8175 documentation: None,
8176 },
8177 lsp::ParameterInformation {
8178 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8179 documentation: None,
8180 },
8181 ]),
8182 active_parameter: None,
8183 }],
8184 active_signature: Some(0),
8185 active_parameter: Some(1),
8186 };
8187 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8188 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8189 .await;
8190 cx.update_editor(|editor, cx| {
8191 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
8192 });
8193 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8194 .await;
8195 cx.update_editor(|editor, cx| {
8196 editor.change_selections(None, cx, |s| {
8197 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8198 })
8199 });
8200 cx.assert_editor_state(indoc! {"
8201 fn main() {
8202 sample(param1, «ˇparam2»);
8203 }
8204
8205 fn sample(param1: u8, param2: u8) {}
8206 "});
8207 cx.update_editor(|editor, cx| {
8208 editor.change_selections(None, cx, |s| {
8209 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8210 })
8211 });
8212 cx.assert_editor_state(indoc! {"
8213 fn main() {
8214 sample(param1, ˇparam2);
8215 }
8216
8217 fn sample(param1: u8, param2: u8) {}
8218 "});
8219 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
8220 .await;
8221}
8222
8223#[gpui::test]
8224async fn test_completion(cx: &mut gpui::TestAppContext) {
8225 init_test(cx, |_| {});
8226
8227 let mut cx = EditorLspTestContext::new_rust(
8228 lsp::ServerCapabilities {
8229 completion_provider: Some(lsp::CompletionOptions {
8230 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
8231 resolve_provider: Some(true),
8232 ..Default::default()
8233 }),
8234 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
8235 ..Default::default()
8236 },
8237 cx,
8238 )
8239 .await;
8240 let counter = Arc::new(AtomicUsize::new(0));
8241
8242 cx.set_state(indoc! {"
8243 oneˇ
8244 two
8245 three
8246 "});
8247 cx.simulate_keystroke(".");
8248 handle_completion_request(
8249 &mut cx,
8250 indoc! {"
8251 one.|<>
8252 two
8253 three
8254 "},
8255 vec!["first_completion", "second_completion"],
8256 counter.clone(),
8257 )
8258 .await;
8259 cx.condition(|editor, _| editor.context_menu_visible())
8260 .await;
8261 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8262
8263 let _handler = handle_signature_help_request(
8264 &mut cx,
8265 lsp::SignatureHelp {
8266 signatures: vec![lsp::SignatureInformation {
8267 label: "test signature".to_string(),
8268 documentation: None,
8269 parameters: Some(vec![lsp::ParameterInformation {
8270 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
8271 documentation: None,
8272 }]),
8273 active_parameter: None,
8274 }],
8275 active_signature: None,
8276 active_parameter: None,
8277 },
8278 );
8279 cx.update_editor(|editor, cx| {
8280 assert!(
8281 !editor.signature_help_state.is_shown(),
8282 "No signature help was called for"
8283 );
8284 editor.show_signature_help(&ShowSignatureHelp, cx);
8285 });
8286 cx.run_until_parked();
8287 cx.update_editor(|editor, _| {
8288 assert!(
8289 !editor.signature_help_state.is_shown(),
8290 "No signature help should be shown when completions menu is open"
8291 );
8292 });
8293
8294 let apply_additional_edits = cx.update_editor(|editor, cx| {
8295 editor.context_menu_next(&Default::default(), cx);
8296 editor
8297 .confirm_completion(&ConfirmCompletion::default(), cx)
8298 .unwrap()
8299 });
8300 cx.assert_editor_state(indoc! {"
8301 one.second_completionˇ
8302 two
8303 three
8304 "});
8305
8306 handle_resolve_completion_request(
8307 &mut cx,
8308 Some(vec![
8309 (
8310 //This overlaps with the primary completion edit which is
8311 //misbehavior from the LSP spec, test that we filter it out
8312 indoc! {"
8313 one.second_ˇcompletion
8314 two
8315 threeˇ
8316 "},
8317 "overlapping additional edit",
8318 ),
8319 (
8320 indoc! {"
8321 one.second_completion
8322 two
8323 threeˇ
8324 "},
8325 "\nadditional edit",
8326 ),
8327 ]),
8328 )
8329 .await;
8330 apply_additional_edits.await.unwrap();
8331 cx.assert_editor_state(indoc! {"
8332 one.second_completionˇ
8333 two
8334 three
8335 additional edit
8336 "});
8337
8338 cx.set_state(indoc! {"
8339 one.second_completion
8340 twoˇ
8341 threeˇ
8342 additional edit
8343 "});
8344 cx.simulate_keystroke(" ");
8345 assert!(cx.editor(|e, _| e.context_menu.borrow_mut().is_none()));
8346 cx.simulate_keystroke("s");
8347 assert!(cx.editor(|e, _| e.context_menu.borrow_mut().is_none()));
8348
8349 cx.assert_editor_state(indoc! {"
8350 one.second_completion
8351 two sˇ
8352 three sˇ
8353 additional edit
8354 "});
8355 handle_completion_request(
8356 &mut cx,
8357 indoc! {"
8358 one.second_completion
8359 two s
8360 three <s|>
8361 additional edit
8362 "},
8363 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8364 counter.clone(),
8365 )
8366 .await;
8367 cx.condition(|editor, _| editor.context_menu_visible())
8368 .await;
8369 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
8370
8371 cx.simulate_keystroke("i");
8372
8373 handle_completion_request(
8374 &mut cx,
8375 indoc! {"
8376 one.second_completion
8377 two si
8378 three <si|>
8379 additional edit
8380 "},
8381 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8382 counter.clone(),
8383 )
8384 .await;
8385 cx.condition(|editor, _| editor.context_menu_visible())
8386 .await;
8387 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
8388
8389 let apply_additional_edits = cx.update_editor(|editor, cx| {
8390 editor
8391 .confirm_completion(&ConfirmCompletion::default(), cx)
8392 .unwrap()
8393 });
8394 cx.assert_editor_state(indoc! {"
8395 one.second_completion
8396 two sixth_completionˇ
8397 three sixth_completionˇ
8398 additional edit
8399 "});
8400
8401 apply_additional_edits.await.unwrap();
8402
8403 update_test_language_settings(&mut cx, |settings| {
8404 settings.defaults.show_completions_on_input = Some(false);
8405 });
8406 cx.set_state("editorˇ");
8407 cx.simulate_keystroke(".");
8408 assert!(cx.editor(|e, _| e.context_menu.borrow_mut().is_none()));
8409 cx.simulate_keystroke("c");
8410 cx.simulate_keystroke("l");
8411 cx.simulate_keystroke("o");
8412 cx.assert_editor_state("editor.cloˇ");
8413 assert!(cx.editor(|e, _| e.context_menu.borrow_mut().is_none()));
8414 cx.update_editor(|editor, cx| {
8415 editor.show_completions(&ShowCompletions { trigger: None }, cx);
8416 });
8417 handle_completion_request(
8418 &mut cx,
8419 "editor.<clo|>",
8420 vec!["close", "clobber"],
8421 counter.clone(),
8422 )
8423 .await;
8424 cx.condition(|editor, _| editor.context_menu_visible())
8425 .await;
8426 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
8427
8428 let apply_additional_edits = cx.update_editor(|editor, cx| {
8429 editor
8430 .confirm_completion(&ConfirmCompletion::default(), cx)
8431 .unwrap()
8432 });
8433 cx.assert_editor_state("editor.closeˇ");
8434 handle_resolve_completion_request(&mut cx, None).await;
8435 apply_additional_edits.await.unwrap();
8436}
8437
8438#[gpui::test]
8439async fn test_completion_page_up_down_keys(cx: &mut gpui::TestAppContext) {
8440 init_test(cx, |_| {});
8441 let mut cx = EditorLspTestContext::new_rust(
8442 lsp::ServerCapabilities {
8443 completion_provider: Some(lsp::CompletionOptions {
8444 trigger_characters: Some(vec![".".to_string()]),
8445 ..Default::default()
8446 }),
8447 ..Default::default()
8448 },
8449 cx,
8450 )
8451 .await;
8452 cx.lsp
8453 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8454 Ok(Some(lsp::CompletionResponse::Array(vec![
8455 lsp::CompletionItem {
8456 label: "first".into(),
8457 ..Default::default()
8458 },
8459 lsp::CompletionItem {
8460 label: "last".into(),
8461 ..Default::default()
8462 },
8463 ])))
8464 });
8465 cx.set_state("variableˇ");
8466 cx.simulate_keystroke(".");
8467 cx.executor().run_until_parked();
8468
8469 cx.update_editor(|editor, _| {
8470 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
8471 {
8472 assert_eq!(completion_menu_entries(&menu.entries), &["first", "last"]);
8473 } else {
8474 panic!("expected completion menu to be open");
8475 }
8476 });
8477
8478 cx.update_editor(|editor, cx| {
8479 editor.move_page_down(&MovePageDown::default(), cx);
8480 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
8481 {
8482 assert!(
8483 menu.selected_item == 1,
8484 "expected PageDown to select the last item from the context menu"
8485 );
8486 } else {
8487 panic!("expected completion menu to stay open after PageDown");
8488 }
8489 });
8490
8491 cx.update_editor(|editor, cx| {
8492 editor.move_page_up(&MovePageUp::default(), cx);
8493 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
8494 {
8495 assert!(
8496 menu.selected_item == 0,
8497 "expected PageUp to select the first item from the context menu"
8498 );
8499 } else {
8500 panic!("expected completion menu to stay open after PageUp");
8501 }
8502 });
8503}
8504
8505#[gpui::test]
8506async fn test_completion_sort(cx: &mut gpui::TestAppContext) {
8507 init_test(cx, |_| {});
8508 let mut cx = EditorLspTestContext::new_rust(
8509 lsp::ServerCapabilities {
8510 completion_provider: Some(lsp::CompletionOptions {
8511 trigger_characters: Some(vec![".".to_string()]),
8512 ..Default::default()
8513 }),
8514 ..Default::default()
8515 },
8516 cx,
8517 )
8518 .await;
8519 cx.lsp
8520 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8521 Ok(Some(lsp::CompletionResponse::Array(vec![
8522 lsp::CompletionItem {
8523 label: "Range".into(),
8524 sort_text: Some("a".into()),
8525 ..Default::default()
8526 },
8527 lsp::CompletionItem {
8528 label: "r".into(),
8529 sort_text: Some("b".into()),
8530 ..Default::default()
8531 },
8532 lsp::CompletionItem {
8533 label: "ret".into(),
8534 sort_text: Some("c".into()),
8535 ..Default::default()
8536 },
8537 lsp::CompletionItem {
8538 label: "return".into(),
8539 sort_text: Some("d".into()),
8540 ..Default::default()
8541 },
8542 lsp::CompletionItem {
8543 label: "slice".into(),
8544 sort_text: Some("d".into()),
8545 ..Default::default()
8546 },
8547 ])))
8548 });
8549 cx.set_state("rˇ");
8550 cx.executor().run_until_parked();
8551 cx.update_editor(|editor, cx| {
8552 editor.show_completions(
8553 &ShowCompletions {
8554 trigger: Some("r".into()),
8555 },
8556 cx,
8557 );
8558 });
8559 cx.executor().run_until_parked();
8560
8561 cx.update_editor(|editor, _| {
8562 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
8563 {
8564 assert_eq!(
8565 completion_menu_entries(&menu.entries),
8566 &["r", "ret", "Range", "return"]
8567 );
8568 } else {
8569 panic!("expected completion menu to be open");
8570 }
8571 });
8572}
8573
8574#[gpui::test]
8575async fn test_no_duplicated_completion_requests(cx: &mut gpui::TestAppContext) {
8576 init_test(cx, |_| {});
8577
8578 let mut cx = EditorLspTestContext::new_rust(
8579 lsp::ServerCapabilities {
8580 completion_provider: Some(lsp::CompletionOptions {
8581 trigger_characters: Some(vec![".".to_string()]),
8582 resolve_provider: Some(true),
8583 ..Default::default()
8584 }),
8585 ..Default::default()
8586 },
8587 cx,
8588 )
8589 .await;
8590
8591 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
8592 cx.simulate_keystroke(".");
8593 let completion_item = lsp::CompletionItem {
8594 label: "Some".into(),
8595 kind: Some(lsp::CompletionItemKind::SNIPPET),
8596 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
8597 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
8598 kind: lsp::MarkupKind::Markdown,
8599 value: "```rust\nSome(2)\n```".to_string(),
8600 })),
8601 deprecated: Some(false),
8602 sort_text: Some("Some".to_string()),
8603 filter_text: Some("Some".to_string()),
8604 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
8605 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8606 range: lsp::Range {
8607 start: lsp::Position {
8608 line: 0,
8609 character: 22,
8610 },
8611 end: lsp::Position {
8612 line: 0,
8613 character: 22,
8614 },
8615 },
8616 new_text: "Some(2)".to_string(),
8617 })),
8618 additional_text_edits: Some(vec![lsp::TextEdit {
8619 range: lsp::Range {
8620 start: lsp::Position {
8621 line: 0,
8622 character: 20,
8623 },
8624 end: lsp::Position {
8625 line: 0,
8626 character: 22,
8627 },
8628 },
8629 new_text: "".to_string(),
8630 }]),
8631 ..Default::default()
8632 };
8633
8634 let closure_completion_item = completion_item.clone();
8635 let counter = Arc::new(AtomicUsize::new(0));
8636 let counter_clone = counter.clone();
8637 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
8638 let task_completion_item = closure_completion_item.clone();
8639 counter_clone.fetch_add(1, atomic::Ordering::Release);
8640 async move {
8641 Ok(Some(lsp::CompletionResponse::Array(vec![
8642 task_completion_item,
8643 ])))
8644 }
8645 });
8646
8647 cx.condition(|editor, _| editor.context_menu_visible())
8648 .await;
8649 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
8650 assert!(request.next().await.is_some());
8651 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8652
8653 cx.simulate_keystroke("S");
8654 cx.simulate_keystroke("o");
8655 cx.simulate_keystroke("m");
8656 cx.condition(|editor, _| editor.context_menu_visible())
8657 .await;
8658 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
8659 assert!(request.next().await.is_some());
8660 assert!(request.next().await.is_some());
8661 assert!(request.next().await.is_some());
8662 request.close();
8663 assert!(request.next().await.is_none());
8664 assert_eq!(
8665 counter.load(atomic::Ordering::Acquire),
8666 4,
8667 "With the completions menu open, only one LSP request should happen per input"
8668 );
8669}
8670
8671#[gpui::test]
8672async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
8673 init_test(cx, |_| {});
8674 let mut cx = EditorTestContext::new(cx).await;
8675 let language = Arc::new(Language::new(
8676 LanguageConfig {
8677 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
8678 ..Default::default()
8679 },
8680 Some(tree_sitter_rust::LANGUAGE.into()),
8681 ));
8682 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
8683
8684 // If multiple selections intersect a line, the line is only toggled once.
8685 cx.set_state(indoc! {"
8686 fn a() {
8687 «//b();
8688 ˇ»// «c();
8689 //ˇ» d();
8690 }
8691 "});
8692
8693 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8694
8695 cx.assert_editor_state(indoc! {"
8696 fn a() {
8697 «b();
8698 c();
8699 ˇ» d();
8700 }
8701 "});
8702
8703 // The comment prefix is inserted at the same column for every line in a
8704 // selection.
8705 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8706
8707 cx.assert_editor_state(indoc! {"
8708 fn a() {
8709 // «b();
8710 // c();
8711 ˇ»// d();
8712 }
8713 "});
8714
8715 // If a selection ends at the beginning of a line, that line is not toggled.
8716 cx.set_selections_state(indoc! {"
8717 fn a() {
8718 // b();
8719 «// c();
8720 ˇ» // d();
8721 }
8722 "});
8723
8724 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8725
8726 cx.assert_editor_state(indoc! {"
8727 fn a() {
8728 // b();
8729 «c();
8730 ˇ» // d();
8731 }
8732 "});
8733
8734 // If a selection span a single line and is empty, the line is toggled.
8735 cx.set_state(indoc! {"
8736 fn a() {
8737 a();
8738 b();
8739 ˇ
8740 }
8741 "});
8742
8743 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8744
8745 cx.assert_editor_state(indoc! {"
8746 fn a() {
8747 a();
8748 b();
8749 //•ˇ
8750 }
8751 "});
8752
8753 // If a selection span multiple lines, empty lines are not toggled.
8754 cx.set_state(indoc! {"
8755 fn a() {
8756 «a();
8757
8758 c();ˇ»
8759 }
8760 "});
8761
8762 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8763
8764 cx.assert_editor_state(indoc! {"
8765 fn a() {
8766 // «a();
8767
8768 // c();ˇ»
8769 }
8770 "});
8771
8772 // If a selection includes multiple comment prefixes, all lines are uncommented.
8773 cx.set_state(indoc! {"
8774 fn a() {
8775 «// a();
8776 /// b();
8777 //! c();ˇ»
8778 }
8779 "});
8780
8781 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8782
8783 cx.assert_editor_state(indoc! {"
8784 fn a() {
8785 «a();
8786 b();
8787 c();ˇ»
8788 }
8789 "});
8790}
8791
8792#[gpui::test]
8793async fn test_toggle_comment_ignore_indent(cx: &mut gpui::TestAppContext) {
8794 init_test(cx, |_| {});
8795 let mut cx = EditorTestContext::new(cx).await;
8796 let language = Arc::new(Language::new(
8797 LanguageConfig {
8798 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
8799 ..Default::default()
8800 },
8801 Some(tree_sitter_rust::LANGUAGE.into()),
8802 ));
8803 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
8804
8805 let toggle_comments = &ToggleComments {
8806 advance_downwards: false,
8807 ignore_indent: true,
8808 };
8809
8810 // If multiple selections intersect a line, the line is only toggled once.
8811 cx.set_state(indoc! {"
8812 fn a() {
8813 // «b();
8814 // c();
8815 // ˇ» d();
8816 }
8817 "});
8818
8819 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8820
8821 cx.assert_editor_state(indoc! {"
8822 fn a() {
8823 «b();
8824 c();
8825 ˇ» d();
8826 }
8827 "});
8828
8829 // The comment prefix is inserted at the beginning of each line
8830 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8831
8832 cx.assert_editor_state(indoc! {"
8833 fn a() {
8834 // «b();
8835 // c();
8836 // ˇ» d();
8837 }
8838 "});
8839
8840 // If a selection ends at the beginning of a line, that line is not toggled.
8841 cx.set_selections_state(indoc! {"
8842 fn a() {
8843 // b();
8844 // «c();
8845 ˇ»// d();
8846 }
8847 "});
8848
8849 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8850
8851 cx.assert_editor_state(indoc! {"
8852 fn a() {
8853 // b();
8854 «c();
8855 ˇ»// d();
8856 }
8857 "});
8858
8859 // If a selection span a single line and is empty, the line is toggled.
8860 cx.set_state(indoc! {"
8861 fn a() {
8862 a();
8863 b();
8864 ˇ
8865 }
8866 "});
8867
8868 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8869
8870 cx.assert_editor_state(indoc! {"
8871 fn a() {
8872 a();
8873 b();
8874 //ˇ
8875 }
8876 "});
8877
8878 // If a selection span multiple lines, empty lines are not toggled.
8879 cx.set_state(indoc! {"
8880 fn a() {
8881 «a();
8882
8883 c();ˇ»
8884 }
8885 "});
8886
8887 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8888
8889 cx.assert_editor_state(indoc! {"
8890 fn a() {
8891 // «a();
8892
8893 // c();ˇ»
8894 }
8895 "});
8896
8897 // If a selection includes multiple comment prefixes, all lines are uncommented.
8898 cx.set_state(indoc! {"
8899 fn a() {
8900 // «a();
8901 /// b();
8902 //! c();ˇ»
8903 }
8904 "});
8905
8906 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8907
8908 cx.assert_editor_state(indoc! {"
8909 fn a() {
8910 «a();
8911 b();
8912 c();ˇ»
8913 }
8914 "});
8915}
8916
8917#[gpui::test]
8918async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
8919 init_test(cx, |_| {});
8920
8921 let language = Arc::new(Language::new(
8922 LanguageConfig {
8923 line_comments: vec!["// ".into()],
8924 ..Default::default()
8925 },
8926 Some(tree_sitter_rust::LANGUAGE.into()),
8927 ));
8928
8929 let mut cx = EditorTestContext::new(cx).await;
8930
8931 cx.language_registry().add(language.clone());
8932 cx.update_buffer(|buffer, cx| {
8933 buffer.set_language(Some(language), cx);
8934 });
8935
8936 let toggle_comments = &ToggleComments {
8937 advance_downwards: true,
8938 ignore_indent: false,
8939 };
8940
8941 // Single cursor on one line -> advance
8942 // Cursor moves horizontally 3 characters as well on non-blank line
8943 cx.set_state(indoc!(
8944 "fn a() {
8945 ˇdog();
8946 cat();
8947 }"
8948 ));
8949 cx.update_editor(|editor, cx| {
8950 editor.toggle_comments(toggle_comments, cx);
8951 });
8952 cx.assert_editor_state(indoc!(
8953 "fn a() {
8954 // dog();
8955 catˇ();
8956 }"
8957 ));
8958
8959 // Single selection on one line -> don't advance
8960 cx.set_state(indoc!(
8961 "fn a() {
8962 «dog()ˇ»;
8963 cat();
8964 }"
8965 ));
8966 cx.update_editor(|editor, cx| {
8967 editor.toggle_comments(toggle_comments, cx);
8968 });
8969 cx.assert_editor_state(indoc!(
8970 "fn a() {
8971 // «dog()ˇ»;
8972 cat();
8973 }"
8974 ));
8975
8976 // Multiple cursors on one line -> advance
8977 cx.set_state(indoc!(
8978 "fn a() {
8979 ˇdˇog();
8980 cat();
8981 }"
8982 ));
8983 cx.update_editor(|editor, cx| {
8984 editor.toggle_comments(toggle_comments, cx);
8985 });
8986 cx.assert_editor_state(indoc!(
8987 "fn a() {
8988 // dog();
8989 catˇ(ˇ);
8990 }"
8991 ));
8992
8993 // Multiple cursors on one line, with selection -> don't advance
8994 cx.set_state(indoc!(
8995 "fn a() {
8996 ˇdˇog«()ˇ»;
8997 cat();
8998 }"
8999 ));
9000 cx.update_editor(|editor, cx| {
9001 editor.toggle_comments(toggle_comments, cx);
9002 });
9003 cx.assert_editor_state(indoc!(
9004 "fn a() {
9005 // ˇdˇog«()ˇ»;
9006 cat();
9007 }"
9008 ));
9009
9010 // Single cursor on one line -> advance
9011 // Cursor moves to column 0 on blank line
9012 cx.set_state(indoc!(
9013 "fn a() {
9014 ˇdog();
9015
9016 cat();
9017 }"
9018 ));
9019 cx.update_editor(|editor, cx| {
9020 editor.toggle_comments(toggle_comments, cx);
9021 });
9022 cx.assert_editor_state(indoc!(
9023 "fn a() {
9024 // dog();
9025 ˇ
9026 cat();
9027 }"
9028 ));
9029
9030 // Single cursor on one line -> advance
9031 // Cursor starts and ends at column 0
9032 cx.set_state(indoc!(
9033 "fn a() {
9034 ˇ dog();
9035 cat();
9036 }"
9037 ));
9038 cx.update_editor(|editor, cx| {
9039 editor.toggle_comments(toggle_comments, cx);
9040 });
9041 cx.assert_editor_state(indoc!(
9042 "fn a() {
9043 // dog();
9044 ˇ cat();
9045 }"
9046 ));
9047}
9048
9049#[gpui::test]
9050async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
9051 init_test(cx, |_| {});
9052
9053 let mut cx = EditorTestContext::new(cx).await;
9054
9055 let html_language = Arc::new(
9056 Language::new(
9057 LanguageConfig {
9058 name: "HTML".into(),
9059 block_comment: Some(("<!-- ".into(), " -->".into())),
9060 ..Default::default()
9061 },
9062 Some(tree_sitter_html::language()),
9063 )
9064 .with_injection_query(
9065 r#"
9066 (script_element
9067 (raw_text) @content
9068 (#set! "language" "javascript"))
9069 "#,
9070 )
9071 .unwrap(),
9072 );
9073
9074 let javascript_language = Arc::new(Language::new(
9075 LanguageConfig {
9076 name: "JavaScript".into(),
9077 line_comments: vec!["// ".into()],
9078 ..Default::default()
9079 },
9080 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
9081 ));
9082
9083 cx.language_registry().add(html_language.clone());
9084 cx.language_registry().add(javascript_language.clone());
9085 cx.update_buffer(|buffer, cx| {
9086 buffer.set_language(Some(html_language), cx);
9087 });
9088
9089 // Toggle comments for empty selections
9090 cx.set_state(
9091 &r#"
9092 <p>A</p>ˇ
9093 <p>B</p>ˇ
9094 <p>C</p>ˇ
9095 "#
9096 .unindent(),
9097 );
9098 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9099 cx.assert_editor_state(
9100 &r#"
9101 <!-- <p>A</p>ˇ -->
9102 <!-- <p>B</p>ˇ -->
9103 <!-- <p>C</p>ˇ -->
9104 "#
9105 .unindent(),
9106 );
9107 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9108 cx.assert_editor_state(
9109 &r#"
9110 <p>A</p>ˇ
9111 <p>B</p>ˇ
9112 <p>C</p>ˇ
9113 "#
9114 .unindent(),
9115 );
9116
9117 // Toggle comments for mixture of empty and non-empty selections, where
9118 // multiple selections occupy a given line.
9119 cx.set_state(
9120 &r#"
9121 <p>A«</p>
9122 <p>ˇ»B</p>ˇ
9123 <p>C«</p>
9124 <p>ˇ»D</p>ˇ
9125 "#
9126 .unindent(),
9127 );
9128
9129 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9130 cx.assert_editor_state(
9131 &r#"
9132 <!-- <p>A«</p>
9133 <p>ˇ»B</p>ˇ -->
9134 <!-- <p>C«</p>
9135 <p>ˇ»D</p>ˇ -->
9136 "#
9137 .unindent(),
9138 );
9139 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9140 cx.assert_editor_state(
9141 &r#"
9142 <p>A«</p>
9143 <p>ˇ»B</p>ˇ
9144 <p>C«</p>
9145 <p>ˇ»D</p>ˇ
9146 "#
9147 .unindent(),
9148 );
9149
9150 // Toggle comments when different languages are active for different
9151 // selections.
9152 cx.set_state(
9153 &r#"
9154 ˇ<script>
9155 ˇvar x = new Y();
9156 ˇ</script>
9157 "#
9158 .unindent(),
9159 );
9160 cx.executor().run_until_parked();
9161 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9162 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
9163 // Uncommenting and commenting from this position brings in even more wrong artifacts.
9164 cx.assert_editor_state(
9165 &r#"
9166 <!-- ˇ<script> -->
9167 // ˇvar x = new Y();
9168 // ˇ</script>
9169 "#
9170 .unindent(),
9171 );
9172}
9173
9174#[gpui::test]
9175fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
9176 init_test(cx, |_| {});
9177
9178 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9179 let multibuffer = cx.new_model(|cx| {
9180 let mut multibuffer = MultiBuffer::new(ReadWrite);
9181 multibuffer.push_excerpts(
9182 buffer.clone(),
9183 [
9184 ExcerptRange {
9185 context: Point::new(0, 0)..Point::new(0, 4),
9186 primary: None,
9187 },
9188 ExcerptRange {
9189 context: Point::new(1, 0)..Point::new(1, 4),
9190 primary: None,
9191 },
9192 ],
9193 cx,
9194 );
9195 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
9196 multibuffer
9197 });
9198
9199 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
9200 view.update(cx, |view, cx| {
9201 assert_eq!(view.text(cx), "aaaa\nbbbb");
9202 view.change_selections(None, cx, |s| {
9203 s.select_ranges([
9204 Point::new(0, 0)..Point::new(0, 0),
9205 Point::new(1, 0)..Point::new(1, 0),
9206 ])
9207 });
9208
9209 view.handle_input("X", cx);
9210 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
9211 assert_eq!(
9212 view.selections.ranges(cx),
9213 [
9214 Point::new(0, 1)..Point::new(0, 1),
9215 Point::new(1, 1)..Point::new(1, 1),
9216 ]
9217 );
9218
9219 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
9220 view.change_selections(None, cx, |s| {
9221 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
9222 });
9223 view.backspace(&Default::default(), cx);
9224 assert_eq!(view.text(cx), "Xa\nbbb");
9225 assert_eq!(
9226 view.selections.ranges(cx),
9227 [Point::new(1, 0)..Point::new(1, 0)]
9228 );
9229
9230 view.change_selections(None, cx, |s| {
9231 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
9232 });
9233 view.backspace(&Default::default(), cx);
9234 assert_eq!(view.text(cx), "X\nbb");
9235 assert_eq!(
9236 view.selections.ranges(cx),
9237 [Point::new(0, 1)..Point::new(0, 1)]
9238 );
9239 });
9240}
9241
9242#[gpui::test]
9243fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
9244 init_test(cx, |_| {});
9245
9246 let markers = vec![('[', ']').into(), ('(', ')').into()];
9247 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
9248 indoc! {"
9249 [aaaa
9250 (bbbb]
9251 cccc)",
9252 },
9253 markers.clone(),
9254 );
9255 let excerpt_ranges = markers.into_iter().map(|marker| {
9256 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
9257 ExcerptRange {
9258 context,
9259 primary: None,
9260 }
9261 });
9262 let buffer = cx.new_model(|cx| Buffer::local(initial_text, cx));
9263 let multibuffer = cx.new_model(|cx| {
9264 let mut multibuffer = MultiBuffer::new(ReadWrite);
9265 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
9266 multibuffer
9267 });
9268
9269 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
9270 view.update(cx, |view, cx| {
9271 let (expected_text, selection_ranges) = marked_text_ranges(
9272 indoc! {"
9273 aaaa
9274 bˇbbb
9275 bˇbbˇb
9276 cccc"
9277 },
9278 true,
9279 );
9280 assert_eq!(view.text(cx), expected_text);
9281 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
9282
9283 view.handle_input("X", cx);
9284
9285 let (expected_text, expected_selections) = marked_text_ranges(
9286 indoc! {"
9287 aaaa
9288 bXˇbbXb
9289 bXˇbbXˇb
9290 cccc"
9291 },
9292 false,
9293 );
9294 assert_eq!(view.text(cx), expected_text);
9295 assert_eq!(view.selections.ranges(cx), expected_selections);
9296
9297 view.newline(&Newline, cx);
9298 let (expected_text, expected_selections) = marked_text_ranges(
9299 indoc! {"
9300 aaaa
9301 bX
9302 ˇbbX
9303 b
9304 bX
9305 ˇbbX
9306 ˇb
9307 cccc"
9308 },
9309 false,
9310 );
9311 assert_eq!(view.text(cx), expected_text);
9312 assert_eq!(view.selections.ranges(cx), expected_selections);
9313 });
9314}
9315
9316#[gpui::test]
9317fn test_refresh_selections(cx: &mut TestAppContext) {
9318 init_test(cx, |_| {});
9319
9320 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9321 let mut excerpt1_id = None;
9322 let multibuffer = cx.new_model(|cx| {
9323 let mut multibuffer = MultiBuffer::new(ReadWrite);
9324 excerpt1_id = multibuffer
9325 .push_excerpts(
9326 buffer.clone(),
9327 [
9328 ExcerptRange {
9329 context: Point::new(0, 0)..Point::new(1, 4),
9330 primary: None,
9331 },
9332 ExcerptRange {
9333 context: Point::new(1, 0)..Point::new(2, 4),
9334 primary: None,
9335 },
9336 ],
9337 cx,
9338 )
9339 .into_iter()
9340 .next();
9341 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
9342 multibuffer
9343 });
9344
9345 let editor = cx.add_window(|cx| {
9346 let mut editor = build_editor(multibuffer.clone(), cx);
9347 let snapshot = editor.snapshot(cx);
9348 editor.change_selections(None, cx, |s| {
9349 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
9350 });
9351 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
9352 assert_eq!(
9353 editor.selections.ranges(cx),
9354 [
9355 Point::new(1, 3)..Point::new(1, 3),
9356 Point::new(2, 1)..Point::new(2, 1),
9357 ]
9358 );
9359 editor
9360 });
9361
9362 // Refreshing selections is a no-op when excerpts haven't changed.
9363 _ = editor.update(cx, |editor, cx| {
9364 editor.change_selections(None, cx, |s| s.refresh());
9365 assert_eq!(
9366 editor.selections.ranges(cx),
9367 [
9368 Point::new(1, 3)..Point::new(1, 3),
9369 Point::new(2, 1)..Point::new(2, 1),
9370 ]
9371 );
9372 });
9373
9374 multibuffer.update(cx, |multibuffer, cx| {
9375 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
9376 });
9377 _ = editor.update(cx, |editor, cx| {
9378 // Removing an excerpt causes the first selection to become degenerate.
9379 assert_eq!(
9380 editor.selections.ranges(cx),
9381 [
9382 Point::new(0, 0)..Point::new(0, 0),
9383 Point::new(0, 1)..Point::new(0, 1)
9384 ]
9385 );
9386
9387 // Refreshing selections will relocate the first selection to the original buffer
9388 // location.
9389 editor.change_selections(None, cx, |s| s.refresh());
9390 assert_eq!(
9391 editor.selections.ranges(cx),
9392 [
9393 Point::new(0, 1)..Point::new(0, 1),
9394 Point::new(0, 3)..Point::new(0, 3)
9395 ]
9396 );
9397 assert!(editor.selections.pending_anchor().is_some());
9398 });
9399}
9400
9401#[gpui::test]
9402fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
9403 init_test(cx, |_| {});
9404
9405 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9406 let mut excerpt1_id = None;
9407 let multibuffer = cx.new_model(|cx| {
9408 let mut multibuffer = MultiBuffer::new(ReadWrite);
9409 excerpt1_id = multibuffer
9410 .push_excerpts(
9411 buffer.clone(),
9412 [
9413 ExcerptRange {
9414 context: Point::new(0, 0)..Point::new(1, 4),
9415 primary: None,
9416 },
9417 ExcerptRange {
9418 context: Point::new(1, 0)..Point::new(2, 4),
9419 primary: None,
9420 },
9421 ],
9422 cx,
9423 )
9424 .into_iter()
9425 .next();
9426 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
9427 multibuffer
9428 });
9429
9430 let editor = cx.add_window(|cx| {
9431 let mut editor = build_editor(multibuffer.clone(), cx);
9432 let snapshot = editor.snapshot(cx);
9433 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
9434 assert_eq!(
9435 editor.selections.ranges(cx),
9436 [Point::new(1, 3)..Point::new(1, 3)]
9437 );
9438 editor
9439 });
9440
9441 multibuffer.update(cx, |multibuffer, cx| {
9442 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
9443 });
9444 _ = editor.update(cx, |editor, cx| {
9445 assert_eq!(
9446 editor.selections.ranges(cx),
9447 [Point::new(0, 0)..Point::new(0, 0)]
9448 );
9449
9450 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
9451 editor.change_selections(None, cx, |s| s.refresh());
9452 assert_eq!(
9453 editor.selections.ranges(cx),
9454 [Point::new(0, 3)..Point::new(0, 3)]
9455 );
9456 assert!(editor.selections.pending_anchor().is_some());
9457 });
9458}
9459
9460#[gpui::test]
9461async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
9462 init_test(cx, |_| {});
9463
9464 let language = Arc::new(
9465 Language::new(
9466 LanguageConfig {
9467 brackets: BracketPairConfig {
9468 pairs: vec![
9469 BracketPair {
9470 start: "{".to_string(),
9471 end: "}".to_string(),
9472 close: true,
9473 surround: true,
9474 newline: true,
9475 },
9476 BracketPair {
9477 start: "/* ".to_string(),
9478 end: " */".to_string(),
9479 close: true,
9480 surround: true,
9481 newline: true,
9482 },
9483 ],
9484 ..Default::default()
9485 },
9486 ..Default::default()
9487 },
9488 Some(tree_sitter_rust::LANGUAGE.into()),
9489 )
9490 .with_indents_query("")
9491 .unwrap(),
9492 );
9493
9494 let text = concat!(
9495 "{ }\n", //
9496 " x\n", //
9497 " /* */\n", //
9498 "x\n", //
9499 "{{} }\n", //
9500 );
9501
9502 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
9503 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
9504 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
9505 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
9506 .await;
9507
9508 view.update(cx, |view, cx| {
9509 view.change_selections(None, cx, |s| {
9510 s.select_display_ranges([
9511 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
9512 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
9513 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
9514 ])
9515 });
9516 view.newline(&Newline, cx);
9517
9518 assert_eq!(
9519 view.buffer().read(cx).read(cx).text(),
9520 concat!(
9521 "{ \n", // Suppress rustfmt
9522 "\n", //
9523 "}\n", //
9524 " x\n", //
9525 " /* \n", //
9526 " \n", //
9527 " */\n", //
9528 "x\n", //
9529 "{{} \n", //
9530 "}\n", //
9531 )
9532 );
9533 });
9534}
9535
9536#[gpui::test]
9537fn test_highlighted_ranges(cx: &mut TestAppContext) {
9538 init_test(cx, |_| {});
9539
9540 let editor = cx.add_window(|cx| {
9541 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
9542 build_editor(buffer.clone(), cx)
9543 });
9544
9545 _ = editor.update(cx, |editor, cx| {
9546 struct Type1;
9547 struct Type2;
9548
9549 let buffer = editor.buffer.read(cx).snapshot(cx);
9550
9551 let anchor_range =
9552 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
9553
9554 editor.highlight_background::<Type1>(
9555 &[
9556 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
9557 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
9558 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
9559 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
9560 ],
9561 |_| Hsla::red(),
9562 cx,
9563 );
9564 editor.highlight_background::<Type2>(
9565 &[
9566 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
9567 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
9568 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
9569 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
9570 ],
9571 |_| Hsla::green(),
9572 cx,
9573 );
9574
9575 let snapshot = editor.snapshot(cx);
9576 let mut highlighted_ranges = editor.background_highlights_in_range(
9577 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
9578 &snapshot,
9579 cx.theme().colors(),
9580 );
9581 // Enforce a consistent ordering based on color without relying on the ordering of the
9582 // highlight's `TypeId` which is non-executor.
9583 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
9584 assert_eq!(
9585 highlighted_ranges,
9586 &[
9587 (
9588 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
9589 Hsla::red(),
9590 ),
9591 (
9592 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9593 Hsla::red(),
9594 ),
9595 (
9596 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
9597 Hsla::green(),
9598 ),
9599 (
9600 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
9601 Hsla::green(),
9602 ),
9603 ]
9604 );
9605 assert_eq!(
9606 editor.background_highlights_in_range(
9607 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
9608 &snapshot,
9609 cx.theme().colors(),
9610 ),
9611 &[(
9612 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9613 Hsla::red(),
9614 )]
9615 );
9616 });
9617}
9618
9619#[gpui::test]
9620async fn test_following(cx: &mut gpui::TestAppContext) {
9621 init_test(cx, |_| {});
9622
9623 let fs = FakeFs::new(cx.executor());
9624 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9625
9626 let buffer = project.update(cx, |project, cx| {
9627 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
9628 cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
9629 });
9630 let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
9631 let follower = cx.update(|cx| {
9632 cx.open_window(
9633 WindowOptions {
9634 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
9635 gpui::Point::new(px(0.), px(0.)),
9636 gpui::Point::new(px(10.), px(80.)),
9637 ))),
9638 ..Default::default()
9639 },
9640 |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
9641 )
9642 .unwrap()
9643 });
9644
9645 let is_still_following = Rc::new(RefCell::new(true));
9646 let follower_edit_event_count = Rc::new(RefCell::new(0));
9647 let pending_update = Rc::new(RefCell::new(None));
9648 _ = follower.update(cx, {
9649 let update = pending_update.clone();
9650 let is_still_following = is_still_following.clone();
9651 let follower_edit_event_count = follower_edit_event_count.clone();
9652 |_, cx| {
9653 cx.subscribe(
9654 &leader.root_view(cx).unwrap(),
9655 move |_, leader, event, cx| {
9656 leader
9657 .read(cx)
9658 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9659 },
9660 )
9661 .detach();
9662
9663 cx.subscribe(
9664 &follower.root_view(cx).unwrap(),
9665 move |_, _, event: &EditorEvent, _cx| {
9666 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
9667 *is_still_following.borrow_mut() = false;
9668 }
9669
9670 if let EditorEvent::BufferEdited = event {
9671 *follower_edit_event_count.borrow_mut() += 1;
9672 }
9673 },
9674 )
9675 .detach();
9676 }
9677 });
9678
9679 // Update the selections only
9680 _ = leader.update(cx, |leader, cx| {
9681 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9682 });
9683 follower
9684 .update(cx, |follower, cx| {
9685 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9686 })
9687 .unwrap()
9688 .await
9689 .unwrap();
9690 _ = follower.update(cx, |follower, cx| {
9691 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
9692 });
9693 assert!(*is_still_following.borrow());
9694 assert_eq!(*follower_edit_event_count.borrow(), 0);
9695
9696 // Update the scroll position only
9697 _ = leader.update(cx, |leader, cx| {
9698 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9699 });
9700 follower
9701 .update(cx, |follower, cx| {
9702 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9703 })
9704 .unwrap()
9705 .await
9706 .unwrap();
9707 assert_eq!(
9708 follower
9709 .update(cx, |follower, cx| follower.scroll_position(cx))
9710 .unwrap(),
9711 gpui::Point::new(1.5, 3.5)
9712 );
9713 assert!(*is_still_following.borrow());
9714 assert_eq!(*follower_edit_event_count.borrow(), 0);
9715
9716 // Update the selections and scroll position. The follower's scroll position is updated
9717 // via autoscroll, not via the leader's exact scroll position.
9718 _ = leader.update(cx, |leader, cx| {
9719 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
9720 leader.request_autoscroll(Autoscroll::newest(), cx);
9721 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9722 });
9723 follower
9724 .update(cx, |follower, cx| {
9725 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9726 })
9727 .unwrap()
9728 .await
9729 .unwrap();
9730 _ = follower.update(cx, |follower, cx| {
9731 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
9732 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
9733 });
9734 assert!(*is_still_following.borrow());
9735
9736 // Creating a pending selection that precedes another selection
9737 _ = leader.update(cx, |leader, cx| {
9738 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9739 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, cx);
9740 });
9741 follower
9742 .update(cx, |follower, cx| {
9743 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9744 })
9745 .unwrap()
9746 .await
9747 .unwrap();
9748 _ = follower.update(cx, |follower, cx| {
9749 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
9750 });
9751 assert!(*is_still_following.borrow());
9752
9753 // Extend the pending selection so that it surrounds another selection
9754 _ = leader.update(cx, |leader, cx| {
9755 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, cx);
9756 });
9757 follower
9758 .update(cx, |follower, cx| {
9759 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9760 })
9761 .unwrap()
9762 .await
9763 .unwrap();
9764 _ = follower.update(cx, |follower, cx| {
9765 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
9766 });
9767
9768 // Scrolling locally breaks the follow
9769 _ = follower.update(cx, |follower, cx| {
9770 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
9771 follower.set_scroll_anchor(
9772 ScrollAnchor {
9773 anchor: top_anchor,
9774 offset: gpui::Point::new(0.0, 0.5),
9775 },
9776 cx,
9777 );
9778 });
9779 assert!(!(*is_still_following.borrow()));
9780}
9781
9782#[gpui::test]
9783async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
9784 init_test(cx, |_| {});
9785
9786 let fs = FakeFs::new(cx.executor());
9787 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9788 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9789 let pane = workspace
9790 .update(cx, |workspace, _| workspace.active_pane().clone())
9791 .unwrap();
9792
9793 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
9794
9795 let leader = pane.update(cx, |_, cx| {
9796 let multibuffer = cx.new_model(|_| MultiBuffer::new(ReadWrite));
9797 cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
9798 });
9799
9800 // Start following the editor when it has no excerpts.
9801 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9802 let follower_1 = cx
9803 .update_window(*workspace.deref(), |_, cx| {
9804 Editor::from_state_proto(
9805 workspace.root_view(cx).unwrap(),
9806 ViewId {
9807 creator: Default::default(),
9808 id: 0,
9809 },
9810 &mut state_message,
9811 cx,
9812 )
9813 })
9814 .unwrap()
9815 .unwrap()
9816 .await
9817 .unwrap();
9818
9819 let update_message = Rc::new(RefCell::new(None));
9820 follower_1.update(cx, {
9821 let update = update_message.clone();
9822 |_, cx| {
9823 cx.subscribe(&leader, move |_, leader, event, cx| {
9824 leader
9825 .read(cx)
9826 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9827 })
9828 .detach();
9829 }
9830 });
9831
9832 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
9833 (
9834 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
9835 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
9836 )
9837 });
9838
9839 // Insert some excerpts.
9840 leader.update(cx, |leader, cx| {
9841 leader.buffer.update(cx, |multibuffer, cx| {
9842 let excerpt_ids = multibuffer.push_excerpts(
9843 buffer_1.clone(),
9844 [
9845 ExcerptRange {
9846 context: 1..6,
9847 primary: None,
9848 },
9849 ExcerptRange {
9850 context: 12..15,
9851 primary: None,
9852 },
9853 ExcerptRange {
9854 context: 0..3,
9855 primary: None,
9856 },
9857 ],
9858 cx,
9859 );
9860 multibuffer.insert_excerpts_after(
9861 excerpt_ids[0],
9862 buffer_2.clone(),
9863 [
9864 ExcerptRange {
9865 context: 8..12,
9866 primary: None,
9867 },
9868 ExcerptRange {
9869 context: 0..6,
9870 primary: None,
9871 },
9872 ],
9873 cx,
9874 );
9875 });
9876 });
9877
9878 // Apply the update of adding the excerpts.
9879 follower_1
9880 .update(cx, |follower, cx| {
9881 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9882 })
9883 .await
9884 .unwrap();
9885 assert_eq!(
9886 follower_1.update(cx, |editor, cx| editor.text(cx)),
9887 leader.update(cx, |editor, cx| editor.text(cx))
9888 );
9889 update_message.borrow_mut().take();
9890
9891 // Start following separately after it already has excerpts.
9892 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9893 let follower_2 = cx
9894 .update_window(*workspace.deref(), |_, cx| {
9895 Editor::from_state_proto(
9896 workspace.root_view(cx).unwrap().clone(),
9897 ViewId {
9898 creator: Default::default(),
9899 id: 0,
9900 },
9901 &mut state_message,
9902 cx,
9903 )
9904 })
9905 .unwrap()
9906 .unwrap()
9907 .await
9908 .unwrap();
9909 assert_eq!(
9910 follower_2.update(cx, |editor, cx| editor.text(cx)),
9911 leader.update(cx, |editor, cx| editor.text(cx))
9912 );
9913
9914 // Remove some excerpts.
9915 leader.update(cx, |leader, cx| {
9916 leader.buffer.update(cx, |multibuffer, cx| {
9917 let excerpt_ids = multibuffer.excerpt_ids();
9918 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
9919 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
9920 });
9921 });
9922
9923 // Apply the update of removing the excerpts.
9924 follower_1
9925 .update(cx, |follower, cx| {
9926 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9927 })
9928 .await
9929 .unwrap();
9930 follower_2
9931 .update(cx, |follower, cx| {
9932 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9933 })
9934 .await
9935 .unwrap();
9936 update_message.borrow_mut().take();
9937 assert_eq!(
9938 follower_1.update(cx, |editor, cx| editor.text(cx)),
9939 leader.update(cx, |editor, cx| editor.text(cx))
9940 );
9941}
9942
9943#[gpui::test]
9944async fn go_to_prev_overlapping_diagnostic(
9945 executor: BackgroundExecutor,
9946 cx: &mut gpui::TestAppContext,
9947) {
9948 init_test(cx, |_| {});
9949
9950 let mut cx = EditorTestContext::new(cx).await;
9951 let lsp_store =
9952 cx.update_editor(|editor, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
9953
9954 cx.set_state(indoc! {"
9955 ˇfn func(abc def: i32) -> u32 {
9956 }
9957 "});
9958
9959 cx.update(|cx| {
9960 lsp_store.update(cx, |lsp_store, cx| {
9961 lsp_store
9962 .update_diagnostics(
9963 LanguageServerId(0),
9964 lsp::PublishDiagnosticsParams {
9965 uri: lsp::Url::from_file_path("/root/file").unwrap(),
9966 version: None,
9967 diagnostics: vec![
9968 lsp::Diagnostic {
9969 range: lsp::Range::new(
9970 lsp::Position::new(0, 11),
9971 lsp::Position::new(0, 12),
9972 ),
9973 severity: Some(lsp::DiagnosticSeverity::ERROR),
9974 ..Default::default()
9975 },
9976 lsp::Diagnostic {
9977 range: lsp::Range::new(
9978 lsp::Position::new(0, 12),
9979 lsp::Position::new(0, 15),
9980 ),
9981 severity: Some(lsp::DiagnosticSeverity::ERROR),
9982 ..Default::default()
9983 },
9984 lsp::Diagnostic {
9985 range: lsp::Range::new(
9986 lsp::Position::new(0, 25),
9987 lsp::Position::new(0, 28),
9988 ),
9989 severity: Some(lsp::DiagnosticSeverity::ERROR),
9990 ..Default::default()
9991 },
9992 ],
9993 },
9994 &[],
9995 cx,
9996 )
9997 .unwrap()
9998 });
9999 });
10000
10001 executor.run_until_parked();
10002
10003 cx.update_editor(|editor, cx| {
10004 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
10005 });
10006
10007 cx.assert_editor_state(indoc! {"
10008 fn func(abc def: i32) -> ˇu32 {
10009 }
10010 "});
10011
10012 cx.update_editor(|editor, cx| {
10013 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
10014 });
10015
10016 cx.assert_editor_state(indoc! {"
10017 fn func(abc ˇdef: i32) -> u32 {
10018 }
10019 "});
10020
10021 cx.update_editor(|editor, cx| {
10022 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
10023 });
10024
10025 cx.assert_editor_state(indoc! {"
10026 fn func(abcˇ def: i32) -> u32 {
10027 }
10028 "});
10029
10030 cx.update_editor(|editor, cx| {
10031 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
10032 });
10033
10034 cx.assert_editor_state(indoc! {"
10035 fn func(abc def: i32) -> ˇu32 {
10036 }
10037 "});
10038}
10039
10040#[gpui::test]
10041async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
10042 init_test(cx, |_| {});
10043
10044 let mut cx = EditorTestContext::new(cx).await;
10045
10046 cx.set_state(indoc! {"
10047 fn func(abˇc def: i32) -> u32 {
10048 }
10049 "});
10050 let lsp_store =
10051 cx.update_editor(|editor, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
10052
10053 cx.update(|cx| {
10054 lsp_store.update(cx, |lsp_store, cx| {
10055 lsp_store.update_diagnostics(
10056 LanguageServerId(0),
10057 lsp::PublishDiagnosticsParams {
10058 uri: lsp::Url::from_file_path("/root/file").unwrap(),
10059 version: None,
10060 diagnostics: vec![lsp::Diagnostic {
10061 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
10062 severity: Some(lsp::DiagnosticSeverity::ERROR),
10063 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
10064 ..Default::default()
10065 }],
10066 },
10067 &[],
10068 cx,
10069 )
10070 })
10071 }).unwrap();
10072 cx.run_until_parked();
10073 cx.update_editor(|editor, cx| hover_popover::hover(editor, &Default::default(), cx));
10074 cx.run_until_parked();
10075 cx.update_editor(|editor, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
10076}
10077
10078#[gpui::test]
10079async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
10080 init_test(cx, |_| {});
10081
10082 let mut cx = EditorTestContext::new(cx).await;
10083
10084 let diff_base = r#"
10085 use some::mod;
10086
10087 const A: u32 = 42;
10088
10089 fn main() {
10090 println!("hello");
10091
10092 println!("world");
10093 }
10094 "#
10095 .unindent();
10096
10097 // Edits are modified, removed, modified, added
10098 cx.set_state(
10099 &r#"
10100 use some::modified;
10101
10102 ˇ
10103 fn main() {
10104 println!("hello there");
10105
10106 println!("around the");
10107 println!("world");
10108 }
10109 "#
10110 .unindent(),
10111 );
10112
10113 cx.set_diff_base(&diff_base);
10114 executor.run_until_parked();
10115
10116 cx.update_editor(|editor, cx| {
10117 //Wrap around the bottom of the buffer
10118 for _ in 0..3 {
10119 editor.go_to_next_hunk(&GoToHunk, cx);
10120 }
10121 });
10122
10123 cx.assert_editor_state(
10124 &r#"
10125 ˇuse some::modified;
10126
10127
10128 fn main() {
10129 println!("hello there");
10130
10131 println!("around the");
10132 println!("world");
10133 }
10134 "#
10135 .unindent(),
10136 );
10137
10138 cx.update_editor(|editor, cx| {
10139 //Wrap around the top of the buffer
10140 for _ in 0..2 {
10141 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10142 }
10143 });
10144
10145 cx.assert_editor_state(
10146 &r#"
10147 use some::modified;
10148
10149
10150 fn main() {
10151 ˇ println!("hello there");
10152
10153 println!("around the");
10154 println!("world");
10155 }
10156 "#
10157 .unindent(),
10158 );
10159
10160 cx.update_editor(|editor, cx| {
10161 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10162 });
10163
10164 cx.assert_editor_state(
10165 &r#"
10166 use some::modified;
10167
10168 ˇ
10169 fn main() {
10170 println!("hello there");
10171
10172 println!("around the");
10173 println!("world");
10174 }
10175 "#
10176 .unindent(),
10177 );
10178
10179 cx.update_editor(|editor, cx| {
10180 for _ in 0..3 {
10181 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10182 }
10183 });
10184
10185 cx.assert_editor_state(
10186 &r#"
10187 use some::modified;
10188
10189
10190 fn main() {
10191 ˇ println!("hello there");
10192
10193 println!("around the");
10194 println!("world");
10195 }
10196 "#
10197 .unindent(),
10198 );
10199
10200 cx.update_editor(|editor, cx| {
10201 editor.fold(&Fold, cx);
10202
10203 //Make sure that the fold only gets one hunk
10204 for _ in 0..4 {
10205 editor.go_to_next_hunk(&GoToHunk, cx);
10206 }
10207 });
10208
10209 cx.assert_editor_state(
10210 &r#"
10211 ˇuse some::modified;
10212
10213
10214 fn main() {
10215 println!("hello there");
10216
10217 println!("around the");
10218 println!("world");
10219 }
10220 "#
10221 .unindent(),
10222 );
10223}
10224
10225#[test]
10226fn test_split_words() {
10227 fn split(text: &str) -> Vec<&str> {
10228 split_words(text).collect()
10229 }
10230
10231 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
10232 assert_eq!(split("hello_world"), &["hello_", "world"]);
10233 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
10234 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
10235 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
10236 assert_eq!(split("helloworld"), &["helloworld"]);
10237
10238 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
10239}
10240
10241#[gpui::test]
10242async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
10243 init_test(cx, |_| {});
10244
10245 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
10246 let mut assert = |before, after| {
10247 let _state_context = cx.set_state(before);
10248 cx.update_editor(|editor, cx| {
10249 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
10250 });
10251 cx.assert_editor_state(after);
10252 };
10253
10254 // Outside bracket jumps to outside of matching bracket
10255 assert("console.logˇ(var);", "console.log(var)ˇ;");
10256 assert("console.log(var)ˇ;", "console.logˇ(var);");
10257
10258 // Inside bracket jumps to inside of matching bracket
10259 assert("console.log(ˇvar);", "console.log(varˇ);");
10260 assert("console.log(varˇ);", "console.log(ˇvar);");
10261
10262 // When outside a bracket and inside, favor jumping to the inside bracket
10263 assert(
10264 "console.log('foo', [1, 2, 3]ˇ);",
10265 "console.log(ˇ'foo', [1, 2, 3]);",
10266 );
10267 assert(
10268 "console.log(ˇ'foo', [1, 2, 3]);",
10269 "console.log('foo', [1, 2, 3]ˇ);",
10270 );
10271
10272 // Bias forward if two options are equally likely
10273 assert(
10274 "let result = curried_fun()ˇ();",
10275 "let result = curried_fun()()ˇ;",
10276 );
10277
10278 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
10279 assert(
10280 indoc! {"
10281 function test() {
10282 console.log('test')ˇ
10283 }"},
10284 indoc! {"
10285 function test() {
10286 console.logˇ('test')
10287 }"},
10288 );
10289}
10290
10291#[gpui::test]
10292async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
10293 init_test(cx, |_| {});
10294
10295 let fs = FakeFs::new(cx.executor());
10296 fs.insert_tree(
10297 "/a",
10298 json!({
10299 "main.rs": "fn main() { let a = 5; }",
10300 "other.rs": "// Test file",
10301 }),
10302 )
10303 .await;
10304 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10305
10306 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10307 language_registry.add(Arc::new(Language::new(
10308 LanguageConfig {
10309 name: "Rust".into(),
10310 matcher: LanguageMatcher {
10311 path_suffixes: vec!["rs".to_string()],
10312 ..Default::default()
10313 },
10314 brackets: BracketPairConfig {
10315 pairs: vec![BracketPair {
10316 start: "{".to_string(),
10317 end: "}".to_string(),
10318 close: true,
10319 surround: true,
10320 newline: true,
10321 }],
10322 disabled_scopes_by_bracket_ix: Vec::new(),
10323 },
10324 ..Default::default()
10325 },
10326 Some(tree_sitter_rust::LANGUAGE.into()),
10327 )));
10328 let mut fake_servers = language_registry.register_fake_lsp(
10329 "Rust",
10330 FakeLspAdapter {
10331 capabilities: lsp::ServerCapabilities {
10332 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
10333 first_trigger_character: "{".to_string(),
10334 more_trigger_character: None,
10335 }),
10336 ..Default::default()
10337 },
10338 ..Default::default()
10339 },
10340 );
10341
10342 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10343
10344 let cx = &mut VisualTestContext::from_window(*workspace, cx);
10345
10346 let worktree_id = workspace
10347 .update(cx, |workspace, cx| {
10348 workspace.project().update(cx, |project, cx| {
10349 project.worktrees(cx).next().unwrap().read(cx).id()
10350 })
10351 })
10352 .unwrap();
10353
10354 let buffer = project
10355 .update(cx, |project, cx| {
10356 project.open_local_buffer("/a/main.rs", cx)
10357 })
10358 .await
10359 .unwrap();
10360 let editor_handle = workspace
10361 .update(cx, |workspace, cx| {
10362 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
10363 })
10364 .unwrap()
10365 .await
10366 .unwrap()
10367 .downcast::<Editor>()
10368 .unwrap();
10369
10370 cx.executor().start_waiting();
10371 let fake_server = fake_servers.next().await.unwrap();
10372
10373 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
10374 assert_eq!(
10375 params.text_document_position.text_document.uri,
10376 lsp::Url::from_file_path("/a/main.rs").unwrap(),
10377 );
10378 assert_eq!(
10379 params.text_document_position.position,
10380 lsp::Position::new(0, 21),
10381 );
10382
10383 Ok(Some(vec![lsp::TextEdit {
10384 new_text: "]".to_string(),
10385 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10386 }]))
10387 });
10388
10389 editor_handle.update(cx, |editor, cx| {
10390 editor.focus(cx);
10391 editor.change_selections(None, cx, |s| {
10392 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
10393 });
10394 editor.handle_input("{", cx);
10395 });
10396
10397 cx.executor().run_until_parked();
10398
10399 buffer.update(cx, |buffer, _| {
10400 assert_eq!(
10401 buffer.text(),
10402 "fn main() { let a = {5}; }",
10403 "No extra braces from on type formatting should appear in the buffer"
10404 )
10405 });
10406}
10407
10408#[gpui::test]
10409async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
10410 init_test(cx, |_| {});
10411
10412 let fs = FakeFs::new(cx.executor());
10413 fs.insert_tree(
10414 "/a",
10415 json!({
10416 "main.rs": "fn main() { let a = 5; }",
10417 "other.rs": "// Test file",
10418 }),
10419 )
10420 .await;
10421
10422 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10423
10424 let server_restarts = Arc::new(AtomicUsize::new(0));
10425 let closure_restarts = Arc::clone(&server_restarts);
10426 let language_server_name = "test language server";
10427 let language_name: LanguageName = "Rust".into();
10428
10429 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10430 language_registry.add(Arc::new(Language::new(
10431 LanguageConfig {
10432 name: language_name.clone(),
10433 matcher: LanguageMatcher {
10434 path_suffixes: vec!["rs".to_string()],
10435 ..Default::default()
10436 },
10437 ..Default::default()
10438 },
10439 Some(tree_sitter_rust::LANGUAGE.into()),
10440 )));
10441 let mut fake_servers = language_registry.register_fake_lsp(
10442 "Rust",
10443 FakeLspAdapter {
10444 name: language_server_name,
10445 initialization_options: Some(json!({
10446 "testOptionValue": true
10447 })),
10448 initializer: Some(Box::new(move |fake_server| {
10449 let task_restarts = Arc::clone(&closure_restarts);
10450 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
10451 task_restarts.fetch_add(1, atomic::Ordering::Release);
10452 futures::future::ready(Ok(()))
10453 });
10454 })),
10455 ..Default::default()
10456 },
10457 );
10458
10459 let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10460 let _buffer = project
10461 .update(cx, |project, cx| {
10462 project.open_local_buffer_with_lsp("/a/main.rs", cx)
10463 })
10464 .await
10465 .unwrap();
10466 let _fake_server = fake_servers.next().await.unwrap();
10467 update_test_language_settings(cx, |language_settings| {
10468 language_settings.languages.insert(
10469 language_name.clone(),
10470 LanguageSettingsContent {
10471 tab_size: NonZeroU32::new(8),
10472 ..Default::default()
10473 },
10474 );
10475 });
10476 cx.executor().run_until_parked();
10477 assert_eq!(
10478 server_restarts.load(atomic::Ordering::Acquire),
10479 0,
10480 "Should not restart LSP server on an unrelated change"
10481 );
10482
10483 update_test_project_settings(cx, |project_settings| {
10484 project_settings.lsp.insert(
10485 "Some other server name".into(),
10486 LspSettings {
10487 binary: None,
10488 settings: None,
10489 initialization_options: Some(json!({
10490 "some other init value": false
10491 })),
10492 },
10493 );
10494 });
10495 cx.executor().run_until_parked();
10496 assert_eq!(
10497 server_restarts.load(atomic::Ordering::Acquire),
10498 0,
10499 "Should not restart LSP server on an unrelated LSP settings change"
10500 );
10501
10502 update_test_project_settings(cx, |project_settings| {
10503 project_settings.lsp.insert(
10504 language_server_name.into(),
10505 LspSettings {
10506 binary: None,
10507 settings: None,
10508 initialization_options: Some(json!({
10509 "anotherInitValue": false
10510 })),
10511 },
10512 );
10513 });
10514 cx.executor().run_until_parked();
10515 assert_eq!(
10516 server_restarts.load(atomic::Ordering::Acquire),
10517 1,
10518 "Should restart LSP server on a related LSP settings change"
10519 );
10520
10521 update_test_project_settings(cx, |project_settings| {
10522 project_settings.lsp.insert(
10523 language_server_name.into(),
10524 LspSettings {
10525 binary: None,
10526 settings: None,
10527 initialization_options: Some(json!({
10528 "anotherInitValue": false
10529 })),
10530 },
10531 );
10532 });
10533 cx.executor().run_until_parked();
10534 assert_eq!(
10535 server_restarts.load(atomic::Ordering::Acquire),
10536 1,
10537 "Should not restart LSP server on a related LSP settings change that is the same"
10538 );
10539
10540 update_test_project_settings(cx, |project_settings| {
10541 project_settings.lsp.insert(
10542 language_server_name.into(),
10543 LspSettings {
10544 binary: None,
10545 settings: None,
10546 initialization_options: None,
10547 },
10548 );
10549 });
10550 cx.executor().run_until_parked();
10551 assert_eq!(
10552 server_restarts.load(atomic::Ordering::Acquire),
10553 2,
10554 "Should restart LSP server on another related LSP settings change"
10555 );
10556}
10557
10558#[gpui::test]
10559async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
10560 init_test(cx, |_| {});
10561
10562 let mut cx = EditorLspTestContext::new_rust(
10563 lsp::ServerCapabilities {
10564 completion_provider: Some(lsp::CompletionOptions {
10565 trigger_characters: Some(vec![".".to_string()]),
10566 resolve_provider: Some(true),
10567 ..Default::default()
10568 }),
10569 ..Default::default()
10570 },
10571 cx,
10572 )
10573 .await;
10574
10575 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10576 cx.simulate_keystroke(".");
10577 let completion_item = lsp::CompletionItem {
10578 label: "some".into(),
10579 kind: Some(lsp::CompletionItemKind::SNIPPET),
10580 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
10581 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
10582 kind: lsp::MarkupKind::Markdown,
10583 value: "```rust\nSome(2)\n```".to_string(),
10584 })),
10585 deprecated: Some(false),
10586 sort_text: Some("fffffff2".to_string()),
10587 filter_text: Some("some".to_string()),
10588 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
10589 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10590 range: lsp::Range {
10591 start: lsp::Position {
10592 line: 0,
10593 character: 22,
10594 },
10595 end: lsp::Position {
10596 line: 0,
10597 character: 22,
10598 },
10599 },
10600 new_text: "Some(2)".to_string(),
10601 })),
10602 additional_text_edits: Some(vec![lsp::TextEdit {
10603 range: lsp::Range {
10604 start: lsp::Position {
10605 line: 0,
10606 character: 20,
10607 },
10608 end: lsp::Position {
10609 line: 0,
10610 character: 22,
10611 },
10612 },
10613 new_text: "".to_string(),
10614 }]),
10615 ..Default::default()
10616 };
10617
10618 let closure_completion_item = completion_item.clone();
10619 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10620 let task_completion_item = closure_completion_item.clone();
10621 async move {
10622 Ok(Some(lsp::CompletionResponse::Array(vec![
10623 task_completion_item,
10624 ])))
10625 }
10626 });
10627
10628 request.next().await;
10629
10630 cx.condition(|editor, _| editor.context_menu_visible())
10631 .await;
10632 let apply_additional_edits = cx.update_editor(|editor, cx| {
10633 editor
10634 .confirm_completion(&ConfirmCompletion::default(), cx)
10635 .unwrap()
10636 });
10637 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
10638
10639 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
10640 let task_completion_item = completion_item.clone();
10641 async move { Ok(task_completion_item) }
10642 })
10643 .next()
10644 .await
10645 .unwrap();
10646 apply_additional_edits.await.unwrap();
10647 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
10648}
10649
10650#[gpui::test]
10651async fn test_completions_resolve_updates_labels_if_filter_text_matches(
10652 cx: &mut gpui::TestAppContext,
10653) {
10654 init_test(cx, |_| {});
10655
10656 let mut cx = EditorLspTestContext::new_rust(
10657 lsp::ServerCapabilities {
10658 completion_provider: Some(lsp::CompletionOptions {
10659 trigger_characters: Some(vec![".".to_string()]),
10660 resolve_provider: Some(true),
10661 ..Default::default()
10662 }),
10663 ..Default::default()
10664 },
10665 cx,
10666 )
10667 .await;
10668
10669 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10670 cx.simulate_keystroke(".");
10671
10672 let item1 = lsp::CompletionItem {
10673 label: "id".to_string(),
10674 filter_text: Some("id".to_string()),
10675 detail: None,
10676 documentation: None,
10677 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10678 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10679 new_text: ".id".to_string(),
10680 })),
10681 ..lsp::CompletionItem::default()
10682 };
10683
10684 let item2 = lsp::CompletionItem {
10685 label: "other".to_string(),
10686 filter_text: Some("other".to_string()),
10687 detail: None,
10688 documentation: None,
10689 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10690 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10691 new_text: ".other".to_string(),
10692 })),
10693 ..lsp::CompletionItem::default()
10694 };
10695
10696 let item1 = item1.clone();
10697 cx.handle_request::<lsp::request::Completion, _, _>({
10698 let item1 = item1.clone();
10699 move |_, _, _| {
10700 let item1 = item1.clone();
10701 let item2 = item2.clone();
10702 async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
10703 }
10704 })
10705 .next()
10706 .await;
10707
10708 cx.condition(|editor, _| editor.context_menu_visible())
10709 .await;
10710 cx.update_editor(|editor, _| {
10711 let context_menu = editor.context_menu.borrow_mut();
10712 let context_menu = context_menu
10713 .as_ref()
10714 .expect("Should have the context menu deployed");
10715 match context_menu {
10716 CodeContextMenu::Completions(completions_menu) => {
10717 let completions = completions_menu.completions.borrow_mut();
10718 assert_eq!(
10719 completions
10720 .iter()
10721 .map(|completion| &completion.label.text)
10722 .collect::<Vec<_>>(),
10723 vec!["id", "other"]
10724 )
10725 }
10726 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
10727 }
10728 });
10729
10730 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>({
10731 let item1 = item1.clone();
10732 move |_, item_to_resolve, _| {
10733 let item1 = item1.clone();
10734 async move {
10735 if item1 == item_to_resolve {
10736 Ok(lsp::CompletionItem {
10737 label: "method id()".to_string(),
10738 filter_text: Some("id".to_string()),
10739 detail: Some("Now resolved!".to_string()),
10740 documentation: Some(lsp::Documentation::String("Docs".to_string())),
10741 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10742 range: lsp::Range::new(
10743 lsp::Position::new(0, 22),
10744 lsp::Position::new(0, 22),
10745 ),
10746 new_text: ".id".to_string(),
10747 })),
10748 ..lsp::CompletionItem::default()
10749 })
10750 } else {
10751 Ok(item_to_resolve)
10752 }
10753 }
10754 }
10755 })
10756 .next()
10757 .await
10758 .unwrap();
10759 cx.run_until_parked();
10760
10761 cx.update_editor(|editor, cx| {
10762 editor.context_menu_next(&Default::default(), cx);
10763 });
10764
10765 cx.update_editor(|editor, _| {
10766 let context_menu = editor.context_menu.borrow_mut();
10767 let context_menu = context_menu
10768 .as_ref()
10769 .expect("Should have the context menu deployed");
10770 match context_menu {
10771 CodeContextMenu::Completions(completions_menu) => {
10772 let completions = completions_menu.completions.borrow_mut();
10773 assert_eq!(
10774 completions
10775 .iter()
10776 .map(|completion| &completion.label.text)
10777 .collect::<Vec<_>>(),
10778 vec!["method id()", "other"],
10779 "Should update first completion label, but not second as the filter text did not match."
10780 );
10781 }
10782 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
10783 }
10784 });
10785}
10786
10787#[gpui::test]
10788async fn test_completions_resolve_happens_once(cx: &mut gpui::TestAppContext) {
10789 init_test(cx, |_| {});
10790
10791 let mut cx = EditorLspTestContext::new_rust(
10792 lsp::ServerCapabilities {
10793 completion_provider: Some(lsp::CompletionOptions {
10794 trigger_characters: Some(vec![".".to_string()]),
10795 resolve_provider: Some(true),
10796 ..Default::default()
10797 }),
10798 ..Default::default()
10799 },
10800 cx,
10801 )
10802 .await;
10803
10804 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10805 cx.simulate_keystroke(".");
10806
10807 let unresolved_item_1 = lsp::CompletionItem {
10808 label: "id".to_string(),
10809 filter_text: Some("id".to_string()),
10810 detail: None,
10811 documentation: None,
10812 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10813 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10814 new_text: ".id".to_string(),
10815 })),
10816 ..lsp::CompletionItem::default()
10817 };
10818 let resolved_item_1 = lsp::CompletionItem {
10819 additional_text_edits: Some(vec![lsp::TextEdit {
10820 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
10821 new_text: "!!".to_string(),
10822 }]),
10823 ..unresolved_item_1.clone()
10824 };
10825 let unresolved_item_2 = lsp::CompletionItem {
10826 label: "other".to_string(),
10827 filter_text: Some("other".to_string()),
10828 detail: None,
10829 documentation: None,
10830 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10831 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10832 new_text: ".other".to_string(),
10833 })),
10834 ..lsp::CompletionItem::default()
10835 };
10836 let resolved_item_2 = lsp::CompletionItem {
10837 additional_text_edits: Some(vec![lsp::TextEdit {
10838 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
10839 new_text: "??".to_string(),
10840 }]),
10841 ..unresolved_item_2.clone()
10842 };
10843
10844 let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
10845 let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
10846 cx.lsp
10847 .server
10848 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
10849 let unresolved_item_1 = unresolved_item_1.clone();
10850 let resolved_item_1 = resolved_item_1.clone();
10851 let unresolved_item_2 = unresolved_item_2.clone();
10852 let resolved_item_2 = resolved_item_2.clone();
10853 let resolve_requests_1 = resolve_requests_1.clone();
10854 let resolve_requests_2 = resolve_requests_2.clone();
10855 move |unresolved_request, _| {
10856 let unresolved_item_1 = unresolved_item_1.clone();
10857 let resolved_item_1 = resolved_item_1.clone();
10858 let unresolved_item_2 = unresolved_item_2.clone();
10859 let resolved_item_2 = resolved_item_2.clone();
10860 let resolve_requests_1 = resolve_requests_1.clone();
10861 let resolve_requests_2 = resolve_requests_2.clone();
10862 async move {
10863 if unresolved_request == unresolved_item_1 {
10864 resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
10865 Ok(resolved_item_1.clone())
10866 } else if unresolved_request == unresolved_item_2 {
10867 resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
10868 Ok(resolved_item_2.clone())
10869 } else {
10870 panic!("Unexpected completion item {unresolved_request:?}")
10871 }
10872 }
10873 }
10874 })
10875 .detach();
10876
10877 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10878 let unresolved_item_1 = unresolved_item_1.clone();
10879 let unresolved_item_2 = unresolved_item_2.clone();
10880 async move {
10881 Ok(Some(lsp::CompletionResponse::Array(vec![
10882 unresolved_item_1,
10883 unresolved_item_2,
10884 ])))
10885 }
10886 })
10887 .next()
10888 .await;
10889
10890 cx.condition(|editor, _| editor.context_menu_visible())
10891 .await;
10892 cx.update_editor(|editor, _| {
10893 let context_menu = editor.context_menu.borrow_mut();
10894 let context_menu = context_menu
10895 .as_ref()
10896 .expect("Should have the context menu deployed");
10897 match context_menu {
10898 CodeContextMenu::Completions(completions_menu) => {
10899 let completions = completions_menu.completions.borrow_mut();
10900 assert_eq!(
10901 completions
10902 .iter()
10903 .map(|completion| &completion.label.text)
10904 .collect::<Vec<_>>(),
10905 vec!["id", "other"]
10906 )
10907 }
10908 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
10909 }
10910 });
10911 cx.run_until_parked();
10912
10913 cx.update_editor(|editor, cx| {
10914 editor.context_menu_next(&ContextMenuNext, cx);
10915 });
10916 cx.run_until_parked();
10917 cx.update_editor(|editor, cx| {
10918 editor.context_menu_prev(&ContextMenuPrev, cx);
10919 });
10920 cx.run_until_parked();
10921 cx.update_editor(|editor, cx| {
10922 editor.context_menu_next(&ContextMenuNext, cx);
10923 });
10924 cx.run_until_parked();
10925 cx.update_editor(|editor, cx| {
10926 editor
10927 .compose_completion(&ComposeCompletion::default(), cx)
10928 .expect("No task returned")
10929 })
10930 .await
10931 .expect("Completion failed");
10932 cx.run_until_parked();
10933
10934 cx.update_editor(|editor, cx| {
10935 assert_eq!(
10936 resolve_requests_1.load(atomic::Ordering::Acquire),
10937 1,
10938 "Should always resolve once despite multiple selections"
10939 );
10940 assert_eq!(
10941 resolve_requests_2.load(atomic::Ordering::Acquire),
10942 1,
10943 "Should always resolve once after multiple selections and applying the completion"
10944 );
10945 assert_eq!(
10946 editor.text(cx),
10947 "fn main() { let a = ??.other; }",
10948 "Should use resolved data when applying the completion"
10949 );
10950 });
10951}
10952
10953#[gpui::test]
10954async fn test_completions_default_resolve_data_handling(cx: &mut gpui::TestAppContext) {
10955 init_test(cx, |_| {});
10956
10957 let mut cx = EditorLspTestContext::new_rust(
10958 lsp::ServerCapabilities {
10959 completion_provider: Some(lsp::CompletionOptions {
10960 trigger_characters: Some(vec![".".to_string()]),
10961 resolve_provider: Some(true),
10962 ..Default::default()
10963 }),
10964 ..Default::default()
10965 },
10966 cx,
10967 )
10968 .await;
10969
10970 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10971 cx.simulate_keystroke(".");
10972
10973 let default_commit_characters = vec!["?".to_string()];
10974 let default_data = json!({ "very": "special"});
10975 let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
10976 let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
10977 let default_edit_range = lsp::Range {
10978 start: lsp::Position {
10979 line: 0,
10980 character: 5,
10981 },
10982 end: lsp::Position {
10983 line: 0,
10984 character: 5,
10985 },
10986 };
10987
10988 let resolve_requests_number = Arc::new(AtomicUsize::new(0));
10989 let expect_first_item = Arc::new(AtomicBool::new(true));
10990 cx.lsp
10991 .server
10992 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
10993 let closure_default_data = default_data.clone();
10994 let closure_resolve_requests_number = resolve_requests_number.clone();
10995 let closure_expect_first_item = expect_first_item.clone();
10996 let closure_default_commit_characters = default_commit_characters.clone();
10997 move |item_to_resolve, _| {
10998 closure_resolve_requests_number.fetch_add(1, atomic::Ordering::Release);
10999 let default_data = closure_default_data.clone();
11000 let default_commit_characters = closure_default_commit_characters.clone();
11001 let expect_first_item = closure_expect_first_item.clone();
11002 async move {
11003 if expect_first_item.load(atomic::Ordering::Acquire) {
11004 assert_eq!(
11005 item_to_resolve.label, "Some(2)",
11006 "Should have selected the first item"
11007 );
11008 assert_eq!(
11009 item_to_resolve.data,
11010 Some(json!({ "very": "special"})),
11011 "First item should bring its own data for resolving"
11012 );
11013 assert_eq!(
11014 item_to_resolve.commit_characters,
11015 Some(default_commit_characters),
11016 "First item had no own commit characters and should inherit the default ones"
11017 );
11018 assert!(
11019 matches!(
11020 item_to_resolve.text_edit,
11021 Some(lsp::CompletionTextEdit::InsertAndReplace { .. })
11022 ),
11023 "First item should bring its own edit range for resolving"
11024 );
11025 assert_eq!(
11026 item_to_resolve.insert_text_format,
11027 Some(default_insert_text_format),
11028 "First item had no own insert text format and should inherit the default one"
11029 );
11030 assert_eq!(
11031 item_to_resolve.insert_text_mode,
11032 Some(lsp::InsertTextMode::ADJUST_INDENTATION),
11033 "First item should bring its own insert text mode for resolving"
11034 );
11035 Ok(item_to_resolve)
11036 } else {
11037 assert_eq!(
11038 item_to_resolve.label, "vec![2]",
11039 "Should have selected the last item"
11040 );
11041 assert_eq!(
11042 item_to_resolve.data,
11043 Some(default_data),
11044 "Last item has no own resolve data and should inherit the default one"
11045 );
11046 assert_eq!(
11047 item_to_resolve.commit_characters,
11048 Some(default_commit_characters),
11049 "Last item had no own commit characters and should inherit the default ones"
11050 );
11051 assert_eq!(
11052 item_to_resolve.text_edit,
11053 Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11054 range: default_edit_range,
11055 new_text: "vec![2]".to_string()
11056 })),
11057 "Last item had no own edit range and should inherit the default one"
11058 );
11059 assert_eq!(
11060 item_to_resolve.insert_text_format,
11061 Some(lsp::InsertTextFormat::PLAIN_TEXT),
11062 "Last item should bring its own insert text format for resolving"
11063 );
11064 assert_eq!(
11065 item_to_resolve.insert_text_mode,
11066 Some(default_insert_text_mode),
11067 "Last item had no own insert text mode and should inherit the default one"
11068 );
11069
11070 Ok(item_to_resolve)
11071 }
11072 }
11073 }
11074 }).detach();
11075
11076 let completion_data = default_data.clone();
11077 let completion_characters = default_commit_characters.clone();
11078 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
11079 let default_data = completion_data.clone();
11080 let default_commit_characters = completion_characters.clone();
11081 async move {
11082 Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
11083 items: vec![
11084 lsp::CompletionItem {
11085 label: "Some(2)".into(),
11086 insert_text: Some("Some(2)".into()),
11087 data: Some(json!({ "very": "special"})),
11088 insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
11089 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
11090 lsp::InsertReplaceEdit {
11091 new_text: "Some(2)".to_string(),
11092 insert: lsp::Range::default(),
11093 replace: lsp::Range::default(),
11094 },
11095 )),
11096 ..lsp::CompletionItem::default()
11097 },
11098 lsp::CompletionItem {
11099 label: "vec![2]".into(),
11100 insert_text: Some("vec![2]".into()),
11101 insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
11102 ..lsp::CompletionItem::default()
11103 },
11104 ],
11105 item_defaults: Some(lsp::CompletionListItemDefaults {
11106 data: Some(default_data.clone()),
11107 commit_characters: Some(default_commit_characters.clone()),
11108 edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
11109 default_edit_range,
11110 )),
11111 insert_text_format: Some(default_insert_text_format),
11112 insert_text_mode: Some(default_insert_text_mode),
11113 }),
11114 ..lsp::CompletionList::default()
11115 })))
11116 }
11117 })
11118 .next()
11119 .await;
11120
11121 cx.condition(|editor, _| editor.context_menu_visible())
11122 .await;
11123 cx.run_until_parked();
11124 cx.update_editor(|editor, _| {
11125 let menu = editor.context_menu.borrow_mut();
11126 match menu.as_ref().expect("should have the completions menu") {
11127 CodeContextMenu::Completions(completions_menu) => {
11128 assert_eq!(
11129 completion_menu_entries(&completions_menu.entries),
11130 vec!["Some(2)", "vec![2]"]
11131 );
11132 }
11133 CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
11134 }
11135 });
11136 assert_eq!(
11137 resolve_requests_number.load(atomic::Ordering::Acquire),
11138 1,
11139 "While there are 2 items in the completion list, only 1 resolve request should have been sent, for the selected item"
11140 );
11141
11142 cx.update_editor(|editor, cx| {
11143 editor.context_menu_first(&ContextMenuFirst, cx);
11144 });
11145 cx.run_until_parked();
11146 assert_eq!(
11147 resolve_requests_number.load(atomic::Ordering::Acquire),
11148 1,
11149 "After re-selecting the first item, no new resolve requests should be sent"
11150 );
11151
11152 expect_first_item.store(false, atomic::Ordering::Release);
11153 cx.update_editor(|editor, cx| {
11154 editor.context_menu_last(&ContextMenuLast, cx);
11155 });
11156 cx.run_until_parked();
11157 assert_eq!(
11158 resolve_requests_number.load(atomic::Ordering::Acquire),
11159 2,
11160 "After selecting the other item, another resolve request should have been sent"
11161 );
11162}
11163
11164#[gpui::test]
11165async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
11166 init_test(cx, |_| {});
11167
11168 let mut cx = EditorLspTestContext::new(
11169 Language::new(
11170 LanguageConfig {
11171 matcher: LanguageMatcher {
11172 path_suffixes: vec!["jsx".into()],
11173 ..Default::default()
11174 },
11175 overrides: [(
11176 "element".into(),
11177 LanguageConfigOverride {
11178 word_characters: Override::Set(['-'].into_iter().collect()),
11179 ..Default::default()
11180 },
11181 )]
11182 .into_iter()
11183 .collect(),
11184 ..Default::default()
11185 },
11186 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
11187 )
11188 .with_override_query("(jsx_self_closing_element) @element")
11189 .unwrap(),
11190 lsp::ServerCapabilities {
11191 completion_provider: Some(lsp::CompletionOptions {
11192 trigger_characters: Some(vec![":".to_string()]),
11193 ..Default::default()
11194 }),
11195 ..Default::default()
11196 },
11197 cx,
11198 )
11199 .await;
11200
11201 cx.lsp
11202 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
11203 Ok(Some(lsp::CompletionResponse::Array(vec![
11204 lsp::CompletionItem {
11205 label: "bg-blue".into(),
11206 ..Default::default()
11207 },
11208 lsp::CompletionItem {
11209 label: "bg-red".into(),
11210 ..Default::default()
11211 },
11212 lsp::CompletionItem {
11213 label: "bg-yellow".into(),
11214 ..Default::default()
11215 },
11216 ])))
11217 });
11218
11219 cx.set_state(r#"<p class="bgˇ" />"#);
11220
11221 // Trigger completion when typing a dash, because the dash is an extra
11222 // word character in the 'element' scope, which contains the cursor.
11223 cx.simulate_keystroke("-");
11224 cx.executor().run_until_parked();
11225 cx.update_editor(|editor, _| {
11226 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11227 {
11228 assert_eq!(
11229 completion_menu_entries(&menu.entries),
11230 &["bg-red", "bg-blue", "bg-yellow"]
11231 );
11232 } else {
11233 panic!("expected completion menu to be open");
11234 }
11235 });
11236
11237 cx.simulate_keystroke("l");
11238 cx.executor().run_until_parked();
11239 cx.update_editor(|editor, _| {
11240 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11241 {
11242 assert_eq!(
11243 completion_menu_entries(&menu.entries),
11244 &["bg-blue", "bg-yellow"]
11245 );
11246 } else {
11247 panic!("expected completion menu to be open");
11248 }
11249 });
11250
11251 // When filtering completions, consider the character after the '-' to
11252 // be the start of a subword.
11253 cx.set_state(r#"<p class="yelˇ" />"#);
11254 cx.simulate_keystroke("l");
11255 cx.executor().run_until_parked();
11256 cx.update_editor(|editor, _| {
11257 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11258 {
11259 assert_eq!(completion_menu_entries(&menu.entries), &["bg-yellow"]);
11260 } else {
11261 panic!("expected completion menu to be open");
11262 }
11263 });
11264}
11265
11266fn completion_menu_entries(entries: &[CompletionEntry]) -> Vec<&str> {
11267 entries
11268 .iter()
11269 .flat_map(|e| match e {
11270 CompletionEntry::Match(mat) => Some(mat.string.as_str()),
11271 _ => None,
11272 })
11273 .collect()
11274}
11275
11276#[gpui::test]
11277async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
11278 init_test(cx, |settings| {
11279 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
11280 FormatterList(vec![Formatter::Prettier].into()),
11281 ))
11282 });
11283
11284 let fs = FakeFs::new(cx.executor());
11285 fs.insert_file("/file.ts", Default::default()).await;
11286
11287 let project = Project::test(fs, ["/file.ts".as_ref()], cx).await;
11288 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11289
11290 language_registry.add(Arc::new(Language::new(
11291 LanguageConfig {
11292 name: "TypeScript".into(),
11293 matcher: LanguageMatcher {
11294 path_suffixes: vec!["ts".to_string()],
11295 ..Default::default()
11296 },
11297 ..Default::default()
11298 },
11299 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
11300 )));
11301 update_test_language_settings(cx, |settings| {
11302 settings.defaults.prettier = Some(PrettierSettings {
11303 allowed: true,
11304 ..PrettierSettings::default()
11305 });
11306 });
11307
11308 let test_plugin = "test_plugin";
11309 let _ = language_registry.register_fake_lsp(
11310 "TypeScript",
11311 FakeLspAdapter {
11312 prettier_plugins: vec![test_plugin],
11313 ..Default::default()
11314 },
11315 );
11316
11317 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
11318 let buffer = project
11319 .update(cx, |project, cx| project.open_local_buffer("/file.ts", cx))
11320 .await
11321 .unwrap();
11322
11323 let buffer_text = "one\ntwo\nthree\n";
11324 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
11325 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
11326 editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
11327
11328 editor
11329 .update(cx, |editor, cx| {
11330 editor.perform_format(
11331 project.clone(),
11332 FormatTrigger::Manual,
11333 FormatTarget::Buffer,
11334 cx,
11335 )
11336 })
11337 .unwrap()
11338 .await;
11339 assert_eq!(
11340 editor.update(cx, |editor, cx| editor.text(cx)),
11341 buffer_text.to_string() + prettier_format_suffix,
11342 "Test prettier formatting was not applied to the original buffer text",
11343 );
11344
11345 update_test_language_settings(cx, |settings| {
11346 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
11347 });
11348 let format = editor.update(cx, |editor, cx| {
11349 editor.perform_format(
11350 project.clone(),
11351 FormatTrigger::Manual,
11352 FormatTarget::Buffer,
11353 cx,
11354 )
11355 });
11356 format.await.unwrap();
11357 assert_eq!(
11358 editor.update(cx, |editor, cx| editor.text(cx)),
11359 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
11360 "Autoformatting (via test prettier) was not applied to the original buffer text",
11361 );
11362}
11363
11364#[gpui::test]
11365async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
11366 init_test(cx, |_| {});
11367 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11368 let base_text = indoc! {r#"
11369 struct Row;
11370 struct Row1;
11371 struct Row2;
11372
11373 struct Row4;
11374 struct Row5;
11375 struct Row6;
11376
11377 struct Row8;
11378 struct Row9;
11379 struct Row10;"#};
11380
11381 // When addition hunks are not adjacent to carets, no hunk revert is performed
11382 assert_hunk_revert(
11383 indoc! {r#"struct Row;
11384 struct Row1;
11385 struct Row1.1;
11386 struct Row1.2;
11387 struct Row2;ˇ
11388
11389 struct Row4;
11390 struct Row5;
11391 struct Row6;
11392
11393 struct Row8;
11394 ˇstruct Row9;
11395 struct Row9.1;
11396 struct Row9.2;
11397 struct Row9.3;
11398 struct Row10;"#},
11399 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
11400 indoc! {r#"struct Row;
11401 struct Row1;
11402 struct Row1.1;
11403 struct Row1.2;
11404 struct Row2;ˇ
11405
11406 struct Row4;
11407 struct Row5;
11408 struct Row6;
11409
11410 struct Row8;
11411 ˇstruct Row9;
11412 struct Row9.1;
11413 struct Row9.2;
11414 struct Row9.3;
11415 struct Row10;"#},
11416 base_text,
11417 &mut cx,
11418 );
11419 // Same for selections
11420 assert_hunk_revert(
11421 indoc! {r#"struct Row;
11422 struct Row1;
11423 struct Row2;
11424 struct Row2.1;
11425 struct Row2.2;
11426 «ˇ
11427 struct Row4;
11428 struct» Row5;
11429 «struct Row6;
11430 ˇ»
11431 struct Row9.1;
11432 struct Row9.2;
11433 struct Row9.3;
11434 struct Row8;
11435 struct Row9;
11436 struct Row10;"#},
11437 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
11438 indoc! {r#"struct Row;
11439 struct Row1;
11440 struct Row2;
11441 struct Row2.1;
11442 struct Row2.2;
11443 «ˇ
11444 struct Row4;
11445 struct» Row5;
11446 «struct Row6;
11447 ˇ»
11448 struct Row9.1;
11449 struct Row9.2;
11450 struct Row9.3;
11451 struct Row8;
11452 struct Row9;
11453 struct Row10;"#},
11454 base_text,
11455 &mut cx,
11456 );
11457
11458 // When carets and selections intersect the addition hunks, those are reverted.
11459 // Adjacent carets got merged.
11460 assert_hunk_revert(
11461 indoc! {r#"struct Row;
11462 ˇ// something on the top
11463 struct Row1;
11464 struct Row2;
11465 struct Roˇw3.1;
11466 struct Row2.2;
11467 struct Row2.3;ˇ
11468
11469 struct Row4;
11470 struct ˇRow5.1;
11471 struct Row5.2;
11472 struct «Rowˇ»5.3;
11473 struct Row5;
11474 struct Row6;
11475 ˇ
11476 struct Row9.1;
11477 struct «Rowˇ»9.2;
11478 struct «ˇRow»9.3;
11479 struct Row8;
11480 struct Row9;
11481 «ˇ// something on bottom»
11482 struct Row10;"#},
11483 vec![
11484 DiffHunkStatus::Added,
11485 DiffHunkStatus::Added,
11486 DiffHunkStatus::Added,
11487 DiffHunkStatus::Added,
11488 DiffHunkStatus::Added,
11489 ],
11490 indoc! {r#"struct Row;
11491 ˇstruct Row1;
11492 struct Row2;
11493 ˇ
11494 struct Row4;
11495 ˇstruct Row5;
11496 struct Row6;
11497 ˇ
11498 ˇstruct Row8;
11499 struct Row9;
11500 ˇstruct Row10;"#},
11501 base_text,
11502 &mut cx,
11503 );
11504}
11505
11506#[gpui::test]
11507async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
11508 init_test(cx, |_| {});
11509 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11510 let base_text = indoc! {r#"
11511 struct Row;
11512 struct Row1;
11513 struct Row2;
11514
11515 struct Row4;
11516 struct Row5;
11517 struct Row6;
11518
11519 struct Row8;
11520 struct Row9;
11521 struct Row10;"#};
11522
11523 // Modification hunks behave the same as the addition ones.
11524 assert_hunk_revert(
11525 indoc! {r#"struct Row;
11526 struct Row1;
11527 struct Row33;
11528 ˇ
11529 struct Row4;
11530 struct Row5;
11531 struct Row6;
11532 ˇ
11533 struct Row99;
11534 struct Row9;
11535 struct Row10;"#},
11536 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
11537 indoc! {r#"struct Row;
11538 struct Row1;
11539 struct Row33;
11540 ˇ
11541 struct Row4;
11542 struct Row5;
11543 struct Row6;
11544 ˇ
11545 struct Row99;
11546 struct Row9;
11547 struct Row10;"#},
11548 base_text,
11549 &mut cx,
11550 );
11551 assert_hunk_revert(
11552 indoc! {r#"struct Row;
11553 struct Row1;
11554 struct Row33;
11555 «ˇ
11556 struct Row4;
11557 struct» Row5;
11558 «struct Row6;
11559 ˇ»
11560 struct Row99;
11561 struct Row9;
11562 struct Row10;"#},
11563 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
11564 indoc! {r#"struct Row;
11565 struct Row1;
11566 struct Row33;
11567 «ˇ
11568 struct Row4;
11569 struct» Row5;
11570 «struct Row6;
11571 ˇ»
11572 struct Row99;
11573 struct Row9;
11574 struct Row10;"#},
11575 base_text,
11576 &mut cx,
11577 );
11578
11579 assert_hunk_revert(
11580 indoc! {r#"ˇstruct Row1.1;
11581 struct Row1;
11582 «ˇstr»uct Row22;
11583
11584 struct ˇRow44;
11585 struct Row5;
11586 struct «Rˇ»ow66;ˇ
11587
11588 «struˇ»ct Row88;
11589 struct Row9;
11590 struct Row1011;ˇ"#},
11591 vec![
11592 DiffHunkStatus::Modified,
11593 DiffHunkStatus::Modified,
11594 DiffHunkStatus::Modified,
11595 DiffHunkStatus::Modified,
11596 DiffHunkStatus::Modified,
11597 DiffHunkStatus::Modified,
11598 ],
11599 indoc! {r#"struct Row;
11600 ˇstruct Row1;
11601 struct Row2;
11602 ˇ
11603 struct Row4;
11604 ˇstruct Row5;
11605 struct Row6;
11606 ˇ
11607 struct Row8;
11608 ˇstruct Row9;
11609 struct Row10;ˇ"#},
11610 base_text,
11611 &mut cx,
11612 );
11613}
11614
11615#[gpui::test]
11616async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
11617 init_test(cx, |_| {});
11618 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11619 let base_text = indoc! {r#"struct Row;
11620struct Row1;
11621struct Row2;
11622
11623struct Row4;
11624struct Row5;
11625struct Row6;
11626
11627struct Row8;
11628struct Row9;
11629struct Row10;"#};
11630
11631 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
11632 assert_hunk_revert(
11633 indoc! {r#"struct Row;
11634 struct Row2;
11635
11636 ˇstruct Row4;
11637 struct Row5;
11638 struct Row6;
11639 ˇ
11640 struct Row8;
11641 struct Row10;"#},
11642 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11643 indoc! {r#"struct Row;
11644 struct Row2;
11645
11646 ˇstruct Row4;
11647 struct Row5;
11648 struct Row6;
11649 ˇ
11650 struct Row8;
11651 struct Row10;"#},
11652 base_text,
11653 &mut cx,
11654 );
11655 assert_hunk_revert(
11656 indoc! {r#"struct Row;
11657 struct Row2;
11658
11659 «ˇstruct Row4;
11660 struct» Row5;
11661 «struct Row6;
11662 ˇ»
11663 struct Row8;
11664 struct Row10;"#},
11665 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11666 indoc! {r#"struct Row;
11667 struct Row2;
11668
11669 «ˇstruct Row4;
11670 struct» Row5;
11671 «struct Row6;
11672 ˇ»
11673 struct Row8;
11674 struct Row10;"#},
11675 base_text,
11676 &mut cx,
11677 );
11678
11679 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
11680 assert_hunk_revert(
11681 indoc! {r#"struct Row;
11682 ˇstruct Row2;
11683
11684 struct Row4;
11685 struct Row5;
11686 struct Row6;
11687
11688 struct Row8;ˇ
11689 struct Row10;"#},
11690 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11691 indoc! {r#"struct Row;
11692 struct Row1;
11693 ˇstruct Row2;
11694
11695 struct Row4;
11696 struct Row5;
11697 struct Row6;
11698
11699 struct Row8;ˇ
11700 struct Row9;
11701 struct Row10;"#},
11702 base_text,
11703 &mut cx,
11704 );
11705 assert_hunk_revert(
11706 indoc! {r#"struct Row;
11707 struct Row2«ˇ;
11708 struct Row4;
11709 struct» Row5;
11710 «struct Row6;
11711
11712 struct Row8;ˇ»
11713 struct Row10;"#},
11714 vec![
11715 DiffHunkStatus::Removed,
11716 DiffHunkStatus::Removed,
11717 DiffHunkStatus::Removed,
11718 ],
11719 indoc! {r#"struct Row;
11720 struct Row1;
11721 struct Row2«ˇ;
11722
11723 struct Row4;
11724 struct» Row5;
11725 «struct Row6;
11726
11727 struct Row8;ˇ»
11728 struct Row9;
11729 struct Row10;"#},
11730 base_text,
11731 &mut cx,
11732 );
11733}
11734
11735#[gpui::test]
11736async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
11737 init_test(cx, |_| {});
11738
11739 let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
11740 let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
11741 let base_text_3 =
11742 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
11743
11744 let text_1 = edit_first_char_of_every_line(base_text_1);
11745 let text_2 = edit_first_char_of_every_line(base_text_2);
11746 let text_3 = edit_first_char_of_every_line(base_text_3);
11747
11748 let buffer_1 = cx.new_model(|cx| Buffer::local(text_1.clone(), cx));
11749 let buffer_2 = cx.new_model(|cx| Buffer::local(text_2.clone(), cx));
11750 let buffer_3 = cx.new_model(|cx| Buffer::local(text_3.clone(), cx));
11751
11752 let multibuffer = cx.new_model(|cx| {
11753 let mut multibuffer = MultiBuffer::new(ReadWrite);
11754 multibuffer.push_excerpts(
11755 buffer_1.clone(),
11756 [
11757 ExcerptRange {
11758 context: Point::new(0, 0)..Point::new(3, 0),
11759 primary: None,
11760 },
11761 ExcerptRange {
11762 context: Point::new(5, 0)..Point::new(7, 0),
11763 primary: None,
11764 },
11765 ExcerptRange {
11766 context: Point::new(9, 0)..Point::new(10, 4),
11767 primary: None,
11768 },
11769 ],
11770 cx,
11771 );
11772 multibuffer.push_excerpts(
11773 buffer_2.clone(),
11774 [
11775 ExcerptRange {
11776 context: Point::new(0, 0)..Point::new(3, 0),
11777 primary: None,
11778 },
11779 ExcerptRange {
11780 context: Point::new(5, 0)..Point::new(7, 0),
11781 primary: None,
11782 },
11783 ExcerptRange {
11784 context: Point::new(9, 0)..Point::new(10, 4),
11785 primary: None,
11786 },
11787 ],
11788 cx,
11789 );
11790 multibuffer.push_excerpts(
11791 buffer_3.clone(),
11792 [
11793 ExcerptRange {
11794 context: Point::new(0, 0)..Point::new(3, 0),
11795 primary: None,
11796 },
11797 ExcerptRange {
11798 context: Point::new(5, 0)..Point::new(7, 0),
11799 primary: None,
11800 },
11801 ExcerptRange {
11802 context: Point::new(9, 0)..Point::new(10, 4),
11803 primary: None,
11804 },
11805 ],
11806 cx,
11807 );
11808 multibuffer
11809 });
11810
11811 let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
11812 editor.update(cx, |editor, cx| {
11813 for (buffer, diff_base) in [
11814 (buffer_1.clone(), base_text_1),
11815 (buffer_2.clone(), base_text_2),
11816 (buffer_3.clone(), base_text_3),
11817 ] {
11818 let change_set = cx.new_model(|cx| {
11819 BufferChangeSet::new_with_base_text(
11820 diff_base.to_string(),
11821 buffer.read(cx).text_snapshot(),
11822 cx,
11823 )
11824 });
11825 editor.diff_map.add_change_set(change_set, cx)
11826 }
11827 });
11828 cx.executor().run_until_parked();
11829
11830 editor.update(cx, |editor, cx| {
11831 assert_eq!(editor.text(cx), "Xaaa\nXbbb\nXccc\n\nXfff\nXggg\n\nXjjj\nXlll\nXmmm\nXnnn\n\nXqqq\nXrrr\n\nXuuu\nXvvv\nXwww\nXxxx\n\nX{{{\nX|||\n\nX\u{7f}\u{7f}\u{7f}");
11832 editor.select_all(&SelectAll, cx);
11833 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
11834 });
11835 cx.executor().run_until_parked();
11836
11837 // When all ranges are selected, all buffer hunks are reverted.
11838 editor.update(cx, |editor, cx| {
11839 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");
11840 });
11841 buffer_1.update(cx, |buffer, _| {
11842 assert_eq!(buffer.text(), base_text_1);
11843 });
11844 buffer_2.update(cx, |buffer, _| {
11845 assert_eq!(buffer.text(), base_text_2);
11846 });
11847 buffer_3.update(cx, |buffer, _| {
11848 assert_eq!(buffer.text(), base_text_3);
11849 });
11850
11851 editor.update(cx, |editor, cx| {
11852 editor.undo(&Default::default(), cx);
11853 });
11854
11855 editor.update(cx, |editor, cx| {
11856 editor.change_selections(None, cx, |s| {
11857 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
11858 });
11859 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
11860 });
11861
11862 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
11863 // but not affect buffer_2 and its related excerpts.
11864 editor.update(cx, |editor, cx| {
11865 assert_eq!(
11866 editor.text(cx),
11867 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n\n\nXlll\nXmmm\nXnnn\n\nXqqq\nXrrr\n\nXuuu\nXvvv\nXwww\nXxxx\n\nX{{{\nX|||\n\nX\u{7f}\u{7f}\u{7f}"
11868 );
11869 });
11870 buffer_1.update(cx, |buffer, _| {
11871 assert_eq!(buffer.text(), base_text_1);
11872 });
11873 buffer_2.update(cx, |buffer, _| {
11874 assert_eq!(
11875 buffer.text(),
11876 "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
11877 );
11878 });
11879 buffer_3.update(cx, |buffer, _| {
11880 assert_eq!(
11881 buffer.text(),
11882 "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
11883 );
11884 });
11885
11886 fn edit_first_char_of_every_line(text: &str) -> String {
11887 text.split('\n')
11888 .map(|line| format!("X{}", &line[1..]))
11889 .collect::<Vec<_>>()
11890 .join("\n")
11891 }
11892}
11893
11894#[gpui::test]
11895async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
11896 init_test(cx, |_| {});
11897
11898 let cols = 4;
11899 let rows = 10;
11900 let sample_text_1 = sample_text(rows, cols, 'a');
11901 assert_eq!(
11902 sample_text_1,
11903 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11904 );
11905 let sample_text_2 = sample_text(rows, cols, 'l');
11906 assert_eq!(
11907 sample_text_2,
11908 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11909 );
11910 let sample_text_3 = sample_text(rows, cols, 'v');
11911 assert_eq!(
11912 sample_text_3,
11913 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11914 );
11915
11916 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
11917 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
11918 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
11919
11920 let multi_buffer = cx.new_model(|cx| {
11921 let mut multibuffer = MultiBuffer::new(ReadWrite);
11922 multibuffer.push_excerpts(
11923 buffer_1.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, 4),
11935 primary: None,
11936 },
11937 ],
11938 cx,
11939 );
11940 multibuffer.push_excerpts(
11941 buffer_2.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, 4),
11953 primary: None,
11954 },
11955 ],
11956 cx,
11957 );
11958 multibuffer.push_excerpts(
11959 buffer_3.clone(),
11960 [
11961 ExcerptRange {
11962 context: Point::new(0, 0)..Point::new(3, 0),
11963 primary: None,
11964 },
11965 ExcerptRange {
11966 context: Point::new(5, 0)..Point::new(7, 0),
11967 primary: None,
11968 },
11969 ExcerptRange {
11970 context: Point::new(9, 0)..Point::new(10, 4),
11971 primary: None,
11972 },
11973 ],
11974 cx,
11975 );
11976 multibuffer
11977 });
11978
11979 let fs = FakeFs::new(cx.executor());
11980 fs.insert_tree(
11981 "/a",
11982 json!({
11983 "main.rs": sample_text_1,
11984 "other.rs": sample_text_2,
11985 "lib.rs": sample_text_3,
11986 }),
11987 )
11988 .await;
11989 let project = Project::test(fs, ["/a".as_ref()], cx).await;
11990 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
11991 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11992 let multi_buffer_editor = cx.new_view(|cx| {
11993 Editor::new(
11994 EditorMode::Full,
11995 multi_buffer,
11996 Some(project.clone()),
11997 true,
11998 cx,
11999 )
12000 });
12001 let multibuffer_item_id = workspace
12002 .update(cx, |workspace, cx| {
12003 assert!(
12004 workspace.active_item(cx).is_none(),
12005 "active item should be None before the first item is added"
12006 );
12007 workspace.add_item_to_active_pane(
12008 Box::new(multi_buffer_editor.clone()),
12009 None,
12010 true,
12011 cx,
12012 );
12013 let active_item = workspace
12014 .active_item(cx)
12015 .expect("should have an active item after adding the multi buffer");
12016 assert!(
12017 !active_item.is_singleton(cx),
12018 "A multi buffer was expected to active after adding"
12019 );
12020 active_item.item_id()
12021 })
12022 .unwrap();
12023 cx.executor().run_until_parked();
12024
12025 multi_buffer_editor.update(cx, |editor, cx| {
12026 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
12027 editor.open_excerpts(&OpenExcerpts, cx);
12028 });
12029 cx.executor().run_until_parked();
12030 let first_item_id = workspace
12031 .update(cx, |workspace, cx| {
12032 let active_item = workspace
12033 .active_item(cx)
12034 .expect("should have an active item after navigating into the 1st buffer");
12035 let first_item_id = active_item.item_id();
12036 assert_ne!(
12037 first_item_id, multibuffer_item_id,
12038 "Should navigate into the 1st buffer and activate it"
12039 );
12040 assert!(
12041 active_item.is_singleton(cx),
12042 "New active item should be a singleton buffer"
12043 );
12044 assert_eq!(
12045 active_item
12046 .act_as::<Editor>(cx)
12047 .expect("should have navigated into an editor for the 1st buffer")
12048 .read(cx)
12049 .text(cx),
12050 sample_text_1
12051 );
12052
12053 workspace
12054 .go_back(workspace.active_pane().downgrade(), cx)
12055 .detach_and_log_err(cx);
12056
12057 first_item_id
12058 })
12059 .unwrap();
12060 cx.executor().run_until_parked();
12061 workspace
12062 .update(cx, |workspace, cx| {
12063 let active_item = workspace
12064 .active_item(cx)
12065 .expect("should have an active item after navigating back");
12066 assert_eq!(
12067 active_item.item_id(),
12068 multibuffer_item_id,
12069 "Should navigate back to the multi buffer"
12070 );
12071 assert!(!active_item.is_singleton(cx));
12072 })
12073 .unwrap();
12074
12075 multi_buffer_editor.update(cx, |editor, cx| {
12076 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
12077 s.select_ranges(Some(39..40))
12078 });
12079 editor.open_excerpts(&OpenExcerpts, cx);
12080 });
12081 cx.executor().run_until_parked();
12082 let second_item_id = workspace
12083 .update(cx, |workspace, cx| {
12084 let active_item = workspace
12085 .active_item(cx)
12086 .expect("should have an active item after navigating into the 2nd buffer");
12087 let second_item_id = active_item.item_id();
12088 assert_ne!(
12089 second_item_id, multibuffer_item_id,
12090 "Should navigate away from the multibuffer"
12091 );
12092 assert_ne!(
12093 second_item_id, first_item_id,
12094 "Should navigate into the 2nd buffer and activate it"
12095 );
12096 assert!(
12097 active_item.is_singleton(cx),
12098 "New active item should be a singleton buffer"
12099 );
12100 assert_eq!(
12101 active_item
12102 .act_as::<Editor>(cx)
12103 .expect("should have navigated into an editor")
12104 .read(cx)
12105 .text(cx),
12106 sample_text_2
12107 );
12108
12109 workspace
12110 .go_back(workspace.active_pane().downgrade(), cx)
12111 .detach_and_log_err(cx);
12112
12113 second_item_id
12114 })
12115 .unwrap();
12116 cx.executor().run_until_parked();
12117 workspace
12118 .update(cx, |workspace, cx| {
12119 let active_item = workspace
12120 .active_item(cx)
12121 .expect("should have an active item after navigating back from the 2nd buffer");
12122 assert_eq!(
12123 active_item.item_id(),
12124 multibuffer_item_id,
12125 "Should navigate back from the 2nd buffer to the multi buffer"
12126 );
12127 assert!(!active_item.is_singleton(cx));
12128 })
12129 .unwrap();
12130
12131 multi_buffer_editor.update(cx, |editor, cx| {
12132 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
12133 s.select_ranges(Some(70..70))
12134 });
12135 editor.open_excerpts(&OpenExcerpts, cx);
12136 });
12137 cx.executor().run_until_parked();
12138 workspace
12139 .update(cx, |workspace, cx| {
12140 let active_item = workspace
12141 .active_item(cx)
12142 .expect("should have an active item after navigating into the 3rd buffer");
12143 let third_item_id = active_item.item_id();
12144 assert_ne!(
12145 third_item_id, multibuffer_item_id,
12146 "Should navigate into the 3rd buffer and activate it"
12147 );
12148 assert_ne!(third_item_id, first_item_id);
12149 assert_ne!(third_item_id, second_item_id);
12150 assert!(
12151 active_item.is_singleton(cx),
12152 "New active item should be a singleton buffer"
12153 );
12154 assert_eq!(
12155 active_item
12156 .act_as::<Editor>(cx)
12157 .expect("should have navigated into an editor")
12158 .read(cx)
12159 .text(cx),
12160 sample_text_3
12161 );
12162
12163 workspace
12164 .go_back(workspace.active_pane().downgrade(), cx)
12165 .detach_and_log_err(cx);
12166 })
12167 .unwrap();
12168 cx.executor().run_until_parked();
12169 workspace
12170 .update(cx, |workspace, cx| {
12171 let active_item = workspace
12172 .active_item(cx)
12173 .expect("should have an active item after navigating back from the 3rd buffer");
12174 assert_eq!(
12175 active_item.item_id(),
12176 multibuffer_item_id,
12177 "Should navigate back from the 3rd buffer to the multi buffer"
12178 );
12179 assert!(!active_item.is_singleton(cx));
12180 })
12181 .unwrap();
12182}
12183
12184#[gpui::test]
12185async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
12186 init_test(cx, |_| {});
12187
12188 let mut cx = EditorTestContext::new(cx).await;
12189
12190 let diff_base = r#"
12191 use some::mod;
12192
12193 const A: u32 = 42;
12194
12195 fn main() {
12196 println!("hello");
12197
12198 println!("world");
12199 }
12200 "#
12201 .unindent();
12202
12203 cx.set_state(
12204 &r#"
12205 use some::modified;
12206
12207 ˇ
12208 fn main() {
12209 println!("hello there");
12210
12211 println!("around the");
12212 println!("world");
12213 }
12214 "#
12215 .unindent(),
12216 );
12217
12218 cx.set_diff_base(&diff_base);
12219 executor.run_until_parked();
12220
12221 cx.update_editor(|editor, cx| {
12222 editor.go_to_next_hunk(&GoToHunk, cx);
12223 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
12224 });
12225 executor.run_until_parked();
12226 cx.assert_state_with_diff(
12227 r#"
12228 use some::modified;
12229
12230
12231 fn main() {
12232 - println!("hello");
12233 + ˇ println!("hello there");
12234
12235 println!("around the");
12236 println!("world");
12237 }
12238 "#
12239 .unindent(),
12240 );
12241
12242 cx.update_editor(|editor, cx| {
12243 for _ in 0..3 {
12244 editor.go_to_next_hunk(&GoToHunk, cx);
12245 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
12246 }
12247 });
12248 executor.run_until_parked();
12249 cx.assert_state_with_diff(
12250 r#"
12251 - use some::mod;
12252 + use some::modified;
12253
12254 - const A: u32 = 42;
12255 ˇ
12256 fn main() {
12257 - println!("hello");
12258 + println!("hello there");
12259
12260 + println!("around the");
12261 println!("world");
12262 }
12263 "#
12264 .unindent(),
12265 );
12266
12267 cx.update_editor(|editor, cx| {
12268 editor.cancel(&Cancel, cx);
12269 });
12270
12271 cx.assert_state_with_diff(
12272 r#"
12273 use some::modified;
12274
12275 ˇ
12276 fn main() {
12277 println!("hello there");
12278
12279 println!("around the");
12280 println!("world");
12281 }
12282 "#
12283 .unindent(),
12284 );
12285}
12286
12287#[gpui::test]
12288async fn test_diff_base_change_with_expanded_diff_hunks(
12289 executor: BackgroundExecutor,
12290 cx: &mut gpui::TestAppContext,
12291) {
12292 init_test(cx, |_| {});
12293
12294 let mut cx = EditorTestContext::new(cx).await;
12295
12296 let diff_base = r#"
12297 use some::mod1;
12298 use some::mod2;
12299
12300 const A: u32 = 42;
12301 const B: u32 = 42;
12302 const C: u32 = 42;
12303
12304 fn main() {
12305 println!("hello");
12306
12307 println!("world");
12308 }
12309 "#
12310 .unindent();
12311
12312 cx.set_state(
12313 &r#"
12314 use some::mod2;
12315
12316 const A: u32 = 42;
12317 const C: u32 = 42;
12318
12319 fn main(ˇ) {
12320 //println!("hello");
12321
12322 println!("world");
12323 //
12324 //
12325 }
12326 "#
12327 .unindent(),
12328 );
12329
12330 cx.set_diff_base(&diff_base);
12331 executor.run_until_parked();
12332
12333 cx.update_editor(|editor, cx| {
12334 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12335 });
12336 executor.run_until_parked();
12337 cx.assert_state_with_diff(
12338 r#"
12339 - use some::mod1;
12340 use some::mod2;
12341
12342 const A: u32 = 42;
12343 - const B: u32 = 42;
12344 const C: u32 = 42;
12345
12346 fn main(ˇ) {
12347 - println!("hello");
12348 + //println!("hello");
12349
12350 println!("world");
12351 + //
12352 + //
12353 }
12354 "#
12355 .unindent(),
12356 );
12357
12358 cx.set_diff_base("new diff base!");
12359 executor.run_until_parked();
12360 cx.assert_state_with_diff(
12361 r#"
12362 use some::mod2;
12363
12364 const A: u32 = 42;
12365 const C: u32 = 42;
12366
12367 fn main(ˇ) {
12368 //println!("hello");
12369
12370 println!("world");
12371 //
12372 //
12373 }
12374 "#
12375 .unindent(),
12376 );
12377
12378 cx.update_editor(|editor, cx| {
12379 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12380 });
12381 executor.run_until_parked();
12382 cx.assert_state_with_diff(
12383 r#"
12384 - new diff base!
12385 + use some::mod2;
12386 +
12387 + const A: u32 = 42;
12388 + const C: u32 = 42;
12389 +
12390 + fn main(ˇ) {
12391 + //println!("hello");
12392 +
12393 + println!("world");
12394 + //
12395 + //
12396 + }
12397 "#
12398 .unindent(),
12399 );
12400}
12401
12402#[gpui::test]
12403async fn test_fold_unfold_diff_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
12404 init_test(cx, |_| {});
12405
12406 let mut cx = EditorTestContext::new(cx).await;
12407
12408 let diff_base = r#"
12409 use some::mod1;
12410 use some::mod2;
12411
12412 const A: u32 = 42;
12413 const B: u32 = 42;
12414 const C: u32 = 42;
12415
12416 fn main() {
12417 println!("hello");
12418
12419 println!("world");
12420 }
12421
12422 fn another() {
12423 println!("another");
12424 }
12425
12426 fn another2() {
12427 println!("another2");
12428 }
12429 "#
12430 .unindent();
12431
12432 cx.set_state(
12433 &r#"
12434 «use some::mod2;
12435
12436 const A: u32 = 42;
12437 const C: u32 = 42;
12438
12439 fn main() {
12440 //println!("hello");
12441
12442 println!("world");
12443 //
12444 //ˇ»
12445 }
12446
12447 fn another() {
12448 println!("another");
12449 println!("another");
12450 }
12451
12452 println!("another2");
12453 }
12454 "#
12455 .unindent(),
12456 );
12457
12458 cx.set_diff_base(&diff_base);
12459 executor.run_until_parked();
12460
12461 cx.update_editor(|editor, cx| {
12462 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12463 });
12464 executor.run_until_parked();
12465
12466 cx.assert_state_with_diff(
12467 r#"
12468 - use some::mod1;
12469 «use some::mod2;
12470
12471 const A: u32 = 42;
12472 - const B: u32 = 42;
12473 const C: u32 = 42;
12474
12475 fn main() {
12476 - println!("hello");
12477 + //println!("hello");
12478
12479 println!("world");
12480 + //
12481 + //ˇ»
12482 }
12483
12484 fn another() {
12485 println!("another");
12486 + println!("another");
12487 }
12488
12489 - fn another2() {
12490 println!("another2");
12491 }
12492 "#
12493 .unindent(),
12494 );
12495
12496 // Fold across some of the diff hunks. They should no longer appear expanded.
12497 cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx));
12498 cx.executor().run_until_parked();
12499
12500 // Hunks are not shown if their position is within a fold
12501 cx.assert_state_with_diff(
12502 r#"
12503 «use some::mod2;
12504
12505 const A: u32 = 42;
12506 const C: u32 = 42;
12507
12508 fn main() {
12509 //println!("hello");
12510
12511 println!("world");
12512 //
12513 //ˇ»
12514 }
12515
12516 fn another() {
12517 println!("another");
12518 + println!("another");
12519 }
12520
12521 - fn another2() {
12522 println!("another2");
12523 }
12524 "#
12525 .unindent(),
12526 );
12527
12528 cx.update_editor(|editor, cx| {
12529 editor.select_all(&SelectAll, cx);
12530 editor.unfold_lines(&UnfoldLines, cx);
12531 });
12532 cx.executor().run_until_parked();
12533
12534 // The deletions reappear when unfolding.
12535 cx.assert_state_with_diff(
12536 r#"
12537 - use some::mod1;
12538 «use some::mod2;
12539
12540 const A: u32 = 42;
12541 - const B: u32 = 42;
12542 const C: u32 = 42;
12543
12544 fn main() {
12545 - println!("hello");
12546 + //println!("hello");
12547
12548 println!("world");
12549 + //
12550 + //
12551 }
12552
12553 fn another() {
12554 println!("another");
12555 + println!("another");
12556 }
12557
12558 - fn another2() {
12559 println!("another2");
12560 }
12561 ˇ»"#
12562 .unindent(),
12563 );
12564}
12565
12566#[gpui::test]
12567async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
12568 init_test(cx, |_| {});
12569
12570 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
12571 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
12572 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
12573 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
12574 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
12575 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
12576
12577 let buffer_1 = cx.new_model(|cx| Buffer::local(file_1_new.to_string(), cx));
12578 let buffer_2 = cx.new_model(|cx| Buffer::local(file_2_new.to_string(), cx));
12579 let buffer_3 = cx.new_model(|cx| Buffer::local(file_3_new.to_string(), cx));
12580
12581 let multi_buffer = cx.new_model(|cx| {
12582 let mut multibuffer = MultiBuffer::new(ReadWrite);
12583 multibuffer.push_excerpts(
12584 buffer_1.clone(),
12585 [
12586 ExcerptRange {
12587 context: Point::new(0, 0)..Point::new(3, 0),
12588 primary: None,
12589 },
12590 ExcerptRange {
12591 context: Point::new(5, 0)..Point::new(7, 0),
12592 primary: None,
12593 },
12594 ExcerptRange {
12595 context: Point::new(9, 0)..Point::new(10, 3),
12596 primary: None,
12597 },
12598 ],
12599 cx,
12600 );
12601 multibuffer.push_excerpts(
12602 buffer_2.clone(),
12603 [
12604 ExcerptRange {
12605 context: Point::new(0, 0)..Point::new(3, 0),
12606 primary: None,
12607 },
12608 ExcerptRange {
12609 context: Point::new(5, 0)..Point::new(7, 0),
12610 primary: None,
12611 },
12612 ExcerptRange {
12613 context: Point::new(9, 0)..Point::new(10, 3),
12614 primary: None,
12615 },
12616 ],
12617 cx,
12618 );
12619 multibuffer.push_excerpts(
12620 buffer_3.clone(),
12621 [
12622 ExcerptRange {
12623 context: Point::new(0, 0)..Point::new(3, 0),
12624 primary: None,
12625 },
12626 ExcerptRange {
12627 context: Point::new(5, 0)..Point::new(7, 0),
12628 primary: None,
12629 },
12630 ExcerptRange {
12631 context: Point::new(9, 0)..Point::new(10, 3),
12632 primary: None,
12633 },
12634 ],
12635 cx,
12636 );
12637 multibuffer
12638 });
12639
12640 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
12641 editor
12642 .update(cx, |editor, cx| {
12643 for (buffer, diff_base) in [
12644 (buffer_1.clone(), file_1_old),
12645 (buffer_2.clone(), file_2_old),
12646 (buffer_3.clone(), file_3_old),
12647 ] {
12648 let change_set = cx.new_model(|cx| {
12649 BufferChangeSet::new_with_base_text(
12650 diff_base.to_string(),
12651 buffer.read(cx).text_snapshot(),
12652 cx,
12653 )
12654 });
12655 editor.diff_map.add_change_set(change_set, cx)
12656 }
12657 })
12658 .unwrap();
12659
12660 let mut cx = EditorTestContext::for_editor(editor, cx).await;
12661 cx.run_until_parked();
12662
12663 cx.assert_editor_state(
12664 &"
12665 ˇaaa
12666 ccc
12667 ddd
12668
12669 ggg
12670 hhh
12671
12672
12673 lll
12674 mmm
12675 NNN
12676
12677 qqq
12678 rrr
12679
12680 uuu
12681 111
12682 222
12683 333
12684
12685 666
12686 777
12687
12688 000
12689 !!!"
12690 .unindent(),
12691 );
12692
12693 cx.update_editor(|editor, cx| {
12694 editor.select_all(&SelectAll, cx);
12695 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
12696 });
12697 cx.executor().run_until_parked();
12698
12699 cx.assert_state_with_diff(
12700 "
12701 «aaa
12702 - bbb
12703 ccc
12704 ddd
12705
12706 ggg
12707 hhh
12708
12709
12710 lll
12711 mmm
12712 - nnn
12713 + NNN
12714
12715 qqq
12716 rrr
12717
12718 uuu
12719 111
12720 222
12721 333
12722
12723 + 666
12724 777
12725
12726 000
12727 !!!ˇ»"
12728 .unindent(),
12729 );
12730}
12731
12732#[gpui::test]
12733async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut gpui::TestAppContext) {
12734 init_test(cx, |_| {});
12735
12736 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
12737 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\n";
12738
12739 let buffer = cx.new_model(|cx| Buffer::local(text.to_string(), cx));
12740 let multi_buffer = cx.new_model(|cx| {
12741 let mut multibuffer = MultiBuffer::new(ReadWrite);
12742 multibuffer.push_excerpts(
12743 buffer.clone(),
12744 [
12745 ExcerptRange {
12746 context: Point::new(0, 0)..Point::new(2, 0),
12747 primary: None,
12748 },
12749 ExcerptRange {
12750 context: Point::new(5, 0)..Point::new(7, 0),
12751 primary: None,
12752 },
12753 ],
12754 cx,
12755 );
12756 multibuffer
12757 });
12758
12759 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
12760 editor
12761 .update(cx, |editor, cx| {
12762 let buffer = buffer.read(cx).text_snapshot();
12763 let change_set = cx
12764 .new_model(|cx| BufferChangeSet::new_with_base_text(base.to_string(), buffer, cx));
12765 editor.diff_map.add_change_set(change_set, cx)
12766 })
12767 .unwrap();
12768
12769 let mut cx = EditorTestContext::for_editor(editor, cx).await;
12770 cx.run_until_parked();
12771
12772 cx.update_editor(|editor, cx| editor.expand_all_hunk_diffs(&Default::default(), cx));
12773 cx.executor().run_until_parked();
12774
12775 cx.assert_state_with_diff(
12776 "
12777 ˇaaa
12778 - bbb
12779 + BBB
12780
12781 - ddd
12782 - eee
12783 + EEE
12784 fff
12785 "
12786 .unindent(),
12787 );
12788}
12789
12790#[gpui::test]
12791async fn test_edits_around_expanded_insertion_hunks(
12792 executor: BackgroundExecutor,
12793 cx: &mut gpui::TestAppContext,
12794) {
12795 init_test(cx, |_| {});
12796
12797 let mut cx = EditorTestContext::new(cx).await;
12798
12799 let diff_base = r#"
12800 use some::mod1;
12801 use some::mod2;
12802
12803 const A: u32 = 42;
12804
12805 fn main() {
12806 println!("hello");
12807
12808 println!("world");
12809 }
12810 "#
12811 .unindent();
12812 executor.run_until_parked();
12813 cx.set_state(
12814 &r#"
12815 use some::mod1;
12816 use some::mod2;
12817
12818 const A: u32 = 42;
12819 const B: u32 = 42;
12820 const C: u32 = 42;
12821 ˇ
12822
12823 fn main() {
12824 println!("hello");
12825
12826 println!("world");
12827 }
12828 "#
12829 .unindent(),
12830 );
12831
12832 cx.set_diff_base(&diff_base);
12833 executor.run_until_parked();
12834
12835 cx.update_editor(|editor, cx| {
12836 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12837 });
12838 executor.run_until_parked();
12839
12840 cx.assert_state_with_diff(
12841 r#"
12842 use some::mod1;
12843 use some::mod2;
12844
12845 const A: u32 = 42;
12846 + const B: u32 = 42;
12847 + const C: u32 = 42;
12848 + ˇ
12849
12850 fn main() {
12851 println!("hello");
12852
12853 println!("world");
12854 }
12855 "#
12856 .unindent(),
12857 );
12858
12859 cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx));
12860 executor.run_until_parked();
12861
12862 cx.assert_state_with_diff(
12863 r#"
12864 use some::mod1;
12865 use some::mod2;
12866
12867 const A: u32 = 42;
12868 + const B: u32 = 42;
12869 + const C: u32 = 42;
12870 + const D: u32 = 42;
12871 + ˇ
12872
12873 fn main() {
12874 println!("hello");
12875
12876 println!("world");
12877 }
12878 "#
12879 .unindent(),
12880 );
12881
12882 cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx));
12883 executor.run_until_parked();
12884
12885 cx.assert_state_with_diff(
12886 r#"
12887 use some::mod1;
12888 use some::mod2;
12889
12890 const A: u32 = 42;
12891 + const B: u32 = 42;
12892 + const C: u32 = 42;
12893 + const D: u32 = 42;
12894 + const E: u32 = 42;
12895 + ˇ
12896
12897 fn main() {
12898 println!("hello");
12899
12900 println!("world");
12901 }
12902 "#
12903 .unindent(),
12904 );
12905
12906 cx.update_editor(|editor, cx| {
12907 editor.delete_line(&DeleteLine, cx);
12908 });
12909 executor.run_until_parked();
12910
12911 cx.assert_state_with_diff(
12912 r#"
12913 use some::mod1;
12914 use some::mod2;
12915
12916 const A: u32 = 42;
12917 + const B: u32 = 42;
12918 + const C: u32 = 42;
12919 + const D: u32 = 42;
12920 + const E: u32 = 42;
12921 ˇ
12922 fn main() {
12923 println!("hello");
12924
12925 println!("world");
12926 }
12927 "#
12928 .unindent(),
12929 );
12930
12931 cx.update_editor(|editor, cx| {
12932 editor.move_up(&MoveUp, cx);
12933 editor.delete_line(&DeleteLine, cx);
12934 editor.move_up(&MoveUp, cx);
12935 editor.delete_line(&DeleteLine, cx);
12936 editor.move_up(&MoveUp, cx);
12937 editor.delete_line(&DeleteLine, cx);
12938 });
12939 executor.run_until_parked();
12940 cx.assert_state_with_diff(
12941 r#"
12942 use some::mod1;
12943 use some::mod2;
12944
12945 const A: u32 = 42;
12946 + const B: u32 = 42;
12947 ˇ
12948 fn main() {
12949 println!("hello");
12950
12951 println!("world");
12952 }
12953 "#
12954 .unindent(),
12955 );
12956
12957 cx.update_editor(|editor, cx| {
12958 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, cx);
12959 editor.delete_line(&DeleteLine, cx);
12960 });
12961 executor.run_until_parked();
12962 cx.assert_state_with_diff(
12963 r#"
12964 use some::mod1;
12965 - use some::mod2;
12966 -
12967 - const A: u32 = 42;
12968 ˇ
12969 fn main() {
12970 println!("hello");
12971
12972 println!("world");
12973 }
12974 "#
12975 .unindent(),
12976 );
12977}
12978
12979#[gpui::test]
12980async fn test_edits_around_expanded_deletion_hunks(
12981 executor: BackgroundExecutor,
12982 cx: &mut gpui::TestAppContext,
12983) {
12984 init_test(cx, |_| {});
12985
12986 let mut cx = EditorTestContext::new(cx).await;
12987
12988 let diff_base = r#"
12989 use some::mod1;
12990 use some::mod2;
12991
12992 const A: u32 = 42;
12993 const B: u32 = 42;
12994 const C: u32 = 42;
12995
12996
12997 fn main() {
12998 println!("hello");
12999
13000 println!("world");
13001 }
13002 "#
13003 .unindent();
13004 executor.run_until_parked();
13005 cx.set_state(
13006 &r#"
13007 use some::mod1;
13008 use some::mod2;
13009
13010 ˇconst B: u32 = 42;
13011 const C: u32 = 42;
13012
13013
13014 fn main() {
13015 println!("hello");
13016
13017 println!("world");
13018 }
13019 "#
13020 .unindent(),
13021 );
13022
13023 cx.set_diff_base(&diff_base);
13024 executor.run_until_parked();
13025
13026 cx.update_editor(|editor, cx| {
13027 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
13028 });
13029 executor.run_until_parked();
13030
13031 cx.assert_state_with_diff(
13032 r#"
13033 use some::mod1;
13034 use some::mod2;
13035
13036 - const A: u32 = 42;
13037 ˇconst B: u32 = 42;
13038 const C: u32 = 42;
13039
13040
13041 fn main() {
13042 println!("hello");
13043
13044 println!("world");
13045 }
13046 "#
13047 .unindent(),
13048 );
13049
13050 cx.update_editor(|editor, cx| {
13051 editor.delete_line(&DeleteLine, cx);
13052 });
13053 executor.run_until_parked();
13054 cx.assert_state_with_diff(
13055 r#"
13056 use some::mod1;
13057 use some::mod2;
13058
13059 - const A: u32 = 42;
13060 - const B: u32 = 42;
13061 ˇconst C: u32 = 42;
13062
13063
13064 fn main() {
13065 println!("hello");
13066
13067 println!("world");
13068 }
13069 "#
13070 .unindent(),
13071 );
13072
13073 cx.update_editor(|editor, cx| {
13074 editor.delete_line(&DeleteLine, cx);
13075 });
13076 executor.run_until_parked();
13077 cx.assert_state_with_diff(
13078 r#"
13079 use some::mod1;
13080 use some::mod2;
13081
13082 - const A: u32 = 42;
13083 - const B: u32 = 42;
13084 - const C: u32 = 42;
13085 ˇ
13086
13087 fn main() {
13088 println!("hello");
13089
13090 println!("world");
13091 }
13092 "#
13093 .unindent(),
13094 );
13095
13096 cx.update_editor(|editor, cx| {
13097 editor.handle_input("replacement", cx);
13098 });
13099 executor.run_until_parked();
13100 cx.assert_state_with_diff(
13101 r#"
13102 use some::mod1;
13103 use some::mod2;
13104
13105 - const A: u32 = 42;
13106 - const B: u32 = 42;
13107 - const C: u32 = 42;
13108 -
13109 + replacementˇ
13110
13111 fn main() {
13112 println!("hello");
13113
13114 println!("world");
13115 }
13116 "#
13117 .unindent(),
13118 );
13119}
13120
13121#[gpui::test]
13122async fn test_edit_after_expanded_modification_hunk(
13123 executor: BackgroundExecutor,
13124 cx: &mut gpui::TestAppContext,
13125) {
13126 init_test(cx, |_| {});
13127
13128 let mut cx = EditorTestContext::new(cx).await;
13129
13130 let diff_base = r#"
13131 use some::mod1;
13132 use some::mod2;
13133
13134 const A: u32 = 42;
13135 const B: u32 = 42;
13136 const C: u32 = 42;
13137 const D: u32 = 42;
13138
13139
13140 fn main() {
13141 println!("hello");
13142
13143 println!("world");
13144 }"#
13145 .unindent();
13146
13147 cx.set_state(
13148 &r#"
13149 use some::mod1;
13150 use some::mod2;
13151
13152 const A: u32 = 42;
13153 const B: u32 = 42;
13154 const C: u32 = 43ˇ
13155 const D: u32 = 42;
13156
13157
13158 fn main() {
13159 println!("hello");
13160
13161 println!("world");
13162 }"#
13163 .unindent(),
13164 );
13165
13166 cx.set_diff_base(&diff_base);
13167 executor.run_until_parked();
13168 cx.update_editor(|editor, cx| {
13169 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
13170 });
13171 executor.run_until_parked();
13172
13173 cx.assert_state_with_diff(
13174 r#"
13175 use some::mod1;
13176 use some::mod2;
13177
13178 const A: u32 = 42;
13179 const B: u32 = 42;
13180 - const C: u32 = 42;
13181 + const C: u32 = 43ˇ
13182 const D: u32 = 42;
13183
13184
13185 fn main() {
13186 println!("hello");
13187
13188 println!("world");
13189 }"#
13190 .unindent(),
13191 );
13192
13193 cx.update_editor(|editor, cx| {
13194 editor.handle_input("\nnew_line\n", cx);
13195 });
13196 executor.run_until_parked();
13197
13198 cx.assert_state_with_diff(
13199 r#"
13200 use some::mod1;
13201 use some::mod2;
13202
13203 const A: u32 = 42;
13204 const B: u32 = 42;
13205 - const C: u32 = 42;
13206 + const C: u32 = 43
13207 + new_line
13208 + ˇ
13209 const D: u32 = 42;
13210
13211
13212 fn main() {
13213 println!("hello");
13214
13215 println!("world");
13216 }"#
13217 .unindent(),
13218 );
13219}
13220
13221async fn setup_indent_guides_editor(
13222 text: &str,
13223 cx: &mut gpui::TestAppContext,
13224) -> (BufferId, EditorTestContext) {
13225 init_test(cx, |_| {});
13226
13227 let mut cx = EditorTestContext::new(cx).await;
13228
13229 let buffer_id = cx.update_editor(|editor, cx| {
13230 editor.set_text(text, cx);
13231 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
13232
13233 buffer_ids[0]
13234 });
13235
13236 (buffer_id, cx)
13237}
13238
13239fn assert_indent_guides(
13240 range: Range<u32>,
13241 expected: Vec<IndentGuide>,
13242 active_indices: Option<Vec<usize>>,
13243 cx: &mut EditorTestContext,
13244) {
13245 let indent_guides = cx.update_editor(|editor, cx| {
13246 let snapshot = editor.snapshot(cx).display_snapshot;
13247 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
13248 editor,
13249 MultiBufferRow(range.start)..MultiBufferRow(range.end),
13250 true,
13251 &snapshot,
13252 cx,
13253 );
13254
13255 indent_guides.sort_by(|a, b| {
13256 a.depth.cmp(&b.depth).then(
13257 a.start_row
13258 .cmp(&b.start_row)
13259 .then(a.end_row.cmp(&b.end_row)),
13260 )
13261 });
13262 indent_guides
13263 });
13264
13265 if let Some(expected) = active_indices {
13266 let active_indices = cx.update_editor(|editor, cx| {
13267 let snapshot = editor.snapshot(cx).display_snapshot;
13268 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, cx)
13269 });
13270
13271 assert_eq!(
13272 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
13273 expected,
13274 "Active indent guide indices do not match"
13275 );
13276 }
13277
13278 let expected: Vec<_> = expected
13279 .into_iter()
13280 .map(|guide| MultiBufferIndentGuide {
13281 multibuffer_row_range: MultiBufferRow(guide.start_row)..MultiBufferRow(guide.end_row),
13282 buffer: guide,
13283 })
13284 .collect();
13285
13286 assert_eq!(indent_guides, expected, "Indent guides do not match");
13287}
13288
13289fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
13290 IndentGuide {
13291 buffer_id,
13292 start_row,
13293 end_row,
13294 depth,
13295 tab_size: 4,
13296 settings: IndentGuideSettings {
13297 enabled: true,
13298 line_width: 1,
13299 active_line_width: 1,
13300 ..Default::default()
13301 },
13302 }
13303}
13304
13305#[gpui::test]
13306async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
13307 let (buffer_id, mut cx) = setup_indent_guides_editor(
13308 &"
13309 fn main() {
13310 let a = 1;
13311 }"
13312 .unindent(),
13313 cx,
13314 )
13315 .await;
13316
13317 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
13318}
13319
13320#[gpui::test]
13321async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) {
13322 let (buffer_id, mut cx) = setup_indent_guides_editor(
13323 &"
13324 fn main() {
13325 let a = 1;
13326 let b = 2;
13327 }"
13328 .unindent(),
13329 cx,
13330 )
13331 .await;
13332
13333 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
13334}
13335
13336#[gpui::test]
13337async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) {
13338 let (buffer_id, mut cx) = setup_indent_guides_editor(
13339 &"
13340 fn main() {
13341 let a = 1;
13342 if a == 3 {
13343 let b = 2;
13344 } else {
13345 let c = 3;
13346 }
13347 }"
13348 .unindent(),
13349 cx,
13350 )
13351 .await;
13352
13353 assert_indent_guides(
13354 0..8,
13355 vec![
13356 indent_guide(buffer_id, 1, 6, 0),
13357 indent_guide(buffer_id, 3, 3, 1),
13358 indent_guide(buffer_id, 5, 5, 1),
13359 ],
13360 None,
13361 &mut cx,
13362 );
13363}
13364
13365#[gpui::test]
13366async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) {
13367 let (buffer_id, mut cx) = setup_indent_guides_editor(
13368 &"
13369 fn main() {
13370 let a = 1;
13371 let b = 2;
13372 let c = 3;
13373 }"
13374 .unindent(),
13375 cx,
13376 )
13377 .await;
13378
13379 assert_indent_guides(
13380 0..5,
13381 vec![
13382 indent_guide(buffer_id, 1, 3, 0),
13383 indent_guide(buffer_id, 2, 2, 1),
13384 ],
13385 None,
13386 &mut cx,
13387 );
13388}
13389
13390#[gpui::test]
13391async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext) {
13392 let (buffer_id, mut cx) = setup_indent_guides_editor(
13393 &"
13394 fn main() {
13395 let a = 1;
13396
13397 let c = 3;
13398 }"
13399 .unindent(),
13400 cx,
13401 )
13402 .await;
13403
13404 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
13405}
13406
13407#[gpui::test]
13408async fn test_indent_guide_complex(cx: &mut gpui::TestAppContext) {
13409 let (buffer_id, mut cx) = setup_indent_guides_editor(
13410 &"
13411 fn main() {
13412 let a = 1;
13413
13414 let c = 3;
13415
13416 if a == 3 {
13417 let b = 2;
13418 } else {
13419 let c = 3;
13420 }
13421 }"
13422 .unindent(),
13423 cx,
13424 )
13425 .await;
13426
13427 assert_indent_guides(
13428 0..11,
13429 vec![
13430 indent_guide(buffer_id, 1, 9, 0),
13431 indent_guide(buffer_id, 6, 6, 1),
13432 indent_guide(buffer_id, 8, 8, 1),
13433 ],
13434 None,
13435 &mut cx,
13436 );
13437}
13438
13439#[gpui::test]
13440async fn test_indent_guide_starts_off_screen(cx: &mut gpui::TestAppContext) {
13441 let (buffer_id, mut cx) = setup_indent_guides_editor(
13442 &"
13443 fn main() {
13444 let a = 1;
13445
13446 let c = 3;
13447
13448 if a == 3 {
13449 let b = 2;
13450 } else {
13451 let c = 3;
13452 }
13453 }"
13454 .unindent(),
13455 cx,
13456 )
13457 .await;
13458
13459 assert_indent_guides(
13460 1..11,
13461 vec![
13462 indent_guide(buffer_id, 1, 9, 0),
13463 indent_guide(buffer_id, 6, 6, 1),
13464 indent_guide(buffer_id, 8, 8, 1),
13465 ],
13466 None,
13467 &mut cx,
13468 );
13469}
13470
13471#[gpui::test]
13472async fn test_indent_guide_ends_off_screen(cx: &mut gpui::TestAppContext) {
13473 let (buffer_id, mut cx) = setup_indent_guides_editor(
13474 &"
13475 fn main() {
13476 let a = 1;
13477
13478 let c = 3;
13479
13480 if a == 3 {
13481 let b = 2;
13482 } else {
13483 let c = 3;
13484 }
13485 }"
13486 .unindent(),
13487 cx,
13488 )
13489 .await;
13490
13491 assert_indent_guides(
13492 1..10,
13493 vec![
13494 indent_guide(buffer_id, 1, 9, 0),
13495 indent_guide(buffer_id, 6, 6, 1),
13496 indent_guide(buffer_id, 8, 8, 1),
13497 ],
13498 None,
13499 &mut cx,
13500 );
13501}
13502
13503#[gpui::test]
13504async fn test_indent_guide_without_brackets(cx: &mut gpui::TestAppContext) {
13505 let (buffer_id, mut cx) = setup_indent_guides_editor(
13506 &"
13507 block1
13508 block2
13509 block3
13510 block4
13511 block2
13512 block1
13513 block1"
13514 .unindent(),
13515 cx,
13516 )
13517 .await;
13518
13519 assert_indent_guides(
13520 1..10,
13521 vec![
13522 indent_guide(buffer_id, 1, 4, 0),
13523 indent_guide(buffer_id, 2, 3, 1),
13524 indent_guide(buffer_id, 3, 3, 2),
13525 ],
13526 None,
13527 &mut cx,
13528 );
13529}
13530
13531#[gpui::test]
13532async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext) {
13533 let (buffer_id, mut cx) = setup_indent_guides_editor(
13534 &"
13535 block1
13536 block2
13537 block3
13538
13539 block1
13540 block1"
13541 .unindent(),
13542 cx,
13543 )
13544 .await;
13545
13546 assert_indent_guides(
13547 0..6,
13548 vec![
13549 indent_guide(buffer_id, 1, 2, 0),
13550 indent_guide(buffer_id, 2, 2, 1),
13551 ],
13552 None,
13553 &mut cx,
13554 );
13555}
13556
13557#[gpui::test]
13558async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext) {
13559 let (buffer_id, mut cx) = setup_indent_guides_editor(
13560 &"
13561 block1
13562
13563
13564
13565 block2
13566 "
13567 .unindent(),
13568 cx,
13569 )
13570 .await;
13571
13572 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
13573}
13574
13575#[gpui::test]
13576async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) {
13577 let (buffer_id, mut cx) = setup_indent_guides_editor(
13578 &"
13579 def a:
13580 \tb = 3
13581 \tif True:
13582 \t\tc = 4
13583 \t\td = 5
13584 \tprint(b)
13585 "
13586 .unindent(),
13587 cx,
13588 )
13589 .await;
13590
13591 assert_indent_guides(
13592 0..6,
13593 vec![
13594 indent_guide(buffer_id, 1, 6, 0),
13595 indent_guide(buffer_id, 3, 4, 1),
13596 ],
13597 None,
13598 &mut cx,
13599 );
13600}
13601
13602#[gpui::test]
13603async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
13604 let (buffer_id, mut cx) = setup_indent_guides_editor(
13605 &"
13606 fn main() {
13607 let a = 1;
13608 }"
13609 .unindent(),
13610 cx,
13611 )
13612 .await;
13613
13614 cx.update_editor(|editor, cx| {
13615 editor.change_selections(None, cx, |s| {
13616 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13617 });
13618 });
13619
13620 assert_indent_guides(
13621 0..3,
13622 vec![indent_guide(buffer_id, 1, 1, 0)],
13623 Some(vec![0]),
13624 &mut cx,
13625 );
13626}
13627
13628#[gpui::test]
13629async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppContext) {
13630 let (buffer_id, mut cx) = setup_indent_guides_editor(
13631 &"
13632 fn main() {
13633 if 1 == 2 {
13634 let a = 1;
13635 }
13636 }"
13637 .unindent(),
13638 cx,
13639 )
13640 .await;
13641
13642 cx.update_editor(|editor, cx| {
13643 editor.change_selections(None, cx, |s| {
13644 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13645 });
13646 });
13647
13648 assert_indent_guides(
13649 0..4,
13650 vec![
13651 indent_guide(buffer_id, 1, 3, 0),
13652 indent_guide(buffer_id, 2, 2, 1),
13653 ],
13654 Some(vec![1]),
13655 &mut cx,
13656 );
13657
13658 cx.update_editor(|editor, cx| {
13659 editor.change_selections(None, cx, |s| {
13660 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13661 });
13662 });
13663
13664 assert_indent_guides(
13665 0..4,
13666 vec![
13667 indent_guide(buffer_id, 1, 3, 0),
13668 indent_guide(buffer_id, 2, 2, 1),
13669 ],
13670 Some(vec![1]),
13671 &mut cx,
13672 );
13673
13674 cx.update_editor(|editor, cx| {
13675 editor.change_selections(None, cx, |s| {
13676 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
13677 });
13678 });
13679
13680 assert_indent_guides(
13681 0..4,
13682 vec![
13683 indent_guide(buffer_id, 1, 3, 0),
13684 indent_guide(buffer_id, 2, 2, 1),
13685 ],
13686 Some(vec![0]),
13687 &mut cx,
13688 );
13689}
13690
13691#[gpui::test]
13692async fn test_active_indent_guide_empty_line(cx: &mut gpui::TestAppContext) {
13693 let (buffer_id, mut cx) = setup_indent_guides_editor(
13694 &"
13695 fn main() {
13696 let a = 1;
13697
13698 let b = 2;
13699 }"
13700 .unindent(),
13701 cx,
13702 )
13703 .await;
13704
13705 cx.update_editor(|editor, cx| {
13706 editor.change_selections(None, cx, |s| {
13707 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13708 });
13709 });
13710
13711 assert_indent_guides(
13712 0..5,
13713 vec![indent_guide(buffer_id, 1, 3, 0)],
13714 Some(vec![0]),
13715 &mut cx,
13716 );
13717}
13718
13719#[gpui::test]
13720async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppContext) {
13721 let (buffer_id, mut cx) = setup_indent_guides_editor(
13722 &"
13723 def m:
13724 a = 1
13725 pass"
13726 .unindent(),
13727 cx,
13728 )
13729 .await;
13730
13731 cx.update_editor(|editor, cx| {
13732 editor.change_selections(None, cx, |s| {
13733 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13734 });
13735 });
13736
13737 assert_indent_guides(
13738 0..3,
13739 vec![indent_guide(buffer_id, 1, 2, 0)],
13740 Some(vec![0]),
13741 &mut cx,
13742 );
13743}
13744
13745#[gpui::test]
13746fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
13747 init_test(cx, |_| {});
13748
13749 let editor = cx.add_window(|cx| {
13750 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
13751 build_editor(buffer, cx)
13752 });
13753
13754 let render_args = Arc::new(Mutex::new(None));
13755 let snapshot = editor
13756 .update(cx, |editor, cx| {
13757 let snapshot = editor.buffer().read(cx).snapshot(cx);
13758 let range =
13759 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
13760
13761 struct RenderArgs {
13762 row: MultiBufferRow,
13763 folded: bool,
13764 callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
13765 }
13766
13767 let crease = Crease::inline(
13768 range,
13769 FoldPlaceholder::test(),
13770 {
13771 let toggle_callback = render_args.clone();
13772 move |row, folded, callback, _cx| {
13773 *toggle_callback.lock() = Some(RenderArgs {
13774 row,
13775 folded,
13776 callback,
13777 });
13778 div()
13779 }
13780 },
13781 |_row, _folded, _cx| div(),
13782 );
13783
13784 editor.insert_creases(Some(crease), cx);
13785 let snapshot = editor.snapshot(cx);
13786 let _div =
13787 snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
13788 snapshot
13789 })
13790 .unwrap();
13791
13792 let render_args = render_args.lock().take().unwrap();
13793 assert_eq!(render_args.row, MultiBufferRow(1));
13794 assert!(!render_args.folded);
13795 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13796
13797 cx.update_window(*editor, |_, cx| (render_args.callback)(true, cx))
13798 .unwrap();
13799 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13800 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
13801
13802 cx.update_window(*editor, |_, cx| (render_args.callback)(false, cx))
13803 .unwrap();
13804 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13805 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13806}
13807
13808#[gpui::test]
13809async fn test_input_text(cx: &mut gpui::TestAppContext) {
13810 init_test(cx, |_| {});
13811 let mut cx = EditorTestContext::new(cx).await;
13812
13813 cx.set_state(
13814 &r#"ˇone
13815 two
13816
13817 three
13818 fourˇ
13819 five
13820
13821 siˇx"#
13822 .unindent(),
13823 );
13824
13825 cx.dispatch_action(HandleInput(String::new()));
13826 cx.assert_editor_state(
13827 &r#"ˇone
13828 two
13829
13830 three
13831 fourˇ
13832 five
13833
13834 siˇx"#
13835 .unindent(),
13836 );
13837
13838 cx.dispatch_action(HandleInput("AAAA".to_string()));
13839 cx.assert_editor_state(
13840 &r#"AAAAˇone
13841 two
13842
13843 three
13844 fourAAAAˇ
13845 five
13846
13847 siAAAAˇx"#
13848 .unindent(),
13849 );
13850}
13851
13852#[gpui::test]
13853async fn test_scroll_cursor_center_top_bottom(cx: &mut gpui::TestAppContext) {
13854 init_test(cx, |_| {});
13855
13856 let mut cx = EditorTestContext::new(cx).await;
13857 cx.set_state(
13858 r#"let foo = 1;
13859let foo = 2;
13860let foo = 3;
13861let fooˇ = 4;
13862let foo = 5;
13863let foo = 6;
13864let foo = 7;
13865let foo = 8;
13866let foo = 9;
13867let foo = 10;
13868let foo = 11;
13869let foo = 12;
13870let foo = 13;
13871let foo = 14;
13872let foo = 15;"#,
13873 );
13874
13875 cx.update_editor(|e, cx| {
13876 assert_eq!(
13877 e.next_scroll_position,
13878 NextScrollCursorCenterTopBottom::Center,
13879 "Default next scroll direction is center",
13880 );
13881
13882 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13883 assert_eq!(
13884 e.next_scroll_position,
13885 NextScrollCursorCenterTopBottom::Top,
13886 "After center, next scroll direction should be top",
13887 );
13888
13889 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13890 assert_eq!(
13891 e.next_scroll_position,
13892 NextScrollCursorCenterTopBottom::Bottom,
13893 "After top, next scroll direction should be bottom",
13894 );
13895
13896 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13897 assert_eq!(
13898 e.next_scroll_position,
13899 NextScrollCursorCenterTopBottom::Center,
13900 "After bottom, scrolling should start over",
13901 );
13902
13903 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13904 assert_eq!(
13905 e.next_scroll_position,
13906 NextScrollCursorCenterTopBottom::Top,
13907 "Scrolling continues if retriggered fast enough"
13908 );
13909 });
13910
13911 cx.executor()
13912 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
13913 cx.executor().run_until_parked();
13914 cx.update_editor(|e, _| {
13915 assert_eq!(
13916 e.next_scroll_position,
13917 NextScrollCursorCenterTopBottom::Center,
13918 "If scrolling is not triggered fast enough, it should reset"
13919 );
13920 });
13921}
13922
13923#[gpui::test]
13924async fn test_goto_definition_with_find_all_references_fallback(cx: &mut gpui::TestAppContext) {
13925 init_test(cx, |_| {});
13926 let mut cx = EditorLspTestContext::new_rust(
13927 lsp::ServerCapabilities {
13928 definition_provider: Some(lsp::OneOf::Left(true)),
13929 references_provider: Some(lsp::OneOf::Left(true)),
13930 ..lsp::ServerCapabilities::default()
13931 },
13932 cx,
13933 )
13934 .await;
13935
13936 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
13937 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
13938 move |params, _| async move {
13939 if empty_go_to_definition {
13940 Ok(None)
13941 } else {
13942 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
13943 uri: params.text_document_position_params.text_document.uri,
13944 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
13945 })))
13946 }
13947 },
13948 );
13949 let references =
13950 cx.lsp
13951 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
13952 Ok(Some(vec![lsp::Location {
13953 uri: params.text_document_position.text_document.uri,
13954 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
13955 }]))
13956 });
13957 (go_to_definition, references)
13958 };
13959
13960 cx.set_state(
13961 &r#"fn one() {
13962 let mut a = ˇtwo();
13963 }
13964
13965 fn two() {}"#
13966 .unindent(),
13967 );
13968 set_up_lsp_handlers(false, &mut cx);
13969 let navigated = cx
13970 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13971 .await
13972 .expect("Failed to navigate to definition");
13973 assert_eq!(
13974 navigated,
13975 Navigated::Yes,
13976 "Should have navigated to definition from the GetDefinition response"
13977 );
13978 cx.assert_editor_state(
13979 &r#"fn one() {
13980 let mut a = two();
13981 }
13982
13983 fn «twoˇ»() {}"#
13984 .unindent(),
13985 );
13986
13987 let editors = cx.update_workspace(|workspace, cx| {
13988 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13989 });
13990 cx.update_editor(|_, test_editor_cx| {
13991 assert_eq!(
13992 editors.len(),
13993 1,
13994 "Initially, only one, test, editor should be open in the workspace"
13995 );
13996 assert_eq!(
13997 test_editor_cx.view(),
13998 editors.last().expect("Asserted len is 1")
13999 );
14000 });
14001
14002 set_up_lsp_handlers(true, &mut cx);
14003 let navigated = cx
14004 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
14005 .await
14006 .expect("Failed to navigate to lookup references");
14007 assert_eq!(
14008 navigated,
14009 Navigated::Yes,
14010 "Should have navigated to references as a fallback after empty GoToDefinition response"
14011 );
14012 // We should not change the selections in the existing file,
14013 // if opening another milti buffer with the references
14014 cx.assert_editor_state(
14015 &r#"fn one() {
14016 let mut a = two();
14017 }
14018
14019 fn «twoˇ»() {}"#
14020 .unindent(),
14021 );
14022 let editors = cx.update_workspace(|workspace, cx| {
14023 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
14024 });
14025 cx.update_editor(|_, test_editor_cx| {
14026 assert_eq!(
14027 editors.len(),
14028 2,
14029 "After falling back to references search, we open a new editor with the results"
14030 );
14031 let references_fallback_text = editors
14032 .into_iter()
14033 .find(|new_editor| new_editor != test_editor_cx.view())
14034 .expect("Should have one non-test editor now")
14035 .read(test_editor_cx)
14036 .text(test_editor_cx);
14037 assert_eq!(
14038 references_fallback_text, "fn one() {\n let mut a = two();\n}",
14039 "Should use the range from the references response and not the GoToDefinition one"
14040 );
14041 });
14042}
14043
14044#[gpui::test]
14045async fn test_find_enclosing_node_with_task(cx: &mut gpui::TestAppContext) {
14046 init_test(cx, |_| {});
14047
14048 let language = Arc::new(Language::new(
14049 LanguageConfig::default(),
14050 Some(tree_sitter_rust::LANGUAGE.into()),
14051 ));
14052
14053 let text = r#"
14054 #[cfg(test)]
14055 mod tests() {
14056 #[test]
14057 fn runnable_1() {
14058 let a = 1;
14059 }
14060
14061 #[test]
14062 fn runnable_2() {
14063 let a = 1;
14064 let b = 2;
14065 }
14066 }
14067 "#
14068 .unindent();
14069
14070 let fs = FakeFs::new(cx.executor());
14071 fs.insert_file("/file.rs", Default::default()).await;
14072
14073 let project = Project::test(fs, ["/a".as_ref()], cx).await;
14074 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
14075 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14076 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
14077 let multi_buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx));
14078
14079 let editor = cx.new_view(|cx| {
14080 Editor::new(
14081 EditorMode::Full,
14082 multi_buffer,
14083 Some(project.clone()),
14084 true,
14085 cx,
14086 )
14087 });
14088
14089 editor.update(cx, |editor, cx| {
14090 editor.tasks.insert(
14091 (buffer.read(cx).remote_id(), 3),
14092 RunnableTasks {
14093 templates: vec![],
14094 offset: MultiBufferOffset(43),
14095 column: 0,
14096 extra_variables: HashMap::default(),
14097 context_range: BufferOffset(43)..BufferOffset(85),
14098 },
14099 );
14100 editor.tasks.insert(
14101 (buffer.read(cx).remote_id(), 8),
14102 RunnableTasks {
14103 templates: vec![],
14104 offset: MultiBufferOffset(86),
14105 column: 0,
14106 extra_variables: HashMap::default(),
14107 context_range: BufferOffset(86)..BufferOffset(191),
14108 },
14109 );
14110
14111 // Test finding task when cursor is inside function body
14112 editor.change_selections(None, cx, |s| {
14113 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
14114 });
14115 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
14116 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
14117
14118 // Test finding task when cursor is on function name
14119 editor.change_selections(None, cx, |s| {
14120 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
14121 });
14122 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
14123 assert_eq!(row, 8, "Should find task when cursor is on function name");
14124 });
14125}
14126
14127#[gpui::test]
14128async fn test_multi_buffer_folding(cx: &mut gpui::TestAppContext) {
14129 init_test(cx, |_| {});
14130
14131 let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
14132 let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
14133 let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
14134
14135 let fs = FakeFs::new(cx.executor());
14136 fs.insert_tree(
14137 "/a",
14138 json!({
14139 "first.rs": sample_text_1,
14140 "second.rs": sample_text_2,
14141 "third.rs": sample_text_3,
14142 }),
14143 )
14144 .await;
14145 let project = Project::test(fs, ["/a".as_ref()], cx).await;
14146 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
14147 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14148 let worktree = project.update(cx, |project, cx| {
14149 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
14150 assert_eq!(worktrees.len(), 1);
14151 worktrees.pop().unwrap()
14152 });
14153 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
14154
14155 let buffer_1 = project
14156 .update(cx, |project, cx| {
14157 project.open_buffer((worktree_id, "first.rs"), cx)
14158 })
14159 .await
14160 .unwrap();
14161 let buffer_2 = project
14162 .update(cx, |project, cx| {
14163 project.open_buffer((worktree_id, "second.rs"), cx)
14164 })
14165 .await
14166 .unwrap();
14167 let buffer_3 = project
14168 .update(cx, |project, cx| {
14169 project.open_buffer((worktree_id, "third.rs"), cx)
14170 })
14171 .await
14172 .unwrap();
14173
14174 let multi_buffer = cx.new_model(|cx| {
14175 let mut multi_buffer = MultiBuffer::new(ReadWrite);
14176 multi_buffer.push_excerpts(
14177 buffer_1.clone(),
14178 [
14179 ExcerptRange {
14180 context: Point::new(0, 0)..Point::new(3, 0),
14181 primary: None,
14182 },
14183 ExcerptRange {
14184 context: Point::new(5, 0)..Point::new(7, 0),
14185 primary: None,
14186 },
14187 ExcerptRange {
14188 context: Point::new(9, 0)..Point::new(10, 4),
14189 primary: None,
14190 },
14191 ],
14192 cx,
14193 );
14194 multi_buffer.push_excerpts(
14195 buffer_2.clone(),
14196 [
14197 ExcerptRange {
14198 context: Point::new(0, 0)..Point::new(3, 0),
14199 primary: None,
14200 },
14201 ExcerptRange {
14202 context: Point::new(5, 0)..Point::new(7, 0),
14203 primary: None,
14204 },
14205 ExcerptRange {
14206 context: Point::new(9, 0)..Point::new(10, 4),
14207 primary: None,
14208 },
14209 ],
14210 cx,
14211 );
14212 multi_buffer.push_excerpts(
14213 buffer_3.clone(),
14214 [
14215 ExcerptRange {
14216 context: Point::new(0, 0)..Point::new(3, 0),
14217 primary: None,
14218 },
14219 ExcerptRange {
14220 context: Point::new(5, 0)..Point::new(7, 0),
14221 primary: None,
14222 },
14223 ExcerptRange {
14224 context: Point::new(9, 0)..Point::new(10, 4),
14225 primary: None,
14226 },
14227 ],
14228 cx,
14229 );
14230 multi_buffer
14231 });
14232 let multi_buffer_editor = cx.new_view(|cx| {
14233 Editor::new(
14234 EditorMode::Full,
14235 multi_buffer,
14236 Some(project.clone()),
14237 true,
14238 cx,
14239 )
14240 });
14241
14242 let full_text = "\n\n\naaaa\nbbbb\ncccc\n\n\n\nffff\ngggg\n\n\n\njjjj\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n";
14243 assert_eq!(
14244 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14245 full_text,
14246 );
14247
14248 multi_buffer_editor.update(cx, |editor, cx| {
14249 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
14250 });
14251 assert_eq!(
14252 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14253 "\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n",
14254 "After folding the first buffer, its text should not be displayed"
14255 );
14256
14257 multi_buffer_editor.update(cx, |editor, cx| {
14258 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
14259 });
14260 assert_eq!(
14261 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14262 "\n\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n",
14263 "After folding the second buffer, its text should not be displayed"
14264 );
14265
14266 multi_buffer_editor.update(cx, |editor, cx| {
14267 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
14268 });
14269 assert_eq!(
14270 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14271 "\n\n\n\n\n",
14272 "After folding the third buffer, its text should not be displayed"
14273 );
14274
14275 // Emulate selection inside the fold logic, that should work
14276 multi_buffer_editor.update(cx, |editor, cx| {
14277 editor.snapshot(cx).next_line_boundary(Point::new(0, 4));
14278 });
14279
14280 multi_buffer_editor.update(cx, |editor, cx| {
14281 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
14282 });
14283 assert_eq!(
14284 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14285 "\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n",
14286 "After unfolding the second buffer, its text should be displayed"
14287 );
14288
14289 multi_buffer_editor.update(cx, |editor, cx| {
14290 editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
14291 });
14292 assert_eq!(
14293 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14294 "\n\n\naaaa\nbbbb\ncccc\n\n\n\nffff\ngggg\n\n\n\njjjj\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n",
14295 "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
14296 );
14297
14298 multi_buffer_editor.update(cx, |editor, cx| {
14299 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
14300 });
14301 assert_eq!(
14302 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14303 full_text,
14304 "After unfolding the all buffers, all original text should be displayed"
14305 );
14306}
14307
14308#[gpui::test]
14309async fn test_multi_buffer_single_excerpts_folding(cx: &mut gpui::TestAppContext) {
14310 init_test(cx, |_| {});
14311
14312 let sample_text_1 = "1111\n2222\n3333".to_string();
14313 let sample_text_2 = "4444\n5555\n6666".to_string();
14314 let sample_text_3 = "7777\n8888\n9999".to_string();
14315
14316 let fs = FakeFs::new(cx.executor());
14317 fs.insert_tree(
14318 "/a",
14319 json!({
14320 "first.rs": sample_text_1,
14321 "second.rs": sample_text_2,
14322 "third.rs": sample_text_3,
14323 }),
14324 )
14325 .await;
14326 let project = Project::test(fs, ["/a".as_ref()], cx).await;
14327 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
14328 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14329 let worktree = project.update(cx, |project, cx| {
14330 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
14331 assert_eq!(worktrees.len(), 1);
14332 worktrees.pop().unwrap()
14333 });
14334 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
14335
14336 let buffer_1 = project
14337 .update(cx, |project, cx| {
14338 project.open_buffer((worktree_id, "first.rs"), cx)
14339 })
14340 .await
14341 .unwrap();
14342 let buffer_2 = project
14343 .update(cx, |project, cx| {
14344 project.open_buffer((worktree_id, "second.rs"), cx)
14345 })
14346 .await
14347 .unwrap();
14348 let buffer_3 = project
14349 .update(cx, |project, cx| {
14350 project.open_buffer((worktree_id, "third.rs"), cx)
14351 })
14352 .await
14353 .unwrap();
14354
14355 let multi_buffer = cx.new_model(|cx| {
14356 let mut multi_buffer = MultiBuffer::new(ReadWrite);
14357 multi_buffer.push_excerpts(
14358 buffer_1.clone(),
14359 [ExcerptRange {
14360 context: Point::new(0, 0)..Point::new(3, 0),
14361 primary: None,
14362 }],
14363 cx,
14364 );
14365 multi_buffer.push_excerpts(
14366 buffer_2.clone(),
14367 [ExcerptRange {
14368 context: Point::new(0, 0)..Point::new(3, 0),
14369 primary: None,
14370 }],
14371 cx,
14372 );
14373 multi_buffer.push_excerpts(
14374 buffer_3.clone(),
14375 [ExcerptRange {
14376 context: Point::new(0, 0)..Point::new(3, 0),
14377 primary: None,
14378 }],
14379 cx,
14380 );
14381 multi_buffer
14382 });
14383
14384 let multi_buffer_editor = cx.new_view(|cx| {
14385 Editor::new(
14386 EditorMode::Full,
14387 multi_buffer,
14388 Some(project.clone()),
14389 true,
14390 cx,
14391 )
14392 });
14393
14394 let full_text = "\n\n\n1111\n2222\n3333\n\n\n\n\n4444\n5555\n6666\n\n\n\n\n7777\n8888\n9999\n";
14395 assert_eq!(
14396 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14397 full_text,
14398 );
14399
14400 multi_buffer_editor.update(cx, |editor, cx| {
14401 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
14402 });
14403 assert_eq!(
14404 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14405 "\n\n\n\n\n4444\n5555\n6666\n\n\n\n\n7777\n8888\n9999\n",
14406 "After folding the first buffer, its text should not be displayed"
14407 );
14408
14409 multi_buffer_editor.update(cx, |editor, cx| {
14410 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
14411 });
14412
14413 assert_eq!(
14414 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14415 "\n\n\n\n\n\n\n7777\n8888\n9999\n",
14416 "After folding the second buffer, its text should not be displayed"
14417 );
14418
14419 multi_buffer_editor.update(cx, |editor, cx| {
14420 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
14421 });
14422 assert_eq!(
14423 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14424 "\n\n\n\n\n",
14425 "After folding the third buffer, its text should not be displayed"
14426 );
14427
14428 multi_buffer_editor.update(cx, |editor, cx| {
14429 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
14430 });
14431 assert_eq!(
14432 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14433 "\n\n\n\n\n4444\n5555\n6666\n\n\n",
14434 "After unfolding the second buffer, its text should be displayed"
14435 );
14436
14437 multi_buffer_editor.update(cx, |editor, cx| {
14438 editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
14439 });
14440 assert_eq!(
14441 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14442 "\n\n\n1111\n2222\n3333\n\n\n\n\n4444\n5555\n6666\n\n\n",
14443 "After unfolding the first buffer, its text should be displayed"
14444 );
14445
14446 multi_buffer_editor.update(cx, |editor, cx| {
14447 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
14448 });
14449 assert_eq!(
14450 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14451 full_text,
14452 "After unfolding all buffers, all original text should be displayed"
14453 );
14454}
14455
14456#[gpui::test]
14457async fn test_multi_buffer_with_single_excerpt_folding(cx: &mut gpui::TestAppContext) {
14458 init_test(cx, |_| {});
14459
14460 let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
14461
14462 let fs = FakeFs::new(cx.executor());
14463 fs.insert_tree(
14464 "/a",
14465 json!({
14466 "main.rs": sample_text,
14467 }),
14468 )
14469 .await;
14470 let project = Project::test(fs, ["/a".as_ref()], cx).await;
14471 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
14472 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14473 let worktree = project.update(cx, |project, cx| {
14474 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
14475 assert_eq!(worktrees.len(), 1);
14476 worktrees.pop().unwrap()
14477 });
14478 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
14479
14480 let buffer_1 = project
14481 .update(cx, |project, cx| {
14482 project.open_buffer((worktree_id, "main.rs"), cx)
14483 })
14484 .await
14485 .unwrap();
14486
14487 let multi_buffer = cx.new_model(|cx| {
14488 let mut multi_buffer = MultiBuffer::new(ReadWrite);
14489 multi_buffer.push_excerpts(
14490 buffer_1.clone(),
14491 [ExcerptRange {
14492 context: Point::new(0, 0)
14493 ..Point::new(
14494 sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
14495 0,
14496 ),
14497 primary: None,
14498 }],
14499 cx,
14500 );
14501 multi_buffer
14502 });
14503 let multi_buffer_editor = cx.new_view(|cx| {
14504 Editor::new(
14505 EditorMode::Full,
14506 multi_buffer,
14507 Some(project.clone()),
14508 true,
14509 cx,
14510 )
14511 });
14512
14513 let selection_range = Point::new(1, 0)..Point::new(2, 0);
14514 multi_buffer_editor.update(cx, |editor, cx| {
14515 enum TestHighlight {}
14516 let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
14517 let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
14518 editor.highlight_text::<TestHighlight>(
14519 vec![highlight_range.clone()],
14520 HighlightStyle::color(Hsla::green()),
14521 cx,
14522 );
14523 editor.change_selections(None, cx, |s| s.select_ranges(Some(highlight_range)));
14524 });
14525
14526 let full_text = format!("\n\n\n{sample_text}\n");
14527 assert_eq!(
14528 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14529 full_text,
14530 );
14531}
14532
14533#[gpui::test]
14534fn test_inline_completion_text(cx: &mut TestAppContext) {
14535 init_test(cx, |_| {});
14536
14537 // Simple insertion
14538 {
14539 let window = cx.add_window(|cx| {
14540 let buffer = MultiBuffer::build_simple("Hello, world!", cx);
14541 Editor::new(EditorMode::Full, buffer, None, true, cx)
14542 });
14543 let cx = &mut VisualTestContext::from_window(*window, cx);
14544
14545 window
14546 .update(cx, |editor, cx| {
14547 let snapshot = editor.snapshot(cx);
14548 let edit_range = snapshot.buffer_snapshot.anchor_after(Point::new(0, 6))
14549 ..snapshot.buffer_snapshot.anchor_before(Point::new(0, 6));
14550 let edits = vec![(edit_range, " beautiful".to_string())];
14551
14552 let InlineCompletionText::Edit { text, highlights } =
14553 inline_completion_edit_text(&snapshot, &edits, false, cx)
14554 else {
14555 panic!("Failed to generate inline completion text");
14556 };
14557
14558 assert_eq!(text, "Hello, beautiful world!");
14559 assert_eq!(highlights.len(), 1);
14560 assert_eq!(highlights[0].0, 6..16);
14561 assert_eq!(
14562 highlights[0].1.background_color,
14563 Some(cx.theme().status().created_background)
14564 );
14565 })
14566 .unwrap();
14567 }
14568
14569 // Replacement
14570 {
14571 let window = cx.add_window(|cx| {
14572 let buffer = MultiBuffer::build_simple("This is a test.", cx);
14573 Editor::new(EditorMode::Full, buffer, None, true, cx)
14574 });
14575 let cx = &mut VisualTestContext::from_window(*window, cx);
14576
14577 window
14578 .update(cx, |editor, cx| {
14579 let snapshot = editor.snapshot(cx);
14580 let edits = vec![(
14581 snapshot.buffer_snapshot.anchor_after(Point::new(0, 0))
14582 ..snapshot.buffer_snapshot.anchor_before(Point::new(0, 4)),
14583 "That".to_string(),
14584 )];
14585
14586 let InlineCompletionText::Edit { text, highlights } =
14587 inline_completion_edit_text(&snapshot, &edits, false, cx)
14588 else {
14589 panic!("Failed to generate inline completion text");
14590 };
14591
14592 assert_eq!(text, "That is a test.");
14593 assert_eq!(highlights.len(), 1);
14594 assert_eq!(highlights[0].0, 0..4);
14595 assert_eq!(
14596 highlights[0].1.background_color,
14597 Some(cx.theme().status().created_background)
14598 );
14599 })
14600 .unwrap();
14601 }
14602
14603 // Multiple edits
14604 {
14605 let window = cx.add_window(|cx| {
14606 let buffer = MultiBuffer::build_simple("Hello, world!", cx);
14607 Editor::new(EditorMode::Full, buffer, None, true, cx)
14608 });
14609 let cx = &mut VisualTestContext::from_window(*window, cx);
14610
14611 window
14612 .update(cx, |editor, cx| {
14613 let snapshot = editor.snapshot(cx);
14614 let edits = vec![
14615 (
14616 snapshot.buffer_snapshot.anchor_after(Point::new(0, 0))
14617 ..snapshot.buffer_snapshot.anchor_before(Point::new(0, 5)),
14618 "Greetings".into(),
14619 ),
14620 (
14621 snapshot.buffer_snapshot.anchor_after(Point::new(0, 12))
14622 ..snapshot.buffer_snapshot.anchor_before(Point::new(0, 12)),
14623 " and universe".into(),
14624 ),
14625 ];
14626
14627 let InlineCompletionText::Edit { text, highlights } =
14628 inline_completion_edit_text(&snapshot, &edits, false, cx)
14629 else {
14630 panic!("Failed to generate inline completion text");
14631 };
14632
14633 assert_eq!(text, "Greetings, world and universe!");
14634 assert_eq!(highlights.len(), 2);
14635 assert_eq!(highlights[0].0, 0..9);
14636 assert_eq!(highlights[1].0, 16..29);
14637 assert_eq!(
14638 highlights[0].1.background_color,
14639 Some(cx.theme().status().created_background)
14640 );
14641 assert_eq!(
14642 highlights[1].1.background_color,
14643 Some(cx.theme().status().created_background)
14644 );
14645 })
14646 .unwrap();
14647 }
14648
14649 // Multiple lines with edits
14650 {
14651 let window = cx.add_window(|cx| {
14652 let buffer =
14653 MultiBuffer::build_simple("First line\nSecond line\nThird line\nFourth line", cx);
14654 Editor::new(EditorMode::Full, buffer, None, true, cx)
14655 });
14656 let cx = &mut VisualTestContext::from_window(*window, cx);
14657
14658 window
14659 .update(cx, |editor, cx| {
14660 let snapshot = editor.snapshot(cx);
14661 let edits = vec![
14662 (
14663 snapshot.buffer_snapshot.anchor_before(Point::new(1, 7))
14664 ..snapshot.buffer_snapshot.anchor_before(Point::new(1, 11)),
14665 "modified".to_string(),
14666 ),
14667 (
14668 snapshot.buffer_snapshot.anchor_before(Point::new(2, 0))
14669 ..snapshot.buffer_snapshot.anchor_before(Point::new(2, 10)),
14670 "New third line".to_string(),
14671 ),
14672 (
14673 snapshot.buffer_snapshot.anchor_before(Point::new(3, 6))
14674 ..snapshot.buffer_snapshot.anchor_before(Point::new(3, 6)),
14675 " updated".to_string(),
14676 ),
14677 ];
14678
14679 let InlineCompletionText::Edit { text, highlights } =
14680 inline_completion_edit_text(&snapshot, &edits, false, cx)
14681 else {
14682 panic!("Failed to generate inline completion text");
14683 };
14684
14685 assert_eq!(text, "Second modified\nNew third line\nFourth updated line");
14686 assert_eq!(highlights.len(), 3);
14687 assert_eq!(highlights[0].0, 7..15); // "modified"
14688 assert_eq!(highlights[1].0, 16..30); // "New third line"
14689 assert_eq!(highlights[2].0, 37..45); // " updated"
14690
14691 for highlight in &highlights {
14692 assert_eq!(
14693 highlight.1.background_color,
14694 Some(cx.theme().status().created_background)
14695 );
14696 }
14697 })
14698 .unwrap();
14699 }
14700}
14701
14702#[gpui::test]
14703fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
14704 init_test(cx, |_| {});
14705
14706 // Deletion
14707 {
14708 let window = cx.add_window(|cx| {
14709 let buffer = MultiBuffer::build_simple("Hello, world!", cx);
14710 Editor::new(EditorMode::Full, buffer, None, true, cx)
14711 });
14712 let cx = &mut VisualTestContext::from_window(*window, cx);
14713
14714 window
14715 .update(cx, |editor, cx| {
14716 let snapshot = editor.snapshot(cx);
14717 let edit_range = snapshot.buffer_snapshot.anchor_after(Point::new(0, 5))
14718 ..snapshot.buffer_snapshot.anchor_before(Point::new(0, 11));
14719 let edits = vec![(edit_range, "".to_string())];
14720
14721 let InlineCompletionText::Edit { text, highlights } =
14722 inline_completion_edit_text(&snapshot, &edits, true, cx)
14723 else {
14724 panic!("Failed to generate inline completion text");
14725 };
14726
14727 assert_eq!(text, "Hello, world!");
14728 assert_eq!(highlights.len(), 1);
14729 assert_eq!(highlights[0].0, 5..11);
14730 assert_eq!(
14731 highlights[0].1.background_color,
14732 Some(cx.theme().status().deleted_background)
14733 );
14734 })
14735 .unwrap();
14736 }
14737
14738 // Insertion
14739 {
14740 let window = cx.add_window(|cx| {
14741 let buffer = MultiBuffer::build_simple("Hello, world!", cx);
14742 Editor::new(EditorMode::Full, buffer, None, true, cx)
14743 });
14744 let cx = &mut VisualTestContext::from_window(*window, cx);
14745
14746 window
14747 .update(cx, |editor, cx| {
14748 let snapshot = editor.snapshot(cx);
14749 let edit_range = snapshot.buffer_snapshot.anchor_after(Point::new(0, 6))
14750 ..snapshot.buffer_snapshot.anchor_before(Point::new(0, 6));
14751 let edits = vec![(edit_range, " digital".to_string())];
14752
14753 let InlineCompletionText::Edit { text, highlights } =
14754 inline_completion_edit_text(&snapshot, &edits, true, cx)
14755 else {
14756 panic!("Failed to generate inline completion text");
14757 };
14758
14759 assert_eq!(text, "Hello, digital world!");
14760 assert_eq!(highlights.len(), 1);
14761 assert_eq!(highlights[0].0, 6..14);
14762 assert_eq!(
14763 highlights[0].1.background_color,
14764 Some(cx.theme().status().created_background)
14765 );
14766 })
14767 .unwrap();
14768 }
14769}
14770
14771#[gpui::test]
14772async fn test_rename_with_duplicate_edits(cx: &mut gpui::TestAppContext) {
14773 init_test(cx, |_| {});
14774 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14775
14776 cx.set_state(indoc! {"
14777 struct Fˇoo {}
14778 "});
14779
14780 cx.update_editor(|editor, cx| {
14781 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
14782 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
14783 editor.highlight_background::<DocumentHighlightRead>(
14784 &[highlight_range],
14785 |c| c.editor_document_highlight_read_background,
14786 cx,
14787 );
14788 });
14789
14790 cx.update_editor(|e, cx| e.rename(&Rename, cx))
14791 .expect("Rename was not started")
14792 .await
14793 .expect("Rename failed");
14794 let mut rename_handler =
14795 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
14796 let edit = lsp::TextEdit {
14797 range: lsp::Range {
14798 start: lsp::Position {
14799 line: 0,
14800 character: 7,
14801 },
14802 end: lsp::Position {
14803 line: 0,
14804 character: 10,
14805 },
14806 },
14807 new_text: "FooRenamed".to_string(),
14808 };
14809 Ok(Some(lsp::WorkspaceEdit::new(
14810 // Specify the same edit twice
14811 std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
14812 )))
14813 });
14814 cx.update_editor(|e, cx| e.confirm_rename(&ConfirmRename, cx))
14815 .expect("Confirm rename was not started")
14816 .await
14817 .expect("Confirm rename failed");
14818 rename_handler.next().await.unwrap();
14819 cx.run_until_parked();
14820
14821 // Despite two edits, only one is actually applied as those are identical
14822 cx.assert_editor_state(indoc! {"
14823 struct FooRenamedˇ {}
14824 "});
14825}
14826
14827fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
14828 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
14829 point..point
14830}
14831
14832fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
14833 let (text, ranges) = marked_text_ranges(marked_text, true);
14834 assert_eq!(view.text(cx), text);
14835 assert_eq!(
14836 view.selections.ranges(cx),
14837 ranges,
14838 "Assert selections are {}",
14839 marked_text
14840 );
14841}
14842
14843pub fn handle_signature_help_request(
14844 cx: &mut EditorLspTestContext,
14845 mocked_response: lsp::SignatureHelp,
14846) -> impl Future<Output = ()> {
14847 let mut request =
14848 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
14849 let mocked_response = mocked_response.clone();
14850 async move { Ok(Some(mocked_response)) }
14851 });
14852
14853 async move {
14854 request.next().await;
14855 }
14856}
14857
14858/// Handle completion request passing a marked string specifying where the completion
14859/// should be triggered from using '|' character, what range should be replaced, and what completions
14860/// should be returned using '<' and '>' to delimit the range
14861pub fn handle_completion_request(
14862 cx: &mut EditorLspTestContext,
14863 marked_string: &str,
14864 completions: Vec<&'static str>,
14865 counter: Arc<AtomicUsize>,
14866) -> impl Future<Output = ()> {
14867 let complete_from_marker: TextRangeMarker = '|'.into();
14868 let replace_range_marker: TextRangeMarker = ('<', '>').into();
14869 let (_, mut marked_ranges) = marked_text_ranges_by(
14870 marked_string,
14871 vec![complete_from_marker.clone(), replace_range_marker.clone()],
14872 );
14873
14874 let complete_from_position =
14875 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
14876 let replace_range =
14877 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
14878
14879 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
14880 let completions = completions.clone();
14881 counter.fetch_add(1, atomic::Ordering::Release);
14882 async move {
14883 assert_eq!(params.text_document_position.text_document.uri, url.clone());
14884 assert_eq!(
14885 params.text_document_position.position,
14886 complete_from_position
14887 );
14888 Ok(Some(lsp::CompletionResponse::Array(
14889 completions
14890 .iter()
14891 .map(|completion_text| lsp::CompletionItem {
14892 label: completion_text.to_string(),
14893 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14894 range: replace_range,
14895 new_text: completion_text.to_string(),
14896 })),
14897 ..Default::default()
14898 })
14899 .collect(),
14900 )))
14901 }
14902 });
14903
14904 async move {
14905 request.next().await;
14906 }
14907}
14908
14909fn handle_resolve_completion_request(
14910 cx: &mut EditorLspTestContext,
14911 edits: Option<Vec<(&'static str, &'static str)>>,
14912) -> impl Future<Output = ()> {
14913 let edits = edits.map(|edits| {
14914 edits
14915 .iter()
14916 .map(|(marked_string, new_text)| {
14917 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
14918 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
14919 lsp::TextEdit::new(replace_range, new_text.to_string())
14920 })
14921 .collect::<Vec<_>>()
14922 });
14923
14924 let mut request =
14925 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
14926 let edits = edits.clone();
14927 async move {
14928 Ok(lsp::CompletionItem {
14929 additional_text_edits: edits,
14930 ..Default::default()
14931 })
14932 }
14933 });
14934
14935 async move {
14936 request.next().await;
14937 }
14938}
14939
14940pub(crate) fn update_test_language_settings(
14941 cx: &mut TestAppContext,
14942 f: impl Fn(&mut AllLanguageSettingsContent),
14943) {
14944 cx.update(|cx| {
14945 SettingsStore::update_global(cx, |store, cx| {
14946 store.update_user_settings::<AllLanguageSettings>(cx, f);
14947 });
14948 });
14949}
14950
14951pub(crate) fn update_test_project_settings(
14952 cx: &mut TestAppContext,
14953 f: impl Fn(&mut ProjectSettings),
14954) {
14955 cx.update(|cx| {
14956 SettingsStore::update_global(cx, |store, cx| {
14957 store.update_user_settings::<ProjectSettings>(cx, f);
14958 });
14959 });
14960}
14961
14962pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
14963 cx.update(|cx| {
14964 assets::Assets.load_test_fonts(cx);
14965 let store = SettingsStore::test(cx);
14966 cx.set_global(store);
14967 theme::init(theme::LoadThemes::JustBase, cx);
14968 release_channel::init(SemanticVersion::default(), cx);
14969 client::init_settings(cx);
14970 language::init(cx);
14971 Project::init_settings(cx);
14972 workspace::init_settings(cx);
14973 crate::init(cx);
14974 });
14975
14976 update_test_language_settings(cx, f);
14977}
14978
14979#[track_caller]
14980fn assert_hunk_revert(
14981 not_reverted_text_with_selections: &str,
14982 expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
14983 expected_reverted_text_with_selections: &str,
14984 base_text: &str,
14985 cx: &mut EditorLspTestContext,
14986) {
14987 cx.set_state(not_reverted_text_with_selections);
14988 cx.set_diff_base(base_text);
14989 cx.executor().run_until_parked();
14990
14991 let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
14992 let snapshot = editor.snapshot(cx);
14993 let reverted_hunk_statuses = snapshot
14994 .diff_map
14995 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len(), &snapshot.buffer_snapshot)
14996 .map(|hunk| hunk_status(&hunk))
14997 .collect::<Vec<_>>();
14998
14999 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
15000 reverted_hunk_statuses
15001 });
15002 cx.executor().run_until_parked();
15003 cx.assert_editor_state(expected_reverted_text_with_selections);
15004 assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
15005}