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 pretty_assertions::{assert_eq, assert_ne};
29use project::{buffer_store::BufferChangeSet, FakeFs};
30use project::{
31 lsp_command::SIGNATURE_HELP_HIGHLIGHT_CURRENT,
32 project_settings::{LspSettings, ProjectSettings},
33};
34use serde_json::{self, json};
35use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
36use std::{
37 iter,
38 sync::atomic::{self, AtomicUsize},
39};
40use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
41use unindent::Unindent;
42use util::{
43 assert_set_eq,
44 test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
45};
46use workspace::{
47 item::{FollowEvent, FollowableItem, Item, ItemHandle},
48 NavigationEntry, ViewId,
49};
50
51#[gpui::test]
52fn test_edit_events(cx: &mut TestAppContext) {
53 init_test(cx, |_| {});
54
55 let buffer = cx.new_model(|cx| {
56 let mut buffer = language::Buffer::local("123456", cx);
57 buffer.set_group_interval(Duration::from_secs(1));
58 buffer
59 });
60
61 let events = Rc::new(RefCell::new(Vec::new()));
62 let editor1 = cx.add_window({
63 let events = events.clone();
64 |cx| {
65 let view = cx.view().clone();
66 cx.subscribe(&view, move |_, _, event: &EditorEvent, _| match event {
67 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
68 EditorEvent::BufferEdited => events.borrow_mut().push(("editor1", "buffer edited")),
69 _ => {}
70 })
71 .detach();
72 Editor::for_buffer(buffer.clone(), None, cx)
73 }
74 });
75
76 let editor2 = cx.add_window({
77 let events = events.clone();
78 |cx| {
79 cx.subscribe(
80 &cx.view().clone(),
81 move |_, _, event: &EditorEvent, _| match event {
82 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
83 EditorEvent::BufferEdited => {
84 events.borrow_mut().push(("editor2", "buffer edited"))
85 }
86 _ => {}
87 },
88 )
89 .detach();
90 Editor::for_buffer(buffer.clone(), None, cx)
91 }
92 });
93
94 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
95
96 // Mutating editor 1 will emit an `Edited` event only for that editor.
97 _ = editor1.update(cx, |editor, cx| editor.insert("X", cx));
98 assert_eq!(
99 mem::take(&mut *events.borrow_mut()),
100 [
101 ("editor1", "edited"),
102 ("editor1", "buffer edited"),
103 ("editor2", "buffer edited"),
104 ]
105 );
106
107 // Mutating editor 2 will emit an `Edited` event only for that editor.
108 _ = editor2.update(cx, |editor, cx| editor.delete(&Delete, cx));
109 assert_eq!(
110 mem::take(&mut *events.borrow_mut()),
111 [
112 ("editor2", "edited"),
113 ("editor1", "buffer edited"),
114 ("editor2", "buffer edited"),
115 ]
116 );
117
118 // Undoing on editor 1 will emit an `Edited` event only for that editor.
119 _ = editor1.update(cx, |editor, cx| editor.undo(&Undo, cx));
120 assert_eq!(
121 mem::take(&mut *events.borrow_mut()),
122 [
123 ("editor1", "edited"),
124 ("editor1", "buffer edited"),
125 ("editor2", "buffer edited"),
126 ]
127 );
128
129 // Redoing on editor 1 will emit an `Edited` event only for that editor.
130 _ = editor1.update(cx, |editor, cx| editor.redo(&Redo, cx));
131 assert_eq!(
132 mem::take(&mut *events.borrow_mut()),
133 [
134 ("editor1", "edited"),
135 ("editor1", "buffer edited"),
136 ("editor2", "buffer edited"),
137 ]
138 );
139
140 // Undoing on editor 2 will emit an `Edited` event only for that editor.
141 _ = editor2.update(cx, |editor, cx| editor.undo(&Undo, cx));
142 assert_eq!(
143 mem::take(&mut *events.borrow_mut()),
144 [
145 ("editor2", "edited"),
146 ("editor1", "buffer edited"),
147 ("editor2", "buffer edited"),
148 ]
149 );
150
151 // Redoing on editor 2 will emit an `Edited` event only for that editor.
152 _ = editor2.update(cx, |editor, cx| editor.redo(&Redo, cx));
153 assert_eq!(
154 mem::take(&mut *events.borrow_mut()),
155 [
156 ("editor2", "edited"),
157 ("editor1", "buffer edited"),
158 ("editor2", "buffer edited"),
159 ]
160 );
161
162 // No event is emitted when the mutation is a no-op.
163 _ = editor2.update(cx, |editor, cx| {
164 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
165
166 editor.backspace(&Backspace, cx);
167 });
168 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
169}
170
171#[gpui::test]
172fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
173 init_test(cx, |_| {});
174
175 let mut now = Instant::now();
176 let group_interval = Duration::from_millis(1);
177 let buffer = cx.new_model(|cx| {
178 let mut buf = language::Buffer::local("123456", cx);
179 buf.set_group_interval(group_interval);
180 buf
181 });
182 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
183 let editor = cx.add_window(|cx| build_editor(buffer.clone(), cx));
184
185 _ = editor.update(cx, |editor, cx| {
186 editor.start_transaction_at(now, cx);
187 editor.change_selections(None, cx, |s| s.select_ranges([2..4]));
188
189 editor.insert("cd", cx);
190 editor.end_transaction_at(now, cx);
191 assert_eq!(editor.text(cx), "12cd56");
192 assert_eq!(editor.selections.ranges(cx), vec![4..4]);
193
194 editor.start_transaction_at(now, cx);
195 editor.change_selections(None, cx, |s| s.select_ranges([4..5]));
196 editor.insert("e", cx);
197 editor.end_transaction_at(now, cx);
198 assert_eq!(editor.text(cx), "12cde6");
199 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
200
201 now += group_interval + Duration::from_millis(1);
202 editor.change_selections(None, cx, |s| s.select_ranges([2..2]));
203
204 // Simulate an edit in another editor
205 buffer.update(cx, |buffer, cx| {
206 buffer.start_transaction_at(now, cx);
207 buffer.edit([(0..1, "a")], None, cx);
208 buffer.edit([(1..1, "b")], None, cx);
209 buffer.end_transaction_at(now, cx);
210 });
211
212 assert_eq!(editor.text(cx), "ab2cde6");
213 assert_eq!(editor.selections.ranges(cx), vec![3..3]);
214
215 // Last transaction happened past the group interval in a different editor.
216 // Undo it individually and don't restore selections.
217 editor.undo(&Undo, cx);
218 assert_eq!(editor.text(cx), "12cde6");
219 assert_eq!(editor.selections.ranges(cx), vec![2..2]);
220
221 // First two transactions happened within the group interval in this editor.
222 // Undo them together and restore selections.
223 editor.undo(&Undo, cx);
224 editor.undo(&Undo, cx); // Undo stack is empty here, so this is a no-op.
225 assert_eq!(editor.text(cx), "123456");
226 assert_eq!(editor.selections.ranges(cx), vec![0..0]);
227
228 // Redo the first two transactions together.
229 editor.redo(&Redo, cx);
230 assert_eq!(editor.text(cx), "12cde6");
231 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
232
233 // Redo the last transaction on its own.
234 editor.redo(&Redo, cx);
235 assert_eq!(editor.text(cx), "ab2cde6");
236 assert_eq!(editor.selections.ranges(cx), vec![6..6]);
237
238 // Test empty transactions.
239 editor.start_transaction_at(now, cx);
240 editor.end_transaction_at(now, cx);
241 editor.undo(&Undo, cx);
242 assert_eq!(editor.text(cx), "12cde6");
243 });
244}
245
246#[gpui::test]
247fn test_ime_composition(cx: &mut TestAppContext) {
248 init_test(cx, |_| {});
249
250 let buffer = cx.new_model(|cx| {
251 let mut buffer = language::Buffer::local("abcde", cx);
252 // Ensure automatic grouping doesn't occur.
253 buffer.set_group_interval(Duration::ZERO);
254 buffer
255 });
256
257 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
258 cx.add_window(|cx| {
259 let mut editor = build_editor(buffer.clone(), cx);
260
261 // Start a new IME composition.
262 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
263 editor.replace_and_mark_text_in_range(Some(0..1), "á", None, cx);
264 editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, cx);
265 assert_eq!(editor.text(cx), "äbcde");
266 assert_eq!(
267 editor.marked_text_ranges(cx),
268 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
269 );
270
271 // Finalize IME composition.
272 editor.replace_text_in_range(None, "ā", cx);
273 assert_eq!(editor.text(cx), "ābcde");
274 assert_eq!(editor.marked_text_ranges(cx), None);
275
276 // IME composition edits are grouped and are undone/redone at once.
277 editor.undo(&Default::default(), cx);
278 assert_eq!(editor.text(cx), "abcde");
279 assert_eq!(editor.marked_text_ranges(cx), None);
280 editor.redo(&Default::default(), cx);
281 assert_eq!(editor.text(cx), "ābcde");
282 assert_eq!(editor.marked_text_ranges(cx), None);
283
284 // Start a new IME composition.
285 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
286 assert_eq!(
287 editor.marked_text_ranges(cx),
288 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
289 );
290
291 // Undoing during an IME composition cancels it.
292 editor.undo(&Default::default(), cx);
293 assert_eq!(editor.text(cx), "ābcde");
294 assert_eq!(editor.marked_text_ranges(cx), None);
295
296 // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
297 editor.replace_and_mark_text_in_range(Some(4..999), "è", None, cx);
298 assert_eq!(editor.text(cx), "ābcdè");
299 assert_eq!(
300 editor.marked_text_ranges(cx),
301 Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
302 );
303
304 // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
305 editor.replace_text_in_range(Some(4..999), "ę", cx);
306 assert_eq!(editor.text(cx), "ābcdę");
307 assert_eq!(editor.marked_text_ranges(cx), None);
308
309 // Start a new IME composition with multiple cursors.
310 editor.change_selections(None, cx, |s| {
311 s.select_ranges([
312 OffsetUtf16(1)..OffsetUtf16(1),
313 OffsetUtf16(3)..OffsetUtf16(3),
314 OffsetUtf16(5)..OffsetUtf16(5),
315 ])
316 });
317 editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, cx);
318 assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
319 assert_eq!(
320 editor.marked_text_ranges(cx),
321 Some(vec![
322 OffsetUtf16(0)..OffsetUtf16(3),
323 OffsetUtf16(4)..OffsetUtf16(7),
324 OffsetUtf16(8)..OffsetUtf16(11)
325 ])
326 );
327
328 // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
329 editor.replace_and_mark_text_in_range(Some(1..2), "1", None, cx);
330 assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
331 assert_eq!(
332 editor.marked_text_ranges(cx),
333 Some(vec![
334 OffsetUtf16(1)..OffsetUtf16(2),
335 OffsetUtf16(5)..OffsetUtf16(6),
336 OffsetUtf16(9)..OffsetUtf16(10)
337 ])
338 );
339
340 // Finalize IME composition with multiple cursors.
341 editor.replace_text_in_range(Some(9..10), "2", cx);
342 assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
343 assert_eq!(editor.marked_text_ranges(cx), None);
344
345 editor
346 });
347}
348
349#[gpui::test]
350fn test_selection_with_mouse(cx: &mut TestAppContext) {
351 init_test(cx, |_| {});
352
353 let editor = cx.add_window(|cx| {
354 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
355 build_editor(buffer, cx)
356 });
357
358 _ = editor.update(cx, |view, cx| {
359 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
360 });
361 assert_eq!(
362 editor
363 .update(cx, |view, cx| view.selections.display_ranges(cx))
364 .unwrap(),
365 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
366 );
367
368 _ = editor.update(cx, |view, cx| {
369 view.update_selection(
370 DisplayPoint::new(DisplayRow(3), 3),
371 0,
372 gpui::Point::<f32>::default(),
373 cx,
374 );
375 });
376
377 assert_eq!(
378 editor
379 .update(cx, |view, cx| view.selections.display_ranges(cx))
380 .unwrap(),
381 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
382 );
383
384 _ = editor.update(cx, |view, cx| {
385 view.update_selection(
386 DisplayPoint::new(DisplayRow(1), 1),
387 0,
388 gpui::Point::<f32>::default(),
389 cx,
390 );
391 });
392
393 assert_eq!(
394 editor
395 .update(cx, |view, cx| view.selections.display_ranges(cx))
396 .unwrap(),
397 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
398 );
399
400 _ = editor.update(cx, |view, cx| {
401 view.end_selection(cx);
402 view.update_selection(
403 DisplayPoint::new(DisplayRow(3), 3),
404 0,
405 gpui::Point::<f32>::default(),
406 cx,
407 );
408 });
409
410 assert_eq!(
411 editor
412 .update(cx, |view, cx| view.selections.display_ranges(cx))
413 .unwrap(),
414 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
415 );
416
417 _ = editor.update(cx, |view, cx| {
418 view.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, cx);
419 view.update_selection(
420 DisplayPoint::new(DisplayRow(0), 0),
421 0,
422 gpui::Point::<f32>::default(),
423 cx,
424 );
425 });
426
427 assert_eq!(
428 editor
429 .update(cx, |view, cx| view.selections.display_ranges(cx))
430 .unwrap(),
431 [
432 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
433 DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
434 ]
435 );
436
437 _ = editor.update(cx, |view, cx| {
438 view.end_selection(cx);
439 });
440
441 assert_eq!(
442 editor
443 .update(cx, |view, cx| view.selections.display_ranges(cx))
444 .unwrap(),
445 [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
446 );
447}
448
449#[gpui::test]
450fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
451 init_test(cx, |_| {});
452
453 let editor = cx.add_window(|cx| {
454 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
455 build_editor(buffer, cx)
456 });
457
458 _ = editor.update(cx, |view, cx| {
459 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, cx);
460 });
461
462 _ = editor.update(cx, |view, cx| {
463 view.end_selection(cx);
464 });
465
466 _ = editor.update(cx, |view, cx| {
467 view.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, cx);
468 });
469
470 _ = editor.update(cx, |view, cx| {
471 view.end_selection(cx);
472 });
473
474 assert_eq!(
475 editor
476 .update(cx, |view, cx| view.selections.display_ranges(cx))
477 .unwrap(),
478 [
479 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
480 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
481 ]
482 );
483
484 _ = editor.update(cx, |view, cx| {
485 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, cx);
486 });
487
488 _ = editor.update(cx, |view, cx| {
489 view.end_selection(cx);
490 });
491
492 assert_eq!(
493 editor
494 .update(cx, |view, cx| view.selections.display_ranges(cx))
495 .unwrap(),
496 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
497 );
498}
499
500#[gpui::test]
501fn test_canceling_pending_selection(cx: &mut TestAppContext) {
502 init_test(cx, |_| {});
503
504 let view = cx.add_window(|cx| {
505 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
506 build_editor(buffer, cx)
507 });
508
509 _ = view.update(cx, |view, cx| {
510 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
511 assert_eq!(
512 view.selections.display_ranges(cx),
513 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
514 );
515 });
516
517 _ = view.update(cx, |view, cx| {
518 view.update_selection(
519 DisplayPoint::new(DisplayRow(3), 3),
520 0,
521 gpui::Point::<f32>::default(),
522 cx,
523 );
524 assert_eq!(
525 view.selections.display_ranges(cx),
526 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
527 );
528 });
529
530 _ = view.update(cx, |view, cx| {
531 view.cancel(&Cancel, cx);
532 view.update_selection(
533 DisplayPoint::new(DisplayRow(1), 1),
534 0,
535 gpui::Point::<f32>::default(),
536 cx,
537 );
538 assert_eq!(
539 view.selections.display_ranges(cx),
540 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
541 );
542 });
543}
544
545#[gpui::test]
546fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
547 init_test(cx, |_| {});
548
549 let view = cx.add_window(|cx| {
550 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
551 build_editor(buffer, cx)
552 });
553
554 _ = view.update(cx, |view, cx| {
555 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
556 assert_eq!(
557 view.selections.display_ranges(cx),
558 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
559 );
560
561 view.move_down(&Default::default(), cx);
562 assert_eq!(
563 view.selections.display_ranges(cx),
564 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
565 );
566
567 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
568 assert_eq!(
569 view.selections.display_ranges(cx),
570 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
571 );
572
573 view.move_up(&Default::default(), cx);
574 assert_eq!(
575 view.selections.display_ranges(cx),
576 [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
577 );
578 });
579}
580
581#[gpui::test]
582fn test_clone(cx: &mut TestAppContext) {
583 init_test(cx, |_| {});
584
585 let (text, selection_ranges) = marked_text_ranges(
586 indoc! {"
587 one
588 two
589 threeˇ
590 four
591 fiveˇ
592 "},
593 true,
594 );
595
596 let editor = cx.add_window(|cx| {
597 let buffer = MultiBuffer::build_simple(&text, cx);
598 build_editor(buffer, cx)
599 });
600
601 _ = editor.update(cx, |editor, cx| {
602 editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
603 editor.fold_creases(
604 vec![
605 Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
606 Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
607 ],
608 true,
609 cx,
610 );
611 });
612
613 let cloned_editor = editor
614 .update(cx, |editor, cx| {
615 cx.open_window(Default::default(), |cx| cx.new_view(|cx| editor.clone(cx)))
616 })
617 .unwrap()
618 .unwrap();
619
620 let snapshot = editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
621 let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
622
623 assert_eq!(
624 cloned_editor
625 .update(cx, |e, cx| e.display_text(cx))
626 .unwrap(),
627 editor.update(cx, |e, cx| e.display_text(cx)).unwrap()
628 );
629 assert_eq!(
630 cloned_snapshot
631 .folds_in_range(0..text.len())
632 .collect::<Vec<_>>(),
633 snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
634 );
635 assert_set_eq!(
636 cloned_editor
637 .update(cx, |editor, cx| editor.selections.ranges::<Point>(cx))
638 .unwrap(),
639 editor
640 .update(cx, |editor, cx| editor.selections.ranges(cx))
641 .unwrap()
642 );
643 assert_set_eq!(
644 cloned_editor
645 .update(cx, |e, cx| e.selections.display_ranges(cx))
646 .unwrap(),
647 editor
648 .update(cx, |e, cx| e.selections.display_ranges(cx))
649 .unwrap()
650 );
651}
652
653#[gpui::test]
654async fn test_navigation_history(cx: &mut TestAppContext) {
655 init_test(cx, |_| {});
656
657 use workspace::item::Item;
658
659 let fs = FakeFs::new(cx.executor());
660 let project = Project::test(fs, [], cx).await;
661 let workspace = cx.add_window(|cx| Workspace::test_new(project, cx));
662 let pane = workspace
663 .update(cx, |workspace, _| workspace.active_pane().clone())
664 .unwrap();
665
666 _ = workspace.update(cx, |_v, cx| {
667 cx.new_view(|cx| {
668 let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
669 let mut editor = build_editor(buffer.clone(), cx);
670 let handle = cx.view();
671 editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(handle)));
672
673 fn pop_history(editor: &mut Editor, cx: &mut WindowContext) -> Option<NavigationEntry> {
674 editor.nav_history.as_mut().unwrap().pop_backward(cx)
675 }
676
677 // Move the cursor a small distance.
678 // Nothing is added to the navigation history.
679 editor.change_selections(None, cx, |s| {
680 s.select_display_ranges([
681 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
682 ])
683 });
684 editor.change_selections(None, cx, |s| {
685 s.select_display_ranges([
686 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
687 ])
688 });
689 assert!(pop_history(&mut editor, cx).is_none());
690
691 // Move the cursor a large distance.
692 // The history can jump back to the previous position.
693 editor.change_selections(None, cx, |s| {
694 s.select_display_ranges([
695 DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
696 ])
697 });
698 let nav_entry = pop_history(&mut editor, cx).unwrap();
699 editor.navigate(nav_entry.data.unwrap(), cx);
700 assert_eq!(nav_entry.item.id(), cx.entity_id());
701 assert_eq!(
702 editor.selections.display_ranges(cx),
703 &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
704 );
705 assert!(pop_history(&mut editor, cx).is_none());
706
707 // Move the cursor a small distance via the mouse.
708 // Nothing is added to the navigation history.
709 editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, cx);
710 editor.end_selection(cx);
711 assert_eq!(
712 editor.selections.display_ranges(cx),
713 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
714 );
715 assert!(pop_history(&mut editor, cx).is_none());
716
717 // Move the cursor a large distance via the mouse.
718 // The history can jump back to the previous position.
719 editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, cx);
720 editor.end_selection(cx);
721 assert_eq!(
722 editor.selections.display_ranges(cx),
723 &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
724 );
725 let nav_entry = pop_history(&mut editor, cx).unwrap();
726 editor.navigate(nav_entry.data.unwrap(), cx);
727 assert_eq!(nav_entry.item.id(), cx.entity_id());
728 assert_eq!(
729 editor.selections.display_ranges(cx),
730 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
731 );
732 assert!(pop_history(&mut editor, cx).is_none());
733
734 // Set scroll position to check later
735 editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), cx);
736 let original_scroll_position = editor.scroll_manager.anchor();
737
738 // Jump to the end of the document and adjust scroll
739 editor.move_to_end(&MoveToEnd, cx);
740 editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), cx);
741 assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
742
743 let nav_entry = pop_history(&mut editor, cx).unwrap();
744 editor.navigate(nav_entry.data.unwrap(), cx);
745 assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
746
747 // Ensure we don't panic when navigation data contains invalid anchors *and* points.
748 let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
749 invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
750 let invalid_point = Point::new(9999, 0);
751 editor.navigate(
752 Box::new(NavigationData {
753 cursor_anchor: invalid_anchor,
754 cursor_position: invalid_point,
755 scroll_anchor: ScrollAnchor {
756 anchor: invalid_anchor,
757 offset: Default::default(),
758 },
759 scroll_top_row: invalid_point.row,
760 }),
761 cx,
762 );
763 assert_eq!(
764 editor.selections.display_ranges(cx),
765 &[editor.max_point(cx)..editor.max_point(cx)]
766 );
767 assert_eq!(
768 editor.scroll_position(cx),
769 gpui::Point::new(0., editor.max_point(cx).row().as_f32())
770 );
771
772 editor
773 })
774 });
775}
776
777#[gpui::test]
778fn test_cancel(cx: &mut TestAppContext) {
779 init_test(cx, |_| {});
780
781 let view = cx.add_window(|cx| {
782 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
783 build_editor(buffer, cx)
784 });
785
786 _ = view.update(cx, |view, cx| {
787 view.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, cx);
788 view.update_selection(
789 DisplayPoint::new(DisplayRow(1), 1),
790 0,
791 gpui::Point::<f32>::default(),
792 cx,
793 );
794 view.end_selection(cx);
795
796 view.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, cx);
797 view.update_selection(
798 DisplayPoint::new(DisplayRow(0), 3),
799 0,
800 gpui::Point::<f32>::default(),
801 cx,
802 );
803 view.end_selection(cx);
804 assert_eq!(
805 view.selections.display_ranges(cx),
806 [
807 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
808 DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
809 ]
810 );
811 });
812
813 _ = view.update(cx, |view, cx| {
814 view.cancel(&Cancel, cx);
815 assert_eq!(
816 view.selections.display_ranges(cx),
817 [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
818 );
819 });
820
821 _ = view.update(cx, |view, cx| {
822 view.cancel(&Cancel, cx);
823 assert_eq!(
824 view.selections.display_ranges(cx),
825 [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
826 );
827 });
828}
829
830#[gpui::test]
831fn test_fold_action(cx: &mut TestAppContext) {
832 init_test(cx, |_| {});
833
834 let view = cx.add_window(|cx| {
835 let buffer = MultiBuffer::build_simple(
836 &"
837 impl Foo {
838 // Hello!
839
840 fn a() {
841 1
842 }
843
844 fn b() {
845 2
846 }
847
848 fn c() {
849 3
850 }
851 }
852 "
853 .unindent(),
854 cx,
855 );
856 build_editor(buffer.clone(), cx)
857 });
858
859 _ = view.update(cx, |view, cx| {
860 view.change_selections(None, cx, |s| {
861 s.select_display_ranges([
862 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
863 ]);
864 });
865 view.fold(&Fold, cx);
866 assert_eq!(
867 view.display_text(cx),
868 "
869 impl Foo {
870 // Hello!
871
872 fn a() {
873 1
874 }
875
876 fn b() {⋯
877 }
878
879 fn c() {⋯
880 }
881 }
882 "
883 .unindent(),
884 );
885
886 view.fold(&Fold, cx);
887 assert_eq!(
888 view.display_text(cx),
889 "
890 impl Foo {⋯
891 }
892 "
893 .unindent(),
894 );
895
896 view.unfold_lines(&UnfoldLines, cx);
897 assert_eq!(
898 view.display_text(cx),
899 "
900 impl Foo {
901 // Hello!
902
903 fn a() {
904 1
905 }
906
907 fn b() {⋯
908 }
909
910 fn c() {⋯
911 }
912 }
913 "
914 .unindent(),
915 );
916
917 view.unfold_lines(&UnfoldLines, cx);
918 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
919 });
920}
921
922#[gpui::test]
923fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
924 init_test(cx, |_| {});
925
926 let view = cx.add_window(|cx| {
927 let buffer = MultiBuffer::build_simple(
928 &"
929 class Foo:
930 # Hello!
931
932 def a():
933 print(1)
934
935 def b():
936 print(2)
937
938 def c():
939 print(3)
940 "
941 .unindent(),
942 cx,
943 );
944 build_editor(buffer.clone(), cx)
945 });
946
947 _ = view.update(cx, |view, cx| {
948 view.change_selections(None, cx, |s| {
949 s.select_display_ranges([
950 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
951 ]);
952 });
953 view.fold(&Fold, cx);
954 assert_eq!(
955 view.display_text(cx),
956 "
957 class Foo:
958 # Hello!
959
960 def a():
961 print(1)
962
963 def b():⋯
964
965 def c():⋯
966 "
967 .unindent(),
968 );
969
970 view.fold(&Fold, cx);
971 assert_eq!(
972 view.display_text(cx),
973 "
974 class Foo:⋯
975 "
976 .unindent(),
977 );
978
979 view.unfold_lines(&UnfoldLines, cx);
980 assert_eq!(
981 view.display_text(cx),
982 "
983 class Foo:
984 # Hello!
985
986 def a():
987 print(1)
988
989 def b():⋯
990
991 def c():⋯
992 "
993 .unindent(),
994 );
995
996 view.unfold_lines(&UnfoldLines, cx);
997 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
998 });
999}
1000
1001#[gpui::test]
1002fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
1003 init_test(cx, |_| {});
1004
1005 let view = cx.add_window(|cx| {
1006 let buffer = MultiBuffer::build_simple(
1007 &"
1008 class Foo:
1009 # Hello!
1010
1011 def a():
1012 print(1)
1013
1014 def b():
1015 print(2)
1016
1017
1018 def c():
1019 print(3)
1020
1021
1022 "
1023 .unindent(),
1024 cx,
1025 );
1026 build_editor(buffer.clone(), cx)
1027 });
1028
1029 _ = view.update(cx, |view, cx| {
1030 view.change_selections(None, cx, |s| {
1031 s.select_display_ranges([
1032 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
1033 ]);
1034 });
1035 view.fold(&Fold, cx);
1036 assert_eq!(
1037 view.display_text(cx),
1038 "
1039 class Foo:
1040 # Hello!
1041
1042 def a():
1043 print(1)
1044
1045 def b():⋯
1046
1047
1048 def c():⋯
1049
1050
1051 "
1052 .unindent(),
1053 );
1054
1055 view.fold(&Fold, cx);
1056 assert_eq!(
1057 view.display_text(cx),
1058 "
1059 class Foo:⋯
1060
1061
1062 "
1063 .unindent(),
1064 );
1065
1066 view.unfold_lines(&UnfoldLines, cx);
1067 assert_eq!(
1068 view.display_text(cx),
1069 "
1070 class Foo:
1071 # Hello!
1072
1073 def a():
1074 print(1)
1075
1076 def b():⋯
1077
1078
1079 def c():⋯
1080
1081
1082 "
1083 .unindent(),
1084 );
1085
1086 view.unfold_lines(&UnfoldLines, cx);
1087 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
1088 });
1089}
1090
1091#[gpui::test]
1092fn test_fold_at_level(cx: &mut TestAppContext) {
1093 init_test(cx, |_| {});
1094
1095 let view = cx.add_window(|cx| {
1096 let buffer = MultiBuffer::build_simple(
1097 &"
1098 class Foo:
1099 # Hello!
1100
1101 def a():
1102 print(1)
1103
1104 def b():
1105 print(2)
1106
1107
1108 class Bar:
1109 # World!
1110
1111 def a():
1112 print(1)
1113
1114 def b():
1115 print(2)
1116
1117
1118 "
1119 .unindent(),
1120 cx,
1121 );
1122 build_editor(buffer.clone(), cx)
1123 });
1124
1125 _ = view.update(cx, |view, cx| {
1126 view.fold_at_level(&FoldAtLevel { level: 2 }, cx);
1127 assert_eq!(
1128 view.display_text(cx),
1129 "
1130 class Foo:
1131 # Hello!
1132
1133 def a():⋯
1134
1135 def b():⋯
1136
1137
1138 class Bar:
1139 # World!
1140
1141 def a():⋯
1142
1143 def b():⋯
1144
1145
1146 "
1147 .unindent(),
1148 );
1149
1150 view.fold_at_level(&FoldAtLevel { level: 1 }, cx);
1151 assert_eq!(
1152 view.display_text(cx),
1153 "
1154 class Foo:⋯
1155
1156
1157 class Bar:⋯
1158
1159
1160 "
1161 .unindent(),
1162 );
1163
1164 view.unfold_all(&UnfoldAll, cx);
1165 view.fold_at_level(&FoldAtLevel { level: 0 }, cx);
1166 assert_eq!(
1167 view.display_text(cx),
1168 "
1169 class Foo:
1170 # Hello!
1171
1172 def a():
1173 print(1)
1174
1175 def b():
1176 print(2)
1177
1178
1179 class Bar:
1180 # World!
1181
1182 def a():
1183 print(1)
1184
1185 def b():
1186 print(2)
1187
1188
1189 "
1190 .unindent(),
1191 );
1192
1193 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
1194 });
1195}
1196
1197#[gpui::test]
1198fn test_move_cursor(cx: &mut TestAppContext) {
1199 init_test(cx, |_| {});
1200
1201 let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
1202 let view = cx.add_window(|cx| build_editor(buffer.clone(), cx));
1203
1204 buffer.update(cx, |buffer, cx| {
1205 buffer.edit(
1206 vec![
1207 (Point::new(1, 0)..Point::new(1, 0), "\t"),
1208 (Point::new(1, 1)..Point::new(1, 1), "\t"),
1209 ],
1210 None,
1211 cx,
1212 );
1213 });
1214 _ = view.update(cx, |view, cx| {
1215 assert_eq!(
1216 view.selections.display_ranges(cx),
1217 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1218 );
1219
1220 view.move_down(&MoveDown, cx);
1221 assert_eq!(
1222 view.selections.display_ranges(cx),
1223 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1224 );
1225
1226 view.move_right(&MoveRight, cx);
1227 assert_eq!(
1228 view.selections.display_ranges(cx),
1229 &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
1230 );
1231
1232 view.move_left(&MoveLeft, cx);
1233 assert_eq!(
1234 view.selections.display_ranges(cx),
1235 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1236 );
1237
1238 view.move_up(&MoveUp, cx);
1239 assert_eq!(
1240 view.selections.display_ranges(cx),
1241 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1242 );
1243
1244 view.move_to_end(&MoveToEnd, cx);
1245 assert_eq!(
1246 view.selections.display_ranges(cx),
1247 &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
1248 );
1249
1250 view.move_to_beginning(&MoveToBeginning, cx);
1251 assert_eq!(
1252 view.selections.display_ranges(cx),
1253 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1254 );
1255
1256 view.change_selections(None, cx, |s| {
1257 s.select_display_ranges([
1258 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
1259 ]);
1260 });
1261 view.select_to_beginning(&SelectToBeginning, cx);
1262 assert_eq!(
1263 view.selections.display_ranges(cx),
1264 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
1265 );
1266
1267 view.select_to_end(&SelectToEnd, cx);
1268 assert_eq!(
1269 view.selections.display_ranges(cx),
1270 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
1271 );
1272 });
1273}
1274
1275// TODO: Re-enable this test
1276#[cfg(target_os = "macos")]
1277#[gpui::test]
1278fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
1279 init_test(cx, |_| {});
1280
1281 let view = cx.add_window(|cx| {
1282 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε", cx);
1283 build_editor(buffer.clone(), cx)
1284 });
1285
1286 assert_eq!('ⓐ'.len_utf8(), 3);
1287 assert_eq!('α'.len_utf8(), 2);
1288
1289 _ = view.update(cx, |view, cx| {
1290 view.fold_creases(
1291 vec![
1292 Crease::simple(Point::new(0, 6)..Point::new(0, 12), FoldPlaceholder::test()),
1293 Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
1294 Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
1295 ],
1296 true,
1297 cx,
1298 );
1299 assert_eq!(view.display_text(cx), "ⓐⓑ⋯ⓔ\nab⋯e\nαβ⋯ε");
1300
1301 view.move_right(&MoveRight, cx);
1302 assert_eq!(
1303 view.selections.display_ranges(cx),
1304 &[empty_range(0, "ⓐ".len())]
1305 );
1306 view.move_right(&MoveRight, cx);
1307 assert_eq!(
1308 view.selections.display_ranges(cx),
1309 &[empty_range(0, "ⓐⓑ".len())]
1310 );
1311 view.move_right(&MoveRight, cx);
1312 assert_eq!(
1313 view.selections.display_ranges(cx),
1314 &[empty_range(0, "ⓐⓑ⋯".len())]
1315 );
1316
1317 view.move_down(&MoveDown, cx);
1318 assert_eq!(
1319 view.selections.display_ranges(cx),
1320 &[empty_range(1, "ab⋯e".len())]
1321 );
1322 view.move_left(&MoveLeft, cx);
1323 assert_eq!(
1324 view.selections.display_ranges(cx),
1325 &[empty_range(1, "ab⋯".len())]
1326 );
1327 view.move_left(&MoveLeft, cx);
1328 assert_eq!(
1329 view.selections.display_ranges(cx),
1330 &[empty_range(1, "ab".len())]
1331 );
1332 view.move_left(&MoveLeft, cx);
1333 assert_eq!(
1334 view.selections.display_ranges(cx),
1335 &[empty_range(1, "a".len())]
1336 );
1337
1338 view.move_down(&MoveDown, cx);
1339 assert_eq!(
1340 view.selections.display_ranges(cx),
1341 &[empty_range(2, "α".len())]
1342 );
1343 view.move_right(&MoveRight, cx);
1344 assert_eq!(
1345 view.selections.display_ranges(cx),
1346 &[empty_range(2, "αβ".len())]
1347 );
1348 view.move_right(&MoveRight, cx);
1349 assert_eq!(
1350 view.selections.display_ranges(cx),
1351 &[empty_range(2, "αβ⋯".len())]
1352 );
1353 view.move_right(&MoveRight, cx);
1354 assert_eq!(
1355 view.selections.display_ranges(cx),
1356 &[empty_range(2, "αβ⋯ε".len())]
1357 );
1358
1359 view.move_up(&MoveUp, cx);
1360 assert_eq!(
1361 view.selections.display_ranges(cx),
1362 &[empty_range(1, "ab⋯e".len())]
1363 );
1364 view.move_down(&MoveDown, cx);
1365 assert_eq!(
1366 view.selections.display_ranges(cx),
1367 &[empty_range(2, "αβ⋯ε".len())]
1368 );
1369 view.move_up(&MoveUp, cx);
1370 assert_eq!(
1371 view.selections.display_ranges(cx),
1372 &[empty_range(1, "ab⋯e".len())]
1373 );
1374
1375 view.move_up(&MoveUp, cx);
1376 assert_eq!(
1377 view.selections.display_ranges(cx),
1378 &[empty_range(0, "ⓐⓑ".len())]
1379 );
1380 view.move_left(&MoveLeft, cx);
1381 assert_eq!(
1382 view.selections.display_ranges(cx),
1383 &[empty_range(0, "ⓐ".len())]
1384 );
1385 view.move_left(&MoveLeft, cx);
1386 assert_eq!(
1387 view.selections.display_ranges(cx),
1388 &[empty_range(0, "".len())]
1389 );
1390 });
1391}
1392
1393#[gpui::test]
1394fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
1395 init_test(cx, |_| {});
1396
1397 let view = cx.add_window(|cx| {
1398 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
1399 build_editor(buffer.clone(), cx)
1400 });
1401 _ = view.update(cx, |view, cx| {
1402 view.change_selections(None, cx, |s| {
1403 s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
1404 });
1405
1406 // moving above start of document should move selection to start of document,
1407 // but the next move down should still be at the original goal_x
1408 view.move_up(&MoveUp, cx);
1409 assert_eq!(
1410 view.selections.display_ranges(cx),
1411 &[empty_range(0, "".len())]
1412 );
1413
1414 view.move_down(&MoveDown, cx);
1415 assert_eq!(
1416 view.selections.display_ranges(cx),
1417 &[empty_range(1, "abcd".len())]
1418 );
1419
1420 view.move_down(&MoveDown, cx);
1421 assert_eq!(
1422 view.selections.display_ranges(cx),
1423 &[empty_range(2, "αβγ".len())]
1424 );
1425
1426 view.move_down(&MoveDown, cx);
1427 assert_eq!(
1428 view.selections.display_ranges(cx),
1429 &[empty_range(3, "abcd".len())]
1430 );
1431
1432 view.move_down(&MoveDown, cx);
1433 assert_eq!(
1434 view.selections.display_ranges(cx),
1435 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1436 );
1437
1438 // moving past end of document should not change goal_x
1439 view.move_down(&MoveDown, cx);
1440 assert_eq!(
1441 view.selections.display_ranges(cx),
1442 &[empty_range(5, "".len())]
1443 );
1444
1445 view.move_down(&MoveDown, cx);
1446 assert_eq!(
1447 view.selections.display_ranges(cx),
1448 &[empty_range(5, "".len())]
1449 );
1450
1451 view.move_up(&MoveUp, cx);
1452 assert_eq!(
1453 view.selections.display_ranges(cx),
1454 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1455 );
1456
1457 view.move_up(&MoveUp, cx);
1458 assert_eq!(
1459 view.selections.display_ranges(cx),
1460 &[empty_range(3, "abcd".len())]
1461 );
1462
1463 view.move_up(&MoveUp, cx);
1464 assert_eq!(
1465 view.selections.display_ranges(cx),
1466 &[empty_range(2, "αβγ".len())]
1467 );
1468 });
1469}
1470
1471#[gpui::test]
1472fn test_beginning_end_of_line(cx: &mut TestAppContext) {
1473 init_test(cx, |_| {});
1474 let move_to_beg = MoveToBeginningOfLine {
1475 stop_at_soft_wraps: true,
1476 };
1477
1478 let move_to_end = MoveToEndOfLine {
1479 stop_at_soft_wraps: true,
1480 };
1481
1482 let view = cx.add_window(|cx| {
1483 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1484 build_editor(buffer, cx)
1485 });
1486 _ = view.update(cx, |view, cx| {
1487 view.change_selections(None, cx, |s| {
1488 s.select_display_ranges([
1489 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1490 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1491 ]);
1492 });
1493 });
1494
1495 _ = view.update(cx, |view, cx| {
1496 view.move_to_beginning_of_line(&move_to_beg, cx);
1497 assert_eq!(
1498 view.selections.display_ranges(cx),
1499 &[
1500 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1501 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1502 ]
1503 );
1504 });
1505
1506 _ = view.update(cx, |view, cx| {
1507 view.move_to_beginning_of_line(&move_to_beg, cx);
1508 assert_eq!(
1509 view.selections.display_ranges(cx),
1510 &[
1511 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1512 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1513 ]
1514 );
1515 });
1516
1517 _ = view.update(cx, |view, cx| {
1518 view.move_to_beginning_of_line(&move_to_beg, cx);
1519 assert_eq!(
1520 view.selections.display_ranges(cx),
1521 &[
1522 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1523 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1524 ]
1525 );
1526 });
1527
1528 _ = view.update(cx, |view, cx| {
1529 view.move_to_end_of_line(&move_to_end, cx);
1530 assert_eq!(
1531 view.selections.display_ranges(cx),
1532 &[
1533 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1534 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1535 ]
1536 );
1537 });
1538
1539 // Moving to the end of line again is a no-op.
1540 _ = view.update(cx, |view, cx| {
1541 view.move_to_end_of_line(&move_to_end, cx);
1542 assert_eq!(
1543 view.selections.display_ranges(cx),
1544 &[
1545 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1546 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1547 ]
1548 );
1549 });
1550
1551 _ = view.update(cx, |view, cx| {
1552 view.move_left(&MoveLeft, cx);
1553 view.select_to_beginning_of_line(
1554 &SelectToBeginningOfLine {
1555 stop_at_soft_wraps: true,
1556 },
1557 cx,
1558 );
1559 assert_eq!(
1560 view.selections.display_ranges(cx),
1561 &[
1562 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1563 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1564 ]
1565 );
1566 });
1567
1568 _ = view.update(cx, |view, cx| {
1569 view.select_to_beginning_of_line(
1570 &SelectToBeginningOfLine {
1571 stop_at_soft_wraps: true,
1572 },
1573 cx,
1574 );
1575 assert_eq!(
1576 view.selections.display_ranges(cx),
1577 &[
1578 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1579 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1580 ]
1581 );
1582 });
1583
1584 _ = view.update(cx, |view, cx| {
1585 view.select_to_beginning_of_line(
1586 &SelectToBeginningOfLine {
1587 stop_at_soft_wraps: true,
1588 },
1589 cx,
1590 );
1591 assert_eq!(
1592 view.selections.display_ranges(cx),
1593 &[
1594 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1595 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1596 ]
1597 );
1598 });
1599
1600 _ = view.update(cx, |view, cx| {
1601 view.select_to_end_of_line(
1602 &SelectToEndOfLine {
1603 stop_at_soft_wraps: true,
1604 },
1605 cx,
1606 );
1607 assert_eq!(
1608 view.selections.display_ranges(cx),
1609 &[
1610 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
1611 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
1612 ]
1613 );
1614 });
1615
1616 _ = view.update(cx, |view, cx| {
1617 view.delete_to_end_of_line(&DeleteToEndOfLine, cx);
1618 assert_eq!(view.display_text(cx), "ab\n de");
1619 assert_eq!(
1620 view.selections.display_ranges(cx),
1621 &[
1622 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
1623 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1624 ]
1625 );
1626 });
1627
1628 _ = view.update(cx, |view, cx| {
1629 view.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
1630 assert_eq!(view.display_text(cx), "\n");
1631 assert_eq!(
1632 view.selections.display_ranges(cx),
1633 &[
1634 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1635 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1636 ]
1637 );
1638 });
1639}
1640
1641#[gpui::test]
1642fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
1643 init_test(cx, |_| {});
1644 let move_to_beg = MoveToBeginningOfLine {
1645 stop_at_soft_wraps: false,
1646 };
1647
1648 let move_to_end = MoveToEndOfLine {
1649 stop_at_soft_wraps: false,
1650 };
1651
1652 let view = cx.add_window(|cx| {
1653 let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
1654 build_editor(buffer, cx)
1655 });
1656
1657 _ = view.update(cx, |view, cx| {
1658 view.set_wrap_width(Some(140.0.into()), cx);
1659
1660 // We expect the following lines after wrapping
1661 // ```
1662 // thequickbrownfox
1663 // jumpedoverthelazydo
1664 // gs
1665 // ```
1666 // The final `gs` was soft-wrapped onto a new line.
1667 assert_eq!(
1668 "thequickbrownfox\njumpedoverthelaz\nydogs",
1669 view.display_text(cx),
1670 );
1671
1672 // First, let's assert behavior on the first line, that was not soft-wrapped.
1673 // Start the cursor at the `k` on the first line
1674 view.change_selections(None, cx, |s| {
1675 s.select_display_ranges([
1676 DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
1677 ]);
1678 });
1679
1680 // Moving to the beginning of the line should put us at the beginning of the line.
1681 view.move_to_beginning_of_line(&move_to_beg, cx);
1682 assert_eq!(
1683 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
1684 view.selections.display_ranges(cx)
1685 );
1686
1687 // Moving to the end of the line should put us at the end of the line.
1688 view.move_to_end_of_line(&move_to_end, cx);
1689 assert_eq!(
1690 vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
1691 view.selections.display_ranges(cx)
1692 );
1693
1694 // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
1695 // Start the cursor at the last line (`y` that was wrapped to a new line)
1696 view.change_selections(None, cx, |s| {
1697 s.select_display_ranges([
1698 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
1699 ]);
1700 });
1701
1702 // Moving to the beginning of the line should put us at the start of the second line of
1703 // display text, i.e., the `j`.
1704 view.move_to_beginning_of_line(&move_to_beg, cx);
1705 assert_eq!(
1706 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1707 view.selections.display_ranges(cx)
1708 );
1709
1710 // Moving to the beginning of the line again should be a no-op.
1711 view.move_to_beginning_of_line(&move_to_beg, cx);
1712 assert_eq!(
1713 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1714 view.selections.display_ranges(cx)
1715 );
1716
1717 // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
1718 // next display line.
1719 view.move_to_end_of_line(&move_to_end, cx);
1720 assert_eq!(
1721 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1722 view.selections.display_ranges(cx)
1723 );
1724
1725 // Moving to the end of the line again should be a no-op.
1726 view.move_to_end_of_line(&move_to_end, cx);
1727 assert_eq!(
1728 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1729 view.selections.display_ranges(cx)
1730 );
1731 });
1732}
1733
1734#[gpui::test]
1735fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1736 init_test(cx, |_| {});
1737
1738 let view = cx.add_window(|cx| {
1739 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1740 build_editor(buffer, cx)
1741 });
1742 _ = view.update(cx, |view, cx| {
1743 view.change_selections(None, cx, |s| {
1744 s.select_display_ranges([
1745 DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
1746 DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
1747 ])
1748 });
1749
1750 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1751 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1752
1753 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1754 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", view, cx);
1755
1756 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1757 assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", view, cx);
1758
1759 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1760 assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1761
1762 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1763 assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", view, cx);
1764
1765 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1766 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", view, cx);
1767
1768 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1769 assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1770
1771 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1772 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1773
1774 view.move_right(&MoveRight, cx);
1775 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1776 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1777
1778 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1779 assert_selection_ranges("use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}", view, cx);
1780
1781 view.select_to_next_word_end(&SelectToNextWordEnd, cx);
1782 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1783 });
1784}
1785
1786#[gpui::test]
1787fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1788 init_test(cx, |_| {});
1789
1790 let view = cx.add_window(|cx| {
1791 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1792 build_editor(buffer, cx)
1793 });
1794
1795 _ = view.update(cx, |view, cx| {
1796 view.set_wrap_width(Some(140.0.into()), cx);
1797 assert_eq!(
1798 view.display_text(cx),
1799 "use one::{\n two::three::\n four::five\n};"
1800 );
1801
1802 view.change_selections(None, cx, |s| {
1803 s.select_display_ranges([
1804 DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
1805 ]);
1806 });
1807
1808 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1809 assert_eq!(
1810 view.selections.display_ranges(cx),
1811 &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
1812 );
1813
1814 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1815 assert_eq!(
1816 view.selections.display_ranges(cx),
1817 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1818 );
1819
1820 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1821 assert_eq!(
1822 view.selections.display_ranges(cx),
1823 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1824 );
1825
1826 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1827 assert_eq!(
1828 view.selections.display_ranges(cx),
1829 &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
1830 );
1831
1832 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1833 assert_eq!(
1834 view.selections.display_ranges(cx),
1835 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1836 );
1837
1838 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1839 assert_eq!(
1840 view.selections.display_ranges(cx),
1841 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1842 );
1843 });
1844}
1845
1846#[gpui::test]
1847async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) {
1848 init_test(cx, |_| {});
1849 let mut cx = EditorTestContext::new(cx).await;
1850
1851 let line_height = cx.editor(|editor, cx| {
1852 editor
1853 .style()
1854 .unwrap()
1855 .text
1856 .line_height_in_pixels(cx.rem_size())
1857 });
1858 cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
1859
1860 cx.set_state(
1861 &r#"ˇone
1862 two
1863
1864 three
1865 fourˇ
1866 five
1867
1868 six"#
1869 .unindent(),
1870 );
1871
1872 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1873 cx.assert_editor_state(
1874 &r#"one
1875 two
1876 ˇ
1877 three
1878 four
1879 five
1880 ˇ
1881 six"#
1882 .unindent(),
1883 );
1884
1885 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1886 cx.assert_editor_state(
1887 &r#"one
1888 two
1889
1890 three
1891 four
1892 five
1893 ˇ
1894 sixˇ"#
1895 .unindent(),
1896 );
1897
1898 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1899 cx.assert_editor_state(
1900 &r#"one
1901 two
1902
1903 three
1904 four
1905 five
1906
1907 sixˇ"#
1908 .unindent(),
1909 );
1910
1911 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1912 cx.assert_editor_state(
1913 &r#"one
1914 two
1915
1916 three
1917 four
1918 five
1919 ˇ
1920 six"#
1921 .unindent(),
1922 );
1923
1924 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1925 cx.assert_editor_state(
1926 &r#"one
1927 two
1928 ˇ
1929 three
1930 four
1931 five
1932
1933 six"#
1934 .unindent(),
1935 );
1936
1937 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1938 cx.assert_editor_state(
1939 &r#"ˇone
1940 two
1941
1942 three
1943 four
1944 five
1945
1946 six"#
1947 .unindent(),
1948 );
1949}
1950
1951#[gpui::test]
1952async fn test_scroll_page_up_page_down(cx: &mut gpui::TestAppContext) {
1953 init_test(cx, |_| {});
1954 let mut cx = EditorTestContext::new(cx).await;
1955 let line_height = cx.editor(|editor, cx| {
1956 editor
1957 .style()
1958 .unwrap()
1959 .text
1960 .line_height_in_pixels(cx.rem_size())
1961 });
1962 let window = cx.window;
1963 cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
1964
1965 cx.set_state(
1966 r#"ˇone
1967 two
1968 three
1969 four
1970 five
1971 six
1972 seven
1973 eight
1974 nine
1975 ten
1976 "#,
1977 );
1978
1979 cx.update_editor(|editor, cx| {
1980 assert_eq!(
1981 editor.snapshot(cx).scroll_position(),
1982 gpui::Point::new(0., 0.)
1983 );
1984 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1985 assert_eq!(
1986 editor.snapshot(cx).scroll_position(),
1987 gpui::Point::new(0., 3.)
1988 );
1989 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1990 assert_eq!(
1991 editor.snapshot(cx).scroll_position(),
1992 gpui::Point::new(0., 6.)
1993 );
1994 editor.scroll_screen(&ScrollAmount::Page(-1.), cx);
1995 assert_eq!(
1996 editor.snapshot(cx).scroll_position(),
1997 gpui::Point::new(0., 3.)
1998 );
1999
2000 editor.scroll_screen(&ScrollAmount::Page(-0.5), cx);
2001 assert_eq!(
2002 editor.snapshot(cx).scroll_position(),
2003 gpui::Point::new(0., 1.)
2004 );
2005 editor.scroll_screen(&ScrollAmount::Page(0.5), cx);
2006 assert_eq!(
2007 editor.snapshot(cx).scroll_position(),
2008 gpui::Point::new(0., 3.)
2009 );
2010 });
2011}
2012
2013#[gpui::test]
2014async fn test_autoscroll(cx: &mut gpui::TestAppContext) {
2015 init_test(cx, |_| {});
2016 let mut cx = EditorTestContext::new(cx).await;
2017
2018 let line_height = cx.update_editor(|editor, cx| {
2019 editor.set_vertical_scroll_margin(2, cx);
2020 editor
2021 .style()
2022 .unwrap()
2023 .text
2024 .line_height_in_pixels(cx.rem_size())
2025 });
2026 let window = cx.window;
2027 cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
2028
2029 cx.set_state(
2030 r#"ˇone
2031 two
2032 three
2033 four
2034 five
2035 six
2036 seven
2037 eight
2038 nine
2039 ten
2040 "#,
2041 );
2042 cx.update_editor(|editor, cx| {
2043 assert_eq!(
2044 editor.snapshot(cx).scroll_position(),
2045 gpui::Point::new(0., 0.0)
2046 );
2047 });
2048
2049 // Add a cursor below the visible area. Since both cursors cannot fit
2050 // on screen, the editor autoscrolls to reveal the newest cursor, and
2051 // allows the vertical scroll margin below that cursor.
2052 cx.update_editor(|editor, cx| {
2053 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
2054 selections.select_ranges([
2055 Point::new(0, 0)..Point::new(0, 0),
2056 Point::new(6, 0)..Point::new(6, 0),
2057 ]);
2058 })
2059 });
2060 cx.update_editor(|editor, cx| {
2061 assert_eq!(
2062 editor.snapshot(cx).scroll_position(),
2063 gpui::Point::new(0., 3.0)
2064 );
2065 });
2066
2067 // Move down. The editor cursor scrolls down to track the newest cursor.
2068 cx.update_editor(|editor, cx| {
2069 editor.move_down(&Default::default(), cx);
2070 });
2071 cx.update_editor(|editor, cx| {
2072 assert_eq!(
2073 editor.snapshot(cx).scroll_position(),
2074 gpui::Point::new(0., 4.0)
2075 );
2076 });
2077
2078 // Add a cursor above the visible area. Since both cursors fit on screen,
2079 // the editor scrolls to show both.
2080 cx.update_editor(|editor, cx| {
2081 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
2082 selections.select_ranges([
2083 Point::new(1, 0)..Point::new(1, 0),
2084 Point::new(6, 0)..Point::new(6, 0),
2085 ]);
2086 })
2087 });
2088 cx.update_editor(|editor, cx| {
2089 assert_eq!(
2090 editor.snapshot(cx).scroll_position(),
2091 gpui::Point::new(0., 1.0)
2092 );
2093 });
2094}
2095
2096#[gpui::test]
2097async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
2098 init_test(cx, |_| {});
2099 let mut cx = EditorTestContext::new(cx).await;
2100
2101 let line_height = cx.editor(|editor, cx| {
2102 editor
2103 .style()
2104 .unwrap()
2105 .text
2106 .line_height_in_pixels(cx.rem_size())
2107 });
2108 let window = cx.window;
2109 cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
2110 cx.set_state(
2111 &r#"
2112 ˇone
2113 two
2114 threeˇ
2115 four
2116 five
2117 six
2118 seven
2119 eight
2120 nine
2121 ten
2122 "#
2123 .unindent(),
2124 );
2125
2126 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
2127 cx.assert_editor_state(
2128 &r#"
2129 one
2130 two
2131 three
2132 ˇfour
2133 five
2134 sixˇ
2135 seven
2136 eight
2137 nine
2138 ten
2139 "#
2140 .unindent(),
2141 );
2142
2143 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
2144 cx.assert_editor_state(
2145 &r#"
2146 one
2147 two
2148 three
2149 four
2150 five
2151 six
2152 ˇseven
2153 eight
2154 nineˇ
2155 ten
2156 "#
2157 .unindent(),
2158 );
2159
2160 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2161 cx.assert_editor_state(
2162 &r#"
2163 one
2164 two
2165 three
2166 ˇfour
2167 five
2168 sixˇ
2169 seven
2170 eight
2171 nine
2172 ten
2173 "#
2174 .unindent(),
2175 );
2176
2177 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2178 cx.assert_editor_state(
2179 &r#"
2180 ˇone
2181 two
2182 threeˇ
2183 four
2184 five
2185 six
2186 seven
2187 eight
2188 nine
2189 ten
2190 "#
2191 .unindent(),
2192 );
2193
2194 // Test select collapsing
2195 cx.update_editor(|editor, cx| {
2196 editor.move_page_down(&MovePageDown::default(), cx);
2197 editor.move_page_down(&MovePageDown::default(), cx);
2198 editor.move_page_down(&MovePageDown::default(), cx);
2199 });
2200 cx.assert_editor_state(
2201 &r#"
2202 one
2203 two
2204 three
2205 four
2206 five
2207 six
2208 seven
2209 eight
2210 nine
2211 ˇten
2212 ˇ"#
2213 .unindent(),
2214 );
2215}
2216
2217#[gpui::test]
2218async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
2219 init_test(cx, |_| {});
2220 let mut cx = EditorTestContext::new(cx).await;
2221 cx.set_state("one «two threeˇ» four");
2222 cx.update_editor(|editor, cx| {
2223 editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
2224 assert_eq!(editor.text(cx), " four");
2225 });
2226}
2227
2228#[gpui::test]
2229fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
2230 init_test(cx, |_| {});
2231
2232 let view = cx.add_window(|cx| {
2233 let buffer = MultiBuffer::build_simple("one two three four", cx);
2234 build_editor(buffer.clone(), cx)
2235 });
2236
2237 _ = view.update(cx, |view, cx| {
2238 view.change_selections(None, cx, |s| {
2239 s.select_display_ranges([
2240 // an empty selection - the preceding word fragment is deleted
2241 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2242 // characters selected - they are deleted
2243 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
2244 ])
2245 });
2246 view.delete_to_previous_word_start(
2247 &DeleteToPreviousWordStart {
2248 ignore_newlines: false,
2249 },
2250 cx,
2251 );
2252 assert_eq!(view.buffer.read(cx).read(cx).text(), "e two te four");
2253 });
2254
2255 _ = view.update(cx, |view, cx| {
2256 view.change_selections(None, cx, |s| {
2257 s.select_display_ranges([
2258 // an empty selection - the following word fragment is deleted
2259 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
2260 // characters selected - they are deleted
2261 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
2262 ])
2263 });
2264 view.delete_to_next_word_end(
2265 &DeleteToNextWordEnd {
2266 ignore_newlines: false,
2267 },
2268 cx,
2269 );
2270 assert_eq!(view.buffer.read(cx).read(cx).text(), "e t te our");
2271 });
2272}
2273
2274#[gpui::test]
2275fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
2276 init_test(cx, |_| {});
2277
2278 let view = cx.add_window(|cx| {
2279 let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
2280 build_editor(buffer.clone(), cx)
2281 });
2282 let del_to_prev_word_start = DeleteToPreviousWordStart {
2283 ignore_newlines: false,
2284 };
2285 let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
2286 ignore_newlines: true,
2287 };
2288
2289 _ = view.update(cx, |view, cx| {
2290 view.change_selections(None, cx, |s| {
2291 s.select_display_ranges([
2292 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
2293 ])
2294 });
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\nthree\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\nthree");
2299 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2300 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\n");
2301 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2302 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2");
2303 view.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, cx);
2304 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n");
2305 view.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, cx);
2306 assert_eq!(view.buffer.read(cx).read(cx).text(), "");
2307 });
2308}
2309
2310#[gpui::test]
2311fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
2312 init_test(cx, |_| {});
2313
2314 let view = cx.add_window(|cx| {
2315 let buffer = MultiBuffer::build_simple("\none\n two\nthree\n four", cx);
2316 build_editor(buffer.clone(), cx)
2317 });
2318 let del_to_next_word_end = DeleteToNextWordEnd {
2319 ignore_newlines: false,
2320 };
2321 let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
2322 ignore_newlines: true,
2323 };
2324
2325 _ = view.update(cx, |view, cx| {
2326 view.change_selections(None, cx, |s| {
2327 s.select_display_ranges([
2328 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
2329 ])
2330 });
2331 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2332 assert_eq!(
2333 view.buffer.read(cx).read(cx).text(),
2334 "one\n two\nthree\n four"
2335 );
2336 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2337 assert_eq!(
2338 view.buffer.read(cx).read(cx).text(),
2339 "\n two\nthree\n four"
2340 );
2341 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2342 assert_eq!(view.buffer.read(cx).read(cx).text(), "two\nthree\n four");
2343 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2344 assert_eq!(view.buffer.read(cx).read(cx).text(), "\nthree\n four");
2345 view.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, cx);
2346 assert_eq!(view.buffer.read(cx).read(cx).text(), "\n four");
2347 view.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, cx);
2348 assert_eq!(view.buffer.read(cx).read(cx).text(), "");
2349 });
2350}
2351
2352#[gpui::test]
2353fn test_newline(cx: &mut TestAppContext) {
2354 init_test(cx, |_| {});
2355
2356 let view = cx.add_window(|cx| {
2357 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
2358 build_editor(buffer.clone(), cx)
2359 });
2360
2361 _ = view.update(cx, |view, cx| {
2362 view.change_selections(None, cx, |s| {
2363 s.select_display_ranges([
2364 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2365 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
2366 DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
2367 ])
2368 });
2369
2370 view.newline(&Newline, cx);
2371 assert_eq!(view.text(cx), "aa\naa\n \n bb\n bb\n");
2372 });
2373}
2374
2375#[gpui::test]
2376fn test_newline_with_old_selections(cx: &mut TestAppContext) {
2377 init_test(cx, |_| {});
2378
2379 let editor = cx.add_window(|cx| {
2380 let buffer = MultiBuffer::build_simple(
2381 "
2382 a
2383 b(
2384 X
2385 )
2386 c(
2387 X
2388 )
2389 "
2390 .unindent()
2391 .as_str(),
2392 cx,
2393 );
2394 let mut editor = build_editor(buffer.clone(), cx);
2395 editor.change_selections(None, cx, |s| {
2396 s.select_ranges([
2397 Point::new(2, 4)..Point::new(2, 5),
2398 Point::new(5, 4)..Point::new(5, 5),
2399 ])
2400 });
2401 editor
2402 });
2403
2404 _ = editor.update(cx, |editor, cx| {
2405 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2406 editor.buffer.update(cx, |buffer, cx| {
2407 buffer.edit(
2408 [
2409 (Point::new(1, 2)..Point::new(3, 0), ""),
2410 (Point::new(4, 2)..Point::new(6, 0), ""),
2411 ],
2412 None,
2413 cx,
2414 );
2415 assert_eq!(
2416 buffer.read(cx).text(),
2417 "
2418 a
2419 b()
2420 c()
2421 "
2422 .unindent()
2423 );
2424 });
2425 assert_eq!(
2426 editor.selections.ranges(cx),
2427 &[
2428 Point::new(1, 2)..Point::new(1, 2),
2429 Point::new(2, 2)..Point::new(2, 2),
2430 ],
2431 );
2432
2433 editor.newline(&Newline, cx);
2434 assert_eq!(
2435 editor.text(cx),
2436 "
2437 a
2438 b(
2439 )
2440 c(
2441 )
2442 "
2443 .unindent()
2444 );
2445
2446 // The selections are moved after the inserted newlines
2447 assert_eq!(
2448 editor.selections.ranges(cx),
2449 &[
2450 Point::new(2, 0)..Point::new(2, 0),
2451 Point::new(4, 0)..Point::new(4, 0),
2452 ],
2453 );
2454 });
2455}
2456
2457#[gpui::test]
2458async fn test_newline_above(cx: &mut gpui::TestAppContext) {
2459 init_test(cx, |settings| {
2460 settings.defaults.tab_size = NonZeroU32::new(4)
2461 });
2462
2463 let language = Arc::new(
2464 Language::new(
2465 LanguageConfig::default(),
2466 Some(tree_sitter_rust::LANGUAGE.into()),
2467 )
2468 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2469 .unwrap(),
2470 );
2471
2472 let mut cx = EditorTestContext::new(cx).await;
2473 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2474 cx.set_state(indoc! {"
2475 const a: ˇA = (
2476 (ˇ
2477 «const_functionˇ»(ˇ),
2478 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2479 )ˇ
2480 ˇ);ˇ
2481 "});
2482
2483 cx.update_editor(|e, cx| e.newline_above(&NewlineAbove, cx));
2484 cx.assert_editor_state(indoc! {"
2485 ˇ
2486 const a: A = (
2487 ˇ
2488 (
2489 ˇ
2490 ˇ
2491 const_function(),
2492 ˇ
2493 ˇ
2494 ˇ
2495 ˇ
2496 something_else,
2497 ˇ
2498 )
2499 ˇ
2500 ˇ
2501 );
2502 "});
2503}
2504
2505#[gpui::test]
2506async fn test_newline_below(cx: &mut gpui::TestAppContext) {
2507 init_test(cx, |settings| {
2508 settings.defaults.tab_size = NonZeroU32::new(4)
2509 });
2510
2511 let language = Arc::new(
2512 Language::new(
2513 LanguageConfig::default(),
2514 Some(tree_sitter_rust::LANGUAGE.into()),
2515 )
2516 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2517 .unwrap(),
2518 );
2519
2520 let mut cx = EditorTestContext::new(cx).await;
2521 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2522 cx.set_state(indoc! {"
2523 const a: ˇA = (
2524 (ˇ
2525 «const_functionˇ»(ˇ),
2526 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2527 )ˇ
2528 ˇ);ˇ
2529 "});
2530
2531 cx.update_editor(|e, cx| e.newline_below(&NewlineBelow, cx));
2532 cx.assert_editor_state(indoc! {"
2533 const a: A = (
2534 ˇ
2535 (
2536 ˇ
2537 const_function(),
2538 ˇ
2539 ˇ
2540 something_else,
2541 ˇ
2542 ˇ
2543 ˇ
2544 ˇ
2545 )
2546 ˇ
2547 );
2548 ˇ
2549 ˇ
2550 "});
2551}
2552
2553#[gpui::test]
2554async fn test_newline_comments(cx: &mut gpui::TestAppContext) {
2555 init_test(cx, |settings| {
2556 settings.defaults.tab_size = NonZeroU32::new(4)
2557 });
2558
2559 let language = Arc::new(Language::new(
2560 LanguageConfig {
2561 line_comments: vec!["//".into()],
2562 ..LanguageConfig::default()
2563 },
2564 None,
2565 ));
2566 {
2567 let mut cx = EditorTestContext::new(cx).await;
2568 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2569 cx.set_state(indoc! {"
2570 // Fooˇ
2571 "});
2572
2573 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2574 cx.assert_editor_state(indoc! {"
2575 // Foo
2576 //ˇ
2577 "});
2578 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
2579 cx.set_state(indoc! {"
2580 ˇ// Foo
2581 "});
2582 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2583 cx.assert_editor_state(indoc! {"
2584
2585 ˇ// Foo
2586 "});
2587 }
2588 // Ensure that comment continuations can be disabled.
2589 update_test_language_settings(cx, |settings| {
2590 settings.defaults.extend_comment_on_newline = Some(false);
2591 });
2592 let mut cx = EditorTestContext::new(cx).await;
2593 cx.set_state(indoc! {"
2594 // Fooˇ
2595 "});
2596 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2597 cx.assert_editor_state(indoc! {"
2598 // Foo
2599 ˇ
2600 "});
2601}
2602
2603#[gpui::test]
2604fn test_insert_with_old_selections(cx: &mut TestAppContext) {
2605 init_test(cx, |_| {});
2606
2607 let editor = cx.add_window(|cx| {
2608 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
2609 let mut editor = build_editor(buffer.clone(), cx);
2610 editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
2611 editor
2612 });
2613
2614 _ = editor.update(cx, |editor, cx| {
2615 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2616 editor.buffer.update(cx, |buffer, cx| {
2617 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
2618 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
2619 });
2620 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
2621
2622 editor.insert("Z", cx);
2623 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
2624
2625 // The selections are moved after the inserted characters
2626 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
2627 });
2628}
2629
2630#[gpui::test]
2631async fn test_tab(cx: &mut gpui::TestAppContext) {
2632 init_test(cx, |settings| {
2633 settings.defaults.tab_size = NonZeroU32::new(3)
2634 });
2635
2636 let mut cx = EditorTestContext::new(cx).await;
2637 cx.set_state(indoc! {"
2638 ˇabˇc
2639 ˇ🏀ˇ🏀ˇefg
2640 dˇ
2641 "});
2642 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2643 cx.assert_editor_state(indoc! {"
2644 ˇab ˇc
2645 ˇ🏀 ˇ🏀 ˇefg
2646 d ˇ
2647 "});
2648
2649 cx.set_state(indoc! {"
2650 a
2651 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2652 "});
2653 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2654 cx.assert_editor_state(indoc! {"
2655 a
2656 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2657 "});
2658}
2659
2660#[gpui::test]
2661async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) {
2662 init_test(cx, |_| {});
2663
2664 let mut cx = EditorTestContext::new(cx).await;
2665 let language = Arc::new(
2666 Language::new(
2667 LanguageConfig::default(),
2668 Some(tree_sitter_rust::LANGUAGE.into()),
2669 )
2670 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2671 .unwrap(),
2672 );
2673 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2674
2675 // cursors that are already at the suggested indent level insert
2676 // a soft tab. cursors that are to the left of the suggested indent
2677 // auto-indent their line.
2678 cx.set_state(indoc! {"
2679 ˇ
2680 const a: B = (
2681 c(
2682 d(
2683 ˇ
2684 )
2685 ˇ
2686 ˇ )
2687 );
2688 "});
2689 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2690 cx.assert_editor_state(indoc! {"
2691 ˇ
2692 const a: B = (
2693 c(
2694 d(
2695 ˇ
2696 )
2697 ˇ
2698 ˇ)
2699 );
2700 "});
2701
2702 // handle auto-indent when there are multiple cursors on the same line
2703 cx.set_state(indoc! {"
2704 const a: B = (
2705 c(
2706 ˇ ˇ
2707 ˇ )
2708 );
2709 "});
2710 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2711 cx.assert_editor_state(indoc! {"
2712 const a: B = (
2713 c(
2714 ˇ
2715 ˇ)
2716 );
2717 "});
2718}
2719
2720#[gpui::test]
2721async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
2722 init_test(cx, |settings| {
2723 settings.defaults.tab_size = NonZeroU32::new(4)
2724 });
2725
2726 let language = Arc::new(
2727 Language::new(
2728 LanguageConfig::default(),
2729 Some(tree_sitter_rust::LANGUAGE.into()),
2730 )
2731 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2732 .unwrap(),
2733 );
2734
2735 let mut cx = EditorTestContext::new(cx).await;
2736 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2737 cx.set_state(indoc! {"
2738 fn a() {
2739 if b {
2740 \t ˇc
2741 }
2742 }
2743 "});
2744
2745 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2746 cx.assert_editor_state(indoc! {"
2747 fn a() {
2748 if b {
2749 ˇc
2750 }
2751 }
2752 "});
2753}
2754
2755#[gpui::test]
2756async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
2757 init_test(cx, |settings| {
2758 settings.defaults.tab_size = NonZeroU32::new(4);
2759 });
2760
2761 let mut cx = EditorTestContext::new(cx).await;
2762
2763 cx.set_state(indoc! {"
2764 «oneˇ» «twoˇ»
2765 three
2766 four
2767 "});
2768 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2769 cx.assert_editor_state(indoc! {"
2770 «oneˇ» «twoˇ»
2771 three
2772 four
2773 "});
2774
2775 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2776 cx.assert_editor_state(indoc! {"
2777 «oneˇ» «twoˇ»
2778 three
2779 four
2780 "});
2781
2782 // select across line ending
2783 cx.set_state(indoc! {"
2784 one two
2785 t«hree
2786 ˇ» four
2787 "});
2788 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2789 cx.assert_editor_state(indoc! {"
2790 one two
2791 t«hree
2792 ˇ» four
2793 "});
2794
2795 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2796 cx.assert_editor_state(indoc! {"
2797 one two
2798 t«hree
2799 ˇ» four
2800 "});
2801
2802 // Ensure that indenting/outdenting works when the cursor is at column 0.
2803 cx.set_state(indoc! {"
2804 one two
2805 ˇthree
2806 four
2807 "});
2808 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2809 cx.assert_editor_state(indoc! {"
2810 one two
2811 ˇthree
2812 four
2813 "});
2814
2815 cx.set_state(indoc! {"
2816 one two
2817 ˇ three
2818 four
2819 "});
2820 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2821 cx.assert_editor_state(indoc! {"
2822 one two
2823 ˇthree
2824 four
2825 "});
2826}
2827
2828#[gpui::test]
2829async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
2830 init_test(cx, |settings| {
2831 settings.defaults.hard_tabs = Some(true);
2832 });
2833
2834 let mut cx = EditorTestContext::new(cx).await;
2835
2836 // select two ranges on one line
2837 cx.set_state(indoc! {"
2838 «oneˇ» «twoˇ»
2839 three
2840 four
2841 "});
2842 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2843 cx.assert_editor_state(indoc! {"
2844 \t«oneˇ» «twoˇ»
2845 three
2846 four
2847 "});
2848 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2849 cx.assert_editor_state(indoc! {"
2850 \t\t«oneˇ» «twoˇ»
2851 three
2852 four
2853 "});
2854 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2855 cx.assert_editor_state(indoc! {"
2856 \t«oneˇ» «twoˇ»
2857 three
2858 four
2859 "});
2860 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2861 cx.assert_editor_state(indoc! {"
2862 «oneˇ» «twoˇ»
2863 three
2864 four
2865 "});
2866
2867 // select across a line ending
2868 cx.set_state(indoc! {"
2869 one two
2870 t«hree
2871 ˇ»four
2872 "});
2873 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2874 cx.assert_editor_state(indoc! {"
2875 one two
2876 \tt«hree
2877 ˇ»four
2878 "});
2879 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2880 cx.assert_editor_state(indoc! {"
2881 one two
2882 \t\tt«hree
2883 ˇ»four
2884 "});
2885 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2886 cx.assert_editor_state(indoc! {"
2887 one two
2888 \tt«hree
2889 ˇ»four
2890 "});
2891 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2892 cx.assert_editor_state(indoc! {"
2893 one two
2894 t«hree
2895 ˇ»four
2896 "});
2897
2898 // Ensure that indenting/outdenting works when the cursor is at column 0.
2899 cx.set_state(indoc! {"
2900 one two
2901 ˇthree
2902 four
2903 "});
2904 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2905 cx.assert_editor_state(indoc! {"
2906 one two
2907 ˇthree
2908 four
2909 "});
2910 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2911 cx.assert_editor_state(indoc! {"
2912 one two
2913 \tˇthree
2914 four
2915 "});
2916 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2917 cx.assert_editor_state(indoc! {"
2918 one two
2919 ˇthree
2920 four
2921 "});
2922}
2923
2924#[gpui::test]
2925fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
2926 init_test(cx, |settings| {
2927 settings.languages.extend([
2928 (
2929 "TOML".into(),
2930 LanguageSettingsContent {
2931 tab_size: NonZeroU32::new(2),
2932 ..Default::default()
2933 },
2934 ),
2935 (
2936 "Rust".into(),
2937 LanguageSettingsContent {
2938 tab_size: NonZeroU32::new(4),
2939 ..Default::default()
2940 },
2941 ),
2942 ]);
2943 });
2944
2945 let toml_language = Arc::new(Language::new(
2946 LanguageConfig {
2947 name: "TOML".into(),
2948 ..Default::default()
2949 },
2950 None,
2951 ));
2952 let rust_language = Arc::new(Language::new(
2953 LanguageConfig {
2954 name: "Rust".into(),
2955 ..Default::default()
2956 },
2957 None,
2958 ));
2959
2960 let toml_buffer =
2961 cx.new_model(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
2962 let rust_buffer = cx.new_model(|cx| {
2963 Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx)
2964 });
2965 let multibuffer = cx.new_model(|cx| {
2966 let mut multibuffer = MultiBuffer::new(ReadWrite);
2967 multibuffer.push_excerpts(
2968 toml_buffer.clone(),
2969 [ExcerptRange {
2970 context: Point::new(0, 0)..Point::new(2, 0),
2971 primary: None,
2972 }],
2973 cx,
2974 );
2975 multibuffer.push_excerpts(
2976 rust_buffer.clone(),
2977 [ExcerptRange {
2978 context: Point::new(0, 0)..Point::new(1, 0),
2979 primary: None,
2980 }],
2981 cx,
2982 );
2983 multibuffer
2984 });
2985
2986 cx.add_window(|cx| {
2987 let mut editor = build_editor(multibuffer, cx);
2988
2989 assert_eq!(
2990 editor.text(cx),
2991 indoc! {"
2992 a = 1
2993 b = 2
2994
2995 const c: usize = 3;
2996 "}
2997 );
2998
2999 select_ranges(
3000 &mut editor,
3001 indoc! {"
3002 «aˇ» = 1
3003 b = 2
3004
3005 «const c:ˇ» usize = 3;
3006 "},
3007 cx,
3008 );
3009
3010 editor.tab(&Tab, cx);
3011 assert_text_with_selections(
3012 &mut editor,
3013 indoc! {"
3014 «aˇ» = 1
3015 b = 2
3016
3017 «const c:ˇ» usize = 3;
3018 "},
3019 cx,
3020 );
3021 editor.tab_prev(&TabPrev, cx);
3022 assert_text_with_selections(
3023 &mut editor,
3024 indoc! {"
3025 «aˇ» = 1
3026 b = 2
3027
3028 «const c:ˇ» usize = 3;
3029 "},
3030 cx,
3031 );
3032
3033 editor
3034 });
3035}
3036
3037#[gpui::test]
3038async fn test_backspace(cx: &mut gpui::TestAppContext) {
3039 init_test(cx, |_| {});
3040
3041 let mut cx = EditorTestContext::new(cx).await;
3042
3043 // Basic backspace
3044 cx.set_state(indoc! {"
3045 onˇe two three
3046 fou«rˇ» five six
3047 seven «ˇeight nine
3048 »ten
3049 "});
3050 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3051 cx.assert_editor_state(indoc! {"
3052 oˇe two three
3053 fouˇ five six
3054 seven ˇten
3055 "});
3056
3057 // Test backspace inside and around indents
3058 cx.set_state(indoc! {"
3059 zero
3060 ˇone
3061 ˇtwo
3062 ˇ ˇ ˇ three
3063 ˇ ˇ four
3064 "});
3065 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3066 cx.assert_editor_state(indoc! {"
3067 zero
3068 ˇone
3069 ˇtwo
3070 ˇ threeˇ four
3071 "});
3072
3073 // Test backspace with line_mode set to true
3074 cx.update_editor(|e, _| e.selections.line_mode = true);
3075 cx.set_state(indoc! {"
3076 The ˇquick ˇbrown
3077 fox jumps over
3078 the lazy dog
3079 ˇThe qu«ick bˇ»rown"});
3080 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3081 cx.assert_editor_state(indoc! {"
3082 ˇfox jumps over
3083 the lazy dogˇ"});
3084}
3085
3086#[gpui::test]
3087async fn test_delete(cx: &mut gpui::TestAppContext) {
3088 init_test(cx, |_| {});
3089
3090 let mut cx = EditorTestContext::new(cx).await;
3091 cx.set_state(indoc! {"
3092 onˇe two three
3093 fou«rˇ» five six
3094 seven «ˇeight nine
3095 »ten
3096 "});
3097 cx.update_editor(|e, cx| e.delete(&Delete, cx));
3098 cx.assert_editor_state(indoc! {"
3099 onˇ two three
3100 fouˇ five six
3101 seven ˇten
3102 "});
3103
3104 // Test backspace with line_mode set to true
3105 cx.update_editor(|e, _| e.selections.line_mode = true);
3106 cx.set_state(indoc! {"
3107 The ˇquick ˇbrown
3108 fox «ˇjum»ps over
3109 the lazy dog
3110 ˇThe qu«ick bˇ»rown"});
3111 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3112 cx.assert_editor_state("ˇthe lazy dogˇ");
3113}
3114
3115#[gpui::test]
3116fn test_delete_line(cx: &mut TestAppContext) {
3117 init_test(cx, |_| {});
3118
3119 let view = cx.add_window(|cx| {
3120 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3121 build_editor(buffer, cx)
3122 });
3123 _ = view.update(cx, |view, cx| {
3124 view.change_selections(None, cx, |s| {
3125 s.select_display_ranges([
3126 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3127 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3128 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3129 ])
3130 });
3131 view.delete_line(&DeleteLine, cx);
3132 assert_eq!(view.display_text(cx), "ghi");
3133 assert_eq!(
3134 view.selections.display_ranges(cx),
3135 vec![
3136 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
3137 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
3138 ]
3139 );
3140 });
3141
3142 let view = cx.add_window(|cx| {
3143 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3144 build_editor(buffer, cx)
3145 });
3146 _ = view.update(cx, |view, cx| {
3147 view.change_selections(None, cx, |s| {
3148 s.select_display_ranges([
3149 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
3150 ])
3151 });
3152 view.delete_line(&DeleteLine, cx);
3153 assert_eq!(view.display_text(cx), "ghi\n");
3154 assert_eq!(
3155 view.selections.display_ranges(cx),
3156 vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
3157 );
3158 });
3159}
3160
3161#[gpui::test]
3162fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
3163 init_test(cx, |_| {});
3164
3165 cx.add_window(|cx| {
3166 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3167 let mut editor = build_editor(buffer.clone(), cx);
3168 let buffer = buffer.read(cx).as_singleton().unwrap();
3169
3170 assert_eq!(
3171 editor.selections.ranges::<Point>(cx),
3172 &[Point::new(0, 0)..Point::new(0, 0)]
3173 );
3174
3175 // When on single line, replace newline at end by space
3176 editor.join_lines(&JoinLines, cx);
3177 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3178 assert_eq!(
3179 editor.selections.ranges::<Point>(cx),
3180 &[Point::new(0, 3)..Point::new(0, 3)]
3181 );
3182
3183 // When multiple lines are selected, remove newlines that are spanned by the selection
3184 editor.change_selections(None, cx, |s| {
3185 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
3186 });
3187 editor.join_lines(&JoinLines, cx);
3188 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
3189 assert_eq!(
3190 editor.selections.ranges::<Point>(cx),
3191 &[Point::new(0, 11)..Point::new(0, 11)]
3192 );
3193
3194 // Undo should be transactional
3195 editor.undo(&Undo, cx);
3196 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3197 assert_eq!(
3198 editor.selections.ranges::<Point>(cx),
3199 &[Point::new(0, 5)..Point::new(2, 2)]
3200 );
3201
3202 // When joining an empty line don't insert a space
3203 editor.change_selections(None, cx, |s| {
3204 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
3205 });
3206 editor.join_lines(&JoinLines, cx);
3207 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
3208 assert_eq!(
3209 editor.selections.ranges::<Point>(cx),
3210 [Point::new(2, 3)..Point::new(2, 3)]
3211 );
3212
3213 // We can remove trailing newlines
3214 editor.join_lines(&JoinLines, cx);
3215 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3216 assert_eq!(
3217 editor.selections.ranges::<Point>(cx),
3218 [Point::new(2, 3)..Point::new(2, 3)]
3219 );
3220
3221 // We don't blow up on the last line
3222 editor.join_lines(&JoinLines, cx);
3223 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3224 assert_eq!(
3225 editor.selections.ranges::<Point>(cx),
3226 [Point::new(2, 3)..Point::new(2, 3)]
3227 );
3228
3229 // reset to test indentation
3230 editor.buffer.update(cx, |buffer, cx| {
3231 buffer.edit(
3232 [
3233 (Point::new(1, 0)..Point::new(1, 2), " "),
3234 (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
3235 ],
3236 None,
3237 cx,
3238 )
3239 });
3240
3241 // We remove any leading spaces
3242 assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
3243 editor.change_selections(None, cx, |s| {
3244 s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
3245 });
3246 editor.join_lines(&JoinLines, cx);
3247 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
3248
3249 // We don't insert a space for a line containing only spaces
3250 editor.join_lines(&JoinLines, cx);
3251 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
3252
3253 // We ignore any leading tabs
3254 editor.join_lines(&JoinLines, cx);
3255 assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
3256
3257 editor
3258 });
3259}
3260
3261#[gpui::test]
3262fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
3263 init_test(cx, |_| {});
3264
3265 cx.add_window(|cx| {
3266 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3267 let mut editor = build_editor(buffer.clone(), cx);
3268 let buffer = buffer.read(cx).as_singleton().unwrap();
3269
3270 editor.change_selections(None, cx, |s| {
3271 s.select_ranges([
3272 Point::new(0, 2)..Point::new(1, 1),
3273 Point::new(1, 2)..Point::new(1, 2),
3274 Point::new(3, 1)..Point::new(3, 2),
3275 ])
3276 });
3277
3278 editor.join_lines(&JoinLines, cx);
3279 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
3280
3281 assert_eq!(
3282 editor.selections.ranges::<Point>(cx),
3283 [
3284 Point::new(0, 7)..Point::new(0, 7),
3285 Point::new(1, 3)..Point::new(1, 3)
3286 ]
3287 );
3288 editor
3289 });
3290}
3291
3292#[gpui::test]
3293async fn test_join_lines_with_git_diff_base(
3294 executor: BackgroundExecutor,
3295 cx: &mut gpui::TestAppContext,
3296) {
3297 init_test(cx, |_| {});
3298
3299 let mut cx = EditorTestContext::new(cx).await;
3300
3301 let diff_base = r#"
3302 Line 0
3303 Line 1
3304 Line 2
3305 Line 3
3306 "#
3307 .unindent();
3308
3309 cx.set_state(
3310 &r#"
3311 ˇLine 0
3312 Line 1
3313 Line 2
3314 Line 3
3315 "#
3316 .unindent(),
3317 );
3318
3319 cx.set_diff_base(&diff_base);
3320 executor.run_until_parked();
3321
3322 // Join lines
3323 cx.update_editor(|editor, cx| {
3324 editor.join_lines(&JoinLines, cx);
3325 });
3326 executor.run_until_parked();
3327
3328 cx.assert_editor_state(
3329 &r#"
3330 Line 0ˇ Line 1
3331 Line 2
3332 Line 3
3333 "#
3334 .unindent(),
3335 );
3336 // Join again
3337 cx.update_editor(|editor, cx| {
3338 editor.join_lines(&JoinLines, cx);
3339 });
3340 executor.run_until_parked();
3341
3342 cx.assert_editor_state(
3343 &r#"
3344 Line 0 Line 1ˇ Line 2
3345 Line 3
3346 "#
3347 .unindent(),
3348 );
3349}
3350
3351#[gpui::test]
3352async fn test_custom_newlines_cause_no_false_positive_diffs(
3353 executor: BackgroundExecutor,
3354 cx: &mut gpui::TestAppContext,
3355) {
3356 init_test(cx, |_| {});
3357 let mut cx = EditorTestContext::new(cx).await;
3358 cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
3359 cx.set_diff_base("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
3360 executor.run_until_parked();
3361
3362 cx.update_editor(|editor, cx| {
3363 let snapshot = editor.snapshot(cx);
3364 assert_eq!(
3365 snapshot
3366 .diff_map
3367 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len(), &snapshot.buffer_snapshot)
3368 .collect::<Vec<_>>(),
3369 Vec::new(),
3370 "Should not have any diffs for files with custom newlines"
3371 );
3372 });
3373}
3374
3375#[gpui::test]
3376async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
3377 init_test(cx, |_| {});
3378
3379 let mut cx = EditorTestContext::new(cx).await;
3380
3381 // Test sort_lines_case_insensitive()
3382 cx.set_state(indoc! {"
3383 «z
3384 y
3385 x
3386 Z
3387 Y
3388 Xˇ»
3389 "});
3390 cx.update_editor(|e, cx| e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, cx));
3391 cx.assert_editor_state(indoc! {"
3392 «x
3393 X
3394 y
3395 Y
3396 z
3397 Zˇ»
3398 "});
3399
3400 // Test reverse_lines()
3401 cx.set_state(indoc! {"
3402 «5
3403 4
3404 3
3405 2
3406 1ˇ»
3407 "});
3408 cx.update_editor(|e, cx| e.reverse_lines(&ReverseLines, cx));
3409 cx.assert_editor_state(indoc! {"
3410 «1
3411 2
3412 3
3413 4
3414 5ˇ»
3415 "});
3416
3417 // Skip testing shuffle_line()
3418
3419 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
3420 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
3421
3422 // Don't manipulate when cursor is on single line, but expand the selection
3423 cx.set_state(indoc! {"
3424 ddˇdd
3425 ccc
3426 bb
3427 a
3428 "});
3429 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3430 cx.assert_editor_state(indoc! {"
3431 «ddddˇ»
3432 ccc
3433 bb
3434 a
3435 "});
3436
3437 // Basic manipulate case
3438 // Start selection moves to column 0
3439 // End of selection shrinks to fit shorter line
3440 cx.set_state(indoc! {"
3441 dd«d
3442 ccc
3443 bb
3444 aaaaaˇ»
3445 "});
3446 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3447 cx.assert_editor_state(indoc! {"
3448 «aaaaa
3449 bb
3450 ccc
3451 dddˇ»
3452 "});
3453
3454 // Manipulate case with newlines
3455 cx.set_state(indoc! {"
3456 dd«d
3457 ccc
3458
3459 bb
3460 aaaaa
3461
3462 ˇ»
3463 "});
3464 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3465 cx.assert_editor_state(indoc! {"
3466 «
3467
3468 aaaaa
3469 bb
3470 ccc
3471 dddˇ»
3472
3473 "});
3474
3475 // Adding new line
3476 cx.set_state(indoc! {"
3477 aa«a
3478 bbˇ»b
3479 "});
3480 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added_line")));
3481 cx.assert_editor_state(indoc! {"
3482 «aaa
3483 bbb
3484 added_lineˇ»
3485 "});
3486
3487 // Removing line
3488 cx.set_state(indoc! {"
3489 aa«a
3490 bbbˇ»
3491 "});
3492 cx.update_editor(|e, cx| {
3493 e.manipulate_lines(cx, |lines| {
3494 lines.pop();
3495 })
3496 });
3497 cx.assert_editor_state(indoc! {"
3498 «aaaˇ»
3499 "});
3500
3501 // Removing all lines
3502 cx.set_state(indoc! {"
3503 aa«a
3504 bbbˇ»
3505 "});
3506 cx.update_editor(|e, cx| {
3507 e.manipulate_lines(cx, |lines| {
3508 lines.drain(..);
3509 })
3510 });
3511 cx.assert_editor_state(indoc! {"
3512 ˇ
3513 "});
3514}
3515
3516#[gpui::test]
3517async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
3518 init_test(cx, |_| {});
3519
3520 let mut cx = EditorTestContext::new(cx).await;
3521
3522 // Consider continuous selection as single selection
3523 cx.set_state(indoc! {"
3524 Aaa«aa
3525 cˇ»c«c
3526 bb
3527 aaaˇ»aa
3528 "});
3529 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3530 cx.assert_editor_state(indoc! {"
3531 «Aaaaa
3532 ccc
3533 bb
3534 aaaaaˇ»
3535 "});
3536
3537 cx.set_state(indoc! {"
3538 Aaa«aa
3539 cˇ»c«c
3540 bb
3541 aaaˇ»aa
3542 "});
3543 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3544 cx.assert_editor_state(indoc! {"
3545 «Aaaaa
3546 ccc
3547 bbˇ»
3548 "});
3549
3550 // Consider non continuous selection as distinct dedup operations
3551 cx.set_state(indoc! {"
3552 «aaaaa
3553 bb
3554 aaaaa
3555 aaaaaˇ»
3556
3557 aaa«aaˇ»
3558 "});
3559 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3560 cx.assert_editor_state(indoc! {"
3561 «aaaaa
3562 bbˇ»
3563
3564 «aaaaaˇ»
3565 "});
3566}
3567
3568#[gpui::test]
3569async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
3570 init_test(cx, |_| {});
3571
3572 let mut cx = EditorTestContext::new(cx).await;
3573
3574 cx.set_state(indoc! {"
3575 «Aaa
3576 aAa
3577 Aaaˇ»
3578 "});
3579 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3580 cx.assert_editor_state(indoc! {"
3581 «Aaa
3582 aAaˇ»
3583 "});
3584
3585 cx.set_state(indoc! {"
3586 «Aaa
3587 aAa
3588 aaAˇ»
3589 "});
3590 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3591 cx.assert_editor_state(indoc! {"
3592 «Aaaˇ»
3593 "});
3594}
3595
3596#[gpui::test]
3597async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
3598 init_test(cx, |_| {});
3599
3600 let mut cx = EditorTestContext::new(cx).await;
3601
3602 // Manipulate with multiple selections on a single line
3603 cx.set_state(indoc! {"
3604 dd«dd
3605 cˇ»c«c
3606 bb
3607 aaaˇ»aa
3608 "});
3609 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3610 cx.assert_editor_state(indoc! {"
3611 «aaaaa
3612 bb
3613 ccc
3614 ddddˇ»
3615 "});
3616
3617 // Manipulate with multiple disjoin selections
3618 cx.set_state(indoc! {"
3619 5«
3620 4
3621 3
3622 2
3623 1ˇ»
3624
3625 dd«dd
3626 ccc
3627 bb
3628 aaaˇ»aa
3629 "});
3630 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3631 cx.assert_editor_state(indoc! {"
3632 «1
3633 2
3634 3
3635 4
3636 5ˇ»
3637
3638 «aaaaa
3639 bb
3640 ccc
3641 ddddˇ»
3642 "});
3643
3644 // Adding lines on each selection
3645 cx.set_state(indoc! {"
3646 2«
3647 1ˇ»
3648
3649 bb«bb
3650 aaaˇ»aa
3651 "});
3652 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added line")));
3653 cx.assert_editor_state(indoc! {"
3654 «2
3655 1
3656 added lineˇ»
3657
3658 «bbbb
3659 aaaaa
3660 added lineˇ»
3661 "});
3662
3663 // Removing lines on each selection
3664 cx.set_state(indoc! {"
3665 2«
3666 1ˇ»
3667
3668 bb«bb
3669 aaaˇ»aa
3670 "});
3671 cx.update_editor(|e, cx| {
3672 e.manipulate_lines(cx, |lines| {
3673 lines.pop();
3674 })
3675 });
3676 cx.assert_editor_state(indoc! {"
3677 «2ˇ»
3678
3679 «bbbbˇ»
3680 "});
3681}
3682
3683#[gpui::test]
3684async fn test_manipulate_text(cx: &mut TestAppContext) {
3685 init_test(cx, |_| {});
3686
3687 let mut cx = EditorTestContext::new(cx).await;
3688
3689 // Test convert_to_upper_case()
3690 cx.set_state(indoc! {"
3691 «hello worldˇ»
3692 "});
3693 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3694 cx.assert_editor_state(indoc! {"
3695 «HELLO WORLDˇ»
3696 "});
3697
3698 // Test convert_to_lower_case()
3699 cx.set_state(indoc! {"
3700 «HELLO WORLDˇ»
3701 "});
3702 cx.update_editor(|e, cx| e.convert_to_lower_case(&ConvertToLowerCase, cx));
3703 cx.assert_editor_state(indoc! {"
3704 «hello worldˇ»
3705 "});
3706
3707 // Test multiple line, single selection case
3708 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3709 cx.set_state(indoc! {"
3710 «The quick brown
3711 fox jumps over
3712 the lazy dogˇ»
3713 "});
3714 cx.update_editor(|e, cx| e.convert_to_title_case(&ConvertToTitleCase, cx));
3715 cx.assert_editor_state(indoc! {"
3716 «The Quick Brown
3717 Fox Jumps Over
3718 The Lazy Dogˇ»
3719 "});
3720
3721 // Test multiple line, single selection case
3722 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3723 cx.set_state(indoc! {"
3724 «The quick brown
3725 fox jumps over
3726 the lazy dogˇ»
3727 "});
3728 cx.update_editor(|e, cx| e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, cx));
3729 cx.assert_editor_state(indoc! {"
3730 «TheQuickBrown
3731 FoxJumpsOver
3732 TheLazyDogˇ»
3733 "});
3734
3735 // From here on out, test more complex cases of manipulate_text()
3736
3737 // Test no selection case - should affect words cursors are in
3738 // Cursor at beginning, middle, and end of word
3739 cx.set_state(indoc! {"
3740 ˇhello big beauˇtiful worldˇ
3741 "});
3742 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3743 cx.assert_editor_state(indoc! {"
3744 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3745 "});
3746
3747 // Test multiple selections on a single line and across multiple lines
3748 cx.set_state(indoc! {"
3749 «Theˇ» quick «brown
3750 foxˇ» jumps «overˇ»
3751 the «lazyˇ» dog
3752 "});
3753 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3754 cx.assert_editor_state(indoc! {"
3755 «THEˇ» quick «BROWN
3756 FOXˇ» jumps «OVERˇ»
3757 the «LAZYˇ» dog
3758 "});
3759
3760 // Test case where text length grows
3761 cx.set_state(indoc! {"
3762 «tschüߡ»
3763 "});
3764 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3765 cx.assert_editor_state(indoc! {"
3766 «TSCHÜSSˇ»
3767 "});
3768
3769 // Test to make sure we don't crash when text shrinks
3770 cx.set_state(indoc! {"
3771 aaa_bbbˇ
3772 "});
3773 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3774 cx.assert_editor_state(indoc! {"
3775 «aaaBbbˇ»
3776 "});
3777
3778 // Test to make sure we all aware of the fact that each word can grow and shrink
3779 // Final selections should be aware of this fact
3780 cx.set_state(indoc! {"
3781 aaa_bˇbb bbˇb_ccc ˇccc_ddd
3782 "});
3783 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3784 cx.assert_editor_state(indoc! {"
3785 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
3786 "});
3787
3788 cx.set_state(indoc! {"
3789 «hElLo, WoRld!ˇ»
3790 "});
3791 cx.update_editor(|e, cx| e.convert_to_opposite_case(&ConvertToOppositeCase, cx));
3792 cx.assert_editor_state(indoc! {"
3793 «HeLlO, wOrLD!ˇ»
3794 "});
3795}
3796
3797#[gpui::test]
3798fn test_duplicate_line(cx: &mut TestAppContext) {
3799 init_test(cx, |_| {});
3800
3801 let view = cx.add_window(|cx| {
3802 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3803 build_editor(buffer, cx)
3804 });
3805 _ = view.update(cx, |view, cx| {
3806 view.change_selections(None, cx, |s| {
3807 s.select_display_ranges([
3808 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3809 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3810 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3811 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3812 ])
3813 });
3814 view.duplicate_line_down(&DuplicateLineDown, cx);
3815 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3816 assert_eq!(
3817 view.selections.display_ranges(cx),
3818 vec![
3819 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3820 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
3821 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3822 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3823 ]
3824 );
3825 });
3826
3827 let view = cx.add_window(|cx| {
3828 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3829 build_editor(buffer, cx)
3830 });
3831 _ = view.update(cx, |view, cx| {
3832 view.change_selections(None, cx, |s| {
3833 s.select_display_ranges([
3834 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3835 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3836 ])
3837 });
3838 view.duplicate_line_down(&DuplicateLineDown, cx);
3839 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3840 assert_eq!(
3841 view.selections.display_ranges(cx),
3842 vec![
3843 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
3844 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
3845 ]
3846 );
3847 });
3848
3849 // With `move_upwards` the selections stay in place, except for
3850 // the lines inserted above them
3851 let view = cx.add_window(|cx| {
3852 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3853 build_editor(buffer, cx)
3854 });
3855 _ = view.update(cx, |view, cx| {
3856 view.change_selections(None, cx, |s| {
3857 s.select_display_ranges([
3858 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3859 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3860 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3861 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3862 ])
3863 });
3864 view.duplicate_line_up(&DuplicateLineUp, cx);
3865 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3866 assert_eq!(
3867 view.selections.display_ranges(cx),
3868 vec![
3869 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3870 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3871 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
3872 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3873 ]
3874 );
3875 });
3876
3877 let view = cx.add_window(|cx| {
3878 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3879 build_editor(buffer, cx)
3880 });
3881 _ = view.update(cx, |view, cx| {
3882 view.change_selections(None, cx, |s| {
3883 s.select_display_ranges([
3884 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3885 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3886 ])
3887 });
3888 view.duplicate_line_up(&DuplicateLineUp, cx);
3889 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3890 assert_eq!(
3891 view.selections.display_ranges(cx),
3892 vec![
3893 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3894 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3895 ]
3896 );
3897 });
3898
3899 let view = cx.add_window(|cx| {
3900 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3901 build_editor(buffer, cx)
3902 });
3903 _ = view.update(cx, |view, cx| {
3904 view.change_selections(None, cx, |s| {
3905 s.select_display_ranges([
3906 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3907 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3908 ])
3909 });
3910 view.duplicate_selection(&DuplicateSelection, cx);
3911 assert_eq!(view.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
3912 assert_eq!(
3913 view.selections.display_ranges(cx),
3914 vec![
3915 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3916 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
3917 ]
3918 );
3919 });
3920}
3921
3922#[gpui::test]
3923fn test_move_line_up_down(cx: &mut TestAppContext) {
3924 init_test(cx, |_| {});
3925
3926 let view = cx.add_window(|cx| {
3927 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3928 build_editor(buffer, cx)
3929 });
3930 _ = view.update(cx, |view, cx| {
3931 view.fold_creases(
3932 vec![
3933 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
3934 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
3935 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
3936 ],
3937 true,
3938 cx,
3939 );
3940 view.change_selections(None, cx, |s| {
3941 s.select_display_ranges([
3942 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3943 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3944 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3945 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
3946 ])
3947 });
3948 assert_eq!(
3949 view.display_text(cx),
3950 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
3951 );
3952
3953 view.move_line_up(&MoveLineUp, cx);
3954 assert_eq!(
3955 view.display_text(cx),
3956 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
3957 );
3958 assert_eq!(
3959 view.selections.display_ranges(cx),
3960 vec![
3961 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3962 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3963 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3964 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3965 ]
3966 );
3967 });
3968
3969 _ = view.update(cx, |view, cx| {
3970 view.move_line_down(&MoveLineDown, cx);
3971 assert_eq!(
3972 view.display_text(cx),
3973 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
3974 );
3975 assert_eq!(
3976 view.selections.display_ranges(cx),
3977 vec![
3978 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3979 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3980 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3981 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3982 ]
3983 );
3984 });
3985
3986 _ = view.update(cx, |view, cx| {
3987 view.move_line_down(&MoveLineDown, cx);
3988 assert_eq!(
3989 view.display_text(cx),
3990 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
3991 );
3992 assert_eq!(
3993 view.selections.display_ranges(cx),
3994 vec![
3995 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3996 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3997 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3998 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3999 ]
4000 );
4001 });
4002
4003 _ = view.update(cx, |view, cx| {
4004 view.move_line_up(&MoveLineUp, cx);
4005 assert_eq!(
4006 view.display_text(cx),
4007 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
4008 );
4009 assert_eq!(
4010 view.selections.display_ranges(cx),
4011 vec![
4012 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
4013 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4014 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
4015 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
4016 ]
4017 );
4018 });
4019}
4020
4021#[gpui::test]
4022fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
4023 init_test(cx, |_| {});
4024
4025 let editor = cx.add_window(|cx| {
4026 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4027 build_editor(buffer, cx)
4028 });
4029 _ = editor.update(cx, |editor, cx| {
4030 let snapshot = editor.buffer.read(cx).snapshot(cx);
4031 editor.insert_blocks(
4032 [BlockProperties {
4033 style: BlockStyle::Fixed,
4034 placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
4035 height: 1,
4036 render: Arc::new(|_| div().into_any()),
4037 priority: 0,
4038 }],
4039 Some(Autoscroll::fit()),
4040 cx,
4041 );
4042 editor.change_selections(None, cx, |s| {
4043 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
4044 });
4045 editor.move_line_down(&MoveLineDown, cx);
4046 });
4047}
4048
4049#[gpui::test]
4050async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
4051 init_test(cx, |_| {});
4052
4053 let mut cx = EditorTestContext::new(cx).await;
4054 cx.set_state(
4055 &"
4056 ˇzero
4057 one
4058 two
4059 three
4060 four
4061 five
4062 "
4063 .unindent(),
4064 );
4065
4066 // Create a four-line block that replaces three lines of text.
4067 cx.update_editor(|editor, cx| {
4068 let snapshot = editor.snapshot(cx);
4069 let snapshot = &snapshot.buffer_snapshot;
4070 let placement = BlockPlacement::Replace(
4071 snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
4072 );
4073 editor.insert_blocks(
4074 [BlockProperties {
4075 placement,
4076 height: 4,
4077 style: BlockStyle::Sticky,
4078 render: Arc::new(|_| gpui::div().into_any_element()),
4079 priority: 0,
4080 }],
4081 None,
4082 cx,
4083 );
4084 });
4085
4086 // Move down so that the cursor touches the block.
4087 cx.update_editor(|editor, cx| {
4088 editor.move_down(&Default::default(), cx);
4089 });
4090 cx.assert_editor_state(
4091 &"
4092 zero
4093 «one
4094 two
4095 threeˇ»
4096 four
4097 five
4098 "
4099 .unindent(),
4100 );
4101
4102 // Move down past the block.
4103 cx.update_editor(|editor, cx| {
4104 editor.move_down(&Default::default(), cx);
4105 });
4106 cx.assert_editor_state(
4107 &"
4108 zero
4109 one
4110 two
4111 three
4112 ˇfour
4113 five
4114 "
4115 .unindent(),
4116 );
4117}
4118
4119#[gpui::test]
4120fn test_transpose(cx: &mut TestAppContext) {
4121 init_test(cx, |_| {});
4122
4123 _ = cx.add_window(|cx| {
4124 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
4125 editor.set_style(EditorStyle::default(), cx);
4126 editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
4127 editor.transpose(&Default::default(), cx);
4128 assert_eq!(editor.text(cx), "bac");
4129 assert_eq!(editor.selections.ranges(cx), [2..2]);
4130
4131 editor.transpose(&Default::default(), cx);
4132 assert_eq!(editor.text(cx), "bca");
4133 assert_eq!(editor.selections.ranges(cx), [3..3]);
4134
4135 editor.transpose(&Default::default(), cx);
4136 assert_eq!(editor.text(cx), "bac");
4137 assert_eq!(editor.selections.ranges(cx), [3..3]);
4138
4139 editor
4140 });
4141
4142 _ = cx.add_window(|cx| {
4143 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
4144 editor.set_style(EditorStyle::default(), cx);
4145 editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
4146 editor.transpose(&Default::default(), cx);
4147 assert_eq!(editor.text(cx), "acb\nde");
4148 assert_eq!(editor.selections.ranges(cx), [3..3]);
4149
4150 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
4151 editor.transpose(&Default::default(), cx);
4152 assert_eq!(editor.text(cx), "acbd\ne");
4153 assert_eq!(editor.selections.ranges(cx), [5..5]);
4154
4155 editor.transpose(&Default::default(), cx);
4156 assert_eq!(editor.text(cx), "acbde\n");
4157 assert_eq!(editor.selections.ranges(cx), [6..6]);
4158
4159 editor.transpose(&Default::default(), cx);
4160 assert_eq!(editor.text(cx), "acbd\ne");
4161 assert_eq!(editor.selections.ranges(cx), [6..6]);
4162
4163 editor
4164 });
4165
4166 _ = cx.add_window(|cx| {
4167 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
4168 editor.set_style(EditorStyle::default(), cx);
4169 editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
4170 editor.transpose(&Default::default(), cx);
4171 assert_eq!(editor.text(cx), "bacd\ne");
4172 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
4173
4174 editor.transpose(&Default::default(), cx);
4175 assert_eq!(editor.text(cx), "bcade\n");
4176 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
4177
4178 editor.transpose(&Default::default(), cx);
4179 assert_eq!(editor.text(cx), "bcda\ne");
4180 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4181
4182 editor.transpose(&Default::default(), cx);
4183 assert_eq!(editor.text(cx), "bcade\n");
4184 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4185
4186 editor.transpose(&Default::default(), cx);
4187 assert_eq!(editor.text(cx), "bcaed\n");
4188 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
4189
4190 editor
4191 });
4192
4193 _ = cx.add_window(|cx| {
4194 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
4195 editor.set_style(EditorStyle::default(), cx);
4196 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
4197 editor.transpose(&Default::default(), cx);
4198 assert_eq!(editor.text(cx), "🏀🍐✋");
4199 assert_eq!(editor.selections.ranges(cx), [8..8]);
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.transpose(&Default::default(), cx);
4206 assert_eq!(editor.text(cx), "🏀🍐✋");
4207 assert_eq!(editor.selections.ranges(cx), [11..11]);
4208
4209 editor
4210 });
4211}
4212
4213#[gpui::test]
4214async fn test_rewrap(cx: &mut TestAppContext) {
4215 init_test(cx, |_| {});
4216
4217 let mut cx = EditorTestContext::new(cx).await;
4218
4219 let language_with_c_comments = Arc::new(Language::new(
4220 LanguageConfig {
4221 line_comments: vec!["// ".into()],
4222 ..LanguageConfig::default()
4223 },
4224 None,
4225 ));
4226 let language_with_pound_comments = Arc::new(Language::new(
4227 LanguageConfig {
4228 line_comments: vec!["# ".into()],
4229 ..LanguageConfig::default()
4230 },
4231 None,
4232 ));
4233 let markdown_language = Arc::new(Language::new(
4234 LanguageConfig {
4235 name: "Markdown".into(),
4236 ..LanguageConfig::default()
4237 },
4238 None,
4239 ));
4240 let language_with_doc_comments = Arc::new(Language::new(
4241 LanguageConfig {
4242 line_comments: vec!["// ".into(), "/// ".into()],
4243 ..LanguageConfig::default()
4244 },
4245 Some(tree_sitter_rust::LANGUAGE.into()),
4246 ));
4247
4248 let plaintext_language = Arc::new(Language::new(
4249 LanguageConfig {
4250 name: "Plain Text".into(),
4251 ..LanguageConfig::default()
4252 },
4253 None,
4254 ));
4255
4256 assert_rewrap(
4257 indoc! {"
4258 // ˇ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.
4259 "},
4260 indoc! {"
4261 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4262 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4263 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4264 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4265 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4266 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4267 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4268 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4269 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4270 // porttitor id. Aliquam id accumsan eros.
4271 "},
4272 language_with_c_comments.clone(),
4273 &mut cx,
4274 );
4275
4276 // Test that rewrapping works inside of a selection
4277 assert_rewrap(
4278 indoc! {"
4279 «// 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.ˇ»
4280 "},
4281 indoc! {"
4282 «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4283 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4284 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4285 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4286 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4287 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4288 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4289 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4290 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4291 // porttitor id. Aliquam id accumsan eros.ˇ»
4292 "},
4293 language_with_c_comments.clone(),
4294 &mut cx,
4295 );
4296
4297 // Test that cursors that expand to the same region are collapsed.
4298 assert_rewrap(
4299 indoc! {"
4300 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4301 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4302 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4303 // ˇ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.
4304 "},
4305 indoc! {"
4306 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4307 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4308 // auctor, eu lacinia sapien scelerisque. ˇVivamus sit amet neque et quam
4309 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4310 // Pellentesque odio lectus, iaculis ac volutpat et, ˇblandit quis urna. Sed
4311 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4312 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4313 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4314 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4315 // porttitor id. Aliquam id accumsan eros.
4316 "},
4317 language_with_c_comments.clone(),
4318 &mut cx,
4319 );
4320
4321 // Test that non-contiguous selections are treated separately.
4322 assert_rewrap(
4323 indoc! {"
4324 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4325 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4326 //
4327 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4328 // ˇ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.
4329 "},
4330 indoc! {"
4331 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4332 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4333 // auctor, eu lacinia sapien scelerisque.
4334 //
4335 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
4336 // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4337 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
4338 // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
4339 // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
4340 // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
4341 // vulputate turpis porttitor id. Aliquam id accumsan eros.
4342 "},
4343 language_with_c_comments.clone(),
4344 &mut cx,
4345 );
4346
4347 // Test that different comment prefixes are supported.
4348 assert_rewrap(
4349 indoc! {"
4350 # ˇ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.
4351 "},
4352 indoc! {"
4353 # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4354 # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4355 # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4356 # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4357 # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
4358 # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
4359 # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
4360 # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
4361 # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
4362 # accumsan eros.
4363 "},
4364 language_with_pound_comments.clone(),
4365 &mut cx,
4366 );
4367
4368 // Test that rewrapping is ignored outside of comments in most languages.
4369 assert_rewrap(
4370 indoc! {"
4371 /// Adds two numbers.
4372 /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4373 fn add(a: u32, b: u32) -> u32 {
4374 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ˇ
4375 }
4376 "},
4377 indoc! {"
4378 /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
4379 /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4380 fn add(a: u32, b: u32) -> u32 {
4381 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ˇ
4382 }
4383 "},
4384 language_with_doc_comments.clone(),
4385 &mut cx,
4386 );
4387
4388 // Test that rewrapping works in Markdown and Plain Text languages.
4389 assert_rewrap(
4390 indoc! {"
4391 # Hello
4392
4393 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.
4394 "},
4395 indoc! {"
4396 # Hello
4397
4398 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4399 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4400 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4401 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4402 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4403 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4404 Integer sit amet scelerisque nisi.
4405 "},
4406 markdown_language,
4407 &mut cx,
4408 );
4409
4410 assert_rewrap(
4411 indoc! {"
4412 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.
4413 "},
4414 indoc! {"
4415 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4416 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4417 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4418 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4419 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4420 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4421 Integer sit amet scelerisque nisi.
4422 "},
4423 plaintext_language,
4424 &mut cx,
4425 );
4426
4427 // Test rewrapping unaligned comments in a selection.
4428 assert_rewrap(
4429 indoc! {"
4430 fn foo() {
4431 if true {
4432 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4433 // Praesent semper egestas tellus id dignissim.ˇ»
4434 do_something();
4435 } else {
4436 //
4437 }
4438 }
4439 "},
4440 indoc! {"
4441 fn foo() {
4442 if true {
4443 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4444 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4445 // egestas tellus id dignissim.ˇ»
4446 do_something();
4447 } else {
4448 //
4449 }
4450 }
4451 "},
4452 language_with_doc_comments.clone(),
4453 &mut cx,
4454 );
4455
4456 assert_rewrap(
4457 indoc! {"
4458 fn foo() {
4459 if true {
4460 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4461 // Praesent semper egestas tellus id dignissim.»
4462 do_something();
4463 } else {
4464 //
4465 }
4466
4467 }
4468 "},
4469 indoc! {"
4470 fn foo() {
4471 if true {
4472 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4473 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4474 // egestas tellus id dignissim.»
4475 do_something();
4476 } else {
4477 //
4478 }
4479
4480 }
4481 "},
4482 language_with_doc_comments.clone(),
4483 &mut cx,
4484 );
4485
4486 #[track_caller]
4487 fn assert_rewrap(
4488 unwrapped_text: &str,
4489 wrapped_text: &str,
4490 language: Arc<Language>,
4491 cx: &mut EditorTestContext,
4492 ) {
4493 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4494 cx.set_state(unwrapped_text);
4495 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4496 cx.assert_editor_state(wrapped_text);
4497 }
4498}
4499
4500#[gpui::test]
4501async fn test_clipboard(cx: &mut gpui::TestAppContext) {
4502 init_test(cx, |_| {});
4503
4504 let mut cx = EditorTestContext::new(cx).await;
4505
4506 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
4507 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4508 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
4509
4510 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
4511 cx.set_state("two ˇfour ˇsix ˇ");
4512 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4513 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
4514
4515 // Paste again but with only two cursors. Since the number of cursors doesn't
4516 // match the number of slices in the clipboard, the entire clipboard text
4517 // is pasted at each cursor.
4518 cx.set_state("ˇtwo one✅ four three six five ˇ");
4519 cx.update_editor(|e, cx| {
4520 e.handle_input("( ", cx);
4521 e.paste(&Paste, cx);
4522 e.handle_input(") ", cx);
4523 });
4524 cx.assert_editor_state(
4525 &([
4526 "( one✅ ",
4527 "three ",
4528 "five ) ˇtwo one✅ four three six five ( one✅ ",
4529 "three ",
4530 "five ) ˇ",
4531 ]
4532 .join("\n")),
4533 );
4534
4535 // Cut with three selections, one of which is full-line.
4536 cx.set_state(indoc! {"
4537 1«2ˇ»3
4538 4ˇ567
4539 «8ˇ»9"});
4540 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4541 cx.assert_editor_state(indoc! {"
4542 1ˇ3
4543 ˇ9"});
4544
4545 // Paste with three selections, noticing how the copied selection that was full-line
4546 // gets inserted before the second cursor.
4547 cx.set_state(indoc! {"
4548 1ˇ3
4549 9ˇ
4550 «oˇ»ne"});
4551 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4552 cx.assert_editor_state(indoc! {"
4553 12ˇ3
4554 4567
4555 9ˇ
4556 8ˇne"});
4557
4558 // Copy with a single cursor only, which writes the whole line into the clipboard.
4559 cx.set_state(indoc! {"
4560 The quick brown
4561 fox juˇmps over
4562 the lazy dog"});
4563 cx.update_editor(|e, cx| e.copy(&Copy, cx));
4564 assert_eq!(
4565 cx.read_from_clipboard()
4566 .and_then(|item| item.text().as_deref().map(str::to_string)),
4567 Some("fox jumps over\n".to_string())
4568 );
4569
4570 // Paste with three selections, noticing how the copied full-line selection is inserted
4571 // before the empty selections but replaces the selection that is non-empty.
4572 cx.set_state(indoc! {"
4573 Tˇhe quick brown
4574 «foˇ»x jumps over
4575 tˇhe lazy dog"});
4576 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4577 cx.assert_editor_state(indoc! {"
4578 fox jumps over
4579 Tˇhe quick brown
4580 fox jumps over
4581 ˇx jumps over
4582 fox jumps over
4583 tˇhe lazy dog"});
4584}
4585
4586#[gpui::test]
4587async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
4588 init_test(cx, |_| {});
4589
4590 let mut cx = EditorTestContext::new(cx).await;
4591 let language = Arc::new(Language::new(
4592 LanguageConfig::default(),
4593 Some(tree_sitter_rust::LANGUAGE.into()),
4594 ));
4595 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4596
4597 // Cut an indented block, without the leading whitespace.
4598 cx.set_state(indoc! {"
4599 const a: B = (
4600 c(),
4601 «d(
4602 e,
4603 f
4604 )ˇ»
4605 );
4606 "});
4607 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4608 cx.assert_editor_state(indoc! {"
4609 const a: B = (
4610 c(),
4611 ˇ
4612 );
4613 "});
4614
4615 // Paste it at the same position.
4616 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4617 cx.assert_editor_state(indoc! {"
4618 const a: B = (
4619 c(),
4620 d(
4621 e,
4622 f
4623 )ˇ
4624 );
4625 "});
4626
4627 // Paste it at a line with a lower indent level.
4628 cx.set_state(indoc! {"
4629 ˇ
4630 const a: B = (
4631 c(),
4632 );
4633 "});
4634 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4635 cx.assert_editor_state(indoc! {"
4636 d(
4637 e,
4638 f
4639 )ˇ
4640 const a: B = (
4641 c(),
4642 );
4643 "});
4644
4645 // Cut an indented block, with the leading whitespace.
4646 cx.set_state(indoc! {"
4647 const a: B = (
4648 c(),
4649 « d(
4650 e,
4651 f
4652 )
4653 ˇ»);
4654 "});
4655 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4656 cx.assert_editor_state(indoc! {"
4657 const a: B = (
4658 c(),
4659 ˇ);
4660 "});
4661
4662 // Paste it at the same position.
4663 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4664 cx.assert_editor_state(indoc! {"
4665 const a: B = (
4666 c(),
4667 d(
4668 e,
4669 f
4670 )
4671 ˇ);
4672 "});
4673
4674 // Paste it at a line with a higher indent level.
4675 cx.set_state(indoc! {"
4676 const a: B = (
4677 c(),
4678 d(
4679 e,
4680 fˇ
4681 )
4682 );
4683 "});
4684 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4685 cx.assert_editor_state(indoc! {"
4686 const a: B = (
4687 c(),
4688 d(
4689 e,
4690 f d(
4691 e,
4692 f
4693 )
4694 ˇ
4695 )
4696 );
4697 "});
4698}
4699
4700#[gpui::test]
4701fn test_select_all(cx: &mut TestAppContext) {
4702 init_test(cx, |_| {});
4703
4704 let view = cx.add_window(|cx| {
4705 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
4706 build_editor(buffer, cx)
4707 });
4708 _ = view.update(cx, |view, cx| {
4709 view.select_all(&SelectAll, cx);
4710 assert_eq!(
4711 view.selections.display_ranges(cx),
4712 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
4713 );
4714 });
4715}
4716
4717#[gpui::test]
4718fn test_select_line(cx: &mut TestAppContext) {
4719 init_test(cx, |_| {});
4720
4721 let view = cx.add_window(|cx| {
4722 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
4723 build_editor(buffer, cx)
4724 });
4725 _ = view.update(cx, |view, cx| {
4726 view.change_selections(None, cx, |s| {
4727 s.select_display_ranges([
4728 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4729 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4730 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4731 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
4732 ])
4733 });
4734 view.select_line(&SelectLine, cx);
4735 assert_eq!(
4736 view.selections.display_ranges(cx),
4737 vec![
4738 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
4739 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
4740 ]
4741 );
4742 });
4743
4744 _ = view.update(cx, |view, cx| {
4745 view.select_line(&SelectLine, cx);
4746 assert_eq!(
4747 view.selections.display_ranges(cx),
4748 vec![
4749 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
4750 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
4751 ]
4752 );
4753 });
4754
4755 _ = view.update(cx, |view, cx| {
4756 view.select_line(&SelectLine, cx);
4757 assert_eq!(
4758 view.selections.display_ranges(cx),
4759 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
4760 );
4761 });
4762}
4763
4764#[gpui::test]
4765fn test_split_selection_into_lines(cx: &mut TestAppContext) {
4766 init_test(cx, |_| {});
4767
4768 let view = cx.add_window(|cx| {
4769 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
4770 build_editor(buffer, cx)
4771 });
4772 _ = view.update(cx, |view, cx| {
4773 view.fold_creases(
4774 vec![
4775 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4776 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4777 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4778 ],
4779 true,
4780 cx,
4781 );
4782 view.change_selections(None, cx, |s| {
4783 s.select_display_ranges([
4784 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4785 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4786 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4787 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
4788 ])
4789 });
4790 assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i");
4791 });
4792
4793 _ = view.update(cx, |view, cx| {
4794 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4795 assert_eq!(
4796 view.display_text(cx),
4797 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
4798 );
4799 assert_eq!(
4800 view.selections.display_ranges(cx),
4801 [
4802 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4803 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4804 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
4805 DisplayPoint::new(DisplayRow(5), 4)..DisplayPoint::new(DisplayRow(5), 4)
4806 ]
4807 );
4808 });
4809
4810 _ = view.update(cx, |view, cx| {
4811 view.change_selections(None, cx, |s| {
4812 s.select_display_ranges([
4813 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
4814 ])
4815 });
4816 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4817 assert_eq!(
4818 view.display_text(cx),
4819 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
4820 );
4821 assert_eq!(
4822 view.selections.display_ranges(cx),
4823 [
4824 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
4825 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
4826 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
4827 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
4828 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
4829 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
4830 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5),
4831 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(7), 0)
4832 ]
4833 );
4834 });
4835}
4836
4837#[gpui::test]
4838async fn test_add_selection_above_below(cx: &mut TestAppContext) {
4839 init_test(cx, |_| {});
4840
4841 let mut cx = EditorTestContext::new(cx).await;
4842
4843 // let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
4844 cx.set_state(indoc!(
4845 r#"abc
4846 defˇghi
4847
4848 jk
4849 nlmo
4850 "#
4851 ));
4852
4853 cx.update_editor(|editor, cx| {
4854 editor.add_selection_above(&Default::default(), cx);
4855 });
4856
4857 cx.assert_editor_state(indoc!(
4858 r#"abcˇ
4859 defˇghi
4860
4861 jk
4862 nlmo
4863 "#
4864 ));
4865
4866 cx.update_editor(|editor, cx| {
4867 editor.add_selection_above(&Default::default(), cx);
4868 });
4869
4870 cx.assert_editor_state(indoc!(
4871 r#"abcˇ
4872 defˇghi
4873
4874 jk
4875 nlmo
4876 "#
4877 ));
4878
4879 cx.update_editor(|view, cx| {
4880 view.add_selection_below(&Default::default(), cx);
4881 });
4882
4883 cx.assert_editor_state(indoc!(
4884 r#"abc
4885 defˇghi
4886
4887 jk
4888 nlmo
4889 "#
4890 ));
4891
4892 cx.update_editor(|view, cx| {
4893 view.undo_selection(&Default::default(), cx);
4894 });
4895
4896 cx.assert_editor_state(indoc!(
4897 r#"abcˇ
4898 defˇghi
4899
4900 jk
4901 nlmo
4902 "#
4903 ));
4904
4905 cx.update_editor(|view, cx| {
4906 view.redo_selection(&Default::default(), cx);
4907 });
4908
4909 cx.assert_editor_state(indoc!(
4910 r#"abc
4911 defˇghi
4912
4913 jk
4914 nlmo
4915 "#
4916 ));
4917
4918 cx.update_editor(|view, cx| {
4919 view.add_selection_below(&Default::default(), cx);
4920 });
4921
4922 cx.assert_editor_state(indoc!(
4923 r#"abc
4924 defˇghi
4925
4926 jk
4927 nlmˇo
4928 "#
4929 ));
4930
4931 cx.update_editor(|view, cx| {
4932 view.add_selection_below(&Default::default(), cx);
4933 });
4934
4935 cx.assert_editor_state(indoc!(
4936 r#"abc
4937 defˇghi
4938
4939 jk
4940 nlmˇo
4941 "#
4942 ));
4943
4944 // change selections
4945 cx.set_state(indoc!(
4946 r#"abc
4947 def«ˇg»hi
4948
4949 jk
4950 nlmo
4951 "#
4952 ));
4953
4954 cx.update_editor(|view, cx| {
4955 view.add_selection_below(&Default::default(), cx);
4956 });
4957
4958 cx.assert_editor_state(indoc!(
4959 r#"abc
4960 def«ˇg»hi
4961
4962 jk
4963 nlm«ˇo»
4964 "#
4965 ));
4966
4967 cx.update_editor(|view, cx| {
4968 view.add_selection_below(&Default::default(), cx);
4969 });
4970
4971 cx.assert_editor_state(indoc!(
4972 r#"abc
4973 def«ˇg»hi
4974
4975 jk
4976 nlm«ˇo»
4977 "#
4978 ));
4979
4980 cx.update_editor(|view, cx| {
4981 view.add_selection_above(&Default::default(), cx);
4982 });
4983
4984 cx.assert_editor_state(indoc!(
4985 r#"abc
4986 def«ˇg»hi
4987
4988 jk
4989 nlmo
4990 "#
4991 ));
4992
4993 cx.update_editor(|view, cx| {
4994 view.add_selection_above(&Default::default(), cx);
4995 });
4996
4997 cx.assert_editor_state(indoc!(
4998 r#"abc
4999 def«ˇg»hi
5000
5001 jk
5002 nlmo
5003 "#
5004 ));
5005
5006 // Change selections again
5007 cx.set_state(indoc!(
5008 r#"a«bc
5009 defgˇ»hi
5010
5011 jk
5012 nlmo
5013 "#
5014 ));
5015
5016 cx.update_editor(|view, cx| {
5017 view.add_selection_below(&Default::default(), cx);
5018 });
5019
5020 cx.assert_editor_state(indoc!(
5021 r#"a«bcˇ»
5022 d«efgˇ»hi
5023
5024 j«kˇ»
5025 nlmo
5026 "#
5027 ));
5028
5029 cx.update_editor(|view, cx| {
5030 view.add_selection_below(&Default::default(), cx);
5031 });
5032 cx.assert_editor_state(indoc!(
5033 r#"a«bcˇ»
5034 d«efgˇ»hi
5035
5036 j«kˇ»
5037 n«lmoˇ»
5038 "#
5039 ));
5040 cx.update_editor(|view, cx| {
5041 view.add_selection_above(&Default::default(), cx);
5042 });
5043
5044 cx.assert_editor_state(indoc!(
5045 r#"a«bcˇ»
5046 d«efgˇ»hi
5047
5048 j«kˇ»
5049 nlmo
5050 "#
5051 ));
5052
5053 // Change selections again
5054 cx.set_state(indoc!(
5055 r#"abc
5056 d«ˇefghi
5057
5058 jk
5059 nlm»o
5060 "#
5061 ));
5062
5063 cx.update_editor(|view, cx| {
5064 view.add_selection_above(&Default::default(), cx);
5065 });
5066
5067 cx.assert_editor_state(indoc!(
5068 r#"a«ˇbc»
5069 d«ˇef»ghi
5070
5071 j«ˇk»
5072 n«ˇlm»o
5073 "#
5074 ));
5075
5076 cx.update_editor(|view, cx| {
5077 view.add_selection_below(&Default::default(), cx);
5078 });
5079
5080 cx.assert_editor_state(indoc!(
5081 r#"abc
5082 d«ˇef»ghi
5083
5084 j«ˇk»
5085 n«ˇlm»o
5086 "#
5087 ));
5088}
5089
5090#[gpui::test]
5091async fn test_select_next(cx: &mut gpui::TestAppContext) {
5092 init_test(cx, |_| {});
5093
5094 let mut cx = EditorTestContext::new(cx).await;
5095 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5096
5097 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5098 .unwrap();
5099 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5100
5101 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5102 .unwrap();
5103 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5104
5105 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5106 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5107
5108 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5109 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5110
5111 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5112 .unwrap();
5113 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5114
5115 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5116 .unwrap();
5117 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5118}
5119
5120#[gpui::test]
5121async fn test_select_all_matches(cx: &mut gpui::TestAppContext) {
5122 init_test(cx, |_| {});
5123
5124 let mut cx = EditorTestContext::new(cx).await;
5125 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5126
5127 cx.update_editor(|e, cx| e.select_all_matches(&SelectAllMatches, cx))
5128 .unwrap();
5129 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5130}
5131
5132#[gpui::test]
5133async fn test_select_next_with_multiple_carets(cx: &mut gpui::TestAppContext) {
5134 init_test(cx, |_| {});
5135
5136 let mut cx = EditorTestContext::new(cx).await;
5137 cx.set_state(
5138 r#"let foo = 2;
5139lˇet foo = 2;
5140let fooˇ = 2;
5141let foo = 2;
5142let foo = ˇ2;"#,
5143 );
5144
5145 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5146 .unwrap();
5147 cx.assert_editor_state(
5148 r#"let foo = 2;
5149«letˇ» foo = 2;
5150let «fooˇ» = 2;
5151let foo = 2;
5152let foo = «2ˇ»;"#,
5153 );
5154
5155 // noop for multiple selections with different contents
5156 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5157 .unwrap();
5158 cx.assert_editor_state(
5159 r#"let foo = 2;
5160«letˇ» foo = 2;
5161let «fooˇ» = 2;
5162let foo = 2;
5163let foo = «2ˇ»;"#,
5164 );
5165}
5166
5167#[gpui::test]
5168async fn test_select_previous_multibuffer(cx: &mut gpui::TestAppContext) {
5169 init_test(cx, |_| {});
5170
5171 let mut cx =
5172 EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
5173
5174 cx.assert_editor_state(indoc! {"
5175 ˇbbb
5176 ccc
5177
5178 bbb
5179 ccc
5180 "});
5181 cx.dispatch_action(SelectPrevious::default());
5182 cx.assert_editor_state(indoc! {"
5183 «bbbˇ»
5184 ccc
5185
5186 bbb
5187 ccc
5188 "});
5189 cx.dispatch_action(SelectPrevious::default());
5190 cx.assert_editor_state(indoc! {"
5191 «bbbˇ»
5192 ccc
5193
5194 «bbbˇ»
5195 ccc
5196 "});
5197}
5198
5199#[gpui::test]
5200async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) {
5201 init_test(cx, |_| {});
5202
5203 let mut cx = EditorTestContext::new(cx).await;
5204 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5205
5206 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5207 .unwrap();
5208 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5209
5210 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5211 .unwrap();
5212 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5213
5214 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5215 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5216
5217 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5218 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5219
5220 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5221 .unwrap();
5222 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
5223
5224 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5225 .unwrap();
5226 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
5227
5228 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5229 .unwrap();
5230 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5231}
5232
5233#[gpui::test]
5234async fn test_select_previous_with_multiple_carets(cx: &mut gpui::TestAppContext) {
5235 init_test(cx, |_| {});
5236
5237 let mut cx = EditorTestContext::new(cx).await;
5238 cx.set_state(
5239 r#"let foo = 2;
5240lˇet foo = 2;
5241let fooˇ = 2;
5242let foo = 2;
5243let foo = ˇ2;"#,
5244 );
5245
5246 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5247 .unwrap();
5248 cx.assert_editor_state(
5249 r#"let foo = 2;
5250«letˇ» foo = 2;
5251let «fooˇ» = 2;
5252let foo = 2;
5253let foo = «2ˇ»;"#,
5254 );
5255
5256 // noop for multiple selections with different contents
5257 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5258 .unwrap();
5259 cx.assert_editor_state(
5260 r#"let foo = 2;
5261«letˇ» foo = 2;
5262let «fooˇ» = 2;
5263let foo = 2;
5264let foo = «2ˇ»;"#,
5265 );
5266}
5267
5268#[gpui::test]
5269async fn test_select_previous_with_single_selection(cx: &mut gpui::TestAppContext) {
5270 init_test(cx, |_| {});
5271
5272 let mut cx = EditorTestContext::new(cx).await;
5273 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
5274
5275 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5276 .unwrap();
5277 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5278
5279 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5280 .unwrap();
5281 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5282
5283 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5284 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5285
5286 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5287 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5288
5289 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5290 .unwrap();
5291 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
5292
5293 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5294 .unwrap();
5295 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5296}
5297
5298#[gpui::test]
5299async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
5300 init_test(cx, |_| {});
5301
5302 let language = Arc::new(Language::new(
5303 LanguageConfig::default(),
5304 Some(tree_sitter_rust::LANGUAGE.into()),
5305 ));
5306
5307 let text = r#"
5308 use mod1::mod2::{mod3, mod4};
5309
5310 fn fn_1(param1: bool, param2: &str) {
5311 let var1 = "text";
5312 }
5313 "#
5314 .unindent();
5315
5316 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5317 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5318 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5319
5320 editor
5321 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5322 .await;
5323
5324 editor.update(cx, |view, cx| {
5325 view.change_selections(None, cx, |s| {
5326 s.select_display_ranges([
5327 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
5328 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
5329 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
5330 ]);
5331 });
5332 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5333 });
5334 editor.update(cx, |editor, cx| {
5335 assert_text_with_selections(
5336 editor,
5337 indoc! {r#"
5338 use mod1::mod2::{mod3, «mod4ˇ»};
5339
5340 fn fn_1«ˇ(param1: bool, param2: &str)» {
5341 let var1 = "«textˇ»";
5342 }
5343 "#},
5344 cx,
5345 );
5346 });
5347
5348 editor.update(cx, |view, cx| {
5349 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5350 });
5351 editor.update(cx, |editor, cx| {
5352 assert_text_with_selections(
5353 editor,
5354 indoc! {r#"
5355 use mod1::mod2::«{mod3, mod4}ˇ»;
5356
5357 «ˇfn fn_1(param1: bool, param2: &str) {
5358 let var1 = "text";
5359 }»
5360 "#},
5361 cx,
5362 );
5363 });
5364
5365 editor.update(cx, |view, cx| {
5366 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5367 });
5368 assert_eq!(
5369 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
5370 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5371 );
5372
5373 // Trying to expand the selected syntax node one more time has no effect.
5374 editor.update(cx, |view, cx| {
5375 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5376 });
5377 assert_eq!(
5378 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
5379 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5380 );
5381
5382 editor.update(cx, |view, cx| {
5383 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5384 });
5385 editor.update(cx, |editor, cx| {
5386 assert_text_with_selections(
5387 editor,
5388 indoc! {r#"
5389 use mod1::mod2::«{mod3, mod4}ˇ»;
5390
5391 «ˇfn fn_1(param1: bool, param2: &str) {
5392 let var1 = "text";
5393 }»
5394 "#},
5395 cx,
5396 );
5397 });
5398
5399 editor.update(cx, |view, cx| {
5400 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5401 });
5402 editor.update(cx, |editor, cx| {
5403 assert_text_with_selections(
5404 editor,
5405 indoc! {r#"
5406 use mod1::mod2::{mod3, «mod4ˇ»};
5407
5408 fn fn_1«ˇ(param1: bool, param2: &str)» {
5409 let var1 = "«textˇ»";
5410 }
5411 "#},
5412 cx,
5413 );
5414 });
5415
5416 editor.update(cx, |view, cx| {
5417 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5418 });
5419 editor.update(cx, |editor, cx| {
5420 assert_text_with_selections(
5421 editor,
5422 indoc! {r#"
5423 use mod1::mod2::{mod3, mo«ˇ»d4};
5424
5425 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5426 let var1 = "te«ˇ»xt";
5427 }
5428 "#},
5429 cx,
5430 );
5431 });
5432
5433 // Trying to shrink the selected syntax node one more time has no effect.
5434 editor.update(cx, |view, cx| {
5435 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5436 });
5437 editor.update(cx, |editor, cx| {
5438 assert_text_with_selections(
5439 editor,
5440 indoc! {r#"
5441 use mod1::mod2::{mod3, mo«ˇ»d4};
5442
5443 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5444 let var1 = "te«ˇ»xt";
5445 }
5446 "#},
5447 cx,
5448 );
5449 });
5450
5451 // Ensure that we keep expanding the selection if the larger selection starts or ends within
5452 // a fold.
5453 editor.update(cx, |view, cx| {
5454 view.fold_creases(
5455 vec![
5456 Crease::simple(
5457 Point::new(0, 21)..Point::new(0, 24),
5458 FoldPlaceholder::test(),
5459 ),
5460 Crease::simple(
5461 Point::new(3, 20)..Point::new(3, 22),
5462 FoldPlaceholder::test(),
5463 ),
5464 ],
5465 true,
5466 cx,
5467 );
5468 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5469 });
5470 editor.update(cx, |editor, cx| {
5471 assert_text_with_selections(
5472 editor,
5473 indoc! {r#"
5474 use mod1::mod2::«{mod3, mod4}ˇ»;
5475
5476 fn fn_1«ˇ(param1: bool, param2: &str)» {
5477 «let var1 = "text";ˇ»
5478 }
5479 "#},
5480 cx,
5481 );
5482 });
5483}
5484
5485#[gpui::test]
5486async fn test_autoindent(cx: &mut gpui::TestAppContext) {
5487 init_test(cx, |_| {});
5488
5489 let language = Arc::new(
5490 Language::new(
5491 LanguageConfig {
5492 brackets: BracketPairConfig {
5493 pairs: vec![
5494 BracketPair {
5495 start: "{".to_string(),
5496 end: "}".to_string(),
5497 close: false,
5498 surround: false,
5499 newline: true,
5500 },
5501 BracketPair {
5502 start: "(".to_string(),
5503 end: ")".to_string(),
5504 close: false,
5505 surround: false,
5506 newline: true,
5507 },
5508 ],
5509 ..Default::default()
5510 },
5511 ..Default::default()
5512 },
5513 Some(tree_sitter_rust::LANGUAGE.into()),
5514 )
5515 .with_indents_query(
5516 r#"
5517 (_ "(" ")" @end) @indent
5518 (_ "{" "}" @end) @indent
5519 "#,
5520 )
5521 .unwrap(),
5522 );
5523
5524 let text = "fn a() {}";
5525
5526 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5527 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5528 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5529 editor
5530 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5531 .await;
5532
5533 editor.update(cx, |editor, cx| {
5534 editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
5535 editor.newline(&Newline, cx);
5536 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
5537 assert_eq!(
5538 editor.selections.ranges(cx),
5539 &[
5540 Point::new(1, 4)..Point::new(1, 4),
5541 Point::new(3, 4)..Point::new(3, 4),
5542 Point::new(5, 0)..Point::new(5, 0)
5543 ]
5544 );
5545 });
5546}
5547
5548#[gpui::test]
5549async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
5550 init_test(cx, |_| {});
5551
5552 {
5553 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
5554 cx.set_state(indoc! {"
5555 impl A {
5556
5557 fn b() {}
5558
5559 «fn c() {
5560
5561 }ˇ»
5562 }
5563 "});
5564
5565 cx.update_editor(|editor, cx| {
5566 editor.autoindent(&Default::default(), cx);
5567 });
5568
5569 cx.assert_editor_state(indoc! {"
5570 impl A {
5571
5572 fn b() {}
5573
5574 «fn c() {
5575
5576 }ˇ»
5577 }
5578 "});
5579 }
5580
5581 {
5582 let mut cx = EditorTestContext::new_multibuffer(
5583 cx,
5584 [indoc! { "
5585 impl A {
5586 «
5587 // a
5588 fn b(){}
5589 »
5590 «
5591 }
5592 fn c(){}
5593 »
5594 "}],
5595 );
5596
5597 let buffer = cx.update_editor(|editor, cx| {
5598 let buffer = editor.buffer().update(cx, |buffer, _| {
5599 buffer.all_buffers().iter().next().unwrap().clone()
5600 });
5601 buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
5602 buffer
5603 });
5604
5605 cx.run_until_parked();
5606 cx.update_editor(|editor, cx| {
5607 editor.select_all(&Default::default(), cx);
5608 editor.autoindent(&Default::default(), cx)
5609 });
5610 cx.run_until_parked();
5611
5612 cx.update(|cx| {
5613 pretty_assertions::assert_eq!(
5614 buffer.read(cx).text(),
5615 indoc! { "
5616 impl A {
5617
5618 // a
5619 fn b(){}
5620
5621
5622 }
5623 fn c(){}
5624
5625 " }
5626 )
5627 });
5628 }
5629}
5630
5631#[gpui::test]
5632async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
5633 init_test(cx, |_| {});
5634
5635 let mut cx = EditorTestContext::new(cx).await;
5636
5637 let language = Arc::new(Language::new(
5638 LanguageConfig {
5639 brackets: BracketPairConfig {
5640 pairs: vec![
5641 BracketPair {
5642 start: "{".to_string(),
5643 end: "}".to_string(),
5644 close: true,
5645 surround: true,
5646 newline: true,
5647 },
5648 BracketPair {
5649 start: "(".to_string(),
5650 end: ")".to_string(),
5651 close: true,
5652 surround: true,
5653 newline: true,
5654 },
5655 BracketPair {
5656 start: "/*".to_string(),
5657 end: " */".to_string(),
5658 close: true,
5659 surround: true,
5660 newline: true,
5661 },
5662 BracketPair {
5663 start: "[".to_string(),
5664 end: "]".to_string(),
5665 close: false,
5666 surround: false,
5667 newline: true,
5668 },
5669 BracketPair {
5670 start: "\"".to_string(),
5671 end: "\"".to_string(),
5672 close: true,
5673 surround: true,
5674 newline: false,
5675 },
5676 BracketPair {
5677 start: "<".to_string(),
5678 end: ">".to_string(),
5679 close: false,
5680 surround: true,
5681 newline: true,
5682 },
5683 ],
5684 ..Default::default()
5685 },
5686 autoclose_before: "})]".to_string(),
5687 ..Default::default()
5688 },
5689 Some(tree_sitter_rust::LANGUAGE.into()),
5690 ));
5691
5692 cx.language_registry().add(language.clone());
5693 cx.update_buffer(|buffer, cx| {
5694 buffer.set_language(Some(language), cx);
5695 });
5696
5697 cx.set_state(
5698 &r#"
5699 🏀ˇ
5700 εˇ
5701 ❤️ˇ
5702 "#
5703 .unindent(),
5704 );
5705
5706 // autoclose multiple nested brackets at multiple cursors
5707 cx.update_editor(|view, cx| {
5708 view.handle_input("{", cx);
5709 view.handle_input("{", cx);
5710 view.handle_input("{", cx);
5711 });
5712 cx.assert_editor_state(
5713 &"
5714 🏀{{{ˇ}}}
5715 ε{{{ˇ}}}
5716 ❤️{{{ˇ}}}
5717 "
5718 .unindent(),
5719 );
5720
5721 // insert a different closing bracket
5722 cx.update_editor(|view, cx| {
5723 view.handle_input(")", cx);
5724 });
5725 cx.assert_editor_state(
5726 &"
5727 🏀{{{)ˇ}}}
5728 ε{{{)ˇ}}}
5729 ❤️{{{)ˇ}}}
5730 "
5731 .unindent(),
5732 );
5733
5734 // skip over the auto-closed brackets when typing a closing bracket
5735 cx.update_editor(|view, cx| {
5736 view.move_right(&MoveRight, cx);
5737 view.handle_input("}", cx);
5738 view.handle_input("}", cx);
5739 view.handle_input("}", cx);
5740 });
5741 cx.assert_editor_state(
5742 &"
5743 🏀{{{)}}}}ˇ
5744 ε{{{)}}}}ˇ
5745 ❤️{{{)}}}}ˇ
5746 "
5747 .unindent(),
5748 );
5749
5750 // autoclose multi-character pairs
5751 cx.set_state(
5752 &"
5753 ˇ
5754 ˇ
5755 "
5756 .unindent(),
5757 );
5758 cx.update_editor(|view, cx| {
5759 view.handle_input("/", cx);
5760 view.handle_input("*", cx);
5761 });
5762 cx.assert_editor_state(
5763 &"
5764 /*ˇ */
5765 /*ˇ */
5766 "
5767 .unindent(),
5768 );
5769
5770 // one cursor autocloses a multi-character pair, one cursor
5771 // does not autoclose.
5772 cx.set_state(
5773 &"
5774 /ˇ
5775 ˇ
5776 "
5777 .unindent(),
5778 );
5779 cx.update_editor(|view, cx| view.handle_input("*", cx));
5780 cx.assert_editor_state(
5781 &"
5782 /*ˇ */
5783 *ˇ
5784 "
5785 .unindent(),
5786 );
5787
5788 // Don't autoclose if the next character isn't whitespace and isn't
5789 // listed in the language's "autoclose_before" section.
5790 cx.set_state("ˇa b");
5791 cx.update_editor(|view, cx| view.handle_input("{", cx));
5792 cx.assert_editor_state("{ˇa b");
5793
5794 // Don't autoclose if `close` is false for the bracket pair
5795 cx.set_state("ˇ");
5796 cx.update_editor(|view, cx| view.handle_input("[", cx));
5797 cx.assert_editor_state("[ˇ");
5798
5799 // Surround with brackets if text is selected
5800 cx.set_state("«aˇ» b");
5801 cx.update_editor(|view, cx| view.handle_input("{", cx));
5802 cx.assert_editor_state("{«aˇ»} b");
5803
5804 // Autclose pair where the start and end characters are the same
5805 cx.set_state("aˇ");
5806 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5807 cx.assert_editor_state("a\"ˇ\"");
5808 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5809 cx.assert_editor_state("a\"\"ˇ");
5810
5811 // Don't autoclose pair if autoclose is disabled
5812 cx.set_state("ˇ");
5813 cx.update_editor(|view, cx| view.handle_input("<", cx));
5814 cx.assert_editor_state("<ˇ");
5815
5816 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
5817 cx.set_state("«aˇ» b");
5818 cx.update_editor(|view, cx| view.handle_input("<", cx));
5819 cx.assert_editor_state("<«aˇ»> b");
5820}
5821
5822#[gpui::test]
5823async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut gpui::TestAppContext) {
5824 init_test(cx, |settings| {
5825 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
5826 });
5827
5828 let mut cx = EditorTestContext::new(cx).await;
5829
5830 let language = Arc::new(Language::new(
5831 LanguageConfig {
5832 brackets: BracketPairConfig {
5833 pairs: vec![
5834 BracketPair {
5835 start: "{".to_string(),
5836 end: "}".to_string(),
5837 close: true,
5838 surround: true,
5839 newline: true,
5840 },
5841 BracketPair {
5842 start: "(".to_string(),
5843 end: ")".to_string(),
5844 close: true,
5845 surround: true,
5846 newline: true,
5847 },
5848 BracketPair {
5849 start: "[".to_string(),
5850 end: "]".to_string(),
5851 close: false,
5852 surround: false,
5853 newline: true,
5854 },
5855 ],
5856 ..Default::default()
5857 },
5858 autoclose_before: "})]".to_string(),
5859 ..Default::default()
5860 },
5861 Some(tree_sitter_rust::LANGUAGE.into()),
5862 ));
5863
5864 cx.language_registry().add(language.clone());
5865 cx.update_buffer(|buffer, cx| {
5866 buffer.set_language(Some(language), cx);
5867 });
5868
5869 cx.set_state(
5870 &"
5871 ˇ
5872 ˇ
5873 ˇ
5874 "
5875 .unindent(),
5876 );
5877
5878 // ensure only matching closing brackets are skipped over
5879 cx.update_editor(|view, cx| {
5880 view.handle_input("}", cx);
5881 view.move_left(&MoveLeft, cx);
5882 view.handle_input(")", cx);
5883 view.move_left(&MoveLeft, cx);
5884 });
5885 cx.assert_editor_state(
5886 &"
5887 ˇ)}
5888 ˇ)}
5889 ˇ)}
5890 "
5891 .unindent(),
5892 );
5893
5894 // skip-over closing brackets at multiple cursors
5895 cx.update_editor(|view, cx| {
5896 view.handle_input(")", cx);
5897 view.handle_input("}", cx);
5898 });
5899 cx.assert_editor_state(
5900 &"
5901 )}ˇ
5902 )}ˇ
5903 )}ˇ
5904 "
5905 .unindent(),
5906 );
5907
5908 // ignore non-close brackets
5909 cx.update_editor(|view, cx| {
5910 view.handle_input("]", cx);
5911 view.move_left(&MoveLeft, cx);
5912 view.handle_input("]", cx);
5913 });
5914 cx.assert_editor_state(
5915 &"
5916 )}]ˇ]
5917 )}]ˇ]
5918 )}]ˇ]
5919 "
5920 .unindent(),
5921 );
5922}
5923
5924#[gpui::test]
5925async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
5926 init_test(cx, |_| {});
5927
5928 let mut cx = EditorTestContext::new(cx).await;
5929
5930 let html_language = Arc::new(
5931 Language::new(
5932 LanguageConfig {
5933 name: "HTML".into(),
5934 brackets: BracketPairConfig {
5935 pairs: vec![
5936 BracketPair {
5937 start: "<".into(),
5938 end: ">".into(),
5939 close: true,
5940 ..Default::default()
5941 },
5942 BracketPair {
5943 start: "{".into(),
5944 end: "}".into(),
5945 close: true,
5946 ..Default::default()
5947 },
5948 BracketPair {
5949 start: "(".into(),
5950 end: ")".into(),
5951 close: true,
5952 ..Default::default()
5953 },
5954 ],
5955 ..Default::default()
5956 },
5957 autoclose_before: "})]>".into(),
5958 ..Default::default()
5959 },
5960 Some(tree_sitter_html::language()),
5961 )
5962 .with_injection_query(
5963 r#"
5964 (script_element
5965 (raw_text) @injection.content
5966 (#set! injection.language "javascript"))
5967 "#,
5968 )
5969 .unwrap(),
5970 );
5971
5972 let javascript_language = Arc::new(Language::new(
5973 LanguageConfig {
5974 name: "JavaScript".into(),
5975 brackets: BracketPairConfig {
5976 pairs: vec![
5977 BracketPair {
5978 start: "/*".into(),
5979 end: " */".into(),
5980 close: true,
5981 ..Default::default()
5982 },
5983 BracketPair {
5984 start: "{".into(),
5985 end: "}".into(),
5986 close: true,
5987 ..Default::default()
5988 },
5989 BracketPair {
5990 start: "(".into(),
5991 end: ")".into(),
5992 close: true,
5993 ..Default::default()
5994 },
5995 ],
5996 ..Default::default()
5997 },
5998 autoclose_before: "})]>".into(),
5999 ..Default::default()
6000 },
6001 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
6002 ));
6003
6004 cx.language_registry().add(html_language.clone());
6005 cx.language_registry().add(javascript_language.clone());
6006
6007 cx.update_buffer(|buffer, cx| {
6008 buffer.set_language(Some(html_language), cx);
6009 });
6010
6011 cx.set_state(
6012 &r#"
6013 <body>ˇ
6014 <script>
6015 var x = 1;ˇ
6016 </script>
6017 </body>ˇ
6018 "#
6019 .unindent(),
6020 );
6021
6022 // Precondition: different languages are active at different locations.
6023 cx.update_editor(|editor, cx| {
6024 let snapshot = editor.snapshot(cx);
6025 let cursors = editor.selections.ranges::<usize>(cx);
6026 let languages = cursors
6027 .iter()
6028 .map(|c| snapshot.language_at(c.start).unwrap().name())
6029 .collect::<Vec<_>>();
6030 assert_eq!(
6031 languages,
6032 &["HTML".into(), "JavaScript".into(), "HTML".into()]
6033 );
6034 });
6035
6036 // Angle brackets autoclose in HTML, but not JavaScript.
6037 cx.update_editor(|editor, cx| {
6038 editor.handle_input("<", cx);
6039 editor.handle_input("a", cx);
6040 });
6041 cx.assert_editor_state(
6042 &r#"
6043 <body><aˇ>
6044 <script>
6045 var x = 1;<aˇ
6046 </script>
6047 </body><aˇ>
6048 "#
6049 .unindent(),
6050 );
6051
6052 // Curly braces and parens autoclose in both HTML and JavaScript.
6053 cx.update_editor(|editor, cx| {
6054 editor.handle_input(" b=", cx);
6055 editor.handle_input("{", cx);
6056 editor.handle_input("c", cx);
6057 editor.handle_input("(", cx);
6058 });
6059 cx.assert_editor_state(
6060 &r#"
6061 <body><a b={c(ˇ)}>
6062 <script>
6063 var x = 1;<a b={c(ˇ)}
6064 </script>
6065 </body><a b={c(ˇ)}>
6066 "#
6067 .unindent(),
6068 );
6069
6070 // Brackets that were already autoclosed are skipped.
6071 cx.update_editor(|editor, cx| {
6072 editor.handle_input(")", cx);
6073 editor.handle_input("d", cx);
6074 editor.handle_input("}", cx);
6075 });
6076 cx.assert_editor_state(
6077 &r#"
6078 <body><a b={c()d}ˇ>
6079 <script>
6080 var x = 1;<a b={c()d}ˇ
6081 </script>
6082 </body><a b={c()d}ˇ>
6083 "#
6084 .unindent(),
6085 );
6086 cx.update_editor(|editor, cx| {
6087 editor.handle_input(">", cx);
6088 });
6089 cx.assert_editor_state(
6090 &r#"
6091 <body><a b={c()d}>ˇ
6092 <script>
6093 var x = 1;<a b={c()d}>ˇ
6094 </script>
6095 </body><a b={c()d}>ˇ
6096 "#
6097 .unindent(),
6098 );
6099
6100 // Reset
6101 cx.set_state(
6102 &r#"
6103 <body>ˇ
6104 <script>
6105 var x = 1;ˇ
6106 </script>
6107 </body>ˇ
6108 "#
6109 .unindent(),
6110 );
6111
6112 cx.update_editor(|editor, cx| {
6113 editor.handle_input("<", cx);
6114 });
6115 cx.assert_editor_state(
6116 &r#"
6117 <body><ˇ>
6118 <script>
6119 var x = 1;<ˇ
6120 </script>
6121 </body><ˇ>
6122 "#
6123 .unindent(),
6124 );
6125
6126 // When backspacing, the closing angle brackets are removed.
6127 cx.update_editor(|editor, cx| {
6128 editor.backspace(&Backspace, cx);
6129 });
6130 cx.assert_editor_state(
6131 &r#"
6132 <body>ˇ
6133 <script>
6134 var x = 1;ˇ
6135 </script>
6136 </body>ˇ
6137 "#
6138 .unindent(),
6139 );
6140
6141 // Block comments autoclose in JavaScript, but not HTML.
6142 cx.update_editor(|editor, cx| {
6143 editor.handle_input("/", cx);
6144 editor.handle_input("*", cx);
6145 });
6146 cx.assert_editor_state(
6147 &r#"
6148 <body>/*ˇ
6149 <script>
6150 var x = 1;/*ˇ */
6151 </script>
6152 </body>/*ˇ
6153 "#
6154 .unindent(),
6155 );
6156}
6157
6158#[gpui::test]
6159async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
6160 init_test(cx, |_| {});
6161
6162 let mut cx = EditorTestContext::new(cx).await;
6163
6164 let rust_language = Arc::new(
6165 Language::new(
6166 LanguageConfig {
6167 name: "Rust".into(),
6168 brackets: serde_json::from_value(json!([
6169 { "start": "{", "end": "}", "close": true, "newline": true },
6170 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
6171 ]))
6172 .unwrap(),
6173 autoclose_before: "})]>".into(),
6174 ..Default::default()
6175 },
6176 Some(tree_sitter_rust::LANGUAGE.into()),
6177 )
6178 .with_override_query("(string_literal) @string")
6179 .unwrap(),
6180 );
6181
6182 cx.language_registry().add(rust_language.clone());
6183 cx.update_buffer(|buffer, cx| {
6184 buffer.set_language(Some(rust_language), cx);
6185 });
6186
6187 cx.set_state(
6188 &r#"
6189 let x = ˇ
6190 "#
6191 .unindent(),
6192 );
6193
6194 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
6195 cx.update_editor(|editor, cx| {
6196 editor.handle_input("\"", cx);
6197 });
6198 cx.assert_editor_state(
6199 &r#"
6200 let x = "ˇ"
6201 "#
6202 .unindent(),
6203 );
6204
6205 // Inserting another quotation mark. The cursor moves across the existing
6206 // automatically-inserted quotation mark.
6207 cx.update_editor(|editor, cx| {
6208 editor.handle_input("\"", cx);
6209 });
6210 cx.assert_editor_state(
6211 &r#"
6212 let x = ""ˇ
6213 "#
6214 .unindent(),
6215 );
6216
6217 // Reset
6218 cx.set_state(
6219 &r#"
6220 let x = ˇ
6221 "#
6222 .unindent(),
6223 );
6224
6225 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
6226 cx.update_editor(|editor, cx| {
6227 editor.handle_input("\"", cx);
6228 editor.handle_input(" ", cx);
6229 editor.move_left(&Default::default(), cx);
6230 editor.handle_input("\\", cx);
6231 editor.handle_input("\"", cx);
6232 });
6233 cx.assert_editor_state(
6234 &r#"
6235 let x = "\"ˇ "
6236 "#
6237 .unindent(),
6238 );
6239
6240 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
6241 // mark. Nothing is inserted.
6242 cx.update_editor(|editor, cx| {
6243 editor.move_right(&Default::default(), cx);
6244 editor.handle_input("\"", cx);
6245 });
6246 cx.assert_editor_state(
6247 &r#"
6248 let x = "\" "ˇ
6249 "#
6250 .unindent(),
6251 );
6252}
6253
6254#[gpui::test]
6255async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
6256 init_test(cx, |_| {});
6257
6258 let language = Arc::new(Language::new(
6259 LanguageConfig {
6260 brackets: BracketPairConfig {
6261 pairs: vec![
6262 BracketPair {
6263 start: "{".to_string(),
6264 end: "}".to_string(),
6265 close: true,
6266 surround: true,
6267 newline: true,
6268 },
6269 BracketPair {
6270 start: "/* ".to_string(),
6271 end: "*/".to_string(),
6272 close: true,
6273 surround: true,
6274 ..Default::default()
6275 },
6276 ],
6277 ..Default::default()
6278 },
6279 ..Default::default()
6280 },
6281 Some(tree_sitter_rust::LANGUAGE.into()),
6282 ));
6283
6284 let text = r#"
6285 a
6286 b
6287 c
6288 "#
6289 .unindent();
6290
6291 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
6292 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6293 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6294 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6295 .await;
6296
6297 view.update(cx, |view, cx| {
6298 view.change_selections(None, cx, |s| {
6299 s.select_display_ranges([
6300 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6301 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6302 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
6303 ])
6304 });
6305
6306 view.handle_input("{", cx);
6307 view.handle_input("{", cx);
6308 view.handle_input("{", cx);
6309 assert_eq!(
6310 view.text(cx),
6311 "
6312 {{{a}}}
6313 {{{b}}}
6314 {{{c}}}
6315 "
6316 .unindent()
6317 );
6318 assert_eq!(
6319 view.selections.display_ranges(cx),
6320 [
6321 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
6322 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
6323 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
6324 ]
6325 );
6326
6327 view.undo(&Undo, cx);
6328 view.undo(&Undo, cx);
6329 view.undo(&Undo, cx);
6330 assert_eq!(
6331 view.text(cx),
6332 "
6333 a
6334 b
6335 c
6336 "
6337 .unindent()
6338 );
6339 assert_eq!(
6340 view.selections.display_ranges(cx),
6341 [
6342 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6343 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6344 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6345 ]
6346 );
6347
6348 // Ensure inserting the first character of a multi-byte bracket pair
6349 // doesn't surround the selections with the bracket.
6350 view.handle_input("/", cx);
6351 assert_eq!(
6352 view.text(cx),
6353 "
6354 /
6355 /
6356 /
6357 "
6358 .unindent()
6359 );
6360 assert_eq!(
6361 view.selections.display_ranges(cx),
6362 [
6363 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6364 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6365 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6366 ]
6367 );
6368
6369 view.undo(&Undo, cx);
6370 assert_eq!(
6371 view.text(cx),
6372 "
6373 a
6374 b
6375 c
6376 "
6377 .unindent()
6378 );
6379 assert_eq!(
6380 view.selections.display_ranges(cx),
6381 [
6382 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6383 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6384 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6385 ]
6386 );
6387
6388 // Ensure inserting the last character of a multi-byte bracket pair
6389 // doesn't surround the selections with the bracket.
6390 view.handle_input("*", cx);
6391 assert_eq!(
6392 view.text(cx),
6393 "
6394 *
6395 *
6396 *
6397 "
6398 .unindent()
6399 );
6400 assert_eq!(
6401 view.selections.display_ranges(cx),
6402 [
6403 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6404 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6405 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6406 ]
6407 );
6408 });
6409}
6410
6411#[gpui::test]
6412async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
6413 init_test(cx, |_| {});
6414
6415 let language = Arc::new(Language::new(
6416 LanguageConfig {
6417 brackets: BracketPairConfig {
6418 pairs: vec![BracketPair {
6419 start: "{".to_string(),
6420 end: "}".to_string(),
6421 close: true,
6422 surround: true,
6423 newline: true,
6424 }],
6425 ..Default::default()
6426 },
6427 autoclose_before: "}".to_string(),
6428 ..Default::default()
6429 },
6430 Some(tree_sitter_rust::LANGUAGE.into()),
6431 ));
6432
6433 let text = r#"
6434 a
6435 b
6436 c
6437 "#
6438 .unindent();
6439
6440 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
6441 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6442 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6443 editor
6444 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6445 .await;
6446
6447 editor.update(cx, |editor, cx| {
6448 editor.change_selections(None, cx, |s| {
6449 s.select_ranges([
6450 Point::new(0, 1)..Point::new(0, 1),
6451 Point::new(1, 1)..Point::new(1, 1),
6452 Point::new(2, 1)..Point::new(2, 1),
6453 ])
6454 });
6455
6456 editor.handle_input("{", cx);
6457 editor.handle_input("{", cx);
6458 editor.handle_input("_", cx);
6459 assert_eq!(
6460 editor.text(cx),
6461 "
6462 a{{_}}
6463 b{{_}}
6464 c{{_}}
6465 "
6466 .unindent()
6467 );
6468 assert_eq!(
6469 editor.selections.ranges::<Point>(cx),
6470 [
6471 Point::new(0, 4)..Point::new(0, 4),
6472 Point::new(1, 4)..Point::new(1, 4),
6473 Point::new(2, 4)..Point::new(2, 4)
6474 ]
6475 );
6476
6477 editor.backspace(&Default::default(), cx);
6478 editor.backspace(&Default::default(), cx);
6479 assert_eq!(
6480 editor.text(cx),
6481 "
6482 a{}
6483 b{}
6484 c{}
6485 "
6486 .unindent()
6487 );
6488 assert_eq!(
6489 editor.selections.ranges::<Point>(cx),
6490 [
6491 Point::new(0, 2)..Point::new(0, 2),
6492 Point::new(1, 2)..Point::new(1, 2),
6493 Point::new(2, 2)..Point::new(2, 2)
6494 ]
6495 );
6496
6497 editor.delete_to_previous_word_start(&Default::default(), cx);
6498 assert_eq!(
6499 editor.text(cx),
6500 "
6501 a
6502 b
6503 c
6504 "
6505 .unindent()
6506 );
6507 assert_eq!(
6508 editor.selections.ranges::<Point>(cx),
6509 [
6510 Point::new(0, 1)..Point::new(0, 1),
6511 Point::new(1, 1)..Point::new(1, 1),
6512 Point::new(2, 1)..Point::new(2, 1)
6513 ]
6514 );
6515 });
6516}
6517
6518#[gpui::test]
6519async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut gpui::TestAppContext) {
6520 init_test(cx, |settings| {
6521 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6522 });
6523
6524 let mut cx = EditorTestContext::new(cx).await;
6525
6526 let language = Arc::new(Language::new(
6527 LanguageConfig {
6528 brackets: BracketPairConfig {
6529 pairs: vec![
6530 BracketPair {
6531 start: "{".to_string(),
6532 end: "}".to_string(),
6533 close: true,
6534 surround: true,
6535 newline: true,
6536 },
6537 BracketPair {
6538 start: "(".to_string(),
6539 end: ")".to_string(),
6540 close: true,
6541 surround: true,
6542 newline: true,
6543 },
6544 BracketPair {
6545 start: "[".to_string(),
6546 end: "]".to_string(),
6547 close: false,
6548 surround: true,
6549 newline: true,
6550 },
6551 ],
6552 ..Default::default()
6553 },
6554 autoclose_before: "})]".to_string(),
6555 ..Default::default()
6556 },
6557 Some(tree_sitter_rust::LANGUAGE.into()),
6558 ));
6559
6560 cx.language_registry().add(language.clone());
6561 cx.update_buffer(|buffer, cx| {
6562 buffer.set_language(Some(language), cx);
6563 });
6564
6565 cx.set_state(
6566 &"
6567 {(ˇ)}
6568 [[ˇ]]
6569 {(ˇ)}
6570 "
6571 .unindent(),
6572 );
6573
6574 cx.update_editor(|view, cx| {
6575 view.backspace(&Default::default(), cx);
6576 view.backspace(&Default::default(), cx);
6577 });
6578
6579 cx.assert_editor_state(
6580 &"
6581 ˇ
6582 ˇ]]
6583 ˇ
6584 "
6585 .unindent(),
6586 );
6587
6588 cx.update_editor(|view, cx| {
6589 view.handle_input("{", cx);
6590 view.handle_input("{", cx);
6591 view.move_right(&MoveRight, cx);
6592 view.move_right(&MoveRight, cx);
6593 view.move_left(&MoveLeft, cx);
6594 view.move_left(&MoveLeft, cx);
6595 view.backspace(&Default::default(), cx);
6596 });
6597
6598 cx.assert_editor_state(
6599 &"
6600 {ˇ}
6601 {ˇ}]]
6602 {ˇ}
6603 "
6604 .unindent(),
6605 );
6606
6607 cx.update_editor(|view, cx| {
6608 view.backspace(&Default::default(), cx);
6609 });
6610
6611 cx.assert_editor_state(
6612 &"
6613 ˇ
6614 ˇ]]
6615 ˇ
6616 "
6617 .unindent(),
6618 );
6619}
6620
6621#[gpui::test]
6622async fn test_auto_replace_emoji_shortcode(cx: &mut gpui::TestAppContext) {
6623 init_test(cx, |_| {});
6624
6625 let language = Arc::new(Language::new(
6626 LanguageConfig::default(),
6627 Some(tree_sitter_rust::LANGUAGE.into()),
6628 ));
6629
6630 let buffer = cx.new_model(|cx| Buffer::local("", cx).with_language(language, cx));
6631 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6632 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6633 editor
6634 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6635 .await;
6636
6637 editor.update(cx, |editor, cx| {
6638 editor.set_auto_replace_emoji_shortcode(true);
6639
6640 editor.handle_input("Hello ", cx);
6641 editor.handle_input(":wave", cx);
6642 assert_eq!(editor.text(cx), "Hello :wave".unindent());
6643
6644 editor.handle_input(":", cx);
6645 assert_eq!(editor.text(cx), "Hello 👋".unindent());
6646
6647 editor.handle_input(" :smile", cx);
6648 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
6649
6650 editor.handle_input(":", cx);
6651 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
6652
6653 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
6654 editor.handle_input(":wave", cx);
6655 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
6656
6657 editor.handle_input(":", cx);
6658 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
6659
6660 editor.handle_input(":1", cx);
6661 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
6662
6663 editor.handle_input(":", cx);
6664 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
6665
6666 // Ensure shortcode does not get replaced when it is part of a word
6667 editor.handle_input(" Test:wave", cx);
6668 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
6669
6670 editor.handle_input(":", cx);
6671 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
6672
6673 editor.set_auto_replace_emoji_shortcode(false);
6674
6675 // Ensure shortcode does not get replaced when auto replace is off
6676 editor.handle_input(" :wave", cx);
6677 assert_eq!(
6678 editor.text(cx),
6679 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
6680 );
6681
6682 editor.handle_input(":", cx);
6683 assert_eq!(
6684 editor.text(cx),
6685 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
6686 );
6687 });
6688}
6689
6690#[gpui::test]
6691async fn test_snippet_placeholder_choices(cx: &mut gpui::TestAppContext) {
6692 init_test(cx, |_| {});
6693
6694 let (text, insertion_ranges) = marked_text_ranges(
6695 indoc! {"
6696 ˇ
6697 "},
6698 false,
6699 );
6700
6701 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
6702 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6703
6704 _ = editor.update(cx, |editor, cx| {
6705 let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
6706
6707 editor
6708 .insert_snippet(&insertion_ranges, snippet, cx)
6709 .unwrap();
6710
6711 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
6712 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
6713 assert_eq!(editor.text(cx), expected_text);
6714 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
6715 }
6716
6717 assert(
6718 editor,
6719 cx,
6720 indoc! {"
6721 type «» =•
6722 "},
6723 );
6724
6725 assert!(editor.context_menu_visible(), "There should be a matches");
6726 });
6727}
6728
6729#[gpui::test]
6730async fn test_snippets(cx: &mut gpui::TestAppContext) {
6731 init_test(cx, |_| {});
6732
6733 let (text, insertion_ranges) = marked_text_ranges(
6734 indoc! {"
6735 a.ˇ b
6736 a.ˇ b
6737 a.ˇ b
6738 "},
6739 false,
6740 );
6741
6742 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
6743 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6744
6745 editor.update(cx, |editor, cx| {
6746 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
6747
6748 editor
6749 .insert_snippet(&insertion_ranges, snippet, cx)
6750 .unwrap();
6751
6752 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
6753 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
6754 assert_eq!(editor.text(cx), expected_text);
6755 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
6756 }
6757
6758 assert(
6759 editor,
6760 cx,
6761 indoc! {"
6762 a.f(«one», two, «three») b
6763 a.f(«one», two, «three») b
6764 a.f(«one», two, «three») b
6765 "},
6766 );
6767
6768 // Can't move earlier than the first tab stop
6769 assert!(!editor.move_to_prev_snippet_tabstop(cx));
6770 assert(
6771 editor,
6772 cx,
6773 indoc! {"
6774 a.f(«one», two, «three») b
6775 a.f(«one», two, «three») b
6776 a.f(«one», two, «three») b
6777 "},
6778 );
6779
6780 assert!(editor.move_to_next_snippet_tabstop(cx));
6781 assert(
6782 editor,
6783 cx,
6784 indoc! {"
6785 a.f(one, «two», three) b
6786 a.f(one, «two», three) b
6787 a.f(one, «two», three) b
6788 "},
6789 );
6790
6791 editor.move_to_prev_snippet_tabstop(cx);
6792 assert(
6793 editor,
6794 cx,
6795 indoc! {"
6796 a.f(«one», two, «three») b
6797 a.f(«one», two, «three») b
6798 a.f(«one», two, «three») b
6799 "},
6800 );
6801
6802 assert!(editor.move_to_next_snippet_tabstop(cx));
6803 assert(
6804 editor,
6805 cx,
6806 indoc! {"
6807 a.f(one, «two», three) b
6808 a.f(one, «two», three) b
6809 a.f(one, «two», three) b
6810 "},
6811 );
6812 assert!(editor.move_to_next_snippet_tabstop(cx));
6813 assert(
6814 editor,
6815 cx,
6816 indoc! {"
6817 a.f(one, two, three)ˇ b
6818 a.f(one, two, three)ˇ b
6819 a.f(one, two, three)ˇ b
6820 "},
6821 );
6822
6823 // As soon as the last tab stop is reached, snippet state is gone
6824 editor.move_to_prev_snippet_tabstop(cx);
6825 assert(
6826 editor,
6827 cx,
6828 indoc! {"
6829 a.f(one, two, three)ˇ b
6830 a.f(one, two, three)ˇ b
6831 a.f(one, two, three)ˇ b
6832 "},
6833 );
6834 });
6835}
6836
6837#[gpui::test]
6838async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
6839 init_test(cx, |_| {});
6840
6841 let fs = FakeFs::new(cx.executor());
6842 fs.insert_file("/file.rs", Default::default()).await;
6843
6844 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6845
6846 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6847 language_registry.add(rust_lang());
6848 let mut fake_servers = language_registry.register_fake_lsp(
6849 "Rust",
6850 FakeLspAdapter {
6851 capabilities: lsp::ServerCapabilities {
6852 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6853 ..Default::default()
6854 },
6855 ..Default::default()
6856 },
6857 );
6858
6859 let buffer = project
6860 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6861 .await
6862 .unwrap();
6863
6864 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6865 let (editor, cx) =
6866 cx.add_window_view(|cx| build_editor_with_project(project.clone(), buffer, cx));
6867 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6868 assert!(cx.read(|cx| editor.is_dirty(cx)));
6869
6870 cx.executor().start_waiting();
6871 let fake_server = fake_servers.next().await.unwrap();
6872
6873 let save = editor
6874 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6875 .unwrap();
6876 fake_server
6877 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6878 assert_eq!(
6879 params.text_document.uri,
6880 lsp::Url::from_file_path("/file.rs").unwrap()
6881 );
6882 assert_eq!(params.options.tab_size, 4);
6883 Ok(Some(vec![lsp::TextEdit::new(
6884 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6885 ", ".to_string(),
6886 )]))
6887 })
6888 .next()
6889 .await;
6890 cx.executor().start_waiting();
6891 save.await;
6892
6893 assert_eq!(
6894 editor.update(cx, |editor, cx| editor.text(cx)),
6895 "one, two\nthree\n"
6896 );
6897 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6898
6899 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6900 assert!(cx.read(|cx| editor.is_dirty(cx)));
6901
6902 // Ensure we can still save even if formatting hangs.
6903 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6904 assert_eq!(
6905 params.text_document.uri,
6906 lsp::Url::from_file_path("/file.rs").unwrap()
6907 );
6908 futures::future::pending::<()>().await;
6909 unreachable!()
6910 });
6911 let save = editor
6912 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6913 .unwrap();
6914 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6915 cx.executor().start_waiting();
6916 save.await;
6917 assert_eq!(
6918 editor.update(cx, |editor, cx| editor.text(cx)),
6919 "one\ntwo\nthree\n"
6920 );
6921 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6922
6923 // For non-dirty buffer, no formatting request should be sent
6924 let save = editor
6925 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6926 .unwrap();
6927 let _pending_format_request = fake_server
6928 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
6929 panic!("Should not be invoked on non-dirty buffer");
6930 })
6931 .next();
6932 cx.executor().start_waiting();
6933 save.await;
6934
6935 // Set rust language override and assert overridden tabsize is sent to language server
6936 update_test_language_settings(cx, |settings| {
6937 settings.languages.insert(
6938 "Rust".into(),
6939 LanguageSettingsContent {
6940 tab_size: NonZeroU32::new(8),
6941 ..Default::default()
6942 },
6943 );
6944 });
6945
6946 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
6947 assert!(cx.read(|cx| editor.is_dirty(cx)));
6948 let save = editor
6949 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6950 .unwrap();
6951 fake_server
6952 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6953 assert_eq!(
6954 params.text_document.uri,
6955 lsp::Url::from_file_path("/file.rs").unwrap()
6956 );
6957 assert_eq!(params.options.tab_size, 8);
6958 Ok(Some(vec![]))
6959 })
6960 .next()
6961 .await;
6962 cx.executor().start_waiting();
6963 save.await;
6964}
6965
6966#[gpui::test]
6967async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
6968 init_test(cx, |_| {});
6969
6970 let cols = 4;
6971 let rows = 10;
6972 let sample_text_1 = sample_text(rows, cols, 'a');
6973 assert_eq!(
6974 sample_text_1,
6975 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
6976 );
6977 let sample_text_2 = sample_text(rows, cols, 'l');
6978 assert_eq!(
6979 sample_text_2,
6980 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
6981 );
6982 let sample_text_3 = sample_text(rows, cols, 'v');
6983 assert_eq!(
6984 sample_text_3,
6985 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
6986 );
6987
6988 let fs = FakeFs::new(cx.executor());
6989 fs.insert_tree(
6990 "/a",
6991 json!({
6992 "main.rs": sample_text_1,
6993 "other.rs": sample_text_2,
6994 "lib.rs": sample_text_3,
6995 }),
6996 )
6997 .await;
6998
6999 let project = Project::test(fs, ["/a".as_ref()], cx).await;
7000 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
7001 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
7002
7003 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7004 language_registry.add(rust_lang());
7005 let mut fake_servers = language_registry.register_fake_lsp(
7006 "Rust",
7007 FakeLspAdapter {
7008 capabilities: lsp::ServerCapabilities {
7009 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7010 ..Default::default()
7011 },
7012 ..Default::default()
7013 },
7014 );
7015
7016 let worktree = project.update(cx, |project, cx| {
7017 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
7018 assert_eq!(worktrees.len(), 1);
7019 worktrees.pop().unwrap()
7020 });
7021 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
7022
7023 let buffer_1 = project
7024 .update(cx, |project, cx| {
7025 project.open_buffer((worktree_id, "main.rs"), cx)
7026 })
7027 .await
7028 .unwrap();
7029 let buffer_2 = project
7030 .update(cx, |project, cx| {
7031 project.open_buffer((worktree_id, "other.rs"), cx)
7032 })
7033 .await
7034 .unwrap();
7035 let buffer_3 = project
7036 .update(cx, |project, cx| {
7037 project.open_buffer((worktree_id, "lib.rs"), cx)
7038 })
7039 .await
7040 .unwrap();
7041
7042 let multi_buffer = cx.new_model(|cx| {
7043 let mut multi_buffer = MultiBuffer::new(ReadWrite);
7044 multi_buffer.push_excerpts(
7045 buffer_1.clone(),
7046 [
7047 ExcerptRange {
7048 context: Point::new(0, 0)..Point::new(3, 0),
7049 primary: None,
7050 },
7051 ExcerptRange {
7052 context: Point::new(5, 0)..Point::new(7, 0),
7053 primary: None,
7054 },
7055 ExcerptRange {
7056 context: Point::new(9, 0)..Point::new(10, 4),
7057 primary: None,
7058 },
7059 ],
7060 cx,
7061 );
7062 multi_buffer.push_excerpts(
7063 buffer_2.clone(),
7064 [
7065 ExcerptRange {
7066 context: Point::new(0, 0)..Point::new(3, 0),
7067 primary: None,
7068 },
7069 ExcerptRange {
7070 context: Point::new(5, 0)..Point::new(7, 0),
7071 primary: None,
7072 },
7073 ExcerptRange {
7074 context: Point::new(9, 0)..Point::new(10, 4),
7075 primary: None,
7076 },
7077 ],
7078 cx,
7079 );
7080 multi_buffer.push_excerpts(
7081 buffer_3.clone(),
7082 [
7083 ExcerptRange {
7084 context: Point::new(0, 0)..Point::new(3, 0),
7085 primary: None,
7086 },
7087 ExcerptRange {
7088 context: Point::new(5, 0)..Point::new(7, 0),
7089 primary: None,
7090 },
7091 ExcerptRange {
7092 context: Point::new(9, 0)..Point::new(10, 4),
7093 primary: None,
7094 },
7095 ],
7096 cx,
7097 );
7098 multi_buffer
7099 });
7100 let multi_buffer_editor = cx.new_view(|cx| {
7101 Editor::new(
7102 EditorMode::Full,
7103 multi_buffer,
7104 Some(project.clone()),
7105 true,
7106 cx,
7107 )
7108 });
7109
7110 multi_buffer_editor.update(cx, |editor, cx| {
7111 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
7112 editor.insert("|one|two|three|", cx);
7113 });
7114 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7115 multi_buffer_editor.update(cx, |editor, cx| {
7116 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
7117 s.select_ranges(Some(60..70))
7118 });
7119 editor.insert("|four|five|six|", cx);
7120 });
7121 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7122
7123 // First two buffers should be edited, but not the third one.
7124 assert_eq!(
7125 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7126 "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}",
7127 );
7128 buffer_1.update(cx, |buffer, _| {
7129 assert!(buffer.is_dirty());
7130 assert_eq!(
7131 buffer.text(),
7132 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
7133 )
7134 });
7135 buffer_2.update(cx, |buffer, _| {
7136 assert!(buffer.is_dirty());
7137 assert_eq!(
7138 buffer.text(),
7139 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
7140 )
7141 });
7142 buffer_3.update(cx, |buffer, _| {
7143 assert!(!buffer.is_dirty());
7144 assert_eq!(buffer.text(), sample_text_3,)
7145 });
7146 cx.executor().run_until_parked();
7147
7148 cx.executor().start_waiting();
7149 let save = multi_buffer_editor
7150 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7151 .unwrap();
7152
7153 let fake_server = fake_servers.next().await.unwrap();
7154 fake_server
7155 .server
7156 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7157 Ok(Some(vec![lsp::TextEdit::new(
7158 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7159 format!("[{} formatted]", params.text_document.uri),
7160 )]))
7161 })
7162 .detach();
7163 save.await;
7164
7165 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
7166 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
7167 assert_eq!(
7168 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7169 "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}",
7170 );
7171 buffer_1.update(cx, |buffer, _| {
7172 assert!(!buffer.is_dirty());
7173 assert_eq!(
7174 buffer.text(),
7175 "a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n",
7176 )
7177 });
7178 buffer_2.update(cx, |buffer, _| {
7179 assert!(!buffer.is_dirty());
7180 assert_eq!(
7181 buffer.text(),
7182 "lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n",
7183 )
7184 });
7185 buffer_3.update(cx, |buffer, _| {
7186 assert!(!buffer.is_dirty());
7187 assert_eq!(buffer.text(), sample_text_3,)
7188 });
7189}
7190
7191#[gpui::test]
7192async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
7193 init_test(cx, |_| {});
7194
7195 let fs = FakeFs::new(cx.executor());
7196 fs.insert_file("/file.rs", Default::default()).await;
7197
7198 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7199
7200 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7201 language_registry.add(rust_lang());
7202 let mut fake_servers = language_registry.register_fake_lsp(
7203 "Rust",
7204 FakeLspAdapter {
7205 capabilities: lsp::ServerCapabilities {
7206 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
7207 ..Default::default()
7208 },
7209 ..Default::default()
7210 },
7211 );
7212
7213 let buffer = project
7214 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
7215 .await
7216 .unwrap();
7217
7218 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
7219 let (editor, cx) =
7220 cx.add_window_view(|cx| build_editor_with_project(project.clone(), buffer, cx));
7221 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7222 assert!(cx.read(|cx| editor.is_dirty(cx)));
7223
7224 cx.executor().start_waiting();
7225 let fake_server = fake_servers.next().await.unwrap();
7226
7227 let save = editor
7228 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7229 .unwrap();
7230 fake_server
7231 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7232 assert_eq!(
7233 params.text_document.uri,
7234 lsp::Url::from_file_path("/file.rs").unwrap()
7235 );
7236 assert_eq!(params.options.tab_size, 4);
7237 Ok(Some(vec![lsp::TextEdit::new(
7238 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7239 ", ".to_string(),
7240 )]))
7241 })
7242 .next()
7243 .await;
7244 cx.executor().start_waiting();
7245 save.await;
7246 assert_eq!(
7247 editor.update(cx, |editor, cx| editor.text(cx)),
7248 "one, two\nthree\n"
7249 );
7250 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7251
7252 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7253 assert!(cx.read(|cx| editor.is_dirty(cx)));
7254
7255 // Ensure we can still save even if formatting hangs.
7256 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
7257 move |params, _| async move {
7258 assert_eq!(
7259 params.text_document.uri,
7260 lsp::Url::from_file_path("/file.rs").unwrap()
7261 );
7262 futures::future::pending::<()>().await;
7263 unreachable!()
7264 },
7265 );
7266 let save = editor
7267 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7268 .unwrap();
7269 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7270 cx.executor().start_waiting();
7271 save.await;
7272 assert_eq!(
7273 editor.update(cx, |editor, cx| editor.text(cx)),
7274 "one\ntwo\nthree\n"
7275 );
7276 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7277
7278 // For non-dirty buffer, no formatting request should be sent
7279 let save = editor
7280 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7281 .unwrap();
7282 let _pending_format_request = fake_server
7283 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7284 panic!("Should not be invoked on non-dirty buffer");
7285 })
7286 .next();
7287 cx.executor().start_waiting();
7288 save.await;
7289
7290 // Set Rust language override and assert overridden tabsize is sent to language server
7291 update_test_language_settings(cx, |settings| {
7292 settings.languages.insert(
7293 "Rust".into(),
7294 LanguageSettingsContent {
7295 tab_size: NonZeroU32::new(8),
7296 ..Default::default()
7297 },
7298 );
7299 });
7300
7301 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
7302 assert!(cx.read(|cx| editor.is_dirty(cx)));
7303 let save = editor
7304 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7305 .unwrap();
7306 fake_server
7307 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7308 assert_eq!(
7309 params.text_document.uri,
7310 lsp::Url::from_file_path("/file.rs").unwrap()
7311 );
7312 assert_eq!(params.options.tab_size, 8);
7313 Ok(Some(vec![]))
7314 })
7315 .next()
7316 .await;
7317 cx.executor().start_waiting();
7318 save.await;
7319}
7320
7321#[gpui::test]
7322async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
7323 init_test(cx, |settings| {
7324 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
7325 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
7326 ))
7327 });
7328
7329 let fs = FakeFs::new(cx.executor());
7330 fs.insert_file("/file.rs", Default::default()).await;
7331
7332 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7333
7334 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7335 language_registry.add(Arc::new(Language::new(
7336 LanguageConfig {
7337 name: "Rust".into(),
7338 matcher: LanguageMatcher {
7339 path_suffixes: vec!["rs".to_string()],
7340 ..Default::default()
7341 },
7342 ..LanguageConfig::default()
7343 },
7344 Some(tree_sitter_rust::LANGUAGE.into()),
7345 )));
7346 update_test_language_settings(cx, |settings| {
7347 // Enable Prettier formatting for the same buffer, and ensure
7348 // LSP is called instead of Prettier.
7349 settings.defaults.prettier = Some(PrettierSettings {
7350 allowed: true,
7351 ..PrettierSettings::default()
7352 });
7353 });
7354 let mut fake_servers = language_registry.register_fake_lsp(
7355 "Rust",
7356 FakeLspAdapter {
7357 capabilities: lsp::ServerCapabilities {
7358 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7359 ..Default::default()
7360 },
7361 ..Default::default()
7362 },
7363 );
7364
7365 let buffer = project
7366 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
7367 .await
7368 .unwrap();
7369
7370 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
7371 let (editor, cx) =
7372 cx.add_window_view(|cx| build_editor_with_project(project.clone(), buffer, cx));
7373 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7374
7375 cx.executor().start_waiting();
7376 let fake_server = fake_servers.next().await.unwrap();
7377
7378 let format = editor
7379 .update(cx, |editor, cx| {
7380 editor.perform_format(
7381 project.clone(),
7382 FormatTrigger::Manual,
7383 FormatTarget::Buffer,
7384 cx,
7385 )
7386 })
7387 .unwrap();
7388 fake_server
7389 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7390 assert_eq!(
7391 params.text_document.uri,
7392 lsp::Url::from_file_path("/file.rs").unwrap()
7393 );
7394 assert_eq!(params.options.tab_size, 4);
7395 Ok(Some(vec![lsp::TextEdit::new(
7396 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7397 ", ".to_string(),
7398 )]))
7399 })
7400 .next()
7401 .await;
7402 cx.executor().start_waiting();
7403 format.await;
7404 assert_eq!(
7405 editor.update(cx, |editor, cx| editor.text(cx)),
7406 "one, two\nthree\n"
7407 );
7408
7409 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7410 // Ensure we don't lock if formatting hangs.
7411 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7412 assert_eq!(
7413 params.text_document.uri,
7414 lsp::Url::from_file_path("/file.rs").unwrap()
7415 );
7416 futures::future::pending::<()>().await;
7417 unreachable!()
7418 });
7419 let format = editor
7420 .update(cx, |editor, cx| {
7421 editor.perform_format(project, FormatTrigger::Manual, FormatTarget::Buffer, cx)
7422 })
7423 .unwrap();
7424 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7425 cx.executor().start_waiting();
7426 format.await;
7427 assert_eq!(
7428 editor.update(cx, |editor, cx| editor.text(cx)),
7429 "one\ntwo\nthree\n"
7430 );
7431}
7432
7433#[gpui::test]
7434async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
7435 init_test(cx, |_| {});
7436
7437 let mut cx = EditorLspTestContext::new_rust(
7438 lsp::ServerCapabilities {
7439 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7440 ..Default::default()
7441 },
7442 cx,
7443 )
7444 .await;
7445
7446 cx.set_state(indoc! {"
7447 one.twoˇ
7448 "});
7449
7450 // The format request takes a long time. When it completes, it inserts
7451 // a newline and an indent before the `.`
7452 cx.lsp
7453 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
7454 let executor = cx.background_executor().clone();
7455 async move {
7456 executor.timer(Duration::from_millis(100)).await;
7457 Ok(Some(vec![lsp::TextEdit {
7458 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
7459 new_text: "\n ".into(),
7460 }]))
7461 }
7462 });
7463
7464 // Submit a format request.
7465 let format_1 = cx
7466 .update_editor(|editor, cx| editor.format(&Format, cx))
7467 .unwrap();
7468 cx.executor().run_until_parked();
7469
7470 // Submit a second format request.
7471 let format_2 = cx
7472 .update_editor(|editor, cx| editor.format(&Format, cx))
7473 .unwrap();
7474 cx.executor().run_until_parked();
7475
7476 // Wait for both format requests to complete
7477 cx.executor().advance_clock(Duration::from_millis(200));
7478 cx.executor().start_waiting();
7479 format_1.await.unwrap();
7480 cx.executor().start_waiting();
7481 format_2.await.unwrap();
7482
7483 // The formatting edits only happens once.
7484 cx.assert_editor_state(indoc! {"
7485 one
7486 .twoˇ
7487 "});
7488}
7489
7490#[gpui::test]
7491async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
7492 init_test(cx, |settings| {
7493 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
7494 });
7495
7496 let mut cx = EditorLspTestContext::new_rust(
7497 lsp::ServerCapabilities {
7498 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7499 ..Default::default()
7500 },
7501 cx,
7502 )
7503 .await;
7504
7505 // Set up a buffer white some trailing whitespace and no trailing newline.
7506 cx.set_state(
7507 &[
7508 "one ", //
7509 "twoˇ", //
7510 "three ", //
7511 "four", //
7512 ]
7513 .join("\n"),
7514 );
7515
7516 // Submit a format request.
7517 let format = cx
7518 .update_editor(|editor, cx| editor.format(&Format, cx))
7519 .unwrap();
7520
7521 // Record which buffer changes have been sent to the language server
7522 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
7523 cx.lsp
7524 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
7525 let buffer_changes = buffer_changes.clone();
7526 move |params, _| {
7527 buffer_changes.lock().extend(
7528 params
7529 .content_changes
7530 .into_iter()
7531 .map(|e| (e.range.unwrap(), e.text)),
7532 );
7533 }
7534 });
7535
7536 // Handle formatting requests to the language server.
7537 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
7538 let buffer_changes = buffer_changes.clone();
7539 move |_, _| {
7540 // When formatting is requested, trailing whitespace has already been stripped,
7541 // and the trailing newline has already been added.
7542 assert_eq!(
7543 &buffer_changes.lock()[1..],
7544 &[
7545 (
7546 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
7547 "".into()
7548 ),
7549 (
7550 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
7551 "".into()
7552 ),
7553 (
7554 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
7555 "\n".into()
7556 ),
7557 ]
7558 );
7559
7560 // Insert blank lines between each line of the buffer.
7561 async move {
7562 Ok(Some(vec![
7563 lsp::TextEdit {
7564 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
7565 new_text: "\n".into(),
7566 },
7567 lsp::TextEdit {
7568 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
7569 new_text: "\n".into(),
7570 },
7571 ]))
7572 }
7573 }
7574 });
7575
7576 // After formatting the buffer, the trailing whitespace is stripped,
7577 // a newline is appended, and the edits provided by the language server
7578 // have been applied.
7579 format.await.unwrap();
7580 cx.assert_editor_state(
7581 &[
7582 "one", //
7583 "", //
7584 "twoˇ", //
7585 "", //
7586 "three", //
7587 "four", //
7588 "", //
7589 ]
7590 .join("\n"),
7591 );
7592
7593 // Undoing the formatting undoes the trailing whitespace removal, the
7594 // trailing newline, and the LSP edits.
7595 cx.update_buffer(|buffer, cx| buffer.undo(cx));
7596 cx.assert_editor_state(
7597 &[
7598 "one ", //
7599 "twoˇ", //
7600 "three ", //
7601 "four", //
7602 ]
7603 .join("\n"),
7604 );
7605}
7606
7607#[gpui::test]
7608async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
7609 cx: &mut gpui::TestAppContext,
7610) {
7611 init_test(cx, |_| {});
7612
7613 cx.update(|cx| {
7614 cx.update_global::<SettingsStore, _>(|settings, cx| {
7615 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7616 settings.auto_signature_help = Some(true);
7617 });
7618 });
7619 });
7620
7621 let mut cx = EditorLspTestContext::new_rust(
7622 lsp::ServerCapabilities {
7623 signature_help_provider: Some(lsp::SignatureHelpOptions {
7624 ..Default::default()
7625 }),
7626 ..Default::default()
7627 },
7628 cx,
7629 )
7630 .await;
7631
7632 let language = Language::new(
7633 LanguageConfig {
7634 name: "Rust".into(),
7635 brackets: BracketPairConfig {
7636 pairs: vec![
7637 BracketPair {
7638 start: "{".to_string(),
7639 end: "}".to_string(),
7640 close: true,
7641 surround: true,
7642 newline: true,
7643 },
7644 BracketPair {
7645 start: "(".to_string(),
7646 end: ")".to_string(),
7647 close: true,
7648 surround: true,
7649 newline: true,
7650 },
7651 BracketPair {
7652 start: "/*".to_string(),
7653 end: " */".to_string(),
7654 close: true,
7655 surround: true,
7656 newline: true,
7657 },
7658 BracketPair {
7659 start: "[".to_string(),
7660 end: "]".to_string(),
7661 close: false,
7662 surround: false,
7663 newline: true,
7664 },
7665 BracketPair {
7666 start: "\"".to_string(),
7667 end: "\"".to_string(),
7668 close: true,
7669 surround: true,
7670 newline: false,
7671 },
7672 BracketPair {
7673 start: "<".to_string(),
7674 end: ">".to_string(),
7675 close: false,
7676 surround: true,
7677 newline: true,
7678 },
7679 ],
7680 ..Default::default()
7681 },
7682 autoclose_before: "})]".to_string(),
7683 ..Default::default()
7684 },
7685 Some(tree_sitter_rust::LANGUAGE.into()),
7686 );
7687 let language = Arc::new(language);
7688
7689 cx.language_registry().add(language.clone());
7690 cx.update_buffer(|buffer, cx| {
7691 buffer.set_language(Some(language), cx);
7692 });
7693
7694 cx.set_state(
7695 &r#"
7696 fn main() {
7697 sampleˇ
7698 }
7699 "#
7700 .unindent(),
7701 );
7702
7703 cx.update_editor(|view, cx| {
7704 view.handle_input("(", cx);
7705 });
7706 cx.assert_editor_state(
7707 &"
7708 fn main() {
7709 sample(ˇ)
7710 }
7711 "
7712 .unindent(),
7713 );
7714
7715 let mocked_response = lsp::SignatureHelp {
7716 signatures: vec![lsp::SignatureInformation {
7717 label: "fn sample(param1: u8, param2: u8)".to_string(),
7718 documentation: None,
7719 parameters: Some(vec![
7720 lsp::ParameterInformation {
7721 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7722 documentation: None,
7723 },
7724 lsp::ParameterInformation {
7725 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7726 documentation: None,
7727 },
7728 ]),
7729 active_parameter: None,
7730 }],
7731 active_signature: Some(0),
7732 active_parameter: Some(0),
7733 };
7734 handle_signature_help_request(&mut cx, mocked_response).await;
7735
7736 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7737 .await;
7738
7739 cx.editor(|editor, _| {
7740 let signature_help_state = editor.signature_help_state.popover().cloned();
7741 assert!(signature_help_state.is_some());
7742 let ParsedMarkdown {
7743 text, highlights, ..
7744 } = signature_help_state.unwrap().parsed_content;
7745 assert_eq!(text, "param1: u8, param2: u8");
7746 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7747 });
7748}
7749
7750#[gpui::test]
7751async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui::TestAppContext) {
7752 init_test(cx, |_| {});
7753
7754 cx.update(|cx| {
7755 cx.update_global::<SettingsStore, _>(|settings, cx| {
7756 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7757 settings.auto_signature_help = Some(false);
7758 settings.show_signature_help_after_edits = Some(false);
7759 });
7760 });
7761 });
7762
7763 let mut cx = EditorLspTestContext::new_rust(
7764 lsp::ServerCapabilities {
7765 signature_help_provider: Some(lsp::SignatureHelpOptions {
7766 ..Default::default()
7767 }),
7768 ..Default::default()
7769 },
7770 cx,
7771 )
7772 .await;
7773
7774 let language = Language::new(
7775 LanguageConfig {
7776 name: "Rust".into(),
7777 brackets: BracketPairConfig {
7778 pairs: vec![
7779 BracketPair {
7780 start: "{".to_string(),
7781 end: "}".to_string(),
7782 close: true,
7783 surround: true,
7784 newline: true,
7785 },
7786 BracketPair {
7787 start: "(".to_string(),
7788 end: ")".to_string(),
7789 close: true,
7790 surround: true,
7791 newline: true,
7792 },
7793 BracketPair {
7794 start: "/*".to_string(),
7795 end: " */".to_string(),
7796 close: true,
7797 surround: true,
7798 newline: true,
7799 },
7800 BracketPair {
7801 start: "[".to_string(),
7802 end: "]".to_string(),
7803 close: false,
7804 surround: false,
7805 newline: true,
7806 },
7807 BracketPair {
7808 start: "\"".to_string(),
7809 end: "\"".to_string(),
7810 close: true,
7811 surround: true,
7812 newline: false,
7813 },
7814 BracketPair {
7815 start: "<".to_string(),
7816 end: ">".to_string(),
7817 close: false,
7818 surround: true,
7819 newline: true,
7820 },
7821 ],
7822 ..Default::default()
7823 },
7824 autoclose_before: "})]".to_string(),
7825 ..Default::default()
7826 },
7827 Some(tree_sitter_rust::LANGUAGE.into()),
7828 );
7829 let language = Arc::new(language);
7830
7831 cx.language_registry().add(language.clone());
7832 cx.update_buffer(|buffer, cx| {
7833 buffer.set_language(Some(language), cx);
7834 });
7835
7836 // Ensure that signature_help is not called when no signature help is enabled.
7837 cx.set_state(
7838 &r#"
7839 fn main() {
7840 sampleˇ
7841 }
7842 "#
7843 .unindent(),
7844 );
7845 cx.update_editor(|view, cx| {
7846 view.handle_input("(", cx);
7847 });
7848 cx.assert_editor_state(
7849 &"
7850 fn main() {
7851 sample(ˇ)
7852 }
7853 "
7854 .unindent(),
7855 );
7856 cx.editor(|editor, _| {
7857 assert!(editor.signature_help_state.task().is_none());
7858 });
7859
7860 let mocked_response = lsp::SignatureHelp {
7861 signatures: vec![lsp::SignatureInformation {
7862 label: "fn sample(param1: u8, param2: u8)".to_string(),
7863 documentation: None,
7864 parameters: Some(vec![
7865 lsp::ParameterInformation {
7866 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7867 documentation: None,
7868 },
7869 lsp::ParameterInformation {
7870 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7871 documentation: None,
7872 },
7873 ]),
7874 active_parameter: None,
7875 }],
7876 active_signature: Some(0),
7877 active_parameter: Some(0),
7878 };
7879
7880 // Ensure that signature_help is called when enabled afte edits
7881 cx.update(|cx| {
7882 cx.update_global::<SettingsStore, _>(|settings, cx| {
7883 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7884 settings.auto_signature_help = Some(false);
7885 settings.show_signature_help_after_edits = Some(true);
7886 });
7887 });
7888 });
7889 cx.set_state(
7890 &r#"
7891 fn main() {
7892 sampleˇ
7893 }
7894 "#
7895 .unindent(),
7896 );
7897 cx.update_editor(|view, cx| {
7898 view.handle_input("(", cx);
7899 });
7900 cx.assert_editor_state(
7901 &"
7902 fn main() {
7903 sample(ˇ)
7904 }
7905 "
7906 .unindent(),
7907 );
7908 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7909 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7910 .await;
7911 cx.update_editor(|editor, _| {
7912 let signature_help_state = editor.signature_help_state.popover().cloned();
7913 assert!(signature_help_state.is_some());
7914 let ParsedMarkdown {
7915 text, highlights, ..
7916 } = signature_help_state.unwrap().parsed_content;
7917 assert_eq!(text, "param1: u8, param2: u8");
7918 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7919 editor.signature_help_state = SignatureHelpState::default();
7920 });
7921
7922 // Ensure that signature_help is called when auto signature help override is enabled
7923 cx.update(|cx| {
7924 cx.update_global::<SettingsStore, _>(|settings, cx| {
7925 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7926 settings.auto_signature_help = Some(true);
7927 settings.show_signature_help_after_edits = Some(false);
7928 });
7929 });
7930 });
7931 cx.set_state(
7932 &r#"
7933 fn main() {
7934 sampleˇ
7935 }
7936 "#
7937 .unindent(),
7938 );
7939 cx.update_editor(|view, cx| {
7940 view.handle_input("(", cx);
7941 });
7942 cx.assert_editor_state(
7943 &"
7944 fn main() {
7945 sample(ˇ)
7946 }
7947 "
7948 .unindent(),
7949 );
7950 handle_signature_help_request(&mut cx, mocked_response).await;
7951 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7952 .await;
7953 cx.editor(|editor, _| {
7954 let signature_help_state = editor.signature_help_state.popover().cloned();
7955 assert!(signature_help_state.is_some());
7956 let ParsedMarkdown {
7957 text, highlights, ..
7958 } = signature_help_state.unwrap().parsed_content;
7959 assert_eq!(text, "param1: u8, param2: u8");
7960 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7961 });
7962}
7963
7964#[gpui::test]
7965async fn test_signature_help(cx: &mut gpui::TestAppContext) {
7966 init_test(cx, |_| {});
7967 cx.update(|cx| {
7968 cx.update_global::<SettingsStore, _>(|settings, cx| {
7969 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7970 settings.auto_signature_help = Some(true);
7971 });
7972 });
7973 });
7974
7975 let mut cx = EditorLspTestContext::new_rust(
7976 lsp::ServerCapabilities {
7977 signature_help_provider: Some(lsp::SignatureHelpOptions {
7978 ..Default::default()
7979 }),
7980 ..Default::default()
7981 },
7982 cx,
7983 )
7984 .await;
7985
7986 // A test that directly calls `show_signature_help`
7987 cx.update_editor(|editor, cx| {
7988 editor.show_signature_help(&ShowSignatureHelp, cx);
7989 });
7990
7991 let mocked_response = lsp::SignatureHelp {
7992 signatures: vec![lsp::SignatureInformation {
7993 label: "fn sample(param1: u8, param2: u8)".to_string(),
7994 documentation: None,
7995 parameters: Some(vec![
7996 lsp::ParameterInformation {
7997 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7998 documentation: None,
7999 },
8000 lsp::ParameterInformation {
8001 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8002 documentation: None,
8003 },
8004 ]),
8005 active_parameter: None,
8006 }],
8007 active_signature: Some(0),
8008 active_parameter: Some(0),
8009 };
8010 handle_signature_help_request(&mut cx, mocked_response).await;
8011
8012 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8013 .await;
8014
8015 cx.editor(|editor, _| {
8016 let signature_help_state = editor.signature_help_state.popover().cloned();
8017 assert!(signature_help_state.is_some());
8018 let ParsedMarkdown {
8019 text, highlights, ..
8020 } = signature_help_state.unwrap().parsed_content;
8021 assert_eq!(text, "param1: u8, param2: u8");
8022 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
8023 });
8024
8025 // When exiting outside from inside the brackets, `signature_help` is closed.
8026 cx.set_state(indoc! {"
8027 fn main() {
8028 sample(ˇ);
8029 }
8030
8031 fn sample(param1: u8, param2: u8) {}
8032 "});
8033
8034 cx.update_editor(|editor, cx| {
8035 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
8036 });
8037
8038 let mocked_response = lsp::SignatureHelp {
8039 signatures: Vec::new(),
8040 active_signature: None,
8041 active_parameter: None,
8042 };
8043 handle_signature_help_request(&mut cx, mocked_response).await;
8044
8045 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8046 .await;
8047
8048 cx.editor(|editor, _| {
8049 assert!(!editor.signature_help_state.is_shown());
8050 });
8051
8052 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
8053 cx.set_state(indoc! {"
8054 fn main() {
8055 sample(ˇ);
8056 }
8057
8058 fn sample(param1: u8, param2: u8) {}
8059 "});
8060
8061 let mocked_response = lsp::SignatureHelp {
8062 signatures: vec![lsp::SignatureInformation {
8063 label: "fn sample(param1: u8, param2: u8)".to_string(),
8064 documentation: None,
8065 parameters: Some(vec![
8066 lsp::ParameterInformation {
8067 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8068 documentation: None,
8069 },
8070 lsp::ParameterInformation {
8071 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8072 documentation: None,
8073 },
8074 ]),
8075 active_parameter: None,
8076 }],
8077 active_signature: Some(0),
8078 active_parameter: Some(0),
8079 };
8080 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8081 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8082 .await;
8083 cx.editor(|editor, _| {
8084 assert!(editor.signature_help_state.is_shown());
8085 });
8086
8087 // Restore the popover with more parameter input
8088 cx.set_state(indoc! {"
8089 fn main() {
8090 sample(param1, param2ˇ);
8091 }
8092
8093 fn sample(param1: u8, param2: u8) {}
8094 "});
8095
8096 let mocked_response = lsp::SignatureHelp {
8097 signatures: vec![lsp::SignatureInformation {
8098 label: "fn sample(param1: u8, param2: u8)".to_string(),
8099 documentation: None,
8100 parameters: Some(vec![
8101 lsp::ParameterInformation {
8102 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8103 documentation: None,
8104 },
8105 lsp::ParameterInformation {
8106 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8107 documentation: None,
8108 },
8109 ]),
8110 active_parameter: None,
8111 }],
8112 active_signature: Some(0),
8113 active_parameter: Some(1),
8114 };
8115 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8116 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8117 .await;
8118
8119 // When selecting a range, the popover is gone.
8120 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
8121 cx.update_editor(|editor, cx| {
8122 editor.change_selections(None, cx, |s| {
8123 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8124 })
8125 });
8126 cx.assert_editor_state(indoc! {"
8127 fn main() {
8128 sample(param1, «ˇparam2»);
8129 }
8130
8131 fn sample(param1: u8, param2: u8) {}
8132 "});
8133 cx.editor(|editor, _| {
8134 assert!(!editor.signature_help_state.is_shown());
8135 });
8136
8137 // When unselecting again, the popover is back if within the brackets.
8138 cx.update_editor(|editor, cx| {
8139 editor.change_selections(None, cx, |s| {
8140 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8141 })
8142 });
8143 cx.assert_editor_state(indoc! {"
8144 fn main() {
8145 sample(param1, ˇparam2);
8146 }
8147
8148 fn sample(param1: u8, param2: u8) {}
8149 "});
8150 handle_signature_help_request(&mut cx, mocked_response).await;
8151 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8152 .await;
8153 cx.editor(|editor, _| {
8154 assert!(editor.signature_help_state.is_shown());
8155 });
8156
8157 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
8158 cx.update_editor(|editor, cx| {
8159 editor.change_selections(None, cx, |s| {
8160 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
8161 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8162 })
8163 });
8164 cx.assert_editor_state(indoc! {"
8165 fn main() {
8166 sample(param1, ˇparam2);
8167 }
8168
8169 fn sample(param1: u8, param2: u8) {}
8170 "});
8171
8172 let mocked_response = lsp::SignatureHelp {
8173 signatures: vec![lsp::SignatureInformation {
8174 label: "fn sample(param1: u8, param2: u8)".to_string(),
8175 documentation: None,
8176 parameters: Some(vec![
8177 lsp::ParameterInformation {
8178 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8179 documentation: None,
8180 },
8181 lsp::ParameterInformation {
8182 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8183 documentation: None,
8184 },
8185 ]),
8186 active_parameter: None,
8187 }],
8188 active_signature: Some(0),
8189 active_parameter: Some(1),
8190 };
8191 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8192 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8193 .await;
8194 cx.update_editor(|editor, cx| {
8195 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
8196 });
8197 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8198 .await;
8199 cx.update_editor(|editor, cx| {
8200 editor.change_selections(None, cx, |s| {
8201 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8202 })
8203 });
8204 cx.assert_editor_state(indoc! {"
8205 fn main() {
8206 sample(param1, «ˇparam2»);
8207 }
8208
8209 fn sample(param1: u8, param2: u8) {}
8210 "});
8211 cx.update_editor(|editor, cx| {
8212 editor.change_selections(None, cx, |s| {
8213 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8214 })
8215 });
8216 cx.assert_editor_state(indoc! {"
8217 fn main() {
8218 sample(param1, ˇparam2);
8219 }
8220
8221 fn sample(param1: u8, param2: u8) {}
8222 "});
8223 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
8224 .await;
8225}
8226
8227#[gpui::test]
8228async fn test_completion(cx: &mut gpui::TestAppContext) {
8229 init_test(cx, |_| {});
8230
8231 let mut cx = EditorLspTestContext::new_rust(
8232 lsp::ServerCapabilities {
8233 completion_provider: Some(lsp::CompletionOptions {
8234 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
8235 resolve_provider: Some(true),
8236 ..Default::default()
8237 }),
8238 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
8239 ..Default::default()
8240 },
8241 cx,
8242 )
8243 .await;
8244 let counter = Arc::new(AtomicUsize::new(0));
8245
8246 cx.set_state(indoc! {"
8247 oneˇ
8248 two
8249 three
8250 "});
8251 cx.simulate_keystroke(".");
8252 handle_completion_request(
8253 &mut cx,
8254 indoc! {"
8255 one.|<>
8256 two
8257 three
8258 "},
8259 vec!["first_completion", "second_completion"],
8260 counter.clone(),
8261 )
8262 .await;
8263 cx.condition(|editor, _| editor.context_menu_visible())
8264 .await;
8265 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8266
8267 let _handler = handle_signature_help_request(
8268 &mut cx,
8269 lsp::SignatureHelp {
8270 signatures: vec![lsp::SignatureInformation {
8271 label: "test signature".to_string(),
8272 documentation: None,
8273 parameters: Some(vec![lsp::ParameterInformation {
8274 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
8275 documentation: None,
8276 }]),
8277 active_parameter: None,
8278 }],
8279 active_signature: None,
8280 active_parameter: None,
8281 },
8282 );
8283 cx.update_editor(|editor, cx| {
8284 assert!(
8285 !editor.signature_help_state.is_shown(),
8286 "No signature help was called for"
8287 );
8288 editor.show_signature_help(&ShowSignatureHelp, cx);
8289 });
8290 cx.run_until_parked();
8291 cx.update_editor(|editor, _| {
8292 assert!(
8293 !editor.signature_help_state.is_shown(),
8294 "No signature help should be shown when completions menu is open"
8295 );
8296 });
8297
8298 let apply_additional_edits = cx.update_editor(|editor, cx| {
8299 editor.context_menu_next(&Default::default(), cx);
8300 editor
8301 .confirm_completion(&ConfirmCompletion::default(), cx)
8302 .unwrap()
8303 });
8304 cx.assert_editor_state(indoc! {"
8305 one.second_completionˇ
8306 two
8307 three
8308 "});
8309
8310 handle_resolve_completion_request(
8311 &mut cx,
8312 Some(vec![
8313 (
8314 //This overlaps with the primary completion edit which is
8315 //misbehavior from the LSP spec, test that we filter it out
8316 indoc! {"
8317 one.second_ˇcompletion
8318 two
8319 threeˇ
8320 "},
8321 "overlapping additional edit",
8322 ),
8323 (
8324 indoc! {"
8325 one.second_completion
8326 two
8327 threeˇ
8328 "},
8329 "\nadditional edit",
8330 ),
8331 ]),
8332 )
8333 .await;
8334 apply_additional_edits.await.unwrap();
8335 cx.assert_editor_state(indoc! {"
8336 one.second_completionˇ
8337 two
8338 three
8339 additional edit
8340 "});
8341
8342 cx.set_state(indoc! {"
8343 one.second_completion
8344 twoˇ
8345 threeˇ
8346 additional edit
8347 "});
8348 cx.simulate_keystroke(" ");
8349 assert!(cx.editor(|e, _| e.context_menu.borrow_mut().is_none()));
8350 cx.simulate_keystroke("s");
8351 assert!(cx.editor(|e, _| e.context_menu.borrow_mut().is_none()));
8352
8353 cx.assert_editor_state(indoc! {"
8354 one.second_completion
8355 two sˇ
8356 three sˇ
8357 additional edit
8358 "});
8359 handle_completion_request(
8360 &mut cx,
8361 indoc! {"
8362 one.second_completion
8363 two s
8364 three <s|>
8365 additional edit
8366 "},
8367 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8368 counter.clone(),
8369 )
8370 .await;
8371 cx.condition(|editor, _| editor.context_menu_visible())
8372 .await;
8373 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
8374
8375 cx.simulate_keystroke("i");
8376
8377 handle_completion_request(
8378 &mut cx,
8379 indoc! {"
8380 one.second_completion
8381 two si
8382 three <si|>
8383 additional edit
8384 "},
8385 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8386 counter.clone(),
8387 )
8388 .await;
8389 cx.condition(|editor, _| editor.context_menu_visible())
8390 .await;
8391 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
8392
8393 let apply_additional_edits = cx.update_editor(|editor, cx| {
8394 editor
8395 .confirm_completion(&ConfirmCompletion::default(), cx)
8396 .unwrap()
8397 });
8398 cx.assert_editor_state(indoc! {"
8399 one.second_completion
8400 two sixth_completionˇ
8401 three sixth_completionˇ
8402 additional edit
8403 "});
8404
8405 apply_additional_edits.await.unwrap();
8406
8407 update_test_language_settings(&mut cx, |settings| {
8408 settings.defaults.show_completions_on_input = Some(false);
8409 });
8410 cx.set_state("editorˇ");
8411 cx.simulate_keystroke(".");
8412 assert!(cx.editor(|e, _| e.context_menu.borrow_mut().is_none()));
8413 cx.simulate_keystroke("c");
8414 cx.simulate_keystroke("l");
8415 cx.simulate_keystroke("o");
8416 cx.assert_editor_state("editor.cloˇ");
8417 assert!(cx.editor(|e, _| e.context_menu.borrow_mut().is_none()));
8418 cx.update_editor(|editor, cx| {
8419 editor.show_completions(&ShowCompletions { trigger: None }, cx);
8420 });
8421 handle_completion_request(
8422 &mut cx,
8423 "editor.<clo|>",
8424 vec!["close", "clobber"],
8425 counter.clone(),
8426 )
8427 .await;
8428 cx.condition(|editor, _| editor.context_menu_visible())
8429 .await;
8430 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
8431
8432 let apply_additional_edits = cx.update_editor(|editor, cx| {
8433 editor
8434 .confirm_completion(&ConfirmCompletion::default(), cx)
8435 .unwrap()
8436 });
8437 cx.assert_editor_state("editor.closeˇ");
8438 handle_resolve_completion_request(&mut cx, None).await;
8439 apply_additional_edits.await.unwrap();
8440}
8441
8442#[gpui::test]
8443async fn test_completion_page_up_down_keys(cx: &mut gpui::TestAppContext) {
8444 init_test(cx, |_| {});
8445 let mut cx = EditorLspTestContext::new_rust(
8446 lsp::ServerCapabilities {
8447 completion_provider: Some(lsp::CompletionOptions {
8448 trigger_characters: Some(vec![".".to_string()]),
8449 ..Default::default()
8450 }),
8451 ..Default::default()
8452 },
8453 cx,
8454 )
8455 .await;
8456 cx.lsp
8457 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8458 Ok(Some(lsp::CompletionResponse::Array(vec![
8459 lsp::CompletionItem {
8460 label: "first".into(),
8461 ..Default::default()
8462 },
8463 lsp::CompletionItem {
8464 label: "last".into(),
8465 ..Default::default()
8466 },
8467 ])))
8468 });
8469 cx.set_state("variableˇ");
8470 cx.simulate_keystroke(".");
8471 cx.executor().run_until_parked();
8472
8473 cx.update_editor(|editor, _| {
8474 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
8475 {
8476 assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
8477 } else {
8478 panic!("expected completion menu to be open");
8479 }
8480 });
8481
8482 cx.update_editor(|editor, cx| {
8483 editor.move_page_down(&MovePageDown::default(), cx);
8484 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
8485 {
8486 assert!(
8487 menu.selected_item == 1,
8488 "expected PageDown to select the last item from the context menu"
8489 );
8490 } else {
8491 panic!("expected completion menu to stay open after PageDown");
8492 }
8493 });
8494
8495 cx.update_editor(|editor, cx| {
8496 editor.move_page_up(&MovePageUp::default(), cx);
8497 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
8498 {
8499 assert!(
8500 menu.selected_item == 0,
8501 "expected PageUp to select the first item from the context menu"
8502 );
8503 } else {
8504 panic!("expected completion menu to stay open after PageUp");
8505 }
8506 });
8507}
8508
8509#[gpui::test]
8510async fn test_completion_sort(cx: &mut gpui::TestAppContext) {
8511 init_test(cx, |_| {});
8512 let mut cx = EditorLspTestContext::new_rust(
8513 lsp::ServerCapabilities {
8514 completion_provider: Some(lsp::CompletionOptions {
8515 trigger_characters: Some(vec![".".to_string()]),
8516 ..Default::default()
8517 }),
8518 ..Default::default()
8519 },
8520 cx,
8521 )
8522 .await;
8523 cx.lsp
8524 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8525 Ok(Some(lsp::CompletionResponse::Array(vec![
8526 lsp::CompletionItem {
8527 label: "Range".into(),
8528 sort_text: Some("a".into()),
8529 ..Default::default()
8530 },
8531 lsp::CompletionItem {
8532 label: "r".into(),
8533 sort_text: Some("b".into()),
8534 ..Default::default()
8535 },
8536 lsp::CompletionItem {
8537 label: "ret".into(),
8538 sort_text: Some("c".into()),
8539 ..Default::default()
8540 },
8541 lsp::CompletionItem {
8542 label: "return".into(),
8543 sort_text: Some("d".into()),
8544 ..Default::default()
8545 },
8546 lsp::CompletionItem {
8547 label: "slice".into(),
8548 sort_text: Some("d".into()),
8549 ..Default::default()
8550 },
8551 ])))
8552 });
8553 cx.set_state("rˇ");
8554 cx.executor().run_until_parked();
8555 cx.update_editor(|editor, cx| {
8556 editor.show_completions(
8557 &ShowCompletions {
8558 trigger: Some("r".into()),
8559 },
8560 cx,
8561 );
8562 });
8563 cx.executor().run_until_parked();
8564
8565 cx.update_editor(|editor, _| {
8566 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
8567 {
8568 assert_eq!(
8569 completion_menu_entries(&menu),
8570 &["r", "ret", "Range", "return"]
8571 );
8572 } else {
8573 panic!("expected completion menu to be open");
8574 }
8575 });
8576}
8577
8578#[gpui::test]
8579async fn test_no_duplicated_completion_requests(cx: &mut gpui::TestAppContext) {
8580 init_test(cx, |_| {});
8581
8582 let mut cx = EditorLspTestContext::new_rust(
8583 lsp::ServerCapabilities {
8584 completion_provider: Some(lsp::CompletionOptions {
8585 trigger_characters: Some(vec![".".to_string()]),
8586 resolve_provider: Some(true),
8587 ..Default::default()
8588 }),
8589 ..Default::default()
8590 },
8591 cx,
8592 )
8593 .await;
8594
8595 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
8596 cx.simulate_keystroke(".");
8597 let completion_item = lsp::CompletionItem {
8598 label: "Some".into(),
8599 kind: Some(lsp::CompletionItemKind::SNIPPET),
8600 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
8601 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
8602 kind: lsp::MarkupKind::Markdown,
8603 value: "```rust\nSome(2)\n```".to_string(),
8604 })),
8605 deprecated: Some(false),
8606 sort_text: Some("Some".to_string()),
8607 filter_text: Some("Some".to_string()),
8608 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
8609 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8610 range: lsp::Range {
8611 start: lsp::Position {
8612 line: 0,
8613 character: 22,
8614 },
8615 end: lsp::Position {
8616 line: 0,
8617 character: 22,
8618 },
8619 },
8620 new_text: "Some(2)".to_string(),
8621 })),
8622 additional_text_edits: Some(vec![lsp::TextEdit {
8623 range: lsp::Range {
8624 start: lsp::Position {
8625 line: 0,
8626 character: 20,
8627 },
8628 end: lsp::Position {
8629 line: 0,
8630 character: 22,
8631 },
8632 },
8633 new_text: "".to_string(),
8634 }]),
8635 ..Default::default()
8636 };
8637
8638 let closure_completion_item = completion_item.clone();
8639 let counter = Arc::new(AtomicUsize::new(0));
8640 let counter_clone = counter.clone();
8641 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
8642 let task_completion_item = closure_completion_item.clone();
8643 counter_clone.fetch_add(1, atomic::Ordering::Release);
8644 async move {
8645 Ok(Some(lsp::CompletionResponse::Array(vec![
8646 task_completion_item,
8647 ])))
8648 }
8649 });
8650
8651 cx.condition(|editor, _| editor.context_menu_visible())
8652 .await;
8653 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
8654 assert!(request.next().await.is_some());
8655 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8656
8657 cx.simulate_keystroke("S");
8658 cx.simulate_keystroke("o");
8659 cx.simulate_keystroke("m");
8660 cx.condition(|editor, _| editor.context_menu_visible())
8661 .await;
8662 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
8663 assert!(request.next().await.is_some());
8664 assert!(request.next().await.is_some());
8665 assert!(request.next().await.is_some());
8666 request.close();
8667 assert!(request.next().await.is_none());
8668 assert_eq!(
8669 counter.load(atomic::Ordering::Acquire),
8670 4,
8671 "With the completions menu open, only one LSP request should happen per input"
8672 );
8673}
8674
8675#[gpui::test]
8676async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
8677 init_test(cx, |_| {});
8678 let mut cx = EditorTestContext::new(cx).await;
8679 let language = Arc::new(Language::new(
8680 LanguageConfig {
8681 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
8682 ..Default::default()
8683 },
8684 Some(tree_sitter_rust::LANGUAGE.into()),
8685 ));
8686 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
8687
8688 // If multiple selections intersect a line, the line is only toggled once.
8689 cx.set_state(indoc! {"
8690 fn a() {
8691 «//b();
8692 ˇ»// «c();
8693 //ˇ» d();
8694 }
8695 "});
8696
8697 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8698
8699 cx.assert_editor_state(indoc! {"
8700 fn a() {
8701 «b();
8702 c();
8703 ˇ» d();
8704 }
8705 "});
8706
8707 // The comment prefix is inserted at the same column for every line in a
8708 // selection.
8709 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8710
8711 cx.assert_editor_state(indoc! {"
8712 fn a() {
8713 // «b();
8714 // c();
8715 ˇ»// d();
8716 }
8717 "});
8718
8719 // If a selection ends at the beginning of a line, that line is not toggled.
8720 cx.set_selections_state(indoc! {"
8721 fn a() {
8722 // b();
8723 «// c();
8724 ˇ» // d();
8725 }
8726 "});
8727
8728 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8729
8730 cx.assert_editor_state(indoc! {"
8731 fn a() {
8732 // b();
8733 «c();
8734 ˇ» // d();
8735 }
8736 "});
8737
8738 // If a selection span a single line and is empty, the line is toggled.
8739 cx.set_state(indoc! {"
8740 fn a() {
8741 a();
8742 b();
8743 ˇ
8744 }
8745 "});
8746
8747 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8748
8749 cx.assert_editor_state(indoc! {"
8750 fn a() {
8751 a();
8752 b();
8753 //•ˇ
8754 }
8755 "});
8756
8757 // If a selection span multiple lines, empty lines are not toggled.
8758 cx.set_state(indoc! {"
8759 fn a() {
8760 «a();
8761
8762 c();ˇ»
8763 }
8764 "});
8765
8766 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8767
8768 cx.assert_editor_state(indoc! {"
8769 fn a() {
8770 // «a();
8771
8772 // c();ˇ»
8773 }
8774 "});
8775
8776 // If a selection includes multiple comment prefixes, all lines are uncommented.
8777 cx.set_state(indoc! {"
8778 fn a() {
8779 «// a();
8780 /// b();
8781 //! c();ˇ»
8782 }
8783 "});
8784
8785 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8786
8787 cx.assert_editor_state(indoc! {"
8788 fn a() {
8789 «a();
8790 b();
8791 c();ˇ»
8792 }
8793 "});
8794}
8795
8796#[gpui::test]
8797async fn test_toggle_comment_ignore_indent(cx: &mut gpui::TestAppContext) {
8798 init_test(cx, |_| {});
8799 let mut cx = EditorTestContext::new(cx).await;
8800 let language = Arc::new(Language::new(
8801 LanguageConfig {
8802 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
8803 ..Default::default()
8804 },
8805 Some(tree_sitter_rust::LANGUAGE.into()),
8806 ));
8807 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
8808
8809 let toggle_comments = &ToggleComments {
8810 advance_downwards: false,
8811 ignore_indent: true,
8812 };
8813
8814 // If multiple selections intersect a line, the line is only toggled once.
8815 cx.set_state(indoc! {"
8816 fn a() {
8817 // «b();
8818 // c();
8819 // ˇ» d();
8820 }
8821 "});
8822
8823 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8824
8825 cx.assert_editor_state(indoc! {"
8826 fn a() {
8827 «b();
8828 c();
8829 ˇ» d();
8830 }
8831 "});
8832
8833 // The comment prefix is inserted at the beginning of each line
8834 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8835
8836 cx.assert_editor_state(indoc! {"
8837 fn a() {
8838 // «b();
8839 // c();
8840 // ˇ» d();
8841 }
8842 "});
8843
8844 // If a selection ends at the beginning of a line, that line is not toggled.
8845 cx.set_selections_state(indoc! {"
8846 fn a() {
8847 // b();
8848 // «c();
8849 ˇ»// d();
8850 }
8851 "});
8852
8853 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8854
8855 cx.assert_editor_state(indoc! {"
8856 fn a() {
8857 // b();
8858 «c();
8859 ˇ»// d();
8860 }
8861 "});
8862
8863 // If a selection span a single line and is empty, the line is toggled.
8864 cx.set_state(indoc! {"
8865 fn a() {
8866 a();
8867 b();
8868 ˇ
8869 }
8870 "});
8871
8872 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8873
8874 cx.assert_editor_state(indoc! {"
8875 fn a() {
8876 a();
8877 b();
8878 //ˇ
8879 }
8880 "});
8881
8882 // If a selection span multiple lines, empty lines are not toggled.
8883 cx.set_state(indoc! {"
8884 fn a() {
8885 «a();
8886
8887 c();ˇ»
8888 }
8889 "});
8890
8891 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8892
8893 cx.assert_editor_state(indoc! {"
8894 fn a() {
8895 // «a();
8896
8897 // c();ˇ»
8898 }
8899 "});
8900
8901 // If a selection includes multiple comment prefixes, all lines are uncommented.
8902 cx.set_state(indoc! {"
8903 fn a() {
8904 // «a();
8905 /// b();
8906 //! c();ˇ»
8907 }
8908 "});
8909
8910 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8911
8912 cx.assert_editor_state(indoc! {"
8913 fn a() {
8914 «a();
8915 b();
8916 c();ˇ»
8917 }
8918 "});
8919}
8920
8921#[gpui::test]
8922async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
8923 init_test(cx, |_| {});
8924
8925 let language = Arc::new(Language::new(
8926 LanguageConfig {
8927 line_comments: vec!["// ".into()],
8928 ..Default::default()
8929 },
8930 Some(tree_sitter_rust::LANGUAGE.into()),
8931 ));
8932
8933 let mut cx = EditorTestContext::new(cx).await;
8934
8935 cx.language_registry().add(language.clone());
8936 cx.update_buffer(|buffer, cx| {
8937 buffer.set_language(Some(language), cx);
8938 });
8939
8940 let toggle_comments = &ToggleComments {
8941 advance_downwards: true,
8942 ignore_indent: false,
8943 };
8944
8945 // Single cursor on one line -> advance
8946 // Cursor moves horizontally 3 characters as well on non-blank line
8947 cx.set_state(indoc!(
8948 "fn a() {
8949 ˇdog();
8950 cat();
8951 }"
8952 ));
8953 cx.update_editor(|editor, cx| {
8954 editor.toggle_comments(toggle_comments, cx);
8955 });
8956 cx.assert_editor_state(indoc!(
8957 "fn a() {
8958 // dog();
8959 catˇ();
8960 }"
8961 ));
8962
8963 // Single selection on one line -> don't advance
8964 cx.set_state(indoc!(
8965 "fn a() {
8966 «dog()ˇ»;
8967 cat();
8968 }"
8969 ));
8970 cx.update_editor(|editor, cx| {
8971 editor.toggle_comments(toggle_comments, cx);
8972 });
8973 cx.assert_editor_state(indoc!(
8974 "fn a() {
8975 // «dog()ˇ»;
8976 cat();
8977 }"
8978 ));
8979
8980 // Multiple cursors on one line -> advance
8981 cx.set_state(indoc!(
8982 "fn a() {
8983 ˇdˇog();
8984 cat();
8985 }"
8986 ));
8987 cx.update_editor(|editor, cx| {
8988 editor.toggle_comments(toggle_comments, cx);
8989 });
8990 cx.assert_editor_state(indoc!(
8991 "fn a() {
8992 // dog();
8993 catˇ(ˇ);
8994 }"
8995 ));
8996
8997 // Multiple cursors on one line, with selection -> don't advance
8998 cx.set_state(indoc!(
8999 "fn a() {
9000 ˇdˇog«()ˇ»;
9001 cat();
9002 }"
9003 ));
9004 cx.update_editor(|editor, cx| {
9005 editor.toggle_comments(toggle_comments, cx);
9006 });
9007 cx.assert_editor_state(indoc!(
9008 "fn a() {
9009 // ˇdˇog«()ˇ»;
9010 cat();
9011 }"
9012 ));
9013
9014 // Single cursor on one line -> advance
9015 // Cursor moves to column 0 on blank line
9016 cx.set_state(indoc!(
9017 "fn a() {
9018 ˇdog();
9019
9020 cat();
9021 }"
9022 ));
9023 cx.update_editor(|editor, cx| {
9024 editor.toggle_comments(toggle_comments, cx);
9025 });
9026 cx.assert_editor_state(indoc!(
9027 "fn a() {
9028 // dog();
9029 ˇ
9030 cat();
9031 }"
9032 ));
9033
9034 // Single cursor on one line -> advance
9035 // Cursor starts and ends at column 0
9036 cx.set_state(indoc!(
9037 "fn a() {
9038 ˇ dog();
9039 cat();
9040 }"
9041 ));
9042 cx.update_editor(|editor, cx| {
9043 editor.toggle_comments(toggle_comments, cx);
9044 });
9045 cx.assert_editor_state(indoc!(
9046 "fn a() {
9047 // dog();
9048 ˇ cat();
9049 }"
9050 ));
9051}
9052
9053#[gpui::test]
9054async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
9055 init_test(cx, |_| {});
9056
9057 let mut cx = EditorTestContext::new(cx).await;
9058
9059 let html_language = Arc::new(
9060 Language::new(
9061 LanguageConfig {
9062 name: "HTML".into(),
9063 block_comment: Some(("<!-- ".into(), " -->".into())),
9064 ..Default::default()
9065 },
9066 Some(tree_sitter_html::language()),
9067 )
9068 .with_injection_query(
9069 r#"
9070 (script_element
9071 (raw_text) @injection.content
9072 (#set! injection.language "javascript"))
9073 "#,
9074 )
9075 .unwrap(),
9076 );
9077
9078 let javascript_language = Arc::new(Language::new(
9079 LanguageConfig {
9080 name: "JavaScript".into(),
9081 line_comments: vec!["// ".into()],
9082 ..Default::default()
9083 },
9084 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
9085 ));
9086
9087 cx.language_registry().add(html_language.clone());
9088 cx.language_registry().add(javascript_language.clone());
9089 cx.update_buffer(|buffer, cx| {
9090 buffer.set_language(Some(html_language), cx);
9091 });
9092
9093 // Toggle comments for empty selections
9094 cx.set_state(
9095 &r#"
9096 <p>A</p>ˇ
9097 <p>B</p>ˇ
9098 <p>C</p>ˇ
9099 "#
9100 .unindent(),
9101 );
9102 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9103 cx.assert_editor_state(
9104 &r#"
9105 <!-- <p>A</p>ˇ -->
9106 <!-- <p>B</p>ˇ -->
9107 <!-- <p>C</p>ˇ -->
9108 "#
9109 .unindent(),
9110 );
9111 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9112 cx.assert_editor_state(
9113 &r#"
9114 <p>A</p>ˇ
9115 <p>B</p>ˇ
9116 <p>C</p>ˇ
9117 "#
9118 .unindent(),
9119 );
9120
9121 // Toggle comments for mixture of empty and non-empty selections, where
9122 // multiple selections occupy a given line.
9123 cx.set_state(
9124 &r#"
9125 <p>A«</p>
9126 <p>ˇ»B</p>ˇ
9127 <p>C«</p>
9128 <p>ˇ»D</p>ˇ
9129 "#
9130 .unindent(),
9131 );
9132
9133 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9134 cx.assert_editor_state(
9135 &r#"
9136 <!-- <p>A«</p>
9137 <p>ˇ»B</p>ˇ -->
9138 <!-- <p>C«</p>
9139 <p>ˇ»D</p>ˇ -->
9140 "#
9141 .unindent(),
9142 );
9143 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9144 cx.assert_editor_state(
9145 &r#"
9146 <p>A«</p>
9147 <p>ˇ»B</p>ˇ
9148 <p>C«</p>
9149 <p>ˇ»D</p>ˇ
9150 "#
9151 .unindent(),
9152 );
9153
9154 // Toggle comments when different languages are active for different
9155 // selections.
9156 cx.set_state(
9157 &r#"
9158 ˇ<script>
9159 ˇvar x = new Y();
9160 ˇ</script>
9161 "#
9162 .unindent(),
9163 );
9164 cx.executor().run_until_parked();
9165 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9166 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
9167 // Uncommenting and commenting from this position brings in even more wrong artifacts.
9168 cx.assert_editor_state(
9169 &r#"
9170 <!-- ˇ<script> -->
9171 // ˇvar x = new Y();
9172 // ˇ</script>
9173 "#
9174 .unindent(),
9175 );
9176}
9177
9178#[gpui::test]
9179fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
9180 init_test(cx, |_| {});
9181
9182 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9183 let multibuffer = cx.new_model(|cx| {
9184 let mut multibuffer = MultiBuffer::new(ReadWrite);
9185 multibuffer.push_excerpts(
9186 buffer.clone(),
9187 [
9188 ExcerptRange {
9189 context: Point::new(0, 0)..Point::new(0, 4),
9190 primary: None,
9191 },
9192 ExcerptRange {
9193 context: Point::new(1, 0)..Point::new(1, 4),
9194 primary: None,
9195 },
9196 ],
9197 cx,
9198 );
9199 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
9200 multibuffer
9201 });
9202
9203 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
9204 view.update(cx, |view, cx| {
9205 assert_eq!(view.text(cx), "aaaa\nbbbb");
9206 view.change_selections(None, cx, |s| {
9207 s.select_ranges([
9208 Point::new(0, 0)..Point::new(0, 0),
9209 Point::new(1, 0)..Point::new(1, 0),
9210 ])
9211 });
9212
9213 view.handle_input("X", cx);
9214 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
9215 assert_eq!(
9216 view.selections.ranges(cx),
9217 [
9218 Point::new(0, 1)..Point::new(0, 1),
9219 Point::new(1, 1)..Point::new(1, 1),
9220 ]
9221 );
9222
9223 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
9224 view.change_selections(None, cx, |s| {
9225 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
9226 });
9227 view.backspace(&Default::default(), cx);
9228 assert_eq!(view.text(cx), "Xa\nbbb");
9229 assert_eq!(
9230 view.selections.ranges(cx),
9231 [Point::new(1, 0)..Point::new(1, 0)]
9232 );
9233
9234 view.change_selections(None, cx, |s| {
9235 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
9236 });
9237 view.backspace(&Default::default(), cx);
9238 assert_eq!(view.text(cx), "X\nbb");
9239 assert_eq!(
9240 view.selections.ranges(cx),
9241 [Point::new(0, 1)..Point::new(0, 1)]
9242 );
9243 });
9244}
9245
9246#[gpui::test]
9247fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
9248 init_test(cx, |_| {});
9249
9250 let markers = vec![('[', ']').into(), ('(', ')').into()];
9251 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
9252 indoc! {"
9253 [aaaa
9254 (bbbb]
9255 cccc)",
9256 },
9257 markers.clone(),
9258 );
9259 let excerpt_ranges = markers.into_iter().map(|marker| {
9260 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
9261 ExcerptRange {
9262 context,
9263 primary: None,
9264 }
9265 });
9266 let buffer = cx.new_model(|cx| Buffer::local(initial_text, cx));
9267 let multibuffer = cx.new_model(|cx| {
9268 let mut multibuffer = MultiBuffer::new(ReadWrite);
9269 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
9270 multibuffer
9271 });
9272
9273 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
9274 view.update(cx, |view, cx| {
9275 let (expected_text, selection_ranges) = marked_text_ranges(
9276 indoc! {"
9277 aaaa
9278 bˇbbb
9279 bˇbbˇb
9280 cccc"
9281 },
9282 true,
9283 );
9284 assert_eq!(view.text(cx), expected_text);
9285 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
9286
9287 view.handle_input("X", cx);
9288
9289 let (expected_text, expected_selections) = marked_text_ranges(
9290 indoc! {"
9291 aaaa
9292 bXˇbbXb
9293 bXˇbbXˇb
9294 cccc"
9295 },
9296 false,
9297 );
9298 assert_eq!(view.text(cx), expected_text);
9299 assert_eq!(view.selections.ranges(cx), expected_selections);
9300
9301 view.newline(&Newline, cx);
9302 let (expected_text, expected_selections) = marked_text_ranges(
9303 indoc! {"
9304 aaaa
9305 bX
9306 ˇbbX
9307 b
9308 bX
9309 ˇbbX
9310 ˇb
9311 cccc"
9312 },
9313 false,
9314 );
9315 assert_eq!(view.text(cx), expected_text);
9316 assert_eq!(view.selections.ranges(cx), expected_selections);
9317 });
9318}
9319
9320#[gpui::test]
9321fn test_refresh_selections(cx: &mut TestAppContext) {
9322 init_test(cx, |_| {});
9323
9324 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9325 let mut excerpt1_id = None;
9326 let multibuffer = cx.new_model(|cx| {
9327 let mut multibuffer = MultiBuffer::new(ReadWrite);
9328 excerpt1_id = multibuffer
9329 .push_excerpts(
9330 buffer.clone(),
9331 [
9332 ExcerptRange {
9333 context: Point::new(0, 0)..Point::new(1, 4),
9334 primary: None,
9335 },
9336 ExcerptRange {
9337 context: Point::new(1, 0)..Point::new(2, 4),
9338 primary: None,
9339 },
9340 ],
9341 cx,
9342 )
9343 .into_iter()
9344 .next();
9345 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
9346 multibuffer
9347 });
9348
9349 let editor = cx.add_window(|cx| {
9350 let mut editor = build_editor(multibuffer.clone(), cx);
9351 let snapshot = editor.snapshot(cx);
9352 editor.change_selections(None, cx, |s| {
9353 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
9354 });
9355 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
9356 assert_eq!(
9357 editor.selections.ranges(cx),
9358 [
9359 Point::new(1, 3)..Point::new(1, 3),
9360 Point::new(2, 1)..Point::new(2, 1),
9361 ]
9362 );
9363 editor
9364 });
9365
9366 // Refreshing selections is a no-op when excerpts haven't changed.
9367 _ = editor.update(cx, |editor, cx| {
9368 editor.change_selections(None, cx, |s| s.refresh());
9369 assert_eq!(
9370 editor.selections.ranges(cx),
9371 [
9372 Point::new(1, 3)..Point::new(1, 3),
9373 Point::new(2, 1)..Point::new(2, 1),
9374 ]
9375 );
9376 });
9377
9378 multibuffer.update(cx, |multibuffer, cx| {
9379 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
9380 });
9381 _ = editor.update(cx, |editor, cx| {
9382 // Removing an excerpt causes the first selection to become degenerate.
9383 assert_eq!(
9384 editor.selections.ranges(cx),
9385 [
9386 Point::new(0, 0)..Point::new(0, 0),
9387 Point::new(0, 1)..Point::new(0, 1)
9388 ]
9389 );
9390
9391 // Refreshing selections will relocate the first selection to the original buffer
9392 // location.
9393 editor.change_selections(None, cx, |s| s.refresh());
9394 assert_eq!(
9395 editor.selections.ranges(cx),
9396 [
9397 Point::new(0, 1)..Point::new(0, 1),
9398 Point::new(0, 3)..Point::new(0, 3)
9399 ]
9400 );
9401 assert!(editor.selections.pending_anchor().is_some());
9402 });
9403}
9404
9405#[gpui::test]
9406fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
9407 init_test(cx, |_| {});
9408
9409 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9410 let mut excerpt1_id = None;
9411 let multibuffer = cx.new_model(|cx| {
9412 let mut multibuffer = MultiBuffer::new(ReadWrite);
9413 excerpt1_id = multibuffer
9414 .push_excerpts(
9415 buffer.clone(),
9416 [
9417 ExcerptRange {
9418 context: Point::new(0, 0)..Point::new(1, 4),
9419 primary: None,
9420 },
9421 ExcerptRange {
9422 context: Point::new(1, 0)..Point::new(2, 4),
9423 primary: None,
9424 },
9425 ],
9426 cx,
9427 )
9428 .into_iter()
9429 .next();
9430 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
9431 multibuffer
9432 });
9433
9434 let editor = cx.add_window(|cx| {
9435 let mut editor = build_editor(multibuffer.clone(), cx);
9436 let snapshot = editor.snapshot(cx);
9437 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
9438 assert_eq!(
9439 editor.selections.ranges(cx),
9440 [Point::new(1, 3)..Point::new(1, 3)]
9441 );
9442 editor
9443 });
9444
9445 multibuffer.update(cx, |multibuffer, cx| {
9446 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
9447 });
9448 _ = editor.update(cx, |editor, cx| {
9449 assert_eq!(
9450 editor.selections.ranges(cx),
9451 [Point::new(0, 0)..Point::new(0, 0)]
9452 );
9453
9454 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
9455 editor.change_selections(None, cx, |s| s.refresh());
9456 assert_eq!(
9457 editor.selections.ranges(cx),
9458 [Point::new(0, 3)..Point::new(0, 3)]
9459 );
9460 assert!(editor.selections.pending_anchor().is_some());
9461 });
9462}
9463
9464#[gpui::test]
9465async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
9466 init_test(cx, |_| {});
9467
9468 let language = Arc::new(
9469 Language::new(
9470 LanguageConfig {
9471 brackets: BracketPairConfig {
9472 pairs: vec![
9473 BracketPair {
9474 start: "{".to_string(),
9475 end: "}".to_string(),
9476 close: true,
9477 surround: true,
9478 newline: true,
9479 },
9480 BracketPair {
9481 start: "/* ".to_string(),
9482 end: " */".to_string(),
9483 close: true,
9484 surround: true,
9485 newline: true,
9486 },
9487 ],
9488 ..Default::default()
9489 },
9490 ..Default::default()
9491 },
9492 Some(tree_sitter_rust::LANGUAGE.into()),
9493 )
9494 .with_indents_query("")
9495 .unwrap(),
9496 );
9497
9498 let text = concat!(
9499 "{ }\n", //
9500 " x\n", //
9501 " /* */\n", //
9502 "x\n", //
9503 "{{} }\n", //
9504 );
9505
9506 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
9507 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
9508 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
9509 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
9510 .await;
9511
9512 view.update(cx, |view, cx| {
9513 view.change_selections(None, cx, |s| {
9514 s.select_display_ranges([
9515 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
9516 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
9517 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
9518 ])
9519 });
9520 view.newline(&Newline, cx);
9521
9522 assert_eq!(
9523 view.buffer().read(cx).read(cx).text(),
9524 concat!(
9525 "{ \n", // Suppress rustfmt
9526 "\n", //
9527 "}\n", //
9528 " x\n", //
9529 " /* \n", //
9530 " \n", //
9531 " */\n", //
9532 "x\n", //
9533 "{{} \n", //
9534 "}\n", //
9535 )
9536 );
9537 });
9538}
9539
9540#[gpui::test]
9541fn test_highlighted_ranges(cx: &mut TestAppContext) {
9542 init_test(cx, |_| {});
9543
9544 let editor = cx.add_window(|cx| {
9545 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
9546 build_editor(buffer.clone(), cx)
9547 });
9548
9549 _ = editor.update(cx, |editor, cx| {
9550 struct Type1;
9551 struct Type2;
9552
9553 let buffer = editor.buffer.read(cx).snapshot(cx);
9554
9555 let anchor_range =
9556 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
9557
9558 editor.highlight_background::<Type1>(
9559 &[
9560 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
9561 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
9562 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
9563 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
9564 ],
9565 |_| Hsla::red(),
9566 cx,
9567 );
9568 editor.highlight_background::<Type2>(
9569 &[
9570 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
9571 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
9572 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
9573 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
9574 ],
9575 |_| Hsla::green(),
9576 cx,
9577 );
9578
9579 let snapshot = editor.snapshot(cx);
9580 let mut highlighted_ranges = editor.background_highlights_in_range(
9581 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
9582 &snapshot,
9583 cx.theme().colors(),
9584 );
9585 // Enforce a consistent ordering based on color without relying on the ordering of the
9586 // highlight's `TypeId` which is non-executor.
9587 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
9588 assert_eq!(
9589 highlighted_ranges,
9590 &[
9591 (
9592 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
9593 Hsla::red(),
9594 ),
9595 (
9596 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9597 Hsla::red(),
9598 ),
9599 (
9600 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
9601 Hsla::green(),
9602 ),
9603 (
9604 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
9605 Hsla::green(),
9606 ),
9607 ]
9608 );
9609 assert_eq!(
9610 editor.background_highlights_in_range(
9611 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
9612 &snapshot,
9613 cx.theme().colors(),
9614 ),
9615 &[(
9616 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9617 Hsla::red(),
9618 )]
9619 );
9620 });
9621}
9622
9623#[gpui::test]
9624async fn test_following(cx: &mut gpui::TestAppContext) {
9625 init_test(cx, |_| {});
9626
9627 let fs = FakeFs::new(cx.executor());
9628 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9629
9630 let buffer = project.update(cx, |project, cx| {
9631 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
9632 cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
9633 });
9634 let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
9635 let follower = cx.update(|cx| {
9636 cx.open_window(
9637 WindowOptions {
9638 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
9639 gpui::Point::new(px(0.), px(0.)),
9640 gpui::Point::new(px(10.), px(80.)),
9641 ))),
9642 ..Default::default()
9643 },
9644 |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
9645 )
9646 .unwrap()
9647 });
9648
9649 let is_still_following = Rc::new(RefCell::new(true));
9650 let follower_edit_event_count = Rc::new(RefCell::new(0));
9651 let pending_update = Rc::new(RefCell::new(None));
9652 _ = follower.update(cx, {
9653 let update = pending_update.clone();
9654 let is_still_following = is_still_following.clone();
9655 let follower_edit_event_count = follower_edit_event_count.clone();
9656 |_, cx| {
9657 cx.subscribe(
9658 &leader.root_view(cx).unwrap(),
9659 move |_, leader, event, cx| {
9660 leader
9661 .read(cx)
9662 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9663 },
9664 )
9665 .detach();
9666
9667 cx.subscribe(
9668 &follower.root_view(cx).unwrap(),
9669 move |_, _, event: &EditorEvent, _cx| {
9670 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
9671 *is_still_following.borrow_mut() = false;
9672 }
9673
9674 if let EditorEvent::BufferEdited = event {
9675 *follower_edit_event_count.borrow_mut() += 1;
9676 }
9677 },
9678 )
9679 .detach();
9680 }
9681 });
9682
9683 // Update the selections only
9684 _ = leader.update(cx, |leader, cx| {
9685 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9686 });
9687 follower
9688 .update(cx, |follower, cx| {
9689 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9690 })
9691 .unwrap()
9692 .await
9693 .unwrap();
9694 _ = follower.update(cx, |follower, cx| {
9695 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
9696 });
9697 assert!(*is_still_following.borrow());
9698 assert_eq!(*follower_edit_event_count.borrow(), 0);
9699
9700 // Update the scroll position only
9701 _ = leader.update(cx, |leader, cx| {
9702 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9703 });
9704 follower
9705 .update(cx, |follower, cx| {
9706 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9707 })
9708 .unwrap()
9709 .await
9710 .unwrap();
9711 assert_eq!(
9712 follower
9713 .update(cx, |follower, cx| follower.scroll_position(cx))
9714 .unwrap(),
9715 gpui::Point::new(1.5, 3.5)
9716 );
9717 assert!(*is_still_following.borrow());
9718 assert_eq!(*follower_edit_event_count.borrow(), 0);
9719
9720 // Update the selections and scroll position. The follower's scroll position is updated
9721 // via autoscroll, not via the leader's exact scroll position.
9722 _ = leader.update(cx, |leader, cx| {
9723 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
9724 leader.request_autoscroll(Autoscroll::newest(), cx);
9725 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9726 });
9727 follower
9728 .update(cx, |follower, cx| {
9729 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9730 })
9731 .unwrap()
9732 .await
9733 .unwrap();
9734 _ = follower.update(cx, |follower, cx| {
9735 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
9736 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
9737 });
9738 assert!(*is_still_following.borrow());
9739
9740 // Creating a pending selection that precedes another selection
9741 _ = leader.update(cx, |leader, cx| {
9742 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9743 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, cx);
9744 });
9745 follower
9746 .update(cx, |follower, cx| {
9747 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9748 })
9749 .unwrap()
9750 .await
9751 .unwrap();
9752 _ = follower.update(cx, |follower, cx| {
9753 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
9754 });
9755 assert!(*is_still_following.borrow());
9756
9757 // Extend the pending selection so that it surrounds another selection
9758 _ = leader.update(cx, |leader, cx| {
9759 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, cx);
9760 });
9761 follower
9762 .update(cx, |follower, cx| {
9763 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9764 })
9765 .unwrap()
9766 .await
9767 .unwrap();
9768 _ = follower.update(cx, |follower, cx| {
9769 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
9770 });
9771
9772 // Scrolling locally breaks the follow
9773 _ = follower.update(cx, |follower, cx| {
9774 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
9775 follower.set_scroll_anchor(
9776 ScrollAnchor {
9777 anchor: top_anchor,
9778 offset: gpui::Point::new(0.0, 0.5),
9779 },
9780 cx,
9781 );
9782 });
9783 assert!(!(*is_still_following.borrow()));
9784}
9785
9786#[gpui::test]
9787async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
9788 init_test(cx, |_| {});
9789
9790 let fs = FakeFs::new(cx.executor());
9791 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9792 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9793 let pane = workspace
9794 .update(cx, |workspace, _| workspace.active_pane().clone())
9795 .unwrap();
9796
9797 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
9798
9799 let leader = pane.update(cx, |_, cx| {
9800 let multibuffer = cx.new_model(|_| MultiBuffer::new(ReadWrite));
9801 cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
9802 });
9803
9804 // Start following the editor when it has no excerpts.
9805 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9806 let follower_1 = cx
9807 .update_window(*workspace.deref(), |_, cx| {
9808 Editor::from_state_proto(
9809 workspace.root_view(cx).unwrap(),
9810 ViewId {
9811 creator: Default::default(),
9812 id: 0,
9813 },
9814 &mut state_message,
9815 cx,
9816 )
9817 })
9818 .unwrap()
9819 .unwrap()
9820 .await
9821 .unwrap();
9822
9823 let update_message = Rc::new(RefCell::new(None));
9824 follower_1.update(cx, {
9825 let update = update_message.clone();
9826 |_, cx| {
9827 cx.subscribe(&leader, move |_, leader, event, cx| {
9828 leader
9829 .read(cx)
9830 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9831 })
9832 .detach();
9833 }
9834 });
9835
9836 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
9837 (
9838 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
9839 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
9840 )
9841 });
9842
9843 // Insert some excerpts.
9844 leader.update(cx, |leader, cx| {
9845 leader.buffer.update(cx, |multibuffer, cx| {
9846 let excerpt_ids = multibuffer.push_excerpts(
9847 buffer_1.clone(),
9848 [
9849 ExcerptRange {
9850 context: 1..6,
9851 primary: None,
9852 },
9853 ExcerptRange {
9854 context: 12..15,
9855 primary: None,
9856 },
9857 ExcerptRange {
9858 context: 0..3,
9859 primary: None,
9860 },
9861 ],
9862 cx,
9863 );
9864 multibuffer.insert_excerpts_after(
9865 excerpt_ids[0],
9866 buffer_2.clone(),
9867 [
9868 ExcerptRange {
9869 context: 8..12,
9870 primary: None,
9871 },
9872 ExcerptRange {
9873 context: 0..6,
9874 primary: None,
9875 },
9876 ],
9877 cx,
9878 );
9879 });
9880 });
9881
9882 // Apply the update of adding the excerpts.
9883 follower_1
9884 .update(cx, |follower, cx| {
9885 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9886 })
9887 .await
9888 .unwrap();
9889 assert_eq!(
9890 follower_1.update(cx, |editor, cx| editor.text(cx)),
9891 leader.update(cx, |editor, cx| editor.text(cx))
9892 );
9893 update_message.borrow_mut().take();
9894
9895 // Start following separately after it already has excerpts.
9896 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9897 let follower_2 = cx
9898 .update_window(*workspace.deref(), |_, cx| {
9899 Editor::from_state_proto(
9900 workspace.root_view(cx).unwrap().clone(),
9901 ViewId {
9902 creator: Default::default(),
9903 id: 0,
9904 },
9905 &mut state_message,
9906 cx,
9907 )
9908 })
9909 .unwrap()
9910 .unwrap()
9911 .await
9912 .unwrap();
9913 assert_eq!(
9914 follower_2.update(cx, |editor, cx| editor.text(cx)),
9915 leader.update(cx, |editor, cx| editor.text(cx))
9916 );
9917
9918 // Remove some excerpts.
9919 leader.update(cx, |leader, cx| {
9920 leader.buffer.update(cx, |multibuffer, cx| {
9921 let excerpt_ids = multibuffer.excerpt_ids();
9922 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
9923 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
9924 });
9925 });
9926
9927 // Apply the update of removing the excerpts.
9928 follower_1
9929 .update(cx, |follower, cx| {
9930 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9931 })
9932 .await
9933 .unwrap();
9934 follower_2
9935 .update(cx, |follower, cx| {
9936 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9937 })
9938 .await
9939 .unwrap();
9940 update_message.borrow_mut().take();
9941 assert_eq!(
9942 follower_1.update(cx, |editor, cx| editor.text(cx)),
9943 leader.update(cx, |editor, cx| editor.text(cx))
9944 );
9945}
9946
9947#[gpui::test]
9948async fn go_to_prev_overlapping_diagnostic(
9949 executor: BackgroundExecutor,
9950 cx: &mut gpui::TestAppContext,
9951) {
9952 init_test(cx, |_| {});
9953
9954 let mut cx = EditorTestContext::new(cx).await;
9955 let lsp_store =
9956 cx.update_editor(|editor, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
9957
9958 cx.set_state(indoc! {"
9959 ˇfn func(abc def: i32) -> u32 {
9960 }
9961 "});
9962
9963 cx.update(|cx| {
9964 lsp_store.update(cx, |lsp_store, cx| {
9965 lsp_store
9966 .update_diagnostics(
9967 LanguageServerId(0),
9968 lsp::PublishDiagnosticsParams {
9969 uri: lsp::Url::from_file_path("/root/file").unwrap(),
9970 version: None,
9971 diagnostics: vec![
9972 lsp::Diagnostic {
9973 range: lsp::Range::new(
9974 lsp::Position::new(0, 11),
9975 lsp::Position::new(0, 12),
9976 ),
9977 severity: Some(lsp::DiagnosticSeverity::ERROR),
9978 ..Default::default()
9979 },
9980 lsp::Diagnostic {
9981 range: lsp::Range::new(
9982 lsp::Position::new(0, 12),
9983 lsp::Position::new(0, 15),
9984 ),
9985 severity: Some(lsp::DiagnosticSeverity::ERROR),
9986 ..Default::default()
9987 },
9988 lsp::Diagnostic {
9989 range: lsp::Range::new(
9990 lsp::Position::new(0, 25),
9991 lsp::Position::new(0, 28),
9992 ),
9993 severity: Some(lsp::DiagnosticSeverity::ERROR),
9994 ..Default::default()
9995 },
9996 ],
9997 },
9998 &[],
9999 cx,
10000 )
10001 .unwrap()
10002 });
10003 });
10004
10005 executor.run_until_parked();
10006
10007 cx.update_editor(|editor, cx| {
10008 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
10009 });
10010
10011 cx.assert_editor_state(indoc! {"
10012 fn func(abc def: i32) -> ˇu32 {
10013 }
10014 "});
10015
10016 cx.update_editor(|editor, cx| {
10017 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
10018 });
10019
10020 cx.assert_editor_state(indoc! {"
10021 fn func(abc ˇdef: i32) -> u32 {
10022 }
10023 "});
10024
10025 cx.update_editor(|editor, cx| {
10026 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
10027 });
10028
10029 cx.assert_editor_state(indoc! {"
10030 fn func(abcˇ def: i32) -> u32 {
10031 }
10032 "});
10033
10034 cx.update_editor(|editor, cx| {
10035 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
10036 });
10037
10038 cx.assert_editor_state(indoc! {"
10039 fn func(abc def: i32) -> ˇu32 {
10040 }
10041 "});
10042}
10043
10044#[gpui::test]
10045async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
10046 init_test(cx, |_| {});
10047
10048 let mut cx = EditorTestContext::new(cx).await;
10049
10050 cx.set_state(indoc! {"
10051 fn func(abˇc def: i32) -> u32 {
10052 }
10053 "});
10054 let lsp_store =
10055 cx.update_editor(|editor, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
10056
10057 cx.update(|cx| {
10058 lsp_store.update(cx, |lsp_store, cx| {
10059 lsp_store.update_diagnostics(
10060 LanguageServerId(0),
10061 lsp::PublishDiagnosticsParams {
10062 uri: lsp::Url::from_file_path("/root/file").unwrap(),
10063 version: None,
10064 diagnostics: vec![lsp::Diagnostic {
10065 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
10066 severity: Some(lsp::DiagnosticSeverity::ERROR),
10067 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
10068 ..Default::default()
10069 }],
10070 },
10071 &[],
10072 cx,
10073 )
10074 })
10075 }).unwrap();
10076 cx.run_until_parked();
10077 cx.update_editor(|editor, cx| hover_popover::hover(editor, &Default::default(), cx));
10078 cx.run_until_parked();
10079 cx.update_editor(|editor, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
10080}
10081
10082#[gpui::test]
10083async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
10084 init_test(cx, |_| {});
10085
10086 let mut cx = EditorTestContext::new(cx).await;
10087
10088 let diff_base = r#"
10089 use some::mod;
10090
10091 const A: u32 = 42;
10092
10093 fn main() {
10094 println!("hello");
10095
10096 println!("world");
10097 }
10098 "#
10099 .unindent();
10100
10101 // Edits are modified, removed, modified, added
10102 cx.set_state(
10103 &r#"
10104 use some::modified;
10105
10106 ˇ
10107 fn main() {
10108 println!("hello there");
10109
10110 println!("around the");
10111 println!("world");
10112 }
10113 "#
10114 .unindent(),
10115 );
10116
10117 cx.set_diff_base(&diff_base);
10118 executor.run_until_parked();
10119
10120 cx.update_editor(|editor, cx| {
10121 //Wrap around the bottom of the buffer
10122 for _ in 0..3 {
10123 editor.go_to_next_hunk(&GoToHunk, cx);
10124 }
10125 });
10126
10127 cx.assert_editor_state(
10128 &r#"
10129 ˇuse some::modified;
10130
10131
10132 fn main() {
10133 println!("hello there");
10134
10135 println!("around the");
10136 println!("world");
10137 }
10138 "#
10139 .unindent(),
10140 );
10141
10142 cx.update_editor(|editor, cx| {
10143 //Wrap around the top of the buffer
10144 for _ in 0..2 {
10145 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10146 }
10147 });
10148
10149 cx.assert_editor_state(
10150 &r#"
10151 use some::modified;
10152
10153
10154 fn main() {
10155 ˇ println!("hello there");
10156
10157 println!("around the");
10158 println!("world");
10159 }
10160 "#
10161 .unindent(),
10162 );
10163
10164 cx.update_editor(|editor, cx| {
10165 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10166 });
10167
10168 cx.assert_editor_state(
10169 &r#"
10170 use some::modified;
10171
10172 ˇ
10173 fn main() {
10174 println!("hello there");
10175
10176 println!("around the");
10177 println!("world");
10178 }
10179 "#
10180 .unindent(),
10181 );
10182
10183 cx.update_editor(|editor, cx| {
10184 for _ in 0..3 {
10185 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10186 }
10187 });
10188
10189 cx.assert_editor_state(
10190 &r#"
10191 use some::modified;
10192
10193
10194 fn main() {
10195 ˇ println!("hello there");
10196
10197 println!("around the");
10198 println!("world");
10199 }
10200 "#
10201 .unindent(),
10202 );
10203
10204 cx.update_editor(|editor, cx| {
10205 editor.fold(&Fold, cx);
10206
10207 //Make sure that the fold only gets one hunk
10208 for _ in 0..4 {
10209 editor.go_to_next_hunk(&GoToHunk, cx);
10210 }
10211 });
10212
10213 cx.assert_editor_state(
10214 &r#"
10215 ˇuse some::modified;
10216
10217
10218 fn main() {
10219 println!("hello there");
10220
10221 println!("around the");
10222 println!("world");
10223 }
10224 "#
10225 .unindent(),
10226 );
10227}
10228
10229#[test]
10230fn test_split_words() {
10231 fn split(text: &str) -> Vec<&str> {
10232 split_words(text).collect()
10233 }
10234
10235 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
10236 assert_eq!(split("hello_world"), &["hello_", "world"]);
10237 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
10238 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
10239 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
10240 assert_eq!(split("helloworld"), &["helloworld"]);
10241
10242 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
10243}
10244
10245#[gpui::test]
10246async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
10247 init_test(cx, |_| {});
10248
10249 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
10250 let mut assert = |before, after| {
10251 let _state_context = cx.set_state(before);
10252 cx.update_editor(|editor, cx| {
10253 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
10254 });
10255 cx.assert_editor_state(after);
10256 };
10257
10258 // Outside bracket jumps to outside of matching bracket
10259 assert("console.logˇ(var);", "console.log(var)ˇ;");
10260 assert("console.log(var)ˇ;", "console.logˇ(var);");
10261
10262 // Inside bracket jumps to inside of matching bracket
10263 assert("console.log(ˇvar);", "console.log(varˇ);");
10264 assert("console.log(varˇ);", "console.log(ˇvar);");
10265
10266 // When outside a bracket and inside, favor jumping to the inside bracket
10267 assert(
10268 "console.log('foo', [1, 2, 3]ˇ);",
10269 "console.log(ˇ'foo', [1, 2, 3]);",
10270 );
10271 assert(
10272 "console.log(ˇ'foo', [1, 2, 3]);",
10273 "console.log('foo', [1, 2, 3]ˇ);",
10274 );
10275
10276 // Bias forward if two options are equally likely
10277 assert(
10278 "let result = curried_fun()ˇ();",
10279 "let result = curried_fun()()ˇ;",
10280 );
10281
10282 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
10283 assert(
10284 indoc! {"
10285 function test() {
10286 console.log('test')ˇ
10287 }"},
10288 indoc! {"
10289 function test() {
10290 console.logˇ('test')
10291 }"},
10292 );
10293}
10294
10295#[gpui::test]
10296async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
10297 init_test(cx, |_| {});
10298
10299 let fs = FakeFs::new(cx.executor());
10300 fs.insert_tree(
10301 "/a",
10302 json!({
10303 "main.rs": "fn main() { let a = 5; }",
10304 "other.rs": "// Test file",
10305 }),
10306 )
10307 .await;
10308 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10309
10310 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10311 language_registry.add(Arc::new(Language::new(
10312 LanguageConfig {
10313 name: "Rust".into(),
10314 matcher: LanguageMatcher {
10315 path_suffixes: vec!["rs".to_string()],
10316 ..Default::default()
10317 },
10318 brackets: BracketPairConfig {
10319 pairs: vec![BracketPair {
10320 start: "{".to_string(),
10321 end: "}".to_string(),
10322 close: true,
10323 surround: true,
10324 newline: true,
10325 }],
10326 disabled_scopes_by_bracket_ix: Vec::new(),
10327 },
10328 ..Default::default()
10329 },
10330 Some(tree_sitter_rust::LANGUAGE.into()),
10331 )));
10332 let mut fake_servers = language_registry.register_fake_lsp(
10333 "Rust",
10334 FakeLspAdapter {
10335 capabilities: lsp::ServerCapabilities {
10336 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
10337 first_trigger_character: "{".to_string(),
10338 more_trigger_character: None,
10339 }),
10340 ..Default::default()
10341 },
10342 ..Default::default()
10343 },
10344 );
10345
10346 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10347
10348 let cx = &mut VisualTestContext::from_window(*workspace, cx);
10349
10350 let worktree_id = workspace
10351 .update(cx, |workspace, cx| {
10352 workspace.project().update(cx, |project, cx| {
10353 project.worktrees(cx).next().unwrap().read(cx).id()
10354 })
10355 })
10356 .unwrap();
10357
10358 let buffer = project
10359 .update(cx, |project, cx| {
10360 project.open_local_buffer("/a/main.rs", cx)
10361 })
10362 .await
10363 .unwrap();
10364 let editor_handle = workspace
10365 .update(cx, |workspace, cx| {
10366 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
10367 })
10368 .unwrap()
10369 .await
10370 .unwrap()
10371 .downcast::<Editor>()
10372 .unwrap();
10373
10374 cx.executor().start_waiting();
10375 let fake_server = fake_servers.next().await.unwrap();
10376
10377 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
10378 assert_eq!(
10379 params.text_document_position.text_document.uri,
10380 lsp::Url::from_file_path("/a/main.rs").unwrap(),
10381 );
10382 assert_eq!(
10383 params.text_document_position.position,
10384 lsp::Position::new(0, 21),
10385 );
10386
10387 Ok(Some(vec![lsp::TextEdit {
10388 new_text: "]".to_string(),
10389 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10390 }]))
10391 });
10392
10393 editor_handle.update(cx, |editor, cx| {
10394 editor.focus(cx);
10395 editor.change_selections(None, cx, |s| {
10396 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
10397 });
10398 editor.handle_input("{", cx);
10399 });
10400
10401 cx.executor().run_until_parked();
10402
10403 buffer.update(cx, |buffer, _| {
10404 assert_eq!(
10405 buffer.text(),
10406 "fn main() { let a = {5}; }",
10407 "No extra braces from on type formatting should appear in the buffer"
10408 )
10409 });
10410}
10411
10412#[gpui::test]
10413async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
10414 init_test(cx, |_| {});
10415
10416 let fs = FakeFs::new(cx.executor());
10417 fs.insert_tree(
10418 "/a",
10419 json!({
10420 "main.rs": "fn main() { let a = 5; }",
10421 "other.rs": "// Test file",
10422 }),
10423 )
10424 .await;
10425
10426 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10427
10428 let server_restarts = Arc::new(AtomicUsize::new(0));
10429 let closure_restarts = Arc::clone(&server_restarts);
10430 let language_server_name = "test language server";
10431 let language_name: LanguageName = "Rust".into();
10432
10433 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10434 language_registry.add(Arc::new(Language::new(
10435 LanguageConfig {
10436 name: language_name.clone(),
10437 matcher: LanguageMatcher {
10438 path_suffixes: vec!["rs".to_string()],
10439 ..Default::default()
10440 },
10441 ..Default::default()
10442 },
10443 Some(tree_sitter_rust::LANGUAGE.into()),
10444 )));
10445 let mut fake_servers = language_registry.register_fake_lsp(
10446 "Rust",
10447 FakeLspAdapter {
10448 name: language_server_name,
10449 initialization_options: Some(json!({
10450 "testOptionValue": true
10451 })),
10452 initializer: Some(Box::new(move |fake_server| {
10453 let task_restarts = Arc::clone(&closure_restarts);
10454 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
10455 task_restarts.fetch_add(1, atomic::Ordering::Release);
10456 futures::future::ready(Ok(()))
10457 });
10458 })),
10459 ..Default::default()
10460 },
10461 );
10462
10463 let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10464 let _buffer = project
10465 .update(cx, |project, cx| {
10466 project.open_local_buffer_with_lsp("/a/main.rs", cx)
10467 })
10468 .await
10469 .unwrap();
10470 let _fake_server = fake_servers.next().await.unwrap();
10471 update_test_language_settings(cx, |language_settings| {
10472 language_settings.languages.insert(
10473 language_name.clone(),
10474 LanguageSettingsContent {
10475 tab_size: NonZeroU32::new(8),
10476 ..Default::default()
10477 },
10478 );
10479 });
10480 cx.executor().run_until_parked();
10481 assert_eq!(
10482 server_restarts.load(atomic::Ordering::Acquire),
10483 0,
10484 "Should not restart LSP server on an unrelated change"
10485 );
10486
10487 update_test_project_settings(cx, |project_settings| {
10488 project_settings.lsp.insert(
10489 "Some other server name".into(),
10490 LspSettings {
10491 binary: None,
10492 settings: None,
10493 initialization_options: Some(json!({
10494 "some other init value": false
10495 })),
10496 },
10497 );
10498 });
10499 cx.executor().run_until_parked();
10500 assert_eq!(
10501 server_restarts.load(atomic::Ordering::Acquire),
10502 0,
10503 "Should not restart LSP server on an unrelated LSP settings change"
10504 );
10505
10506 update_test_project_settings(cx, |project_settings| {
10507 project_settings.lsp.insert(
10508 language_server_name.into(),
10509 LspSettings {
10510 binary: None,
10511 settings: None,
10512 initialization_options: Some(json!({
10513 "anotherInitValue": false
10514 })),
10515 },
10516 );
10517 });
10518 cx.executor().run_until_parked();
10519 assert_eq!(
10520 server_restarts.load(atomic::Ordering::Acquire),
10521 1,
10522 "Should restart LSP server on a related LSP settings change"
10523 );
10524
10525 update_test_project_settings(cx, |project_settings| {
10526 project_settings.lsp.insert(
10527 language_server_name.into(),
10528 LspSettings {
10529 binary: None,
10530 settings: None,
10531 initialization_options: Some(json!({
10532 "anotherInitValue": false
10533 })),
10534 },
10535 );
10536 });
10537 cx.executor().run_until_parked();
10538 assert_eq!(
10539 server_restarts.load(atomic::Ordering::Acquire),
10540 1,
10541 "Should not restart LSP server on a related LSP settings change that is the same"
10542 );
10543
10544 update_test_project_settings(cx, |project_settings| {
10545 project_settings.lsp.insert(
10546 language_server_name.into(),
10547 LspSettings {
10548 binary: None,
10549 settings: None,
10550 initialization_options: None,
10551 },
10552 );
10553 });
10554 cx.executor().run_until_parked();
10555 assert_eq!(
10556 server_restarts.load(atomic::Ordering::Acquire),
10557 2,
10558 "Should restart LSP server on another related LSP settings change"
10559 );
10560}
10561
10562#[gpui::test]
10563async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
10564 init_test(cx, |_| {});
10565
10566 let mut cx = EditorLspTestContext::new_rust(
10567 lsp::ServerCapabilities {
10568 completion_provider: Some(lsp::CompletionOptions {
10569 trigger_characters: Some(vec![".".to_string()]),
10570 resolve_provider: Some(true),
10571 ..Default::default()
10572 }),
10573 ..Default::default()
10574 },
10575 cx,
10576 )
10577 .await;
10578
10579 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10580 cx.simulate_keystroke(".");
10581 let completion_item = lsp::CompletionItem {
10582 label: "some".into(),
10583 kind: Some(lsp::CompletionItemKind::SNIPPET),
10584 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
10585 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
10586 kind: lsp::MarkupKind::Markdown,
10587 value: "```rust\nSome(2)\n```".to_string(),
10588 })),
10589 deprecated: Some(false),
10590 sort_text: Some("fffffff2".to_string()),
10591 filter_text: Some("some".to_string()),
10592 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
10593 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10594 range: lsp::Range {
10595 start: lsp::Position {
10596 line: 0,
10597 character: 22,
10598 },
10599 end: lsp::Position {
10600 line: 0,
10601 character: 22,
10602 },
10603 },
10604 new_text: "Some(2)".to_string(),
10605 })),
10606 additional_text_edits: Some(vec![lsp::TextEdit {
10607 range: lsp::Range {
10608 start: lsp::Position {
10609 line: 0,
10610 character: 20,
10611 },
10612 end: lsp::Position {
10613 line: 0,
10614 character: 22,
10615 },
10616 },
10617 new_text: "".to_string(),
10618 }]),
10619 ..Default::default()
10620 };
10621
10622 let closure_completion_item = completion_item.clone();
10623 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10624 let task_completion_item = closure_completion_item.clone();
10625 async move {
10626 Ok(Some(lsp::CompletionResponse::Array(vec![
10627 task_completion_item,
10628 ])))
10629 }
10630 });
10631
10632 request.next().await;
10633
10634 cx.condition(|editor, _| editor.context_menu_visible())
10635 .await;
10636 let apply_additional_edits = cx.update_editor(|editor, cx| {
10637 editor
10638 .confirm_completion(&ConfirmCompletion::default(), cx)
10639 .unwrap()
10640 });
10641 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
10642
10643 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
10644 let task_completion_item = completion_item.clone();
10645 async move { Ok(task_completion_item) }
10646 })
10647 .next()
10648 .await
10649 .unwrap();
10650 apply_additional_edits.await.unwrap();
10651 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
10652}
10653
10654#[gpui::test]
10655async fn test_completions_resolve_updates_labels_if_filter_text_matches(
10656 cx: &mut gpui::TestAppContext,
10657) {
10658 init_test(cx, |_| {});
10659
10660 let mut cx = EditorLspTestContext::new_rust(
10661 lsp::ServerCapabilities {
10662 completion_provider: Some(lsp::CompletionOptions {
10663 trigger_characters: Some(vec![".".to_string()]),
10664 resolve_provider: Some(true),
10665 ..Default::default()
10666 }),
10667 ..Default::default()
10668 },
10669 cx,
10670 )
10671 .await;
10672
10673 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10674 cx.simulate_keystroke(".");
10675
10676 let item1 = lsp::CompletionItem {
10677 label: "id".to_string(),
10678 filter_text: Some("id".to_string()),
10679 detail: None,
10680 documentation: None,
10681 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10682 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10683 new_text: ".id".to_string(),
10684 })),
10685 ..lsp::CompletionItem::default()
10686 };
10687
10688 let item2 = lsp::CompletionItem {
10689 label: "other".to_string(),
10690 filter_text: Some("other".to_string()),
10691 detail: None,
10692 documentation: None,
10693 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10694 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10695 new_text: ".other".to_string(),
10696 })),
10697 ..lsp::CompletionItem::default()
10698 };
10699
10700 let item1 = item1.clone();
10701 cx.handle_request::<lsp::request::Completion, _, _>({
10702 let item1 = item1.clone();
10703 move |_, _, _| {
10704 let item1 = item1.clone();
10705 let item2 = item2.clone();
10706 async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
10707 }
10708 })
10709 .next()
10710 .await;
10711
10712 cx.condition(|editor, _| editor.context_menu_visible())
10713 .await;
10714 cx.update_editor(|editor, _| {
10715 let context_menu = editor.context_menu.borrow_mut();
10716 let context_menu = context_menu
10717 .as_ref()
10718 .expect("Should have the context menu deployed");
10719 match context_menu {
10720 CodeContextMenu::Completions(completions_menu) => {
10721 let completions = completions_menu.completions.borrow_mut();
10722 assert_eq!(
10723 completions
10724 .iter()
10725 .map(|completion| &completion.label.text)
10726 .collect::<Vec<_>>(),
10727 vec!["id", "other"]
10728 )
10729 }
10730 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
10731 }
10732 });
10733
10734 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>({
10735 let item1 = item1.clone();
10736 move |_, item_to_resolve, _| {
10737 let item1 = item1.clone();
10738 async move {
10739 if item1 == item_to_resolve {
10740 Ok(lsp::CompletionItem {
10741 label: "method id()".to_string(),
10742 filter_text: Some("id".to_string()),
10743 detail: Some("Now resolved!".to_string()),
10744 documentation: Some(lsp::Documentation::String("Docs".to_string())),
10745 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10746 range: lsp::Range::new(
10747 lsp::Position::new(0, 22),
10748 lsp::Position::new(0, 22),
10749 ),
10750 new_text: ".id".to_string(),
10751 })),
10752 ..lsp::CompletionItem::default()
10753 })
10754 } else {
10755 Ok(item_to_resolve)
10756 }
10757 }
10758 }
10759 })
10760 .next()
10761 .await
10762 .unwrap();
10763 cx.run_until_parked();
10764
10765 cx.update_editor(|editor, cx| {
10766 editor.context_menu_next(&Default::default(), cx);
10767 });
10768
10769 cx.update_editor(|editor, _| {
10770 let context_menu = editor.context_menu.borrow_mut();
10771 let context_menu = context_menu
10772 .as_ref()
10773 .expect("Should have the context menu deployed");
10774 match context_menu {
10775 CodeContextMenu::Completions(completions_menu) => {
10776 let completions = completions_menu.completions.borrow_mut();
10777 assert_eq!(
10778 completions
10779 .iter()
10780 .map(|completion| &completion.label.text)
10781 .collect::<Vec<_>>(),
10782 vec!["method id()", "other"],
10783 "Should update first completion label, but not second as the filter text did not match."
10784 );
10785 }
10786 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
10787 }
10788 });
10789}
10790
10791#[gpui::test]
10792async fn test_completions_resolve_happens_once(cx: &mut gpui::TestAppContext) {
10793 init_test(cx, |_| {});
10794
10795 let mut cx = EditorLspTestContext::new_rust(
10796 lsp::ServerCapabilities {
10797 completion_provider: Some(lsp::CompletionOptions {
10798 trigger_characters: Some(vec![".".to_string()]),
10799 resolve_provider: Some(true),
10800 ..Default::default()
10801 }),
10802 ..Default::default()
10803 },
10804 cx,
10805 )
10806 .await;
10807
10808 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10809 cx.simulate_keystroke(".");
10810
10811 let unresolved_item_1 = lsp::CompletionItem {
10812 label: "id".to_string(),
10813 filter_text: Some("id".to_string()),
10814 detail: None,
10815 documentation: None,
10816 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10817 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10818 new_text: ".id".to_string(),
10819 })),
10820 ..lsp::CompletionItem::default()
10821 };
10822 let resolved_item_1 = lsp::CompletionItem {
10823 additional_text_edits: Some(vec![lsp::TextEdit {
10824 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
10825 new_text: "!!".to_string(),
10826 }]),
10827 ..unresolved_item_1.clone()
10828 };
10829 let unresolved_item_2 = lsp::CompletionItem {
10830 label: "other".to_string(),
10831 filter_text: Some("other".to_string()),
10832 detail: None,
10833 documentation: None,
10834 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10835 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10836 new_text: ".other".to_string(),
10837 })),
10838 ..lsp::CompletionItem::default()
10839 };
10840 let resolved_item_2 = lsp::CompletionItem {
10841 additional_text_edits: Some(vec![lsp::TextEdit {
10842 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
10843 new_text: "??".to_string(),
10844 }]),
10845 ..unresolved_item_2.clone()
10846 };
10847
10848 let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
10849 let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
10850 cx.lsp
10851 .server
10852 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
10853 let unresolved_item_1 = unresolved_item_1.clone();
10854 let resolved_item_1 = resolved_item_1.clone();
10855 let unresolved_item_2 = unresolved_item_2.clone();
10856 let resolved_item_2 = resolved_item_2.clone();
10857 let resolve_requests_1 = resolve_requests_1.clone();
10858 let resolve_requests_2 = resolve_requests_2.clone();
10859 move |unresolved_request, _| {
10860 let unresolved_item_1 = unresolved_item_1.clone();
10861 let resolved_item_1 = resolved_item_1.clone();
10862 let unresolved_item_2 = unresolved_item_2.clone();
10863 let resolved_item_2 = resolved_item_2.clone();
10864 let resolve_requests_1 = resolve_requests_1.clone();
10865 let resolve_requests_2 = resolve_requests_2.clone();
10866 async move {
10867 if unresolved_request == unresolved_item_1 {
10868 resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
10869 Ok(resolved_item_1.clone())
10870 } else if unresolved_request == unresolved_item_2 {
10871 resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
10872 Ok(resolved_item_2.clone())
10873 } else {
10874 panic!("Unexpected completion item {unresolved_request:?}")
10875 }
10876 }
10877 }
10878 })
10879 .detach();
10880
10881 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10882 let unresolved_item_1 = unresolved_item_1.clone();
10883 let unresolved_item_2 = unresolved_item_2.clone();
10884 async move {
10885 Ok(Some(lsp::CompletionResponse::Array(vec![
10886 unresolved_item_1,
10887 unresolved_item_2,
10888 ])))
10889 }
10890 })
10891 .next()
10892 .await;
10893
10894 cx.condition(|editor, _| editor.context_menu_visible())
10895 .await;
10896 cx.update_editor(|editor, _| {
10897 let context_menu = editor.context_menu.borrow_mut();
10898 let context_menu = context_menu
10899 .as_ref()
10900 .expect("Should have the context menu deployed");
10901 match context_menu {
10902 CodeContextMenu::Completions(completions_menu) => {
10903 let completions = completions_menu.completions.borrow_mut();
10904 assert_eq!(
10905 completions
10906 .iter()
10907 .map(|completion| &completion.label.text)
10908 .collect::<Vec<_>>(),
10909 vec!["id", "other"]
10910 )
10911 }
10912 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
10913 }
10914 });
10915 cx.run_until_parked();
10916
10917 cx.update_editor(|editor, cx| {
10918 editor.context_menu_next(&ContextMenuNext, cx);
10919 });
10920 cx.run_until_parked();
10921 cx.update_editor(|editor, cx| {
10922 editor.context_menu_prev(&ContextMenuPrev, cx);
10923 });
10924 cx.run_until_parked();
10925 cx.update_editor(|editor, cx| {
10926 editor.context_menu_next(&ContextMenuNext, cx);
10927 });
10928 cx.run_until_parked();
10929 cx.update_editor(|editor, cx| {
10930 editor
10931 .compose_completion(&ComposeCompletion::default(), cx)
10932 .expect("No task returned")
10933 })
10934 .await
10935 .expect("Completion failed");
10936 cx.run_until_parked();
10937
10938 cx.update_editor(|editor, cx| {
10939 assert_eq!(
10940 resolve_requests_1.load(atomic::Ordering::Acquire),
10941 1,
10942 "Should always resolve once despite multiple selections"
10943 );
10944 assert_eq!(
10945 resolve_requests_2.load(atomic::Ordering::Acquire),
10946 1,
10947 "Should always resolve once after multiple selections and applying the completion"
10948 );
10949 assert_eq!(
10950 editor.text(cx),
10951 "fn main() { let a = ??.other; }",
10952 "Should use resolved data when applying the completion"
10953 );
10954 });
10955}
10956
10957#[gpui::test]
10958async fn test_completions_default_resolve_data_handling(cx: &mut gpui::TestAppContext) {
10959 init_test(cx, |_| {});
10960
10961 let item_0 = lsp::CompletionItem {
10962 label: "abs".into(),
10963 insert_text: Some("abs".into()),
10964 data: Some(json!({ "very": "special"})),
10965 insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
10966 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
10967 lsp::InsertReplaceEdit {
10968 new_text: "abs".to_string(),
10969 insert: lsp::Range::default(),
10970 replace: lsp::Range::default(),
10971 },
10972 )),
10973 ..lsp::CompletionItem::default()
10974 };
10975 let items = iter::once(item_0.clone())
10976 .chain((11..51).map(|i| lsp::CompletionItem {
10977 label: format!("item_{}", i),
10978 insert_text: Some(format!("item_{}", i)),
10979 insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
10980 ..lsp::CompletionItem::default()
10981 }))
10982 .collect::<Vec<_>>();
10983
10984 let default_commit_characters = vec!["?".to_string()];
10985 let default_data = json!({ "default": "data"});
10986 let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
10987 let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
10988 let default_edit_range = lsp::Range {
10989 start: lsp::Position {
10990 line: 0,
10991 character: 5,
10992 },
10993 end: lsp::Position {
10994 line: 0,
10995 character: 5,
10996 },
10997 };
10998
10999 let item_0_out = lsp::CompletionItem {
11000 commit_characters: Some(default_commit_characters.clone()),
11001 insert_text_format: Some(default_insert_text_format),
11002 ..item_0
11003 };
11004 let items_out = iter::once(item_0_out)
11005 .chain(items[1..].iter().map(|item| lsp::CompletionItem {
11006 commit_characters: Some(default_commit_characters.clone()),
11007 data: Some(default_data.clone()),
11008 insert_text_mode: Some(default_insert_text_mode),
11009 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11010 range: default_edit_range,
11011 new_text: item.label.clone(),
11012 })),
11013 ..item.clone()
11014 }))
11015 .collect::<Vec<lsp::CompletionItem>>();
11016
11017 let mut cx = EditorLspTestContext::new_rust(
11018 lsp::ServerCapabilities {
11019 completion_provider: Some(lsp::CompletionOptions {
11020 trigger_characters: Some(vec![".".to_string()]),
11021 resolve_provider: Some(true),
11022 ..Default::default()
11023 }),
11024 ..Default::default()
11025 },
11026 cx,
11027 )
11028 .await;
11029
11030 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
11031 cx.simulate_keystroke(".");
11032
11033 let completion_data = default_data.clone();
11034 let completion_characters = default_commit_characters.clone();
11035 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
11036 let default_data = completion_data.clone();
11037 let default_commit_characters = completion_characters.clone();
11038 let items = items.clone();
11039 async move {
11040 Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
11041 items,
11042 item_defaults: Some(lsp::CompletionListItemDefaults {
11043 data: Some(default_data.clone()),
11044 commit_characters: Some(default_commit_characters.clone()),
11045 edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
11046 default_edit_range,
11047 )),
11048 insert_text_format: Some(default_insert_text_format),
11049 insert_text_mode: Some(default_insert_text_mode),
11050 }),
11051 ..lsp::CompletionList::default()
11052 })))
11053 }
11054 })
11055 .next()
11056 .await;
11057
11058 let resolved_items = Arc::new(Mutex::new(Vec::new()));
11059 cx.lsp
11060 .server
11061 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
11062 let closure_resolved_items = resolved_items.clone();
11063 move |item_to_resolve, _| {
11064 let closure_resolved_items = closure_resolved_items.clone();
11065 async move {
11066 closure_resolved_items.lock().push(item_to_resolve.clone());
11067 Ok(item_to_resolve)
11068 }
11069 }
11070 })
11071 .detach();
11072
11073 cx.condition(|editor, _| editor.context_menu_visible())
11074 .await;
11075 cx.run_until_parked();
11076 cx.update_editor(|editor, _| {
11077 let menu = editor.context_menu.borrow_mut();
11078 match menu.as_ref().expect("should have the completions menu") {
11079 CodeContextMenu::Completions(completions_menu) => {
11080 assert_eq!(
11081 completions_menu
11082 .entries
11083 .borrow()
11084 .iter()
11085 .flat_map(|c| match c {
11086 CompletionEntry::Match(mat) => Some(mat.string.clone()),
11087 _ => None,
11088 })
11089 .collect::<Vec<String>>(),
11090 items_out
11091 .iter()
11092 .map(|completion| completion.label.clone())
11093 .collect::<Vec<String>>()
11094 );
11095 }
11096 CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
11097 }
11098 });
11099 // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
11100 // with 4 from the end.
11101 assert_eq!(
11102 *resolved_items.lock(),
11103 [
11104 &items_out[0..16],
11105 &items_out[items_out.len() - 4..items_out.len()]
11106 ]
11107 .concat()
11108 .iter()
11109 .cloned()
11110 .collect::<Vec<lsp::CompletionItem>>()
11111 );
11112 resolved_items.lock().clear();
11113
11114 cx.update_editor(|editor, cx| {
11115 editor.context_menu_prev(&ContextMenuPrev, cx);
11116 });
11117 cx.run_until_parked();
11118 // Completions that have already been resolved are skipped.
11119 assert_eq!(
11120 *resolved_items.lock(),
11121 items_out[items_out.len() - 16..items_out.len() - 4]
11122 .iter()
11123 .cloned()
11124 .collect::<Vec<lsp::CompletionItem>>()
11125 );
11126 resolved_items.lock().clear();
11127}
11128
11129#[gpui::test]
11130async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
11131 init_test(cx, |_| {});
11132
11133 let mut cx = EditorLspTestContext::new(
11134 Language::new(
11135 LanguageConfig {
11136 matcher: LanguageMatcher {
11137 path_suffixes: vec!["jsx".into()],
11138 ..Default::default()
11139 },
11140 overrides: [(
11141 "element".into(),
11142 LanguageConfigOverride {
11143 word_characters: Override::Set(['-'].into_iter().collect()),
11144 ..Default::default()
11145 },
11146 )]
11147 .into_iter()
11148 .collect(),
11149 ..Default::default()
11150 },
11151 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
11152 )
11153 .with_override_query("(jsx_self_closing_element) @element")
11154 .unwrap(),
11155 lsp::ServerCapabilities {
11156 completion_provider: Some(lsp::CompletionOptions {
11157 trigger_characters: Some(vec![":".to_string()]),
11158 ..Default::default()
11159 }),
11160 ..Default::default()
11161 },
11162 cx,
11163 )
11164 .await;
11165
11166 cx.lsp
11167 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
11168 Ok(Some(lsp::CompletionResponse::Array(vec![
11169 lsp::CompletionItem {
11170 label: "bg-blue".into(),
11171 ..Default::default()
11172 },
11173 lsp::CompletionItem {
11174 label: "bg-red".into(),
11175 ..Default::default()
11176 },
11177 lsp::CompletionItem {
11178 label: "bg-yellow".into(),
11179 ..Default::default()
11180 },
11181 ])))
11182 });
11183
11184 cx.set_state(r#"<p class="bgˇ" />"#);
11185
11186 // Trigger completion when typing a dash, because the dash is an extra
11187 // word character in the 'element' scope, which contains the cursor.
11188 cx.simulate_keystroke("-");
11189 cx.executor().run_until_parked();
11190 cx.update_editor(|editor, _| {
11191 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11192 {
11193 assert_eq!(
11194 completion_menu_entries(&menu),
11195 &["bg-red", "bg-blue", "bg-yellow"]
11196 );
11197 } else {
11198 panic!("expected completion menu to be open");
11199 }
11200 });
11201
11202 cx.simulate_keystroke("l");
11203 cx.executor().run_until_parked();
11204 cx.update_editor(|editor, _| {
11205 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11206 {
11207 assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
11208 } else {
11209 panic!("expected completion menu to be open");
11210 }
11211 });
11212
11213 // When filtering completions, consider the character after the '-' to
11214 // be the start of a subword.
11215 cx.set_state(r#"<p class="yelˇ" />"#);
11216 cx.simulate_keystroke("l");
11217 cx.executor().run_until_parked();
11218 cx.update_editor(|editor, _| {
11219 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11220 {
11221 assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
11222 } else {
11223 panic!("expected completion menu to be open");
11224 }
11225 });
11226}
11227
11228fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
11229 let entries = menu.entries.borrow();
11230 entries
11231 .iter()
11232 .flat_map(|e| match e {
11233 CompletionEntry::Match(mat) => Some(mat.string.clone()),
11234 _ => None,
11235 })
11236 .collect()
11237}
11238
11239#[gpui::test]
11240async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
11241 init_test(cx, |settings| {
11242 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
11243 FormatterList(vec![Formatter::Prettier].into()),
11244 ))
11245 });
11246
11247 let fs = FakeFs::new(cx.executor());
11248 fs.insert_file("/file.ts", Default::default()).await;
11249
11250 let project = Project::test(fs, ["/file.ts".as_ref()], cx).await;
11251 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11252
11253 language_registry.add(Arc::new(Language::new(
11254 LanguageConfig {
11255 name: "TypeScript".into(),
11256 matcher: LanguageMatcher {
11257 path_suffixes: vec!["ts".to_string()],
11258 ..Default::default()
11259 },
11260 ..Default::default()
11261 },
11262 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
11263 )));
11264 update_test_language_settings(cx, |settings| {
11265 settings.defaults.prettier = Some(PrettierSettings {
11266 allowed: true,
11267 ..PrettierSettings::default()
11268 });
11269 });
11270
11271 let test_plugin = "test_plugin";
11272 let _ = language_registry.register_fake_lsp(
11273 "TypeScript",
11274 FakeLspAdapter {
11275 prettier_plugins: vec![test_plugin],
11276 ..Default::default()
11277 },
11278 );
11279
11280 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
11281 let buffer = project
11282 .update(cx, |project, cx| project.open_local_buffer("/file.ts", cx))
11283 .await
11284 .unwrap();
11285
11286 let buffer_text = "one\ntwo\nthree\n";
11287 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
11288 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
11289 editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
11290
11291 editor
11292 .update(cx, |editor, cx| {
11293 editor.perform_format(
11294 project.clone(),
11295 FormatTrigger::Manual,
11296 FormatTarget::Buffer,
11297 cx,
11298 )
11299 })
11300 .unwrap()
11301 .await;
11302 assert_eq!(
11303 editor.update(cx, |editor, cx| editor.text(cx)),
11304 buffer_text.to_string() + prettier_format_suffix,
11305 "Test prettier formatting was not applied to the original buffer text",
11306 );
11307
11308 update_test_language_settings(cx, |settings| {
11309 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
11310 });
11311 let format = editor.update(cx, |editor, cx| {
11312 editor.perform_format(
11313 project.clone(),
11314 FormatTrigger::Manual,
11315 FormatTarget::Buffer,
11316 cx,
11317 )
11318 });
11319 format.await.unwrap();
11320 assert_eq!(
11321 editor.update(cx, |editor, cx| editor.text(cx)),
11322 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
11323 "Autoformatting (via test prettier) was not applied to the original buffer text",
11324 );
11325}
11326
11327#[gpui::test]
11328async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
11329 init_test(cx, |_| {});
11330 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11331 let base_text = indoc! {r#"
11332 struct Row;
11333 struct Row1;
11334 struct Row2;
11335
11336 struct Row4;
11337 struct Row5;
11338 struct Row6;
11339
11340 struct Row8;
11341 struct Row9;
11342 struct Row10;"#};
11343
11344 // When addition hunks are not adjacent to carets, no hunk revert is performed
11345 assert_hunk_revert(
11346 indoc! {r#"struct Row;
11347 struct Row1;
11348 struct Row1.1;
11349 struct Row1.2;
11350 struct Row2;ˇ
11351
11352 struct Row4;
11353 struct Row5;
11354 struct Row6;
11355
11356 struct Row8;
11357 ˇstruct Row9;
11358 struct Row9.1;
11359 struct Row9.2;
11360 struct Row9.3;
11361 struct Row10;"#},
11362 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
11363 indoc! {r#"struct Row;
11364 struct Row1;
11365 struct Row1.1;
11366 struct Row1.2;
11367 struct Row2;ˇ
11368
11369 struct Row4;
11370 struct Row5;
11371 struct Row6;
11372
11373 struct Row8;
11374 ˇstruct Row9;
11375 struct Row9.1;
11376 struct Row9.2;
11377 struct Row9.3;
11378 struct Row10;"#},
11379 base_text,
11380 &mut cx,
11381 );
11382 // Same for selections
11383 assert_hunk_revert(
11384 indoc! {r#"struct Row;
11385 struct Row1;
11386 struct Row2;
11387 struct Row2.1;
11388 struct Row2.2;
11389 «ˇ
11390 struct Row4;
11391 struct» Row5;
11392 «struct Row6;
11393 ˇ»
11394 struct Row9.1;
11395 struct Row9.2;
11396 struct Row9.3;
11397 struct Row8;
11398 struct Row9;
11399 struct Row10;"#},
11400 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
11401 indoc! {r#"struct Row;
11402 struct Row1;
11403 struct Row2;
11404 struct Row2.1;
11405 struct Row2.2;
11406 «ˇ
11407 struct Row4;
11408 struct» Row5;
11409 «struct Row6;
11410 ˇ»
11411 struct Row9.1;
11412 struct Row9.2;
11413 struct Row9.3;
11414 struct Row8;
11415 struct Row9;
11416 struct Row10;"#},
11417 base_text,
11418 &mut cx,
11419 );
11420
11421 // When carets and selections intersect the addition hunks, those are reverted.
11422 // Adjacent carets got merged.
11423 assert_hunk_revert(
11424 indoc! {r#"struct Row;
11425 ˇ// something on the top
11426 struct Row1;
11427 struct Row2;
11428 struct Roˇw3.1;
11429 struct Row2.2;
11430 struct Row2.3;ˇ
11431
11432 struct Row4;
11433 struct ˇRow5.1;
11434 struct Row5.2;
11435 struct «Rowˇ»5.3;
11436 struct Row5;
11437 struct Row6;
11438 ˇ
11439 struct Row9.1;
11440 struct «Rowˇ»9.2;
11441 struct «ˇRow»9.3;
11442 struct Row8;
11443 struct Row9;
11444 «ˇ// something on bottom»
11445 struct Row10;"#},
11446 vec![
11447 DiffHunkStatus::Added,
11448 DiffHunkStatus::Added,
11449 DiffHunkStatus::Added,
11450 DiffHunkStatus::Added,
11451 DiffHunkStatus::Added,
11452 ],
11453 indoc! {r#"struct Row;
11454 ˇstruct Row1;
11455 struct Row2;
11456 ˇ
11457 struct Row4;
11458 ˇstruct Row5;
11459 struct Row6;
11460 ˇ
11461 ˇstruct Row8;
11462 struct Row9;
11463 ˇstruct Row10;"#},
11464 base_text,
11465 &mut cx,
11466 );
11467}
11468
11469#[gpui::test]
11470async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
11471 init_test(cx, |_| {});
11472 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11473 let base_text = indoc! {r#"
11474 struct Row;
11475 struct Row1;
11476 struct Row2;
11477
11478 struct Row4;
11479 struct Row5;
11480 struct Row6;
11481
11482 struct Row8;
11483 struct Row9;
11484 struct Row10;"#};
11485
11486 // Modification hunks behave the same as the addition ones.
11487 assert_hunk_revert(
11488 indoc! {r#"struct Row;
11489 struct Row1;
11490 struct Row33;
11491 ˇ
11492 struct Row4;
11493 struct Row5;
11494 struct Row6;
11495 ˇ
11496 struct Row99;
11497 struct Row9;
11498 struct Row10;"#},
11499 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
11500 indoc! {r#"struct Row;
11501 struct Row1;
11502 struct Row33;
11503 ˇ
11504 struct Row4;
11505 struct Row5;
11506 struct Row6;
11507 ˇ
11508 struct Row99;
11509 struct Row9;
11510 struct Row10;"#},
11511 base_text,
11512 &mut cx,
11513 );
11514 assert_hunk_revert(
11515 indoc! {r#"struct Row;
11516 struct Row1;
11517 struct Row33;
11518 «ˇ
11519 struct Row4;
11520 struct» Row5;
11521 «struct Row6;
11522 ˇ»
11523 struct Row99;
11524 struct Row9;
11525 struct Row10;"#},
11526 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
11527 indoc! {r#"struct Row;
11528 struct Row1;
11529 struct Row33;
11530 «ˇ
11531 struct Row4;
11532 struct» Row5;
11533 «struct Row6;
11534 ˇ»
11535 struct Row99;
11536 struct Row9;
11537 struct Row10;"#},
11538 base_text,
11539 &mut cx,
11540 );
11541
11542 assert_hunk_revert(
11543 indoc! {r#"ˇstruct Row1.1;
11544 struct Row1;
11545 «ˇstr»uct Row22;
11546
11547 struct ˇRow44;
11548 struct Row5;
11549 struct «Rˇ»ow66;ˇ
11550
11551 «struˇ»ct Row88;
11552 struct Row9;
11553 struct Row1011;ˇ"#},
11554 vec![
11555 DiffHunkStatus::Modified,
11556 DiffHunkStatus::Modified,
11557 DiffHunkStatus::Modified,
11558 DiffHunkStatus::Modified,
11559 DiffHunkStatus::Modified,
11560 DiffHunkStatus::Modified,
11561 ],
11562 indoc! {r#"struct Row;
11563 ˇstruct Row1;
11564 struct Row2;
11565 ˇ
11566 struct Row4;
11567 ˇstruct Row5;
11568 struct Row6;
11569 ˇ
11570 struct Row8;
11571 ˇstruct Row9;
11572 struct Row10;ˇ"#},
11573 base_text,
11574 &mut cx,
11575 );
11576}
11577
11578#[gpui::test]
11579async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
11580 init_test(cx, |_| {});
11581 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11582 let base_text = indoc! {r#"struct Row;
11583struct Row1;
11584struct Row2;
11585
11586struct Row4;
11587struct Row5;
11588struct Row6;
11589
11590struct Row8;
11591struct Row9;
11592struct Row10;"#};
11593
11594 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
11595 assert_hunk_revert(
11596 indoc! {r#"struct Row;
11597 struct Row2;
11598
11599 ˇstruct Row4;
11600 struct Row5;
11601 struct Row6;
11602 ˇ
11603 struct Row8;
11604 struct Row10;"#},
11605 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11606 indoc! {r#"struct Row;
11607 struct Row2;
11608
11609 ˇstruct Row4;
11610 struct Row5;
11611 struct Row6;
11612 ˇ
11613 struct Row8;
11614 struct Row10;"#},
11615 base_text,
11616 &mut cx,
11617 );
11618 assert_hunk_revert(
11619 indoc! {r#"struct Row;
11620 struct Row2;
11621
11622 «ˇstruct Row4;
11623 struct» Row5;
11624 «struct Row6;
11625 ˇ»
11626 struct Row8;
11627 struct Row10;"#},
11628 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11629 indoc! {r#"struct Row;
11630 struct Row2;
11631
11632 «ˇstruct Row4;
11633 struct» Row5;
11634 «struct Row6;
11635 ˇ»
11636 struct Row8;
11637 struct Row10;"#},
11638 base_text,
11639 &mut cx,
11640 );
11641
11642 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
11643 assert_hunk_revert(
11644 indoc! {r#"struct Row;
11645 ˇstruct Row2;
11646
11647 struct Row4;
11648 struct Row5;
11649 struct Row6;
11650
11651 struct Row8;ˇ
11652 struct Row10;"#},
11653 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11654 indoc! {r#"struct Row;
11655 struct Row1;
11656 ˇstruct Row2;
11657
11658 struct Row4;
11659 struct Row5;
11660 struct Row6;
11661
11662 struct Row8;ˇ
11663 struct Row9;
11664 struct Row10;"#},
11665 base_text,
11666 &mut cx,
11667 );
11668 assert_hunk_revert(
11669 indoc! {r#"struct Row;
11670 struct Row2«ˇ;
11671 struct Row4;
11672 struct» Row5;
11673 «struct Row6;
11674
11675 struct Row8;ˇ»
11676 struct Row10;"#},
11677 vec![
11678 DiffHunkStatus::Removed,
11679 DiffHunkStatus::Removed,
11680 DiffHunkStatus::Removed,
11681 ],
11682 indoc! {r#"struct Row;
11683 struct Row1;
11684 struct Row2«ˇ;
11685
11686 struct Row4;
11687 struct» Row5;
11688 «struct Row6;
11689
11690 struct Row8;ˇ»
11691 struct Row9;
11692 struct Row10;"#},
11693 base_text,
11694 &mut cx,
11695 );
11696}
11697
11698#[gpui::test]
11699async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
11700 init_test(cx, |_| {});
11701
11702 let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
11703 let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
11704 let base_text_3 =
11705 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
11706
11707 let text_1 = edit_first_char_of_every_line(base_text_1);
11708 let text_2 = edit_first_char_of_every_line(base_text_2);
11709 let text_3 = edit_first_char_of_every_line(base_text_3);
11710
11711 let buffer_1 = cx.new_model(|cx| Buffer::local(text_1.clone(), cx));
11712 let buffer_2 = cx.new_model(|cx| Buffer::local(text_2.clone(), cx));
11713 let buffer_3 = cx.new_model(|cx| Buffer::local(text_3.clone(), cx));
11714
11715 let multibuffer = cx.new_model(|cx| {
11716 let mut multibuffer = MultiBuffer::new(ReadWrite);
11717 multibuffer.push_excerpts(
11718 buffer_1.clone(),
11719 [
11720 ExcerptRange {
11721 context: Point::new(0, 0)..Point::new(3, 0),
11722 primary: None,
11723 },
11724 ExcerptRange {
11725 context: Point::new(5, 0)..Point::new(7, 0),
11726 primary: None,
11727 },
11728 ExcerptRange {
11729 context: Point::new(9, 0)..Point::new(10, 4),
11730 primary: None,
11731 },
11732 ],
11733 cx,
11734 );
11735 multibuffer.push_excerpts(
11736 buffer_2.clone(),
11737 [
11738 ExcerptRange {
11739 context: Point::new(0, 0)..Point::new(3, 0),
11740 primary: None,
11741 },
11742 ExcerptRange {
11743 context: Point::new(5, 0)..Point::new(7, 0),
11744 primary: None,
11745 },
11746 ExcerptRange {
11747 context: Point::new(9, 0)..Point::new(10, 4),
11748 primary: None,
11749 },
11750 ],
11751 cx,
11752 );
11753 multibuffer.push_excerpts(
11754 buffer_3.clone(),
11755 [
11756 ExcerptRange {
11757 context: Point::new(0, 0)..Point::new(3, 0),
11758 primary: None,
11759 },
11760 ExcerptRange {
11761 context: Point::new(5, 0)..Point::new(7, 0),
11762 primary: None,
11763 },
11764 ExcerptRange {
11765 context: Point::new(9, 0)..Point::new(10, 4),
11766 primary: None,
11767 },
11768 ],
11769 cx,
11770 );
11771 multibuffer
11772 });
11773
11774 let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
11775 editor.update(cx, |editor, cx| {
11776 for (buffer, diff_base) in [
11777 (buffer_1.clone(), base_text_1),
11778 (buffer_2.clone(), base_text_2),
11779 (buffer_3.clone(), base_text_3),
11780 ] {
11781 let change_set = cx.new_model(|cx| {
11782 BufferChangeSet::new_with_base_text(
11783 diff_base.to_string(),
11784 buffer.read(cx).text_snapshot(),
11785 cx,
11786 )
11787 });
11788 editor.diff_map.add_change_set(change_set, cx)
11789 }
11790 });
11791 cx.executor().run_until_parked();
11792
11793 editor.update(cx, |editor, cx| {
11794 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}");
11795 editor.select_all(&SelectAll, cx);
11796 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
11797 });
11798 cx.executor().run_until_parked();
11799
11800 // When all ranges are selected, all buffer hunks are reverted.
11801 editor.update(cx, |editor, cx| {
11802 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");
11803 });
11804 buffer_1.update(cx, |buffer, _| {
11805 assert_eq!(buffer.text(), base_text_1);
11806 });
11807 buffer_2.update(cx, |buffer, _| {
11808 assert_eq!(buffer.text(), base_text_2);
11809 });
11810 buffer_3.update(cx, |buffer, _| {
11811 assert_eq!(buffer.text(), base_text_3);
11812 });
11813
11814 editor.update(cx, |editor, cx| {
11815 editor.undo(&Default::default(), cx);
11816 });
11817
11818 editor.update(cx, |editor, cx| {
11819 editor.change_selections(None, cx, |s| {
11820 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
11821 });
11822 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
11823 });
11824
11825 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
11826 // but not affect buffer_2 and its related excerpts.
11827 editor.update(cx, |editor, cx| {
11828 assert_eq!(
11829 editor.text(cx),
11830 "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}"
11831 );
11832 });
11833 buffer_1.update(cx, |buffer, _| {
11834 assert_eq!(buffer.text(), base_text_1);
11835 });
11836 buffer_2.update(cx, |buffer, _| {
11837 assert_eq!(
11838 buffer.text(),
11839 "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
11840 );
11841 });
11842 buffer_3.update(cx, |buffer, _| {
11843 assert_eq!(
11844 buffer.text(),
11845 "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
11846 );
11847 });
11848
11849 fn edit_first_char_of_every_line(text: &str) -> String {
11850 text.split('\n')
11851 .map(|line| format!("X{}", &line[1..]))
11852 .collect::<Vec<_>>()
11853 .join("\n")
11854 }
11855}
11856
11857#[gpui::test]
11858async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
11859 init_test(cx, |_| {});
11860
11861 let cols = 4;
11862 let rows = 10;
11863 let sample_text_1 = sample_text(rows, cols, 'a');
11864 assert_eq!(
11865 sample_text_1,
11866 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11867 );
11868 let sample_text_2 = sample_text(rows, cols, 'l');
11869 assert_eq!(
11870 sample_text_2,
11871 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11872 );
11873 let sample_text_3 = sample_text(rows, cols, 'v');
11874 assert_eq!(
11875 sample_text_3,
11876 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11877 );
11878
11879 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
11880 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
11881 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
11882
11883 let multi_buffer = cx.new_model(|cx| {
11884 let mut multibuffer = MultiBuffer::new(ReadWrite);
11885 multibuffer.push_excerpts(
11886 buffer_1.clone(),
11887 [
11888 ExcerptRange {
11889 context: Point::new(0, 0)..Point::new(3, 0),
11890 primary: None,
11891 },
11892 ExcerptRange {
11893 context: Point::new(5, 0)..Point::new(7, 0),
11894 primary: None,
11895 },
11896 ExcerptRange {
11897 context: Point::new(9, 0)..Point::new(10, 4),
11898 primary: None,
11899 },
11900 ],
11901 cx,
11902 );
11903 multibuffer.push_excerpts(
11904 buffer_2.clone(),
11905 [
11906 ExcerptRange {
11907 context: Point::new(0, 0)..Point::new(3, 0),
11908 primary: None,
11909 },
11910 ExcerptRange {
11911 context: Point::new(5, 0)..Point::new(7, 0),
11912 primary: None,
11913 },
11914 ExcerptRange {
11915 context: Point::new(9, 0)..Point::new(10, 4),
11916 primary: None,
11917 },
11918 ],
11919 cx,
11920 );
11921 multibuffer.push_excerpts(
11922 buffer_3.clone(),
11923 [
11924 ExcerptRange {
11925 context: Point::new(0, 0)..Point::new(3, 0),
11926 primary: None,
11927 },
11928 ExcerptRange {
11929 context: Point::new(5, 0)..Point::new(7, 0),
11930 primary: None,
11931 },
11932 ExcerptRange {
11933 context: Point::new(9, 0)..Point::new(10, 4),
11934 primary: None,
11935 },
11936 ],
11937 cx,
11938 );
11939 multibuffer
11940 });
11941
11942 let fs = FakeFs::new(cx.executor());
11943 fs.insert_tree(
11944 "/a",
11945 json!({
11946 "main.rs": sample_text_1,
11947 "other.rs": sample_text_2,
11948 "lib.rs": sample_text_3,
11949 }),
11950 )
11951 .await;
11952 let project = Project::test(fs, ["/a".as_ref()], cx).await;
11953 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
11954 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11955 let multi_buffer_editor = cx.new_view(|cx| {
11956 Editor::new(
11957 EditorMode::Full,
11958 multi_buffer,
11959 Some(project.clone()),
11960 true,
11961 cx,
11962 )
11963 });
11964 let multibuffer_item_id = workspace
11965 .update(cx, |workspace, cx| {
11966 assert!(
11967 workspace.active_item(cx).is_none(),
11968 "active item should be None before the first item is added"
11969 );
11970 workspace.add_item_to_active_pane(
11971 Box::new(multi_buffer_editor.clone()),
11972 None,
11973 true,
11974 cx,
11975 );
11976 let active_item = workspace
11977 .active_item(cx)
11978 .expect("should have an active item after adding the multi buffer");
11979 assert!(
11980 !active_item.is_singleton(cx),
11981 "A multi buffer was expected to active after adding"
11982 );
11983 active_item.item_id()
11984 })
11985 .unwrap();
11986 cx.executor().run_until_parked();
11987
11988 multi_buffer_editor.update(cx, |editor, cx| {
11989 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
11990 editor.open_excerpts(&OpenExcerpts, cx);
11991 });
11992 cx.executor().run_until_parked();
11993 let first_item_id = workspace
11994 .update(cx, |workspace, cx| {
11995 let active_item = workspace
11996 .active_item(cx)
11997 .expect("should have an active item after navigating into the 1st buffer");
11998 let first_item_id = active_item.item_id();
11999 assert_ne!(
12000 first_item_id, multibuffer_item_id,
12001 "Should navigate into the 1st buffer and activate it"
12002 );
12003 assert!(
12004 active_item.is_singleton(cx),
12005 "New active item should be a singleton buffer"
12006 );
12007 assert_eq!(
12008 active_item
12009 .act_as::<Editor>(cx)
12010 .expect("should have navigated into an editor for the 1st buffer")
12011 .read(cx)
12012 .text(cx),
12013 sample_text_1
12014 );
12015
12016 workspace
12017 .go_back(workspace.active_pane().downgrade(), cx)
12018 .detach_and_log_err(cx);
12019
12020 first_item_id
12021 })
12022 .unwrap();
12023 cx.executor().run_until_parked();
12024 workspace
12025 .update(cx, |workspace, cx| {
12026 let active_item = workspace
12027 .active_item(cx)
12028 .expect("should have an active item after navigating back");
12029 assert_eq!(
12030 active_item.item_id(),
12031 multibuffer_item_id,
12032 "Should navigate back to the multi buffer"
12033 );
12034 assert!(!active_item.is_singleton(cx));
12035 })
12036 .unwrap();
12037
12038 multi_buffer_editor.update(cx, |editor, cx| {
12039 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
12040 s.select_ranges(Some(39..40))
12041 });
12042 editor.open_excerpts(&OpenExcerpts, cx);
12043 });
12044 cx.executor().run_until_parked();
12045 let second_item_id = workspace
12046 .update(cx, |workspace, cx| {
12047 let active_item = workspace
12048 .active_item(cx)
12049 .expect("should have an active item after navigating into the 2nd buffer");
12050 let second_item_id = active_item.item_id();
12051 assert_ne!(
12052 second_item_id, multibuffer_item_id,
12053 "Should navigate away from the multibuffer"
12054 );
12055 assert_ne!(
12056 second_item_id, first_item_id,
12057 "Should navigate into the 2nd buffer and activate it"
12058 );
12059 assert!(
12060 active_item.is_singleton(cx),
12061 "New active item should be a singleton buffer"
12062 );
12063 assert_eq!(
12064 active_item
12065 .act_as::<Editor>(cx)
12066 .expect("should have navigated into an editor")
12067 .read(cx)
12068 .text(cx),
12069 sample_text_2
12070 );
12071
12072 workspace
12073 .go_back(workspace.active_pane().downgrade(), cx)
12074 .detach_and_log_err(cx);
12075
12076 second_item_id
12077 })
12078 .unwrap();
12079 cx.executor().run_until_parked();
12080 workspace
12081 .update(cx, |workspace, cx| {
12082 let active_item = workspace
12083 .active_item(cx)
12084 .expect("should have an active item after navigating back from the 2nd buffer");
12085 assert_eq!(
12086 active_item.item_id(),
12087 multibuffer_item_id,
12088 "Should navigate back from the 2nd buffer to the multi buffer"
12089 );
12090 assert!(!active_item.is_singleton(cx));
12091 })
12092 .unwrap();
12093
12094 multi_buffer_editor.update(cx, |editor, cx| {
12095 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
12096 s.select_ranges(Some(70..70))
12097 });
12098 editor.open_excerpts(&OpenExcerpts, cx);
12099 });
12100 cx.executor().run_until_parked();
12101 workspace
12102 .update(cx, |workspace, cx| {
12103 let active_item = workspace
12104 .active_item(cx)
12105 .expect("should have an active item after navigating into the 3rd buffer");
12106 let third_item_id = active_item.item_id();
12107 assert_ne!(
12108 third_item_id, multibuffer_item_id,
12109 "Should navigate into the 3rd buffer and activate it"
12110 );
12111 assert_ne!(third_item_id, first_item_id);
12112 assert_ne!(third_item_id, second_item_id);
12113 assert!(
12114 active_item.is_singleton(cx),
12115 "New active item should be a singleton buffer"
12116 );
12117 assert_eq!(
12118 active_item
12119 .act_as::<Editor>(cx)
12120 .expect("should have navigated into an editor")
12121 .read(cx)
12122 .text(cx),
12123 sample_text_3
12124 );
12125
12126 workspace
12127 .go_back(workspace.active_pane().downgrade(), cx)
12128 .detach_and_log_err(cx);
12129 })
12130 .unwrap();
12131 cx.executor().run_until_parked();
12132 workspace
12133 .update(cx, |workspace, cx| {
12134 let active_item = workspace
12135 .active_item(cx)
12136 .expect("should have an active item after navigating back from the 3rd buffer");
12137 assert_eq!(
12138 active_item.item_id(),
12139 multibuffer_item_id,
12140 "Should navigate back from the 3rd buffer to the multi buffer"
12141 );
12142 assert!(!active_item.is_singleton(cx));
12143 })
12144 .unwrap();
12145}
12146
12147#[gpui::test]
12148async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
12149 init_test(cx, |_| {});
12150
12151 let mut cx = EditorTestContext::new(cx).await;
12152
12153 let diff_base = r#"
12154 use some::mod;
12155
12156 const A: u32 = 42;
12157
12158 fn main() {
12159 println!("hello");
12160
12161 println!("world");
12162 }
12163 "#
12164 .unindent();
12165
12166 cx.set_state(
12167 &r#"
12168 use some::modified;
12169
12170 ˇ
12171 fn main() {
12172 println!("hello there");
12173
12174 println!("around the");
12175 println!("world");
12176 }
12177 "#
12178 .unindent(),
12179 );
12180
12181 cx.set_diff_base(&diff_base);
12182 executor.run_until_parked();
12183
12184 cx.update_editor(|editor, cx| {
12185 editor.go_to_next_hunk(&GoToHunk, cx);
12186 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
12187 });
12188 executor.run_until_parked();
12189 cx.assert_state_with_diff(
12190 r#"
12191 use some::modified;
12192
12193
12194 fn main() {
12195 - println!("hello");
12196 + ˇ println!("hello there");
12197
12198 println!("around the");
12199 println!("world");
12200 }
12201 "#
12202 .unindent(),
12203 );
12204
12205 cx.update_editor(|editor, cx| {
12206 for _ in 0..3 {
12207 editor.go_to_next_hunk(&GoToHunk, cx);
12208 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
12209 }
12210 });
12211 executor.run_until_parked();
12212 cx.assert_state_with_diff(
12213 r#"
12214 - use some::mod;
12215 + use some::modified;
12216
12217 - const A: u32 = 42;
12218 ˇ
12219 fn main() {
12220 - println!("hello");
12221 + println!("hello there");
12222
12223 + println!("around the");
12224 println!("world");
12225 }
12226 "#
12227 .unindent(),
12228 );
12229
12230 cx.update_editor(|editor, cx| {
12231 editor.cancel(&Cancel, cx);
12232 });
12233
12234 cx.assert_state_with_diff(
12235 r#"
12236 use some::modified;
12237
12238 ˇ
12239 fn main() {
12240 println!("hello there");
12241
12242 println!("around the");
12243 println!("world");
12244 }
12245 "#
12246 .unindent(),
12247 );
12248}
12249
12250#[gpui::test]
12251async fn test_diff_base_change_with_expanded_diff_hunks(
12252 executor: BackgroundExecutor,
12253 cx: &mut gpui::TestAppContext,
12254) {
12255 init_test(cx, |_| {});
12256
12257 let mut cx = EditorTestContext::new(cx).await;
12258
12259 let diff_base = r#"
12260 use some::mod1;
12261 use some::mod2;
12262
12263 const A: u32 = 42;
12264 const B: u32 = 42;
12265 const C: u32 = 42;
12266
12267 fn main() {
12268 println!("hello");
12269
12270 println!("world");
12271 }
12272 "#
12273 .unindent();
12274
12275 cx.set_state(
12276 &r#"
12277 use some::mod2;
12278
12279 const A: u32 = 42;
12280 const C: u32 = 42;
12281
12282 fn main(ˇ) {
12283 //println!("hello");
12284
12285 println!("world");
12286 //
12287 //
12288 }
12289 "#
12290 .unindent(),
12291 );
12292
12293 cx.set_diff_base(&diff_base);
12294 executor.run_until_parked();
12295
12296 cx.update_editor(|editor, cx| {
12297 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12298 });
12299 executor.run_until_parked();
12300 cx.assert_state_with_diff(
12301 r#"
12302 - use some::mod1;
12303 use some::mod2;
12304
12305 const A: u32 = 42;
12306 - const B: u32 = 42;
12307 const C: u32 = 42;
12308
12309 fn main(ˇ) {
12310 - println!("hello");
12311 + //println!("hello");
12312
12313 println!("world");
12314 + //
12315 + //
12316 }
12317 "#
12318 .unindent(),
12319 );
12320
12321 cx.set_diff_base("new diff base!");
12322 executor.run_until_parked();
12323 cx.assert_state_with_diff(
12324 r#"
12325 use some::mod2;
12326
12327 const A: u32 = 42;
12328 const C: u32 = 42;
12329
12330 fn main(ˇ) {
12331 //println!("hello");
12332
12333 println!("world");
12334 //
12335 //
12336 }
12337 "#
12338 .unindent(),
12339 );
12340
12341 cx.update_editor(|editor, cx| {
12342 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12343 });
12344 executor.run_until_parked();
12345 cx.assert_state_with_diff(
12346 r#"
12347 - new diff base!
12348 + use some::mod2;
12349 +
12350 + const A: u32 = 42;
12351 + const C: u32 = 42;
12352 +
12353 + fn main(ˇ) {
12354 + //println!("hello");
12355 +
12356 + println!("world");
12357 + //
12358 + //
12359 + }
12360 "#
12361 .unindent(),
12362 );
12363}
12364
12365#[gpui::test]
12366async fn test_fold_unfold_diff_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
12367 init_test(cx, |_| {});
12368
12369 let mut cx = EditorTestContext::new(cx).await;
12370
12371 let diff_base = r#"
12372 use some::mod1;
12373 use some::mod2;
12374
12375 const A: u32 = 42;
12376 const B: u32 = 42;
12377 const C: u32 = 42;
12378
12379 fn main() {
12380 println!("hello");
12381
12382 println!("world");
12383 }
12384
12385 fn another() {
12386 println!("another");
12387 }
12388
12389 fn another2() {
12390 println!("another2");
12391 }
12392 "#
12393 .unindent();
12394
12395 cx.set_state(
12396 &r#"
12397 «use some::mod2;
12398
12399 const A: u32 = 42;
12400 const C: u32 = 42;
12401
12402 fn main() {
12403 //println!("hello");
12404
12405 println!("world");
12406 //
12407 //ˇ»
12408 }
12409
12410 fn another() {
12411 println!("another");
12412 println!("another");
12413 }
12414
12415 println!("another2");
12416 }
12417 "#
12418 .unindent(),
12419 );
12420
12421 cx.set_diff_base(&diff_base);
12422 executor.run_until_parked();
12423
12424 cx.update_editor(|editor, cx| {
12425 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12426 });
12427 executor.run_until_parked();
12428
12429 cx.assert_state_with_diff(
12430 r#"
12431 - use some::mod1;
12432 «use some::mod2;
12433
12434 const A: u32 = 42;
12435 - const B: u32 = 42;
12436 const C: u32 = 42;
12437
12438 fn main() {
12439 - println!("hello");
12440 + //println!("hello");
12441
12442 println!("world");
12443 + //
12444 + //ˇ»
12445 }
12446
12447 fn another() {
12448 println!("another");
12449 + println!("another");
12450 }
12451
12452 - fn another2() {
12453 println!("another2");
12454 }
12455 "#
12456 .unindent(),
12457 );
12458
12459 // Fold across some of the diff hunks. They should no longer appear expanded.
12460 cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx));
12461 cx.executor().run_until_parked();
12462
12463 // Hunks are not shown if their position is within a fold
12464 cx.assert_state_with_diff(
12465 r#"
12466 «use some::mod2;
12467
12468 const A: u32 = 42;
12469 const C: u32 = 42;
12470
12471 fn main() {
12472 //println!("hello");
12473
12474 println!("world");
12475 //
12476 //ˇ»
12477 }
12478
12479 fn another() {
12480 println!("another");
12481 + println!("another");
12482 }
12483
12484 - fn another2() {
12485 println!("another2");
12486 }
12487 "#
12488 .unindent(),
12489 );
12490
12491 cx.update_editor(|editor, cx| {
12492 editor.select_all(&SelectAll, cx);
12493 editor.unfold_lines(&UnfoldLines, cx);
12494 });
12495 cx.executor().run_until_parked();
12496
12497 // The deletions reappear when unfolding.
12498 cx.assert_state_with_diff(
12499 r#"
12500 - use some::mod1;
12501 «use some::mod2;
12502
12503 const A: u32 = 42;
12504 - const B: u32 = 42;
12505 const C: u32 = 42;
12506
12507 fn main() {
12508 - println!("hello");
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
12529#[gpui::test]
12530async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
12531 init_test(cx, |_| {});
12532
12533 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
12534 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
12535 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
12536 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
12537 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
12538 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
12539
12540 let buffer_1 = cx.new_model(|cx| Buffer::local(file_1_new.to_string(), cx));
12541 let buffer_2 = cx.new_model(|cx| Buffer::local(file_2_new.to_string(), cx));
12542 let buffer_3 = cx.new_model(|cx| Buffer::local(file_3_new.to_string(), cx));
12543
12544 let multi_buffer = cx.new_model(|cx| {
12545 let mut multibuffer = MultiBuffer::new(ReadWrite);
12546 multibuffer.push_excerpts(
12547 buffer_1.clone(),
12548 [
12549 ExcerptRange {
12550 context: Point::new(0, 0)..Point::new(3, 0),
12551 primary: None,
12552 },
12553 ExcerptRange {
12554 context: Point::new(5, 0)..Point::new(7, 0),
12555 primary: None,
12556 },
12557 ExcerptRange {
12558 context: Point::new(9, 0)..Point::new(10, 3),
12559 primary: None,
12560 },
12561 ],
12562 cx,
12563 );
12564 multibuffer.push_excerpts(
12565 buffer_2.clone(),
12566 [
12567 ExcerptRange {
12568 context: Point::new(0, 0)..Point::new(3, 0),
12569 primary: None,
12570 },
12571 ExcerptRange {
12572 context: Point::new(5, 0)..Point::new(7, 0),
12573 primary: None,
12574 },
12575 ExcerptRange {
12576 context: Point::new(9, 0)..Point::new(10, 3),
12577 primary: None,
12578 },
12579 ],
12580 cx,
12581 );
12582 multibuffer.push_excerpts(
12583 buffer_3.clone(),
12584 [
12585 ExcerptRange {
12586 context: Point::new(0, 0)..Point::new(3, 0),
12587 primary: None,
12588 },
12589 ExcerptRange {
12590 context: Point::new(5, 0)..Point::new(7, 0),
12591 primary: None,
12592 },
12593 ExcerptRange {
12594 context: Point::new(9, 0)..Point::new(10, 3),
12595 primary: None,
12596 },
12597 ],
12598 cx,
12599 );
12600 multibuffer
12601 });
12602
12603 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
12604 editor
12605 .update(cx, |editor, cx| {
12606 for (buffer, diff_base) in [
12607 (buffer_1.clone(), file_1_old),
12608 (buffer_2.clone(), file_2_old),
12609 (buffer_3.clone(), file_3_old),
12610 ] {
12611 let change_set = cx.new_model(|cx| {
12612 BufferChangeSet::new_with_base_text(
12613 diff_base.to_string(),
12614 buffer.read(cx).text_snapshot(),
12615 cx,
12616 )
12617 });
12618 editor.diff_map.add_change_set(change_set, cx)
12619 }
12620 })
12621 .unwrap();
12622
12623 let mut cx = EditorTestContext::for_editor(editor, cx).await;
12624 cx.run_until_parked();
12625
12626 cx.assert_editor_state(
12627 &"
12628 ˇaaa
12629 ccc
12630 ddd
12631
12632 ggg
12633 hhh
12634
12635
12636 lll
12637 mmm
12638 NNN
12639
12640 qqq
12641 rrr
12642
12643 uuu
12644 111
12645 222
12646 333
12647
12648 666
12649 777
12650
12651 000
12652 !!!"
12653 .unindent(),
12654 );
12655
12656 cx.update_editor(|editor, cx| {
12657 editor.select_all(&SelectAll, cx);
12658 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
12659 });
12660 cx.executor().run_until_parked();
12661
12662 cx.assert_state_with_diff(
12663 "
12664 «aaa
12665 - bbb
12666 ccc
12667 ddd
12668
12669 ggg
12670 hhh
12671
12672
12673 lll
12674 mmm
12675 - nnn
12676 + NNN
12677
12678 qqq
12679 rrr
12680
12681 uuu
12682 111
12683 222
12684 333
12685
12686 + 666
12687 777
12688
12689 000
12690 !!!ˇ»"
12691 .unindent(),
12692 );
12693}
12694
12695#[gpui::test]
12696async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut gpui::TestAppContext) {
12697 init_test(cx, |_| {});
12698
12699 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
12700 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\n";
12701
12702 let buffer = cx.new_model(|cx| Buffer::local(text.to_string(), cx));
12703 let multi_buffer = cx.new_model(|cx| {
12704 let mut multibuffer = MultiBuffer::new(ReadWrite);
12705 multibuffer.push_excerpts(
12706 buffer.clone(),
12707 [
12708 ExcerptRange {
12709 context: Point::new(0, 0)..Point::new(2, 0),
12710 primary: None,
12711 },
12712 ExcerptRange {
12713 context: Point::new(5, 0)..Point::new(7, 0),
12714 primary: None,
12715 },
12716 ],
12717 cx,
12718 );
12719 multibuffer
12720 });
12721
12722 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
12723 editor
12724 .update(cx, |editor, cx| {
12725 let buffer = buffer.read(cx).text_snapshot();
12726 let change_set = cx
12727 .new_model(|cx| BufferChangeSet::new_with_base_text(base.to_string(), buffer, cx));
12728 editor.diff_map.add_change_set(change_set, cx)
12729 })
12730 .unwrap();
12731
12732 let mut cx = EditorTestContext::for_editor(editor, cx).await;
12733 cx.run_until_parked();
12734
12735 cx.update_editor(|editor, cx| editor.expand_all_hunk_diffs(&Default::default(), cx));
12736 cx.executor().run_until_parked();
12737
12738 cx.assert_state_with_diff(
12739 "
12740 ˇaaa
12741 - bbb
12742 + BBB
12743
12744 - ddd
12745 - eee
12746 + EEE
12747 fff
12748 "
12749 .unindent(),
12750 );
12751}
12752
12753#[gpui::test]
12754async fn test_edits_around_expanded_insertion_hunks(
12755 executor: BackgroundExecutor,
12756 cx: &mut gpui::TestAppContext,
12757) {
12758 init_test(cx, |_| {});
12759
12760 let mut cx = EditorTestContext::new(cx).await;
12761
12762 let diff_base = r#"
12763 use some::mod1;
12764 use some::mod2;
12765
12766 const A: u32 = 42;
12767
12768 fn main() {
12769 println!("hello");
12770
12771 println!("world");
12772 }
12773 "#
12774 .unindent();
12775 executor.run_until_parked();
12776 cx.set_state(
12777 &r#"
12778 use some::mod1;
12779 use some::mod2;
12780
12781 const A: u32 = 42;
12782 const B: u32 = 42;
12783 const C: u32 = 42;
12784 ˇ
12785
12786 fn main() {
12787 println!("hello");
12788
12789 println!("world");
12790 }
12791 "#
12792 .unindent(),
12793 );
12794
12795 cx.set_diff_base(&diff_base);
12796 executor.run_until_parked();
12797
12798 cx.update_editor(|editor, cx| {
12799 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12800 });
12801 executor.run_until_parked();
12802
12803 cx.assert_state_with_diff(
12804 r#"
12805 use some::mod1;
12806 use some::mod2;
12807
12808 const A: u32 = 42;
12809 + const B: u32 = 42;
12810 + const C: u32 = 42;
12811 + ˇ
12812
12813 fn main() {
12814 println!("hello");
12815
12816 println!("world");
12817 }
12818 "#
12819 .unindent(),
12820 );
12821
12822 cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx));
12823 executor.run_until_parked();
12824
12825 cx.assert_state_with_diff(
12826 r#"
12827 use some::mod1;
12828 use some::mod2;
12829
12830 const A: u32 = 42;
12831 + const B: u32 = 42;
12832 + const C: u32 = 42;
12833 + const D: u32 = 42;
12834 + ˇ
12835
12836 fn main() {
12837 println!("hello");
12838
12839 println!("world");
12840 }
12841 "#
12842 .unindent(),
12843 );
12844
12845 cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx));
12846 executor.run_until_parked();
12847
12848 cx.assert_state_with_diff(
12849 r#"
12850 use some::mod1;
12851 use some::mod2;
12852
12853 const A: u32 = 42;
12854 + const B: u32 = 42;
12855 + const C: u32 = 42;
12856 + const D: u32 = 42;
12857 + const E: u32 = 42;
12858 + ˇ
12859
12860 fn main() {
12861 println!("hello");
12862
12863 println!("world");
12864 }
12865 "#
12866 .unindent(),
12867 );
12868
12869 cx.update_editor(|editor, cx| {
12870 editor.delete_line(&DeleteLine, cx);
12871 });
12872 executor.run_until_parked();
12873
12874 cx.assert_state_with_diff(
12875 r#"
12876 use some::mod1;
12877 use some::mod2;
12878
12879 const A: u32 = 42;
12880 + const B: u32 = 42;
12881 + const C: u32 = 42;
12882 + const D: u32 = 42;
12883 + const E: u32 = 42;
12884 ˇ
12885 fn main() {
12886 println!("hello");
12887
12888 println!("world");
12889 }
12890 "#
12891 .unindent(),
12892 );
12893
12894 cx.update_editor(|editor, cx| {
12895 editor.move_up(&MoveUp, cx);
12896 editor.delete_line(&DeleteLine, cx);
12897 editor.move_up(&MoveUp, cx);
12898 editor.delete_line(&DeleteLine, cx);
12899 editor.move_up(&MoveUp, cx);
12900 editor.delete_line(&DeleteLine, cx);
12901 });
12902 executor.run_until_parked();
12903 cx.assert_state_with_diff(
12904 r#"
12905 use some::mod1;
12906 use some::mod2;
12907
12908 const A: u32 = 42;
12909 + const B: u32 = 42;
12910 ˇ
12911 fn main() {
12912 println!("hello");
12913
12914 println!("world");
12915 }
12916 "#
12917 .unindent(),
12918 );
12919
12920 cx.update_editor(|editor, cx| {
12921 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, cx);
12922 editor.delete_line(&DeleteLine, cx);
12923 });
12924 executor.run_until_parked();
12925 cx.assert_state_with_diff(
12926 r#"
12927 use some::mod1;
12928 - use some::mod2;
12929 -
12930 - const A: u32 = 42;
12931 ˇ
12932 fn main() {
12933 println!("hello");
12934
12935 println!("world");
12936 }
12937 "#
12938 .unindent(),
12939 );
12940}
12941
12942#[gpui::test]
12943async fn test_edits_around_expanded_deletion_hunks(
12944 executor: BackgroundExecutor,
12945 cx: &mut gpui::TestAppContext,
12946) {
12947 init_test(cx, |_| {});
12948
12949 let mut cx = EditorTestContext::new(cx).await;
12950
12951 let diff_base = r#"
12952 use some::mod1;
12953 use some::mod2;
12954
12955 const A: u32 = 42;
12956 const B: u32 = 42;
12957 const C: u32 = 42;
12958
12959
12960 fn main() {
12961 println!("hello");
12962
12963 println!("world");
12964 }
12965 "#
12966 .unindent();
12967 executor.run_until_parked();
12968 cx.set_state(
12969 &r#"
12970 use some::mod1;
12971 use some::mod2;
12972
12973 ˇconst B: u32 = 42;
12974 const C: u32 = 42;
12975
12976
12977 fn main() {
12978 println!("hello");
12979
12980 println!("world");
12981 }
12982 "#
12983 .unindent(),
12984 );
12985
12986 cx.set_diff_base(&diff_base);
12987 executor.run_until_parked();
12988
12989 cx.update_editor(|editor, cx| {
12990 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12991 });
12992 executor.run_until_parked();
12993
12994 cx.assert_state_with_diff(
12995 r#"
12996 use some::mod1;
12997 use some::mod2;
12998
12999 - const A: u32 = 42;
13000 ˇconst B: u32 = 42;
13001 const C: u32 = 42;
13002
13003
13004 fn main() {
13005 println!("hello");
13006
13007 println!("world");
13008 }
13009 "#
13010 .unindent(),
13011 );
13012
13013 cx.update_editor(|editor, cx| {
13014 editor.delete_line(&DeleteLine, cx);
13015 });
13016 executor.run_until_parked();
13017 cx.assert_state_with_diff(
13018 r#"
13019 use some::mod1;
13020 use some::mod2;
13021
13022 - const A: u32 = 42;
13023 - const B: u32 = 42;
13024 ˇconst C: u32 = 42;
13025
13026
13027 fn main() {
13028 println!("hello");
13029
13030 println!("world");
13031 }
13032 "#
13033 .unindent(),
13034 );
13035
13036 cx.update_editor(|editor, cx| {
13037 editor.delete_line(&DeleteLine, cx);
13038 });
13039 executor.run_until_parked();
13040 cx.assert_state_with_diff(
13041 r#"
13042 use some::mod1;
13043 use some::mod2;
13044
13045 - const A: u32 = 42;
13046 - const B: u32 = 42;
13047 - const C: u32 = 42;
13048 ˇ
13049
13050 fn main() {
13051 println!("hello");
13052
13053 println!("world");
13054 }
13055 "#
13056 .unindent(),
13057 );
13058
13059 cx.update_editor(|editor, cx| {
13060 editor.handle_input("replacement", cx);
13061 });
13062 executor.run_until_parked();
13063 cx.assert_state_with_diff(
13064 r#"
13065 use some::mod1;
13066 use some::mod2;
13067
13068 - const A: u32 = 42;
13069 - const B: u32 = 42;
13070 - const C: u32 = 42;
13071 -
13072 + replacementˇ
13073
13074 fn main() {
13075 println!("hello");
13076
13077 println!("world");
13078 }
13079 "#
13080 .unindent(),
13081 );
13082}
13083
13084#[gpui::test]
13085async fn test_edit_after_expanded_modification_hunk(
13086 executor: BackgroundExecutor,
13087 cx: &mut gpui::TestAppContext,
13088) {
13089 init_test(cx, |_| {});
13090
13091 let mut cx = EditorTestContext::new(cx).await;
13092
13093 let diff_base = r#"
13094 use some::mod1;
13095 use some::mod2;
13096
13097 const A: u32 = 42;
13098 const B: u32 = 42;
13099 const C: u32 = 42;
13100 const D: u32 = 42;
13101
13102
13103 fn main() {
13104 println!("hello");
13105
13106 println!("world");
13107 }"#
13108 .unindent();
13109
13110 cx.set_state(
13111 &r#"
13112 use some::mod1;
13113 use some::mod2;
13114
13115 const A: u32 = 42;
13116 const B: u32 = 42;
13117 const C: u32 = 43ˇ
13118 const D: u32 = 42;
13119
13120
13121 fn main() {
13122 println!("hello");
13123
13124 println!("world");
13125 }"#
13126 .unindent(),
13127 );
13128
13129 cx.set_diff_base(&diff_base);
13130 executor.run_until_parked();
13131 cx.update_editor(|editor, cx| {
13132 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
13133 });
13134 executor.run_until_parked();
13135
13136 cx.assert_state_with_diff(
13137 r#"
13138 use some::mod1;
13139 use some::mod2;
13140
13141 const A: u32 = 42;
13142 const B: u32 = 42;
13143 - const C: u32 = 42;
13144 + const C: u32 = 43ˇ
13145 const D: u32 = 42;
13146
13147
13148 fn main() {
13149 println!("hello");
13150
13151 println!("world");
13152 }"#
13153 .unindent(),
13154 );
13155
13156 cx.update_editor(|editor, cx| {
13157 editor.handle_input("\nnew_line\n", cx);
13158 });
13159 executor.run_until_parked();
13160
13161 cx.assert_state_with_diff(
13162 r#"
13163 use some::mod1;
13164 use some::mod2;
13165
13166 const A: u32 = 42;
13167 const B: u32 = 42;
13168 - const C: u32 = 42;
13169 + const C: u32 = 43
13170 + new_line
13171 + ˇ
13172 const D: u32 = 42;
13173
13174
13175 fn main() {
13176 println!("hello");
13177
13178 println!("world");
13179 }"#
13180 .unindent(),
13181 );
13182}
13183
13184async fn setup_indent_guides_editor(
13185 text: &str,
13186 cx: &mut gpui::TestAppContext,
13187) -> (BufferId, EditorTestContext) {
13188 init_test(cx, |_| {});
13189
13190 let mut cx = EditorTestContext::new(cx).await;
13191
13192 let buffer_id = cx.update_editor(|editor, cx| {
13193 editor.set_text(text, cx);
13194 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
13195
13196 buffer_ids[0]
13197 });
13198
13199 (buffer_id, cx)
13200}
13201
13202fn assert_indent_guides(
13203 range: Range<u32>,
13204 expected: Vec<IndentGuide>,
13205 active_indices: Option<Vec<usize>>,
13206 cx: &mut EditorTestContext,
13207) {
13208 let indent_guides = cx.update_editor(|editor, cx| {
13209 let snapshot = editor.snapshot(cx).display_snapshot;
13210 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
13211 editor,
13212 MultiBufferRow(range.start)..MultiBufferRow(range.end),
13213 true,
13214 &snapshot,
13215 cx,
13216 );
13217
13218 indent_guides.sort_by(|a, b| {
13219 a.depth.cmp(&b.depth).then(
13220 a.start_row
13221 .cmp(&b.start_row)
13222 .then(a.end_row.cmp(&b.end_row)),
13223 )
13224 });
13225 indent_guides
13226 });
13227
13228 if let Some(expected) = active_indices {
13229 let active_indices = cx.update_editor(|editor, cx| {
13230 let snapshot = editor.snapshot(cx).display_snapshot;
13231 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, cx)
13232 });
13233
13234 assert_eq!(
13235 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
13236 expected,
13237 "Active indent guide indices do not match"
13238 );
13239 }
13240
13241 let expected: Vec<_> = expected
13242 .into_iter()
13243 .map(|guide| MultiBufferIndentGuide {
13244 multibuffer_row_range: MultiBufferRow(guide.start_row)..MultiBufferRow(guide.end_row),
13245 buffer: guide,
13246 })
13247 .collect();
13248
13249 assert_eq!(indent_guides, expected, "Indent guides do not match");
13250}
13251
13252fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
13253 IndentGuide {
13254 buffer_id,
13255 start_row,
13256 end_row,
13257 depth,
13258 tab_size: 4,
13259 settings: IndentGuideSettings {
13260 enabled: true,
13261 line_width: 1,
13262 active_line_width: 1,
13263 ..Default::default()
13264 },
13265 }
13266}
13267
13268#[gpui::test]
13269async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
13270 let (buffer_id, mut cx) = setup_indent_guides_editor(
13271 &"
13272 fn main() {
13273 let a = 1;
13274 }"
13275 .unindent(),
13276 cx,
13277 )
13278 .await;
13279
13280 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
13281}
13282
13283#[gpui::test]
13284async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) {
13285 let (buffer_id, mut cx) = setup_indent_guides_editor(
13286 &"
13287 fn main() {
13288 let a = 1;
13289 let b = 2;
13290 }"
13291 .unindent(),
13292 cx,
13293 )
13294 .await;
13295
13296 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
13297}
13298
13299#[gpui::test]
13300async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) {
13301 let (buffer_id, mut cx) = setup_indent_guides_editor(
13302 &"
13303 fn main() {
13304 let a = 1;
13305 if a == 3 {
13306 let b = 2;
13307 } else {
13308 let c = 3;
13309 }
13310 }"
13311 .unindent(),
13312 cx,
13313 )
13314 .await;
13315
13316 assert_indent_guides(
13317 0..8,
13318 vec![
13319 indent_guide(buffer_id, 1, 6, 0),
13320 indent_guide(buffer_id, 3, 3, 1),
13321 indent_guide(buffer_id, 5, 5, 1),
13322 ],
13323 None,
13324 &mut cx,
13325 );
13326}
13327
13328#[gpui::test]
13329async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) {
13330 let (buffer_id, mut cx) = setup_indent_guides_editor(
13331 &"
13332 fn main() {
13333 let a = 1;
13334 let b = 2;
13335 let c = 3;
13336 }"
13337 .unindent(),
13338 cx,
13339 )
13340 .await;
13341
13342 assert_indent_guides(
13343 0..5,
13344 vec![
13345 indent_guide(buffer_id, 1, 3, 0),
13346 indent_guide(buffer_id, 2, 2, 1),
13347 ],
13348 None,
13349 &mut cx,
13350 );
13351}
13352
13353#[gpui::test]
13354async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext) {
13355 let (buffer_id, mut cx) = setup_indent_guides_editor(
13356 &"
13357 fn main() {
13358 let a = 1;
13359
13360 let c = 3;
13361 }"
13362 .unindent(),
13363 cx,
13364 )
13365 .await;
13366
13367 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
13368}
13369
13370#[gpui::test]
13371async fn test_indent_guide_complex(cx: &mut gpui::TestAppContext) {
13372 let (buffer_id, mut cx) = setup_indent_guides_editor(
13373 &"
13374 fn main() {
13375 let a = 1;
13376
13377 let c = 3;
13378
13379 if a == 3 {
13380 let b = 2;
13381 } else {
13382 let c = 3;
13383 }
13384 }"
13385 .unindent(),
13386 cx,
13387 )
13388 .await;
13389
13390 assert_indent_guides(
13391 0..11,
13392 vec![
13393 indent_guide(buffer_id, 1, 9, 0),
13394 indent_guide(buffer_id, 6, 6, 1),
13395 indent_guide(buffer_id, 8, 8, 1),
13396 ],
13397 None,
13398 &mut cx,
13399 );
13400}
13401
13402#[gpui::test]
13403async fn test_indent_guide_starts_off_screen(cx: &mut gpui::TestAppContext) {
13404 let (buffer_id, mut cx) = setup_indent_guides_editor(
13405 &"
13406 fn main() {
13407 let a = 1;
13408
13409 let c = 3;
13410
13411 if a == 3 {
13412 let b = 2;
13413 } else {
13414 let c = 3;
13415 }
13416 }"
13417 .unindent(),
13418 cx,
13419 )
13420 .await;
13421
13422 assert_indent_guides(
13423 1..11,
13424 vec![
13425 indent_guide(buffer_id, 1, 9, 0),
13426 indent_guide(buffer_id, 6, 6, 1),
13427 indent_guide(buffer_id, 8, 8, 1),
13428 ],
13429 None,
13430 &mut cx,
13431 );
13432}
13433
13434#[gpui::test]
13435async fn test_indent_guide_ends_off_screen(cx: &mut gpui::TestAppContext) {
13436 let (buffer_id, mut cx) = setup_indent_guides_editor(
13437 &"
13438 fn main() {
13439 let a = 1;
13440
13441 let c = 3;
13442
13443 if a == 3 {
13444 let b = 2;
13445 } else {
13446 let c = 3;
13447 }
13448 }"
13449 .unindent(),
13450 cx,
13451 )
13452 .await;
13453
13454 assert_indent_guides(
13455 1..10,
13456 vec![
13457 indent_guide(buffer_id, 1, 9, 0),
13458 indent_guide(buffer_id, 6, 6, 1),
13459 indent_guide(buffer_id, 8, 8, 1),
13460 ],
13461 None,
13462 &mut cx,
13463 );
13464}
13465
13466#[gpui::test]
13467async fn test_indent_guide_without_brackets(cx: &mut gpui::TestAppContext) {
13468 let (buffer_id, mut cx) = setup_indent_guides_editor(
13469 &"
13470 block1
13471 block2
13472 block3
13473 block4
13474 block2
13475 block1
13476 block1"
13477 .unindent(),
13478 cx,
13479 )
13480 .await;
13481
13482 assert_indent_guides(
13483 1..10,
13484 vec![
13485 indent_guide(buffer_id, 1, 4, 0),
13486 indent_guide(buffer_id, 2, 3, 1),
13487 indent_guide(buffer_id, 3, 3, 2),
13488 ],
13489 None,
13490 &mut cx,
13491 );
13492}
13493
13494#[gpui::test]
13495async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext) {
13496 let (buffer_id, mut cx) = setup_indent_guides_editor(
13497 &"
13498 block1
13499 block2
13500 block3
13501
13502 block1
13503 block1"
13504 .unindent(),
13505 cx,
13506 )
13507 .await;
13508
13509 assert_indent_guides(
13510 0..6,
13511 vec![
13512 indent_guide(buffer_id, 1, 2, 0),
13513 indent_guide(buffer_id, 2, 2, 1),
13514 ],
13515 None,
13516 &mut cx,
13517 );
13518}
13519
13520#[gpui::test]
13521async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext) {
13522 let (buffer_id, mut cx) = setup_indent_guides_editor(
13523 &"
13524 block1
13525
13526
13527
13528 block2
13529 "
13530 .unindent(),
13531 cx,
13532 )
13533 .await;
13534
13535 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
13536}
13537
13538#[gpui::test]
13539async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) {
13540 let (buffer_id, mut cx) = setup_indent_guides_editor(
13541 &"
13542 def a:
13543 \tb = 3
13544 \tif True:
13545 \t\tc = 4
13546 \t\td = 5
13547 \tprint(b)
13548 "
13549 .unindent(),
13550 cx,
13551 )
13552 .await;
13553
13554 assert_indent_guides(
13555 0..6,
13556 vec![
13557 indent_guide(buffer_id, 1, 6, 0),
13558 indent_guide(buffer_id, 3, 4, 1),
13559 ],
13560 None,
13561 &mut cx,
13562 );
13563}
13564
13565#[gpui::test]
13566async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
13567 let (buffer_id, mut cx) = setup_indent_guides_editor(
13568 &"
13569 fn main() {
13570 let a = 1;
13571 }"
13572 .unindent(),
13573 cx,
13574 )
13575 .await;
13576
13577 cx.update_editor(|editor, cx| {
13578 editor.change_selections(None, cx, |s| {
13579 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13580 });
13581 });
13582
13583 assert_indent_guides(
13584 0..3,
13585 vec![indent_guide(buffer_id, 1, 1, 0)],
13586 Some(vec![0]),
13587 &mut cx,
13588 );
13589}
13590
13591#[gpui::test]
13592async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppContext) {
13593 let (buffer_id, mut cx) = setup_indent_guides_editor(
13594 &"
13595 fn main() {
13596 if 1 == 2 {
13597 let a = 1;
13598 }
13599 }"
13600 .unindent(),
13601 cx,
13602 )
13603 .await;
13604
13605 cx.update_editor(|editor, cx| {
13606 editor.change_selections(None, cx, |s| {
13607 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13608 });
13609 });
13610
13611 assert_indent_guides(
13612 0..4,
13613 vec![
13614 indent_guide(buffer_id, 1, 3, 0),
13615 indent_guide(buffer_id, 2, 2, 1),
13616 ],
13617 Some(vec![1]),
13618 &mut cx,
13619 );
13620
13621 cx.update_editor(|editor, cx| {
13622 editor.change_selections(None, cx, |s| {
13623 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13624 });
13625 });
13626
13627 assert_indent_guides(
13628 0..4,
13629 vec![
13630 indent_guide(buffer_id, 1, 3, 0),
13631 indent_guide(buffer_id, 2, 2, 1),
13632 ],
13633 Some(vec![1]),
13634 &mut cx,
13635 );
13636
13637 cx.update_editor(|editor, cx| {
13638 editor.change_selections(None, cx, |s| {
13639 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
13640 });
13641 });
13642
13643 assert_indent_guides(
13644 0..4,
13645 vec![
13646 indent_guide(buffer_id, 1, 3, 0),
13647 indent_guide(buffer_id, 2, 2, 1),
13648 ],
13649 Some(vec![0]),
13650 &mut cx,
13651 );
13652}
13653
13654#[gpui::test]
13655async fn test_active_indent_guide_empty_line(cx: &mut gpui::TestAppContext) {
13656 let (buffer_id, mut cx) = setup_indent_guides_editor(
13657 &"
13658 fn main() {
13659 let a = 1;
13660
13661 let b = 2;
13662 }"
13663 .unindent(),
13664 cx,
13665 )
13666 .await;
13667
13668 cx.update_editor(|editor, cx| {
13669 editor.change_selections(None, cx, |s| {
13670 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13671 });
13672 });
13673
13674 assert_indent_guides(
13675 0..5,
13676 vec![indent_guide(buffer_id, 1, 3, 0)],
13677 Some(vec![0]),
13678 &mut cx,
13679 );
13680}
13681
13682#[gpui::test]
13683async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppContext) {
13684 let (buffer_id, mut cx) = setup_indent_guides_editor(
13685 &"
13686 def m:
13687 a = 1
13688 pass"
13689 .unindent(),
13690 cx,
13691 )
13692 .await;
13693
13694 cx.update_editor(|editor, cx| {
13695 editor.change_selections(None, cx, |s| {
13696 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13697 });
13698 });
13699
13700 assert_indent_guides(
13701 0..3,
13702 vec![indent_guide(buffer_id, 1, 2, 0)],
13703 Some(vec![0]),
13704 &mut cx,
13705 );
13706}
13707
13708#[gpui::test]
13709fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
13710 init_test(cx, |_| {});
13711
13712 let editor = cx.add_window(|cx| {
13713 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
13714 build_editor(buffer, cx)
13715 });
13716
13717 let render_args = Arc::new(Mutex::new(None));
13718 let snapshot = editor
13719 .update(cx, |editor, cx| {
13720 let snapshot = editor.buffer().read(cx).snapshot(cx);
13721 let range =
13722 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
13723
13724 struct RenderArgs {
13725 row: MultiBufferRow,
13726 folded: bool,
13727 callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
13728 }
13729
13730 let crease = Crease::inline(
13731 range,
13732 FoldPlaceholder::test(),
13733 {
13734 let toggle_callback = render_args.clone();
13735 move |row, folded, callback, _cx| {
13736 *toggle_callback.lock() = Some(RenderArgs {
13737 row,
13738 folded,
13739 callback,
13740 });
13741 div()
13742 }
13743 },
13744 |_row, _folded, _cx| div(),
13745 );
13746
13747 editor.insert_creases(Some(crease), cx);
13748 let snapshot = editor.snapshot(cx);
13749 let _div =
13750 snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
13751 snapshot
13752 })
13753 .unwrap();
13754
13755 let render_args = render_args.lock().take().unwrap();
13756 assert_eq!(render_args.row, MultiBufferRow(1));
13757 assert!(!render_args.folded);
13758 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13759
13760 cx.update_window(*editor, |_, cx| (render_args.callback)(true, cx))
13761 .unwrap();
13762 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13763 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
13764
13765 cx.update_window(*editor, |_, cx| (render_args.callback)(false, cx))
13766 .unwrap();
13767 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13768 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13769}
13770
13771#[gpui::test]
13772async fn test_input_text(cx: &mut gpui::TestAppContext) {
13773 init_test(cx, |_| {});
13774 let mut cx = EditorTestContext::new(cx).await;
13775
13776 cx.set_state(
13777 &r#"ˇone
13778 two
13779
13780 three
13781 fourˇ
13782 five
13783
13784 siˇx"#
13785 .unindent(),
13786 );
13787
13788 cx.dispatch_action(HandleInput(String::new()));
13789 cx.assert_editor_state(
13790 &r#"ˇone
13791 two
13792
13793 three
13794 fourˇ
13795 five
13796
13797 siˇx"#
13798 .unindent(),
13799 );
13800
13801 cx.dispatch_action(HandleInput("AAAA".to_string()));
13802 cx.assert_editor_state(
13803 &r#"AAAAˇone
13804 two
13805
13806 three
13807 fourAAAAˇ
13808 five
13809
13810 siAAAAˇx"#
13811 .unindent(),
13812 );
13813}
13814
13815#[gpui::test]
13816async fn test_scroll_cursor_center_top_bottom(cx: &mut gpui::TestAppContext) {
13817 init_test(cx, |_| {});
13818
13819 let mut cx = EditorTestContext::new(cx).await;
13820 cx.set_state(
13821 r#"let foo = 1;
13822let foo = 2;
13823let foo = 3;
13824let fooˇ = 4;
13825let foo = 5;
13826let foo = 6;
13827let foo = 7;
13828let foo = 8;
13829let foo = 9;
13830let foo = 10;
13831let foo = 11;
13832let foo = 12;
13833let foo = 13;
13834let foo = 14;
13835let foo = 15;"#,
13836 );
13837
13838 cx.update_editor(|e, cx| {
13839 assert_eq!(
13840 e.next_scroll_position,
13841 NextScrollCursorCenterTopBottom::Center,
13842 "Default next scroll direction is center",
13843 );
13844
13845 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13846 assert_eq!(
13847 e.next_scroll_position,
13848 NextScrollCursorCenterTopBottom::Top,
13849 "After center, next scroll direction should be top",
13850 );
13851
13852 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13853 assert_eq!(
13854 e.next_scroll_position,
13855 NextScrollCursorCenterTopBottom::Bottom,
13856 "After top, next scroll direction should be bottom",
13857 );
13858
13859 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13860 assert_eq!(
13861 e.next_scroll_position,
13862 NextScrollCursorCenterTopBottom::Center,
13863 "After bottom, scrolling should start over",
13864 );
13865
13866 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13867 assert_eq!(
13868 e.next_scroll_position,
13869 NextScrollCursorCenterTopBottom::Top,
13870 "Scrolling continues if retriggered fast enough"
13871 );
13872 });
13873
13874 cx.executor()
13875 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
13876 cx.executor().run_until_parked();
13877 cx.update_editor(|e, _| {
13878 assert_eq!(
13879 e.next_scroll_position,
13880 NextScrollCursorCenterTopBottom::Center,
13881 "If scrolling is not triggered fast enough, it should reset"
13882 );
13883 });
13884}
13885
13886#[gpui::test]
13887async fn test_goto_definition_with_find_all_references_fallback(cx: &mut gpui::TestAppContext) {
13888 init_test(cx, |_| {});
13889 let mut cx = EditorLspTestContext::new_rust(
13890 lsp::ServerCapabilities {
13891 definition_provider: Some(lsp::OneOf::Left(true)),
13892 references_provider: Some(lsp::OneOf::Left(true)),
13893 ..lsp::ServerCapabilities::default()
13894 },
13895 cx,
13896 )
13897 .await;
13898
13899 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
13900 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
13901 move |params, _| async move {
13902 if empty_go_to_definition {
13903 Ok(None)
13904 } else {
13905 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
13906 uri: params.text_document_position_params.text_document.uri,
13907 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
13908 })))
13909 }
13910 },
13911 );
13912 let references =
13913 cx.lsp
13914 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
13915 Ok(Some(vec![lsp::Location {
13916 uri: params.text_document_position.text_document.uri,
13917 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
13918 }]))
13919 });
13920 (go_to_definition, references)
13921 };
13922
13923 cx.set_state(
13924 &r#"fn one() {
13925 let mut a = ˇtwo();
13926 }
13927
13928 fn two() {}"#
13929 .unindent(),
13930 );
13931 set_up_lsp_handlers(false, &mut cx);
13932 let navigated = cx
13933 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13934 .await
13935 .expect("Failed to navigate to definition");
13936 assert_eq!(
13937 navigated,
13938 Navigated::Yes,
13939 "Should have navigated to definition from the GetDefinition response"
13940 );
13941 cx.assert_editor_state(
13942 &r#"fn one() {
13943 let mut a = two();
13944 }
13945
13946 fn «twoˇ»() {}"#
13947 .unindent(),
13948 );
13949
13950 let editors = cx.update_workspace(|workspace, cx| {
13951 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13952 });
13953 cx.update_editor(|_, test_editor_cx| {
13954 assert_eq!(
13955 editors.len(),
13956 1,
13957 "Initially, only one, test, editor should be open in the workspace"
13958 );
13959 assert_eq!(
13960 test_editor_cx.view(),
13961 editors.last().expect("Asserted len is 1")
13962 );
13963 });
13964
13965 set_up_lsp_handlers(true, &mut cx);
13966 let navigated = cx
13967 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13968 .await
13969 .expect("Failed to navigate to lookup references");
13970 assert_eq!(
13971 navigated,
13972 Navigated::Yes,
13973 "Should have navigated to references as a fallback after empty GoToDefinition response"
13974 );
13975 // We should not change the selections in the existing file,
13976 // if opening another milti buffer with the references
13977 cx.assert_editor_state(
13978 &r#"fn one() {
13979 let mut a = two();
13980 }
13981
13982 fn «twoˇ»() {}"#
13983 .unindent(),
13984 );
13985 let editors = cx.update_workspace(|workspace, cx| {
13986 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13987 });
13988 cx.update_editor(|_, test_editor_cx| {
13989 assert_eq!(
13990 editors.len(),
13991 2,
13992 "After falling back to references search, we open a new editor with the results"
13993 );
13994 let references_fallback_text = editors
13995 .into_iter()
13996 .find(|new_editor| new_editor != test_editor_cx.view())
13997 .expect("Should have one non-test editor now")
13998 .read(test_editor_cx)
13999 .text(test_editor_cx);
14000 assert_eq!(
14001 references_fallback_text, "fn one() {\n let mut a = two();\n}",
14002 "Should use the range from the references response and not the GoToDefinition one"
14003 );
14004 });
14005}
14006
14007#[gpui::test]
14008async fn test_find_enclosing_node_with_task(cx: &mut gpui::TestAppContext) {
14009 init_test(cx, |_| {});
14010
14011 let language = Arc::new(Language::new(
14012 LanguageConfig::default(),
14013 Some(tree_sitter_rust::LANGUAGE.into()),
14014 ));
14015
14016 let text = r#"
14017 #[cfg(test)]
14018 mod tests() {
14019 #[test]
14020 fn runnable_1() {
14021 let a = 1;
14022 }
14023
14024 #[test]
14025 fn runnable_2() {
14026 let a = 1;
14027 let b = 2;
14028 }
14029 }
14030 "#
14031 .unindent();
14032
14033 let fs = FakeFs::new(cx.executor());
14034 fs.insert_file("/file.rs", Default::default()).await;
14035
14036 let project = Project::test(fs, ["/a".as_ref()], cx).await;
14037 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
14038 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14039 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
14040 let multi_buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx));
14041
14042 let editor = cx.new_view(|cx| {
14043 Editor::new(
14044 EditorMode::Full,
14045 multi_buffer,
14046 Some(project.clone()),
14047 true,
14048 cx,
14049 )
14050 });
14051
14052 editor.update(cx, |editor, cx| {
14053 editor.tasks.insert(
14054 (buffer.read(cx).remote_id(), 3),
14055 RunnableTasks {
14056 templates: vec![],
14057 offset: MultiBufferOffset(43),
14058 column: 0,
14059 extra_variables: HashMap::default(),
14060 context_range: BufferOffset(43)..BufferOffset(85),
14061 },
14062 );
14063 editor.tasks.insert(
14064 (buffer.read(cx).remote_id(), 8),
14065 RunnableTasks {
14066 templates: vec![],
14067 offset: MultiBufferOffset(86),
14068 column: 0,
14069 extra_variables: HashMap::default(),
14070 context_range: BufferOffset(86)..BufferOffset(191),
14071 },
14072 );
14073
14074 // Test finding task when cursor is inside function body
14075 editor.change_selections(None, cx, |s| {
14076 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
14077 });
14078 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
14079 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
14080
14081 // Test finding task when cursor is on function name
14082 editor.change_selections(None, cx, |s| {
14083 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
14084 });
14085 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
14086 assert_eq!(row, 8, "Should find task when cursor is on function name");
14087 });
14088}
14089
14090#[gpui::test]
14091async fn test_multi_buffer_folding(cx: &mut gpui::TestAppContext) {
14092 init_test(cx, |_| {});
14093
14094 let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
14095 let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
14096 let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
14097
14098 let fs = FakeFs::new(cx.executor());
14099 fs.insert_tree(
14100 "/a",
14101 json!({
14102 "first.rs": sample_text_1,
14103 "second.rs": sample_text_2,
14104 "third.rs": sample_text_3,
14105 }),
14106 )
14107 .await;
14108 let project = Project::test(fs, ["/a".as_ref()], cx).await;
14109 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
14110 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14111 let worktree = project.update(cx, |project, cx| {
14112 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
14113 assert_eq!(worktrees.len(), 1);
14114 worktrees.pop().unwrap()
14115 });
14116 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
14117
14118 let buffer_1 = project
14119 .update(cx, |project, cx| {
14120 project.open_buffer((worktree_id, "first.rs"), cx)
14121 })
14122 .await
14123 .unwrap();
14124 let buffer_2 = project
14125 .update(cx, |project, cx| {
14126 project.open_buffer((worktree_id, "second.rs"), cx)
14127 })
14128 .await
14129 .unwrap();
14130 let buffer_3 = project
14131 .update(cx, |project, cx| {
14132 project.open_buffer((worktree_id, "third.rs"), cx)
14133 })
14134 .await
14135 .unwrap();
14136
14137 let multi_buffer = cx.new_model(|cx| {
14138 let mut multi_buffer = MultiBuffer::new(ReadWrite);
14139 multi_buffer.push_excerpts(
14140 buffer_1.clone(),
14141 [
14142 ExcerptRange {
14143 context: Point::new(0, 0)..Point::new(3, 0),
14144 primary: None,
14145 },
14146 ExcerptRange {
14147 context: Point::new(5, 0)..Point::new(7, 0),
14148 primary: None,
14149 },
14150 ExcerptRange {
14151 context: Point::new(9, 0)..Point::new(10, 4),
14152 primary: None,
14153 },
14154 ],
14155 cx,
14156 );
14157 multi_buffer.push_excerpts(
14158 buffer_2.clone(),
14159 [
14160 ExcerptRange {
14161 context: Point::new(0, 0)..Point::new(3, 0),
14162 primary: None,
14163 },
14164 ExcerptRange {
14165 context: Point::new(5, 0)..Point::new(7, 0),
14166 primary: None,
14167 },
14168 ExcerptRange {
14169 context: Point::new(9, 0)..Point::new(10, 4),
14170 primary: None,
14171 },
14172 ],
14173 cx,
14174 );
14175 multi_buffer.push_excerpts(
14176 buffer_3.clone(),
14177 [
14178 ExcerptRange {
14179 context: Point::new(0, 0)..Point::new(3, 0),
14180 primary: None,
14181 },
14182 ExcerptRange {
14183 context: Point::new(5, 0)..Point::new(7, 0),
14184 primary: None,
14185 },
14186 ExcerptRange {
14187 context: Point::new(9, 0)..Point::new(10, 4),
14188 primary: None,
14189 },
14190 ],
14191 cx,
14192 );
14193 multi_buffer
14194 });
14195 let multi_buffer_editor = cx.new_view(|cx| {
14196 Editor::new(
14197 EditorMode::Full,
14198 multi_buffer,
14199 Some(project.clone()),
14200 true,
14201 cx,
14202 )
14203 });
14204
14205 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";
14206 assert_eq!(
14207 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14208 full_text,
14209 );
14210
14211 multi_buffer_editor.update(cx, |editor, cx| {
14212 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
14213 });
14214 assert_eq!(
14215 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14216 "\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",
14217 "After folding the first buffer, its text should not be displayed"
14218 );
14219
14220 multi_buffer_editor.update(cx, |editor, cx| {
14221 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
14222 });
14223 assert_eq!(
14224 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14225 "\n\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n",
14226 "After folding the second buffer, its text should not be displayed"
14227 );
14228
14229 multi_buffer_editor.update(cx, |editor, cx| {
14230 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
14231 });
14232 assert_eq!(
14233 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14234 "\n\n\n\n\n",
14235 "After folding the third buffer, its text should not be displayed"
14236 );
14237
14238 // Emulate selection inside the fold logic, that should work
14239 multi_buffer_editor.update(cx, |editor, cx| {
14240 editor.snapshot(cx).next_line_boundary(Point::new(0, 4));
14241 });
14242
14243 multi_buffer_editor.update(cx, |editor, cx| {
14244 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
14245 });
14246 assert_eq!(
14247 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14248 "\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n",
14249 "After unfolding the second buffer, its text should be displayed"
14250 );
14251
14252 multi_buffer_editor.update(cx, |editor, cx| {
14253 editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
14254 });
14255 assert_eq!(
14256 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14257 "\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",
14258 "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
14259 );
14260
14261 multi_buffer_editor.update(cx, |editor, cx| {
14262 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
14263 });
14264 assert_eq!(
14265 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14266 full_text,
14267 "After unfolding the all buffers, all original text should be displayed"
14268 );
14269}
14270
14271#[gpui::test]
14272async fn test_multi_buffer_single_excerpts_folding(cx: &mut gpui::TestAppContext) {
14273 init_test(cx, |_| {});
14274
14275 let sample_text_1 = "1111\n2222\n3333".to_string();
14276 let sample_text_2 = "4444\n5555\n6666".to_string();
14277 let sample_text_3 = "7777\n8888\n9999".to_string();
14278
14279 let fs = FakeFs::new(cx.executor());
14280 fs.insert_tree(
14281 "/a",
14282 json!({
14283 "first.rs": sample_text_1,
14284 "second.rs": sample_text_2,
14285 "third.rs": sample_text_3,
14286 }),
14287 )
14288 .await;
14289 let project = Project::test(fs, ["/a".as_ref()], cx).await;
14290 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
14291 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14292 let worktree = project.update(cx, |project, cx| {
14293 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
14294 assert_eq!(worktrees.len(), 1);
14295 worktrees.pop().unwrap()
14296 });
14297 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
14298
14299 let buffer_1 = project
14300 .update(cx, |project, cx| {
14301 project.open_buffer((worktree_id, "first.rs"), cx)
14302 })
14303 .await
14304 .unwrap();
14305 let buffer_2 = project
14306 .update(cx, |project, cx| {
14307 project.open_buffer((worktree_id, "second.rs"), cx)
14308 })
14309 .await
14310 .unwrap();
14311 let buffer_3 = project
14312 .update(cx, |project, cx| {
14313 project.open_buffer((worktree_id, "third.rs"), cx)
14314 })
14315 .await
14316 .unwrap();
14317
14318 let multi_buffer = cx.new_model(|cx| {
14319 let mut multi_buffer = MultiBuffer::new(ReadWrite);
14320 multi_buffer.push_excerpts(
14321 buffer_1.clone(),
14322 [ExcerptRange {
14323 context: Point::new(0, 0)..Point::new(3, 0),
14324 primary: None,
14325 }],
14326 cx,
14327 );
14328 multi_buffer.push_excerpts(
14329 buffer_2.clone(),
14330 [ExcerptRange {
14331 context: Point::new(0, 0)..Point::new(3, 0),
14332 primary: None,
14333 }],
14334 cx,
14335 );
14336 multi_buffer.push_excerpts(
14337 buffer_3.clone(),
14338 [ExcerptRange {
14339 context: Point::new(0, 0)..Point::new(3, 0),
14340 primary: None,
14341 }],
14342 cx,
14343 );
14344 multi_buffer
14345 });
14346
14347 let multi_buffer_editor = cx.new_view(|cx| {
14348 Editor::new(
14349 EditorMode::Full,
14350 multi_buffer,
14351 Some(project.clone()),
14352 true,
14353 cx,
14354 )
14355 });
14356
14357 let full_text = "\n\n\n1111\n2222\n3333\n\n\n\n\n4444\n5555\n6666\n\n\n\n\n7777\n8888\n9999\n";
14358 assert_eq!(
14359 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14360 full_text,
14361 );
14362
14363 multi_buffer_editor.update(cx, |editor, cx| {
14364 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
14365 });
14366 assert_eq!(
14367 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14368 "\n\n\n\n\n4444\n5555\n6666\n\n\n\n\n7777\n8888\n9999\n",
14369 "After folding the first buffer, its text should not be displayed"
14370 );
14371
14372 multi_buffer_editor.update(cx, |editor, cx| {
14373 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
14374 });
14375
14376 assert_eq!(
14377 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14378 "\n\n\n\n\n\n\n7777\n8888\n9999\n",
14379 "After folding the second buffer, its text should not be displayed"
14380 );
14381
14382 multi_buffer_editor.update(cx, |editor, cx| {
14383 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
14384 });
14385 assert_eq!(
14386 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14387 "\n\n\n\n\n",
14388 "After folding the third buffer, its text should not be displayed"
14389 );
14390
14391 multi_buffer_editor.update(cx, |editor, cx| {
14392 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
14393 });
14394 assert_eq!(
14395 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14396 "\n\n\n\n\n4444\n5555\n6666\n\n\n",
14397 "After unfolding the second buffer, its text should be displayed"
14398 );
14399
14400 multi_buffer_editor.update(cx, |editor, cx| {
14401 editor.unfold_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\n1111\n2222\n3333\n\n\n\n\n4444\n5555\n6666\n\n\n",
14406 "After unfolding the first buffer, its text should be displayed"
14407 );
14408
14409 multi_buffer_editor.update(cx, |editor, cx| {
14410 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
14411 });
14412 assert_eq!(
14413 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14414 full_text,
14415 "After unfolding all buffers, all original text should be displayed"
14416 );
14417}
14418
14419#[gpui::test]
14420async fn test_multi_buffer_with_single_excerpt_folding(cx: &mut gpui::TestAppContext) {
14421 init_test(cx, |_| {});
14422
14423 let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
14424
14425 let fs = FakeFs::new(cx.executor());
14426 fs.insert_tree(
14427 "/a",
14428 json!({
14429 "main.rs": sample_text,
14430 }),
14431 )
14432 .await;
14433 let project = Project::test(fs, ["/a".as_ref()], cx).await;
14434 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
14435 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14436 let worktree = project.update(cx, |project, cx| {
14437 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
14438 assert_eq!(worktrees.len(), 1);
14439 worktrees.pop().unwrap()
14440 });
14441 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
14442
14443 let buffer_1 = project
14444 .update(cx, |project, cx| {
14445 project.open_buffer((worktree_id, "main.rs"), cx)
14446 })
14447 .await
14448 .unwrap();
14449
14450 let multi_buffer = cx.new_model(|cx| {
14451 let mut multi_buffer = MultiBuffer::new(ReadWrite);
14452 multi_buffer.push_excerpts(
14453 buffer_1.clone(),
14454 [ExcerptRange {
14455 context: Point::new(0, 0)
14456 ..Point::new(
14457 sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
14458 0,
14459 ),
14460 primary: None,
14461 }],
14462 cx,
14463 );
14464 multi_buffer
14465 });
14466 let multi_buffer_editor = cx.new_view(|cx| {
14467 Editor::new(
14468 EditorMode::Full,
14469 multi_buffer,
14470 Some(project.clone()),
14471 true,
14472 cx,
14473 )
14474 });
14475
14476 let selection_range = Point::new(1, 0)..Point::new(2, 0);
14477 multi_buffer_editor.update(cx, |editor, cx| {
14478 enum TestHighlight {}
14479 let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
14480 let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
14481 editor.highlight_text::<TestHighlight>(
14482 vec![highlight_range.clone()],
14483 HighlightStyle::color(Hsla::green()),
14484 cx,
14485 );
14486 editor.change_selections(None, cx, |s| s.select_ranges(Some(highlight_range)));
14487 });
14488
14489 let full_text = format!("\n\n\n{sample_text}\n");
14490 assert_eq!(
14491 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14492 full_text,
14493 );
14494}
14495
14496#[gpui::test]
14497fn test_inline_completion_text(cx: &mut TestAppContext) {
14498 init_test(cx, |_| {});
14499
14500 // Simple insertion
14501 {
14502 let window = cx.add_window(|cx| {
14503 let buffer = MultiBuffer::build_simple("Hello, world!", cx);
14504 Editor::new(EditorMode::Full, buffer, None, true, cx)
14505 });
14506 let cx = &mut VisualTestContext::from_window(*window, cx);
14507
14508 window
14509 .update(cx, |editor, cx| {
14510 let snapshot = editor.snapshot(cx);
14511 let edit_range = snapshot.buffer_snapshot.anchor_after(Point::new(0, 6))
14512 ..snapshot.buffer_snapshot.anchor_before(Point::new(0, 6));
14513 let edits = vec![(edit_range, " beautiful".to_string())];
14514
14515 let InlineCompletionText::Edit { text, highlights } =
14516 inline_completion_edit_text(&snapshot, &edits, false, cx)
14517 else {
14518 panic!("Failed to generate inline completion text");
14519 };
14520
14521 assert_eq!(text, "Hello, beautiful world!");
14522 assert_eq!(highlights.len(), 1);
14523 assert_eq!(highlights[0].0, 6..16);
14524 assert_eq!(
14525 highlights[0].1.background_color,
14526 Some(cx.theme().status().created_background)
14527 );
14528 })
14529 .unwrap();
14530 }
14531
14532 // Replacement
14533 {
14534 let window = cx.add_window(|cx| {
14535 let buffer = MultiBuffer::build_simple("This is a test.", cx);
14536 Editor::new(EditorMode::Full, buffer, None, true, cx)
14537 });
14538 let cx = &mut VisualTestContext::from_window(*window, cx);
14539
14540 window
14541 .update(cx, |editor, cx| {
14542 let snapshot = editor.snapshot(cx);
14543 let edits = vec![(
14544 snapshot.buffer_snapshot.anchor_after(Point::new(0, 0))
14545 ..snapshot.buffer_snapshot.anchor_before(Point::new(0, 4)),
14546 "That".to_string(),
14547 )];
14548
14549 let InlineCompletionText::Edit { text, highlights } =
14550 inline_completion_edit_text(&snapshot, &edits, false, cx)
14551 else {
14552 panic!("Failed to generate inline completion text");
14553 };
14554
14555 assert_eq!(text, "That is a test.");
14556 assert_eq!(highlights.len(), 1);
14557 assert_eq!(highlights[0].0, 0..4);
14558 assert_eq!(
14559 highlights[0].1.background_color,
14560 Some(cx.theme().status().created_background)
14561 );
14562 })
14563 .unwrap();
14564 }
14565
14566 // Multiple edits
14567 {
14568 let window = cx.add_window(|cx| {
14569 let buffer = MultiBuffer::build_simple("Hello, world!", cx);
14570 Editor::new(EditorMode::Full, buffer, None, true, cx)
14571 });
14572 let cx = &mut VisualTestContext::from_window(*window, cx);
14573
14574 window
14575 .update(cx, |editor, cx| {
14576 let snapshot = editor.snapshot(cx);
14577 let edits = vec![
14578 (
14579 snapshot.buffer_snapshot.anchor_after(Point::new(0, 0))
14580 ..snapshot.buffer_snapshot.anchor_before(Point::new(0, 5)),
14581 "Greetings".into(),
14582 ),
14583 (
14584 snapshot.buffer_snapshot.anchor_after(Point::new(0, 12))
14585 ..snapshot.buffer_snapshot.anchor_before(Point::new(0, 12)),
14586 " and universe".into(),
14587 ),
14588 ];
14589
14590 let InlineCompletionText::Edit { text, highlights } =
14591 inline_completion_edit_text(&snapshot, &edits, false, cx)
14592 else {
14593 panic!("Failed to generate inline completion text");
14594 };
14595
14596 assert_eq!(text, "Greetings, world and universe!");
14597 assert_eq!(highlights.len(), 2);
14598 assert_eq!(highlights[0].0, 0..9);
14599 assert_eq!(highlights[1].0, 16..29);
14600 assert_eq!(
14601 highlights[0].1.background_color,
14602 Some(cx.theme().status().created_background)
14603 );
14604 assert_eq!(
14605 highlights[1].1.background_color,
14606 Some(cx.theme().status().created_background)
14607 );
14608 })
14609 .unwrap();
14610 }
14611
14612 // Multiple lines with edits
14613 {
14614 let window = cx.add_window(|cx| {
14615 let buffer =
14616 MultiBuffer::build_simple("First line\nSecond line\nThird line\nFourth line", cx);
14617 Editor::new(EditorMode::Full, buffer, None, true, cx)
14618 });
14619 let cx = &mut VisualTestContext::from_window(*window, cx);
14620
14621 window
14622 .update(cx, |editor, cx| {
14623 let snapshot = editor.snapshot(cx);
14624 let edits = vec![
14625 (
14626 snapshot.buffer_snapshot.anchor_before(Point::new(1, 7))
14627 ..snapshot.buffer_snapshot.anchor_before(Point::new(1, 11)),
14628 "modified".to_string(),
14629 ),
14630 (
14631 snapshot.buffer_snapshot.anchor_before(Point::new(2, 0))
14632 ..snapshot.buffer_snapshot.anchor_before(Point::new(2, 10)),
14633 "New third line".to_string(),
14634 ),
14635 (
14636 snapshot.buffer_snapshot.anchor_before(Point::new(3, 6))
14637 ..snapshot.buffer_snapshot.anchor_before(Point::new(3, 6)),
14638 " updated".to_string(),
14639 ),
14640 ];
14641
14642 let InlineCompletionText::Edit { text, highlights } =
14643 inline_completion_edit_text(&snapshot, &edits, false, cx)
14644 else {
14645 panic!("Failed to generate inline completion text");
14646 };
14647
14648 assert_eq!(text, "Second modified\nNew third line\nFourth updated line");
14649 assert_eq!(highlights.len(), 3);
14650 assert_eq!(highlights[0].0, 7..15); // "modified"
14651 assert_eq!(highlights[1].0, 16..30); // "New third line"
14652 assert_eq!(highlights[2].0, 37..45); // " updated"
14653
14654 for highlight in &highlights {
14655 assert_eq!(
14656 highlight.1.background_color,
14657 Some(cx.theme().status().created_background)
14658 );
14659 }
14660 })
14661 .unwrap();
14662 }
14663}
14664
14665#[gpui::test]
14666fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
14667 init_test(cx, |_| {});
14668
14669 // Deletion
14670 {
14671 let window = cx.add_window(|cx| {
14672 let buffer = MultiBuffer::build_simple("Hello, world!", cx);
14673 Editor::new(EditorMode::Full, buffer, None, true, cx)
14674 });
14675 let cx = &mut VisualTestContext::from_window(*window, cx);
14676
14677 window
14678 .update(cx, |editor, cx| {
14679 let snapshot = editor.snapshot(cx);
14680 let edit_range = snapshot.buffer_snapshot.anchor_after(Point::new(0, 5))
14681 ..snapshot.buffer_snapshot.anchor_before(Point::new(0, 11));
14682 let edits = vec![(edit_range, "".to_string())];
14683
14684 let InlineCompletionText::Edit { text, highlights } =
14685 inline_completion_edit_text(&snapshot, &edits, true, cx)
14686 else {
14687 panic!("Failed to generate inline completion text");
14688 };
14689
14690 assert_eq!(text, "Hello, world!");
14691 assert_eq!(highlights.len(), 1);
14692 assert_eq!(highlights[0].0, 5..11);
14693 assert_eq!(
14694 highlights[0].1.background_color,
14695 Some(cx.theme().status().deleted_background)
14696 );
14697 })
14698 .unwrap();
14699 }
14700
14701 // Insertion
14702 {
14703 let window = cx.add_window(|cx| {
14704 let buffer = MultiBuffer::build_simple("Hello, world!", cx);
14705 Editor::new(EditorMode::Full, buffer, None, true, cx)
14706 });
14707 let cx = &mut VisualTestContext::from_window(*window, cx);
14708
14709 window
14710 .update(cx, |editor, cx| {
14711 let snapshot = editor.snapshot(cx);
14712 let edit_range = snapshot.buffer_snapshot.anchor_after(Point::new(0, 6))
14713 ..snapshot.buffer_snapshot.anchor_before(Point::new(0, 6));
14714 let edits = vec![(edit_range, " digital".to_string())];
14715
14716 let InlineCompletionText::Edit { text, highlights } =
14717 inline_completion_edit_text(&snapshot, &edits, true, cx)
14718 else {
14719 panic!("Failed to generate inline completion text");
14720 };
14721
14722 assert_eq!(text, "Hello, digital world!");
14723 assert_eq!(highlights.len(), 1);
14724 assert_eq!(highlights[0].0, 6..14);
14725 assert_eq!(
14726 highlights[0].1.background_color,
14727 Some(cx.theme().status().created_background)
14728 );
14729 })
14730 .unwrap();
14731 }
14732}
14733
14734#[gpui::test]
14735async fn test_rename_with_duplicate_edits(cx: &mut gpui::TestAppContext) {
14736 init_test(cx, |_| {});
14737 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14738
14739 cx.set_state(indoc! {"
14740 struct Fˇoo {}
14741 "});
14742
14743 cx.update_editor(|editor, cx| {
14744 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
14745 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
14746 editor.highlight_background::<DocumentHighlightRead>(
14747 &[highlight_range],
14748 |c| c.editor_document_highlight_read_background,
14749 cx,
14750 );
14751 });
14752
14753 cx.update_editor(|e, cx| e.rename(&Rename, cx))
14754 .expect("Rename was not started")
14755 .await
14756 .expect("Rename failed");
14757 let mut rename_handler =
14758 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
14759 let edit = lsp::TextEdit {
14760 range: lsp::Range {
14761 start: lsp::Position {
14762 line: 0,
14763 character: 7,
14764 },
14765 end: lsp::Position {
14766 line: 0,
14767 character: 10,
14768 },
14769 },
14770 new_text: "FooRenamed".to_string(),
14771 };
14772 Ok(Some(lsp::WorkspaceEdit::new(
14773 // Specify the same edit twice
14774 std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
14775 )))
14776 });
14777 cx.update_editor(|e, cx| e.confirm_rename(&ConfirmRename, cx))
14778 .expect("Confirm rename was not started")
14779 .await
14780 .expect("Confirm rename failed");
14781 rename_handler.next().await.unwrap();
14782 cx.run_until_parked();
14783
14784 // Despite two edits, only one is actually applied as those are identical
14785 cx.assert_editor_state(indoc! {"
14786 struct FooRenamedˇ {}
14787 "});
14788}
14789
14790fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
14791 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
14792 point..point
14793}
14794
14795fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
14796 let (text, ranges) = marked_text_ranges(marked_text, true);
14797 assert_eq!(view.text(cx), text);
14798 assert_eq!(
14799 view.selections.ranges(cx),
14800 ranges,
14801 "Assert selections are {}",
14802 marked_text
14803 );
14804}
14805
14806pub fn handle_signature_help_request(
14807 cx: &mut EditorLspTestContext,
14808 mocked_response: lsp::SignatureHelp,
14809) -> impl Future<Output = ()> {
14810 let mut request =
14811 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
14812 let mocked_response = mocked_response.clone();
14813 async move { Ok(Some(mocked_response)) }
14814 });
14815
14816 async move {
14817 request.next().await;
14818 }
14819}
14820
14821/// Handle completion request passing a marked string specifying where the completion
14822/// should be triggered from using '|' character, what range should be replaced, and what completions
14823/// should be returned using '<' and '>' to delimit the range
14824pub fn handle_completion_request(
14825 cx: &mut EditorLspTestContext,
14826 marked_string: &str,
14827 completions: Vec<&'static str>,
14828 counter: Arc<AtomicUsize>,
14829) -> impl Future<Output = ()> {
14830 let complete_from_marker: TextRangeMarker = '|'.into();
14831 let replace_range_marker: TextRangeMarker = ('<', '>').into();
14832 let (_, mut marked_ranges) = marked_text_ranges_by(
14833 marked_string,
14834 vec![complete_from_marker.clone(), replace_range_marker.clone()],
14835 );
14836
14837 let complete_from_position =
14838 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
14839 let replace_range =
14840 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
14841
14842 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
14843 let completions = completions.clone();
14844 counter.fetch_add(1, atomic::Ordering::Release);
14845 async move {
14846 assert_eq!(params.text_document_position.text_document.uri, url.clone());
14847 assert_eq!(
14848 params.text_document_position.position,
14849 complete_from_position
14850 );
14851 Ok(Some(lsp::CompletionResponse::Array(
14852 completions
14853 .iter()
14854 .map(|completion_text| lsp::CompletionItem {
14855 label: completion_text.to_string(),
14856 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14857 range: replace_range,
14858 new_text: completion_text.to_string(),
14859 })),
14860 ..Default::default()
14861 })
14862 .collect(),
14863 )))
14864 }
14865 });
14866
14867 async move {
14868 request.next().await;
14869 }
14870}
14871
14872fn handle_resolve_completion_request(
14873 cx: &mut EditorLspTestContext,
14874 edits: Option<Vec<(&'static str, &'static str)>>,
14875) -> impl Future<Output = ()> {
14876 let edits = edits.map(|edits| {
14877 edits
14878 .iter()
14879 .map(|(marked_string, new_text)| {
14880 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
14881 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
14882 lsp::TextEdit::new(replace_range, new_text.to_string())
14883 })
14884 .collect::<Vec<_>>()
14885 });
14886
14887 let mut request =
14888 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
14889 let edits = edits.clone();
14890 async move {
14891 Ok(lsp::CompletionItem {
14892 additional_text_edits: edits,
14893 ..Default::default()
14894 })
14895 }
14896 });
14897
14898 async move {
14899 request.next().await;
14900 }
14901}
14902
14903pub(crate) fn update_test_language_settings(
14904 cx: &mut TestAppContext,
14905 f: impl Fn(&mut AllLanguageSettingsContent),
14906) {
14907 cx.update(|cx| {
14908 SettingsStore::update_global(cx, |store, cx| {
14909 store.update_user_settings::<AllLanguageSettings>(cx, f);
14910 });
14911 });
14912}
14913
14914pub(crate) fn update_test_project_settings(
14915 cx: &mut TestAppContext,
14916 f: impl Fn(&mut ProjectSettings),
14917) {
14918 cx.update(|cx| {
14919 SettingsStore::update_global(cx, |store, cx| {
14920 store.update_user_settings::<ProjectSettings>(cx, f);
14921 });
14922 });
14923}
14924
14925pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
14926 cx.update(|cx| {
14927 assets::Assets.load_test_fonts(cx);
14928 let store = SettingsStore::test(cx);
14929 cx.set_global(store);
14930 theme::init(theme::LoadThemes::JustBase, cx);
14931 release_channel::init(SemanticVersion::default(), cx);
14932 client::init_settings(cx);
14933 language::init(cx);
14934 Project::init_settings(cx);
14935 workspace::init_settings(cx);
14936 crate::init(cx);
14937 });
14938
14939 update_test_language_settings(cx, f);
14940}
14941
14942#[track_caller]
14943fn assert_hunk_revert(
14944 not_reverted_text_with_selections: &str,
14945 expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
14946 expected_reverted_text_with_selections: &str,
14947 base_text: &str,
14948 cx: &mut EditorLspTestContext,
14949) {
14950 cx.set_state(not_reverted_text_with_selections);
14951 cx.set_diff_base(base_text);
14952 cx.executor().run_until_parked();
14953
14954 let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
14955 let snapshot = editor.snapshot(cx);
14956 let reverted_hunk_statuses = snapshot
14957 .diff_map
14958 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len(), &snapshot.buffer_snapshot)
14959 .map(|hunk| hunk_status(&hunk))
14960 .collect::<Vec<_>>();
14961
14962 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
14963 reverted_hunk_statuses
14964 });
14965 cx.executor().run_until_parked();
14966 cx.assert_editor_state(expected_reverted_text_with_selections);
14967 assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
14968}