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) @content
5966 (#set! "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.read().is_none()));
8350 cx.simulate_keystroke("s");
8351 assert!(cx.editor(|e, _| e.context_menu.read().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 handle_resolve_completion_request(&mut cx, None).await;
8406 apply_additional_edits.await.unwrap();
8407
8408 update_test_language_settings(&mut cx, |settings| {
8409 settings.defaults.show_completions_on_input = Some(false);
8410 });
8411 cx.set_state("editorˇ");
8412 cx.simulate_keystroke(".");
8413 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8414 cx.simulate_keystroke("c");
8415 cx.simulate_keystroke("l");
8416 cx.simulate_keystroke("o");
8417 cx.assert_editor_state("editor.cloˇ");
8418 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8419 cx.update_editor(|editor, cx| {
8420 editor.show_completions(&ShowCompletions { trigger: None }, cx);
8421 });
8422 handle_completion_request(
8423 &mut cx,
8424 "editor.<clo|>",
8425 vec!["close", "clobber"],
8426 counter.clone(),
8427 )
8428 .await;
8429 cx.condition(|editor, _| editor.context_menu_visible())
8430 .await;
8431 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
8432
8433 let apply_additional_edits = cx.update_editor(|editor, cx| {
8434 editor
8435 .confirm_completion(&ConfirmCompletion::default(), cx)
8436 .unwrap()
8437 });
8438 cx.assert_editor_state("editor.closeˇ");
8439 handle_resolve_completion_request(&mut cx, None).await;
8440 apply_additional_edits.await.unwrap();
8441}
8442
8443#[gpui::test]
8444async fn test_completion_page_up_down_keys(cx: &mut gpui::TestAppContext) {
8445 init_test(cx, |_| {});
8446 let mut cx = EditorLspTestContext::new_rust(
8447 lsp::ServerCapabilities {
8448 completion_provider: Some(lsp::CompletionOptions {
8449 trigger_characters: Some(vec![".".to_string()]),
8450 ..Default::default()
8451 }),
8452 ..Default::default()
8453 },
8454 cx,
8455 )
8456 .await;
8457 cx.lsp
8458 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8459 Ok(Some(lsp::CompletionResponse::Array(vec![
8460 lsp::CompletionItem {
8461 label: "first".into(),
8462 ..Default::default()
8463 },
8464 lsp::CompletionItem {
8465 label: "last".into(),
8466 ..Default::default()
8467 },
8468 ])))
8469 });
8470 cx.set_state("variableˇ");
8471 cx.simulate_keystroke(".");
8472 cx.executor().run_until_parked();
8473
8474 cx.update_editor(|editor, _| {
8475 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8476 assert_eq!(
8477 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8478 &["first", "last"]
8479 );
8480 } else {
8481 panic!("expected completion menu to be open");
8482 }
8483 });
8484
8485 cx.update_editor(|editor, cx| {
8486 editor.move_page_down(&MovePageDown::default(), cx);
8487 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8488 assert!(
8489 menu.selected_item == 1,
8490 "expected PageDown to select the last item from the context menu"
8491 );
8492 } else {
8493 panic!("expected completion menu to stay open after PageDown");
8494 }
8495 });
8496
8497 cx.update_editor(|editor, cx| {
8498 editor.move_page_up(&MovePageUp::default(), cx);
8499 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8500 assert!(
8501 menu.selected_item == 0,
8502 "expected PageUp to select the first item from the context menu"
8503 );
8504 } else {
8505 panic!("expected completion menu to stay open after PageUp");
8506 }
8507 });
8508}
8509
8510#[gpui::test]
8511async fn test_completion_sort(cx: &mut gpui::TestAppContext) {
8512 init_test(cx, |_| {});
8513 let mut cx = EditorLspTestContext::new_rust(
8514 lsp::ServerCapabilities {
8515 completion_provider: Some(lsp::CompletionOptions {
8516 trigger_characters: Some(vec![".".to_string()]),
8517 ..Default::default()
8518 }),
8519 ..Default::default()
8520 },
8521 cx,
8522 )
8523 .await;
8524 cx.lsp
8525 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8526 Ok(Some(lsp::CompletionResponse::Array(vec![
8527 lsp::CompletionItem {
8528 label: "Range".into(),
8529 sort_text: Some("a".into()),
8530 ..Default::default()
8531 },
8532 lsp::CompletionItem {
8533 label: "r".into(),
8534 sort_text: Some("b".into()),
8535 ..Default::default()
8536 },
8537 lsp::CompletionItem {
8538 label: "ret".into(),
8539 sort_text: Some("c".into()),
8540 ..Default::default()
8541 },
8542 lsp::CompletionItem {
8543 label: "return".into(),
8544 sort_text: Some("d".into()),
8545 ..Default::default()
8546 },
8547 lsp::CompletionItem {
8548 label: "slice".into(),
8549 sort_text: Some("d".into()),
8550 ..Default::default()
8551 },
8552 ])))
8553 });
8554 cx.set_state("rˇ");
8555 cx.executor().run_until_parked();
8556 cx.update_editor(|editor, cx| {
8557 editor.show_completions(
8558 &ShowCompletions {
8559 trigger: Some("r".into()),
8560 },
8561 cx,
8562 );
8563 });
8564 cx.executor().run_until_parked();
8565
8566 cx.update_editor(|editor, _| {
8567 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8568 assert_eq!(
8569 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
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) @content
9072 (#set! "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(cx: &mut gpui::TestAppContext) {
10656 init_test(cx, |_| {});
10657
10658 let mut cx = EditorLspTestContext::new_rust(
10659 lsp::ServerCapabilities {
10660 completion_provider: Some(lsp::CompletionOptions {
10661 trigger_characters: Some(vec![".".to_string()]),
10662 resolve_provider: Some(true),
10663 ..Default::default()
10664 }),
10665 ..Default::default()
10666 },
10667 cx,
10668 )
10669 .await;
10670
10671 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10672 cx.simulate_keystroke(".");
10673
10674 let completion_item = lsp::CompletionItem {
10675 label: "unresolved".to_string(),
10676 detail: None,
10677 documentation: None,
10678 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10679 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10680 new_text: ".unresolved".to_string(),
10681 })),
10682 ..lsp::CompletionItem::default()
10683 };
10684
10685 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10686 let item = completion_item.clone();
10687 async move { Ok(Some(lsp::CompletionResponse::Array(vec![item]))) }
10688 })
10689 .next()
10690 .await;
10691
10692 cx.condition(|editor, _| editor.context_menu_visible())
10693 .await;
10694 cx.update_editor(|editor, _| {
10695 let context_menu = editor.context_menu.read();
10696 let context_menu = context_menu
10697 .as_ref()
10698 .expect("Should have the context menu deployed");
10699 match context_menu {
10700 CodeContextMenu::Completions(completions_menu) => {
10701 let completions = completions_menu.completions.read();
10702 assert_eq!(completions.len(), 1, "Should have one completion");
10703 assert_eq!(completions.get(0).unwrap().label.text, "unresolved");
10704 }
10705 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
10706 }
10707 });
10708
10709 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| async move {
10710 Ok(lsp::CompletionItem {
10711 label: "resolved".to_string(),
10712 detail: Some("Now resolved!".to_string()),
10713 documentation: Some(lsp::Documentation::String("Docs".to_string())),
10714 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10715 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10716 new_text: ".resolved".to_string(),
10717 })),
10718 ..lsp::CompletionItem::default()
10719 })
10720 })
10721 .next()
10722 .await;
10723 cx.run_until_parked();
10724
10725 cx.update_editor(|editor, _| {
10726 let context_menu = editor.context_menu.read();
10727 let context_menu = context_menu
10728 .as_ref()
10729 .expect("Should have the context menu deployed");
10730 match context_menu {
10731 CodeContextMenu::Completions(completions_menu) => {
10732 let completions = completions_menu.completions.read();
10733 assert_eq!(completions.len(), 1, "Should have one completion");
10734 assert_eq!(
10735 completions.get(0).unwrap().label.text,
10736 "resolved",
10737 "Should update the completion label after resolving"
10738 );
10739 }
10740 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
10741 }
10742 });
10743}
10744
10745#[gpui::test]
10746async fn test_completions_default_resolve_data_handling(cx: &mut gpui::TestAppContext) {
10747 init_test(cx, |_| {});
10748
10749 let item_0 = lsp::CompletionItem {
10750 label: "abs".into(),
10751 insert_text: Some("abs".into()),
10752 data: Some(json!({ "very": "special"})),
10753 insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
10754 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
10755 lsp::InsertReplaceEdit {
10756 new_text: "abs".to_string(),
10757 insert: lsp::Range::default(),
10758 replace: lsp::Range::default(),
10759 },
10760 )),
10761 ..lsp::CompletionItem::default()
10762 };
10763 let items = iter::once(item_0.clone())
10764 .chain((11..51).map(|i| lsp::CompletionItem {
10765 label: format!("item_{}", i),
10766 insert_text: Some(format!("item_{}", i)),
10767 insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
10768 ..lsp::CompletionItem::default()
10769 }))
10770 .collect::<Vec<_>>();
10771
10772 let default_commit_characters = vec!["?".to_string()];
10773 let default_data = json!({ "default": "data"});
10774 let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
10775 let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
10776 let default_edit_range = lsp::Range {
10777 start: lsp::Position {
10778 line: 0,
10779 character: 5,
10780 },
10781 end: lsp::Position {
10782 line: 0,
10783 character: 5,
10784 },
10785 };
10786
10787 let item_0_out = lsp::CompletionItem {
10788 commit_characters: Some(default_commit_characters.clone()),
10789 insert_text_format: Some(default_insert_text_format),
10790 ..item_0
10791 };
10792 let items_out = iter::once(item_0_out)
10793 .chain(items[1..].iter().map(|item| lsp::CompletionItem {
10794 commit_characters: Some(default_commit_characters.clone()),
10795 data: Some(default_data.clone()),
10796 insert_text_mode: Some(default_insert_text_mode),
10797 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10798 range: default_edit_range,
10799 new_text: item.label.clone(),
10800 })),
10801 ..item.clone()
10802 }))
10803 .collect::<Vec<lsp::CompletionItem>>();
10804
10805 let mut cx = EditorLspTestContext::new_rust(
10806 lsp::ServerCapabilities {
10807 completion_provider: Some(lsp::CompletionOptions {
10808 trigger_characters: Some(vec![".".to_string()]),
10809 resolve_provider: Some(true),
10810 ..Default::default()
10811 }),
10812 ..Default::default()
10813 },
10814 cx,
10815 )
10816 .await;
10817
10818 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10819 cx.simulate_keystroke(".");
10820
10821 let completion_data = default_data.clone();
10822 let completion_characters = default_commit_characters.clone();
10823 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10824 let default_data = completion_data.clone();
10825 let default_commit_characters = completion_characters.clone();
10826 let items = items.clone();
10827 async move {
10828 Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
10829 items,
10830 item_defaults: Some(lsp::CompletionListItemDefaults {
10831 data: Some(default_data.clone()),
10832 commit_characters: Some(default_commit_characters.clone()),
10833 edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
10834 default_edit_range,
10835 )),
10836 insert_text_format: Some(default_insert_text_format),
10837 insert_text_mode: Some(default_insert_text_mode),
10838 }),
10839 ..lsp::CompletionList::default()
10840 })))
10841 }
10842 })
10843 .next()
10844 .await;
10845
10846 let resolved_items = Arc::new(Mutex::new(Vec::new()));
10847 cx.lsp
10848 .server
10849 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
10850 let closure_resolved_items = resolved_items.clone();
10851 move |item_to_resolve, _| {
10852 let closure_resolved_items = closure_resolved_items.clone();
10853 async move {
10854 closure_resolved_items.lock().push(item_to_resolve.clone());
10855 Ok(item_to_resolve)
10856 }
10857 }
10858 })
10859 .detach();
10860
10861 cx.condition(|editor, _| editor.context_menu_visible())
10862 .await;
10863 cx.run_until_parked();
10864 cx.update_editor(|editor, _| {
10865 let menu = editor.context_menu.read();
10866 match menu.as_ref().expect("should have the completions menu") {
10867 CodeContextMenu::Completions(completions_menu) => {
10868 assert_eq!(
10869 completions_menu
10870 .matches
10871 .iter()
10872 .map(|c| c.string.clone())
10873 .collect::<Vec<String>>(),
10874 items_out
10875 .iter()
10876 .map(|completion| completion.label.clone())
10877 .collect::<Vec<String>>()
10878 );
10879 }
10880 CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
10881 }
10882 });
10883 // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
10884 // with 4 from the end.
10885 assert_eq!(
10886 *resolved_items.lock(),
10887 [
10888 &items_out[0..16],
10889 &items_out[items_out.len() - 4..items_out.len()]
10890 ]
10891 .concat()
10892 .iter()
10893 .cloned()
10894 .collect::<Vec<lsp::CompletionItem>>()
10895 );
10896 resolved_items.lock().clear();
10897
10898 cx.update_editor(|editor, cx| {
10899 editor.context_menu_prev(&ContextMenuPrev, cx);
10900 });
10901 cx.run_until_parked();
10902 // Completions that have already been resolved are skipped.
10903 assert_eq!(
10904 *resolved_items.lock(),
10905 [
10906 // Selected item is always resolved even if it was resolved before.
10907 &items_out[items_out.len() - 1..items_out.len()],
10908 &items_out[items_out.len() - 16..items_out.len() - 4]
10909 ]
10910 .concat()
10911 .iter()
10912 .cloned()
10913 .collect::<Vec<lsp::CompletionItem>>()
10914 );
10915 resolved_items.lock().clear();
10916}
10917
10918#[gpui::test]
10919async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
10920 init_test(cx, |_| {});
10921
10922 let mut cx = EditorLspTestContext::new(
10923 Language::new(
10924 LanguageConfig {
10925 matcher: LanguageMatcher {
10926 path_suffixes: vec!["jsx".into()],
10927 ..Default::default()
10928 },
10929 overrides: [(
10930 "element".into(),
10931 LanguageConfigOverride {
10932 word_characters: Override::Set(['-'].into_iter().collect()),
10933 ..Default::default()
10934 },
10935 )]
10936 .into_iter()
10937 .collect(),
10938 ..Default::default()
10939 },
10940 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10941 )
10942 .with_override_query("(jsx_self_closing_element) @element")
10943 .unwrap(),
10944 lsp::ServerCapabilities {
10945 completion_provider: Some(lsp::CompletionOptions {
10946 trigger_characters: Some(vec![":".to_string()]),
10947 ..Default::default()
10948 }),
10949 ..Default::default()
10950 },
10951 cx,
10952 )
10953 .await;
10954
10955 cx.lsp
10956 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
10957 Ok(Some(lsp::CompletionResponse::Array(vec![
10958 lsp::CompletionItem {
10959 label: "bg-blue".into(),
10960 ..Default::default()
10961 },
10962 lsp::CompletionItem {
10963 label: "bg-red".into(),
10964 ..Default::default()
10965 },
10966 lsp::CompletionItem {
10967 label: "bg-yellow".into(),
10968 ..Default::default()
10969 },
10970 ])))
10971 });
10972
10973 cx.set_state(r#"<p class="bgˇ" />"#);
10974
10975 // Trigger completion when typing a dash, because the dash is an extra
10976 // word character in the 'element' scope, which contains the cursor.
10977 cx.simulate_keystroke("-");
10978 cx.executor().run_until_parked();
10979 cx.update_editor(|editor, _| {
10980 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10981 assert_eq!(
10982 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10983 &["bg-red", "bg-blue", "bg-yellow"]
10984 );
10985 } else {
10986 panic!("expected completion menu to be open");
10987 }
10988 });
10989
10990 cx.simulate_keystroke("l");
10991 cx.executor().run_until_parked();
10992 cx.update_editor(|editor, _| {
10993 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10994 assert_eq!(
10995 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10996 &["bg-blue", "bg-yellow"]
10997 );
10998 } else {
10999 panic!("expected completion menu to be open");
11000 }
11001 });
11002
11003 // When filtering completions, consider the character after the '-' to
11004 // be the start of a subword.
11005 cx.set_state(r#"<p class="yelˇ" />"#);
11006 cx.simulate_keystroke("l");
11007 cx.executor().run_until_parked();
11008 cx.update_editor(|editor, _| {
11009 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
11010 assert_eq!(
11011 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
11012 &["bg-yellow"]
11013 );
11014 } else {
11015 panic!("expected completion menu to be open");
11016 }
11017 });
11018}
11019
11020#[gpui::test]
11021async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
11022 init_test(cx, |settings| {
11023 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
11024 FormatterList(vec![Formatter::Prettier].into()),
11025 ))
11026 });
11027
11028 let fs = FakeFs::new(cx.executor());
11029 fs.insert_file("/file.ts", Default::default()).await;
11030
11031 let project = Project::test(fs, ["/file.ts".as_ref()], cx).await;
11032 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11033
11034 language_registry.add(Arc::new(Language::new(
11035 LanguageConfig {
11036 name: "TypeScript".into(),
11037 matcher: LanguageMatcher {
11038 path_suffixes: vec!["ts".to_string()],
11039 ..Default::default()
11040 },
11041 ..Default::default()
11042 },
11043 Some(tree_sitter_rust::LANGUAGE.into()),
11044 )));
11045 update_test_language_settings(cx, |settings| {
11046 settings.defaults.prettier = Some(PrettierSettings {
11047 allowed: true,
11048 ..PrettierSettings::default()
11049 });
11050 });
11051
11052 let test_plugin = "test_plugin";
11053 let _ = language_registry.register_fake_lsp(
11054 "TypeScript",
11055 FakeLspAdapter {
11056 prettier_plugins: vec![test_plugin],
11057 ..Default::default()
11058 },
11059 );
11060
11061 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
11062 let buffer = project
11063 .update(cx, |project, cx| project.open_local_buffer("/file.ts", cx))
11064 .await
11065 .unwrap();
11066
11067 let buffer_text = "one\ntwo\nthree\n";
11068 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
11069 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
11070 editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
11071
11072 editor
11073 .update(cx, |editor, cx| {
11074 editor.perform_format(
11075 project.clone(),
11076 FormatTrigger::Manual,
11077 FormatTarget::Buffer,
11078 cx,
11079 )
11080 })
11081 .unwrap()
11082 .await;
11083 assert_eq!(
11084 editor.update(cx, |editor, cx| editor.text(cx)),
11085 buffer_text.to_string() + prettier_format_suffix,
11086 "Test prettier formatting was not applied to the original buffer text",
11087 );
11088
11089 update_test_language_settings(cx, |settings| {
11090 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
11091 });
11092 let format = editor.update(cx, |editor, cx| {
11093 editor.perform_format(
11094 project.clone(),
11095 FormatTrigger::Manual,
11096 FormatTarget::Buffer,
11097 cx,
11098 )
11099 });
11100 format.await.unwrap();
11101 assert_eq!(
11102 editor.update(cx, |editor, cx| editor.text(cx)),
11103 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
11104 "Autoformatting (via test prettier) was not applied to the original buffer text",
11105 );
11106}
11107
11108#[gpui::test]
11109async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
11110 init_test(cx, |_| {});
11111 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11112 let base_text = indoc! {r#"
11113 struct Row;
11114 struct Row1;
11115 struct Row2;
11116
11117 struct Row4;
11118 struct Row5;
11119 struct Row6;
11120
11121 struct Row8;
11122 struct Row9;
11123 struct Row10;"#};
11124
11125 // When addition hunks are not adjacent to carets, no hunk revert is performed
11126 assert_hunk_revert(
11127 indoc! {r#"struct Row;
11128 struct Row1;
11129 struct Row1.1;
11130 struct Row1.2;
11131 struct Row2;ˇ
11132
11133 struct Row4;
11134 struct Row5;
11135 struct Row6;
11136
11137 struct Row8;
11138 ˇstruct Row9;
11139 struct Row9.1;
11140 struct Row9.2;
11141 struct Row9.3;
11142 struct Row10;"#},
11143 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
11144 indoc! {r#"struct Row;
11145 struct Row1;
11146 struct Row1.1;
11147 struct Row1.2;
11148 struct Row2;ˇ
11149
11150 struct Row4;
11151 struct Row5;
11152 struct Row6;
11153
11154 struct Row8;
11155 ˇstruct Row9;
11156 struct Row9.1;
11157 struct Row9.2;
11158 struct Row9.3;
11159 struct Row10;"#},
11160 base_text,
11161 &mut cx,
11162 );
11163 // Same for selections
11164 assert_hunk_revert(
11165 indoc! {r#"struct Row;
11166 struct Row1;
11167 struct Row2;
11168 struct Row2.1;
11169 struct Row2.2;
11170 «ˇ
11171 struct Row4;
11172 struct» Row5;
11173 «struct Row6;
11174 ˇ»
11175 struct Row9.1;
11176 struct Row9.2;
11177 struct Row9.3;
11178 struct Row8;
11179 struct Row9;
11180 struct Row10;"#},
11181 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
11182 indoc! {r#"struct Row;
11183 struct Row1;
11184 struct Row2;
11185 struct Row2.1;
11186 struct Row2.2;
11187 «ˇ
11188 struct Row4;
11189 struct» Row5;
11190 «struct Row6;
11191 ˇ»
11192 struct Row9.1;
11193 struct Row9.2;
11194 struct Row9.3;
11195 struct Row8;
11196 struct Row9;
11197 struct Row10;"#},
11198 base_text,
11199 &mut cx,
11200 );
11201
11202 // When carets and selections intersect the addition hunks, those are reverted.
11203 // Adjacent carets got merged.
11204 assert_hunk_revert(
11205 indoc! {r#"struct Row;
11206 ˇ// something on the top
11207 struct Row1;
11208 struct Row2;
11209 struct Roˇw3.1;
11210 struct Row2.2;
11211 struct Row2.3;ˇ
11212
11213 struct Row4;
11214 struct ˇRow5.1;
11215 struct Row5.2;
11216 struct «Rowˇ»5.3;
11217 struct Row5;
11218 struct Row6;
11219 ˇ
11220 struct Row9.1;
11221 struct «Rowˇ»9.2;
11222 struct «ˇRow»9.3;
11223 struct Row8;
11224 struct Row9;
11225 «ˇ// something on bottom»
11226 struct Row10;"#},
11227 vec![
11228 DiffHunkStatus::Added,
11229 DiffHunkStatus::Added,
11230 DiffHunkStatus::Added,
11231 DiffHunkStatus::Added,
11232 DiffHunkStatus::Added,
11233 ],
11234 indoc! {r#"struct Row;
11235 ˇstruct Row1;
11236 struct Row2;
11237 ˇ
11238 struct Row4;
11239 ˇstruct Row5;
11240 struct Row6;
11241 ˇ
11242 ˇstruct Row8;
11243 struct Row9;
11244 ˇstruct Row10;"#},
11245 base_text,
11246 &mut cx,
11247 );
11248}
11249
11250#[gpui::test]
11251async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
11252 init_test(cx, |_| {});
11253 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11254 let base_text = indoc! {r#"
11255 struct Row;
11256 struct Row1;
11257 struct Row2;
11258
11259 struct Row4;
11260 struct Row5;
11261 struct Row6;
11262
11263 struct Row8;
11264 struct Row9;
11265 struct Row10;"#};
11266
11267 // Modification hunks behave the same as the addition ones.
11268 assert_hunk_revert(
11269 indoc! {r#"struct Row;
11270 struct Row1;
11271 struct Row33;
11272 ˇ
11273 struct Row4;
11274 struct Row5;
11275 struct Row6;
11276 ˇ
11277 struct Row99;
11278 struct Row9;
11279 struct Row10;"#},
11280 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
11281 indoc! {r#"struct Row;
11282 struct Row1;
11283 struct Row33;
11284 ˇ
11285 struct Row4;
11286 struct Row5;
11287 struct Row6;
11288 ˇ
11289 struct Row99;
11290 struct Row9;
11291 struct Row10;"#},
11292 base_text,
11293 &mut cx,
11294 );
11295 assert_hunk_revert(
11296 indoc! {r#"struct Row;
11297 struct Row1;
11298 struct Row33;
11299 «ˇ
11300 struct Row4;
11301 struct» Row5;
11302 «struct Row6;
11303 ˇ»
11304 struct Row99;
11305 struct Row9;
11306 struct Row10;"#},
11307 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
11308 indoc! {r#"struct Row;
11309 struct Row1;
11310 struct Row33;
11311 «ˇ
11312 struct Row4;
11313 struct» Row5;
11314 «struct Row6;
11315 ˇ»
11316 struct Row99;
11317 struct Row9;
11318 struct Row10;"#},
11319 base_text,
11320 &mut cx,
11321 );
11322
11323 assert_hunk_revert(
11324 indoc! {r#"ˇstruct Row1.1;
11325 struct Row1;
11326 «ˇstr»uct Row22;
11327
11328 struct ˇRow44;
11329 struct Row5;
11330 struct «Rˇ»ow66;ˇ
11331
11332 «struˇ»ct Row88;
11333 struct Row9;
11334 struct Row1011;ˇ"#},
11335 vec![
11336 DiffHunkStatus::Modified,
11337 DiffHunkStatus::Modified,
11338 DiffHunkStatus::Modified,
11339 DiffHunkStatus::Modified,
11340 DiffHunkStatus::Modified,
11341 DiffHunkStatus::Modified,
11342 ],
11343 indoc! {r#"struct Row;
11344 ˇstruct Row1;
11345 struct Row2;
11346 ˇ
11347 struct Row4;
11348 ˇstruct Row5;
11349 struct Row6;
11350 ˇ
11351 struct Row8;
11352 ˇstruct Row9;
11353 struct Row10;ˇ"#},
11354 base_text,
11355 &mut cx,
11356 );
11357}
11358
11359#[gpui::test]
11360async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
11361 init_test(cx, |_| {});
11362 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11363 let base_text = indoc! {r#"struct Row;
11364struct Row1;
11365struct Row2;
11366
11367struct Row4;
11368struct Row5;
11369struct Row6;
11370
11371struct Row8;
11372struct Row9;
11373struct Row10;"#};
11374
11375 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
11376 assert_hunk_revert(
11377 indoc! {r#"struct Row;
11378 struct Row2;
11379
11380 ˇstruct Row4;
11381 struct Row5;
11382 struct Row6;
11383 ˇ
11384 struct Row8;
11385 struct Row10;"#},
11386 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11387 indoc! {r#"struct Row;
11388 struct Row2;
11389
11390 ˇstruct Row4;
11391 struct Row5;
11392 struct Row6;
11393 ˇ
11394 struct Row8;
11395 struct Row10;"#},
11396 base_text,
11397 &mut cx,
11398 );
11399 assert_hunk_revert(
11400 indoc! {r#"struct Row;
11401 struct Row2;
11402
11403 «ˇstruct Row4;
11404 struct» Row5;
11405 «struct Row6;
11406 ˇ»
11407 struct Row8;
11408 struct Row10;"#},
11409 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11410 indoc! {r#"struct Row;
11411 struct Row2;
11412
11413 «ˇstruct Row4;
11414 struct» Row5;
11415 «struct Row6;
11416 ˇ»
11417 struct Row8;
11418 struct Row10;"#},
11419 base_text,
11420 &mut cx,
11421 );
11422
11423 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
11424 assert_hunk_revert(
11425 indoc! {r#"struct Row;
11426 ˇstruct Row2;
11427
11428 struct Row4;
11429 struct Row5;
11430 struct Row6;
11431
11432 struct Row8;ˇ
11433 struct Row10;"#},
11434 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11435 indoc! {r#"struct Row;
11436 struct Row1;
11437 ˇstruct Row2;
11438
11439 struct Row4;
11440 struct Row5;
11441 struct Row6;
11442
11443 struct Row8;ˇ
11444 struct Row9;
11445 struct Row10;"#},
11446 base_text,
11447 &mut cx,
11448 );
11449 assert_hunk_revert(
11450 indoc! {r#"struct Row;
11451 struct Row2«ˇ;
11452 struct Row4;
11453 struct» Row5;
11454 «struct Row6;
11455
11456 struct Row8;ˇ»
11457 struct Row10;"#},
11458 vec![
11459 DiffHunkStatus::Removed,
11460 DiffHunkStatus::Removed,
11461 DiffHunkStatus::Removed,
11462 ],
11463 indoc! {r#"struct Row;
11464 struct Row1;
11465 struct Row2«ˇ;
11466
11467 struct Row4;
11468 struct» Row5;
11469 «struct Row6;
11470
11471 struct Row8;ˇ»
11472 struct Row9;
11473 struct Row10;"#},
11474 base_text,
11475 &mut cx,
11476 );
11477}
11478
11479#[gpui::test]
11480async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
11481 init_test(cx, |_| {});
11482
11483 let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
11484 let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
11485 let base_text_3 =
11486 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
11487
11488 let text_1 = edit_first_char_of_every_line(base_text_1);
11489 let text_2 = edit_first_char_of_every_line(base_text_2);
11490 let text_3 = edit_first_char_of_every_line(base_text_3);
11491
11492 let buffer_1 = cx.new_model(|cx| Buffer::local(text_1.clone(), cx));
11493 let buffer_2 = cx.new_model(|cx| Buffer::local(text_2.clone(), cx));
11494 let buffer_3 = cx.new_model(|cx| Buffer::local(text_3.clone(), cx));
11495
11496 let multibuffer = cx.new_model(|cx| {
11497 let mut multibuffer = MultiBuffer::new(ReadWrite);
11498 multibuffer.push_excerpts(
11499 buffer_1.clone(),
11500 [
11501 ExcerptRange {
11502 context: Point::new(0, 0)..Point::new(3, 0),
11503 primary: None,
11504 },
11505 ExcerptRange {
11506 context: Point::new(5, 0)..Point::new(7, 0),
11507 primary: None,
11508 },
11509 ExcerptRange {
11510 context: Point::new(9, 0)..Point::new(10, 4),
11511 primary: None,
11512 },
11513 ],
11514 cx,
11515 );
11516 multibuffer.push_excerpts(
11517 buffer_2.clone(),
11518 [
11519 ExcerptRange {
11520 context: Point::new(0, 0)..Point::new(3, 0),
11521 primary: None,
11522 },
11523 ExcerptRange {
11524 context: Point::new(5, 0)..Point::new(7, 0),
11525 primary: None,
11526 },
11527 ExcerptRange {
11528 context: Point::new(9, 0)..Point::new(10, 4),
11529 primary: None,
11530 },
11531 ],
11532 cx,
11533 );
11534 multibuffer.push_excerpts(
11535 buffer_3.clone(),
11536 [
11537 ExcerptRange {
11538 context: Point::new(0, 0)..Point::new(3, 0),
11539 primary: None,
11540 },
11541 ExcerptRange {
11542 context: Point::new(5, 0)..Point::new(7, 0),
11543 primary: None,
11544 },
11545 ExcerptRange {
11546 context: Point::new(9, 0)..Point::new(10, 4),
11547 primary: None,
11548 },
11549 ],
11550 cx,
11551 );
11552 multibuffer
11553 });
11554
11555 let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
11556 editor.update(cx, |editor, cx| {
11557 for (buffer, diff_base) in [
11558 (buffer_1.clone(), base_text_1),
11559 (buffer_2.clone(), base_text_2),
11560 (buffer_3.clone(), base_text_3),
11561 ] {
11562 let change_set = cx.new_model(|cx| {
11563 BufferChangeSet::new_with_base_text(
11564 diff_base.to_string(),
11565 buffer.read(cx).text_snapshot(),
11566 cx,
11567 )
11568 });
11569 editor.diff_map.add_change_set(change_set, cx)
11570 }
11571 });
11572 cx.executor().run_until_parked();
11573
11574 editor.update(cx, |editor, cx| {
11575 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}");
11576 editor.select_all(&SelectAll, cx);
11577 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
11578 });
11579 cx.executor().run_until_parked();
11580
11581 // When all ranges are selected, all buffer hunks are reverted.
11582 editor.update(cx, |editor, cx| {
11583 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");
11584 });
11585 buffer_1.update(cx, |buffer, _| {
11586 assert_eq!(buffer.text(), base_text_1);
11587 });
11588 buffer_2.update(cx, |buffer, _| {
11589 assert_eq!(buffer.text(), base_text_2);
11590 });
11591 buffer_3.update(cx, |buffer, _| {
11592 assert_eq!(buffer.text(), base_text_3);
11593 });
11594
11595 editor.update(cx, |editor, cx| {
11596 editor.undo(&Default::default(), cx);
11597 });
11598
11599 editor.update(cx, |editor, cx| {
11600 editor.change_selections(None, cx, |s| {
11601 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
11602 });
11603 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
11604 });
11605
11606 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
11607 // but not affect buffer_2 and its related excerpts.
11608 editor.update(cx, |editor, cx| {
11609 assert_eq!(
11610 editor.text(cx),
11611 "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}"
11612 );
11613 });
11614 buffer_1.update(cx, |buffer, _| {
11615 assert_eq!(buffer.text(), base_text_1);
11616 });
11617 buffer_2.update(cx, |buffer, _| {
11618 assert_eq!(
11619 buffer.text(),
11620 "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
11621 );
11622 });
11623 buffer_3.update(cx, |buffer, _| {
11624 assert_eq!(
11625 buffer.text(),
11626 "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
11627 );
11628 });
11629
11630 fn edit_first_char_of_every_line(text: &str) -> String {
11631 text.split('\n')
11632 .map(|line| format!("X{}", &line[1..]))
11633 .collect::<Vec<_>>()
11634 .join("\n")
11635 }
11636}
11637
11638#[gpui::test]
11639async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
11640 init_test(cx, |_| {});
11641
11642 let cols = 4;
11643 let rows = 10;
11644 let sample_text_1 = sample_text(rows, cols, 'a');
11645 assert_eq!(
11646 sample_text_1,
11647 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11648 );
11649 let sample_text_2 = sample_text(rows, cols, 'l');
11650 assert_eq!(
11651 sample_text_2,
11652 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11653 );
11654 let sample_text_3 = sample_text(rows, cols, 'v');
11655 assert_eq!(
11656 sample_text_3,
11657 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11658 );
11659
11660 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
11661 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
11662 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
11663
11664 let multi_buffer = cx.new_model(|cx| {
11665 let mut multibuffer = MultiBuffer::new(ReadWrite);
11666 multibuffer.push_excerpts(
11667 buffer_1.clone(),
11668 [
11669 ExcerptRange {
11670 context: Point::new(0, 0)..Point::new(3, 0),
11671 primary: None,
11672 },
11673 ExcerptRange {
11674 context: Point::new(5, 0)..Point::new(7, 0),
11675 primary: None,
11676 },
11677 ExcerptRange {
11678 context: Point::new(9, 0)..Point::new(10, 4),
11679 primary: None,
11680 },
11681 ],
11682 cx,
11683 );
11684 multibuffer.push_excerpts(
11685 buffer_2.clone(),
11686 [
11687 ExcerptRange {
11688 context: Point::new(0, 0)..Point::new(3, 0),
11689 primary: None,
11690 },
11691 ExcerptRange {
11692 context: Point::new(5, 0)..Point::new(7, 0),
11693 primary: None,
11694 },
11695 ExcerptRange {
11696 context: Point::new(9, 0)..Point::new(10, 4),
11697 primary: None,
11698 },
11699 ],
11700 cx,
11701 );
11702 multibuffer.push_excerpts(
11703 buffer_3.clone(),
11704 [
11705 ExcerptRange {
11706 context: Point::new(0, 0)..Point::new(3, 0),
11707 primary: None,
11708 },
11709 ExcerptRange {
11710 context: Point::new(5, 0)..Point::new(7, 0),
11711 primary: None,
11712 },
11713 ExcerptRange {
11714 context: Point::new(9, 0)..Point::new(10, 4),
11715 primary: None,
11716 },
11717 ],
11718 cx,
11719 );
11720 multibuffer
11721 });
11722
11723 let fs = FakeFs::new(cx.executor());
11724 fs.insert_tree(
11725 "/a",
11726 json!({
11727 "main.rs": sample_text_1,
11728 "other.rs": sample_text_2,
11729 "lib.rs": sample_text_3,
11730 }),
11731 )
11732 .await;
11733 let project = Project::test(fs, ["/a".as_ref()], cx).await;
11734 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
11735 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11736 let multi_buffer_editor = cx.new_view(|cx| {
11737 Editor::new(
11738 EditorMode::Full,
11739 multi_buffer,
11740 Some(project.clone()),
11741 true,
11742 cx,
11743 )
11744 });
11745 let multibuffer_item_id = workspace
11746 .update(cx, |workspace, cx| {
11747 assert!(
11748 workspace.active_item(cx).is_none(),
11749 "active item should be None before the first item is added"
11750 );
11751 workspace.add_item_to_active_pane(
11752 Box::new(multi_buffer_editor.clone()),
11753 None,
11754 true,
11755 cx,
11756 );
11757 let active_item = workspace
11758 .active_item(cx)
11759 .expect("should have an active item after adding the multi buffer");
11760 assert!(
11761 !active_item.is_singleton(cx),
11762 "A multi buffer was expected to active after adding"
11763 );
11764 active_item.item_id()
11765 })
11766 .unwrap();
11767 cx.executor().run_until_parked();
11768
11769 multi_buffer_editor.update(cx, |editor, cx| {
11770 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
11771 editor.open_excerpts(&OpenExcerpts, cx);
11772 });
11773 cx.executor().run_until_parked();
11774 let first_item_id = workspace
11775 .update(cx, |workspace, cx| {
11776 let active_item = workspace
11777 .active_item(cx)
11778 .expect("should have an active item after navigating into the 1st buffer");
11779 let first_item_id = active_item.item_id();
11780 assert_ne!(
11781 first_item_id, multibuffer_item_id,
11782 "Should navigate into the 1st buffer and activate it"
11783 );
11784 assert!(
11785 active_item.is_singleton(cx),
11786 "New active item should be a singleton buffer"
11787 );
11788 assert_eq!(
11789 active_item
11790 .act_as::<Editor>(cx)
11791 .expect("should have navigated into an editor for the 1st buffer")
11792 .read(cx)
11793 .text(cx),
11794 sample_text_1
11795 );
11796
11797 workspace
11798 .go_back(workspace.active_pane().downgrade(), cx)
11799 .detach_and_log_err(cx);
11800
11801 first_item_id
11802 })
11803 .unwrap();
11804 cx.executor().run_until_parked();
11805 workspace
11806 .update(cx, |workspace, cx| {
11807 let active_item = workspace
11808 .active_item(cx)
11809 .expect("should have an active item after navigating back");
11810 assert_eq!(
11811 active_item.item_id(),
11812 multibuffer_item_id,
11813 "Should navigate back to the multi buffer"
11814 );
11815 assert!(!active_item.is_singleton(cx));
11816 })
11817 .unwrap();
11818
11819 multi_buffer_editor.update(cx, |editor, cx| {
11820 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
11821 s.select_ranges(Some(39..40))
11822 });
11823 editor.open_excerpts(&OpenExcerpts, cx);
11824 });
11825 cx.executor().run_until_parked();
11826 let second_item_id = workspace
11827 .update(cx, |workspace, cx| {
11828 let active_item = workspace
11829 .active_item(cx)
11830 .expect("should have an active item after navigating into the 2nd buffer");
11831 let second_item_id = active_item.item_id();
11832 assert_ne!(
11833 second_item_id, multibuffer_item_id,
11834 "Should navigate away from the multibuffer"
11835 );
11836 assert_ne!(
11837 second_item_id, first_item_id,
11838 "Should navigate into the 2nd buffer and activate it"
11839 );
11840 assert!(
11841 active_item.is_singleton(cx),
11842 "New active item should be a singleton buffer"
11843 );
11844 assert_eq!(
11845 active_item
11846 .act_as::<Editor>(cx)
11847 .expect("should have navigated into an editor")
11848 .read(cx)
11849 .text(cx),
11850 sample_text_2
11851 );
11852
11853 workspace
11854 .go_back(workspace.active_pane().downgrade(), cx)
11855 .detach_and_log_err(cx);
11856
11857 second_item_id
11858 })
11859 .unwrap();
11860 cx.executor().run_until_parked();
11861 workspace
11862 .update(cx, |workspace, cx| {
11863 let active_item = workspace
11864 .active_item(cx)
11865 .expect("should have an active item after navigating back from the 2nd buffer");
11866 assert_eq!(
11867 active_item.item_id(),
11868 multibuffer_item_id,
11869 "Should navigate back from the 2nd buffer to the multi buffer"
11870 );
11871 assert!(!active_item.is_singleton(cx));
11872 })
11873 .unwrap();
11874
11875 multi_buffer_editor.update(cx, |editor, cx| {
11876 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
11877 s.select_ranges(Some(70..70))
11878 });
11879 editor.open_excerpts(&OpenExcerpts, cx);
11880 });
11881 cx.executor().run_until_parked();
11882 workspace
11883 .update(cx, |workspace, cx| {
11884 let active_item = workspace
11885 .active_item(cx)
11886 .expect("should have an active item after navigating into the 3rd buffer");
11887 let third_item_id = active_item.item_id();
11888 assert_ne!(
11889 third_item_id, multibuffer_item_id,
11890 "Should navigate into the 3rd buffer and activate it"
11891 );
11892 assert_ne!(third_item_id, first_item_id);
11893 assert_ne!(third_item_id, second_item_id);
11894 assert!(
11895 active_item.is_singleton(cx),
11896 "New active item should be a singleton buffer"
11897 );
11898 assert_eq!(
11899 active_item
11900 .act_as::<Editor>(cx)
11901 .expect("should have navigated into an editor")
11902 .read(cx)
11903 .text(cx),
11904 sample_text_3
11905 );
11906
11907 workspace
11908 .go_back(workspace.active_pane().downgrade(), cx)
11909 .detach_and_log_err(cx);
11910 })
11911 .unwrap();
11912 cx.executor().run_until_parked();
11913 workspace
11914 .update(cx, |workspace, cx| {
11915 let active_item = workspace
11916 .active_item(cx)
11917 .expect("should have an active item after navigating back from the 3rd buffer");
11918 assert_eq!(
11919 active_item.item_id(),
11920 multibuffer_item_id,
11921 "Should navigate back from the 3rd buffer to the multi buffer"
11922 );
11923 assert!(!active_item.is_singleton(cx));
11924 })
11925 .unwrap();
11926}
11927
11928#[gpui::test]
11929async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
11930 init_test(cx, |_| {});
11931
11932 let mut cx = EditorTestContext::new(cx).await;
11933
11934 let diff_base = r#"
11935 use some::mod;
11936
11937 const A: u32 = 42;
11938
11939 fn main() {
11940 println!("hello");
11941
11942 println!("world");
11943 }
11944 "#
11945 .unindent();
11946
11947 cx.set_state(
11948 &r#"
11949 use some::modified;
11950
11951 ˇ
11952 fn main() {
11953 println!("hello there");
11954
11955 println!("around the");
11956 println!("world");
11957 }
11958 "#
11959 .unindent(),
11960 );
11961
11962 cx.set_diff_base(&diff_base);
11963 executor.run_until_parked();
11964
11965 cx.update_editor(|editor, cx| {
11966 editor.go_to_next_hunk(&GoToHunk, cx);
11967 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11968 });
11969 executor.run_until_parked();
11970 cx.assert_state_with_diff(
11971 r#"
11972 use some::modified;
11973
11974
11975 fn main() {
11976 - println!("hello");
11977 + ˇ println!("hello there");
11978
11979 println!("around the");
11980 println!("world");
11981 }
11982 "#
11983 .unindent(),
11984 );
11985
11986 cx.update_editor(|editor, cx| {
11987 for _ in 0..3 {
11988 editor.go_to_next_hunk(&GoToHunk, cx);
11989 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11990 }
11991 });
11992 executor.run_until_parked();
11993 cx.assert_state_with_diff(
11994 r#"
11995 - use some::mod;
11996 + use some::modified;
11997
11998 - const A: u32 = 42;
11999 ˇ
12000 fn main() {
12001 - println!("hello");
12002 + println!("hello there");
12003
12004 + println!("around the");
12005 println!("world");
12006 }
12007 "#
12008 .unindent(),
12009 );
12010
12011 cx.update_editor(|editor, cx| {
12012 editor.cancel(&Cancel, cx);
12013 });
12014
12015 cx.assert_state_with_diff(
12016 r#"
12017 use some::modified;
12018
12019 ˇ
12020 fn main() {
12021 println!("hello there");
12022
12023 println!("around the");
12024 println!("world");
12025 }
12026 "#
12027 .unindent(),
12028 );
12029}
12030
12031#[gpui::test]
12032async fn test_diff_base_change_with_expanded_diff_hunks(
12033 executor: BackgroundExecutor,
12034 cx: &mut gpui::TestAppContext,
12035) {
12036 init_test(cx, |_| {});
12037
12038 let mut cx = EditorTestContext::new(cx).await;
12039
12040 let diff_base = r#"
12041 use some::mod1;
12042 use some::mod2;
12043
12044 const A: u32 = 42;
12045 const B: u32 = 42;
12046 const C: u32 = 42;
12047
12048 fn main() {
12049 println!("hello");
12050
12051 println!("world");
12052 }
12053 "#
12054 .unindent();
12055
12056 cx.set_state(
12057 &r#"
12058 use some::mod2;
12059
12060 const A: u32 = 42;
12061 const C: u32 = 42;
12062
12063 fn main(ˇ) {
12064 //println!("hello");
12065
12066 println!("world");
12067 //
12068 //
12069 }
12070 "#
12071 .unindent(),
12072 );
12073
12074 cx.set_diff_base(&diff_base);
12075 executor.run_until_parked();
12076
12077 cx.update_editor(|editor, cx| {
12078 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12079 });
12080 executor.run_until_parked();
12081 cx.assert_state_with_diff(
12082 r#"
12083 - use some::mod1;
12084 use some::mod2;
12085
12086 const A: u32 = 42;
12087 - const B: u32 = 42;
12088 const C: u32 = 42;
12089
12090 fn main(ˇ) {
12091 - println!("hello");
12092 + //println!("hello");
12093
12094 println!("world");
12095 + //
12096 + //
12097 }
12098 "#
12099 .unindent(),
12100 );
12101
12102 cx.set_diff_base("new diff base!");
12103 executor.run_until_parked();
12104 cx.assert_state_with_diff(
12105 r#"
12106 use some::mod2;
12107
12108 const A: u32 = 42;
12109 const C: u32 = 42;
12110
12111 fn main(ˇ) {
12112 //println!("hello");
12113
12114 println!("world");
12115 //
12116 //
12117 }
12118 "#
12119 .unindent(),
12120 );
12121
12122 cx.update_editor(|editor, cx| {
12123 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12124 });
12125 executor.run_until_parked();
12126 cx.assert_state_with_diff(
12127 r#"
12128 - new diff base!
12129 + use some::mod2;
12130 +
12131 + const A: u32 = 42;
12132 + const C: u32 = 42;
12133 +
12134 + fn main(ˇ) {
12135 + //println!("hello");
12136 +
12137 + println!("world");
12138 + //
12139 + //
12140 + }
12141 "#
12142 .unindent(),
12143 );
12144}
12145
12146#[gpui::test]
12147async fn test_fold_unfold_diff_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
12148 init_test(cx, |_| {});
12149
12150 let mut cx = EditorTestContext::new(cx).await;
12151
12152 let diff_base = r#"
12153 use some::mod1;
12154 use some::mod2;
12155
12156 const A: u32 = 42;
12157 const B: u32 = 42;
12158 const C: u32 = 42;
12159
12160 fn main() {
12161 println!("hello");
12162
12163 println!("world");
12164 }
12165
12166 fn another() {
12167 println!("another");
12168 }
12169
12170 fn another2() {
12171 println!("another2");
12172 }
12173 "#
12174 .unindent();
12175
12176 cx.set_state(
12177 &r#"
12178 «use some::mod2;
12179
12180 const A: u32 = 42;
12181 const C: u32 = 42;
12182
12183 fn main() {
12184 //println!("hello");
12185
12186 println!("world");
12187 //
12188 //ˇ»
12189 }
12190
12191 fn another() {
12192 println!("another");
12193 println!("another");
12194 }
12195
12196 println!("another2");
12197 }
12198 "#
12199 .unindent(),
12200 );
12201
12202 cx.set_diff_base(&diff_base);
12203 executor.run_until_parked();
12204
12205 cx.update_editor(|editor, cx| {
12206 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12207 });
12208 executor.run_until_parked();
12209
12210 cx.assert_state_with_diff(
12211 r#"
12212 - use some::mod1;
12213 «use some::mod2;
12214
12215 const A: u32 = 42;
12216 - const B: u32 = 42;
12217 const C: u32 = 42;
12218
12219 fn main() {
12220 - println!("hello");
12221 + //println!("hello");
12222
12223 println!("world");
12224 + //
12225 + //ˇ»
12226 }
12227
12228 fn another() {
12229 println!("another");
12230 + println!("another");
12231 }
12232
12233 - fn another2() {
12234 println!("another2");
12235 }
12236 "#
12237 .unindent(),
12238 );
12239
12240 // Fold across some of the diff hunks. They should no longer appear expanded.
12241 cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx));
12242 cx.executor().run_until_parked();
12243
12244 // Hunks are not shown if their position is within a fold
12245 cx.assert_state_with_diff(
12246 r#"
12247 «use some::mod2;
12248
12249 const A: u32 = 42;
12250 const C: u32 = 42;
12251
12252 fn main() {
12253 //println!("hello");
12254
12255 println!("world");
12256 //
12257 //ˇ»
12258 }
12259
12260 fn another() {
12261 println!("another");
12262 + println!("another");
12263 }
12264
12265 - fn another2() {
12266 println!("another2");
12267 }
12268 "#
12269 .unindent(),
12270 );
12271
12272 cx.update_editor(|editor, cx| {
12273 editor.select_all(&SelectAll, cx);
12274 editor.unfold_lines(&UnfoldLines, cx);
12275 });
12276 cx.executor().run_until_parked();
12277
12278 // The deletions reappear when unfolding.
12279 cx.assert_state_with_diff(
12280 r#"
12281 - use some::mod1;
12282 «use some::mod2;
12283
12284 const A: u32 = 42;
12285 - const B: u32 = 42;
12286 const C: u32 = 42;
12287
12288 fn main() {
12289 - println!("hello");
12290 + //println!("hello");
12291
12292 println!("world");
12293 + //
12294 + //
12295 }
12296
12297 fn another() {
12298 println!("another");
12299 + println!("another");
12300 }
12301
12302 - fn another2() {
12303 println!("another2");
12304 }
12305 ˇ»"#
12306 .unindent(),
12307 );
12308}
12309
12310#[gpui::test]
12311async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
12312 init_test(cx, |_| {});
12313
12314 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
12315 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
12316 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
12317 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
12318 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
12319 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
12320
12321 let buffer_1 = cx.new_model(|cx| Buffer::local(file_1_new.to_string(), cx));
12322 let buffer_2 = cx.new_model(|cx| Buffer::local(file_2_new.to_string(), cx));
12323 let buffer_3 = cx.new_model(|cx| Buffer::local(file_3_new.to_string(), cx));
12324
12325 let multi_buffer = cx.new_model(|cx| {
12326 let mut multibuffer = MultiBuffer::new(ReadWrite);
12327 multibuffer.push_excerpts(
12328 buffer_1.clone(),
12329 [
12330 ExcerptRange {
12331 context: Point::new(0, 0)..Point::new(3, 0),
12332 primary: None,
12333 },
12334 ExcerptRange {
12335 context: Point::new(5, 0)..Point::new(7, 0),
12336 primary: None,
12337 },
12338 ExcerptRange {
12339 context: Point::new(9, 0)..Point::new(10, 3),
12340 primary: None,
12341 },
12342 ],
12343 cx,
12344 );
12345 multibuffer.push_excerpts(
12346 buffer_2.clone(),
12347 [
12348 ExcerptRange {
12349 context: Point::new(0, 0)..Point::new(3, 0),
12350 primary: None,
12351 },
12352 ExcerptRange {
12353 context: Point::new(5, 0)..Point::new(7, 0),
12354 primary: None,
12355 },
12356 ExcerptRange {
12357 context: Point::new(9, 0)..Point::new(10, 3),
12358 primary: None,
12359 },
12360 ],
12361 cx,
12362 );
12363 multibuffer.push_excerpts(
12364 buffer_3.clone(),
12365 [
12366 ExcerptRange {
12367 context: Point::new(0, 0)..Point::new(3, 0),
12368 primary: None,
12369 },
12370 ExcerptRange {
12371 context: Point::new(5, 0)..Point::new(7, 0),
12372 primary: None,
12373 },
12374 ExcerptRange {
12375 context: Point::new(9, 0)..Point::new(10, 3),
12376 primary: None,
12377 },
12378 ],
12379 cx,
12380 );
12381 multibuffer
12382 });
12383
12384 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
12385 editor
12386 .update(cx, |editor, cx| {
12387 for (buffer, diff_base) in [
12388 (buffer_1.clone(), file_1_old),
12389 (buffer_2.clone(), file_2_old),
12390 (buffer_3.clone(), file_3_old),
12391 ] {
12392 let change_set = cx.new_model(|cx| {
12393 BufferChangeSet::new_with_base_text(
12394 diff_base.to_string(),
12395 buffer.read(cx).text_snapshot(),
12396 cx,
12397 )
12398 });
12399 editor.diff_map.add_change_set(change_set, cx)
12400 }
12401 })
12402 .unwrap();
12403
12404 let mut cx = EditorTestContext::for_editor(editor, cx).await;
12405 cx.run_until_parked();
12406
12407 cx.assert_editor_state(
12408 &"
12409 ˇaaa
12410 ccc
12411 ddd
12412
12413 ggg
12414 hhh
12415
12416
12417 lll
12418 mmm
12419 NNN
12420
12421 qqq
12422 rrr
12423
12424 uuu
12425 111
12426 222
12427 333
12428
12429 666
12430 777
12431
12432 000
12433 !!!"
12434 .unindent(),
12435 );
12436
12437 cx.update_editor(|editor, cx| {
12438 editor.select_all(&SelectAll, cx);
12439 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
12440 });
12441 cx.executor().run_until_parked();
12442
12443 cx.assert_state_with_diff(
12444 "
12445 «aaa
12446 - bbb
12447 ccc
12448 ddd
12449
12450 ggg
12451 hhh
12452
12453
12454 lll
12455 mmm
12456 - nnn
12457 + NNN
12458
12459 qqq
12460 rrr
12461
12462 uuu
12463 111
12464 222
12465 333
12466
12467 + 666
12468 777
12469
12470 000
12471 !!!ˇ»"
12472 .unindent(),
12473 );
12474}
12475
12476#[gpui::test]
12477async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut gpui::TestAppContext) {
12478 init_test(cx, |_| {});
12479
12480 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
12481 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\n";
12482
12483 let buffer = cx.new_model(|cx| Buffer::local(text.to_string(), cx));
12484 let multi_buffer = cx.new_model(|cx| {
12485 let mut multibuffer = MultiBuffer::new(ReadWrite);
12486 multibuffer.push_excerpts(
12487 buffer.clone(),
12488 [
12489 ExcerptRange {
12490 context: Point::new(0, 0)..Point::new(2, 0),
12491 primary: None,
12492 },
12493 ExcerptRange {
12494 context: Point::new(5, 0)..Point::new(7, 0),
12495 primary: None,
12496 },
12497 ],
12498 cx,
12499 );
12500 multibuffer
12501 });
12502
12503 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
12504 editor
12505 .update(cx, |editor, cx| {
12506 let buffer = buffer.read(cx).text_snapshot();
12507 let change_set = cx
12508 .new_model(|cx| BufferChangeSet::new_with_base_text(base.to_string(), buffer, cx));
12509 editor.diff_map.add_change_set(change_set, cx)
12510 })
12511 .unwrap();
12512
12513 let mut cx = EditorTestContext::for_editor(editor, cx).await;
12514 cx.run_until_parked();
12515
12516 cx.update_editor(|editor, cx| editor.expand_all_hunk_diffs(&Default::default(), cx));
12517 cx.executor().run_until_parked();
12518
12519 cx.assert_state_with_diff(
12520 "
12521 ˇaaa
12522 - bbb
12523 + BBB
12524
12525 - ddd
12526 - eee
12527 + EEE
12528 fff
12529 "
12530 .unindent(),
12531 );
12532}
12533
12534#[gpui::test]
12535async fn test_edits_around_expanded_insertion_hunks(
12536 executor: BackgroundExecutor,
12537 cx: &mut gpui::TestAppContext,
12538) {
12539 init_test(cx, |_| {});
12540
12541 let mut cx = EditorTestContext::new(cx).await;
12542
12543 let diff_base = r#"
12544 use some::mod1;
12545 use some::mod2;
12546
12547 const A: u32 = 42;
12548
12549 fn main() {
12550 println!("hello");
12551
12552 println!("world");
12553 }
12554 "#
12555 .unindent();
12556 executor.run_until_parked();
12557 cx.set_state(
12558 &r#"
12559 use some::mod1;
12560 use some::mod2;
12561
12562 const A: u32 = 42;
12563 const B: u32 = 42;
12564 const C: u32 = 42;
12565 ˇ
12566
12567 fn main() {
12568 println!("hello");
12569
12570 println!("world");
12571 }
12572 "#
12573 .unindent(),
12574 );
12575
12576 cx.set_diff_base(&diff_base);
12577 executor.run_until_parked();
12578
12579 cx.update_editor(|editor, cx| {
12580 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12581 });
12582 executor.run_until_parked();
12583
12584 cx.assert_state_with_diff(
12585 r#"
12586 use some::mod1;
12587 use some::mod2;
12588
12589 const A: u32 = 42;
12590 + const B: u32 = 42;
12591 + const C: u32 = 42;
12592 + ˇ
12593
12594 fn main() {
12595 println!("hello");
12596
12597 println!("world");
12598 }
12599 "#
12600 .unindent(),
12601 );
12602
12603 cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx));
12604 executor.run_until_parked();
12605
12606 cx.assert_state_with_diff(
12607 r#"
12608 use some::mod1;
12609 use some::mod2;
12610
12611 const A: u32 = 42;
12612 + const B: u32 = 42;
12613 + const C: u32 = 42;
12614 + const D: u32 = 42;
12615 + ˇ
12616
12617 fn main() {
12618 println!("hello");
12619
12620 println!("world");
12621 }
12622 "#
12623 .unindent(),
12624 );
12625
12626 cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx));
12627 executor.run_until_parked();
12628
12629 cx.assert_state_with_diff(
12630 r#"
12631 use some::mod1;
12632 use some::mod2;
12633
12634 const A: u32 = 42;
12635 + const B: u32 = 42;
12636 + const C: u32 = 42;
12637 + const D: u32 = 42;
12638 + const E: u32 = 42;
12639 + ˇ
12640
12641 fn main() {
12642 println!("hello");
12643
12644 println!("world");
12645 }
12646 "#
12647 .unindent(),
12648 );
12649
12650 cx.update_editor(|editor, cx| {
12651 editor.delete_line(&DeleteLine, cx);
12652 });
12653 executor.run_until_parked();
12654
12655 cx.assert_state_with_diff(
12656 r#"
12657 use some::mod1;
12658 use some::mod2;
12659
12660 const A: u32 = 42;
12661 + const B: u32 = 42;
12662 + const C: u32 = 42;
12663 + const D: u32 = 42;
12664 + const E: u32 = 42;
12665 ˇ
12666 fn main() {
12667 println!("hello");
12668
12669 println!("world");
12670 }
12671 "#
12672 .unindent(),
12673 );
12674
12675 cx.update_editor(|editor, cx| {
12676 editor.move_up(&MoveUp, cx);
12677 editor.delete_line(&DeleteLine, cx);
12678 editor.move_up(&MoveUp, cx);
12679 editor.delete_line(&DeleteLine, cx);
12680 editor.move_up(&MoveUp, cx);
12681 editor.delete_line(&DeleteLine, cx);
12682 });
12683 executor.run_until_parked();
12684 cx.assert_state_with_diff(
12685 r#"
12686 use some::mod1;
12687 use some::mod2;
12688
12689 const A: u32 = 42;
12690 + const B: u32 = 42;
12691 ˇ
12692 fn main() {
12693 println!("hello");
12694
12695 println!("world");
12696 }
12697 "#
12698 .unindent(),
12699 );
12700
12701 cx.update_editor(|editor, cx| {
12702 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, cx);
12703 editor.delete_line(&DeleteLine, cx);
12704 });
12705 executor.run_until_parked();
12706 cx.assert_state_with_diff(
12707 r#"
12708 use some::mod1;
12709 - use some::mod2;
12710 -
12711 - const A: u32 = 42;
12712 ˇ
12713 fn main() {
12714 println!("hello");
12715
12716 println!("world");
12717 }
12718 "#
12719 .unindent(),
12720 );
12721}
12722
12723#[gpui::test]
12724async fn test_edits_around_expanded_deletion_hunks(
12725 executor: BackgroundExecutor,
12726 cx: &mut gpui::TestAppContext,
12727) {
12728 init_test(cx, |_| {});
12729
12730 let mut cx = EditorTestContext::new(cx).await;
12731
12732 let diff_base = r#"
12733 use some::mod1;
12734 use some::mod2;
12735
12736 const A: u32 = 42;
12737 const B: u32 = 42;
12738 const C: u32 = 42;
12739
12740
12741 fn main() {
12742 println!("hello");
12743
12744 println!("world");
12745 }
12746 "#
12747 .unindent();
12748 executor.run_until_parked();
12749 cx.set_state(
12750 &r#"
12751 use some::mod1;
12752 use some::mod2;
12753
12754 ˇconst B: u32 = 42;
12755 const C: u32 = 42;
12756
12757
12758 fn main() {
12759 println!("hello");
12760
12761 println!("world");
12762 }
12763 "#
12764 .unindent(),
12765 );
12766
12767 cx.set_diff_base(&diff_base);
12768 executor.run_until_parked();
12769
12770 cx.update_editor(|editor, cx| {
12771 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12772 });
12773 executor.run_until_parked();
12774
12775 cx.assert_state_with_diff(
12776 r#"
12777 use some::mod1;
12778 use some::mod2;
12779
12780 - const A: u32 = 42;
12781 ˇconst B: u32 = 42;
12782 const C: u32 = 42;
12783
12784
12785 fn main() {
12786 println!("hello");
12787
12788 println!("world");
12789 }
12790 "#
12791 .unindent(),
12792 );
12793
12794 cx.update_editor(|editor, cx| {
12795 editor.delete_line(&DeleteLine, cx);
12796 });
12797 executor.run_until_parked();
12798 cx.assert_state_with_diff(
12799 r#"
12800 use some::mod1;
12801 use some::mod2;
12802
12803 - const A: u32 = 42;
12804 - const B: u32 = 42;
12805 ˇconst C: u32 = 42;
12806
12807
12808 fn main() {
12809 println!("hello");
12810
12811 println!("world");
12812 }
12813 "#
12814 .unindent(),
12815 );
12816
12817 cx.update_editor(|editor, cx| {
12818 editor.delete_line(&DeleteLine, cx);
12819 });
12820 executor.run_until_parked();
12821 cx.assert_state_with_diff(
12822 r#"
12823 use some::mod1;
12824 use some::mod2;
12825
12826 - const A: u32 = 42;
12827 - const B: u32 = 42;
12828 - const C: u32 = 42;
12829 ˇ
12830
12831 fn main() {
12832 println!("hello");
12833
12834 println!("world");
12835 }
12836 "#
12837 .unindent(),
12838 );
12839
12840 cx.update_editor(|editor, cx| {
12841 editor.handle_input("replacement", cx);
12842 });
12843 executor.run_until_parked();
12844 cx.assert_state_with_diff(
12845 r#"
12846 use some::mod1;
12847 use some::mod2;
12848
12849 - const A: u32 = 42;
12850 - const B: u32 = 42;
12851 - const C: u32 = 42;
12852 -
12853 + replacementˇ
12854
12855 fn main() {
12856 println!("hello");
12857
12858 println!("world");
12859 }
12860 "#
12861 .unindent(),
12862 );
12863}
12864
12865#[gpui::test]
12866async fn test_edit_after_expanded_modification_hunk(
12867 executor: BackgroundExecutor,
12868 cx: &mut gpui::TestAppContext,
12869) {
12870 init_test(cx, |_| {});
12871
12872 let mut cx = EditorTestContext::new(cx).await;
12873
12874 let diff_base = r#"
12875 use some::mod1;
12876 use some::mod2;
12877
12878 const A: u32 = 42;
12879 const B: u32 = 42;
12880 const C: u32 = 42;
12881 const D: u32 = 42;
12882
12883
12884 fn main() {
12885 println!("hello");
12886
12887 println!("world");
12888 }"#
12889 .unindent();
12890
12891 cx.set_state(
12892 &r#"
12893 use some::mod1;
12894 use some::mod2;
12895
12896 const A: u32 = 42;
12897 const B: u32 = 42;
12898 const C: u32 = 43ˇ
12899 const D: u32 = 42;
12900
12901
12902 fn main() {
12903 println!("hello");
12904
12905 println!("world");
12906 }"#
12907 .unindent(),
12908 );
12909
12910 cx.set_diff_base(&diff_base);
12911 executor.run_until_parked();
12912 cx.update_editor(|editor, cx| {
12913 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12914 });
12915 executor.run_until_parked();
12916
12917 cx.assert_state_with_diff(
12918 r#"
12919 use some::mod1;
12920 use some::mod2;
12921
12922 const A: u32 = 42;
12923 const B: u32 = 42;
12924 - const C: u32 = 42;
12925 + const C: u32 = 43ˇ
12926 const D: u32 = 42;
12927
12928
12929 fn main() {
12930 println!("hello");
12931
12932 println!("world");
12933 }"#
12934 .unindent(),
12935 );
12936
12937 cx.update_editor(|editor, cx| {
12938 editor.handle_input("\nnew_line\n", cx);
12939 });
12940 executor.run_until_parked();
12941
12942 cx.assert_state_with_diff(
12943 r#"
12944 use some::mod1;
12945 use some::mod2;
12946
12947 const A: u32 = 42;
12948 const B: u32 = 42;
12949 - const C: u32 = 42;
12950 + const C: u32 = 43
12951 + new_line
12952 + ˇ
12953 const D: u32 = 42;
12954
12955
12956 fn main() {
12957 println!("hello");
12958
12959 println!("world");
12960 }"#
12961 .unindent(),
12962 );
12963}
12964
12965async fn setup_indent_guides_editor(
12966 text: &str,
12967 cx: &mut gpui::TestAppContext,
12968) -> (BufferId, EditorTestContext) {
12969 init_test(cx, |_| {});
12970
12971 let mut cx = EditorTestContext::new(cx).await;
12972
12973 let buffer_id = cx.update_editor(|editor, cx| {
12974 editor.set_text(text, cx);
12975 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
12976
12977 buffer_ids[0]
12978 });
12979
12980 (buffer_id, cx)
12981}
12982
12983fn assert_indent_guides(
12984 range: Range<u32>,
12985 expected: Vec<IndentGuide>,
12986 active_indices: Option<Vec<usize>>,
12987 cx: &mut EditorTestContext,
12988) {
12989 let indent_guides = cx.update_editor(|editor, cx| {
12990 let snapshot = editor.snapshot(cx).display_snapshot;
12991 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
12992 MultiBufferRow(range.start)..MultiBufferRow(range.end),
12993 true,
12994 &snapshot,
12995 cx,
12996 );
12997
12998 indent_guides.sort_by(|a, b| {
12999 a.depth.cmp(&b.depth).then(
13000 a.start_row
13001 .cmp(&b.start_row)
13002 .then(a.end_row.cmp(&b.end_row)),
13003 )
13004 });
13005 indent_guides
13006 });
13007
13008 if let Some(expected) = active_indices {
13009 let active_indices = cx.update_editor(|editor, cx| {
13010 let snapshot = editor.snapshot(cx).display_snapshot;
13011 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, cx)
13012 });
13013
13014 assert_eq!(
13015 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
13016 expected,
13017 "Active indent guide indices do not match"
13018 );
13019 }
13020
13021 let expected: Vec<_> = expected
13022 .into_iter()
13023 .map(|guide| MultiBufferIndentGuide {
13024 multibuffer_row_range: MultiBufferRow(guide.start_row)..MultiBufferRow(guide.end_row),
13025 buffer: guide,
13026 })
13027 .collect();
13028
13029 assert_eq!(indent_guides, expected, "Indent guides do not match");
13030}
13031
13032fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
13033 IndentGuide {
13034 buffer_id,
13035 start_row,
13036 end_row,
13037 depth,
13038 tab_size: 4,
13039 settings: IndentGuideSettings {
13040 enabled: true,
13041 line_width: 1,
13042 active_line_width: 1,
13043 ..Default::default()
13044 },
13045 }
13046}
13047
13048#[gpui::test]
13049async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
13050 let (buffer_id, mut cx) = setup_indent_guides_editor(
13051 &"
13052 fn main() {
13053 let a = 1;
13054 }"
13055 .unindent(),
13056 cx,
13057 )
13058 .await;
13059
13060 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
13061}
13062
13063#[gpui::test]
13064async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) {
13065 let (buffer_id, mut cx) = setup_indent_guides_editor(
13066 &"
13067 fn main() {
13068 let a = 1;
13069 let b = 2;
13070 }"
13071 .unindent(),
13072 cx,
13073 )
13074 .await;
13075
13076 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
13077}
13078
13079#[gpui::test]
13080async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) {
13081 let (buffer_id, mut cx) = setup_indent_guides_editor(
13082 &"
13083 fn main() {
13084 let a = 1;
13085 if a == 3 {
13086 let b = 2;
13087 } else {
13088 let c = 3;
13089 }
13090 }"
13091 .unindent(),
13092 cx,
13093 )
13094 .await;
13095
13096 assert_indent_guides(
13097 0..8,
13098 vec![
13099 indent_guide(buffer_id, 1, 6, 0),
13100 indent_guide(buffer_id, 3, 3, 1),
13101 indent_guide(buffer_id, 5, 5, 1),
13102 ],
13103 None,
13104 &mut cx,
13105 );
13106}
13107
13108#[gpui::test]
13109async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) {
13110 let (buffer_id, mut cx) = setup_indent_guides_editor(
13111 &"
13112 fn main() {
13113 let a = 1;
13114 let b = 2;
13115 let c = 3;
13116 }"
13117 .unindent(),
13118 cx,
13119 )
13120 .await;
13121
13122 assert_indent_guides(
13123 0..5,
13124 vec![
13125 indent_guide(buffer_id, 1, 3, 0),
13126 indent_guide(buffer_id, 2, 2, 1),
13127 ],
13128 None,
13129 &mut cx,
13130 );
13131}
13132
13133#[gpui::test]
13134async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext) {
13135 let (buffer_id, mut cx) = setup_indent_guides_editor(
13136 &"
13137 fn main() {
13138 let a = 1;
13139
13140 let c = 3;
13141 }"
13142 .unindent(),
13143 cx,
13144 )
13145 .await;
13146
13147 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
13148}
13149
13150#[gpui::test]
13151async fn test_indent_guide_complex(cx: &mut gpui::TestAppContext) {
13152 let (buffer_id, mut cx) = setup_indent_guides_editor(
13153 &"
13154 fn main() {
13155 let a = 1;
13156
13157 let c = 3;
13158
13159 if a == 3 {
13160 let b = 2;
13161 } else {
13162 let c = 3;
13163 }
13164 }"
13165 .unindent(),
13166 cx,
13167 )
13168 .await;
13169
13170 assert_indent_guides(
13171 0..11,
13172 vec![
13173 indent_guide(buffer_id, 1, 9, 0),
13174 indent_guide(buffer_id, 6, 6, 1),
13175 indent_guide(buffer_id, 8, 8, 1),
13176 ],
13177 None,
13178 &mut cx,
13179 );
13180}
13181
13182#[gpui::test]
13183async fn test_indent_guide_starts_off_screen(cx: &mut gpui::TestAppContext) {
13184 let (buffer_id, mut cx) = setup_indent_guides_editor(
13185 &"
13186 fn main() {
13187 let a = 1;
13188
13189 let c = 3;
13190
13191 if a == 3 {
13192 let b = 2;
13193 } else {
13194 let c = 3;
13195 }
13196 }"
13197 .unindent(),
13198 cx,
13199 )
13200 .await;
13201
13202 assert_indent_guides(
13203 1..11,
13204 vec![
13205 indent_guide(buffer_id, 1, 9, 0),
13206 indent_guide(buffer_id, 6, 6, 1),
13207 indent_guide(buffer_id, 8, 8, 1),
13208 ],
13209 None,
13210 &mut cx,
13211 );
13212}
13213
13214#[gpui::test]
13215async fn test_indent_guide_ends_off_screen(cx: &mut gpui::TestAppContext) {
13216 let (buffer_id, mut cx) = setup_indent_guides_editor(
13217 &"
13218 fn main() {
13219 let a = 1;
13220
13221 let c = 3;
13222
13223 if a == 3 {
13224 let b = 2;
13225 } else {
13226 let c = 3;
13227 }
13228 }"
13229 .unindent(),
13230 cx,
13231 )
13232 .await;
13233
13234 assert_indent_guides(
13235 1..10,
13236 vec![
13237 indent_guide(buffer_id, 1, 9, 0),
13238 indent_guide(buffer_id, 6, 6, 1),
13239 indent_guide(buffer_id, 8, 8, 1),
13240 ],
13241 None,
13242 &mut cx,
13243 );
13244}
13245
13246#[gpui::test]
13247async fn test_indent_guide_without_brackets(cx: &mut gpui::TestAppContext) {
13248 let (buffer_id, mut cx) = setup_indent_guides_editor(
13249 &"
13250 block1
13251 block2
13252 block3
13253 block4
13254 block2
13255 block1
13256 block1"
13257 .unindent(),
13258 cx,
13259 )
13260 .await;
13261
13262 assert_indent_guides(
13263 1..10,
13264 vec![
13265 indent_guide(buffer_id, 1, 4, 0),
13266 indent_guide(buffer_id, 2, 3, 1),
13267 indent_guide(buffer_id, 3, 3, 2),
13268 ],
13269 None,
13270 &mut cx,
13271 );
13272}
13273
13274#[gpui::test]
13275async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext) {
13276 let (buffer_id, mut cx) = setup_indent_guides_editor(
13277 &"
13278 block1
13279 block2
13280 block3
13281
13282 block1
13283 block1"
13284 .unindent(),
13285 cx,
13286 )
13287 .await;
13288
13289 assert_indent_guides(
13290 0..6,
13291 vec![
13292 indent_guide(buffer_id, 1, 2, 0),
13293 indent_guide(buffer_id, 2, 2, 1),
13294 ],
13295 None,
13296 &mut cx,
13297 );
13298}
13299
13300#[gpui::test]
13301async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext) {
13302 let (buffer_id, mut cx) = setup_indent_guides_editor(
13303 &"
13304 block1
13305
13306
13307
13308 block2
13309 "
13310 .unindent(),
13311 cx,
13312 )
13313 .await;
13314
13315 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
13316}
13317
13318#[gpui::test]
13319async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) {
13320 let (buffer_id, mut cx) = setup_indent_guides_editor(
13321 &"
13322 def a:
13323 \tb = 3
13324 \tif True:
13325 \t\tc = 4
13326 \t\td = 5
13327 \tprint(b)
13328 "
13329 .unindent(),
13330 cx,
13331 )
13332 .await;
13333
13334 assert_indent_guides(
13335 0..6,
13336 vec![
13337 indent_guide(buffer_id, 1, 6, 0),
13338 indent_guide(buffer_id, 3, 4, 1),
13339 ],
13340 None,
13341 &mut cx,
13342 );
13343}
13344
13345#[gpui::test]
13346async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
13347 let (buffer_id, mut cx) = setup_indent_guides_editor(
13348 &"
13349 fn main() {
13350 let a = 1;
13351 }"
13352 .unindent(),
13353 cx,
13354 )
13355 .await;
13356
13357 cx.update_editor(|editor, cx| {
13358 editor.change_selections(None, cx, |s| {
13359 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13360 });
13361 });
13362
13363 assert_indent_guides(
13364 0..3,
13365 vec![indent_guide(buffer_id, 1, 1, 0)],
13366 Some(vec![0]),
13367 &mut cx,
13368 );
13369}
13370
13371#[gpui::test]
13372async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppContext) {
13373 let (buffer_id, mut cx) = setup_indent_guides_editor(
13374 &"
13375 fn main() {
13376 if 1 == 2 {
13377 let a = 1;
13378 }
13379 }"
13380 .unindent(),
13381 cx,
13382 )
13383 .await;
13384
13385 cx.update_editor(|editor, cx| {
13386 editor.change_selections(None, cx, |s| {
13387 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13388 });
13389 });
13390
13391 assert_indent_guides(
13392 0..4,
13393 vec![
13394 indent_guide(buffer_id, 1, 3, 0),
13395 indent_guide(buffer_id, 2, 2, 1),
13396 ],
13397 Some(vec![1]),
13398 &mut cx,
13399 );
13400
13401 cx.update_editor(|editor, cx| {
13402 editor.change_selections(None, cx, |s| {
13403 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13404 });
13405 });
13406
13407 assert_indent_guides(
13408 0..4,
13409 vec![
13410 indent_guide(buffer_id, 1, 3, 0),
13411 indent_guide(buffer_id, 2, 2, 1),
13412 ],
13413 Some(vec![1]),
13414 &mut cx,
13415 );
13416
13417 cx.update_editor(|editor, cx| {
13418 editor.change_selections(None, cx, |s| {
13419 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
13420 });
13421 });
13422
13423 assert_indent_guides(
13424 0..4,
13425 vec![
13426 indent_guide(buffer_id, 1, 3, 0),
13427 indent_guide(buffer_id, 2, 2, 1),
13428 ],
13429 Some(vec![0]),
13430 &mut cx,
13431 );
13432}
13433
13434#[gpui::test]
13435async fn test_active_indent_guide_empty_line(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 b = 2;
13442 }"
13443 .unindent(),
13444 cx,
13445 )
13446 .await;
13447
13448 cx.update_editor(|editor, cx| {
13449 editor.change_selections(None, cx, |s| {
13450 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13451 });
13452 });
13453
13454 assert_indent_guides(
13455 0..5,
13456 vec![indent_guide(buffer_id, 1, 3, 0)],
13457 Some(vec![0]),
13458 &mut cx,
13459 );
13460}
13461
13462#[gpui::test]
13463async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppContext) {
13464 let (buffer_id, mut cx) = setup_indent_guides_editor(
13465 &"
13466 def m:
13467 a = 1
13468 pass"
13469 .unindent(),
13470 cx,
13471 )
13472 .await;
13473
13474 cx.update_editor(|editor, cx| {
13475 editor.change_selections(None, cx, |s| {
13476 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13477 });
13478 });
13479
13480 assert_indent_guides(
13481 0..3,
13482 vec![indent_guide(buffer_id, 1, 2, 0)],
13483 Some(vec![0]),
13484 &mut cx,
13485 );
13486}
13487
13488#[gpui::test]
13489fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
13490 init_test(cx, |_| {});
13491
13492 let editor = cx.add_window(|cx| {
13493 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
13494 build_editor(buffer, cx)
13495 });
13496
13497 let render_args = Arc::new(Mutex::new(None));
13498 let snapshot = editor
13499 .update(cx, |editor, cx| {
13500 let snapshot = editor.buffer().read(cx).snapshot(cx);
13501 let range =
13502 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
13503
13504 struct RenderArgs {
13505 row: MultiBufferRow,
13506 folded: bool,
13507 callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
13508 }
13509
13510 let crease = Crease::inline(
13511 range,
13512 FoldPlaceholder::test(),
13513 {
13514 let toggle_callback = render_args.clone();
13515 move |row, folded, callback, _cx| {
13516 *toggle_callback.lock() = Some(RenderArgs {
13517 row,
13518 folded,
13519 callback,
13520 });
13521 div()
13522 }
13523 },
13524 |_row, _folded, _cx| div(),
13525 );
13526
13527 editor.insert_creases(Some(crease), cx);
13528 let snapshot = editor.snapshot(cx);
13529 let _div =
13530 snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
13531 snapshot
13532 })
13533 .unwrap();
13534
13535 let render_args = render_args.lock().take().unwrap();
13536 assert_eq!(render_args.row, MultiBufferRow(1));
13537 assert!(!render_args.folded);
13538 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13539
13540 cx.update_window(*editor, |_, cx| (render_args.callback)(true, cx))
13541 .unwrap();
13542 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13543 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
13544
13545 cx.update_window(*editor, |_, cx| (render_args.callback)(false, cx))
13546 .unwrap();
13547 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13548 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13549}
13550
13551#[gpui::test]
13552async fn test_input_text(cx: &mut gpui::TestAppContext) {
13553 init_test(cx, |_| {});
13554 let mut cx = EditorTestContext::new(cx).await;
13555
13556 cx.set_state(
13557 &r#"ˇone
13558 two
13559
13560 three
13561 fourˇ
13562 five
13563
13564 siˇx"#
13565 .unindent(),
13566 );
13567
13568 cx.dispatch_action(HandleInput(String::new()));
13569 cx.assert_editor_state(
13570 &r#"ˇone
13571 two
13572
13573 three
13574 fourˇ
13575 five
13576
13577 siˇx"#
13578 .unindent(),
13579 );
13580
13581 cx.dispatch_action(HandleInput("AAAA".to_string()));
13582 cx.assert_editor_state(
13583 &r#"AAAAˇone
13584 two
13585
13586 three
13587 fourAAAAˇ
13588 five
13589
13590 siAAAAˇx"#
13591 .unindent(),
13592 );
13593}
13594
13595#[gpui::test]
13596async fn test_scroll_cursor_center_top_bottom(cx: &mut gpui::TestAppContext) {
13597 init_test(cx, |_| {});
13598
13599 let mut cx = EditorTestContext::new(cx).await;
13600 cx.set_state(
13601 r#"let foo = 1;
13602let foo = 2;
13603let foo = 3;
13604let fooˇ = 4;
13605let foo = 5;
13606let foo = 6;
13607let foo = 7;
13608let foo = 8;
13609let foo = 9;
13610let foo = 10;
13611let foo = 11;
13612let foo = 12;
13613let foo = 13;
13614let foo = 14;
13615let foo = 15;"#,
13616 );
13617
13618 cx.update_editor(|e, cx| {
13619 assert_eq!(
13620 e.next_scroll_position,
13621 NextScrollCursorCenterTopBottom::Center,
13622 "Default next scroll direction is center",
13623 );
13624
13625 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13626 assert_eq!(
13627 e.next_scroll_position,
13628 NextScrollCursorCenterTopBottom::Top,
13629 "After center, next scroll direction should be top",
13630 );
13631
13632 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13633 assert_eq!(
13634 e.next_scroll_position,
13635 NextScrollCursorCenterTopBottom::Bottom,
13636 "After top, next scroll direction should be bottom",
13637 );
13638
13639 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13640 assert_eq!(
13641 e.next_scroll_position,
13642 NextScrollCursorCenterTopBottom::Center,
13643 "After bottom, scrolling should start over",
13644 );
13645
13646 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13647 assert_eq!(
13648 e.next_scroll_position,
13649 NextScrollCursorCenterTopBottom::Top,
13650 "Scrolling continues if retriggered fast enough"
13651 );
13652 });
13653
13654 cx.executor()
13655 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
13656 cx.executor().run_until_parked();
13657 cx.update_editor(|e, _| {
13658 assert_eq!(
13659 e.next_scroll_position,
13660 NextScrollCursorCenterTopBottom::Center,
13661 "If scrolling is not triggered fast enough, it should reset"
13662 );
13663 });
13664}
13665
13666#[gpui::test]
13667async fn test_goto_definition_with_find_all_references_fallback(cx: &mut gpui::TestAppContext) {
13668 init_test(cx, |_| {});
13669 let mut cx = EditorLspTestContext::new_rust(
13670 lsp::ServerCapabilities {
13671 definition_provider: Some(lsp::OneOf::Left(true)),
13672 references_provider: Some(lsp::OneOf::Left(true)),
13673 ..lsp::ServerCapabilities::default()
13674 },
13675 cx,
13676 )
13677 .await;
13678
13679 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
13680 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
13681 move |params, _| async move {
13682 if empty_go_to_definition {
13683 Ok(None)
13684 } else {
13685 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
13686 uri: params.text_document_position_params.text_document.uri,
13687 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
13688 })))
13689 }
13690 },
13691 );
13692 let references =
13693 cx.lsp
13694 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
13695 Ok(Some(vec![lsp::Location {
13696 uri: params.text_document_position.text_document.uri,
13697 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
13698 }]))
13699 });
13700 (go_to_definition, references)
13701 };
13702
13703 cx.set_state(
13704 &r#"fn one() {
13705 let mut a = ˇtwo();
13706 }
13707
13708 fn two() {}"#
13709 .unindent(),
13710 );
13711 set_up_lsp_handlers(false, &mut cx);
13712 let navigated = cx
13713 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13714 .await
13715 .expect("Failed to navigate to definition");
13716 assert_eq!(
13717 navigated,
13718 Navigated::Yes,
13719 "Should have navigated to definition from the GetDefinition response"
13720 );
13721 cx.assert_editor_state(
13722 &r#"fn one() {
13723 let mut a = two();
13724 }
13725
13726 fn «twoˇ»() {}"#
13727 .unindent(),
13728 );
13729
13730 let editors = cx.update_workspace(|workspace, cx| {
13731 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13732 });
13733 cx.update_editor(|_, test_editor_cx| {
13734 assert_eq!(
13735 editors.len(),
13736 1,
13737 "Initially, only one, test, editor should be open in the workspace"
13738 );
13739 assert_eq!(
13740 test_editor_cx.view(),
13741 editors.last().expect("Asserted len is 1")
13742 );
13743 });
13744
13745 set_up_lsp_handlers(true, &mut cx);
13746 let navigated = cx
13747 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13748 .await
13749 .expect("Failed to navigate to lookup references");
13750 assert_eq!(
13751 navigated,
13752 Navigated::Yes,
13753 "Should have navigated to references as a fallback after empty GoToDefinition response"
13754 );
13755 // We should not change the selections in the existing file,
13756 // if opening another milti buffer with the references
13757 cx.assert_editor_state(
13758 &r#"fn one() {
13759 let mut a = two();
13760 }
13761
13762 fn «twoˇ»() {}"#
13763 .unindent(),
13764 );
13765 let editors = cx.update_workspace(|workspace, cx| {
13766 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13767 });
13768 cx.update_editor(|_, test_editor_cx| {
13769 assert_eq!(
13770 editors.len(),
13771 2,
13772 "After falling back to references search, we open a new editor with the results"
13773 );
13774 let references_fallback_text = editors
13775 .into_iter()
13776 .find(|new_editor| new_editor != test_editor_cx.view())
13777 .expect("Should have one non-test editor now")
13778 .read(test_editor_cx)
13779 .text(test_editor_cx);
13780 assert_eq!(
13781 references_fallback_text, "fn one() {\n let mut a = two();\n}",
13782 "Should use the range from the references response and not the GoToDefinition one"
13783 );
13784 });
13785}
13786
13787#[gpui::test]
13788async fn test_find_enclosing_node_with_task(cx: &mut gpui::TestAppContext) {
13789 init_test(cx, |_| {});
13790
13791 let language = Arc::new(Language::new(
13792 LanguageConfig::default(),
13793 Some(tree_sitter_rust::LANGUAGE.into()),
13794 ));
13795
13796 let text = r#"
13797 #[cfg(test)]
13798 mod tests() {
13799 #[test]
13800 fn runnable_1() {
13801 let a = 1;
13802 }
13803
13804 #[test]
13805 fn runnable_2() {
13806 let a = 1;
13807 let b = 2;
13808 }
13809 }
13810 "#
13811 .unindent();
13812
13813 let fs = FakeFs::new(cx.executor());
13814 fs.insert_file("/file.rs", Default::default()).await;
13815
13816 let project = Project::test(fs, ["/a".as_ref()], cx).await;
13817 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
13818 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
13819 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
13820 let multi_buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx));
13821
13822 let editor = cx.new_view(|cx| {
13823 Editor::new(
13824 EditorMode::Full,
13825 multi_buffer,
13826 Some(project.clone()),
13827 true,
13828 cx,
13829 )
13830 });
13831
13832 editor.update(cx, |editor, cx| {
13833 editor.tasks.insert(
13834 (buffer.read(cx).remote_id(), 3),
13835 RunnableTasks {
13836 templates: vec![],
13837 offset: MultiBufferOffset(43),
13838 column: 0,
13839 extra_variables: HashMap::default(),
13840 context_range: BufferOffset(43)..BufferOffset(85),
13841 },
13842 );
13843 editor.tasks.insert(
13844 (buffer.read(cx).remote_id(), 8),
13845 RunnableTasks {
13846 templates: vec![],
13847 offset: MultiBufferOffset(86),
13848 column: 0,
13849 extra_variables: HashMap::default(),
13850 context_range: BufferOffset(86)..BufferOffset(191),
13851 },
13852 );
13853
13854 // Test finding task when cursor is inside function body
13855 editor.change_selections(None, cx, |s| {
13856 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
13857 });
13858 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
13859 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
13860
13861 // Test finding task when cursor is on function name
13862 editor.change_selections(None, cx, |s| {
13863 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
13864 });
13865 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
13866 assert_eq!(row, 8, "Should find task when cursor is on function name");
13867 });
13868}
13869
13870fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
13871 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
13872 point..point
13873}
13874
13875fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
13876 let (text, ranges) = marked_text_ranges(marked_text, true);
13877 assert_eq!(view.text(cx), text);
13878 assert_eq!(
13879 view.selections.ranges(cx),
13880 ranges,
13881 "Assert selections are {}",
13882 marked_text
13883 );
13884}
13885
13886pub fn handle_signature_help_request(
13887 cx: &mut EditorLspTestContext,
13888 mocked_response: lsp::SignatureHelp,
13889) -> impl Future<Output = ()> {
13890 let mut request =
13891 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
13892 let mocked_response = mocked_response.clone();
13893 async move { Ok(Some(mocked_response)) }
13894 });
13895
13896 async move {
13897 request.next().await;
13898 }
13899}
13900
13901/// Handle completion request passing a marked string specifying where the completion
13902/// should be triggered from using '|' character, what range should be replaced, and what completions
13903/// should be returned using '<' and '>' to delimit the range
13904pub fn handle_completion_request(
13905 cx: &mut EditorLspTestContext,
13906 marked_string: &str,
13907 completions: Vec<&'static str>,
13908 counter: Arc<AtomicUsize>,
13909) -> impl Future<Output = ()> {
13910 let complete_from_marker: TextRangeMarker = '|'.into();
13911 let replace_range_marker: TextRangeMarker = ('<', '>').into();
13912 let (_, mut marked_ranges) = marked_text_ranges_by(
13913 marked_string,
13914 vec![complete_from_marker.clone(), replace_range_marker.clone()],
13915 );
13916
13917 let complete_from_position =
13918 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
13919 let replace_range =
13920 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
13921
13922 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
13923 let completions = completions.clone();
13924 counter.fetch_add(1, atomic::Ordering::Release);
13925 async move {
13926 assert_eq!(params.text_document_position.text_document.uri, url.clone());
13927 assert_eq!(
13928 params.text_document_position.position,
13929 complete_from_position
13930 );
13931 Ok(Some(lsp::CompletionResponse::Array(
13932 completions
13933 .iter()
13934 .map(|completion_text| lsp::CompletionItem {
13935 label: completion_text.to_string(),
13936 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13937 range: replace_range,
13938 new_text: completion_text.to_string(),
13939 })),
13940 ..Default::default()
13941 })
13942 .collect(),
13943 )))
13944 }
13945 });
13946
13947 async move {
13948 request.next().await;
13949 }
13950}
13951
13952fn handle_resolve_completion_request(
13953 cx: &mut EditorLspTestContext,
13954 edits: Option<Vec<(&'static str, &'static str)>>,
13955) -> impl Future<Output = ()> {
13956 let edits = edits.map(|edits| {
13957 edits
13958 .iter()
13959 .map(|(marked_string, new_text)| {
13960 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
13961 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
13962 lsp::TextEdit::new(replace_range, new_text.to_string())
13963 })
13964 .collect::<Vec<_>>()
13965 });
13966
13967 let mut request =
13968 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
13969 let edits = edits.clone();
13970 async move {
13971 Ok(lsp::CompletionItem {
13972 additional_text_edits: edits,
13973 ..Default::default()
13974 })
13975 }
13976 });
13977
13978 async move {
13979 request.next().await;
13980 }
13981}
13982
13983pub(crate) fn update_test_language_settings(
13984 cx: &mut TestAppContext,
13985 f: impl Fn(&mut AllLanguageSettingsContent),
13986) {
13987 cx.update(|cx| {
13988 SettingsStore::update_global(cx, |store, cx| {
13989 store.update_user_settings::<AllLanguageSettings>(cx, f);
13990 });
13991 });
13992}
13993
13994pub(crate) fn update_test_project_settings(
13995 cx: &mut TestAppContext,
13996 f: impl Fn(&mut ProjectSettings),
13997) {
13998 cx.update(|cx| {
13999 SettingsStore::update_global(cx, |store, cx| {
14000 store.update_user_settings::<ProjectSettings>(cx, f);
14001 });
14002 });
14003}
14004
14005pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
14006 cx.update(|cx| {
14007 assets::Assets.load_test_fonts(cx);
14008 let store = SettingsStore::test(cx);
14009 cx.set_global(store);
14010 theme::init(theme::LoadThemes::JustBase, cx);
14011 release_channel::init(SemanticVersion::default(), cx);
14012 client::init_settings(cx);
14013 language::init(cx);
14014 Project::init_settings(cx);
14015 workspace::init_settings(cx);
14016 crate::init(cx);
14017 });
14018
14019 update_test_language_settings(cx, f);
14020}
14021
14022#[track_caller]
14023fn assert_hunk_revert(
14024 not_reverted_text_with_selections: &str,
14025 expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
14026 expected_reverted_text_with_selections: &str,
14027 base_text: &str,
14028 cx: &mut EditorLspTestContext,
14029) {
14030 cx.set_state(not_reverted_text_with_selections);
14031 cx.set_diff_base(base_text);
14032 cx.executor().run_until_parked();
14033
14034 let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
14035 let snapshot = editor.snapshot(cx);
14036 let reverted_hunk_statuses = snapshot
14037 .diff_map
14038 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len(), &snapshot.buffer_snapshot)
14039 .map(|hunk| hunk_status(&hunk))
14040 .collect::<Vec<_>>();
14041
14042 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
14043 reverted_hunk_statuses
14044 });
14045 cx.executor().run_until_parked();
14046 cx.assert_editor_state(expected_reverted_text_with_selections);
14047 assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
14048}