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.borrow_mut().is_none()));
8350 cx.simulate_keystroke("s");
8351 assert!(cx.editor(|e, _| e.context_menu.borrow_mut().is_none()));
8352
8353 cx.assert_editor_state(indoc! {"
8354 one.second_completion
8355 two sˇ
8356 three sˇ
8357 additional edit
8358 "});
8359 handle_completion_request(
8360 &mut cx,
8361 indoc! {"
8362 one.second_completion
8363 two s
8364 three <s|>
8365 additional edit
8366 "},
8367 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8368 counter.clone(),
8369 )
8370 .await;
8371 cx.condition(|editor, _| editor.context_menu_visible())
8372 .await;
8373 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
8374
8375 cx.simulate_keystroke("i");
8376
8377 handle_completion_request(
8378 &mut cx,
8379 indoc! {"
8380 one.second_completion
8381 two si
8382 three <si|>
8383 additional edit
8384 "},
8385 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8386 counter.clone(),
8387 )
8388 .await;
8389 cx.condition(|editor, _| editor.context_menu_visible())
8390 .await;
8391 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
8392
8393 let apply_additional_edits = cx.update_editor(|editor, cx| {
8394 editor
8395 .confirm_completion(&ConfirmCompletion::default(), cx)
8396 .unwrap()
8397 });
8398 cx.assert_editor_state(indoc! {"
8399 one.second_completion
8400 two sixth_completionˇ
8401 three sixth_completionˇ
8402 additional edit
8403 "});
8404
8405 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.borrow_mut().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.borrow_mut().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.borrow_mut().as_ref()
8476 {
8477 assert_eq!(completion_menu_entries(&menu.entries), &["first", "last"]);
8478 } else {
8479 panic!("expected completion menu to be open");
8480 }
8481 });
8482
8483 cx.update_editor(|editor, cx| {
8484 editor.move_page_down(&MovePageDown::default(), cx);
8485 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
8486 {
8487 assert!(
8488 menu.selected_item == 1,
8489 "expected PageDown to select the last item from the context menu"
8490 );
8491 } else {
8492 panic!("expected completion menu to stay open after PageDown");
8493 }
8494 });
8495
8496 cx.update_editor(|editor, cx| {
8497 editor.move_page_up(&MovePageUp::default(), cx);
8498 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
8499 {
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.borrow_mut().as_ref()
8568 {
8569 assert_eq!(
8570 completion_menu_entries(&menu.entries),
8571 &["r", "ret", "Range", "return"]
8572 );
8573 } else {
8574 panic!("expected completion menu to be open");
8575 }
8576 });
8577}
8578
8579#[gpui::test]
8580async fn test_no_duplicated_completion_requests(cx: &mut gpui::TestAppContext) {
8581 init_test(cx, |_| {});
8582
8583 let mut cx = EditorLspTestContext::new_rust(
8584 lsp::ServerCapabilities {
8585 completion_provider: Some(lsp::CompletionOptions {
8586 trigger_characters: Some(vec![".".to_string()]),
8587 resolve_provider: Some(true),
8588 ..Default::default()
8589 }),
8590 ..Default::default()
8591 },
8592 cx,
8593 )
8594 .await;
8595
8596 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
8597 cx.simulate_keystroke(".");
8598 let completion_item = lsp::CompletionItem {
8599 label: "Some".into(),
8600 kind: Some(lsp::CompletionItemKind::SNIPPET),
8601 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
8602 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
8603 kind: lsp::MarkupKind::Markdown,
8604 value: "```rust\nSome(2)\n```".to_string(),
8605 })),
8606 deprecated: Some(false),
8607 sort_text: Some("Some".to_string()),
8608 filter_text: Some("Some".to_string()),
8609 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
8610 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8611 range: lsp::Range {
8612 start: lsp::Position {
8613 line: 0,
8614 character: 22,
8615 },
8616 end: lsp::Position {
8617 line: 0,
8618 character: 22,
8619 },
8620 },
8621 new_text: "Some(2)".to_string(),
8622 })),
8623 additional_text_edits: Some(vec![lsp::TextEdit {
8624 range: lsp::Range {
8625 start: lsp::Position {
8626 line: 0,
8627 character: 20,
8628 },
8629 end: lsp::Position {
8630 line: 0,
8631 character: 22,
8632 },
8633 },
8634 new_text: "".to_string(),
8635 }]),
8636 ..Default::default()
8637 };
8638
8639 let closure_completion_item = completion_item.clone();
8640 let counter = Arc::new(AtomicUsize::new(0));
8641 let counter_clone = counter.clone();
8642 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
8643 let task_completion_item = closure_completion_item.clone();
8644 counter_clone.fetch_add(1, atomic::Ordering::Release);
8645 async move {
8646 Ok(Some(lsp::CompletionResponse::Array(vec![
8647 task_completion_item,
8648 ])))
8649 }
8650 });
8651
8652 cx.condition(|editor, _| editor.context_menu_visible())
8653 .await;
8654 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
8655 assert!(request.next().await.is_some());
8656 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8657
8658 cx.simulate_keystroke("S");
8659 cx.simulate_keystroke("o");
8660 cx.simulate_keystroke("m");
8661 cx.condition(|editor, _| editor.context_menu_visible())
8662 .await;
8663 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
8664 assert!(request.next().await.is_some());
8665 assert!(request.next().await.is_some());
8666 assert!(request.next().await.is_some());
8667 request.close();
8668 assert!(request.next().await.is_none());
8669 assert_eq!(
8670 counter.load(atomic::Ordering::Acquire),
8671 4,
8672 "With the completions menu open, only one LSP request should happen per input"
8673 );
8674}
8675
8676#[gpui::test]
8677async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
8678 init_test(cx, |_| {});
8679 let mut cx = EditorTestContext::new(cx).await;
8680 let language = Arc::new(Language::new(
8681 LanguageConfig {
8682 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
8683 ..Default::default()
8684 },
8685 Some(tree_sitter_rust::LANGUAGE.into()),
8686 ));
8687 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
8688
8689 // If multiple selections intersect a line, the line is only toggled once.
8690 cx.set_state(indoc! {"
8691 fn a() {
8692 «//b();
8693 ˇ»// «c();
8694 //ˇ» d();
8695 }
8696 "});
8697
8698 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8699
8700 cx.assert_editor_state(indoc! {"
8701 fn a() {
8702 «b();
8703 c();
8704 ˇ» d();
8705 }
8706 "});
8707
8708 // The comment prefix is inserted at the same column for every line in a
8709 // selection.
8710 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8711
8712 cx.assert_editor_state(indoc! {"
8713 fn a() {
8714 // «b();
8715 // c();
8716 ˇ»// d();
8717 }
8718 "});
8719
8720 // If a selection ends at the beginning of a line, that line is not toggled.
8721 cx.set_selections_state(indoc! {"
8722 fn a() {
8723 // b();
8724 «// c();
8725 ˇ» // d();
8726 }
8727 "});
8728
8729 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8730
8731 cx.assert_editor_state(indoc! {"
8732 fn a() {
8733 // b();
8734 «c();
8735 ˇ» // d();
8736 }
8737 "});
8738
8739 // If a selection span a single line and is empty, the line is toggled.
8740 cx.set_state(indoc! {"
8741 fn a() {
8742 a();
8743 b();
8744 ˇ
8745 }
8746 "});
8747
8748 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8749
8750 cx.assert_editor_state(indoc! {"
8751 fn a() {
8752 a();
8753 b();
8754 //•ˇ
8755 }
8756 "});
8757
8758 // If a selection span multiple lines, empty lines are not toggled.
8759 cx.set_state(indoc! {"
8760 fn a() {
8761 «a();
8762
8763 c();ˇ»
8764 }
8765 "});
8766
8767 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8768
8769 cx.assert_editor_state(indoc! {"
8770 fn a() {
8771 // «a();
8772
8773 // c();ˇ»
8774 }
8775 "});
8776
8777 // If a selection includes multiple comment prefixes, all lines are uncommented.
8778 cx.set_state(indoc! {"
8779 fn a() {
8780 «// a();
8781 /// b();
8782 //! c();ˇ»
8783 }
8784 "});
8785
8786 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8787
8788 cx.assert_editor_state(indoc! {"
8789 fn a() {
8790 «a();
8791 b();
8792 c();ˇ»
8793 }
8794 "});
8795}
8796
8797#[gpui::test]
8798async fn test_toggle_comment_ignore_indent(cx: &mut gpui::TestAppContext) {
8799 init_test(cx, |_| {});
8800 let mut cx = EditorTestContext::new(cx).await;
8801 let language = Arc::new(Language::new(
8802 LanguageConfig {
8803 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
8804 ..Default::default()
8805 },
8806 Some(tree_sitter_rust::LANGUAGE.into()),
8807 ));
8808 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
8809
8810 let toggle_comments = &ToggleComments {
8811 advance_downwards: false,
8812 ignore_indent: true,
8813 };
8814
8815 // If multiple selections intersect a line, the line is only toggled once.
8816 cx.set_state(indoc! {"
8817 fn a() {
8818 // «b();
8819 // c();
8820 // ˇ» d();
8821 }
8822 "});
8823
8824 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8825
8826 cx.assert_editor_state(indoc! {"
8827 fn a() {
8828 «b();
8829 c();
8830 ˇ» d();
8831 }
8832 "});
8833
8834 // The comment prefix is inserted at the beginning of each line
8835 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8836
8837 cx.assert_editor_state(indoc! {"
8838 fn a() {
8839 // «b();
8840 // c();
8841 // ˇ» d();
8842 }
8843 "});
8844
8845 // If a selection ends at the beginning of a line, that line is not toggled.
8846 cx.set_selections_state(indoc! {"
8847 fn a() {
8848 // b();
8849 // «c();
8850 ˇ»// d();
8851 }
8852 "});
8853
8854 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8855
8856 cx.assert_editor_state(indoc! {"
8857 fn a() {
8858 // b();
8859 «c();
8860 ˇ»// d();
8861 }
8862 "});
8863
8864 // If a selection span a single line and is empty, the line is toggled.
8865 cx.set_state(indoc! {"
8866 fn a() {
8867 a();
8868 b();
8869 ˇ
8870 }
8871 "});
8872
8873 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8874
8875 cx.assert_editor_state(indoc! {"
8876 fn a() {
8877 a();
8878 b();
8879 //ˇ
8880 }
8881 "});
8882
8883 // If a selection span multiple lines, empty lines are not toggled.
8884 cx.set_state(indoc! {"
8885 fn a() {
8886 «a();
8887
8888 c();ˇ»
8889 }
8890 "});
8891
8892 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8893
8894 cx.assert_editor_state(indoc! {"
8895 fn a() {
8896 // «a();
8897
8898 // c();ˇ»
8899 }
8900 "});
8901
8902 // If a selection includes multiple comment prefixes, all lines are uncommented.
8903 cx.set_state(indoc! {"
8904 fn a() {
8905 // «a();
8906 /// b();
8907 //! c();ˇ»
8908 }
8909 "});
8910
8911 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8912
8913 cx.assert_editor_state(indoc! {"
8914 fn a() {
8915 «a();
8916 b();
8917 c();ˇ»
8918 }
8919 "});
8920}
8921
8922#[gpui::test]
8923async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
8924 init_test(cx, |_| {});
8925
8926 let language = Arc::new(Language::new(
8927 LanguageConfig {
8928 line_comments: vec!["// ".into()],
8929 ..Default::default()
8930 },
8931 Some(tree_sitter_rust::LANGUAGE.into()),
8932 ));
8933
8934 let mut cx = EditorTestContext::new(cx).await;
8935
8936 cx.language_registry().add(language.clone());
8937 cx.update_buffer(|buffer, cx| {
8938 buffer.set_language(Some(language), cx);
8939 });
8940
8941 let toggle_comments = &ToggleComments {
8942 advance_downwards: true,
8943 ignore_indent: false,
8944 };
8945
8946 // Single cursor on one line -> advance
8947 // Cursor moves horizontally 3 characters as well on non-blank line
8948 cx.set_state(indoc!(
8949 "fn a() {
8950 ˇdog();
8951 cat();
8952 }"
8953 ));
8954 cx.update_editor(|editor, cx| {
8955 editor.toggle_comments(toggle_comments, cx);
8956 });
8957 cx.assert_editor_state(indoc!(
8958 "fn a() {
8959 // dog();
8960 catˇ();
8961 }"
8962 ));
8963
8964 // Single selection on one line -> don't advance
8965 cx.set_state(indoc!(
8966 "fn a() {
8967 «dog()ˇ»;
8968 cat();
8969 }"
8970 ));
8971 cx.update_editor(|editor, cx| {
8972 editor.toggle_comments(toggle_comments, cx);
8973 });
8974 cx.assert_editor_state(indoc!(
8975 "fn a() {
8976 // «dog()ˇ»;
8977 cat();
8978 }"
8979 ));
8980
8981 // Multiple cursors on one line -> advance
8982 cx.set_state(indoc!(
8983 "fn a() {
8984 ˇdˇog();
8985 cat();
8986 }"
8987 ));
8988 cx.update_editor(|editor, cx| {
8989 editor.toggle_comments(toggle_comments, cx);
8990 });
8991 cx.assert_editor_state(indoc!(
8992 "fn a() {
8993 // dog();
8994 catˇ(ˇ);
8995 }"
8996 ));
8997
8998 // Multiple cursors on one line, with selection -> don't advance
8999 cx.set_state(indoc!(
9000 "fn a() {
9001 ˇdˇog«()ˇ»;
9002 cat();
9003 }"
9004 ));
9005 cx.update_editor(|editor, cx| {
9006 editor.toggle_comments(toggle_comments, cx);
9007 });
9008 cx.assert_editor_state(indoc!(
9009 "fn a() {
9010 // ˇdˇog«()ˇ»;
9011 cat();
9012 }"
9013 ));
9014
9015 // Single cursor on one line -> advance
9016 // Cursor moves to column 0 on blank line
9017 cx.set_state(indoc!(
9018 "fn a() {
9019 ˇdog();
9020
9021 cat();
9022 }"
9023 ));
9024 cx.update_editor(|editor, cx| {
9025 editor.toggle_comments(toggle_comments, cx);
9026 });
9027 cx.assert_editor_state(indoc!(
9028 "fn a() {
9029 // dog();
9030 ˇ
9031 cat();
9032 }"
9033 ));
9034
9035 // Single cursor on one line -> advance
9036 // Cursor starts and ends at column 0
9037 cx.set_state(indoc!(
9038 "fn a() {
9039 ˇ dog();
9040 cat();
9041 }"
9042 ));
9043 cx.update_editor(|editor, cx| {
9044 editor.toggle_comments(toggle_comments, cx);
9045 });
9046 cx.assert_editor_state(indoc!(
9047 "fn a() {
9048 // dog();
9049 ˇ cat();
9050 }"
9051 ));
9052}
9053
9054#[gpui::test]
9055async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
9056 init_test(cx, |_| {});
9057
9058 let mut cx = EditorTestContext::new(cx).await;
9059
9060 let html_language = Arc::new(
9061 Language::new(
9062 LanguageConfig {
9063 name: "HTML".into(),
9064 block_comment: Some(("<!-- ".into(), " -->".into())),
9065 ..Default::default()
9066 },
9067 Some(tree_sitter_html::language()),
9068 )
9069 .with_injection_query(
9070 r#"
9071 (script_element
9072 (raw_text) @content
9073 (#set! "language" "javascript"))
9074 "#,
9075 )
9076 .unwrap(),
9077 );
9078
9079 let javascript_language = Arc::new(Language::new(
9080 LanguageConfig {
9081 name: "JavaScript".into(),
9082 line_comments: vec!["// ".into()],
9083 ..Default::default()
9084 },
9085 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
9086 ));
9087
9088 cx.language_registry().add(html_language.clone());
9089 cx.language_registry().add(javascript_language.clone());
9090 cx.update_buffer(|buffer, cx| {
9091 buffer.set_language(Some(html_language), cx);
9092 });
9093
9094 // Toggle comments for empty selections
9095 cx.set_state(
9096 &r#"
9097 <p>A</p>ˇ
9098 <p>B</p>ˇ
9099 <p>C</p>ˇ
9100 "#
9101 .unindent(),
9102 );
9103 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9104 cx.assert_editor_state(
9105 &r#"
9106 <!-- <p>A</p>ˇ -->
9107 <!-- <p>B</p>ˇ -->
9108 <!-- <p>C</p>ˇ -->
9109 "#
9110 .unindent(),
9111 );
9112 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9113 cx.assert_editor_state(
9114 &r#"
9115 <p>A</p>ˇ
9116 <p>B</p>ˇ
9117 <p>C</p>ˇ
9118 "#
9119 .unindent(),
9120 );
9121
9122 // Toggle comments for mixture of empty and non-empty selections, where
9123 // multiple selections occupy a given line.
9124 cx.set_state(
9125 &r#"
9126 <p>A«</p>
9127 <p>ˇ»B</p>ˇ
9128 <p>C«</p>
9129 <p>ˇ»D</p>ˇ
9130 "#
9131 .unindent(),
9132 );
9133
9134 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9135 cx.assert_editor_state(
9136 &r#"
9137 <!-- <p>A«</p>
9138 <p>ˇ»B</p>ˇ -->
9139 <!-- <p>C«</p>
9140 <p>ˇ»D</p>ˇ -->
9141 "#
9142 .unindent(),
9143 );
9144 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9145 cx.assert_editor_state(
9146 &r#"
9147 <p>A«</p>
9148 <p>ˇ»B</p>ˇ
9149 <p>C«</p>
9150 <p>ˇ»D</p>ˇ
9151 "#
9152 .unindent(),
9153 );
9154
9155 // Toggle comments when different languages are active for different
9156 // selections.
9157 cx.set_state(
9158 &r#"
9159 ˇ<script>
9160 ˇvar x = new Y();
9161 ˇ</script>
9162 "#
9163 .unindent(),
9164 );
9165 cx.executor().run_until_parked();
9166 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9167 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
9168 // Uncommenting and commenting from this position brings in even more wrong artifacts.
9169 cx.assert_editor_state(
9170 &r#"
9171 <!-- ˇ<script> -->
9172 // ˇvar x = new Y();
9173 // ˇ</script>
9174 "#
9175 .unindent(),
9176 );
9177}
9178
9179#[gpui::test]
9180fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
9181 init_test(cx, |_| {});
9182
9183 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9184 let multibuffer = cx.new_model(|cx| {
9185 let mut multibuffer = MultiBuffer::new(ReadWrite);
9186 multibuffer.push_excerpts(
9187 buffer.clone(),
9188 [
9189 ExcerptRange {
9190 context: Point::new(0, 0)..Point::new(0, 4),
9191 primary: None,
9192 },
9193 ExcerptRange {
9194 context: Point::new(1, 0)..Point::new(1, 4),
9195 primary: None,
9196 },
9197 ],
9198 cx,
9199 );
9200 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
9201 multibuffer
9202 });
9203
9204 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
9205 view.update(cx, |view, cx| {
9206 assert_eq!(view.text(cx), "aaaa\nbbbb");
9207 view.change_selections(None, cx, |s| {
9208 s.select_ranges([
9209 Point::new(0, 0)..Point::new(0, 0),
9210 Point::new(1, 0)..Point::new(1, 0),
9211 ])
9212 });
9213
9214 view.handle_input("X", cx);
9215 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
9216 assert_eq!(
9217 view.selections.ranges(cx),
9218 [
9219 Point::new(0, 1)..Point::new(0, 1),
9220 Point::new(1, 1)..Point::new(1, 1),
9221 ]
9222 );
9223
9224 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
9225 view.change_selections(None, cx, |s| {
9226 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
9227 });
9228 view.backspace(&Default::default(), cx);
9229 assert_eq!(view.text(cx), "Xa\nbbb");
9230 assert_eq!(
9231 view.selections.ranges(cx),
9232 [Point::new(1, 0)..Point::new(1, 0)]
9233 );
9234
9235 view.change_selections(None, cx, |s| {
9236 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
9237 });
9238 view.backspace(&Default::default(), cx);
9239 assert_eq!(view.text(cx), "X\nbb");
9240 assert_eq!(
9241 view.selections.ranges(cx),
9242 [Point::new(0, 1)..Point::new(0, 1)]
9243 );
9244 });
9245}
9246
9247#[gpui::test]
9248fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
9249 init_test(cx, |_| {});
9250
9251 let markers = vec![('[', ']').into(), ('(', ')').into()];
9252 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
9253 indoc! {"
9254 [aaaa
9255 (bbbb]
9256 cccc)",
9257 },
9258 markers.clone(),
9259 );
9260 let excerpt_ranges = markers.into_iter().map(|marker| {
9261 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
9262 ExcerptRange {
9263 context,
9264 primary: None,
9265 }
9266 });
9267 let buffer = cx.new_model(|cx| Buffer::local(initial_text, cx));
9268 let multibuffer = cx.new_model(|cx| {
9269 let mut multibuffer = MultiBuffer::new(ReadWrite);
9270 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
9271 multibuffer
9272 });
9273
9274 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
9275 view.update(cx, |view, cx| {
9276 let (expected_text, selection_ranges) = marked_text_ranges(
9277 indoc! {"
9278 aaaa
9279 bˇbbb
9280 bˇbbˇb
9281 cccc"
9282 },
9283 true,
9284 );
9285 assert_eq!(view.text(cx), expected_text);
9286 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
9287
9288 view.handle_input("X", cx);
9289
9290 let (expected_text, expected_selections) = marked_text_ranges(
9291 indoc! {"
9292 aaaa
9293 bXˇbbXb
9294 bXˇbbXˇb
9295 cccc"
9296 },
9297 false,
9298 );
9299 assert_eq!(view.text(cx), expected_text);
9300 assert_eq!(view.selections.ranges(cx), expected_selections);
9301
9302 view.newline(&Newline, cx);
9303 let (expected_text, expected_selections) = marked_text_ranges(
9304 indoc! {"
9305 aaaa
9306 bX
9307 ˇbbX
9308 b
9309 bX
9310 ˇbbX
9311 ˇb
9312 cccc"
9313 },
9314 false,
9315 );
9316 assert_eq!(view.text(cx), expected_text);
9317 assert_eq!(view.selections.ranges(cx), expected_selections);
9318 });
9319}
9320
9321#[gpui::test]
9322fn test_refresh_selections(cx: &mut TestAppContext) {
9323 init_test(cx, |_| {});
9324
9325 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9326 let mut excerpt1_id = None;
9327 let multibuffer = cx.new_model(|cx| {
9328 let mut multibuffer = MultiBuffer::new(ReadWrite);
9329 excerpt1_id = multibuffer
9330 .push_excerpts(
9331 buffer.clone(),
9332 [
9333 ExcerptRange {
9334 context: Point::new(0, 0)..Point::new(1, 4),
9335 primary: None,
9336 },
9337 ExcerptRange {
9338 context: Point::new(1, 0)..Point::new(2, 4),
9339 primary: None,
9340 },
9341 ],
9342 cx,
9343 )
9344 .into_iter()
9345 .next();
9346 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
9347 multibuffer
9348 });
9349
9350 let editor = cx.add_window(|cx| {
9351 let mut editor = build_editor(multibuffer.clone(), cx);
9352 let snapshot = editor.snapshot(cx);
9353 editor.change_selections(None, cx, |s| {
9354 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
9355 });
9356 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
9357 assert_eq!(
9358 editor.selections.ranges(cx),
9359 [
9360 Point::new(1, 3)..Point::new(1, 3),
9361 Point::new(2, 1)..Point::new(2, 1),
9362 ]
9363 );
9364 editor
9365 });
9366
9367 // Refreshing selections is a no-op when excerpts haven't changed.
9368 _ = editor.update(cx, |editor, cx| {
9369 editor.change_selections(None, cx, |s| s.refresh());
9370 assert_eq!(
9371 editor.selections.ranges(cx),
9372 [
9373 Point::new(1, 3)..Point::new(1, 3),
9374 Point::new(2, 1)..Point::new(2, 1),
9375 ]
9376 );
9377 });
9378
9379 multibuffer.update(cx, |multibuffer, cx| {
9380 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
9381 });
9382 _ = editor.update(cx, |editor, cx| {
9383 // Removing an excerpt causes the first selection to become degenerate.
9384 assert_eq!(
9385 editor.selections.ranges(cx),
9386 [
9387 Point::new(0, 0)..Point::new(0, 0),
9388 Point::new(0, 1)..Point::new(0, 1)
9389 ]
9390 );
9391
9392 // Refreshing selections will relocate the first selection to the original buffer
9393 // location.
9394 editor.change_selections(None, cx, |s| s.refresh());
9395 assert_eq!(
9396 editor.selections.ranges(cx),
9397 [
9398 Point::new(0, 1)..Point::new(0, 1),
9399 Point::new(0, 3)..Point::new(0, 3)
9400 ]
9401 );
9402 assert!(editor.selections.pending_anchor().is_some());
9403 });
9404}
9405
9406#[gpui::test]
9407fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
9408 init_test(cx, |_| {});
9409
9410 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9411 let mut excerpt1_id = None;
9412 let multibuffer = cx.new_model(|cx| {
9413 let mut multibuffer = MultiBuffer::new(ReadWrite);
9414 excerpt1_id = multibuffer
9415 .push_excerpts(
9416 buffer.clone(),
9417 [
9418 ExcerptRange {
9419 context: Point::new(0, 0)..Point::new(1, 4),
9420 primary: None,
9421 },
9422 ExcerptRange {
9423 context: Point::new(1, 0)..Point::new(2, 4),
9424 primary: None,
9425 },
9426 ],
9427 cx,
9428 )
9429 .into_iter()
9430 .next();
9431 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
9432 multibuffer
9433 });
9434
9435 let editor = cx.add_window(|cx| {
9436 let mut editor = build_editor(multibuffer.clone(), cx);
9437 let snapshot = editor.snapshot(cx);
9438 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
9439 assert_eq!(
9440 editor.selections.ranges(cx),
9441 [Point::new(1, 3)..Point::new(1, 3)]
9442 );
9443 editor
9444 });
9445
9446 multibuffer.update(cx, |multibuffer, cx| {
9447 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
9448 });
9449 _ = editor.update(cx, |editor, cx| {
9450 assert_eq!(
9451 editor.selections.ranges(cx),
9452 [Point::new(0, 0)..Point::new(0, 0)]
9453 );
9454
9455 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
9456 editor.change_selections(None, cx, |s| s.refresh());
9457 assert_eq!(
9458 editor.selections.ranges(cx),
9459 [Point::new(0, 3)..Point::new(0, 3)]
9460 );
9461 assert!(editor.selections.pending_anchor().is_some());
9462 });
9463}
9464
9465#[gpui::test]
9466async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
9467 init_test(cx, |_| {});
9468
9469 let language = Arc::new(
9470 Language::new(
9471 LanguageConfig {
9472 brackets: BracketPairConfig {
9473 pairs: vec![
9474 BracketPair {
9475 start: "{".to_string(),
9476 end: "}".to_string(),
9477 close: true,
9478 surround: true,
9479 newline: true,
9480 },
9481 BracketPair {
9482 start: "/* ".to_string(),
9483 end: " */".to_string(),
9484 close: true,
9485 surround: true,
9486 newline: true,
9487 },
9488 ],
9489 ..Default::default()
9490 },
9491 ..Default::default()
9492 },
9493 Some(tree_sitter_rust::LANGUAGE.into()),
9494 )
9495 .with_indents_query("")
9496 .unwrap(),
9497 );
9498
9499 let text = concat!(
9500 "{ }\n", //
9501 " x\n", //
9502 " /* */\n", //
9503 "x\n", //
9504 "{{} }\n", //
9505 );
9506
9507 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
9508 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
9509 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
9510 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
9511 .await;
9512
9513 view.update(cx, |view, cx| {
9514 view.change_selections(None, cx, |s| {
9515 s.select_display_ranges([
9516 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
9517 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
9518 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
9519 ])
9520 });
9521 view.newline(&Newline, cx);
9522
9523 assert_eq!(
9524 view.buffer().read(cx).read(cx).text(),
9525 concat!(
9526 "{ \n", // Suppress rustfmt
9527 "\n", //
9528 "}\n", //
9529 " x\n", //
9530 " /* \n", //
9531 " \n", //
9532 " */\n", //
9533 "x\n", //
9534 "{{} \n", //
9535 "}\n", //
9536 )
9537 );
9538 });
9539}
9540
9541#[gpui::test]
9542fn test_highlighted_ranges(cx: &mut TestAppContext) {
9543 init_test(cx, |_| {});
9544
9545 let editor = cx.add_window(|cx| {
9546 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
9547 build_editor(buffer.clone(), cx)
9548 });
9549
9550 _ = editor.update(cx, |editor, cx| {
9551 struct Type1;
9552 struct Type2;
9553
9554 let buffer = editor.buffer.read(cx).snapshot(cx);
9555
9556 let anchor_range =
9557 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
9558
9559 editor.highlight_background::<Type1>(
9560 &[
9561 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
9562 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
9563 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
9564 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
9565 ],
9566 |_| Hsla::red(),
9567 cx,
9568 );
9569 editor.highlight_background::<Type2>(
9570 &[
9571 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
9572 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
9573 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
9574 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
9575 ],
9576 |_| Hsla::green(),
9577 cx,
9578 );
9579
9580 let snapshot = editor.snapshot(cx);
9581 let mut highlighted_ranges = editor.background_highlights_in_range(
9582 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
9583 &snapshot,
9584 cx.theme().colors(),
9585 );
9586 // Enforce a consistent ordering based on color without relying on the ordering of the
9587 // highlight's `TypeId` which is non-executor.
9588 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
9589 assert_eq!(
9590 highlighted_ranges,
9591 &[
9592 (
9593 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
9594 Hsla::red(),
9595 ),
9596 (
9597 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9598 Hsla::red(),
9599 ),
9600 (
9601 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
9602 Hsla::green(),
9603 ),
9604 (
9605 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
9606 Hsla::green(),
9607 ),
9608 ]
9609 );
9610 assert_eq!(
9611 editor.background_highlights_in_range(
9612 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
9613 &snapshot,
9614 cx.theme().colors(),
9615 ),
9616 &[(
9617 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9618 Hsla::red(),
9619 )]
9620 );
9621 });
9622}
9623
9624#[gpui::test]
9625async fn test_following(cx: &mut gpui::TestAppContext) {
9626 init_test(cx, |_| {});
9627
9628 let fs = FakeFs::new(cx.executor());
9629 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9630
9631 let buffer = project.update(cx, |project, cx| {
9632 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
9633 cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
9634 });
9635 let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
9636 let follower = cx.update(|cx| {
9637 cx.open_window(
9638 WindowOptions {
9639 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
9640 gpui::Point::new(px(0.), px(0.)),
9641 gpui::Point::new(px(10.), px(80.)),
9642 ))),
9643 ..Default::default()
9644 },
9645 |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
9646 )
9647 .unwrap()
9648 });
9649
9650 let is_still_following = Rc::new(RefCell::new(true));
9651 let follower_edit_event_count = Rc::new(RefCell::new(0));
9652 let pending_update = Rc::new(RefCell::new(None));
9653 _ = follower.update(cx, {
9654 let update = pending_update.clone();
9655 let is_still_following = is_still_following.clone();
9656 let follower_edit_event_count = follower_edit_event_count.clone();
9657 |_, cx| {
9658 cx.subscribe(
9659 &leader.root_view(cx).unwrap(),
9660 move |_, leader, event, cx| {
9661 leader
9662 .read(cx)
9663 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9664 },
9665 )
9666 .detach();
9667
9668 cx.subscribe(
9669 &follower.root_view(cx).unwrap(),
9670 move |_, _, event: &EditorEvent, _cx| {
9671 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
9672 *is_still_following.borrow_mut() = false;
9673 }
9674
9675 if let EditorEvent::BufferEdited = event {
9676 *follower_edit_event_count.borrow_mut() += 1;
9677 }
9678 },
9679 )
9680 .detach();
9681 }
9682 });
9683
9684 // Update the selections only
9685 _ = leader.update(cx, |leader, cx| {
9686 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9687 });
9688 follower
9689 .update(cx, |follower, cx| {
9690 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9691 })
9692 .unwrap()
9693 .await
9694 .unwrap();
9695 _ = follower.update(cx, |follower, cx| {
9696 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
9697 });
9698 assert!(*is_still_following.borrow());
9699 assert_eq!(*follower_edit_event_count.borrow(), 0);
9700
9701 // Update the scroll position only
9702 _ = leader.update(cx, |leader, cx| {
9703 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9704 });
9705 follower
9706 .update(cx, |follower, cx| {
9707 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9708 })
9709 .unwrap()
9710 .await
9711 .unwrap();
9712 assert_eq!(
9713 follower
9714 .update(cx, |follower, cx| follower.scroll_position(cx))
9715 .unwrap(),
9716 gpui::Point::new(1.5, 3.5)
9717 );
9718 assert!(*is_still_following.borrow());
9719 assert_eq!(*follower_edit_event_count.borrow(), 0);
9720
9721 // Update the selections and scroll position. The follower's scroll position is updated
9722 // via autoscroll, not via the leader's exact scroll position.
9723 _ = leader.update(cx, |leader, cx| {
9724 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
9725 leader.request_autoscroll(Autoscroll::newest(), cx);
9726 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9727 });
9728 follower
9729 .update(cx, |follower, cx| {
9730 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9731 })
9732 .unwrap()
9733 .await
9734 .unwrap();
9735 _ = follower.update(cx, |follower, cx| {
9736 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
9737 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
9738 });
9739 assert!(*is_still_following.borrow());
9740
9741 // Creating a pending selection that precedes another selection
9742 _ = leader.update(cx, |leader, cx| {
9743 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9744 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, cx);
9745 });
9746 follower
9747 .update(cx, |follower, cx| {
9748 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9749 })
9750 .unwrap()
9751 .await
9752 .unwrap();
9753 _ = follower.update(cx, |follower, cx| {
9754 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
9755 });
9756 assert!(*is_still_following.borrow());
9757
9758 // Extend the pending selection so that it surrounds another selection
9759 _ = leader.update(cx, |leader, cx| {
9760 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, cx);
9761 });
9762 follower
9763 .update(cx, |follower, cx| {
9764 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9765 })
9766 .unwrap()
9767 .await
9768 .unwrap();
9769 _ = follower.update(cx, |follower, cx| {
9770 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
9771 });
9772
9773 // Scrolling locally breaks the follow
9774 _ = follower.update(cx, |follower, cx| {
9775 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
9776 follower.set_scroll_anchor(
9777 ScrollAnchor {
9778 anchor: top_anchor,
9779 offset: gpui::Point::new(0.0, 0.5),
9780 },
9781 cx,
9782 );
9783 });
9784 assert!(!(*is_still_following.borrow()));
9785}
9786
9787#[gpui::test]
9788async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
9789 init_test(cx, |_| {});
9790
9791 let fs = FakeFs::new(cx.executor());
9792 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9793 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9794 let pane = workspace
9795 .update(cx, |workspace, _| workspace.active_pane().clone())
9796 .unwrap();
9797
9798 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
9799
9800 let leader = pane.update(cx, |_, cx| {
9801 let multibuffer = cx.new_model(|_| MultiBuffer::new(ReadWrite));
9802 cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
9803 });
9804
9805 // Start following the editor when it has no excerpts.
9806 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9807 let follower_1 = cx
9808 .update_window(*workspace.deref(), |_, cx| {
9809 Editor::from_state_proto(
9810 workspace.root_view(cx).unwrap(),
9811 ViewId {
9812 creator: Default::default(),
9813 id: 0,
9814 },
9815 &mut state_message,
9816 cx,
9817 )
9818 })
9819 .unwrap()
9820 .unwrap()
9821 .await
9822 .unwrap();
9823
9824 let update_message = Rc::new(RefCell::new(None));
9825 follower_1.update(cx, {
9826 let update = update_message.clone();
9827 |_, cx| {
9828 cx.subscribe(&leader, move |_, leader, event, cx| {
9829 leader
9830 .read(cx)
9831 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9832 })
9833 .detach();
9834 }
9835 });
9836
9837 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
9838 (
9839 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
9840 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
9841 )
9842 });
9843
9844 // Insert some excerpts.
9845 leader.update(cx, |leader, cx| {
9846 leader.buffer.update(cx, |multibuffer, cx| {
9847 let excerpt_ids = multibuffer.push_excerpts(
9848 buffer_1.clone(),
9849 [
9850 ExcerptRange {
9851 context: 1..6,
9852 primary: None,
9853 },
9854 ExcerptRange {
9855 context: 12..15,
9856 primary: None,
9857 },
9858 ExcerptRange {
9859 context: 0..3,
9860 primary: None,
9861 },
9862 ],
9863 cx,
9864 );
9865 multibuffer.insert_excerpts_after(
9866 excerpt_ids[0],
9867 buffer_2.clone(),
9868 [
9869 ExcerptRange {
9870 context: 8..12,
9871 primary: None,
9872 },
9873 ExcerptRange {
9874 context: 0..6,
9875 primary: None,
9876 },
9877 ],
9878 cx,
9879 );
9880 });
9881 });
9882
9883 // Apply the update of adding the excerpts.
9884 follower_1
9885 .update(cx, |follower, cx| {
9886 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9887 })
9888 .await
9889 .unwrap();
9890 assert_eq!(
9891 follower_1.update(cx, |editor, cx| editor.text(cx)),
9892 leader.update(cx, |editor, cx| editor.text(cx))
9893 );
9894 update_message.borrow_mut().take();
9895
9896 // Start following separately after it already has excerpts.
9897 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9898 let follower_2 = cx
9899 .update_window(*workspace.deref(), |_, cx| {
9900 Editor::from_state_proto(
9901 workspace.root_view(cx).unwrap().clone(),
9902 ViewId {
9903 creator: Default::default(),
9904 id: 0,
9905 },
9906 &mut state_message,
9907 cx,
9908 )
9909 })
9910 .unwrap()
9911 .unwrap()
9912 .await
9913 .unwrap();
9914 assert_eq!(
9915 follower_2.update(cx, |editor, cx| editor.text(cx)),
9916 leader.update(cx, |editor, cx| editor.text(cx))
9917 );
9918
9919 // Remove some excerpts.
9920 leader.update(cx, |leader, cx| {
9921 leader.buffer.update(cx, |multibuffer, cx| {
9922 let excerpt_ids = multibuffer.excerpt_ids();
9923 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
9924 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
9925 });
9926 });
9927
9928 // Apply the update of removing the excerpts.
9929 follower_1
9930 .update(cx, |follower, cx| {
9931 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9932 })
9933 .await
9934 .unwrap();
9935 follower_2
9936 .update(cx, |follower, cx| {
9937 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9938 })
9939 .await
9940 .unwrap();
9941 update_message.borrow_mut().take();
9942 assert_eq!(
9943 follower_1.update(cx, |editor, cx| editor.text(cx)),
9944 leader.update(cx, |editor, cx| editor.text(cx))
9945 );
9946}
9947
9948#[gpui::test]
9949async fn go_to_prev_overlapping_diagnostic(
9950 executor: BackgroundExecutor,
9951 cx: &mut gpui::TestAppContext,
9952) {
9953 init_test(cx, |_| {});
9954
9955 let mut cx = EditorTestContext::new(cx).await;
9956 let lsp_store =
9957 cx.update_editor(|editor, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
9958
9959 cx.set_state(indoc! {"
9960 ˇfn func(abc def: i32) -> u32 {
9961 }
9962 "});
9963
9964 cx.update(|cx| {
9965 lsp_store.update(cx, |lsp_store, cx| {
9966 lsp_store
9967 .update_diagnostics(
9968 LanguageServerId(0),
9969 lsp::PublishDiagnosticsParams {
9970 uri: lsp::Url::from_file_path("/root/file").unwrap(),
9971 version: None,
9972 diagnostics: vec![
9973 lsp::Diagnostic {
9974 range: lsp::Range::new(
9975 lsp::Position::new(0, 11),
9976 lsp::Position::new(0, 12),
9977 ),
9978 severity: Some(lsp::DiagnosticSeverity::ERROR),
9979 ..Default::default()
9980 },
9981 lsp::Diagnostic {
9982 range: lsp::Range::new(
9983 lsp::Position::new(0, 12),
9984 lsp::Position::new(0, 15),
9985 ),
9986 severity: Some(lsp::DiagnosticSeverity::ERROR),
9987 ..Default::default()
9988 },
9989 lsp::Diagnostic {
9990 range: lsp::Range::new(
9991 lsp::Position::new(0, 25),
9992 lsp::Position::new(0, 28),
9993 ),
9994 severity: Some(lsp::DiagnosticSeverity::ERROR),
9995 ..Default::default()
9996 },
9997 ],
9998 },
9999 &[],
10000 cx,
10001 )
10002 .unwrap()
10003 });
10004 });
10005
10006 executor.run_until_parked();
10007
10008 cx.update_editor(|editor, cx| {
10009 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
10010 });
10011
10012 cx.assert_editor_state(indoc! {"
10013 fn func(abc def: i32) -> ˇu32 {
10014 }
10015 "});
10016
10017 cx.update_editor(|editor, cx| {
10018 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
10019 });
10020
10021 cx.assert_editor_state(indoc! {"
10022 fn func(abc ˇdef: i32) -> u32 {
10023 }
10024 "});
10025
10026 cx.update_editor(|editor, cx| {
10027 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
10028 });
10029
10030 cx.assert_editor_state(indoc! {"
10031 fn func(abcˇ def: i32) -> u32 {
10032 }
10033 "});
10034
10035 cx.update_editor(|editor, cx| {
10036 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
10037 });
10038
10039 cx.assert_editor_state(indoc! {"
10040 fn func(abc def: i32) -> ˇu32 {
10041 }
10042 "});
10043}
10044
10045#[gpui::test]
10046async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
10047 init_test(cx, |_| {});
10048
10049 let mut cx = EditorTestContext::new(cx).await;
10050
10051 cx.set_state(indoc! {"
10052 fn func(abˇc def: i32) -> u32 {
10053 }
10054 "});
10055 let lsp_store =
10056 cx.update_editor(|editor, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
10057
10058 cx.update(|cx| {
10059 lsp_store.update(cx, |lsp_store, cx| {
10060 lsp_store.update_diagnostics(
10061 LanguageServerId(0),
10062 lsp::PublishDiagnosticsParams {
10063 uri: lsp::Url::from_file_path("/root/file").unwrap(),
10064 version: None,
10065 diagnostics: vec![lsp::Diagnostic {
10066 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
10067 severity: Some(lsp::DiagnosticSeverity::ERROR),
10068 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
10069 ..Default::default()
10070 }],
10071 },
10072 &[],
10073 cx,
10074 )
10075 })
10076 }).unwrap();
10077 cx.run_until_parked();
10078 cx.update_editor(|editor, cx| hover_popover::hover(editor, &Default::default(), cx));
10079 cx.run_until_parked();
10080 cx.update_editor(|editor, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
10081}
10082
10083#[gpui::test]
10084async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
10085 init_test(cx, |_| {});
10086
10087 let mut cx = EditorTestContext::new(cx).await;
10088
10089 let diff_base = r#"
10090 use some::mod;
10091
10092 const A: u32 = 42;
10093
10094 fn main() {
10095 println!("hello");
10096
10097 println!("world");
10098 }
10099 "#
10100 .unindent();
10101
10102 // Edits are modified, removed, modified, added
10103 cx.set_state(
10104 &r#"
10105 use some::modified;
10106
10107 ˇ
10108 fn main() {
10109 println!("hello there");
10110
10111 println!("around the");
10112 println!("world");
10113 }
10114 "#
10115 .unindent(),
10116 );
10117
10118 cx.set_diff_base(&diff_base);
10119 executor.run_until_parked();
10120
10121 cx.update_editor(|editor, cx| {
10122 //Wrap around the bottom of the buffer
10123 for _ in 0..3 {
10124 editor.go_to_next_hunk(&GoToHunk, cx);
10125 }
10126 });
10127
10128 cx.assert_editor_state(
10129 &r#"
10130 ˇuse some::modified;
10131
10132
10133 fn main() {
10134 println!("hello there");
10135
10136 println!("around the");
10137 println!("world");
10138 }
10139 "#
10140 .unindent(),
10141 );
10142
10143 cx.update_editor(|editor, cx| {
10144 //Wrap around the top of the buffer
10145 for _ in 0..2 {
10146 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10147 }
10148 });
10149
10150 cx.assert_editor_state(
10151 &r#"
10152 use some::modified;
10153
10154
10155 fn main() {
10156 ˇ println!("hello there");
10157
10158 println!("around the");
10159 println!("world");
10160 }
10161 "#
10162 .unindent(),
10163 );
10164
10165 cx.update_editor(|editor, cx| {
10166 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10167 });
10168
10169 cx.assert_editor_state(
10170 &r#"
10171 use some::modified;
10172
10173 ˇ
10174 fn main() {
10175 println!("hello there");
10176
10177 println!("around the");
10178 println!("world");
10179 }
10180 "#
10181 .unindent(),
10182 );
10183
10184 cx.update_editor(|editor, cx| {
10185 for _ in 0..3 {
10186 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10187 }
10188 });
10189
10190 cx.assert_editor_state(
10191 &r#"
10192 use some::modified;
10193
10194
10195 fn main() {
10196 ˇ println!("hello there");
10197
10198 println!("around the");
10199 println!("world");
10200 }
10201 "#
10202 .unindent(),
10203 );
10204
10205 cx.update_editor(|editor, cx| {
10206 editor.fold(&Fold, cx);
10207
10208 //Make sure that the fold only gets one hunk
10209 for _ in 0..4 {
10210 editor.go_to_next_hunk(&GoToHunk, cx);
10211 }
10212 });
10213
10214 cx.assert_editor_state(
10215 &r#"
10216 ˇuse some::modified;
10217
10218
10219 fn main() {
10220 println!("hello there");
10221
10222 println!("around the");
10223 println!("world");
10224 }
10225 "#
10226 .unindent(),
10227 );
10228}
10229
10230#[test]
10231fn test_split_words() {
10232 fn split(text: &str) -> Vec<&str> {
10233 split_words(text).collect()
10234 }
10235
10236 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
10237 assert_eq!(split("hello_world"), &["hello_", "world"]);
10238 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
10239 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
10240 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
10241 assert_eq!(split("helloworld"), &["helloworld"]);
10242
10243 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
10244}
10245
10246#[gpui::test]
10247async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
10248 init_test(cx, |_| {});
10249
10250 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
10251 let mut assert = |before, after| {
10252 let _state_context = cx.set_state(before);
10253 cx.update_editor(|editor, cx| {
10254 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
10255 });
10256 cx.assert_editor_state(after);
10257 };
10258
10259 // Outside bracket jumps to outside of matching bracket
10260 assert("console.logˇ(var);", "console.log(var)ˇ;");
10261 assert("console.log(var)ˇ;", "console.logˇ(var);");
10262
10263 // Inside bracket jumps to inside of matching bracket
10264 assert("console.log(ˇvar);", "console.log(varˇ);");
10265 assert("console.log(varˇ);", "console.log(ˇvar);");
10266
10267 // When outside a bracket and inside, favor jumping to the inside bracket
10268 assert(
10269 "console.log('foo', [1, 2, 3]ˇ);",
10270 "console.log(ˇ'foo', [1, 2, 3]);",
10271 );
10272 assert(
10273 "console.log(ˇ'foo', [1, 2, 3]);",
10274 "console.log('foo', [1, 2, 3]ˇ);",
10275 );
10276
10277 // Bias forward if two options are equally likely
10278 assert(
10279 "let result = curried_fun()ˇ();",
10280 "let result = curried_fun()()ˇ;",
10281 );
10282
10283 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
10284 assert(
10285 indoc! {"
10286 function test() {
10287 console.log('test')ˇ
10288 }"},
10289 indoc! {"
10290 function test() {
10291 console.logˇ('test')
10292 }"},
10293 );
10294}
10295
10296#[gpui::test]
10297async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
10298 init_test(cx, |_| {});
10299
10300 let fs = FakeFs::new(cx.executor());
10301 fs.insert_tree(
10302 "/a",
10303 json!({
10304 "main.rs": "fn main() { let a = 5; }",
10305 "other.rs": "// Test file",
10306 }),
10307 )
10308 .await;
10309 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10310
10311 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10312 language_registry.add(Arc::new(Language::new(
10313 LanguageConfig {
10314 name: "Rust".into(),
10315 matcher: LanguageMatcher {
10316 path_suffixes: vec!["rs".to_string()],
10317 ..Default::default()
10318 },
10319 brackets: BracketPairConfig {
10320 pairs: vec![BracketPair {
10321 start: "{".to_string(),
10322 end: "}".to_string(),
10323 close: true,
10324 surround: true,
10325 newline: true,
10326 }],
10327 disabled_scopes_by_bracket_ix: Vec::new(),
10328 },
10329 ..Default::default()
10330 },
10331 Some(tree_sitter_rust::LANGUAGE.into()),
10332 )));
10333 let mut fake_servers = language_registry.register_fake_lsp(
10334 "Rust",
10335 FakeLspAdapter {
10336 capabilities: lsp::ServerCapabilities {
10337 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
10338 first_trigger_character: "{".to_string(),
10339 more_trigger_character: None,
10340 }),
10341 ..Default::default()
10342 },
10343 ..Default::default()
10344 },
10345 );
10346
10347 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10348
10349 let cx = &mut VisualTestContext::from_window(*workspace, cx);
10350
10351 let worktree_id = workspace
10352 .update(cx, |workspace, cx| {
10353 workspace.project().update(cx, |project, cx| {
10354 project.worktrees(cx).next().unwrap().read(cx).id()
10355 })
10356 })
10357 .unwrap();
10358
10359 let buffer = project
10360 .update(cx, |project, cx| {
10361 project.open_local_buffer("/a/main.rs", cx)
10362 })
10363 .await
10364 .unwrap();
10365 let editor_handle = workspace
10366 .update(cx, |workspace, cx| {
10367 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
10368 })
10369 .unwrap()
10370 .await
10371 .unwrap()
10372 .downcast::<Editor>()
10373 .unwrap();
10374
10375 cx.executor().start_waiting();
10376 let fake_server = fake_servers.next().await.unwrap();
10377
10378 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
10379 assert_eq!(
10380 params.text_document_position.text_document.uri,
10381 lsp::Url::from_file_path("/a/main.rs").unwrap(),
10382 );
10383 assert_eq!(
10384 params.text_document_position.position,
10385 lsp::Position::new(0, 21),
10386 );
10387
10388 Ok(Some(vec![lsp::TextEdit {
10389 new_text: "]".to_string(),
10390 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10391 }]))
10392 });
10393
10394 editor_handle.update(cx, |editor, cx| {
10395 editor.focus(cx);
10396 editor.change_selections(None, cx, |s| {
10397 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
10398 });
10399 editor.handle_input("{", cx);
10400 });
10401
10402 cx.executor().run_until_parked();
10403
10404 buffer.update(cx, |buffer, _| {
10405 assert_eq!(
10406 buffer.text(),
10407 "fn main() { let a = {5}; }",
10408 "No extra braces from on type formatting should appear in the buffer"
10409 )
10410 });
10411}
10412
10413#[gpui::test]
10414async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
10415 init_test(cx, |_| {});
10416
10417 let fs = FakeFs::new(cx.executor());
10418 fs.insert_tree(
10419 "/a",
10420 json!({
10421 "main.rs": "fn main() { let a = 5; }",
10422 "other.rs": "// Test file",
10423 }),
10424 )
10425 .await;
10426
10427 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10428
10429 let server_restarts = Arc::new(AtomicUsize::new(0));
10430 let closure_restarts = Arc::clone(&server_restarts);
10431 let language_server_name = "test language server";
10432 let language_name: LanguageName = "Rust".into();
10433
10434 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10435 language_registry.add(Arc::new(Language::new(
10436 LanguageConfig {
10437 name: language_name.clone(),
10438 matcher: LanguageMatcher {
10439 path_suffixes: vec!["rs".to_string()],
10440 ..Default::default()
10441 },
10442 ..Default::default()
10443 },
10444 Some(tree_sitter_rust::LANGUAGE.into()),
10445 )));
10446 let mut fake_servers = language_registry.register_fake_lsp(
10447 "Rust",
10448 FakeLspAdapter {
10449 name: language_server_name,
10450 initialization_options: Some(json!({
10451 "testOptionValue": true
10452 })),
10453 initializer: Some(Box::new(move |fake_server| {
10454 let task_restarts = Arc::clone(&closure_restarts);
10455 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
10456 task_restarts.fetch_add(1, atomic::Ordering::Release);
10457 futures::future::ready(Ok(()))
10458 });
10459 })),
10460 ..Default::default()
10461 },
10462 );
10463
10464 let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10465 let _buffer = project
10466 .update(cx, |project, cx| {
10467 project.open_local_buffer_with_lsp("/a/main.rs", cx)
10468 })
10469 .await
10470 .unwrap();
10471 let _fake_server = fake_servers.next().await.unwrap();
10472 update_test_language_settings(cx, |language_settings| {
10473 language_settings.languages.insert(
10474 language_name.clone(),
10475 LanguageSettingsContent {
10476 tab_size: NonZeroU32::new(8),
10477 ..Default::default()
10478 },
10479 );
10480 });
10481 cx.executor().run_until_parked();
10482 assert_eq!(
10483 server_restarts.load(atomic::Ordering::Acquire),
10484 0,
10485 "Should not restart LSP server on an unrelated change"
10486 );
10487
10488 update_test_project_settings(cx, |project_settings| {
10489 project_settings.lsp.insert(
10490 "Some other server name".into(),
10491 LspSettings {
10492 binary: None,
10493 settings: None,
10494 initialization_options: Some(json!({
10495 "some other init value": false
10496 })),
10497 },
10498 );
10499 });
10500 cx.executor().run_until_parked();
10501 assert_eq!(
10502 server_restarts.load(atomic::Ordering::Acquire),
10503 0,
10504 "Should not restart LSP server on an unrelated LSP settings change"
10505 );
10506
10507 update_test_project_settings(cx, |project_settings| {
10508 project_settings.lsp.insert(
10509 language_server_name.into(),
10510 LspSettings {
10511 binary: None,
10512 settings: None,
10513 initialization_options: Some(json!({
10514 "anotherInitValue": false
10515 })),
10516 },
10517 );
10518 });
10519 cx.executor().run_until_parked();
10520 assert_eq!(
10521 server_restarts.load(atomic::Ordering::Acquire),
10522 1,
10523 "Should restart LSP server on a related LSP settings change"
10524 );
10525
10526 update_test_project_settings(cx, |project_settings| {
10527 project_settings.lsp.insert(
10528 language_server_name.into(),
10529 LspSettings {
10530 binary: None,
10531 settings: None,
10532 initialization_options: Some(json!({
10533 "anotherInitValue": false
10534 })),
10535 },
10536 );
10537 });
10538 cx.executor().run_until_parked();
10539 assert_eq!(
10540 server_restarts.load(atomic::Ordering::Acquire),
10541 1,
10542 "Should not restart LSP server on a related LSP settings change that is the same"
10543 );
10544
10545 update_test_project_settings(cx, |project_settings| {
10546 project_settings.lsp.insert(
10547 language_server_name.into(),
10548 LspSettings {
10549 binary: None,
10550 settings: None,
10551 initialization_options: None,
10552 },
10553 );
10554 });
10555 cx.executor().run_until_parked();
10556 assert_eq!(
10557 server_restarts.load(atomic::Ordering::Acquire),
10558 2,
10559 "Should restart LSP server on another related LSP settings change"
10560 );
10561}
10562
10563#[gpui::test]
10564async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
10565 init_test(cx, |_| {});
10566
10567 let mut cx = EditorLspTestContext::new_rust(
10568 lsp::ServerCapabilities {
10569 completion_provider: Some(lsp::CompletionOptions {
10570 trigger_characters: Some(vec![".".to_string()]),
10571 resolve_provider: Some(true),
10572 ..Default::default()
10573 }),
10574 ..Default::default()
10575 },
10576 cx,
10577 )
10578 .await;
10579
10580 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10581 cx.simulate_keystroke(".");
10582 let completion_item = lsp::CompletionItem {
10583 label: "some".into(),
10584 kind: Some(lsp::CompletionItemKind::SNIPPET),
10585 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
10586 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
10587 kind: lsp::MarkupKind::Markdown,
10588 value: "```rust\nSome(2)\n```".to_string(),
10589 })),
10590 deprecated: Some(false),
10591 sort_text: Some("fffffff2".to_string()),
10592 filter_text: Some("some".to_string()),
10593 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
10594 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10595 range: lsp::Range {
10596 start: lsp::Position {
10597 line: 0,
10598 character: 22,
10599 },
10600 end: lsp::Position {
10601 line: 0,
10602 character: 22,
10603 },
10604 },
10605 new_text: "Some(2)".to_string(),
10606 })),
10607 additional_text_edits: Some(vec![lsp::TextEdit {
10608 range: lsp::Range {
10609 start: lsp::Position {
10610 line: 0,
10611 character: 20,
10612 },
10613 end: lsp::Position {
10614 line: 0,
10615 character: 22,
10616 },
10617 },
10618 new_text: "".to_string(),
10619 }]),
10620 ..Default::default()
10621 };
10622
10623 let closure_completion_item = completion_item.clone();
10624 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10625 let task_completion_item = closure_completion_item.clone();
10626 async move {
10627 Ok(Some(lsp::CompletionResponse::Array(vec![
10628 task_completion_item,
10629 ])))
10630 }
10631 });
10632
10633 request.next().await;
10634
10635 cx.condition(|editor, _| editor.context_menu_visible())
10636 .await;
10637 let apply_additional_edits = cx.update_editor(|editor, cx| {
10638 editor
10639 .confirm_completion(&ConfirmCompletion::default(), cx)
10640 .unwrap()
10641 });
10642 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
10643
10644 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
10645 let task_completion_item = completion_item.clone();
10646 async move { Ok(task_completion_item) }
10647 })
10648 .next()
10649 .await
10650 .unwrap();
10651 apply_additional_edits.await.unwrap();
10652 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
10653}
10654
10655#[gpui::test]
10656async fn test_completions_resolve_updates_labels_if_filter_text_matches(
10657 cx: &mut gpui::TestAppContext,
10658) {
10659 init_test(cx, |_| {});
10660
10661 let mut cx = EditorLspTestContext::new_rust(
10662 lsp::ServerCapabilities {
10663 completion_provider: Some(lsp::CompletionOptions {
10664 trigger_characters: Some(vec![".".to_string()]),
10665 resolve_provider: Some(true),
10666 ..Default::default()
10667 }),
10668 ..Default::default()
10669 },
10670 cx,
10671 )
10672 .await;
10673
10674 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10675 cx.simulate_keystroke(".");
10676
10677 let item1 = lsp::CompletionItem {
10678 label: "id".to_string(),
10679 filter_text: Some("id".to_string()),
10680 detail: None,
10681 documentation: None,
10682 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10683 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10684 new_text: ".id".to_string(),
10685 })),
10686 ..lsp::CompletionItem::default()
10687 };
10688
10689 let item2 = lsp::CompletionItem {
10690 label: "other".to_string(),
10691 filter_text: Some("other".to_string()),
10692 detail: None,
10693 documentation: None,
10694 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10695 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10696 new_text: ".other".to_string(),
10697 })),
10698 ..lsp::CompletionItem::default()
10699 };
10700
10701 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10702 let item1 = item1.clone();
10703 let item2 = item2.clone();
10704 async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
10705 })
10706 .next()
10707 .await;
10708
10709 cx.condition(|editor, _| editor.context_menu_visible())
10710 .await;
10711 cx.update_editor(|editor, _| {
10712 let context_menu = editor.context_menu.borrow_mut();
10713 let context_menu = context_menu
10714 .as_ref()
10715 .expect("Should have the context menu deployed");
10716 match context_menu {
10717 CodeContextMenu::Completions(completions_menu) => {
10718 let completions = completions_menu.completions.borrow_mut();
10719 assert_eq!(
10720 completions
10721 .iter()
10722 .map(|completion| &completion.label.text)
10723 .collect::<Vec<_>>(),
10724 vec!["id", "other"]
10725 )
10726 }
10727 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
10728 }
10729 });
10730
10731 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| async move {
10732 Ok(lsp::CompletionItem {
10733 label: "method id()".to_string(),
10734 filter_text: Some("id".to_string()),
10735 detail: Some("Now resolved!".to_string()),
10736 documentation: Some(lsp::Documentation::String("Docs".to_string())),
10737 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10738 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10739 new_text: ".id".to_string(),
10740 })),
10741 ..lsp::CompletionItem::default()
10742 })
10743 })
10744 .next()
10745 .await;
10746 cx.run_until_parked();
10747
10748 cx.update_editor(|editor, cx| {
10749 editor.context_menu_next(&Default::default(), cx);
10750 });
10751
10752 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| async move {
10753 Ok(lsp::CompletionItem {
10754 label: "invalid changed label".to_string(),
10755 detail: Some("Now resolved!".to_string()),
10756 documentation: Some(lsp::Documentation::String("Docs".to_string())),
10757 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10758 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10759 new_text: ".id".to_string(),
10760 })),
10761 ..lsp::CompletionItem::default()
10762 })
10763 })
10764 .next()
10765 .await;
10766 cx.run_until_parked();
10767
10768 cx.update_editor(|editor, _| {
10769 let context_menu = editor.context_menu.borrow_mut();
10770 let context_menu = context_menu
10771 .as_ref()
10772 .expect("Should have the context menu deployed");
10773 match context_menu {
10774 CodeContextMenu::Completions(completions_menu) => {
10775 let completions = completions_menu.completions.borrow_mut();
10776 assert_eq!(
10777 completions
10778 .iter()
10779 .map(|completion| &completion.label.text)
10780 .collect::<Vec<_>>(),
10781 vec!["method id()", "other"],
10782 "Should update first completion label, but not second as the filter text did not match."
10783 );
10784 }
10785 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
10786 }
10787 });
10788}
10789
10790#[gpui::test]
10791async fn test_completions_default_resolve_data_handling(cx: &mut gpui::TestAppContext) {
10792 init_test(cx, |_| {});
10793
10794 let item_0 = lsp::CompletionItem {
10795 label: "abs".into(),
10796 insert_text: Some("abs".into()),
10797 data: Some(json!({ "very": "special"})),
10798 insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
10799 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
10800 lsp::InsertReplaceEdit {
10801 new_text: "abs".to_string(),
10802 insert: lsp::Range::default(),
10803 replace: lsp::Range::default(),
10804 },
10805 )),
10806 ..lsp::CompletionItem::default()
10807 };
10808 let items = iter::once(item_0.clone())
10809 .chain((11..51).map(|i| lsp::CompletionItem {
10810 label: format!("item_{}", i),
10811 insert_text: Some(format!("item_{}", i)),
10812 insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
10813 ..lsp::CompletionItem::default()
10814 }))
10815 .collect::<Vec<_>>();
10816
10817 let default_commit_characters = vec!["?".to_string()];
10818 let default_data = json!({ "default": "data"});
10819 let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
10820 let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
10821 let default_edit_range = lsp::Range {
10822 start: lsp::Position {
10823 line: 0,
10824 character: 5,
10825 },
10826 end: lsp::Position {
10827 line: 0,
10828 character: 5,
10829 },
10830 };
10831
10832 let item_0_out = lsp::CompletionItem {
10833 commit_characters: Some(default_commit_characters.clone()),
10834 insert_text_format: Some(default_insert_text_format),
10835 ..item_0
10836 };
10837 let items_out = iter::once(item_0_out)
10838 .chain(items[1..].iter().map(|item| lsp::CompletionItem {
10839 commit_characters: Some(default_commit_characters.clone()),
10840 data: Some(default_data.clone()),
10841 insert_text_mode: Some(default_insert_text_mode),
10842 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10843 range: default_edit_range,
10844 new_text: item.label.clone(),
10845 })),
10846 ..item.clone()
10847 }))
10848 .collect::<Vec<lsp::CompletionItem>>();
10849
10850 let mut cx = EditorLspTestContext::new_rust(
10851 lsp::ServerCapabilities {
10852 completion_provider: Some(lsp::CompletionOptions {
10853 trigger_characters: Some(vec![".".to_string()]),
10854 resolve_provider: Some(true),
10855 ..Default::default()
10856 }),
10857 ..Default::default()
10858 },
10859 cx,
10860 )
10861 .await;
10862
10863 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10864 cx.simulate_keystroke(".");
10865
10866 let completion_data = default_data.clone();
10867 let completion_characters = default_commit_characters.clone();
10868 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10869 let default_data = completion_data.clone();
10870 let default_commit_characters = completion_characters.clone();
10871 let items = items.clone();
10872 async move {
10873 Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
10874 items,
10875 item_defaults: Some(lsp::CompletionListItemDefaults {
10876 data: Some(default_data.clone()),
10877 commit_characters: Some(default_commit_characters.clone()),
10878 edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
10879 default_edit_range,
10880 )),
10881 insert_text_format: Some(default_insert_text_format),
10882 insert_text_mode: Some(default_insert_text_mode),
10883 }),
10884 ..lsp::CompletionList::default()
10885 })))
10886 }
10887 })
10888 .next()
10889 .await;
10890
10891 let resolved_items = Arc::new(Mutex::new(Vec::new()));
10892 cx.lsp
10893 .server
10894 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
10895 let closure_resolved_items = resolved_items.clone();
10896 move |item_to_resolve, _| {
10897 let closure_resolved_items = closure_resolved_items.clone();
10898 async move {
10899 closure_resolved_items.lock().push(item_to_resolve.clone());
10900 Ok(item_to_resolve)
10901 }
10902 }
10903 })
10904 .detach();
10905
10906 cx.condition(|editor, _| editor.context_menu_visible())
10907 .await;
10908 cx.run_until_parked();
10909 cx.update_editor(|editor, _| {
10910 let menu = editor.context_menu.borrow_mut();
10911 match menu.as_ref().expect("should have the completions menu") {
10912 CodeContextMenu::Completions(completions_menu) => {
10913 assert_eq!(
10914 completions_menu
10915 .entries
10916 .iter()
10917 .flat_map(|c| match c {
10918 CompletionEntry::Match(mat) => Some(mat.string.clone()),
10919 _ => None,
10920 })
10921 .collect::<Vec<String>>(),
10922 items_out
10923 .iter()
10924 .map(|completion| completion.label.clone())
10925 .collect::<Vec<String>>()
10926 );
10927 }
10928 CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
10929 }
10930 });
10931 // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
10932 // with 4 from the end.
10933 assert_eq!(
10934 *resolved_items.lock(),
10935 [
10936 &items_out[0..16],
10937 &items_out[items_out.len() - 4..items_out.len()]
10938 ]
10939 .concat()
10940 .iter()
10941 .cloned()
10942 .collect::<Vec<lsp::CompletionItem>>()
10943 );
10944 resolved_items.lock().clear();
10945
10946 cx.update_editor(|editor, cx| {
10947 editor.context_menu_prev(&ContextMenuPrev, cx);
10948 });
10949 cx.run_until_parked();
10950 // Completions that have already been resolved are skipped.
10951 assert_eq!(
10952 *resolved_items.lock(),
10953 [
10954 // Selected item is always resolved even if it was resolved before.
10955 &items_out[items_out.len() - 1..items_out.len()],
10956 &items_out[items_out.len() - 16..items_out.len() - 4]
10957 ]
10958 .concat()
10959 .iter()
10960 .cloned()
10961 .collect::<Vec<lsp::CompletionItem>>()
10962 );
10963 resolved_items.lock().clear();
10964}
10965
10966#[gpui::test]
10967async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
10968 init_test(cx, |_| {});
10969
10970 let mut cx = EditorLspTestContext::new(
10971 Language::new(
10972 LanguageConfig {
10973 matcher: LanguageMatcher {
10974 path_suffixes: vec!["jsx".into()],
10975 ..Default::default()
10976 },
10977 overrides: [(
10978 "element".into(),
10979 LanguageConfigOverride {
10980 word_characters: Override::Set(['-'].into_iter().collect()),
10981 ..Default::default()
10982 },
10983 )]
10984 .into_iter()
10985 .collect(),
10986 ..Default::default()
10987 },
10988 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10989 )
10990 .with_override_query("(jsx_self_closing_element) @element")
10991 .unwrap(),
10992 lsp::ServerCapabilities {
10993 completion_provider: Some(lsp::CompletionOptions {
10994 trigger_characters: Some(vec![":".to_string()]),
10995 ..Default::default()
10996 }),
10997 ..Default::default()
10998 },
10999 cx,
11000 )
11001 .await;
11002
11003 cx.lsp
11004 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
11005 Ok(Some(lsp::CompletionResponse::Array(vec![
11006 lsp::CompletionItem {
11007 label: "bg-blue".into(),
11008 ..Default::default()
11009 },
11010 lsp::CompletionItem {
11011 label: "bg-red".into(),
11012 ..Default::default()
11013 },
11014 lsp::CompletionItem {
11015 label: "bg-yellow".into(),
11016 ..Default::default()
11017 },
11018 ])))
11019 });
11020
11021 cx.set_state(r#"<p class="bgˇ" />"#);
11022
11023 // Trigger completion when typing a dash, because the dash is an extra
11024 // word character in the 'element' scope, which contains the cursor.
11025 cx.simulate_keystroke("-");
11026 cx.executor().run_until_parked();
11027 cx.update_editor(|editor, _| {
11028 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11029 {
11030 assert_eq!(
11031 completion_menu_entries(&menu.entries),
11032 &["bg-red", "bg-blue", "bg-yellow"]
11033 );
11034 } else {
11035 panic!("expected completion menu to be open");
11036 }
11037 });
11038
11039 cx.simulate_keystroke("l");
11040 cx.executor().run_until_parked();
11041 cx.update_editor(|editor, _| {
11042 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11043 {
11044 assert_eq!(
11045 completion_menu_entries(&menu.entries),
11046 &["bg-blue", "bg-yellow"]
11047 );
11048 } else {
11049 panic!("expected completion menu to be open");
11050 }
11051 });
11052
11053 // When filtering completions, consider the character after the '-' to
11054 // be the start of a subword.
11055 cx.set_state(r#"<p class="yelˇ" />"#);
11056 cx.simulate_keystroke("l");
11057 cx.executor().run_until_parked();
11058 cx.update_editor(|editor, _| {
11059 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11060 {
11061 assert_eq!(completion_menu_entries(&menu.entries), &["bg-yellow"]);
11062 } else {
11063 panic!("expected completion menu to be open");
11064 }
11065 });
11066}
11067
11068fn completion_menu_entries(entries: &[CompletionEntry]) -> Vec<&str> {
11069 entries
11070 .iter()
11071 .flat_map(|e| match e {
11072 CompletionEntry::Match(mat) => Some(mat.string.as_str()),
11073 _ => None,
11074 })
11075 .collect()
11076}
11077
11078#[gpui::test]
11079async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
11080 init_test(cx, |settings| {
11081 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
11082 FormatterList(vec![Formatter::Prettier].into()),
11083 ))
11084 });
11085
11086 let fs = FakeFs::new(cx.executor());
11087 fs.insert_file("/file.ts", Default::default()).await;
11088
11089 let project = Project::test(fs, ["/file.ts".as_ref()], cx).await;
11090 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11091
11092 language_registry.add(Arc::new(Language::new(
11093 LanguageConfig {
11094 name: "TypeScript".into(),
11095 matcher: LanguageMatcher {
11096 path_suffixes: vec!["ts".to_string()],
11097 ..Default::default()
11098 },
11099 ..Default::default()
11100 },
11101 Some(tree_sitter_rust::LANGUAGE.into()),
11102 )));
11103 update_test_language_settings(cx, |settings| {
11104 settings.defaults.prettier = Some(PrettierSettings {
11105 allowed: true,
11106 ..PrettierSettings::default()
11107 });
11108 });
11109
11110 let test_plugin = "test_plugin";
11111 let _ = language_registry.register_fake_lsp(
11112 "TypeScript",
11113 FakeLspAdapter {
11114 prettier_plugins: vec![test_plugin],
11115 ..Default::default()
11116 },
11117 );
11118
11119 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
11120 let buffer = project
11121 .update(cx, |project, cx| project.open_local_buffer("/file.ts", cx))
11122 .await
11123 .unwrap();
11124
11125 let buffer_text = "one\ntwo\nthree\n";
11126 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
11127 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
11128 editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
11129
11130 editor
11131 .update(cx, |editor, cx| {
11132 editor.perform_format(
11133 project.clone(),
11134 FormatTrigger::Manual,
11135 FormatTarget::Buffer,
11136 cx,
11137 )
11138 })
11139 .unwrap()
11140 .await;
11141 assert_eq!(
11142 editor.update(cx, |editor, cx| editor.text(cx)),
11143 buffer_text.to_string() + prettier_format_suffix,
11144 "Test prettier formatting was not applied to the original buffer text",
11145 );
11146
11147 update_test_language_settings(cx, |settings| {
11148 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
11149 });
11150 let format = editor.update(cx, |editor, cx| {
11151 editor.perform_format(
11152 project.clone(),
11153 FormatTrigger::Manual,
11154 FormatTarget::Buffer,
11155 cx,
11156 )
11157 });
11158 format.await.unwrap();
11159 assert_eq!(
11160 editor.update(cx, |editor, cx| editor.text(cx)),
11161 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
11162 "Autoformatting (via test prettier) was not applied to the original buffer text",
11163 );
11164}
11165
11166#[gpui::test]
11167async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
11168 init_test(cx, |_| {});
11169 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11170 let base_text = indoc! {r#"
11171 struct Row;
11172 struct Row1;
11173 struct Row2;
11174
11175 struct Row4;
11176 struct Row5;
11177 struct Row6;
11178
11179 struct Row8;
11180 struct Row9;
11181 struct Row10;"#};
11182
11183 // When addition hunks are not adjacent to carets, no hunk revert is performed
11184 assert_hunk_revert(
11185 indoc! {r#"struct Row;
11186 struct Row1;
11187 struct Row1.1;
11188 struct Row1.2;
11189 struct Row2;ˇ
11190
11191 struct Row4;
11192 struct Row5;
11193 struct Row6;
11194
11195 struct Row8;
11196 ˇstruct Row9;
11197 struct Row9.1;
11198 struct Row9.2;
11199 struct Row9.3;
11200 struct Row10;"#},
11201 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
11202 indoc! {r#"struct Row;
11203 struct Row1;
11204 struct Row1.1;
11205 struct Row1.2;
11206 struct Row2;ˇ
11207
11208 struct Row4;
11209 struct Row5;
11210 struct Row6;
11211
11212 struct Row8;
11213 ˇstruct Row9;
11214 struct Row9.1;
11215 struct Row9.2;
11216 struct Row9.3;
11217 struct Row10;"#},
11218 base_text,
11219 &mut cx,
11220 );
11221 // Same for selections
11222 assert_hunk_revert(
11223 indoc! {r#"struct Row;
11224 struct Row1;
11225 struct Row2;
11226 struct Row2.1;
11227 struct Row2.2;
11228 «ˇ
11229 struct Row4;
11230 struct» Row5;
11231 «struct Row6;
11232 ˇ»
11233 struct Row9.1;
11234 struct Row9.2;
11235 struct Row9.3;
11236 struct Row8;
11237 struct Row9;
11238 struct Row10;"#},
11239 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
11240 indoc! {r#"struct Row;
11241 struct Row1;
11242 struct Row2;
11243 struct Row2.1;
11244 struct Row2.2;
11245 «ˇ
11246 struct Row4;
11247 struct» Row5;
11248 «struct Row6;
11249 ˇ»
11250 struct Row9.1;
11251 struct Row9.2;
11252 struct Row9.3;
11253 struct Row8;
11254 struct Row9;
11255 struct Row10;"#},
11256 base_text,
11257 &mut cx,
11258 );
11259
11260 // When carets and selections intersect the addition hunks, those are reverted.
11261 // Adjacent carets got merged.
11262 assert_hunk_revert(
11263 indoc! {r#"struct Row;
11264 ˇ// something on the top
11265 struct Row1;
11266 struct Row2;
11267 struct Roˇw3.1;
11268 struct Row2.2;
11269 struct Row2.3;ˇ
11270
11271 struct Row4;
11272 struct ˇRow5.1;
11273 struct Row5.2;
11274 struct «Rowˇ»5.3;
11275 struct Row5;
11276 struct Row6;
11277 ˇ
11278 struct Row9.1;
11279 struct «Rowˇ»9.2;
11280 struct «ˇRow»9.3;
11281 struct Row8;
11282 struct Row9;
11283 «ˇ// something on bottom»
11284 struct Row10;"#},
11285 vec![
11286 DiffHunkStatus::Added,
11287 DiffHunkStatus::Added,
11288 DiffHunkStatus::Added,
11289 DiffHunkStatus::Added,
11290 DiffHunkStatus::Added,
11291 ],
11292 indoc! {r#"struct Row;
11293 ˇstruct Row1;
11294 struct Row2;
11295 ˇ
11296 struct Row4;
11297 ˇstruct Row5;
11298 struct Row6;
11299 ˇ
11300 ˇstruct Row8;
11301 struct Row9;
11302 ˇstruct Row10;"#},
11303 base_text,
11304 &mut cx,
11305 );
11306}
11307
11308#[gpui::test]
11309async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
11310 init_test(cx, |_| {});
11311 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11312 let base_text = indoc! {r#"
11313 struct Row;
11314 struct Row1;
11315 struct Row2;
11316
11317 struct Row4;
11318 struct Row5;
11319 struct Row6;
11320
11321 struct Row8;
11322 struct Row9;
11323 struct Row10;"#};
11324
11325 // Modification hunks behave the same as the addition ones.
11326 assert_hunk_revert(
11327 indoc! {r#"struct Row;
11328 struct Row1;
11329 struct Row33;
11330 ˇ
11331 struct Row4;
11332 struct Row5;
11333 struct Row6;
11334 ˇ
11335 struct Row99;
11336 struct Row9;
11337 struct Row10;"#},
11338 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
11339 indoc! {r#"struct Row;
11340 struct Row1;
11341 struct Row33;
11342 ˇ
11343 struct Row4;
11344 struct Row5;
11345 struct Row6;
11346 ˇ
11347 struct Row99;
11348 struct Row9;
11349 struct Row10;"#},
11350 base_text,
11351 &mut cx,
11352 );
11353 assert_hunk_revert(
11354 indoc! {r#"struct Row;
11355 struct Row1;
11356 struct Row33;
11357 «ˇ
11358 struct Row4;
11359 struct» Row5;
11360 «struct Row6;
11361 ˇ»
11362 struct Row99;
11363 struct Row9;
11364 struct Row10;"#},
11365 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
11366 indoc! {r#"struct Row;
11367 struct Row1;
11368 struct Row33;
11369 «ˇ
11370 struct Row4;
11371 struct» Row5;
11372 «struct Row6;
11373 ˇ»
11374 struct Row99;
11375 struct Row9;
11376 struct Row10;"#},
11377 base_text,
11378 &mut cx,
11379 );
11380
11381 assert_hunk_revert(
11382 indoc! {r#"ˇstruct Row1.1;
11383 struct Row1;
11384 «ˇstr»uct Row22;
11385
11386 struct ˇRow44;
11387 struct Row5;
11388 struct «Rˇ»ow66;ˇ
11389
11390 «struˇ»ct Row88;
11391 struct Row9;
11392 struct Row1011;ˇ"#},
11393 vec![
11394 DiffHunkStatus::Modified,
11395 DiffHunkStatus::Modified,
11396 DiffHunkStatus::Modified,
11397 DiffHunkStatus::Modified,
11398 DiffHunkStatus::Modified,
11399 DiffHunkStatus::Modified,
11400 ],
11401 indoc! {r#"struct Row;
11402 ˇstruct Row1;
11403 struct Row2;
11404 ˇ
11405 struct Row4;
11406 ˇstruct Row5;
11407 struct Row6;
11408 ˇ
11409 struct Row8;
11410 ˇstruct Row9;
11411 struct Row10;ˇ"#},
11412 base_text,
11413 &mut cx,
11414 );
11415}
11416
11417#[gpui::test]
11418async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
11419 init_test(cx, |_| {});
11420 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11421 let base_text = indoc! {r#"struct Row;
11422struct Row1;
11423struct Row2;
11424
11425struct Row4;
11426struct Row5;
11427struct Row6;
11428
11429struct Row8;
11430struct Row9;
11431struct Row10;"#};
11432
11433 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
11434 assert_hunk_revert(
11435 indoc! {r#"struct Row;
11436 struct Row2;
11437
11438 ˇstruct Row4;
11439 struct Row5;
11440 struct Row6;
11441 ˇ
11442 struct Row8;
11443 struct Row10;"#},
11444 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11445 indoc! {r#"struct Row;
11446 struct Row2;
11447
11448 ˇstruct Row4;
11449 struct Row5;
11450 struct Row6;
11451 ˇ
11452 struct Row8;
11453 struct Row10;"#},
11454 base_text,
11455 &mut cx,
11456 );
11457 assert_hunk_revert(
11458 indoc! {r#"struct Row;
11459 struct Row2;
11460
11461 «ˇstruct Row4;
11462 struct» Row5;
11463 «struct Row6;
11464 ˇ»
11465 struct Row8;
11466 struct Row10;"#},
11467 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11468 indoc! {r#"struct Row;
11469 struct Row2;
11470
11471 «ˇstruct Row4;
11472 struct» Row5;
11473 «struct Row6;
11474 ˇ»
11475 struct Row8;
11476 struct Row10;"#},
11477 base_text,
11478 &mut cx,
11479 );
11480
11481 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
11482 assert_hunk_revert(
11483 indoc! {r#"struct Row;
11484 ˇstruct Row2;
11485
11486 struct Row4;
11487 struct Row5;
11488 struct Row6;
11489
11490 struct Row8;ˇ
11491 struct Row10;"#},
11492 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11493 indoc! {r#"struct Row;
11494 struct Row1;
11495 ˇstruct Row2;
11496
11497 struct Row4;
11498 struct Row5;
11499 struct Row6;
11500
11501 struct Row8;ˇ
11502 struct Row9;
11503 struct Row10;"#},
11504 base_text,
11505 &mut cx,
11506 );
11507 assert_hunk_revert(
11508 indoc! {r#"struct Row;
11509 struct Row2«ˇ;
11510 struct Row4;
11511 struct» Row5;
11512 «struct Row6;
11513
11514 struct Row8;ˇ»
11515 struct Row10;"#},
11516 vec![
11517 DiffHunkStatus::Removed,
11518 DiffHunkStatus::Removed,
11519 DiffHunkStatus::Removed,
11520 ],
11521 indoc! {r#"struct Row;
11522 struct Row1;
11523 struct Row2«ˇ;
11524
11525 struct Row4;
11526 struct» Row5;
11527 «struct Row6;
11528
11529 struct Row8;ˇ»
11530 struct Row9;
11531 struct Row10;"#},
11532 base_text,
11533 &mut cx,
11534 );
11535}
11536
11537#[gpui::test]
11538async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
11539 init_test(cx, |_| {});
11540
11541 let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
11542 let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
11543 let base_text_3 =
11544 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
11545
11546 let text_1 = edit_first_char_of_every_line(base_text_1);
11547 let text_2 = edit_first_char_of_every_line(base_text_2);
11548 let text_3 = edit_first_char_of_every_line(base_text_3);
11549
11550 let buffer_1 = cx.new_model(|cx| Buffer::local(text_1.clone(), cx));
11551 let buffer_2 = cx.new_model(|cx| Buffer::local(text_2.clone(), cx));
11552 let buffer_3 = cx.new_model(|cx| Buffer::local(text_3.clone(), cx));
11553
11554 let multibuffer = cx.new_model(|cx| {
11555 let mut multibuffer = MultiBuffer::new(ReadWrite);
11556 multibuffer.push_excerpts(
11557 buffer_1.clone(),
11558 [
11559 ExcerptRange {
11560 context: Point::new(0, 0)..Point::new(3, 0),
11561 primary: None,
11562 },
11563 ExcerptRange {
11564 context: Point::new(5, 0)..Point::new(7, 0),
11565 primary: None,
11566 },
11567 ExcerptRange {
11568 context: Point::new(9, 0)..Point::new(10, 4),
11569 primary: None,
11570 },
11571 ],
11572 cx,
11573 );
11574 multibuffer.push_excerpts(
11575 buffer_2.clone(),
11576 [
11577 ExcerptRange {
11578 context: Point::new(0, 0)..Point::new(3, 0),
11579 primary: None,
11580 },
11581 ExcerptRange {
11582 context: Point::new(5, 0)..Point::new(7, 0),
11583 primary: None,
11584 },
11585 ExcerptRange {
11586 context: Point::new(9, 0)..Point::new(10, 4),
11587 primary: None,
11588 },
11589 ],
11590 cx,
11591 );
11592 multibuffer.push_excerpts(
11593 buffer_3.clone(),
11594 [
11595 ExcerptRange {
11596 context: Point::new(0, 0)..Point::new(3, 0),
11597 primary: None,
11598 },
11599 ExcerptRange {
11600 context: Point::new(5, 0)..Point::new(7, 0),
11601 primary: None,
11602 },
11603 ExcerptRange {
11604 context: Point::new(9, 0)..Point::new(10, 4),
11605 primary: None,
11606 },
11607 ],
11608 cx,
11609 );
11610 multibuffer
11611 });
11612
11613 let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
11614 editor.update(cx, |editor, cx| {
11615 for (buffer, diff_base) in [
11616 (buffer_1.clone(), base_text_1),
11617 (buffer_2.clone(), base_text_2),
11618 (buffer_3.clone(), base_text_3),
11619 ] {
11620 let change_set = cx.new_model(|cx| {
11621 BufferChangeSet::new_with_base_text(
11622 diff_base.to_string(),
11623 buffer.read(cx).text_snapshot(),
11624 cx,
11625 )
11626 });
11627 editor.diff_map.add_change_set(change_set, cx)
11628 }
11629 });
11630 cx.executor().run_until_parked();
11631
11632 editor.update(cx, |editor, cx| {
11633 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}");
11634 editor.select_all(&SelectAll, cx);
11635 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
11636 });
11637 cx.executor().run_until_parked();
11638
11639 // When all ranges are selected, all buffer hunks are reverted.
11640 editor.update(cx, |editor, cx| {
11641 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");
11642 });
11643 buffer_1.update(cx, |buffer, _| {
11644 assert_eq!(buffer.text(), base_text_1);
11645 });
11646 buffer_2.update(cx, |buffer, _| {
11647 assert_eq!(buffer.text(), base_text_2);
11648 });
11649 buffer_3.update(cx, |buffer, _| {
11650 assert_eq!(buffer.text(), base_text_3);
11651 });
11652
11653 editor.update(cx, |editor, cx| {
11654 editor.undo(&Default::default(), cx);
11655 });
11656
11657 editor.update(cx, |editor, cx| {
11658 editor.change_selections(None, cx, |s| {
11659 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
11660 });
11661 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
11662 });
11663
11664 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
11665 // but not affect buffer_2 and its related excerpts.
11666 editor.update(cx, |editor, cx| {
11667 assert_eq!(
11668 editor.text(cx),
11669 "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}"
11670 );
11671 });
11672 buffer_1.update(cx, |buffer, _| {
11673 assert_eq!(buffer.text(), base_text_1);
11674 });
11675 buffer_2.update(cx, |buffer, _| {
11676 assert_eq!(
11677 buffer.text(),
11678 "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
11679 );
11680 });
11681 buffer_3.update(cx, |buffer, _| {
11682 assert_eq!(
11683 buffer.text(),
11684 "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
11685 );
11686 });
11687
11688 fn edit_first_char_of_every_line(text: &str) -> String {
11689 text.split('\n')
11690 .map(|line| format!("X{}", &line[1..]))
11691 .collect::<Vec<_>>()
11692 .join("\n")
11693 }
11694}
11695
11696#[gpui::test]
11697async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
11698 init_test(cx, |_| {});
11699
11700 let cols = 4;
11701 let rows = 10;
11702 let sample_text_1 = sample_text(rows, cols, 'a');
11703 assert_eq!(
11704 sample_text_1,
11705 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11706 );
11707 let sample_text_2 = sample_text(rows, cols, 'l');
11708 assert_eq!(
11709 sample_text_2,
11710 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11711 );
11712 let sample_text_3 = sample_text(rows, cols, 'v');
11713 assert_eq!(
11714 sample_text_3,
11715 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11716 );
11717
11718 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
11719 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
11720 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
11721
11722 let multi_buffer = cx.new_model(|cx| {
11723 let mut multibuffer = MultiBuffer::new(ReadWrite);
11724 multibuffer.push_excerpts(
11725 buffer_1.clone(),
11726 [
11727 ExcerptRange {
11728 context: Point::new(0, 0)..Point::new(3, 0),
11729 primary: None,
11730 },
11731 ExcerptRange {
11732 context: Point::new(5, 0)..Point::new(7, 0),
11733 primary: None,
11734 },
11735 ExcerptRange {
11736 context: Point::new(9, 0)..Point::new(10, 4),
11737 primary: None,
11738 },
11739 ],
11740 cx,
11741 );
11742 multibuffer.push_excerpts(
11743 buffer_2.clone(),
11744 [
11745 ExcerptRange {
11746 context: Point::new(0, 0)..Point::new(3, 0),
11747 primary: None,
11748 },
11749 ExcerptRange {
11750 context: Point::new(5, 0)..Point::new(7, 0),
11751 primary: None,
11752 },
11753 ExcerptRange {
11754 context: Point::new(9, 0)..Point::new(10, 4),
11755 primary: None,
11756 },
11757 ],
11758 cx,
11759 );
11760 multibuffer.push_excerpts(
11761 buffer_3.clone(),
11762 [
11763 ExcerptRange {
11764 context: Point::new(0, 0)..Point::new(3, 0),
11765 primary: None,
11766 },
11767 ExcerptRange {
11768 context: Point::new(5, 0)..Point::new(7, 0),
11769 primary: None,
11770 },
11771 ExcerptRange {
11772 context: Point::new(9, 0)..Point::new(10, 4),
11773 primary: None,
11774 },
11775 ],
11776 cx,
11777 );
11778 multibuffer
11779 });
11780
11781 let fs = FakeFs::new(cx.executor());
11782 fs.insert_tree(
11783 "/a",
11784 json!({
11785 "main.rs": sample_text_1,
11786 "other.rs": sample_text_2,
11787 "lib.rs": sample_text_3,
11788 }),
11789 )
11790 .await;
11791 let project = Project::test(fs, ["/a".as_ref()], cx).await;
11792 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
11793 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11794 let multi_buffer_editor = cx.new_view(|cx| {
11795 Editor::new(
11796 EditorMode::Full,
11797 multi_buffer,
11798 Some(project.clone()),
11799 true,
11800 cx,
11801 )
11802 });
11803 let multibuffer_item_id = workspace
11804 .update(cx, |workspace, cx| {
11805 assert!(
11806 workspace.active_item(cx).is_none(),
11807 "active item should be None before the first item is added"
11808 );
11809 workspace.add_item_to_active_pane(
11810 Box::new(multi_buffer_editor.clone()),
11811 None,
11812 true,
11813 cx,
11814 );
11815 let active_item = workspace
11816 .active_item(cx)
11817 .expect("should have an active item after adding the multi buffer");
11818 assert!(
11819 !active_item.is_singleton(cx),
11820 "A multi buffer was expected to active after adding"
11821 );
11822 active_item.item_id()
11823 })
11824 .unwrap();
11825 cx.executor().run_until_parked();
11826
11827 multi_buffer_editor.update(cx, |editor, cx| {
11828 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
11829 editor.open_excerpts(&OpenExcerpts, cx);
11830 });
11831 cx.executor().run_until_parked();
11832 let first_item_id = workspace
11833 .update(cx, |workspace, cx| {
11834 let active_item = workspace
11835 .active_item(cx)
11836 .expect("should have an active item after navigating into the 1st buffer");
11837 let first_item_id = active_item.item_id();
11838 assert_ne!(
11839 first_item_id, multibuffer_item_id,
11840 "Should navigate into the 1st buffer and activate it"
11841 );
11842 assert!(
11843 active_item.is_singleton(cx),
11844 "New active item should be a singleton buffer"
11845 );
11846 assert_eq!(
11847 active_item
11848 .act_as::<Editor>(cx)
11849 .expect("should have navigated into an editor for the 1st buffer")
11850 .read(cx)
11851 .text(cx),
11852 sample_text_1
11853 );
11854
11855 workspace
11856 .go_back(workspace.active_pane().downgrade(), cx)
11857 .detach_and_log_err(cx);
11858
11859 first_item_id
11860 })
11861 .unwrap();
11862 cx.executor().run_until_parked();
11863 workspace
11864 .update(cx, |workspace, cx| {
11865 let active_item = workspace
11866 .active_item(cx)
11867 .expect("should have an active item after navigating back");
11868 assert_eq!(
11869 active_item.item_id(),
11870 multibuffer_item_id,
11871 "Should navigate back to the multi buffer"
11872 );
11873 assert!(!active_item.is_singleton(cx));
11874 })
11875 .unwrap();
11876
11877 multi_buffer_editor.update(cx, |editor, cx| {
11878 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
11879 s.select_ranges(Some(39..40))
11880 });
11881 editor.open_excerpts(&OpenExcerpts, cx);
11882 });
11883 cx.executor().run_until_parked();
11884 let second_item_id = workspace
11885 .update(cx, |workspace, cx| {
11886 let active_item = workspace
11887 .active_item(cx)
11888 .expect("should have an active item after navigating into the 2nd buffer");
11889 let second_item_id = active_item.item_id();
11890 assert_ne!(
11891 second_item_id, multibuffer_item_id,
11892 "Should navigate away from the multibuffer"
11893 );
11894 assert_ne!(
11895 second_item_id, first_item_id,
11896 "Should navigate into the 2nd buffer and activate it"
11897 );
11898 assert!(
11899 active_item.is_singleton(cx),
11900 "New active item should be a singleton buffer"
11901 );
11902 assert_eq!(
11903 active_item
11904 .act_as::<Editor>(cx)
11905 .expect("should have navigated into an editor")
11906 .read(cx)
11907 .text(cx),
11908 sample_text_2
11909 );
11910
11911 workspace
11912 .go_back(workspace.active_pane().downgrade(), cx)
11913 .detach_and_log_err(cx);
11914
11915 second_item_id
11916 })
11917 .unwrap();
11918 cx.executor().run_until_parked();
11919 workspace
11920 .update(cx, |workspace, cx| {
11921 let active_item = workspace
11922 .active_item(cx)
11923 .expect("should have an active item after navigating back from the 2nd buffer");
11924 assert_eq!(
11925 active_item.item_id(),
11926 multibuffer_item_id,
11927 "Should navigate back from the 2nd buffer to the multi buffer"
11928 );
11929 assert!(!active_item.is_singleton(cx));
11930 })
11931 .unwrap();
11932
11933 multi_buffer_editor.update(cx, |editor, cx| {
11934 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
11935 s.select_ranges(Some(70..70))
11936 });
11937 editor.open_excerpts(&OpenExcerpts, cx);
11938 });
11939 cx.executor().run_until_parked();
11940 workspace
11941 .update(cx, |workspace, cx| {
11942 let active_item = workspace
11943 .active_item(cx)
11944 .expect("should have an active item after navigating into the 3rd buffer");
11945 let third_item_id = active_item.item_id();
11946 assert_ne!(
11947 third_item_id, multibuffer_item_id,
11948 "Should navigate into the 3rd buffer and activate it"
11949 );
11950 assert_ne!(third_item_id, first_item_id);
11951 assert_ne!(third_item_id, second_item_id);
11952 assert!(
11953 active_item.is_singleton(cx),
11954 "New active item should be a singleton buffer"
11955 );
11956 assert_eq!(
11957 active_item
11958 .act_as::<Editor>(cx)
11959 .expect("should have navigated into an editor")
11960 .read(cx)
11961 .text(cx),
11962 sample_text_3
11963 );
11964
11965 workspace
11966 .go_back(workspace.active_pane().downgrade(), cx)
11967 .detach_and_log_err(cx);
11968 })
11969 .unwrap();
11970 cx.executor().run_until_parked();
11971 workspace
11972 .update(cx, |workspace, cx| {
11973 let active_item = workspace
11974 .active_item(cx)
11975 .expect("should have an active item after navigating back from the 3rd buffer");
11976 assert_eq!(
11977 active_item.item_id(),
11978 multibuffer_item_id,
11979 "Should navigate back from the 3rd buffer to the multi buffer"
11980 );
11981 assert!(!active_item.is_singleton(cx));
11982 })
11983 .unwrap();
11984}
11985
11986#[gpui::test]
11987async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
11988 init_test(cx, |_| {});
11989
11990 let mut cx = EditorTestContext::new(cx).await;
11991
11992 let diff_base = r#"
11993 use some::mod;
11994
11995 const A: u32 = 42;
11996
11997 fn main() {
11998 println!("hello");
11999
12000 println!("world");
12001 }
12002 "#
12003 .unindent();
12004
12005 cx.set_state(
12006 &r#"
12007 use some::modified;
12008
12009 ˇ
12010 fn main() {
12011 println!("hello there");
12012
12013 println!("around the");
12014 println!("world");
12015 }
12016 "#
12017 .unindent(),
12018 );
12019
12020 cx.set_diff_base(&diff_base);
12021 executor.run_until_parked();
12022
12023 cx.update_editor(|editor, cx| {
12024 editor.go_to_next_hunk(&GoToHunk, cx);
12025 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
12026 });
12027 executor.run_until_parked();
12028 cx.assert_state_with_diff(
12029 r#"
12030 use some::modified;
12031
12032
12033 fn main() {
12034 - println!("hello");
12035 + ˇ println!("hello there");
12036
12037 println!("around the");
12038 println!("world");
12039 }
12040 "#
12041 .unindent(),
12042 );
12043
12044 cx.update_editor(|editor, cx| {
12045 for _ in 0..3 {
12046 editor.go_to_next_hunk(&GoToHunk, cx);
12047 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
12048 }
12049 });
12050 executor.run_until_parked();
12051 cx.assert_state_with_diff(
12052 r#"
12053 - use some::mod;
12054 + use some::modified;
12055
12056 - const A: u32 = 42;
12057 ˇ
12058 fn main() {
12059 - println!("hello");
12060 + println!("hello there");
12061
12062 + println!("around the");
12063 println!("world");
12064 }
12065 "#
12066 .unindent(),
12067 );
12068
12069 cx.update_editor(|editor, cx| {
12070 editor.cancel(&Cancel, cx);
12071 });
12072
12073 cx.assert_state_with_diff(
12074 r#"
12075 use some::modified;
12076
12077 ˇ
12078 fn main() {
12079 println!("hello there");
12080
12081 println!("around the");
12082 println!("world");
12083 }
12084 "#
12085 .unindent(),
12086 );
12087}
12088
12089#[gpui::test]
12090async fn test_diff_base_change_with_expanded_diff_hunks(
12091 executor: BackgroundExecutor,
12092 cx: &mut gpui::TestAppContext,
12093) {
12094 init_test(cx, |_| {});
12095
12096 let mut cx = EditorTestContext::new(cx).await;
12097
12098 let diff_base = r#"
12099 use some::mod1;
12100 use some::mod2;
12101
12102 const A: u32 = 42;
12103 const B: u32 = 42;
12104 const C: u32 = 42;
12105
12106 fn main() {
12107 println!("hello");
12108
12109 println!("world");
12110 }
12111 "#
12112 .unindent();
12113
12114 cx.set_state(
12115 &r#"
12116 use some::mod2;
12117
12118 const A: u32 = 42;
12119 const C: u32 = 42;
12120
12121 fn main(ˇ) {
12122 //println!("hello");
12123
12124 println!("world");
12125 //
12126 //
12127 }
12128 "#
12129 .unindent(),
12130 );
12131
12132 cx.set_diff_base(&diff_base);
12133 executor.run_until_parked();
12134
12135 cx.update_editor(|editor, cx| {
12136 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12137 });
12138 executor.run_until_parked();
12139 cx.assert_state_with_diff(
12140 r#"
12141 - use some::mod1;
12142 use some::mod2;
12143
12144 const A: u32 = 42;
12145 - const B: u32 = 42;
12146 const C: u32 = 42;
12147
12148 fn main(ˇ) {
12149 - println!("hello");
12150 + //println!("hello");
12151
12152 println!("world");
12153 + //
12154 + //
12155 }
12156 "#
12157 .unindent(),
12158 );
12159
12160 cx.set_diff_base("new diff base!");
12161 executor.run_until_parked();
12162 cx.assert_state_with_diff(
12163 r#"
12164 use some::mod2;
12165
12166 const A: u32 = 42;
12167 const C: u32 = 42;
12168
12169 fn main(ˇ) {
12170 //println!("hello");
12171
12172 println!("world");
12173 //
12174 //
12175 }
12176 "#
12177 .unindent(),
12178 );
12179
12180 cx.update_editor(|editor, cx| {
12181 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12182 });
12183 executor.run_until_parked();
12184 cx.assert_state_with_diff(
12185 r#"
12186 - new diff base!
12187 + use some::mod2;
12188 +
12189 + const A: u32 = 42;
12190 + const C: u32 = 42;
12191 +
12192 + fn main(ˇ) {
12193 + //println!("hello");
12194 +
12195 + println!("world");
12196 + //
12197 + //
12198 + }
12199 "#
12200 .unindent(),
12201 );
12202}
12203
12204#[gpui::test]
12205async fn test_fold_unfold_diff_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
12206 init_test(cx, |_| {});
12207
12208 let mut cx = EditorTestContext::new(cx).await;
12209
12210 let diff_base = r#"
12211 use some::mod1;
12212 use some::mod2;
12213
12214 const A: u32 = 42;
12215 const B: u32 = 42;
12216 const C: u32 = 42;
12217
12218 fn main() {
12219 println!("hello");
12220
12221 println!("world");
12222 }
12223
12224 fn another() {
12225 println!("another");
12226 }
12227
12228 fn another2() {
12229 println!("another2");
12230 }
12231 "#
12232 .unindent();
12233
12234 cx.set_state(
12235 &r#"
12236 «use some::mod2;
12237
12238 const A: u32 = 42;
12239 const C: u32 = 42;
12240
12241 fn main() {
12242 //println!("hello");
12243
12244 println!("world");
12245 //
12246 //ˇ»
12247 }
12248
12249 fn another() {
12250 println!("another");
12251 println!("another");
12252 }
12253
12254 println!("another2");
12255 }
12256 "#
12257 .unindent(),
12258 );
12259
12260 cx.set_diff_base(&diff_base);
12261 executor.run_until_parked();
12262
12263 cx.update_editor(|editor, cx| {
12264 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12265 });
12266 executor.run_until_parked();
12267
12268 cx.assert_state_with_diff(
12269 r#"
12270 - use some::mod1;
12271 «use some::mod2;
12272
12273 const A: u32 = 42;
12274 - const B: u32 = 42;
12275 const C: u32 = 42;
12276
12277 fn main() {
12278 - println!("hello");
12279 + //println!("hello");
12280
12281 println!("world");
12282 + //
12283 + //ˇ»
12284 }
12285
12286 fn another() {
12287 println!("another");
12288 + println!("another");
12289 }
12290
12291 - fn another2() {
12292 println!("another2");
12293 }
12294 "#
12295 .unindent(),
12296 );
12297
12298 // Fold across some of the diff hunks. They should no longer appear expanded.
12299 cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx));
12300 cx.executor().run_until_parked();
12301
12302 // Hunks are not shown if their position is within a fold
12303 cx.assert_state_with_diff(
12304 r#"
12305 «use some::mod2;
12306
12307 const A: u32 = 42;
12308 const C: u32 = 42;
12309
12310 fn main() {
12311 //println!("hello");
12312
12313 println!("world");
12314 //
12315 //ˇ»
12316 }
12317
12318 fn another() {
12319 println!("another");
12320 + println!("another");
12321 }
12322
12323 - fn another2() {
12324 println!("another2");
12325 }
12326 "#
12327 .unindent(),
12328 );
12329
12330 cx.update_editor(|editor, cx| {
12331 editor.select_all(&SelectAll, cx);
12332 editor.unfold_lines(&UnfoldLines, cx);
12333 });
12334 cx.executor().run_until_parked();
12335
12336 // The deletions reappear when unfolding.
12337 cx.assert_state_with_diff(
12338 r#"
12339 - use some::mod1;
12340 «use some::mod2;
12341
12342 const A: u32 = 42;
12343 - const B: u32 = 42;
12344 const C: u32 = 42;
12345
12346 fn main() {
12347 - println!("hello");
12348 + //println!("hello");
12349
12350 println!("world");
12351 + //
12352 + //
12353 }
12354
12355 fn another() {
12356 println!("another");
12357 + println!("another");
12358 }
12359
12360 - fn another2() {
12361 println!("another2");
12362 }
12363 ˇ»"#
12364 .unindent(),
12365 );
12366}
12367
12368#[gpui::test]
12369async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
12370 init_test(cx, |_| {});
12371
12372 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
12373 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
12374 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
12375 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
12376 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
12377 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
12378
12379 let buffer_1 = cx.new_model(|cx| Buffer::local(file_1_new.to_string(), cx));
12380 let buffer_2 = cx.new_model(|cx| Buffer::local(file_2_new.to_string(), cx));
12381 let buffer_3 = cx.new_model(|cx| Buffer::local(file_3_new.to_string(), cx));
12382
12383 let multi_buffer = cx.new_model(|cx| {
12384 let mut multibuffer = MultiBuffer::new(ReadWrite);
12385 multibuffer.push_excerpts(
12386 buffer_1.clone(),
12387 [
12388 ExcerptRange {
12389 context: Point::new(0, 0)..Point::new(3, 0),
12390 primary: None,
12391 },
12392 ExcerptRange {
12393 context: Point::new(5, 0)..Point::new(7, 0),
12394 primary: None,
12395 },
12396 ExcerptRange {
12397 context: Point::new(9, 0)..Point::new(10, 3),
12398 primary: None,
12399 },
12400 ],
12401 cx,
12402 );
12403 multibuffer.push_excerpts(
12404 buffer_2.clone(),
12405 [
12406 ExcerptRange {
12407 context: Point::new(0, 0)..Point::new(3, 0),
12408 primary: None,
12409 },
12410 ExcerptRange {
12411 context: Point::new(5, 0)..Point::new(7, 0),
12412 primary: None,
12413 },
12414 ExcerptRange {
12415 context: Point::new(9, 0)..Point::new(10, 3),
12416 primary: None,
12417 },
12418 ],
12419 cx,
12420 );
12421 multibuffer.push_excerpts(
12422 buffer_3.clone(),
12423 [
12424 ExcerptRange {
12425 context: Point::new(0, 0)..Point::new(3, 0),
12426 primary: None,
12427 },
12428 ExcerptRange {
12429 context: Point::new(5, 0)..Point::new(7, 0),
12430 primary: None,
12431 },
12432 ExcerptRange {
12433 context: Point::new(9, 0)..Point::new(10, 3),
12434 primary: None,
12435 },
12436 ],
12437 cx,
12438 );
12439 multibuffer
12440 });
12441
12442 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
12443 editor
12444 .update(cx, |editor, cx| {
12445 for (buffer, diff_base) in [
12446 (buffer_1.clone(), file_1_old),
12447 (buffer_2.clone(), file_2_old),
12448 (buffer_3.clone(), file_3_old),
12449 ] {
12450 let change_set = cx.new_model(|cx| {
12451 BufferChangeSet::new_with_base_text(
12452 diff_base.to_string(),
12453 buffer.read(cx).text_snapshot(),
12454 cx,
12455 )
12456 });
12457 editor.diff_map.add_change_set(change_set, cx)
12458 }
12459 })
12460 .unwrap();
12461
12462 let mut cx = EditorTestContext::for_editor(editor, cx).await;
12463 cx.run_until_parked();
12464
12465 cx.assert_editor_state(
12466 &"
12467 ˇaaa
12468 ccc
12469 ddd
12470
12471 ggg
12472 hhh
12473
12474
12475 lll
12476 mmm
12477 NNN
12478
12479 qqq
12480 rrr
12481
12482 uuu
12483 111
12484 222
12485 333
12486
12487 666
12488 777
12489
12490 000
12491 !!!"
12492 .unindent(),
12493 );
12494
12495 cx.update_editor(|editor, cx| {
12496 editor.select_all(&SelectAll, cx);
12497 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
12498 });
12499 cx.executor().run_until_parked();
12500
12501 cx.assert_state_with_diff(
12502 "
12503 «aaa
12504 - bbb
12505 ccc
12506 ddd
12507
12508 ggg
12509 hhh
12510
12511
12512 lll
12513 mmm
12514 - nnn
12515 + NNN
12516
12517 qqq
12518 rrr
12519
12520 uuu
12521 111
12522 222
12523 333
12524
12525 + 666
12526 777
12527
12528 000
12529 !!!ˇ»"
12530 .unindent(),
12531 );
12532}
12533
12534#[gpui::test]
12535async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut gpui::TestAppContext) {
12536 init_test(cx, |_| {});
12537
12538 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
12539 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\n";
12540
12541 let buffer = cx.new_model(|cx| Buffer::local(text.to_string(), cx));
12542 let multi_buffer = cx.new_model(|cx| {
12543 let mut multibuffer = MultiBuffer::new(ReadWrite);
12544 multibuffer.push_excerpts(
12545 buffer.clone(),
12546 [
12547 ExcerptRange {
12548 context: Point::new(0, 0)..Point::new(2, 0),
12549 primary: None,
12550 },
12551 ExcerptRange {
12552 context: Point::new(5, 0)..Point::new(7, 0),
12553 primary: None,
12554 },
12555 ],
12556 cx,
12557 );
12558 multibuffer
12559 });
12560
12561 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
12562 editor
12563 .update(cx, |editor, cx| {
12564 let buffer = buffer.read(cx).text_snapshot();
12565 let change_set = cx
12566 .new_model(|cx| BufferChangeSet::new_with_base_text(base.to_string(), buffer, cx));
12567 editor.diff_map.add_change_set(change_set, cx)
12568 })
12569 .unwrap();
12570
12571 let mut cx = EditorTestContext::for_editor(editor, cx).await;
12572 cx.run_until_parked();
12573
12574 cx.update_editor(|editor, cx| editor.expand_all_hunk_diffs(&Default::default(), cx));
12575 cx.executor().run_until_parked();
12576
12577 cx.assert_state_with_diff(
12578 "
12579 ˇaaa
12580 - bbb
12581 + BBB
12582
12583 - ddd
12584 - eee
12585 + EEE
12586 fff
12587 "
12588 .unindent(),
12589 );
12590}
12591
12592#[gpui::test]
12593async fn test_edits_around_expanded_insertion_hunks(
12594 executor: BackgroundExecutor,
12595 cx: &mut gpui::TestAppContext,
12596) {
12597 init_test(cx, |_| {});
12598
12599 let mut cx = EditorTestContext::new(cx).await;
12600
12601 let diff_base = r#"
12602 use some::mod1;
12603 use some::mod2;
12604
12605 const A: u32 = 42;
12606
12607 fn main() {
12608 println!("hello");
12609
12610 println!("world");
12611 }
12612 "#
12613 .unindent();
12614 executor.run_until_parked();
12615 cx.set_state(
12616 &r#"
12617 use some::mod1;
12618 use some::mod2;
12619
12620 const A: u32 = 42;
12621 const B: u32 = 42;
12622 const C: u32 = 42;
12623 ˇ
12624
12625 fn main() {
12626 println!("hello");
12627
12628 println!("world");
12629 }
12630 "#
12631 .unindent(),
12632 );
12633
12634 cx.set_diff_base(&diff_base);
12635 executor.run_until_parked();
12636
12637 cx.update_editor(|editor, cx| {
12638 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12639 });
12640 executor.run_until_parked();
12641
12642 cx.assert_state_with_diff(
12643 r#"
12644 use some::mod1;
12645 use some::mod2;
12646
12647 const A: u32 = 42;
12648 + const B: u32 = 42;
12649 + const C: u32 = 42;
12650 + ˇ
12651
12652 fn main() {
12653 println!("hello");
12654
12655 println!("world");
12656 }
12657 "#
12658 .unindent(),
12659 );
12660
12661 cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx));
12662 executor.run_until_parked();
12663
12664 cx.assert_state_with_diff(
12665 r#"
12666 use some::mod1;
12667 use some::mod2;
12668
12669 const A: u32 = 42;
12670 + const B: u32 = 42;
12671 + const C: u32 = 42;
12672 + const D: u32 = 42;
12673 + ˇ
12674
12675 fn main() {
12676 println!("hello");
12677
12678 println!("world");
12679 }
12680 "#
12681 .unindent(),
12682 );
12683
12684 cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx));
12685 executor.run_until_parked();
12686
12687 cx.assert_state_with_diff(
12688 r#"
12689 use some::mod1;
12690 use some::mod2;
12691
12692 const A: u32 = 42;
12693 + const B: u32 = 42;
12694 + const C: u32 = 42;
12695 + const D: u32 = 42;
12696 + const E: u32 = 42;
12697 + ˇ
12698
12699 fn main() {
12700 println!("hello");
12701
12702 println!("world");
12703 }
12704 "#
12705 .unindent(),
12706 );
12707
12708 cx.update_editor(|editor, cx| {
12709 editor.delete_line(&DeleteLine, cx);
12710 });
12711 executor.run_until_parked();
12712
12713 cx.assert_state_with_diff(
12714 r#"
12715 use some::mod1;
12716 use some::mod2;
12717
12718 const A: u32 = 42;
12719 + const B: u32 = 42;
12720 + const C: u32 = 42;
12721 + const D: u32 = 42;
12722 + const E: u32 = 42;
12723 ˇ
12724 fn main() {
12725 println!("hello");
12726
12727 println!("world");
12728 }
12729 "#
12730 .unindent(),
12731 );
12732
12733 cx.update_editor(|editor, cx| {
12734 editor.move_up(&MoveUp, cx);
12735 editor.delete_line(&DeleteLine, cx);
12736 editor.move_up(&MoveUp, cx);
12737 editor.delete_line(&DeleteLine, cx);
12738 editor.move_up(&MoveUp, cx);
12739 editor.delete_line(&DeleteLine, cx);
12740 });
12741 executor.run_until_parked();
12742 cx.assert_state_with_diff(
12743 r#"
12744 use some::mod1;
12745 use some::mod2;
12746
12747 const A: u32 = 42;
12748 + const B: u32 = 42;
12749 ˇ
12750 fn main() {
12751 println!("hello");
12752
12753 println!("world");
12754 }
12755 "#
12756 .unindent(),
12757 );
12758
12759 cx.update_editor(|editor, cx| {
12760 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, cx);
12761 editor.delete_line(&DeleteLine, cx);
12762 });
12763 executor.run_until_parked();
12764 cx.assert_state_with_diff(
12765 r#"
12766 use some::mod1;
12767 - use some::mod2;
12768 -
12769 - const A: u32 = 42;
12770 ˇ
12771 fn main() {
12772 println!("hello");
12773
12774 println!("world");
12775 }
12776 "#
12777 .unindent(),
12778 );
12779}
12780
12781#[gpui::test]
12782async fn test_edits_around_expanded_deletion_hunks(
12783 executor: BackgroundExecutor,
12784 cx: &mut gpui::TestAppContext,
12785) {
12786 init_test(cx, |_| {});
12787
12788 let mut cx = EditorTestContext::new(cx).await;
12789
12790 let diff_base = r#"
12791 use some::mod1;
12792 use some::mod2;
12793
12794 const A: u32 = 42;
12795 const B: u32 = 42;
12796 const C: u32 = 42;
12797
12798
12799 fn main() {
12800 println!("hello");
12801
12802 println!("world");
12803 }
12804 "#
12805 .unindent();
12806 executor.run_until_parked();
12807 cx.set_state(
12808 &r#"
12809 use some::mod1;
12810 use some::mod2;
12811
12812 ˇconst B: u32 = 42;
12813 const C: u32 = 42;
12814
12815
12816 fn main() {
12817 println!("hello");
12818
12819 println!("world");
12820 }
12821 "#
12822 .unindent(),
12823 );
12824
12825 cx.set_diff_base(&diff_base);
12826 executor.run_until_parked();
12827
12828 cx.update_editor(|editor, cx| {
12829 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12830 });
12831 executor.run_until_parked();
12832
12833 cx.assert_state_with_diff(
12834 r#"
12835 use some::mod1;
12836 use some::mod2;
12837
12838 - const A: u32 = 42;
12839 ˇconst B: u32 = 42;
12840 const C: u32 = 42;
12841
12842
12843 fn main() {
12844 println!("hello");
12845
12846 println!("world");
12847 }
12848 "#
12849 .unindent(),
12850 );
12851
12852 cx.update_editor(|editor, cx| {
12853 editor.delete_line(&DeleteLine, cx);
12854 });
12855 executor.run_until_parked();
12856 cx.assert_state_with_diff(
12857 r#"
12858 use some::mod1;
12859 use some::mod2;
12860
12861 - const A: u32 = 42;
12862 - const B: u32 = 42;
12863 ˇconst C: u32 = 42;
12864
12865
12866 fn main() {
12867 println!("hello");
12868
12869 println!("world");
12870 }
12871 "#
12872 .unindent(),
12873 );
12874
12875 cx.update_editor(|editor, cx| {
12876 editor.delete_line(&DeleteLine, cx);
12877 });
12878 executor.run_until_parked();
12879 cx.assert_state_with_diff(
12880 r#"
12881 use some::mod1;
12882 use some::mod2;
12883
12884 - const A: u32 = 42;
12885 - const B: u32 = 42;
12886 - const C: u32 = 42;
12887 ˇ
12888
12889 fn main() {
12890 println!("hello");
12891
12892 println!("world");
12893 }
12894 "#
12895 .unindent(),
12896 );
12897
12898 cx.update_editor(|editor, cx| {
12899 editor.handle_input("replacement", cx);
12900 });
12901 executor.run_until_parked();
12902 cx.assert_state_with_diff(
12903 r#"
12904 use some::mod1;
12905 use some::mod2;
12906
12907 - const A: u32 = 42;
12908 - const B: u32 = 42;
12909 - const C: u32 = 42;
12910 -
12911 + replacementˇ
12912
12913 fn main() {
12914 println!("hello");
12915
12916 println!("world");
12917 }
12918 "#
12919 .unindent(),
12920 );
12921}
12922
12923#[gpui::test]
12924async fn test_edit_after_expanded_modification_hunk(
12925 executor: BackgroundExecutor,
12926 cx: &mut gpui::TestAppContext,
12927) {
12928 init_test(cx, |_| {});
12929
12930 let mut cx = EditorTestContext::new(cx).await;
12931
12932 let diff_base = r#"
12933 use some::mod1;
12934 use some::mod2;
12935
12936 const A: u32 = 42;
12937 const B: u32 = 42;
12938 const C: u32 = 42;
12939 const D: u32 = 42;
12940
12941
12942 fn main() {
12943 println!("hello");
12944
12945 println!("world");
12946 }"#
12947 .unindent();
12948
12949 cx.set_state(
12950 &r#"
12951 use some::mod1;
12952 use some::mod2;
12953
12954 const A: u32 = 42;
12955 const B: u32 = 42;
12956 const C: u32 = 43ˇ
12957 const D: u32 = 42;
12958
12959
12960 fn main() {
12961 println!("hello");
12962
12963 println!("world");
12964 }"#
12965 .unindent(),
12966 );
12967
12968 cx.set_diff_base(&diff_base);
12969 executor.run_until_parked();
12970 cx.update_editor(|editor, cx| {
12971 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12972 });
12973 executor.run_until_parked();
12974
12975 cx.assert_state_with_diff(
12976 r#"
12977 use some::mod1;
12978 use some::mod2;
12979
12980 const A: u32 = 42;
12981 const B: u32 = 42;
12982 - const C: u32 = 42;
12983 + const C: u32 = 43ˇ
12984 const D: u32 = 42;
12985
12986
12987 fn main() {
12988 println!("hello");
12989
12990 println!("world");
12991 }"#
12992 .unindent(),
12993 );
12994
12995 cx.update_editor(|editor, cx| {
12996 editor.handle_input("\nnew_line\n", cx);
12997 });
12998 executor.run_until_parked();
12999
13000 cx.assert_state_with_diff(
13001 r#"
13002 use some::mod1;
13003 use some::mod2;
13004
13005 const A: u32 = 42;
13006 const B: u32 = 42;
13007 - const C: u32 = 42;
13008 + const C: u32 = 43
13009 + new_line
13010 + ˇ
13011 const D: u32 = 42;
13012
13013
13014 fn main() {
13015 println!("hello");
13016
13017 println!("world");
13018 }"#
13019 .unindent(),
13020 );
13021}
13022
13023async fn setup_indent_guides_editor(
13024 text: &str,
13025 cx: &mut gpui::TestAppContext,
13026) -> (BufferId, EditorTestContext) {
13027 init_test(cx, |_| {});
13028
13029 let mut cx = EditorTestContext::new(cx).await;
13030
13031 let buffer_id = cx.update_editor(|editor, cx| {
13032 editor.set_text(text, cx);
13033 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
13034
13035 buffer_ids[0]
13036 });
13037
13038 (buffer_id, cx)
13039}
13040
13041fn assert_indent_guides(
13042 range: Range<u32>,
13043 expected: Vec<IndentGuide>,
13044 active_indices: Option<Vec<usize>>,
13045 cx: &mut EditorTestContext,
13046) {
13047 let indent_guides = cx.update_editor(|editor, cx| {
13048 let snapshot = editor.snapshot(cx).display_snapshot;
13049 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
13050 editor,
13051 MultiBufferRow(range.start)..MultiBufferRow(range.end),
13052 true,
13053 &snapshot,
13054 cx,
13055 );
13056
13057 indent_guides.sort_by(|a, b| {
13058 a.depth.cmp(&b.depth).then(
13059 a.start_row
13060 .cmp(&b.start_row)
13061 .then(a.end_row.cmp(&b.end_row)),
13062 )
13063 });
13064 indent_guides
13065 });
13066
13067 if let Some(expected) = active_indices {
13068 let active_indices = cx.update_editor(|editor, cx| {
13069 let snapshot = editor.snapshot(cx).display_snapshot;
13070 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, cx)
13071 });
13072
13073 assert_eq!(
13074 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
13075 expected,
13076 "Active indent guide indices do not match"
13077 );
13078 }
13079
13080 let expected: Vec<_> = expected
13081 .into_iter()
13082 .map(|guide| MultiBufferIndentGuide {
13083 multibuffer_row_range: MultiBufferRow(guide.start_row)..MultiBufferRow(guide.end_row),
13084 buffer: guide,
13085 })
13086 .collect();
13087
13088 assert_eq!(indent_guides, expected, "Indent guides do not match");
13089}
13090
13091fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
13092 IndentGuide {
13093 buffer_id,
13094 start_row,
13095 end_row,
13096 depth,
13097 tab_size: 4,
13098 settings: IndentGuideSettings {
13099 enabled: true,
13100 line_width: 1,
13101 active_line_width: 1,
13102 ..Default::default()
13103 },
13104 }
13105}
13106
13107#[gpui::test]
13108async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
13109 let (buffer_id, mut cx) = setup_indent_guides_editor(
13110 &"
13111 fn main() {
13112 let a = 1;
13113 }"
13114 .unindent(),
13115 cx,
13116 )
13117 .await;
13118
13119 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
13120}
13121
13122#[gpui::test]
13123async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) {
13124 let (buffer_id, mut cx) = setup_indent_guides_editor(
13125 &"
13126 fn main() {
13127 let a = 1;
13128 let b = 2;
13129 }"
13130 .unindent(),
13131 cx,
13132 )
13133 .await;
13134
13135 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
13136}
13137
13138#[gpui::test]
13139async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) {
13140 let (buffer_id, mut cx) = setup_indent_guides_editor(
13141 &"
13142 fn main() {
13143 let a = 1;
13144 if a == 3 {
13145 let b = 2;
13146 } else {
13147 let c = 3;
13148 }
13149 }"
13150 .unindent(),
13151 cx,
13152 )
13153 .await;
13154
13155 assert_indent_guides(
13156 0..8,
13157 vec![
13158 indent_guide(buffer_id, 1, 6, 0),
13159 indent_guide(buffer_id, 3, 3, 1),
13160 indent_guide(buffer_id, 5, 5, 1),
13161 ],
13162 None,
13163 &mut cx,
13164 );
13165}
13166
13167#[gpui::test]
13168async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) {
13169 let (buffer_id, mut cx) = setup_indent_guides_editor(
13170 &"
13171 fn main() {
13172 let a = 1;
13173 let b = 2;
13174 let c = 3;
13175 }"
13176 .unindent(),
13177 cx,
13178 )
13179 .await;
13180
13181 assert_indent_guides(
13182 0..5,
13183 vec![
13184 indent_guide(buffer_id, 1, 3, 0),
13185 indent_guide(buffer_id, 2, 2, 1),
13186 ],
13187 None,
13188 &mut cx,
13189 );
13190}
13191
13192#[gpui::test]
13193async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext) {
13194 let (buffer_id, mut cx) = setup_indent_guides_editor(
13195 &"
13196 fn main() {
13197 let a = 1;
13198
13199 let c = 3;
13200 }"
13201 .unindent(),
13202 cx,
13203 )
13204 .await;
13205
13206 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
13207}
13208
13209#[gpui::test]
13210async fn test_indent_guide_complex(cx: &mut gpui::TestAppContext) {
13211 let (buffer_id, mut cx) = setup_indent_guides_editor(
13212 &"
13213 fn main() {
13214 let a = 1;
13215
13216 let c = 3;
13217
13218 if a == 3 {
13219 let b = 2;
13220 } else {
13221 let c = 3;
13222 }
13223 }"
13224 .unindent(),
13225 cx,
13226 )
13227 .await;
13228
13229 assert_indent_guides(
13230 0..11,
13231 vec![
13232 indent_guide(buffer_id, 1, 9, 0),
13233 indent_guide(buffer_id, 6, 6, 1),
13234 indent_guide(buffer_id, 8, 8, 1),
13235 ],
13236 None,
13237 &mut cx,
13238 );
13239}
13240
13241#[gpui::test]
13242async fn test_indent_guide_starts_off_screen(cx: &mut gpui::TestAppContext) {
13243 let (buffer_id, mut cx) = setup_indent_guides_editor(
13244 &"
13245 fn main() {
13246 let a = 1;
13247
13248 let c = 3;
13249
13250 if a == 3 {
13251 let b = 2;
13252 } else {
13253 let c = 3;
13254 }
13255 }"
13256 .unindent(),
13257 cx,
13258 )
13259 .await;
13260
13261 assert_indent_guides(
13262 1..11,
13263 vec![
13264 indent_guide(buffer_id, 1, 9, 0),
13265 indent_guide(buffer_id, 6, 6, 1),
13266 indent_guide(buffer_id, 8, 8, 1),
13267 ],
13268 None,
13269 &mut cx,
13270 );
13271}
13272
13273#[gpui::test]
13274async fn test_indent_guide_ends_off_screen(cx: &mut gpui::TestAppContext) {
13275 let (buffer_id, mut cx) = setup_indent_guides_editor(
13276 &"
13277 fn main() {
13278 let a = 1;
13279
13280 let c = 3;
13281
13282 if a == 3 {
13283 let b = 2;
13284 } else {
13285 let c = 3;
13286 }
13287 }"
13288 .unindent(),
13289 cx,
13290 )
13291 .await;
13292
13293 assert_indent_guides(
13294 1..10,
13295 vec![
13296 indent_guide(buffer_id, 1, 9, 0),
13297 indent_guide(buffer_id, 6, 6, 1),
13298 indent_guide(buffer_id, 8, 8, 1),
13299 ],
13300 None,
13301 &mut cx,
13302 );
13303}
13304
13305#[gpui::test]
13306async fn test_indent_guide_without_brackets(cx: &mut gpui::TestAppContext) {
13307 let (buffer_id, mut cx) = setup_indent_guides_editor(
13308 &"
13309 block1
13310 block2
13311 block3
13312 block4
13313 block2
13314 block1
13315 block1"
13316 .unindent(),
13317 cx,
13318 )
13319 .await;
13320
13321 assert_indent_guides(
13322 1..10,
13323 vec![
13324 indent_guide(buffer_id, 1, 4, 0),
13325 indent_guide(buffer_id, 2, 3, 1),
13326 indent_guide(buffer_id, 3, 3, 2),
13327 ],
13328 None,
13329 &mut cx,
13330 );
13331}
13332
13333#[gpui::test]
13334async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext) {
13335 let (buffer_id, mut cx) = setup_indent_guides_editor(
13336 &"
13337 block1
13338 block2
13339 block3
13340
13341 block1
13342 block1"
13343 .unindent(),
13344 cx,
13345 )
13346 .await;
13347
13348 assert_indent_guides(
13349 0..6,
13350 vec![
13351 indent_guide(buffer_id, 1, 2, 0),
13352 indent_guide(buffer_id, 2, 2, 1),
13353 ],
13354 None,
13355 &mut cx,
13356 );
13357}
13358
13359#[gpui::test]
13360async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext) {
13361 let (buffer_id, mut cx) = setup_indent_guides_editor(
13362 &"
13363 block1
13364
13365
13366
13367 block2
13368 "
13369 .unindent(),
13370 cx,
13371 )
13372 .await;
13373
13374 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
13375}
13376
13377#[gpui::test]
13378async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) {
13379 let (buffer_id, mut cx) = setup_indent_guides_editor(
13380 &"
13381 def a:
13382 \tb = 3
13383 \tif True:
13384 \t\tc = 4
13385 \t\td = 5
13386 \tprint(b)
13387 "
13388 .unindent(),
13389 cx,
13390 )
13391 .await;
13392
13393 assert_indent_guides(
13394 0..6,
13395 vec![
13396 indent_guide(buffer_id, 1, 6, 0),
13397 indent_guide(buffer_id, 3, 4, 1),
13398 ],
13399 None,
13400 &mut cx,
13401 );
13402}
13403
13404#[gpui::test]
13405async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
13406 let (buffer_id, mut cx) = setup_indent_guides_editor(
13407 &"
13408 fn main() {
13409 let a = 1;
13410 }"
13411 .unindent(),
13412 cx,
13413 )
13414 .await;
13415
13416 cx.update_editor(|editor, cx| {
13417 editor.change_selections(None, cx, |s| {
13418 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13419 });
13420 });
13421
13422 assert_indent_guides(
13423 0..3,
13424 vec![indent_guide(buffer_id, 1, 1, 0)],
13425 Some(vec![0]),
13426 &mut cx,
13427 );
13428}
13429
13430#[gpui::test]
13431async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppContext) {
13432 let (buffer_id, mut cx) = setup_indent_guides_editor(
13433 &"
13434 fn main() {
13435 if 1 == 2 {
13436 let a = 1;
13437 }
13438 }"
13439 .unindent(),
13440 cx,
13441 )
13442 .await;
13443
13444 cx.update_editor(|editor, cx| {
13445 editor.change_selections(None, cx, |s| {
13446 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13447 });
13448 });
13449
13450 assert_indent_guides(
13451 0..4,
13452 vec![
13453 indent_guide(buffer_id, 1, 3, 0),
13454 indent_guide(buffer_id, 2, 2, 1),
13455 ],
13456 Some(vec![1]),
13457 &mut cx,
13458 );
13459
13460 cx.update_editor(|editor, cx| {
13461 editor.change_selections(None, cx, |s| {
13462 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13463 });
13464 });
13465
13466 assert_indent_guides(
13467 0..4,
13468 vec![
13469 indent_guide(buffer_id, 1, 3, 0),
13470 indent_guide(buffer_id, 2, 2, 1),
13471 ],
13472 Some(vec![1]),
13473 &mut cx,
13474 );
13475
13476 cx.update_editor(|editor, cx| {
13477 editor.change_selections(None, cx, |s| {
13478 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
13479 });
13480 });
13481
13482 assert_indent_guides(
13483 0..4,
13484 vec![
13485 indent_guide(buffer_id, 1, 3, 0),
13486 indent_guide(buffer_id, 2, 2, 1),
13487 ],
13488 Some(vec![0]),
13489 &mut cx,
13490 );
13491}
13492
13493#[gpui::test]
13494async fn test_active_indent_guide_empty_line(cx: &mut gpui::TestAppContext) {
13495 let (buffer_id, mut cx) = setup_indent_guides_editor(
13496 &"
13497 fn main() {
13498 let a = 1;
13499
13500 let b = 2;
13501 }"
13502 .unindent(),
13503 cx,
13504 )
13505 .await;
13506
13507 cx.update_editor(|editor, cx| {
13508 editor.change_selections(None, cx, |s| {
13509 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13510 });
13511 });
13512
13513 assert_indent_guides(
13514 0..5,
13515 vec![indent_guide(buffer_id, 1, 3, 0)],
13516 Some(vec![0]),
13517 &mut cx,
13518 );
13519}
13520
13521#[gpui::test]
13522async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppContext) {
13523 let (buffer_id, mut cx) = setup_indent_guides_editor(
13524 &"
13525 def m:
13526 a = 1
13527 pass"
13528 .unindent(),
13529 cx,
13530 )
13531 .await;
13532
13533 cx.update_editor(|editor, cx| {
13534 editor.change_selections(None, cx, |s| {
13535 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13536 });
13537 });
13538
13539 assert_indent_guides(
13540 0..3,
13541 vec![indent_guide(buffer_id, 1, 2, 0)],
13542 Some(vec![0]),
13543 &mut cx,
13544 );
13545}
13546
13547#[gpui::test]
13548fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
13549 init_test(cx, |_| {});
13550
13551 let editor = cx.add_window(|cx| {
13552 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
13553 build_editor(buffer, cx)
13554 });
13555
13556 let render_args = Arc::new(Mutex::new(None));
13557 let snapshot = editor
13558 .update(cx, |editor, cx| {
13559 let snapshot = editor.buffer().read(cx).snapshot(cx);
13560 let range =
13561 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
13562
13563 struct RenderArgs {
13564 row: MultiBufferRow,
13565 folded: bool,
13566 callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
13567 }
13568
13569 let crease = Crease::inline(
13570 range,
13571 FoldPlaceholder::test(),
13572 {
13573 let toggle_callback = render_args.clone();
13574 move |row, folded, callback, _cx| {
13575 *toggle_callback.lock() = Some(RenderArgs {
13576 row,
13577 folded,
13578 callback,
13579 });
13580 div()
13581 }
13582 },
13583 |_row, _folded, _cx| div(),
13584 );
13585
13586 editor.insert_creases(Some(crease), cx);
13587 let snapshot = editor.snapshot(cx);
13588 let _div =
13589 snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
13590 snapshot
13591 })
13592 .unwrap();
13593
13594 let render_args = render_args.lock().take().unwrap();
13595 assert_eq!(render_args.row, MultiBufferRow(1));
13596 assert!(!render_args.folded);
13597 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13598
13599 cx.update_window(*editor, |_, cx| (render_args.callback)(true, cx))
13600 .unwrap();
13601 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13602 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
13603
13604 cx.update_window(*editor, |_, cx| (render_args.callback)(false, cx))
13605 .unwrap();
13606 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13607 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13608}
13609
13610#[gpui::test]
13611async fn test_input_text(cx: &mut gpui::TestAppContext) {
13612 init_test(cx, |_| {});
13613 let mut cx = EditorTestContext::new(cx).await;
13614
13615 cx.set_state(
13616 &r#"ˇone
13617 two
13618
13619 three
13620 fourˇ
13621 five
13622
13623 siˇx"#
13624 .unindent(),
13625 );
13626
13627 cx.dispatch_action(HandleInput(String::new()));
13628 cx.assert_editor_state(
13629 &r#"ˇone
13630 two
13631
13632 three
13633 fourˇ
13634 five
13635
13636 siˇx"#
13637 .unindent(),
13638 );
13639
13640 cx.dispatch_action(HandleInput("AAAA".to_string()));
13641 cx.assert_editor_state(
13642 &r#"AAAAˇone
13643 two
13644
13645 three
13646 fourAAAAˇ
13647 five
13648
13649 siAAAAˇx"#
13650 .unindent(),
13651 );
13652}
13653
13654#[gpui::test]
13655async fn test_scroll_cursor_center_top_bottom(cx: &mut gpui::TestAppContext) {
13656 init_test(cx, |_| {});
13657
13658 let mut cx = EditorTestContext::new(cx).await;
13659 cx.set_state(
13660 r#"let foo = 1;
13661let foo = 2;
13662let foo = 3;
13663let fooˇ = 4;
13664let foo = 5;
13665let foo = 6;
13666let foo = 7;
13667let foo = 8;
13668let foo = 9;
13669let foo = 10;
13670let foo = 11;
13671let foo = 12;
13672let foo = 13;
13673let foo = 14;
13674let foo = 15;"#,
13675 );
13676
13677 cx.update_editor(|e, cx| {
13678 assert_eq!(
13679 e.next_scroll_position,
13680 NextScrollCursorCenterTopBottom::Center,
13681 "Default next scroll direction is center",
13682 );
13683
13684 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13685 assert_eq!(
13686 e.next_scroll_position,
13687 NextScrollCursorCenterTopBottom::Top,
13688 "After center, next scroll direction should be top",
13689 );
13690
13691 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13692 assert_eq!(
13693 e.next_scroll_position,
13694 NextScrollCursorCenterTopBottom::Bottom,
13695 "After top, next scroll direction should be bottom",
13696 );
13697
13698 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13699 assert_eq!(
13700 e.next_scroll_position,
13701 NextScrollCursorCenterTopBottom::Center,
13702 "After bottom, scrolling should start over",
13703 );
13704
13705 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13706 assert_eq!(
13707 e.next_scroll_position,
13708 NextScrollCursorCenterTopBottom::Top,
13709 "Scrolling continues if retriggered fast enough"
13710 );
13711 });
13712
13713 cx.executor()
13714 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
13715 cx.executor().run_until_parked();
13716 cx.update_editor(|e, _| {
13717 assert_eq!(
13718 e.next_scroll_position,
13719 NextScrollCursorCenterTopBottom::Center,
13720 "If scrolling is not triggered fast enough, it should reset"
13721 );
13722 });
13723}
13724
13725#[gpui::test]
13726async fn test_goto_definition_with_find_all_references_fallback(cx: &mut gpui::TestAppContext) {
13727 init_test(cx, |_| {});
13728 let mut cx = EditorLspTestContext::new_rust(
13729 lsp::ServerCapabilities {
13730 definition_provider: Some(lsp::OneOf::Left(true)),
13731 references_provider: Some(lsp::OneOf::Left(true)),
13732 ..lsp::ServerCapabilities::default()
13733 },
13734 cx,
13735 )
13736 .await;
13737
13738 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
13739 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
13740 move |params, _| async move {
13741 if empty_go_to_definition {
13742 Ok(None)
13743 } else {
13744 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
13745 uri: params.text_document_position_params.text_document.uri,
13746 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
13747 })))
13748 }
13749 },
13750 );
13751 let references =
13752 cx.lsp
13753 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
13754 Ok(Some(vec![lsp::Location {
13755 uri: params.text_document_position.text_document.uri,
13756 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
13757 }]))
13758 });
13759 (go_to_definition, references)
13760 };
13761
13762 cx.set_state(
13763 &r#"fn one() {
13764 let mut a = ˇtwo();
13765 }
13766
13767 fn two() {}"#
13768 .unindent(),
13769 );
13770 set_up_lsp_handlers(false, &mut cx);
13771 let navigated = cx
13772 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13773 .await
13774 .expect("Failed to navigate to definition");
13775 assert_eq!(
13776 navigated,
13777 Navigated::Yes,
13778 "Should have navigated to definition from the GetDefinition response"
13779 );
13780 cx.assert_editor_state(
13781 &r#"fn one() {
13782 let mut a = two();
13783 }
13784
13785 fn «twoˇ»() {}"#
13786 .unindent(),
13787 );
13788
13789 let editors = cx.update_workspace(|workspace, cx| {
13790 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13791 });
13792 cx.update_editor(|_, test_editor_cx| {
13793 assert_eq!(
13794 editors.len(),
13795 1,
13796 "Initially, only one, test, editor should be open in the workspace"
13797 );
13798 assert_eq!(
13799 test_editor_cx.view(),
13800 editors.last().expect("Asserted len is 1")
13801 );
13802 });
13803
13804 set_up_lsp_handlers(true, &mut cx);
13805 let navigated = cx
13806 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13807 .await
13808 .expect("Failed to navigate to lookup references");
13809 assert_eq!(
13810 navigated,
13811 Navigated::Yes,
13812 "Should have navigated to references as a fallback after empty GoToDefinition response"
13813 );
13814 // We should not change the selections in the existing file,
13815 // if opening another milti buffer with the references
13816 cx.assert_editor_state(
13817 &r#"fn one() {
13818 let mut a = two();
13819 }
13820
13821 fn «twoˇ»() {}"#
13822 .unindent(),
13823 );
13824 let editors = cx.update_workspace(|workspace, cx| {
13825 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13826 });
13827 cx.update_editor(|_, test_editor_cx| {
13828 assert_eq!(
13829 editors.len(),
13830 2,
13831 "After falling back to references search, we open a new editor with the results"
13832 );
13833 let references_fallback_text = editors
13834 .into_iter()
13835 .find(|new_editor| new_editor != test_editor_cx.view())
13836 .expect("Should have one non-test editor now")
13837 .read(test_editor_cx)
13838 .text(test_editor_cx);
13839 assert_eq!(
13840 references_fallback_text, "fn one() {\n let mut a = two();\n}",
13841 "Should use the range from the references response and not the GoToDefinition one"
13842 );
13843 });
13844}
13845
13846#[gpui::test]
13847async fn test_find_enclosing_node_with_task(cx: &mut gpui::TestAppContext) {
13848 init_test(cx, |_| {});
13849
13850 let language = Arc::new(Language::new(
13851 LanguageConfig::default(),
13852 Some(tree_sitter_rust::LANGUAGE.into()),
13853 ));
13854
13855 let text = r#"
13856 #[cfg(test)]
13857 mod tests() {
13858 #[test]
13859 fn runnable_1() {
13860 let a = 1;
13861 }
13862
13863 #[test]
13864 fn runnable_2() {
13865 let a = 1;
13866 let b = 2;
13867 }
13868 }
13869 "#
13870 .unindent();
13871
13872 let fs = FakeFs::new(cx.executor());
13873 fs.insert_file("/file.rs", Default::default()).await;
13874
13875 let project = Project::test(fs, ["/a".as_ref()], cx).await;
13876 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
13877 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
13878 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
13879 let multi_buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx));
13880
13881 let editor = cx.new_view(|cx| {
13882 Editor::new(
13883 EditorMode::Full,
13884 multi_buffer,
13885 Some(project.clone()),
13886 true,
13887 cx,
13888 )
13889 });
13890
13891 editor.update(cx, |editor, cx| {
13892 editor.tasks.insert(
13893 (buffer.read(cx).remote_id(), 3),
13894 RunnableTasks {
13895 templates: vec![],
13896 offset: MultiBufferOffset(43),
13897 column: 0,
13898 extra_variables: HashMap::default(),
13899 context_range: BufferOffset(43)..BufferOffset(85),
13900 },
13901 );
13902 editor.tasks.insert(
13903 (buffer.read(cx).remote_id(), 8),
13904 RunnableTasks {
13905 templates: vec![],
13906 offset: MultiBufferOffset(86),
13907 column: 0,
13908 extra_variables: HashMap::default(),
13909 context_range: BufferOffset(86)..BufferOffset(191),
13910 },
13911 );
13912
13913 // Test finding task when cursor is inside function body
13914 editor.change_selections(None, cx, |s| {
13915 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
13916 });
13917 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
13918 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
13919
13920 // Test finding task when cursor is on function name
13921 editor.change_selections(None, cx, |s| {
13922 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
13923 });
13924 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
13925 assert_eq!(row, 8, "Should find task when cursor is on function name");
13926 });
13927}
13928
13929#[gpui::test]
13930async fn test_multi_buffer_folding(cx: &mut gpui::TestAppContext) {
13931 init_test(cx, |_| {});
13932
13933 let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
13934 let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
13935 let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
13936
13937 let fs = FakeFs::new(cx.executor());
13938 fs.insert_tree(
13939 "/a",
13940 json!({
13941 "first.rs": sample_text_1,
13942 "second.rs": sample_text_2,
13943 "third.rs": sample_text_3,
13944 }),
13945 )
13946 .await;
13947 let project = Project::test(fs, ["/a".as_ref()], cx).await;
13948 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
13949 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
13950 let worktree = project.update(cx, |project, cx| {
13951 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
13952 assert_eq!(worktrees.len(), 1);
13953 worktrees.pop().unwrap()
13954 });
13955 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
13956
13957 let buffer_1 = project
13958 .update(cx, |project, cx| {
13959 project.open_buffer((worktree_id, "first.rs"), cx)
13960 })
13961 .await
13962 .unwrap();
13963 let buffer_2 = project
13964 .update(cx, |project, cx| {
13965 project.open_buffer((worktree_id, "second.rs"), cx)
13966 })
13967 .await
13968 .unwrap();
13969 let buffer_3 = project
13970 .update(cx, |project, cx| {
13971 project.open_buffer((worktree_id, "third.rs"), cx)
13972 })
13973 .await
13974 .unwrap();
13975
13976 let multi_buffer = cx.new_model(|cx| {
13977 let mut multi_buffer = MultiBuffer::new(ReadWrite);
13978 multi_buffer.push_excerpts(
13979 buffer_1.clone(),
13980 [
13981 ExcerptRange {
13982 context: Point::new(0, 0)..Point::new(3, 0),
13983 primary: None,
13984 },
13985 ExcerptRange {
13986 context: Point::new(5, 0)..Point::new(7, 0),
13987 primary: None,
13988 },
13989 ExcerptRange {
13990 context: Point::new(9, 0)..Point::new(10, 4),
13991 primary: None,
13992 },
13993 ],
13994 cx,
13995 );
13996 multi_buffer.push_excerpts(
13997 buffer_2.clone(),
13998 [
13999 ExcerptRange {
14000 context: Point::new(0, 0)..Point::new(3, 0),
14001 primary: None,
14002 },
14003 ExcerptRange {
14004 context: Point::new(5, 0)..Point::new(7, 0),
14005 primary: None,
14006 },
14007 ExcerptRange {
14008 context: Point::new(9, 0)..Point::new(10, 4),
14009 primary: None,
14010 },
14011 ],
14012 cx,
14013 );
14014 multi_buffer.push_excerpts(
14015 buffer_3.clone(),
14016 [
14017 ExcerptRange {
14018 context: Point::new(0, 0)..Point::new(3, 0),
14019 primary: None,
14020 },
14021 ExcerptRange {
14022 context: Point::new(5, 0)..Point::new(7, 0),
14023 primary: None,
14024 },
14025 ExcerptRange {
14026 context: Point::new(9, 0)..Point::new(10, 4),
14027 primary: None,
14028 },
14029 ],
14030 cx,
14031 );
14032 multi_buffer
14033 });
14034 let multi_buffer_editor = cx.new_view(|cx| {
14035 Editor::new(
14036 EditorMode::Full,
14037 multi_buffer,
14038 Some(project.clone()),
14039 true,
14040 cx,
14041 )
14042 });
14043
14044 let full_text = "\n\n\naaaa\nbbbb\ncccc\n\n\n\nffff\ngggg\n\n\n\njjjj\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n";
14045 assert_eq!(
14046 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14047 full_text,
14048 );
14049
14050 multi_buffer_editor.update(cx, |editor, cx| {
14051 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
14052 });
14053 assert_eq!(
14054 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14055 "\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n",
14056 "After folding the first buffer, its text should not be displayed"
14057 );
14058
14059 multi_buffer_editor.update(cx, |editor, cx| {
14060 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
14061 });
14062 assert_eq!(
14063 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14064 "\n\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n",
14065 "After folding the second buffer, its text should not be displayed"
14066 );
14067
14068 multi_buffer_editor.update(cx, |editor, cx| {
14069 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
14070 });
14071 assert_eq!(
14072 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14073 "\n\n\n\n\n",
14074 "After folding the third buffer, its text should not be displayed"
14075 );
14076
14077 // Emulate selection inside the fold logic, that should work
14078 multi_buffer_editor.update(cx, |editor, cx| {
14079 editor.snapshot(cx).next_line_boundary(Point::new(0, 4));
14080 });
14081
14082 multi_buffer_editor.update(cx, |editor, cx| {
14083 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
14084 });
14085 assert_eq!(
14086 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14087 "\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n",
14088 "After unfolding the second buffer, its text should be displayed"
14089 );
14090
14091 multi_buffer_editor.update(cx, |editor, cx| {
14092 editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
14093 });
14094 assert_eq!(
14095 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14096 "\n\n\naaaa\nbbbb\ncccc\n\n\n\nffff\ngggg\n\n\n\njjjj\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n",
14097 "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
14098 );
14099
14100 multi_buffer_editor.update(cx, |editor, cx| {
14101 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
14102 });
14103 assert_eq!(
14104 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14105 full_text,
14106 "After unfolding the all buffers, all original text should be displayed"
14107 );
14108}
14109
14110#[gpui::test]
14111async fn test_multi_buffer_single_excerpts_folding(cx: &mut gpui::TestAppContext) {
14112 init_test(cx, |_| {});
14113
14114 let sample_text_1 = "1111\n2222\n3333".to_string();
14115 let sample_text_2 = "4444\n5555\n6666".to_string();
14116 let sample_text_3 = "7777\n8888\n9999".to_string();
14117
14118 let fs = FakeFs::new(cx.executor());
14119 fs.insert_tree(
14120 "/a",
14121 json!({
14122 "first.rs": sample_text_1,
14123 "second.rs": sample_text_2,
14124 "third.rs": sample_text_3,
14125 }),
14126 )
14127 .await;
14128 let project = Project::test(fs, ["/a".as_ref()], cx).await;
14129 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
14130 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14131 let worktree = project.update(cx, |project, cx| {
14132 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
14133 assert_eq!(worktrees.len(), 1);
14134 worktrees.pop().unwrap()
14135 });
14136 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
14137
14138 let buffer_1 = project
14139 .update(cx, |project, cx| {
14140 project.open_buffer((worktree_id, "first.rs"), cx)
14141 })
14142 .await
14143 .unwrap();
14144 let buffer_2 = project
14145 .update(cx, |project, cx| {
14146 project.open_buffer((worktree_id, "second.rs"), cx)
14147 })
14148 .await
14149 .unwrap();
14150 let buffer_3 = project
14151 .update(cx, |project, cx| {
14152 project.open_buffer((worktree_id, "third.rs"), cx)
14153 })
14154 .await
14155 .unwrap();
14156
14157 let multi_buffer = cx.new_model(|cx| {
14158 let mut multi_buffer = MultiBuffer::new(ReadWrite);
14159 multi_buffer.push_excerpts(
14160 buffer_1.clone(),
14161 [ExcerptRange {
14162 context: Point::new(0, 0)..Point::new(3, 0),
14163 primary: None,
14164 }],
14165 cx,
14166 );
14167 multi_buffer.push_excerpts(
14168 buffer_2.clone(),
14169 [ExcerptRange {
14170 context: Point::new(0, 0)..Point::new(3, 0),
14171 primary: None,
14172 }],
14173 cx,
14174 );
14175 multi_buffer.push_excerpts(
14176 buffer_3.clone(),
14177 [ExcerptRange {
14178 context: Point::new(0, 0)..Point::new(3, 0),
14179 primary: None,
14180 }],
14181 cx,
14182 );
14183 multi_buffer
14184 });
14185
14186 let multi_buffer_editor = cx.new_view(|cx| {
14187 Editor::new(
14188 EditorMode::Full,
14189 multi_buffer,
14190 Some(project.clone()),
14191 true,
14192 cx,
14193 )
14194 });
14195
14196 let full_text = "\n\n\n1111\n2222\n3333\n\n\n\n\n4444\n5555\n6666\n\n\n\n\n7777\n8888\n9999\n";
14197 assert_eq!(
14198 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14199 full_text,
14200 );
14201
14202 multi_buffer_editor.update(cx, |editor, cx| {
14203 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
14204 });
14205 assert_eq!(
14206 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14207 "\n\n\n\n\n4444\n5555\n6666\n\n\n\n\n7777\n8888\n9999\n",
14208 "After folding the first buffer, its text should not be displayed"
14209 );
14210
14211 multi_buffer_editor.update(cx, |editor, cx| {
14212 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
14213 });
14214
14215 assert_eq!(
14216 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14217 "\n\n\n\n\n\n\n7777\n8888\n9999\n",
14218 "After folding the second buffer, its text should not be displayed"
14219 );
14220
14221 multi_buffer_editor.update(cx, |editor, cx| {
14222 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
14223 });
14224 assert_eq!(
14225 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14226 "\n\n\n\n\n",
14227 "After folding the third buffer, its text should not be displayed"
14228 );
14229
14230 multi_buffer_editor.update(cx, |editor, cx| {
14231 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
14232 });
14233 assert_eq!(
14234 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14235 "\n\n\n\n\n4444\n5555\n6666\n\n\n",
14236 "After unfolding the second buffer, its text should be displayed"
14237 );
14238
14239 multi_buffer_editor.update(cx, |editor, cx| {
14240 editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
14241 });
14242 assert_eq!(
14243 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14244 "\n\n\n1111\n2222\n3333\n\n\n\n\n4444\n5555\n6666\n\n\n",
14245 "After unfolding the first buffer, its text should be displayed"
14246 );
14247
14248 multi_buffer_editor.update(cx, |editor, cx| {
14249 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
14250 });
14251 assert_eq!(
14252 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14253 full_text,
14254 "After unfolding all buffers, all original text should be displayed"
14255 );
14256}
14257
14258#[gpui::test]
14259async fn test_multi_buffer_with_single_excerpt_folding(cx: &mut gpui::TestAppContext) {
14260 init_test(cx, |_| {});
14261
14262 let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
14263
14264 let fs = FakeFs::new(cx.executor());
14265 fs.insert_tree(
14266 "/a",
14267 json!({
14268 "main.rs": sample_text,
14269 }),
14270 )
14271 .await;
14272 let project = Project::test(fs, ["/a".as_ref()], cx).await;
14273 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
14274 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14275 let worktree = project.update(cx, |project, cx| {
14276 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
14277 assert_eq!(worktrees.len(), 1);
14278 worktrees.pop().unwrap()
14279 });
14280 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
14281
14282 let buffer_1 = project
14283 .update(cx, |project, cx| {
14284 project.open_buffer((worktree_id, "main.rs"), cx)
14285 })
14286 .await
14287 .unwrap();
14288
14289 let multi_buffer = cx.new_model(|cx| {
14290 let mut multi_buffer = MultiBuffer::new(ReadWrite);
14291 multi_buffer.push_excerpts(
14292 buffer_1.clone(),
14293 [ExcerptRange {
14294 context: Point::new(0, 0)
14295 ..Point::new(
14296 sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
14297 0,
14298 ),
14299 primary: None,
14300 }],
14301 cx,
14302 );
14303 multi_buffer
14304 });
14305 let multi_buffer_editor = cx.new_view(|cx| {
14306 Editor::new(
14307 EditorMode::Full,
14308 multi_buffer,
14309 Some(project.clone()),
14310 true,
14311 cx,
14312 )
14313 });
14314
14315 let selection_range = Point::new(1, 0)..Point::new(2, 0);
14316 multi_buffer_editor.update(cx, |editor, cx| {
14317 enum TestHighlight {}
14318 let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
14319 let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
14320 editor.highlight_text::<TestHighlight>(
14321 vec![highlight_range.clone()],
14322 HighlightStyle::color(Hsla::green()),
14323 cx,
14324 );
14325 editor.change_selections(None, cx, |s| s.select_ranges(Some(highlight_range)));
14326 });
14327
14328 let full_text = format!("\n\n\n{sample_text}\n");
14329 assert_eq!(
14330 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14331 full_text,
14332 );
14333}
14334
14335#[gpui::test]
14336fn test_inline_completion_text(cx: &mut TestAppContext) {
14337 init_test(cx, |_| {});
14338
14339 // Simple insertion
14340 {
14341 let window = cx.add_window(|cx| {
14342 let buffer = MultiBuffer::build_simple("Hello, world!", cx);
14343 Editor::new(EditorMode::Full, buffer, None, true, cx)
14344 });
14345 let cx = &mut VisualTestContext::from_window(*window, cx);
14346
14347 window
14348 .update(cx, |editor, cx| {
14349 let snapshot = editor.snapshot(cx);
14350 let edit_range = snapshot.buffer_snapshot.anchor_after(Point::new(0, 6))
14351 ..snapshot.buffer_snapshot.anchor_before(Point::new(0, 6));
14352 let edits = vec![(edit_range, " beautiful".to_string())];
14353
14354 let InlineCompletionText::Edit { text, highlights } =
14355 inline_completion_edit_text(&snapshot, &edits, false, cx)
14356 else {
14357 panic!("Failed to generate inline completion text");
14358 };
14359
14360 assert_eq!(text, "Hello, beautiful world!");
14361 assert_eq!(highlights.len(), 1);
14362 assert_eq!(highlights[0].0, 6..16);
14363 assert_eq!(
14364 highlights[0].1.background_color,
14365 Some(cx.theme().status().created_background)
14366 );
14367 })
14368 .unwrap();
14369 }
14370
14371 // Replacement
14372 {
14373 let window = cx.add_window(|cx| {
14374 let buffer = MultiBuffer::build_simple("This is a test.", cx);
14375 Editor::new(EditorMode::Full, buffer, None, true, cx)
14376 });
14377 let cx = &mut VisualTestContext::from_window(*window, cx);
14378
14379 window
14380 .update(cx, |editor, cx| {
14381 let snapshot = editor.snapshot(cx);
14382 let edits = vec![(
14383 snapshot.buffer_snapshot.anchor_after(Point::new(0, 0))
14384 ..snapshot.buffer_snapshot.anchor_before(Point::new(0, 4)),
14385 "That".to_string(),
14386 )];
14387
14388 let InlineCompletionText::Edit { text, highlights } =
14389 inline_completion_edit_text(&snapshot, &edits, false, cx)
14390 else {
14391 panic!("Failed to generate inline completion text");
14392 };
14393
14394 assert_eq!(text, "That is a test.");
14395 assert_eq!(highlights.len(), 1);
14396 assert_eq!(highlights[0].0, 0..4);
14397 assert_eq!(
14398 highlights[0].1.background_color,
14399 Some(cx.theme().status().created_background)
14400 );
14401 })
14402 .unwrap();
14403 }
14404
14405 // Multiple edits
14406 {
14407 let window = cx.add_window(|cx| {
14408 let buffer = MultiBuffer::build_simple("Hello, world!", cx);
14409 Editor::new(EditorMode::Full, buffer, None, true, cx)
14410 });
14411 let cx = &mut VisualTestContext::from_window(*window, cx);
14412
14413 window
14414 .update(cx, |editor, cx| {
14415 let snapshot = editor.snapshot(cx);
14416 let edits = vec![
14417 (
14418 snapshot.buffer_snapshot.anchor_after(Point::new(0, 0))
14419 ..snapshot.buffer_snapshot.anchor_before(Point::new(0, 5)),
14420 "Greetings".into(),
14421 ),
14422 (
14423 snapshot.buffer_snapshot.anchor_after(Point::new(0, 12))
14424 ..snapshot.buffer_snapshot.anchor_before(Point::new(0, 12)),
14425 " and universe".into(),
14426 ),
14427 ];
14428
14429 let InlineCompletionText::Edit { text, highlights } =
14430 inline_completion_edit_text(&snapshot, &edits, false, cx)
14431 else {
14432 panic!("Failed to generate inline completion text");
14433 };
14434
14435 assert_eq!(text, "Greetings, world and universe!");
14436 assert_eq!(highlights.len(), 2);
14437 assert_eq!(highlights[0].0, 0..9);
14438 assert_eq!(highlights[1].0, 16..29);
14439 assert_eq!(
14440 highlights[0].1.background_color,
14441 Some(cx.theme().status().created_background)
14442 );
14443 assert_eq!(
14444 highlights[1].1.background_color,
14445 Some(cx.theme().status().created_background)
14446 );
14447 })
14448 .unwrap();
14449 }
14450
14451 // Multiple lines with edits
14452 {
14453 let window = cx.add_window(|cx| {
14454 let buffer =
14455 MultiBuffer::build_simple("First line\nSecond line\nThird line\nFourth line", cx);
14456 Editor::new(EditorMode::Full, buffer, None, true, cx)
14457 });
14458 let cx = &mut VisualTestContext::from_window(*window, cx);
14459
14460 window
14461 .update(cx, |editor, cx| {
14462 let snapshot = editor.snapshot(cx);
14463 let edits = vec![
14464 (
14465 snapshot.buffer_snapshot.anchor_before(Point::new(1, 7))
14466 ..snapshot.buffer_snapshot.anchor_before(Point::new(1, 11)),
14467 "modified".to_string(),
14468 ),
14469 (
14470 snapshot.buffer_snapshot.anchor_before(Point::new(2, 0))
14471 ..snapshot.buffer_snapshot.anchor_before(Point::new(2, 10)),
14472 "New third line".to_string(),
14473 ),
14474 (
14475 snapshot.buffer_snapshot.anchor_before(Point::new(3, 6))
14476 ..snapshot.buffer_snapshot.anchor_before(Point::new(3, 6)),
14477 " updated".to_string(),
14478 ),
14479 ];
14480
14481 let InlineCompletionText::Edit { text, highlights } =
14482 inline_completion_edit_text(&snapshot, &edits, false, cx)
14483 else {
14484 panic!("Failed to generate inline completion text");
14485 };
14486
14487 assert_eq!(text, "Second modified\nNew third line\nFourth updated line");
14488 assert_eq!(highlights.len(), 3);
14489 assert_eq!(highlights[0].0, 7..15); // "modified"
14490 assert_eq!(highlights[1].0, 16..30); // "New third line"
14491 assert_eq!(highlights[2].0, 37..45); // " updated"
14492
14493 for highlight in &highlights {
14494 assert_eq!(
14495 highlight.1.background_color,
14496 Some(cx.theme().status().created_background)
14497 );
14498 }
14499 })
14500 .unwrap();
14501 }
14502}
14503
14504#[gpui::test]
14505fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
14506 init_test(cx, |_| {});
14507
14508 // Deletion
14509 {
14510 let window = cx.add_window(|cx| {
14511 let buffer = MultiBuffer::build_simple("Hello, world!", cx);
14512 Editor::new(EditorMode::Full, buffer, None, true, cx)
14513 });
14514 let cx = &mut VisualTestContext::from_window(*window, cx);
14515
14516 window
14517 .update(cx, |editor, cx| {
14518 let snapshot = editor.snapshot(cx);
14519 let edit_range = snapshot.buffer_snapshot.anchor_after(Point::new(0, 5))
14520 ..snapshot.buffer_snapshot.anchor_before(Point::new(0, 11));
14521 let edits = vec![(edit_range, "".to_string())];
14522
14523 let InlineCompletionText::Edit { text, highlights } =
14524 inline_completion_edit_text(&snapshot, &edits, true, cx)
14525 else {
14526 panic!("Failed to generate inline completion text");
14527 };
14528
14529 assert_eq!(text, "Hello, world!");
14530 assert_eq!(highlights.len(), 1);
14531 assert_eq!(highlights[0].0, 5..11);
14532 assert_eq!(
14533 highlights[0].1.background_color,
14534 Some(cx.theme().status().deleted_background)
14535 );
14536 })
14537 .unwrap();
14538 }
14539
14540 // Insertion
14541 {
14542 let window = cx.add_window(|cx| {
14543 let buffer = MultiBuffer::build_simple("Hello, world!", cx);
14544 Editor::new(EditorMode::Full, buffer, None, true, cx)
14545 });
14546 let cx = &mut VisualTestContext::from_window(*window, cx);
14547
14548 window
14549 .update(cx, |editor, cx| {
14550 let snapshot = editor.snapshot(cx);
14551 let edit_range = snapshot.buffer_snapshot.anchor_after(Point::new(0, 6))
14552 ..snapshot.buffer_snapshot.anchor_before(Point::new(0, 6));
14553 let edits = vec![(edit_range, " digital".to_string())];
14554
14555 let InlineCompletionText::Edit { text, highlights } =
14556 inline_completion_edit_text(&snapshot, &edits, true, cx)
14557 else {
14558 panic!("Failed to generate inline completion text");
14559 };
14560
14561 assert_eq!(text, "Hello, digital world!");
14562 assert_eq!(highlights.len(), 1);
14563 assert_eq!(highlights[0].0, 6..14);
14564 assert_eq!(
14565 highlights[0].1.background_color,
14566 Some(cx.theme().status().created_background)
14567 );
14568 })
14569 .unwrap();
14570 }
14571}
14572
14573fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
14574 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
14575 point..point
14576}
14577
14578fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
14579 let (text, ranges) = marked_text_ranges(marked_text, true);
14580 assert_eq!(view.text(cx), text);
14581 assert_eq!(
14582 view.selections.ranges(cx),
14583 ranges,
14584 "Assert selections are {}",
14585 marked_text
14586 );
14587}
14588
14589pub fn handle_signature_help_request(
14590 cx: &mut EditorLspTestContext,
14591 mocked_response: lsp::SignatureHelp,
14592) -> impl Future<Output = ()> {
14593 let mut request =
14594 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
14595 let mocked_response = mocked_response.clone();
14596 async move { Ok(Some(mocked_response)) }
14597 });
14598
14599 async move {
14600 request.next().await;
14601 }
14602}
14603
14604/// Handle completion request passing a marked string specifying where the completion
14605/// should be triggered from using '|' character, what range should be replaced, and what completions
14606/// should be returned using '<' and '>' to delimit the range
14607pub fn handle_completion_request(
14608 cx: &mut EditorLspTestContext,
14609 marked_string: &str,
14610 completions: Vec<&'static str>,
14611 counter: Arc<AtomicUsize>,
14612) -> impl Future<Output = ()> {
14613 let complete_from_marker: TextRangeMarker = '|'.into();
14614 let replace_range_marker: TextRangeMarker = ('<', '>').into();
14615 let (_, mut marked_ranges) = marked_text_ranges_by(
14616 marked_string,
14617 vec![complete_from_marker.clone(), replace_range_marker.clone()],
14618 );
14619
14620 let complete_from_position =
14621 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
14622 let replace_range =
14623 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
14624
14625 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
14626 let completions = completions.clone();
14627 counter.fetch_add(1, atomic::Ordering::Release);
14628 async move {
14629 assert_eq!(params.text_document_position.text_document.uri, url.clone());
14630 assert_eq!(
14631 params.text_document_position.position,
14632 complete_from_position
14633 );
14634 Ok(Some(lsp::CompletionResponse::Array(
14635 completions
14636 .iter()
14637 .map(|completion_text| lsp::CompletionItem {
14638 label: completion_text.to_string(),
14639 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14640 range: replace_range,
14641 new_text: completion_text.to_string(),
14642 })),
14643 ..Default::default()
14644 })
14645 .collect(),
14646 )))
14647 }
14648 });
14649
14650 async move {
14651 request.next().await;
14652 }
14653}
14654
14655fn handle_resolve_completion_request(
14656 cx: &mut EditorLspTestContext,
14657 edits: Option<Vec<(&'static str, &'static str)>>,
14658) -> impl Future<Output = ()> {
14659 let edits = edits.map(|edits| {
14660 edits
14661 .iter()
14662 .map(|(marked_string, new_text)| {
14663 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
14664 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
14665 lsp::TextEdit::new(replace_range, new_text.to_string())
14666 })
14667 .collect::<Vec<_>>()
14668 });
14669
14670 let mut request =
14671 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
14672 let edits = edits.clone();
14673 async move {
14674 Ok(lsp::CompletionItem {
14675 additional_text_edits: edits,
14676 ..Default::default()
14677 })
14678 }
14679 });
14680
14681 async move {
14682 request.next().await;
14683 }
14684}
14685
14686pub(crate) fn update_test_language_settings(
14687 cx: &mut TestAppContext,
14688 f: impl Fn(&mut AllLanguageSettingsContent),
14689) {
14690 cx.update(|cx| {
14691 SettingsStore::update_global(cx, |store, cx| {
14692 store.update_user_settings::<AllLanguageSettings>(cx, f);
14693 });
14694 });
14695}
14696
14697pub(crate) fn update_test_project_settings(
14698 cx: &mut TestAppContext,
14699 f: impl Fn(&mut ProjectSettings),
14700) {
14701 cx.update(|cx| {
14702 SettingsStore::update_global(cx, |store, cx| {
14703 store.update_user_settings::<ProjectSettings>(cx, f);
14704 });
14705 });
14706}
14707
14708pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
14709 cx.update(|cx| {
14710 assets::Assets.load_test_fonts(cx);
14711 let store = SettingsStore::test(cx);
14712 cx.set_global(store);
14713 theme::init(theme::LoadThemes::JustBase, cx);
14714 release_channel::init(SemanticVersion::default(), cx);
14715 client::init_settings(cx);
14716 language::init(cx);
14717 Project::init_settings(cx);
14718 workspace::init_settings(cx);
14719 crate::init(cx);
14720 });
14721
14722 update_test_language_settings(cx, f);
14723}
14724
14725#[track_caller]
14726fn assert_hunk_revert(
14727 not_reverted_text_with_selections: &str,
14728 expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
14729 expected_reverted_text_with_selections: &str,
14730 base_text: &str,
14731 cx: &mut EditorLspTestContext,
14732) {
14733 cx.set_state(not_reverted_text_with_selections);
14734 cx.set_diff_base(base_text);
14735 cx.executor().run_until_parked();
14736
14737 let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
14738 let snapshot = editor.snapshot(cx);
14739 let reverted_hunk_statuses = snapshot
14740 .diff_map
14741 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len(), &snapshot.buffer_snapshot)
14742 .map(|hunk| hunk_status(&hunk))
14743 .collect::<Vec<_>>();
14744
14745 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
14746 reverted_hunk_statuses
14747 });
14748 cx.executor().run_until_parked();
14749 cx.assert_editor_state(expected_reverted_text_with_selections);
14750 assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
14751}