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 cx.set_state(indoc! {"
3709 «The quick brown
3710 fox jumps over
3711 the lazy dogˇ»
3712 "});
3713 cx.update_editor(|e, cx| e.convert_to_title_case(&ConvertToTitleCase, cx));
3714 cx.assert_editor_state(indoc! {"
3715 «The Quick Brown
3716 Fox Jumps Over
3717 The Lazy Dogˇ»
3718 "});
3719
3720 // Test multiple line, single selection case
3721 cx.set_state(indoc! {"
3722 «The quick brown
3723 fox jumps over
3724 the lazy dogˇ»
3725 "});
3726 cx.update_editor(|e, cx| e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, cx));
3727 cx.assert_editor_state(indoc! {"
3728 «TheQuickBrown
3729 FoxJumpsOver
3730 TheLazyDogˇ»
3731 "});
3732
3733 // From here on out, test more complex cases of manipulate_text()
3734
3735 // Test no selection case - should affect words cursors are in
3736 // Cursor at beginning, middle, and end of word
3737 cx.set_state(indoc! {"
3738 ˇhello big beauˇtiful worldˇ
3739 "});
3740 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3741 cx.assert_editor_state(indoc! {"
3742 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3743 "});
3744
3745 // Test multiple selections on a single line and across multiple lines
3746 cx.set_state(indoc! {"
3747 «Theˇ» quick «brown
3748 foxˇ» jumps «overˇ»
3749 the «lazyˇ» dog
3750 "});
3751 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3752 cx.assert_editor_state(indoc! {"
3753 «THEˇ» quick «BROWN
3754 FOXˇ» jumps «OVERˇ»
3755 the «LAZYˇ» dog
3756 "});
3757
3758 // Test case where text length grows
3759 cx.set_state(indoc! {"
3760 «tschüߡ»
3761 "});
3762 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3763 cx.assert_editor_state(indoc! {"
3764 «TSCHÜSSˇ»
3765 "});
3766
3767 // Test to make sure we don't crash when text shrinks
3768 cx.set_state(indoc! {"
3769 aaa_bbbˇ
3770 "});
3771 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3772 cx.assert_editor_state(indoc! {"
3773 «aaaBbbˇ»
3774 "});
3775
3776 // Test to make sure we all aware of the fact that each word can grow and shrink
3777 // Final selections should be aware of this fact
3778 cx.set_state(indoc! {"
3779 aaa_bˇbb bbˇb_ccc ˇccc_ddd
3780 "});
3781 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3782 cx.assert_editor_state(indoc! {"
3783 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
3784 "});
3785
3786 cx.set_state(indoc! {"
3787 «hElLo, WoRld!ˇ»
3788 "});
3789 cx.update_editor(|e, cx| e.convert_to_opposite_case(&ConvertToOppositeCase, cx));
3790 cx.assert_editor_state(indoc! {"
3791 «HeLlO, wOrLD!ˇ»
3792 "});
3793}
3794
3795#[gpui::test]
3796fn test_duplicate_line(cx: &mut TestAppContext) {
3797 init_test(cx, |_| {});
3798
3799 let view = cx.add_window(|cx| {
3800 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3801 build_editor(buffer, cx)
3802 });
3803 _ = view.update(cx, |view, cx| {
3804 view.change_selections(None, cx, |s| {
3805 s.select_display_ranges([
3806 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3807 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3808 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3809 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3810 ])
3811 });
3812 view.duplicate_line_down(&DuplicateLineDown, cx);
3813 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3814 assert_eq!(
3815 view.selections.display_ranges(cx),
3816 vec![
3817 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3818 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
3819 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3820 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3821 ]
3822 );
3823 });
3824
3825 let view = cx.add_window(|cx| {
3826 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3827 build_editor(buffer, cx)
3828 });
3829 _ = view.update(cx, |view, cx| {
3830 view.change_selections(None, cx, |s| {
3831 s.select_display_ranges([
3832 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3833 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3834 ])
3835 });
3836 view.duplicate_line_down(&DuplicateLineDown, cx);
3837 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3838 assert_eq!(
3839 view.selections.display_ranges(cx),
3840 vec![
3841 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
3842 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
3843 ]
3844 );
3845 });
3846
3847 // With `move_upwards` the selections stay in place, except for
3848 // the lines inserted above them
3849 let view = cx.add_window(|cx| {
3850 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3851 build_editor(buffer, cx)
3852 });
3853 _ = view.update(cx, |view, cx| {
3854 view.change_selections(None, cx, |s| {
3855 s.select_display_ranges([
3856 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3857 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3858 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3859 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3860 ])
3861 });
3862 view.duplicate_line_up(&DuplicateLineUp, cx);
3863 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3864 assert_eq!(
3865 view.selections.display_ranges(cx),
3866 vec![
3867 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3868 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3869 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
3870 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3871 ]
3872 );
3873 });
3874
3875 let view = cx.add_window(|cx| {
3876 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3877 build_editor(buffer, cx)
3878 });
3879 _ = view.update(cx, |view, cx| {
3880 view.change_selections(None, cx, |s| {
3881 s.select_display_ranges([
3882 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3883 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3884 ])
3885 });
3886 view.duplicate_line_up(&DuplicateLineUp, cx);
3887 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3888 assert_eq!(
3889 view.selections.display_ranges(cx),
3890 vec![
3891 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3892 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3893 ]
3894 );
3895 });
3896
3897 let view = cx.add_window(|cx| {
3898 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3899 build_editor(buffer, cx)
3900 });
3901 _ = view.update(cx, |view, cx| {
3902 view.change_selections(None, cx, |s| {
3903 s.select_display_ranges([
3904 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3905 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3906 ])
3907 });
3908 view.duplicate_selection(&DuplicateSelection, cx);
3909 assert_eq!(view.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
3910 assert_eq!(
3911 view.selections.display_ranges(cx),
3912 vec![
3913 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3914 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
3915 ]
3916 );
3917 });
3918}
3919
3920#[gpui::test]
3921fn test_move_line_up_down(cx: &mut TestAppContext) {
3922 init_test(cx, |_| {});
3923
3924 let view = cx.add_window(|cx| {
3925 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3926 build_editor(buffer, cx)
3927 });
3928 _ = view.update(cx, |view, cx| {
3929 view.fold_creases(
3930 vec![
3931 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
3932 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
3933 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
3934 ],
3935 true,
3936 cx,
3937 );
3938 view.change_selections(None, cx, |s| {
3939 s.select_display_ranges([
3940 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3941 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3942 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3943 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
3944 ])
3945 });
3946 assert_eq!(
3947 view.display_text(cx),
3948 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
3949 );
3950
3951 view.move_line_up(&MoveLineUp, cx);
3952 assert_eq!(
3953 view.display_text(cx),
3954 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
3955 );
3956 assert_eq!(
3957 view.selections.display_ranges(cx),
3958 vec![
3959 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3960 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3961 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3962 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3963 ]
3964 );
3965 });
3966
3967 _ = view.update(cx, |view, cx| {
3968 view.move_line_down(&MoveLineDown, cx);
3969 assert_eq!(
3970 view.display_text(cx),
3971 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
3972 );
3973 assert_eq!(
3974 view.selections.display_ranges(cx),
3975 vec![
3976 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3977 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3978 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3979 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3980 ]
3981 );
3982 });
3983
3984 _ = view.update(cx, |view, cx| {
3985 view.move_line_down(&MoveLineDown, cx);
3986 assert_eq!(
3987 view.display_text(cx),
3988 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
3989 );
3990 assert_eq!(
3991 view.selections.display_ranges(cx),
3992 vec![
3993 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3994 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3995 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3996 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3997 ]
3998 );
3999 });
4000
4001 _ = view.update(cx, |view, cx| {
4002 view.move_line_up(&MoveLineUp, cx);
4003 assert_eq!(
4004 view.display_text(cx),
4005 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
4006 );
4007 assert_eq!(
4008 view.selections.display_ranges(cx),
4009 vec![
4010 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
4011 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4012 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
4013 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
4014 ]
4015 );
4016 });
4017}
4018
4019#[gpui::test]
4020fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
4021 init_test(cx, |_| {});
4022
4023 let editor = cx.add_window(|cx| {
4024 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4025 build_editor(buffer, cx)
4026 });
4027 _ = editor.update(cx, |editor, cx| {
4028 let snapshot = editor.buffer.read(cx).snapshot(cx);
4029 editor.insert_blocks(
4030 [BlockProperties {
4031 style: BlockStyle::Fixed,
4032 placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
4033 height: 1,
4034 render: Arc::new(|_| div().into_any()),
4035 priority: 0,
4036 }],
4037 Some(Autoscroll::fit()),
4038 cx,
4039 );
4040 editor.change_selections(None, cx, |s| {
4041 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
4042 });
4043 editor.move_line_down(&MoveLineDown, cx);
4044 });
4045}
4046
4047#[gpui::test]
4048async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
4049 init_test(cx, |_| {});
4050
4051 let mut cx = EditorTestContext::new(cx).await;
4052 cx.set_state(
4053 &"
4054 ˇzero
4055 one
4056 two
4057 three
4058 four
4059 five
4060 "
4061 .unindent(),
4062 );
4063
4064 // Create a four-line block that replaces three lines of text.
4065 cx.update_editor(|editor, cx| {
4066 let snapshot = editor.snapshot(cx);
4067 let snapshot = &snapshot.buffer_snapshot;
4068 let placement = BlockPlacement::Replace(
4069 snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
4070 );
4071 editor.insert_blocks(
4072 [BlockProperties {
4073 placement,
4074 height: 4,
4075 style: BlockStyle::Sticky,
4076 render: Arc::new(|_| gpui::div().into_any_element()),
4077 priority: 0,
4078 }],
4079 None,
4080 cx,
4081 );
4082 });
4083
4084 // Move down so that the cursor touches the block.
4085 cx.update_editor(|editor, cx| {
4086 editor.move_down(&Default::default(), cx);
4087 });
4088 cx.assert_editor_state(
4089 &"
4090 zero
4091 «one
4092 two
4093 threeˇ»
4094 four
4095 five
4096 "
4097 .unindent(),
4098 );
4099
4100 // Move down past the block.
4101 cx.update_editor(|editor, cx| {
4102 editor.move_down(&Default::default(), cx);
4103 });
4104 cx.assert_editor_state(
4105 &"
4106 zero
4107 one
4108 two
4109 three
4110 ˇfour
4111 five
4112 "
4113 .unindent(),
4114 );
4115}
4116
4117#[gpui::test]
4118fn test_transpose(cx: &mut TestAppContext) {
4119 init_test(cx, |_| {});
4120
4121 _ = cx.add_window(|cx| {
4122 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
4123 editor.set_style(EditorStyle::default(), cx);
4124 editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
4125 editor.transpose(&Default::default(), cx);
4126 assert_eq!(editor.text(cx), "bac");
4127 assert_eq!(editor.selections.ranges(cx), [2..2]);
4128
4129 editor.transpose(&Default::default(), cx);
4130 assert_eq!(editor.text(cx), "bca");
4131 assert_eq!(editor.selections.ranges(cx), [3..3]);
4132
4133 editor.transpose(&Default::default(), cx);
4134 assert_eq!(editor.text(cx), "bac");
4135 assert_eq!(editor.selections.ranges(cx), [3..3]);
4136
4137 editor
4138 });
4139
4140 _ = cx.add_window(|cx| {
4141 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
4142 editor.set_style(EditorStyle::default(), cx);
4143 editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
4144 editor.transpose(&Default::default(), cx);
4145 assert_eq!(editor.text(cx), "acb\nde");
4146 assert_eq!(editor.selections.ranges(cx), [3..3]);
4147
4148 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
4149 editor.transpose(&Default::default(), cx);
4150 assert_eq!(editor.text(cx), "acbd\ne");
4151 assert_eq!(editor.selections.ranges(cx), [5..5]);
4152
4153 editor.transpose(&Default::default(), cx);
4154 assert_eq!(editor.text(cx), "acbde\n");
4155 assert_eq!(editor.selections.ranges(cx), [6..6]);
4156
4157 editor.transpose(&Default::default(), cx);
4158 assert_eq!(editor.text(cx), "acbd\ne");
4159 assert_eq!(editor.selections.ranges(cx), [6..6]);
4160
4161 editor
4162 });
4163
4164 _ = cx.add_window(|cx| {
4165 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
4166 editor.set_style(EditorStyle::default(), cx);
4167 editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
4168 editor.transpose(&Default::default(), cx);
4169 assert_eq!(editor.text(cx), "bacd\ne");
4170 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
4171
4172 editor.transpose(&Default::default(), cx);
4173 assert_eq!(editor.text(cx), "bcade\n");
4174 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
4175
4176 editor.transpose(&Default::default(), cx);
4177 assert_eq!(editor.text(cx), "bcda\ne");
4178 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4179
4180 editor.transpose(&Default::default(), cx);
4181 assert_eq!(editor.text(cx), "bcade\n");
4182 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4183
4184 editor.transpose(&Default::default(), cx);
4185 assert_eq!(editor.text(cx), "bcaed\n");
4186 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
4187
4188 editor
4189 });
4190
4191 _ = cx.add_window(|cx| {
4192 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
4193 editor.set_style(EditorStyle::default(), cx);
4194 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
4195 editor.transpose(&Default::default(), cx);
4196 assert_eq!(editor.text(cx), "🏀🍐✋");
4197 assert_eq!(editor.selections.ranges(cx), [8..8]);
4198
4199 editor.transpose(&Default::default(), cx);
4200 assert_eq!(editor.text(cx), "🏀✋🍐");
4201 assert_eq!(editor.selections.ranges(cx), [11..11]);
4202
4203 editor.transpose(&Default::default(), cx);
4204 assert_eq!(editor.text(cx), "🏀🍐✋");
4205 assert_eq!(editor.selections.ranges(cx), [11..11]);
4206
4207 editor
4208 });
4209}
4210
4211#[gpui::test]
4212async fn test_rewrap(cx: &mut TestAppContext) {
4213 init_test(cx, |_| {});
4214
4215 let mut cx = EditorTestContext::new(cx).await;
4216
4217 let language_with_c_comments = Arc::new(Language::new(
4218 LanguageConfig {
4219 line_comments: vec!["// ".into()],
4220 ..LanguageConfig::default()
4221 },
4222 None,
4223 ));
4224 let language_with_pound_comments = Arc::new(Language::new(
4225 LanguageConfig {
4226 line_comments: vec!["# ".into()],
4227 ..LanguageConfig::default()
4228 },
4229 None,
4230 ));
4231 let markdown_language = Arc::new(Language::new(
4232 LanguageConfig {
4233 name: "Markdown".into(),
4234 ..LanguageConfig::default()
4235 },
4236 None,
4237 ));
4238 let language_with_doc_comments = Arc::new(Language::new(
4239 LanguageConfig {
4240 line_comments: vec!["// ".into(), "/// ".into()],
4241 ..LanguageConfig::default()
4242 },
4243 Some(tree_sitter_rust::LANGUAGE.into()),
4244 ));
4245
4246 let plaintext_language = Arc::new(Language::new(
4247 LanguageConfig {
4248 name: "Plain Text".into(),
4249 ..LanguageConfig::default()
4250 },
4251 None,
4252 ));
4253
4254 assert_rewrap(
4255 indoc! {"
4256 // ˇ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.
4257 "},
4258 indoc! {"
4259 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4260 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4261 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4262 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4263 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4264 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4265 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4266 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4267 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4268 // porttitor id. Aliquam id accumsan eros.
4269 "},
4270 language_with_c_comments.clone(),
4271 &mut cx,
4272 );
4273
4274 // Test that rewrapping works inside of a selection
4275 assert_rewrap(
4276 indoc! {"
4277 «// 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.ˇ»
4278 "},
4279 indoc! {"
4280 «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4281 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4282 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4283 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4284 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4285 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4286 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4287 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4288 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4289 // porttitor id. Aliquam id accumsan eros.ˇ»
4290 "},
4291 language_with_c_comments.clone(),
4292 &mut cx,
4293 );
4294
4295 // Test that cursors that expand to the same region are collapsed.
4296 assert_rewrap(
4297 indoc! {"
4298 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4299 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4300 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4301 // ˇ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.
4302 "},
4303 indoc! {"
4304 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4305 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4306 // auctor, eu lacinia sapien scelerisque. ˇVivamus sit amet neque et quam
4307 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4308 // Pellentesque odio lectus, iaculis ac volutpat et, ˇblandit quis urna. Sed
4309 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4310 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4311 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4312 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4313 // porttitor id. Aliquam id accumsan eros.
4314 "},
4315 language_with_c_comments.clone(),
4316 &mut cx,
4317 );
4318
4319 // Test that non-contiguous selections are treated separately.
4320 assert_rewrap(
4321 indoc! {"
4322 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4323 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4324 //
4325 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4326 // ˇ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.
4327 "},
4328 indoc! {"
4329 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4330 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4331 // auctor, eu lacinia sapien scelerisque.
4332 //
4333 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
4334 // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4335 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
4336 // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
4337 // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
4338 // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
4339 // vulputate turpis porttitor id. Aliquam id accumsan eros.
4340 "},
4341 language_with_c_comments.clone(),
4342 &mut cx,
4343 );
4344
4345 // Test that different comment prefixes are supported.
4346 assert_rewrap(
4347 indoc! {"
4348 # ˇ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.
4349 "},
4350 indoc! {"
4351 # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4352 # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4353 # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4354 # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4355 # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
4356 # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
4357 # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
4358 # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
4359 # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
4360 # accumsan eros.
4361 "},
4362 language_with_pound_comments.clone(),
4363 &mut cx,
4364 );
4365
4366 // Test that rewrapping is ignored outside of comments in most languages.
4367 assert_rewrap(
4368 indoc! {"
4369 /// Adds two numbers.
4370 /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4371 fn add(a: u32, b: u32) -> u32 {
4372 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ˇ
4373 }
4374 "},
4375 indoc! {"
4376 /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
4377 /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4378 fn add(a: u32, b: u32) -> u32 {
4379 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ˇ
4380 }
4381 "},
4382 language_with_doc_comments.clone(),
4383 &mut cx,
4384 );
4385
4386 // Test that rewrapping works in Markdown and Plain Text languages.
4387 assert_rewrap(
4388 indoc! {"
4389 # Hello
4390
4391 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.
4392 "},
4393 indoc! {"
4394 # Hello
4395
4396 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4397 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4398 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4399 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4400 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4401 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4402 Integer sit amet scelerisque nisi.
4403 "},
4404 markdown_language,
4405 &mut cx,
4406 );
4407
4408 assert_rewrap(
4409 indoc! {"
4410 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.
4411 "},
4412 indoc! {"
4413 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4414 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4415 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4416 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4417 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4418 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4419 Integer sit amet scelerisque nisi.
4420 "},
4421 plaintext_language,
4422 &mut cx,
4423 );
4424
4425 // Test rewrapping unaligned comments in a selection.
4426 assert_rewrap(
4427 indoc! {"
4428 fn foo() {
4429 if true {
4430 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4431 // Praesent semper egestas tellus id dignissim.ˇ»
4432 do_something();
4433 } else {
4434 //
4435 }
4436 }
4437 "},
4438 indoc! {"
4439 fn foo() {
4440 if true {
4441 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4442 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4443 // egestas tellus id dignissim.ˇ»
4444 do_something();
4445 } else {
4446 //
4447 }
4448 }
4449 "},
4450 language_with_doc_comments.clone(),
4451 &mut cx,
4452 );
4453
4454 assert_rewrap(
4455 indoc! {"
4456 fn foo() {
4457 if true {
4458 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4459 // Praesent semper egestas tellus id dignissim.»
4460 do_something();
4461 } else {
4462 //
4463 }
4464
4465 }
4466 "},
4467 indoc! {"
4468 fn foo() {
4469 if true {
4470 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4471 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4472 // egestas tellus id dignissim.»
4473 do_something();
4474 } else {
4475 //
4476 }
4477
4478 }
4479 "},
4480 language_with_doc_comments.clone(),
4481 &mut cx,
4482 );
4483
4484 #[track_caller]
4485 fn assert_rewrap(
4486 unwrapped_text: &str,
4487 wrapped_text: &str,
4488 language: Arc<Language>,
4489 cx: &mut EditorTestContext,
4490 ) {
4491 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4492 cx.set_state(unwrapped_text);
4493 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4494 cx.assert_editor_state(wrapped_text);
4495 }
4496}
4497
4498#[gpui::test]
4499async fn test_clipboard(cx: &mut gpui::TestAppContext) {
4500 init_test(cx, |_| {});
4501
4502 let mut cx = EditorTestContext::new(cx).await;
4503
4504 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
4505 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4506 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
4507
4508 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
4509 cx.set_state("two ˇfour ˇsix ˇ");
4510 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4511 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
4512
4513 // Paste again but with only two cursors. Since the number of cursors doesn't
4514 // match the number of slices in the clipboard, the entire clipboard text
4515 // is pasted at each cursor.
4516 cx.set_state("ˇtwo one✅ four three six five ˇ");
4517 cx.update_editor(|e, cx| {
4518 e.handle_input("( ", cx);
4519 e.paste(&Paste, cx);
4520 e.handle_input(") ", cx);
4521 });
4522 cx.assert_editor_state(
4523 &([
4524 "( one✅ ",
4525 "three ",
4526 "five ) ˇtwo one✅ four three six five ( one✅ ",
4527 "three ",
4528 "five ) ˇ",
4529 ]
4530 .join("\n")),
4531 );
4532
4533 // Cut with three selections, one of which is full-line.
4534 cx.set_state(indoc! {"
4535 1«2ˇ»3
4536 4ˇ567
4537 «8ˇ»9"});
4538 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4539 cx.assert_editor_state(indoc! {"
4540 1ˇ3
4541 ˇ9"});
4542
4543 // Paste with three selections, noticing how the copied selection that was full-line
4544 // gets inserted before the second cursor.
4545 cx.set_state(indoc! {"
4546 1ˇ3
4547 9ˇ
4548 «oˇ»ne"});
4549 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4550 cx.assert_editor_state(indoc! {"
4551 12ˇ3
4552 4567
4553 9ˇ
4554 8ˇne"});
4555
4556 // Copy with a single cursor only, which writes the whole line into the clipboard.
4557 cx.set_state(indoc! {"
4558 The quick brown
4559 fox juˇmps over
4560 the lazy dog"});
4561 cx.update_editor(|e, cx| e.copy(&Copy, cx));
4562 assert_eq!(
4563 cx.read_from_clipboard()
4564 .and_then(|item| item.text().as_deref().map(str::to_string)),
4565 Some("fox jumps over\n".to_string())
4566 );
4567
4568 // Paste with three selections, noticing how the copied full-line selection is inserted
4569 // before the empty selections but replaces the selection that is non-empty.
4570 cx.set_state(indoc! {"
4571 Tˇhe quick brown
4572 «foˇ»x jumps over
4573 tˇhe lazy dog"});
4574 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4575 cx.assert_editor_state(indoc! {"
4576 fox jumps over
4577 Tˇhe quick brown
4578 fox jumps over
4579 ˇx jumps over
4580 fox jumps over
4581 tˇhe lazy dog"});
4582}
4583
4584#[gpui::test]
4585async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
4586 init_test(cx, |_| {});
4587
4588 let mut cx = EditorTestContext::new(cx).await;
4589 let language = Arc::new(Language::new(
4590 LanguageConfig::default(),
4591 Some(tree_sitter_rust::LANGUAGE.into()),
4592 ));
4593 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4594
4595 // Cut an indented block, without the leading whitespace.
4596 cx.set_state(indoc! {"
4597 const a: B = (
4598 c(),
4599 «d(
4600 e,
4601 f
4602 )ˇ»
4603 );
4604 "});
4605 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4606 cx.assert_editor_state(indoc! {"
4607 const a: B = (
4608 c(),
4609 ˇ
4610 );
4611 "});
4612
4613 // Paste it at the same position.
4614 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4615 cx.assert_editor_state(indoc! {"
4616 const a: B = (
4617 c(),
4618 d(
4619 e,
4620 f
4621 )ˇ
4622 );
4623 "});
4624
4625 // Paste it at a line with a lower indent level.
4626 cx.set_state(indoc! {"
4627 ˇ
4628 const a: B = (
4629 c(),
4630 );
4631 "});
4632 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4633 cx.assert_editor_state(indoc! {"
4634 d(
4635 e,
4636 f
4637 )ˇ
4638 const a: B = (
4639 c(),
4640 );
4641 "});
4642
4643 // Cut an indented block, with the leading whitespace.
4644 cx.set_state(indoc! {"
4645 const a: B = (
4646 c(),
4647 « d(
4648 e,
4649 f
4650 )
4651 ˇ»);
4652 "});
4653 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4654 cx.assert_editor_state(indoc! {"
4655 const a: B = (
4656 c(),
4657 ˇ);
4658 "});
4659
4660 // Paste it at the same position.
4661 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4662 cx.assert_editor_state(indoc! {"
4663 const a: B = (
4664 c(),
4665 d(
4666 e,
4667 f
4668 )
4669 ˇ);
4670 "});
4671
4672 // Paste it at a line with a higher indent level.
4673 cx.set_state(indoc! {"
4674 const a: B = (
4675 c(),
4676 d(
4677 e,
4678 fˇ
4679 )
4680 );
4681 "});
4682 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4683 cx.assert_editor_state(indoc! {"
4684 const a: B = (
4685 c(),
4686 d(
4687 e,
4688 f d(
4689 e,
4690 f
4691 )
4692 ˇ
4693 )
4694 );
4695 "});
4696}
4697
4698#[gpui::test]
4699fn test_select_all(cx: &mut TestAppContext) {
4700 init_test(cx, |_| {});
4701
4702 let view = cx.add_window(|cx| {
4703 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
4704 build_editor(buffer, cx)
4705 });
4706 _ = view.update(cx, |view, cx| {
4707 view.select_all(&SelectAll, cx);
4708 assert_eq!(
4709 view.selections.display_ranges(cx),
4710 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
4711 );
4712 });
4713}
4714
4715#[gpui::test]
4716fn test_select_line(cx: &mut TestAppContext) {
4717 init_test(cx, |_| {});
4718
4719 let view = cx.add_window(|cx| {
4720 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
4721 build_editor(buffer, cx)
4722 });
4723 _ = view.update(cx, |view, cx| {
4724 view.change_selections(None, cx, |s| {
4725 s.select_display_ranges([
4726 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4727 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4728 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4729 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
4730 ])
4731 });
4732 view.select_line(&SelectLine, cx);
4733 assert_eq!(
4734 view.selections.display_ranges(cx),
4735 vec![
4736 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
4737 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
4738 ]
4739 );
4740 });
4741
4742 _ = view.update(cx, |view, cx| {
4743 view.select_line(&SelectLine, cx);
4744 assert_eq!(
4745 view.selections.display_ranges(cx),
4746 vec![
4747 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
4748 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
4749 ]
4750 );
4751 });
4752
4753 _ = view.update(cx, |view, cx| {
4754 view.select_line(&SelectLine, cx);
4755 assert_eq!(
4756 view.selections.display_ranges(cx),
4757 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
4758 );
4759 });
4760}
4761
4762#[gpui::test]
4763fn test_split_selection_into_lines(cx: &mut TestAppContext) {
4764 init_test(cx, |_| {});
4765
4766 let view = cx.add_window(|cx| {
4767 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
4768 build_editor(buffer, cx)
4769 });
4770 _ = view.update(cx, |view, cx| {
4771 view.fold_creases(
4772 vec![
4773 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4774 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4775 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4776 ],
4777 true,
4778 cx,
4779 );
4780 view.change_selections(None, cx, |s| {
4781 s.select_display_ranges([
4782 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4783 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4784 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4785 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
4786 ])
4787 });
4788 assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i");
4789 });
4790
4791 _ = view.update(cx, |view, cx| {
4792 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4793 assert_eq!(
4794 view.display_text(cx),
4795 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
4796 );
4797 assert_eq!(
4798 view.selections.display_ranges(cx),
4799 [
4800 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4801 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4802 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
4803 DisplayPoint::new(DisplayRow(5), 4)..DisplayPoint::new(DisplayRow(5), 4)
4804 ]
4805 );
4806 });
4807
4808 _ = view.update(cx, |view, cx| {
4809 view.change_selections(None, cx, |s| {
4810 s.select_display_ranges([
4811 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
4812 ])
4813 });
4814 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4815 assert_eq!(
4816 view.display_text(cx),
4817 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
4818 );
4819 assert_eq!(
4820 view.selections.display_ranges(cx),
4821 [
4822 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
4823 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
4824 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
4825 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
4826 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
4827 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
4828 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5),
4829 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(7), 0)
4830 ]
4831 );
4832 });
4833}
4834
4835#[gpui::test]
4836async fn test_add_selection_above_below(cx: &mut TestAppContext) {
4837 init_test(cx, |_| {});
4838
4839 let mut cx = EditorTestContext::new(cx).await;
4840
4841 // let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
4842 cx.set_state(indoc!(
4843 r#"abc
4844 defˇghi
4845
4846 jk
4847 nlmo
4848 "#
4849 ));
4850
4851 cx.update_editor(|editor, cx| {
4852 editor.add_selection_above(&Default::default(), cx);
4853 });
4854
4855 cx.assert_editor_state(indoc!(
4856 r#"abcˇ
4857 defˇghi
4858
4859 jk
4860 nlmo
4861 "#
4862 ));
4863
4864 cx.update_editor(|editor, cx| {
4865 editor.add_selection_above(&Default::default(), cx);
4866 });
4867
4868 cx.assert_editor_state(indoc!(
4869 r#"abcˇ
4870 defˇghi
4871
4872 jk
4873 nlmo
4874 "#
4875 ));
4876
4877 cx.update_editor(|view, cx| {
4878 view.add_selection_below(&Default::default(), cx);
4879 });
4880
4881 cx.assert_editor_state(indoc!(
4882 r#"abc
4883 defˇghi
4884
4885 jk
4886 nlmo
4887 "#
4888 ));
4889
4890 cx.update_editor(|view, cx| {
4891 view.undo_selection(&Default::default(), cx);
4892 });
4893
4894 cx.assert_editor_state(indoc!(
4895 r#"abcˇ
4896 defˇghi
4897
4898 jk
4899 nlmo
4900 "#
4901 ));
4902
4903 cx.update_editor(|view, cx| {
4904 view.redo_selection(&Default::default(), cx);
4905 });
4906
4907 cx.assert_editor_state(indoc!(
4908 r#"abc
4909 defˇghi
4910
4911 jk
4912 nlmo
4913 "#
4914 ));
4915
4916 cx.update_editor(|view, cx| {
4917 view.add_selection_below(&Default::default(), cx);
4918 });
4919
4920 cx.assert_editor_state(indoc!(
4921 r#"abc
4922 defˇghi
4923
4924 jk
4925 nlmˇo
4926 "#
4927 ));
4928
4929 cx.update_editor(|view, cx| {
4930 view.add_selection_below(&Default::default(), cx);
4931 });
4932
4933 cx.assert_editor_state(indoc!(
4934 r#"abc
4935 defˇghi
4936
4937 jk
4938 nlmˇo
4939 "#
4940 ));
4941
4942 // change selections
4943 cx.set_state(indoc!(
4944 r#"abc
4945 def«ˇg»hi
4946
4947 jk
4948 nlmo
4949 "#
4950 ));
4951
4952 cx.update_editor(|view, cx| {
4953 view.add_selection_below(&Default::default(), cx);
4954 });
4955
4956 cx.assert_editor_state(indoc!(
4957 r#"abc
4958 def«ˇg»hi
4959
4960 jk
4961 nlm«ˇo»
4962 "#
4963 ));
4964
4965 cx.update_editor(|view, cx| {
4966 view.add_selection_below(&Default::default(), cx);
4967 });
4968
4969 cx.assert_editor_state(indoc!(
4970 r#"abc
4971 def«ˇg»hi
4972
4973 jk
4974 nlm«ˇo»
4975 "#
4976 ));
4977
4978 cx.update_editor(|view, cx| {
4979 view.add_selection_above(&Default::default(), cx);
4980 });
4981
4982 cx.assert_editor_state(indoc!(
4983 r#"abc
4984 def«ˇg»hi
4985
4986 jk
4987 nlmo
4988 "#
4989 ));
4990
4991 cx.update_editor(|view, cx| {
4992 view.add_selection_above(&Default::default(), cx);
4993 });
4994
4995 cx.assert_editor_state(indoc!(
4996 r#"abc
4997 def«ˇg»hi
4998
4999 jk
5000 nlmo
5001 "#
5002 ));
5003
5004 // Change selections again
5005 cx.set_state(indoc!(
5006 r#"a«bc
5007 defgˇ»hi
5008
5009 jk
5010 nlmo
5011 "#
5012 ));
5013
5014 cx.update_editor(|view, cx| {
5015 view.add_selection_below(&Default::default(), cx);
5016 });
5017
5018 cx.assert_editor_state(indoc!(
5019 r#"a«bcˇ»
5020 d«efgˇ»hi
5021
5022 j«kˇ»
5023 nlmo
5024 "#
5025 ));
5026
5027 cx.update_editor(|view, cx| {
5028 view.add_selection_below(&Default::default(), cx);
5029 });
5030 cx.assert_editor_state(indoc!(
5031 r#"a«bcˇ»
5032 d«efgˇ»hi
5033
5034 j«kˇ»
5035 n«lmoˇ»
5036 "#
5037 ));
5038 cx.update_editor(|view, cx| {
5039 view.add_selection_above(&Default::default(), cx);
5040 });
5041
5042 cx.assert_editor_state(indoc!(
5043 r#"a«bcˇ»
5044 d«efgˇ»hi
5045
5046 j«kˇ»
5047 nlmo
5048 "#
5049 ));
5050
5051 // Change selections again
5052 cx.set_state(indoc!(
5053 r#"abc
5054 d«ˇefghi
5055
5056 jk
5057 nlm»o
5058 "#
5059 ));
5060
5061 cx.update_editor(|view, cx| {
5062 view.add_selection_above(&Default::default(), cx);
5063 });
5064
5065 cx.assert_editor_state(indoc!(
5066 r#"a«ˇbc»
5067 d«ˇef»ghi
5068
5069 j«ˇk»
5070 n«ˇlm»o
5071 "#
5072 ));
5073
5074 cx.update_editor(|view, cx| {
5075 view.add_selection_below(&Default::default(), cx);
5076 });
5077
5078 cx.assert_editor_state(indoc!(
5079 r#"abc
5080 d«ˇef»ghi
5081
5082 j«ˇk»
5083 n«ˇlm»o
5084 "#
5085 ));
5086}
5087
5088#[gpui::test]
5089async fn test_select_next(cx: &mut gpui::TestAppContext) {
5090 init_test(cx, |_| {});
5091
5092 let mut cx = EditorTestContext::new(cx).await;
5093 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5094
5095 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5096 .unwrap();
5097 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5098
5099 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5100 .unwrap();
5101 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5102
5103 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5104 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5105
5106 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5107 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5108
5109 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5110 .unwrap();
5111 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5112
5113 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5114 .unwrap();
5115 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5116}
5117
5118#[gpui::test]
5119async fn test_select_all_matches(cx: &mut gpui::TestAppContext) {
5120 init_test(cx, |_| {});
5121
5122 let mut cx = EditorTestContext::new(cx).await;
5123 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5124
5125 cx.update_editor(|e, cx| e.select_all_matches(&SelectAllMatches, cx))
5126 .unwrap();
5127 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5128}
5129
5130#[gpui::test]
5131async fn test_select_next_with_multiple_carets(cx: &mut gpui::TestAppContext) {
5132 init_test(cx, |_| {});
5133
5134 let mut cx = EditorTestContext::new(cx).await;
5135 cx.set_state(
5136 r#"let foo = 2;
5137lˇet foo = 2;
5138let fooˇ = 2;
5139let foo = 2;
5140let foo = ˇ2;"#,
5141 );
5142
5143 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5144 .unwrap();
5145 cx.assert_editor_state(
5146 r#"let foo = 2;
5147«letˇ» foo = 2;
5148let «fooˇ» = 2;
5149let foo = 2;
5150let foo = «2ˇ»;"#,
5151 );
5152
5153 // noop for multiple selections with different contents
5154 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5155 .unwrap();
5156 cx.assert_editor_state(
5157 r#"let foo = 2;
5158«letˇ» foo = 2;
5159let «fooˇ» = 2;
5160let foo = 2;
5161let foo = «2ˇ»;"#,
5162 );
5163}
5164
5165#[gpui::test]
5166async fn test_select_previous_multibuffer(cx: &mut gpui::TestAppContext) {
5167 init_test(cx, |_| {});
5168
5169 let mut cx =
5170 EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
5171
5172 cx.assert_editor_state(indoc! {"
5173 ˇbbb
5174 ccc
5175
5176 bbb
5177 ccc
5178 "});
5179 cx.dispatch_action(SelectPrevious::default());
5180 cx.assert_editor_state(indoc! {"
5181 «bbbˇ»
5182 ccc
5183
5184 bbb
5185 ccc
5186 "});
5187 cx.dispatch_action(SelectPrevious::default());
5188 cx.assert_editor_state(indoc! {"
5189 «bbbˇ»
5190 ccc
5191
5192 «bbbˇ»
5193 ccc
5194 "});
5195}
5196
5197#[gpui::test]
5198async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) {
5199 init_test(cx, |_| {});
5200
5201 let mut cx = EditorTestContext::new(cx).await;
5202 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5203
5204 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5205 .unwrap();
5206 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5207
5208 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5209 .unwrap();
5210 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5211
5212 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5213 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5214
5215 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5216 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5217
5218 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5219 .unwrap();
5220 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
5221
5222 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5223 .unwrap();
5224 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
5225
5226 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5227 .unwrap();
5228 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5229}
5230
5231#[gpui::test]
5232async fn test_select_previous_with_multiple_carets(cx: &mut gpui::TestAppContext) {
5233 init_test(cx, |_| {});
5234
5235 let mut cx = EditorTestContext::new(cx).await;
5236 cx.set_state(
5237 r#"let foo = 2;
5238lˇet foo = 2;
5239let fooˇ = 2;
5240let foo = 2;
5241let foo = ˇ2;"#,
5242 );
5243
5244 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5245 .unwrap();
5246 cx.assert_editor_state(
5247 r#"let foo = 2;
5248«letˇ» foo = 2;
5249let «fooˇ» = 2;
5250let foo = 2;
5251let foo = «2ˇ»;"#,
5252 );
5253
5254 // noop for multiple selections with different contents
5255 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5256 .unwrap();
5257 cx.assert_editor_state(
5258 r#"let foo = 2;
5259«letˇ» foo = 2;
5260let «fooˇ» = 2;
5261let foo = 2;
5262let foo = «2ˇ»;"#,
5263 );
5264}
5265
5266#[gpui::test]
5267async fn test_select_previous_with_single_selection(cx: &mut gpui::TestAppContext) {
5268 init_test(cx, |_| {});
5269
5270 let mut cx = EditorTestContext::new(cx).await;
5271 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
5272
5273 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5274 .unwrap();
5275 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5276
5277 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5278 .unwrap();
5279 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5280
5281 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5282 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5283
5284 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5285 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5286
5287 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5288 .unwrap();
5289 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
5290
5291 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5292 .unwrap();
5293 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5294}
5295
5296#[gpui::test]
5297async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
5298 init_test(cx, |_| {});
5299
5300 let language = Arc::new(Language::new(
5301 LanguageConfig::default(),
5302 Some(tree_sitter_rust::LANGUAGE.into()),
5303 ));
5304
5305 let text = r#"
5306 use mod1::mod2::{mod3, mod4};
5307
5308 fn fn_1(param1: bool, param2: &str) {
5309 let var1 = "text";
5310 }
5311 "#
5312 .unindent();
5313
5314 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5315 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5316 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5317
5318 editor
5319 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5320 .await;
5321
5322 editor.update(cx, |view, cx| {
5323 view.change_selections(None, cx, |s| {
5324 s.select_display_ranges([
5325 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
5326 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
5327 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
5328 ]);
5329 });
5330 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5331 });
5332 editor.update(cx, |editor, cx| {
5333 assert_text_with_selections(
5334 editor,
5335 indoc! {r#"
5336 use mod1::mod2::{mod3, «mod4ˇ»};
5337
5338 fn fn_1«ˇ(param1: bool, param2: &str)» {
5339 let var1 = "«textˇ»";
5340 }
5341 "#},
5342 cx,
5343 );
5344 });
5345
5346 editor.update(cx, |view, cx| {
5347 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5348 });
5349 editor.update(cx, |editor, cx| {
5350 assert_text_with_selections(
5351 editor,
5352 indoc! {r#"
5353 use mod1::mod2::«{mod3, mod4}ˇ»;
5354
5355 «ˇfn fn_1(param1: bool, param2: &str) {
5356 let var1 = "text";
5357 }»
5358 "#},
5359 cx,
5360 );
5361 });
5362
5363 editor.update(cx, |view, cx| {
5364 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5365 });
5366 assert_eq!(
5367 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
5368 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5369 );
5370
5371 // Trying to expand the selected syntax node one more time has no effect.
5372 editor.update(cx, |view, cx| {
5373 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5374 });
5375 assert_eq!(
5376 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
5377 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5378 );
5379
5380 editor.update(cx, |view, cx| {
5381 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5382 });
5383 editor.update(cx, |editor, cx| {
5384 assert_text_with_selections(
5385 editor,
5386 indoc! {r#"
5387 use mod1::mod2::«{mod3, mod4}ˇ»;
5388
5389 «ˇfn fn_1(param1: bool, param2: &str) {
5390 let var1 = "text";
5391 }»
5392 "#},
5393 cx,
5394 );
5395 });
5396
5397 editor.update(cx, |view, cx| {
5398 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5399 });
5400 editor.update(cx, |editor, cx| {
5401 assert_text_with_selections(
5402 editor,
5403 indoc! {r#"
5404 use mod1::mod2::{mod3, «mod4ˇ»};
5405
5406 fn fn_1«ˇ(param1: bool, param2: &str)» {
5407 let var1 = "«textˇ»";
5408 }
5409 "#},
5410 cx,
5411 );
5412 });
5413
5414 editor.update(cx, |view, cx| {
5415 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5416 });
5417 editor.update(cx, |editor, cx| {
5418 assert_text_with_selections(
5419 editor,
5420 indoc! {r#"
5421 use mod1::mod2::{mod3, mo«ˇ»d4};
5422
5423 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5424 let var1 = "te«ˇ»xt";
5425 }
5426 "#},
5427 cx,
5428 );
5429 });
5430
5431 // Trying to shrink the selected syntax node one more time has no effect.
5432 editor.update(cx, |view, cx| {
5433 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5434 });
5435 editor.update(cx, |editor, cx| {
5436 assert_text_with_selections(
5437 editor,
5438 indoc! {r#"
5439 use mod1::mod2::{mod3, mo«ˇ»d4};
5440
5441 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5442 let var1 = "te«ˇ»xt";
5443 }
5444 "#},
5445 cx,
5446 );
5447 });
5448
5449 // Ensure that we keep expanding the selection if the larger selection starts or ends within
5450 // a fold.
5451 editor.update(cx, |view, cx| {
5452 view.fold_creases(
5453 vec![
5454 Crease::simple(
5455 Point::new(0, 21)..Point::new(0, 24),
5456 FoldPlaceholder::test(),
5457 ),
5458 Crease::simple(
5459 Point::new(3, 20)..Point::new(3, 22),
5460 FoldPlaceholder::test(),
5461 ),
5462 ],
5463 true,
5464 cx,
5465 );
5466 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5467 });
5468 editor.update(cx, |editor, cx| {
5469 assert_text_with_selections(
5470 editor,
5471 indoc! {r#"
5472 use mod1::mod2::«{mod3, mod4}ˇ»;
5473
5474 fn fn_1«ˇ(param1: bool, param2: &str)» {
5475 «let var1 = "text";ˇ»
5476 }
5477 "#},
5478 cx,
5479 );
5480 });
5481}
5482
5483#[gpui::test]
5484async fn test_autoindent(cx: &mut gpui::TestAppContext) {
5485 init_test(cx, |_| {});
5486
5487 let language = Arc::new(
5488 Language::new(
5489 LanguageConfig {
5490 brackets: BracketPairConfig {
5491 pairs: vec![
5492 BracketPair {
5493 start: "{".to_string(),
5494 end: "}".to_string(),
5495 close: false,
5496 surround: false,
5497 newline: true,
5498 },
5499 BracketPair {
5500 start: "(".to_string(),
5501 end: ")".to_string(),
5502 close: false,
5503 surround: false,
5504 newline: true,
5505 },
5506 ],
5507 ..Default::default()
5508 },
5509 ..Default::default()
5510 },
5511 Some(tree_sitter_rust::LANGUAGE.into()),
5512 )
5513 .with_indents_query(
5514 r#"
5515 (_ "(" ")" @end) @indent
5516 (_ "{" "}" @end) @indent
5517 "#,
5518 )
5519 .unwrap(),
5520 );
5521
5522 let text = "fn a() {}";
5523
5524 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5525 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5526 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5527 editor
5528 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5529 .await;
5530
5531 editor.update(cx, |editor, cx| {
5532 editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
5533 editor.newline(&Newline, cx);
5534 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
5535 assert_eq!(
5536 editor.selections.ranges(cx),
5537 &[
5538 Point::new(1, 4)..Point::new(1, 4),
5539 Point::new(3, 4)..Point::new(3, 4),
5540 Point::new(5, 0)..Point::new(5, 0)
5541 ]
5542 );
5543 });
5544}
5545
5546#[gpui::test]
5547async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
5548 init_test(cx, |_| {});
5549
5550 {
5551 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
5552 cx.set_state(indoc! {"
5553 impl A {
5554
5555 fn b() {}
5556
5557 «fn c() {
5558
5559 }ˇ»
5560 }
5561 "});
5562
5563 cx.update_editor(|editor, cx| {
5564 editor.autoindent(&Default::default(), cx);
5565 });
5566
5567 cx.assert_editor_state(indoc! {"
5568 impl A {
5569
5570 fn b() {}
5571
5572 «fn c() {
5573
5574 }ˇ»
5575 }
5576 "});
5577 }
5578
5579 {
5580 let mut cx = EditorTestContext::new_multibuffer(
5581 cx,
5582 [indoc! { "
5583 impl A {
5584 «
5585 // a
5586 fn b(){}
5587 »
5588 «
5589 }
5590 fn c(){}
5591 »
5592 "}],
5593 );
5594
5595 let buffer = cx.update_editor(|editor, cx| {
5596 let buffer = editor.buffer().update(cx, |buffer, _| {
5597 buffer.all_buffers().iter().next().unwrap().clone()
5598 });
5599 buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
5600 buffer
5601 });
5602
5603 cx.run_until_parked();
5604 cx.update_editor(|editor, cx| {
5605 editor.select_all(&Default::default(), cx);
5606 editor.autoindent(&Default::default(), cx)
5607 });
5608 cx.run_until_parked();
5609
5610 cx.update(|cx| {
5611 pretty_assertions::assert_eq!(
5612 buffer.read(cx).text(),
5613 indoc! { "
5614 impl A {
5615
5616 // a
5617 fn b(){}
5618
5619
5620 }
5621 fn c(){}
5622
5623 " }
5624 )
5625 });
5626 }
5627}
5628
5629#[gpui::test]
5630async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
5631 init_test(cx, |_| {});
5632
5633 let mut cx = EditorTestContext::new(cx).await;
5634
5635 let language = Arc::new(Language::new(
5636 LanguageConfig {
5637 brackets: BracketPairConfig {
5638 pairs: vec![
5639 BracketPair {
5640 start: "{".to_string(),
5641 end: "}".to_string(),
5642 close: true,
5643 surround: true,
5644 newline: true,
5645 },
5646 BracketPair {
5647 start: "(".to_string(),
5648 end: ")".to_string(),
5649 close: true,
5650 surround: true,
5651 newline: true,
5652 },
5653 BracketPair {
5654 start: "/*".to_string(),
5655 end: " */".to_string(),
5656 close: true,
5657 surround: true,
5658 newline: true,
5659 },
5660 BracketPair {
5661 start: "[".to_string(),
5662 end: "]".to_string(),
5663 close: false,
5664 surround: false,
5665 newline: true,
5666 },
5667 BracketPair {
5668 start: "\"".to_string(),
5669 end: "\"".to_string(),
5670 close: true,
5671 surround: true,
5672 newline: false,
5673 },
5674 BracketPair {
5675 start: "<".to_string(),
5676 end: ">".to_string(),
5677 close: false,
5678 surround: true,
5679 newline: true,
5680 },
5681 ],
5682 ..Default::default()
5683 },
5684 autoclose_before: "})]".to_string(),
5685 ..Default::default()
5686 },
5687 Some(tree_sitter_rust::LANGUAGE.into()),
5688 ));
5689
5690 cx.language_registry().add(language.clone());
5691 cx.update_buffer(|buffer, cx| {
5692 buffer.set_language(Some(language), cx);
5693 });
5694
5695 cx.set_state(
5696 &r#"
5697 🏀ˇ
5698 εˇ
5699 ❤️ˇ
5700 "#
5701 .unindent(),
5702 );
5703
5704 // autoclose multiple nested brackets at multiple cursors
5705 cx.update_editor(|view, cx| {
5706 view.handle_input("{", cx);
5707 view.handle_input("{", cx);
5708 view.handle_input("{", cx);
5709 });
5710 cx.assert_editor_state(
5711 &"
5712 🏀{{{ˇ}}}
5713 ε{{{ˇ}}}
5714 ❤️{{{ˇ}}}
5715 "
5716 .unindent(),
5717 );
5718
5719 // insert a different closing bracket
5720 cx.update_editor(|view, cx| {
5721 view.handle_input(")", cx);
5722 });
5723 cx.assert_editor_state(
5724 &"
5725 🏀{{{)ˇ}}}
5726 ε{{{)ˇ}}}
5727 ❤️{{{)ˇ}}}
5728 "
5729 .unindent(),
5730 );
5731
5732 // skip over the auto-closed brackets when typing a closing bracket
5733 cx.update_editor(|view, cx| {
5734 view.move_right(&MoveRight, cx);
5735 view.handle_input("}", cx);
5736 view.handle_input("}", cx);
5737 view.handle_input("}", cx);
5738 });
5739 cx.assert_editor_state(
5740 &"
5741 🏀{{{)}}}}ˇ
5742 ε{{{)}}}}ˇ
5743 ❤️{{{)}}}}ˇ
5744 "
5745 .unindent(),
5746 );
5747
5748 // autoclose multi-character pairs
5749 cx.set_state(
5750 &"
5751 ˇ
5752 ˇ
5753 "
5754 .unindent(),
5755 );
5756 cx.update_editor(|view, cx| {
5757 view.handle_input("/", cx);
5758 view.handle_input("*", cx);
5759 });
5760 cx.assert_editor_state(
5761 &"
5762 /*ˇ */
5763 /*ˇ */
5764 "
5765 .unindent(),
5766 );
5767
5768 // one cursor autocloses a multi-character pair, one cursor
5769 // does not autoclose.
5770 cx.set_state(
5771 &"
5772 /ˇ
5773 ˇ
5774 "
5775 .unindent(),
5776 );
5777 cx.update_editor(|view, cx| view.handle_input("*", cx));
5778 cx.assert_editor_state(
5779 &"
5780 /*ˇ */
5781 *ˇ
5782 "
5783 .unindent(),
5784 );
5785
5786 // Don't autoclose if the next character isn't whitespace and isn't
5787 // listed in the language's "autoclose_before" section.
5788 cx.set_state("ˇa b");
5789 cx.update_editor(|view, cx| view.handle_input("{", cx));
5790 cx.assert_editor_state("{ˇa b");
5791
5792 // Don't autoclose if `close` is false for the bracket pair
5793 cx.set_state("ˇ");
5794 cx.update_editor(|view, cx| view.handle_input("[", cx));
5795 cx.assert_editor_state("[ˇ");
5796
5797 // Surround with brackets if text is selected
5798 cx.set_state("«aˇ» b");
5799 cx.update_editor(|view, cx| view.handle_input("{", cx));
5800 cx.assert_editor_state("{«aˇ»} b");
5801
5802 // Autclose pair where the start and end characters are the same
5803 cx.set_state("aˇ");
5804 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5805 cx.assert_editor_state("a\"ˇ\"");
5806 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5807 cx.assert_editor_state("a\"\"ˇ");
5808
5809 // Don't autoclose pair if autoclose is disabled
5810 cx.set_state("ˇ");
5811 cx.update_editor(|view, cx| view.handle_input("<", cx));
5812 cx.assert_editor_state("<ˇ");
5813
5814 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
5815 cx.set_state("«aˇ» b");
5816 cx.update_editor(|view, cx| view.handle_input("<", cx));
5817 cx.assert_editor_state("<«aˇ»> b");
5818}
5819
5820#[gpui::test]
5821async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut gpui::TestAppContext) {
5822 init_test(cx, |settings| {
5823 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
5824 });
5825
5826 let mut cx = EditorTestContext::new(cx).await;
5827
5828 let language = Arc::new(Language::new(
5829 LanguageConfig {
5830 brackets: BracketPairConfig {
5831 pairs: vec![
5832 BracketPair {
5833 start: "{".to_string(),
5834 end: "}".to_string(),
5835 close: true,
5836 surround: true,
5837 newline: true,
5838 },
5839 BracketPair {
5840 start: "(".to_string(),
5841 end: ")".to_string(),
5842 close: true,
5843 surround: true,
5844 newline: true,
5845 },
5846 BracketPair {
5847 start: "[".to_string(),
5848 end: "]".to_string(),
5849 close: false,
5850 surround: false,
5851 newline: true,
5852 },
5853 ],
5854 ..Default::default()
5855 },
5856 autoclose_before: "})]".to_string(),
5857 ..Default::default()
5858 },
5859 Some(tree_sitter_rust::LANGUAGE.into()),
5860 ));
5861
5862 cx.language_registry().add(language.clone());
5863 cx.update_buffer(|buffer, cx| {
5864 buffer.set_language(Some(language), cx);
5865 });
5866
5867 cx.set_state(
5868 &"
5869 ˇ
5870 ˇ
5871 ˇ
5872 "
5873 .unindent(),
5874 );
5875
5876 // ensure only matching closing brackets are skipped over
5877 cx.update_editor(|view, cx| {
5878 view.handle_input("}", cx);
5879 view.move_left(&MoveLeft, cx);
5880 view.handle_input(")", cx);
5881 view.move_left(&MoveLeft, cx);
5882 });
5883 cx.assert_editor_state(
5884 &"
5885 ˇ)}
5886 ˇ)}
5887 ˇ)}
5888 "
5889 .unindent(),
5890 );
5891
5892 // skip-over closing brackets at multiple cursors
5893 cx.update_editor(|view, cx| {
5894 view.handle_input(")", cx);
5895 view.handle_input("}", cx);
5896 });
5897 cx.assert_editor_state(
5898 &"
5899 )}ˇ
5900 )}ˇ
5901 )}ˇ
5902 "
5903 .unindent(),
5904 );
5905
5906 // ignore non-close brackets
5907 cx.update_editor(|view, cx| {
5908 view.handle_input("]", cx);
5909 view.move_left(&MoveLeft, cx);
5910 view.handle_input("]", cx);
5911 });
5912 cx.assert_editor_state(
5913 &"
5914 )}]ˇ]
5915 )}]ˇ]
5916 )}]ˇ]
5917 "
5918 .unindent(),
5919 );
5920}
5921
5922#[gpui::test]
5923async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
5924 init_test(cx, |_| {});
5925
5926 let mut cx = EditorTestContext::new(cx).await;
5927
5928 let html_language = Arc::new(
5929 Language::new(
5930 LanguageConfig {
5931 name: "HTML".into(),
5932 brackets: BracketPairConfig {
5933 pairs: vec![
5934 BracketPair {
5935 start: "<".into(),
5936 end: ">".into(),
5937 close: true,
5938 ..Default::default()
5939 },
5940 BracketPair {
5941 start: "{".into(),
5942 end: "}".into(),
5943 close: true,
5944 ..Default::default()
5945 },
5946 BracketPair {
5947 start: "(".into(),
5948 end: ")".into(),
5949 close: true,
5950 ..Default::default()
5951 },
5952 ],
5953 ..Default::default()
5954 },
5955 autoclose_before: "})]>".into(),
5956 ..Default::default()
5957 },
5958 Some(tree_sitter_html::language()),
5959 )
5960 .with_injection_query(
5961 r#"
5962 (script_element
5963 (raw_text) @injection.content
5964 (#set! injection.language "javascript"))
5965 "#,
5966 )
5967 .unwrap(),
5968 );
5969
5970 let javascript_language = Arc::new(Language::new(
5971 LanguageConfig {
5972 name: "JavaScript".into(),
5973 brackets: BracketPairConfig {
5974 pairs: vec![
5975 BracketPair {
5976 start: "/*".into(),
5977 end: " */".into(),
5978 close: true,
5979 ..Default::default()
5980 },
5981 BracketPair {
5982 start: "{".into(),
5983 end: "}".into(),
5984 close: true,
5985 ..Default::default()
5986 },
5987 BracketPair {
5988 start: "(".into(),
5989 end: ")".into(),
5990 close: true,
5991 ..Default::default()
5992 },
5993 ],
5994 ..Default::default()
5995 },
5996 autoclose_before: "})]>".into(),
5997 ..Default::default()
5998 },
5999 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
6000 ));
6001
6002 cx.language_registry().add(html_language.clone());
6003 cx.language_registry().add(javascript_language.clone());
6004
6005 cx.update_buffer(|buffer, cx| {
6006 buffer.set_language(Some(html_language), cx);
6007 });
6008
6009 cx.set_state(
6010 &r#"
6011 <body>ˇ
6012 <script>
6013 var x = 1;ˇ
6014 </script>
6015 </body>ˇ
6016 "#
6017 .unindent(),
6018 );
6019
6020 // Precondition: different languages are active at different locations.
6021 cx.update_editor(|editor, cx| {
6022 let snapshot = editor.snapshot(cx);
6023 let cursors = editor.selections.ranges::<usize>(cx);
6024 let languages = cursors
6025 .iter()
6026 .map(|c| snapshot.language_at(c.start).unwrap().name())
6027 .collect::<Vec<_>>();
6028 assert_eq!(
6029 languages,
6030 &["HTML".into(), "JavaScript".into(), "HTML".into()]
6031 );
6032 });
6033
6034 // Angle brackets autoclose in HTML, but not JavaScript.
6035 cx.update_editor(|editor, cx| {
6036 editor.handle_input("<", cx);
6037 editor.handle_input("a", cx);
6038 });
6039 cx.assert_editor_state(
6040 &r#"
6041 <body><aˇ>
6042 <script>
6043 var x = 1;<aˇ
6044 </script>
6045 </body><aˇ>
6046 "#
6047 .unindent(),
6048 );
6049
6050 // Curly braces and parens autoclose in both HTML and JavaScript.
6051 cx.update_editor(|editor, cx| {
6052 editor.handle_input(" b=", cx);
6053 editor.handle_input("{", cx);
6054 editor.handle_input("c", cx);
6055 editor.handle_input("(", cx);
6056 });
6057 cx.assert_editor_state(
6058 &r#"
6059 <body><a b={c(ˇ)}>
6060 <script>
6061 var x = 1;<a b={c(ˇ)}
6062 </script>
6063 </body><a b={c(ˇ)}>
6064 "#
6065 .unindent(),
6066 );
6067
6068 // Brackets that were already autoclosed are skipped.
6069 cx.update_editor(|editor, cx| {
6070 editor.handle_input(")", cx);
6071 editor.handle_input("d", cx);
6072 editor.handle_input("}", cx);
6073 });
6074 cx.assert_editor_state(
6075 &r#"
6076 <body><a b={c()d}ˇ>
6077 <script>
6078 var x = 1;<a b={c()d}ˇ
6079 </script>
6080 </body><a b={c()d}ˇ>
6081 "#
6082 .unindent(),
6083 );
6084 cx.update_editor(|editor, cx| {
6085 editor.handle_input(">", cx);
6086 });
6087 cx.assert_editor_state(
6088 &r#"
6089 <body><a b={c()d}>ˇ
6090 <script>
6091 var x = 1;<a b={c()d}>ˇ
6092 </script>
6093 </body><a b={c()d}>ˇ
6094 "#
6095 .unindent(),
6096 );
6097
6098 // Reset
6099 cx.set_state(
6100 &r#"
6101 <body>ˇ
6102 <script>
6103 var x = 1;ˇ
6104 </script>
6105 </body>ˇ
6106 "#
6107 .unindent(),
6108 );
6109
6110 cx.update_editor(|editor, cx| {
6111 editor.handle_input("<", cx);
6112 });
6113 cx.assert_editor_state(
6114 &r#"
6115 <body><ˇ>
6116 <script>
6117 var x = 1;<ˇ
6118 </script>
6119 </body><ˇ>
6120 "#
6121 .unindent(),
6122 );
6123
6124 // When backspacing, the closing angle brackets are removed.
6125 cx.update_editor(|editor, cx| {
6126 editor.backspace(&Backspace, cx);
6127 });
6128 cx.assert_editor_state(
6129 &r#"
6130 <body>ˇ
6131 <script>
6132 var x = 1;ˇ
6133 </script>
6134 </body>ˇ
6135 "#
6136 .unindent(),
6137 );
6138
6139 // Block comments autoclose in JavaScript, but not HTML.
6140 cx.update_editor(|editor, cx| {
6141 editor.handle_input("/", cx);
6142 editor.handle_input("*", cx);
6143 });
6144 cx.assert_editor_state(
6145 &r#"
6146 <body>/*ˇ
6147 <script>
6148 var x = 1;/*ˇ */
6149 </script>
6150 </body>/*ˇ
6151 "#
6152 .unindent(),
6153 );
6154}
6155
6156#[gpui::test]
6157async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
6158 init_test(cx, |_| {});
6159
6160 let mut cx = EditorTestContext::new(cx).await;
6161
6162 let rust_language = Arc::new(
6163 Language::new(
6164 LanguageConfig {
6165 name: "Rust".into(),
6166 brackets: serde_json::from_value(json!([
6167 { "start": "{", "end": "}", "close": true, "newline": true },
6168 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
6169 ]))
6170 .unwrap(),
6171 autoclose_before: "})]>".into(),
6172 ..Default::default()
6173 },
6174 Some(tree_sitter_rust::LANGUAGE.into()),
6175 )
6176 .with_override_query("(string_literal) @string")
6177 .unwrap(),
6178 );
6179
6180 cx.language_registry().add(rust_language.clone());
6181 cx.update_buffer(|buffer, cx| {
6182 buffer.set_language(Some(rust_language), cx);
6183 });
6184
6185 cx.set_state(
6186 &r#"
6187 let x = ˇ
6188 "#
6189 .unindent(),
6190 );
6191
6192 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
6193 cx.update_editor(|editor, cx| {
6194 editor.handle_input("\"", cx);
6195 });
6196 cx.assert_editor_state(
6197 &r#"
6198 let x = "ˇ"
6199 "#
6200 .unindent(),
6201 );
6202
6203 // Inserting another quotation mark. The cursor moves across the existing
6204 // automatically-inserted quotation mark.
6205 cx.update_editor(|editor, cx| {
6206 editor.handle_input("\"", cx);
6207 });
6208 cx.assert_editor_state(
6209 &r#"
6210 let x = ""ˇ
6211 "#
6212 .unindent(),
6213 );
6214
6215 // Reset
6216 cx.set_state(
6217 &r#"
6218 let x = ˇ
6219 "#
6220 .unindent(),
6221 );
6222
6223 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
6224 cx.update_editor(|editor, cx| {
6225 editor.handle_input("\"", cx);
6226 editor.handle_input(" ", cx);
6227 editor.move_left(&Default::default(), cx);
6228 editor.handle_input("\\", cx);
6229 editor.handle_input("\"", cx);
6230 });
6231 cx.assert_editor_state(
6232 &r#"
6233 let x = "\"ˇ "
6234 "#
6235 .unindent(),
6236 );
6237
6238 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
6239 // mark. Nothing is inserted.
6240 cx.update_editor(|editor, cx| {
6241 editor.move_right(&Default::default(), cx);
6242 editor.handle_input("\"", cx);
6243 });
6244 cx.assert_editor_state(
6245 &r#"
6246 let x = "\" "ˇ
6247 "#
6248 .unindent(),
6249 );
6250}
6251
6252#[gpui::test]
6253async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
6254 init_test(cx, |_| {});
6255
6256 let language = Arc::new(Language::new(
6257 LanguageConfig {
6258 brackets: BracketPairConfig {
6259 pairs: vec![
6260 BracketPair {
6261 start: "{".to_string(),
6262 end: "}".to_string(),
6263 close: true,
6264 surround: true,
6265 newline: true,
6266 },
6267 BracketPair {
6268 start: "/* ".to_string(),
6269 end: "*/".to_string(),
6270 close: true,
6271 surround: true,
6272 ..Default::default()
6273 },
6274 ],
6275 ..Default::default()
6276 },
6277 ..Default::default()
6278 },
6279 Some(tree_sitter_rust::LANGUAGE.into()),
6280 ));
6281
6282 let text = r#"
6283 a
6284 b
6285 c
6286 "#
6287 .unindent();
6288
6289 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
6290 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6291 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6292 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6293 .await;
6294
6295 view.update(cx, |view, cx| {
6296 view.change_selections(None, cx, |s| {
6297 s.select_display_ranges([
6298 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6299 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6300 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
6301 ])
6302 });
6303
6304 view.handle_input("{", cx);
6305 view.handle_input("{", cx);
6306 view.handle_input("{", cx);
6307 assert_eq!(
6308 view.text(cx),
6309 "
6310 {{{a}}}
6311 {{{b}}}
6312 {{{c}}}
6313 "
6314 .unindent()
6315 );
6316 assert_eq!(
6317 view.selections.display_ranges(cx),
6318 [
6319 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
6320 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
6321 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
6322 ]
6323 );
6324
6325 view.undo(&Undo, cx);
6326 view.undo(&Undo, cx);
6327 view.undo(&Undo, cx);
6328 assert_eq!(
6329 view.text(cx),
6330 "
6331 a
6332 b
6333 c
6334 "
6335 .unindent()
6336 );
6337 assert_eq!(
6338 view.selections.display_ranges(cx),
6339 [
6340 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6341 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6342 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6343 ]
6344 );
6345
6346 // Ensure inserting the first character of a multi-byte bracket pair
6347 // doesn't surround the selections with the bracket.
6348 view.handle_input("/", cx);
6349 assert_eq!(
6350 view.text(cx),
6351 "
6352 /
6353 /
6354 /
6355 "
6356 .unindent()
6357 );
6358 assert_eq!(
6359 view.selections.display_ranges(cx),
6360 [
6361 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6362 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6363 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6364 ]
6365 );
6366
6367 view.undo(&Undo, cx);
6368 assert_eq!(
6369 view.text(cx),
6370 "
6371 a
6372 b
6373 c
6374 "
6375 .unindent()
6376 );
6377 assert_eq!(
6378 view.selections.display_ranges(cx),
6379 [
6380 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6381 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6382 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6383 ]
6384 );
6385
6386 // Ensure inserting the last character of a multi-byte bracket pair
6387 // doesn't surround the selections with the bracket.
6388 view.handle_input("*", cx);
6389 assert_eq!(
6390 view.text(cx),
6391 "
6392 *
6393 *
6394 *
6395 "
6396 .unindent()
6397 );
6398 assert_eq!(
6399 view.selections.display_ranges(cx),
6400 [
6401 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6402 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6403 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6404 ]
6405 );
6406 });
6407}
6408
6409#[gpui::test]
6410async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
6411 init_test(cx, |_| {});
6412
6413 let language = Arc::new(Language::new(
6414 LanguageConfig {
6415 brackets: BracketPairConfig {
6416 pairs: vec![BracketPair {
6417 start: "{".to_string(),
6418 end: "}".to_string(),
6419 close: true,
6420 surround: true,
6421 newline: true,
6422 }],
6423 ..Default::default()
6424 },
6425 autoclose_before: "}".to_string(),
6426 ..Default::default()
6427 },
6428 Some(tree_sitter_rust::LANGUAGE.into()),
6429 ));
6430
6431 let text = r#"
6432 a
6433 b
6434 c
6435 "#
6436 .unindent();
6437
6438 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
6439 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6440 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6441 editor
6442 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6443 .await;
6444
6445 editor.update(cx, |editor, cx| {
6446 editor.change_selections(None, cx, |s| {
6447 s.select_ranges([
6448 Point::new(0, 1)..Point::new(0, 1),
6449 Point::new(1, 1)..Point::new(1, 1),
6450 Point::new(2, 1)..Point::new(2, 1),
6451 ])
6452 });
6453
6454 editor.handle_input("{", cx);
6455 editor.handle_input("{", cx);
6456 editor.handle_input("_", cx);
6457 assert_eq!(
6458 editor.text(cx),
6459 "
6460 a{{_}}
6461 b{{_}}
6462 c{{_}}
6463 "
6464 .unindent()
6465 );
6466 assert_eq!(
6467 editor.selections.ranges::<Point>(cx),
6468 [
6469 Point::new(0, 4)..Point::new(0, 4),
6470 Point::new(1, 4)..Point::new(1, 4),
6471 Point::new(2, 4)..Point::new(2, 4)
6472 ]
6473 );
6474
6475 editor.backspace(&Default::default(), cx);
6476 editor.backspace(&Default::default(), cx);
6477 assert_eq!(
6478 editor.text(cx),
6479 "
6480 a{}
6481 b{}
6482 c{}
6483 "
6484 .unindent()
6485 );
6486 assert_eq!(
6487 editor.selections.ranges::<Point>(cx),
6488 [
6489 Point::new(0, 2)..Point::new(0, 2),
6490 Point::new(1, 2)..Point::new(1, 2),
6491 Point::new(2, 2)..Point::new(2, 2)
6492 ]
6493 );
6494
6495 editor.delete_to_previous_word_start(&Default::default(), cx);
6496 assert_eq!(
6497 editor.text(cx),
6498 "
6499 a
6500 b
6501 c
6502 "
6503 .unindent()
6504 );
6505 assert_eq!(
6506 editor.selections.ranges::<Point>(cx),
6507 [
6508 Point::new(0, 1)..Point::new(0, 1),
6509 Point::new(1, 1)..Point::new(1, 1),
6510 Point::new(2, 1)..Point::new(2, 1)
6511 ]
6512 );
6513 });
6514}
6515
6516#[gpui::test]
6517async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut gpui::TestAppContext) {
6518 init_test(cx, |settings| {
6519 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6520 });
6521
6522 let mut cx = EditorTestContext::new(cx).await;
6523
6524 let language = Arc::new(Language::new(
6525 LanguageConfig {
6526 brackets: BracketPairConfig {
6527 pairs: vec![
6528 BracketPair {
6529 start: "{".to_string(),
6530 end: "}".to_string(),
6531 close: true,
6532 surround: true,
6533 newline: true,
6534 },
6535 BracketPair {
6536 start: "(".to_string(),
6537 end: ")".to_string(),
6538 close: true,
6539 surround: true,
6540 newline: true,
6541 },
6542 BracketPair {
6543 start: "[".to_string(),
6544 end: "]".to_string(),
6545 close: false,
6546 surround: true,
6547 newline: true,
6548 },
6549 ],
6550 ..Default::default()
6551 },
6552 autoclose_before: "})]".to_string(),
6553 ..Default::default()
6554 },
6555 Some(tree_sitter_rust::LANGUAGE.into()),
6556 ));
6557
6558 cx.language_registry().add(language.clone());
6559 cx.update_buffer(|buffer, cx| {
6560 buffer.set_language(Some(language), cx);
6561 });
6562
6563 cx.set_state(
6564 &"
6565 {(ˇ)}
6566 [[ˇ]]
6567 {(ˇ)}
6568 "
6569 .unindent(),
6570 );
6571
6572 cx.update_editor(|view, cx| {
6573 view.backspace(&Default::default(), cx);
6574 view.backspace(&Default::default(), cx);
6575 });
6576
6577 cx.assert_editor_state(
6578 &"
6579 ˇ
6580 ˇ]]
6581 ˇ
6582 "
6583 .unindent(),
6584 );
6585
6586 cx.update_editor(|view, cx| {
6587 view.handle_input("{", cx);
6588 view.handle_input("{", cx);
6589 view.move_right(&MoveRight, cx);
6590 view.move_right(&MoveRight, cx);
6591 view.move_left(&MoveLeft, cx);
6592 view.move_left(&MoveLeft, cx);
6593 view.backspace(&Default::default(), cx);
6594 });
6595
6596 cx.assert_editor_state(
6597 &"
6598 {ˇ}
6599 {ˇ}]]
6600 {ˇ}
6601 "
6602 .unindent(),
6603 );
6604
6605 cx.update_editor(|view, cx| {
6606 view.backspace(&Default::default(), cx);
6607 });
6608
6609 cx.assert_editor_state(
6610 &"
6611 ˇ
6612 ˇ]]
6613 ˇ
6614 "
6615 .unindent(),
6616 );
6617}
6618
6619#[gpui::test]
6620async fn test_auto_replace_emoji_shortcode(cx: &mut gpui::TestAppContext) {
6621 init_test(cx, |_| {});
6622
6623 let language = Arc::new(Language::new(
6624 LanguageConfig::default(),
6625 Some(tree_sitter_rust::LANGUAGE.into()),
6626 ));
6627
6628 let buffer = cx.new_model(|cx| Buffer::local("", cx).with_language(language, cx));
6629 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6630 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6631 editor
6632 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6633 .await;
6634
6635 editor.update(cx, |editor, cx| {
6636 editor.set_auto_replace_emoji_shortcode(true);
6637
6638 editor.handle_input("Hello ", cx);
6639 editor.handle_input(":wave", cx);
6640 assert_eq!(editor.text(cx), "Hello :wave".unindent());
6641
6642 editor.handle_input(":", cx);
6643 assert_eq!(editor.text(cx), "Hello 👋".unindent());
6644
6645 editor.handle_input(" :smile", cx);
6646 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
6647
6648 editor.handle_input(":", cx);
6649 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
6650
6651 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
6652 editor.handle_input(":wave", cx);
6653 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
6654
6655 editor.handle_input(":", cx);
6656 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
6657
6658 editor.handle_input(":1", cx);
6659 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
6660
6661 editor.handle_input(":", cx);
6662 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
6663
6664 // Ensure shortcode does not get replaced when it is part of a word
6665 editor.handle_input(" Test:wave", cx);
6666 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
6667
6668 editor.handle_input(":", cx);
6669 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
6670
6671 editor.set_auto_replace_emoji_shortcode(false);
6672
6673 // Ensure shortcode does not get replaced when auto replace is off
6674 editor.handle_input(" :wave", cx);
6675 assert_eq!(
6676 editor.text(cx),
6677 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
6678 );
6679
6680 editor.handle_input(":", cx);
6681 assert_eq!(
6682 editor.text(cx),
6683 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
6684 );
6685 });
6686}
6687
6688#[gpui::test]
6689async fn test_snippet_placeholder_choices(cx: &mut gpui::TestAppContext) {
6690 init_test(cx, |_| {});
6691
6692 let (text, insertion_ranges) = marked_text_ranges(
6693 indoc! {"
6694 ˇ
6695 "},
6696 false,
6697 );
6698
6699 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
6700 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6701
6702 _ = editor.update(cx, |editor, cx| {
6703 let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
6704
6705 editor
6706 .insert_snippet(&insertion_ranges, snippet, cx)
6707 .unwrap();
6708
6709 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
6710 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
6711 assert_eq!(editor.text(cx), expected_text);
6712 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
6713 }
6714
6715 assert(
6716 editor,
6717 cx,
6718 indoc! {"
6719 type «» =•
6720 "},
6721 );
6722
6723 assert!(editor.context_menu_visible(), "There should be a matches");
6724 });
6725}
6726
6727#[gpui::test]
6728async fn test_snippets(cx: &mut gpui::TestAppContext) {
6729 init_test(cx, |_| {});
6730
6731 let (text, insertion_ranges) = marked_text_ranges(
6732 indoc! {"
6733 a.ˇ b
6734 a.ˇ b
6735 a.ˇ b
6736 "},
6737 false,
6738 );
6739
6740 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
6741 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6742
6743 editor.update(cx, |editor, cx| {
6744 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
6745
6746 editor
6747 .insert_snippet(&insertion_ranges, snippet, cx)
6748 .unwrap();
6749
6750 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
6751 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
6752 assert_eq!(editor.text(cx), expected_text);
6753 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
6754 }
6755
6756 assert(
6757 editor,
6758 cx,
6759 indoc! {"
6760 a.f(«one», two, «three») b
6761 a.f(«one», two, «three») b
6762 a.f(«one», two, «three») b
6763 "},
6764 );
6765
6766 // Can't move earlier than the first tab stop
6767 assert!(!editor.move_to_prev_snippet_tabstop(cx));
6768 assert(
6769 editor,
6770 cx,
6771 indoc! {"
6772 a.f(«one», two, «three») b
6773 a.f(«one», two, «three») b
6774 a.f(«one», two, «three») b
6775 "},
6776 );
6777
6778 assert!(editor.move_to_next_snippet_tabstop(cx));
6779 assert(
6780 editor,
6781 cx,
6782 indoc! {"
6783 a.f(one, «two», three) b
6784 a.f(one, «two», three) b
6785 a.f(one, «two», three) b
6786 "},
6787 );
6788
6789 editor.move_to_prev_snippet_tabstop(cx);
6790 assert(
6791 editor,
6792 cx,
6793 indoc! {"
6794 a.f(«one», two, «three») b
6795 a.f(«one», two, «three») b
6796 a.f(«one», two, «three») b
6797 "},
6798 );
6799
6800 assert!(editor.move_to_next_snippet_tabstop(cx));
6801 assert(
6802 editor,
6803 cx,
6804 indoc! {"
6805 a.f(one, «two», three) b
6806 a.f(one, «two», three) b
6807 a.f(one, «two», three) b
6808 "},
6809 );
6810 assert!(editor.move_to_next_snippet_tabstop(cx));
6811 assert(
6812 editor,
6813 cx,
6814 indoc! {"
6815 a.f(one, two, three)ˇ b
6816 a.f(one, two, three)ˇ b
6817 a.f(one, two, three)ˇ b
6818 "},
6819 );
6820
6821 // As soon as the last tab stop is reached, snippet state is gone
6822 editor.move_to_prev_snippet_tabstop(cx);
6823 assert(
6824 editor,
6825 cx,
6826 indoc! {"
6827 a.f(one, two, three)ˇ b
6828 a.f(one, two, three)ˇ b
6829 a.f(one, two, three)ˇ b
6830 "},
6831 );
6832 });
6833}
6834
6835#[gpui::test]
6836async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
6837 init_test(cx, |_| {});
6838
6839 let fs = FakeFs::new(cx.executor());
6840 fs.insert_file("/file.rs", Default::default()).await;
6841
6842 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6843
6844 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6845 language_registry.add(rust_lang());
6846 let mut fake_servers = language_registry.register_fake_lsp(
6847 "Rust",
6848 FakeLspAdapter {
6849 capabilities: lsp::ServerCapabilities {
6850 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6851 ..Default::default()
6852 },
6853 ..Default::default()
6854 },
6855 );
6856
6857 let buffer = project
6858 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6859 .await
6860 .unwrap();
6861
6862 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6863 let (editor, cx) =
6864 cx.add_window_view(|cx| build_editor_with_project(project.clone(), buffer, cx));
6865 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6866 assert!(cx.read(|cx| editor.is_dirty(cx)));
6867
6868 cx.executor().start_waiting();
6869 let fake_server = fake_servers.next().await.unwrap();
6870
6871 let save = editor
6872 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6873 .unwrap();
6874 fake_server
6875 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6876 assert_eq!(
6877 params.text_document.uri,
6878 lsp::Url::from_file_path("/file.rs").unwrap()
6879 );
6880 assert_eq!(params.options.tab_size, 4);
6881 Ok(Some(vec![lsp::TextEdit::new(
6882 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6883 ", ".to_string(),
6884 )]))
6885 })
6886 .next()
6887 .await;
6888 cx.executor().start_waiting();
6889 save.await;
6890
6891 assert_eq!(
6892 editor.update(cx, |editor, cx| editor.text(cx)),
6893 "one, two\nthree\n"
6894 );
6895 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6896
6897 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6898 assert!(cx.read(|cx| editor.is_dirty(cx)));
6899
6900 // Ensure we can still save even if formatting hangs.
6901 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6902 assert_eq!(
6903 params.text_document.uri,
6904 lsp::Url::from_file_path("/file.rs").unwrap()
6905 );
6906 futures::future::pending::<()>().await;
6907 unreachable!()
6908 });
6909 let save = editor
6910 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6911 .unwrap();
6912 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6913 cx.executor().start_waiting();
6914 save.await;
6915 assert_eq!(
6916 editor.update(cx, |editor, cx| editor.text(cx)),
6917 "one\ntwo\nthree\n"
6918 );
6919 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6920
6921 // For non-dirty buffer, no formatting request should be sent
6922 let save = editor
6923 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6924 .unwrap();
6925 let _pending_format_request = fake_server
6926 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
6927 panic!("Should not be invoked on non-dirty buffer");
6928 })
6929 .next();
6930 cx.executor().start_waiting();
6931 save.await;
6932
6933 // Set rust language override and assert overridden tabsize is sent to language server
6934 update_test_language_settings(cx, |settings| {
6935 settings.languages.insert(
6936 "Rust".into(),
6937 LanguageSettingsContent {
6938 tab_size: NonZeroU32::new(8),
6939 ..Default::default()
6940 },
6941 );
6942 });
6943
6944 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
6945 assert!(cx.read(|cx| editor.is_dirty(cx)));
6946 let save = editor
6947 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6948 .unwrap();
6949 fake_server
6950 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6951 assert_eq!(
6952 params.text_document.uri,
6953 lsp::Url::from_file_path("/file.rs").unwrap()
6954 );
6955 assert_eq!(params.options.tab_size, 8);
6956 Ok(Some(vec![]))
6957 })
6958 .next()
6959 .await;
6960 cx.executor().start_waiting();
6961 save.await;
6962}
6963
6964#[gpui::test]
6965async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
6966 init_test(cx, |_| {});
6967
6968 let cols = 4;
6969 let rows = 10;
6970 let sample_text_1 = sample_text(rows, cols, 'a');
6971 assert_eq!(
6972 sample_text_1,
6973 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
6974 );
6975 let sample_text_2 = sample_text(rows, cols, 'l');
6976 assert_eq!(
6977 sample_text_2,
6978 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
6979 );
6980 let sample_text_3 = sample_text(rows, cols, 'v');
6981 assert_eq!(
6982 sample_text_3,
6983 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
6984 );
6985
6986 let fs = FakeFs::new(cx.executor());
6987 fs.insert_tree(
6988 "/a",
6989 json!({
6990 "main.rs": sample_text_1,
6991 "other.rs": sample_text_2,
6992 "lib.rs": sample_text_3,
6993 }),
6994 )
6995 .await;
6996
6997 let project = Project::test(fs, ["/a".as_ref()], cx).await;
6998 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
6999 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
7000
7001 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7002 language_registry.add(rust_lang());
7003 let mut fake_servers = language_registry.register_fake_lsp(
7004 "Rust",
7005 FakeLspAdapter {
7006 capabilities: lsp::ServerCapabilities {
7007 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7008 ..Default::default()
7009 },
7010 ..Default::default()
7011 },
7012 );
7013
7014 let worktree = project.update(cx, |project, cx| {
7015 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
7016 assert_eq!(worktrees.len(), 1);
7017 worktrees.pop().unwrap()
7018 });
7019 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
7020
7021 let buffer_1 = project
7022 .update(cx, |project, cx| {
7023 project.open_buffer((worktree_id, "main.rs"), cx)
7024 })
7025 .await
7026 .unwrap();
7027 let buffer_2 = project
7028 .update(cx, |project, cx| {
7029 project.open_buffer((worktree_id, "other.rs"), cx)
7030 })
7031 .await
7032 .unwrap();
7033 let buffer_3 = project
7034 .update(cx, |project, cx| {
7035 project.open_buffer((worktree_id, "lib.rs"), cx)
7036 })
7037 .await
7038 .unwrap();
7039
7040 let multi_buffer = cx.new_model(|cx| {
7041 let mut multi_buffer = MultiBuffer::new(ReadWrite);
7042 multi_buffer.push_excerpts(
7043 buffer_1.clone(),
7044 [
7045 ExcerptRange {
7046 context: Point::new(0, 0)..Point::new(3, 0),
7047 primary: None,
7048 },
7049 ExcerptRange {
7050 context: Point::new(5, 0)..Point::new(7, 0),
7051 primary: None,
7052 },
7053 ExcerptRange {
7054 context: Point::new(9, 0)..Point::new(10, 4),
7055 primary: None,
7056 },
7057 ],
7058 cx,
7059 );
7060 multi_buffer.push_excerpts(
7061 buffer_2.clone(),
7062 [
7063 ExcerptRange {
7064 context: Point::new(0, 0)..Point::new(3, 0),
7065 primary: None,
7066 },
7067 ExcerptRange {
7068 context: Point::new(5, 0)..Point::new(7, 0),
7069 primary: None,
7070 },
7071 ExcerptRange {
7072 context: Point::new(9, 0)..Point::new(10, 4),
7073 primary: None,
7074 },
7075 ],
7076 cx,
7077 );
7078 multi_buffer.push_excerpts(
7079 buffer_3.clone(),
7080 [
7081 ExcerptRange {
7082 context: Point::new(0, 0)..Point::new(3, 0),
7083 primary: None,
7084 },
7085 ExcerptRange {
7086 context: Point::new(5, 0)..Point::new(7, 0),
7087 primary: None,
7088 },
7089 ExcerptRange {
7090 context: Point::new(9, 0)..Point::new(10, 4),
7091 primary: None,
7092 },
7093 ],
7094 cx,
7095 );
7096 multi_buffer
7097 });
7098 let multi_buffer_editor = cx.new_view(|cx| {
7099 Editor::new(
7100 EditorMode::Full,
7101 multi_buffer,
7102 Some(project.clone()),
7103 true,
7104 cx,
7105 )
7106 });
7107
7108 multi_buffer_editor.update(cx, |editor, cx| {
7109 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
7110 editor.insert("|one|two|three|", cx);
7111 });
7112 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7113 multi_buffer_editor.update(cx, |editor, cx| {
7114 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
7115 s.select_ranges(Some(60..70))
7116 });
7117 editor.insert("|four|five|six|", cx);
7118 });
7119 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7120
7121 // First two buffers should be edited, but not the third one.
7122 assert_eq!(
7123 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7124 "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}",
7125 );
7126 buffer_1.update(cx, |buffer, _| {
7127 assert!(buffer.is_dirty());
7128 assert_eq!(
7129 buffer.text(),
7130 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
7131 )
7132 });
7133 buffer_2.update(cx, |buffer, _| {
7134 assert!(buffer.is_dirty());
7135 assert_eq!(
7136 buffer.text(),
7137 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
7138 )
7139 });
7140 buffer_3.update(cx, |buffer, _| {
7141 assert!(!buffer.is_dirty());
7142 assert_eq!(buffer.text(), sample_text_3,)
7143 });
7144 cx.executor().run_until_parked();
7145
7146 cx.executor().start_waiting();
7147 let save = multi_buffer_editor
7148 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7149 .unwrap();
7150
7151 let fake_server = fake_servers.next().await.unwrap();
7152 fake_server
7153 .server
7154 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7155 Ok(Some(vec![lsp::TextEdit::new(
7156 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7157 format!("[{} formatted]", params.text_document.uri),
7158 )]))
7159 })
7160 .detach();
7161 save.await;
7162
7163 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
7164 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
7165 assert_eq!(
7166 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7167 "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}",
7168 );
7169 buffer_1.update(cx, |buffer, _| {
7170 assert!(!buffer.is_dirty());
7171 assert_eq!(
7172 buffer.text(),
7173 "a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n",
7174 )
7175 });
7176 buffer_2.update(cx, |buffer, _| {
7177 assert!(!buffer.is_dirty());
7178 assert_eq!(
7179 buffer.text(),
7180 "lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n",
7181 )
7182 });
7183 buffer_3.update(cx, |buffer, _| {
7184 assert!(!buffer.is_dirty());
7185 assert_eq!(buffer.text(), sample_text_3,)
7186 });
7187}
7188
7189#[gpui::test]
7190async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
7191 init_test(cx, |_| {});
7192
7193 let fs = FakeFs::new(cx.executor());
7194 fs.insert_file("/file.rs", Default::default()).await;
7195
7196 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7197
7198 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7199 language_registry.add(rust_lang());
7200 let mut fake_servers = language_registry.register_fake_lsp(
7201 "Rust",
7202 FakeLspAdapter {
7203 capabilities: lsp::ServerCapabilities {
7204 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
7205 ..Default::default()
7206 },
7207 ..Default::default()
7208 },
7209 );
7210
7211 let buffer = project
7212 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
7213 .await
7214 .unwrap();
7215
7216 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
7217 let (editor, cx) =
7218 cx.add_window_view(|cx| build_editor_with_project(project.clone(), buffer, cx));
7219 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7220 assert!(cx.read(|cx| editor.is_dirty(cx)));
7221
7222 cx.executor().start_waiting();
7223 let fake_server = fake_servers.next().await.unwrap();
7224
7225 let save = editor
7226 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7227 .unwrap();
7228 fake_server
7229 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7230 assert_eq!(
7231 params.text_document.uri,
7232 lsp::Url::from_file_path("/file.rs").unwrap()
7233 );
7234 assert_eq!(params.options.tab_size, 4);
7235 Ok(Some(vec![lsp::TextEdit::new(
7236 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7237 ", ".to_string(),
7238 )]))
7239 })
7240 .next()
7241 .await;
7242 cx.executor().start_waiting();
7243 save.await;
7244 assert_eq!(
7245 editor.update(cx, |editor, cx| editor.text(cx)),
7246 "one, two\nthree\n"
7247 );
7248 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7249
7250 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7251 assert!(cx.read(|cx| editor.is_dirty(cx)));
7252
7253 // Ensure we can still save even if formatting hangs.
7254 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
7255 move |params, _| async move {
7256 assert_eq!(
7257 params.text_document.uri,
7258 lsp::Url::from_file_path("/file.rs").unwrap()
7259 );
7260 futures::future::pending::<()>().await;
7261 unreachable!()
7262 },
7263 );
7264 let save = editor
7265 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7266 .unwrap();
7267 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7268 cx.executor().start_waiting();
7269 save.await;
7270 assert_eq!(
7271 editor.update(cx, |editor, cx| editor.text(cx)),
7272 "one\ntwo\nthree\n"
7273 );
7274 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7275
7276 // For non-dirty buffer, no formatting request should be sent
7277 let save = editor
7278 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7279 .unwrap();
7280 let _pending_format_request = fake_server
7281 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7282 panic!("Should not be invoked on non-dirty buffer");
7283 })
7284 .next();
7285 cx.executor().start_waiting();
7286 save.await;
7287
7288 // Set Rust language override and assert overridden tabsize is sent to language server
7289 update_test_language_settings(cx, |settings| {
7290 settings.languages.insert(
7291 "Rust".into(),
7292 LanguageSettingsContent {
7293 tab_size: NonZeroU32::new(8),
7294 ..Default::default()
7295 },
7296 );
7297 });
7298
7299 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
7300 assert!(cx.read(|cx| editor.is_dirty(cx)));
7301 let save = editor
7302 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7303 .unwrap();
7304 fake_server
7305 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7306 assert_eq!(
7307 params.text_document.uri,
7308 lsp::Url::from_file_path("/file.rs").unwrap()
7309 );
7310 assert_eq!(params.options.tab_size, 8);
7311 Ok(Some(vec![]))
7312 })
7313 .next()
7314 .await;
7315 cx.executor().start_waiting();
7316 save.await;
7317}
7318
7319#[gpui::test]
7320async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
7321 init_test(cx, |settings| {
7322 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
7323 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
7324 ))
7325 });
7326
7327 let fs = FakeFs::new(cx.executor());
7328 fs.insert_file("/file.rs", Default::default()).await;
7329
7330 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7331
7332 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7333 language_registry.add(Arc::new(Language::new(
7334 LanguageConfig {
7335 name: "Rust".into(),
7336 matcher: LanguageMatcher {
7337 path_suffixes: vec!["rs".to_string()],
7338 ..Default::default()
7339 },
7340 ..LanguageConfig::default()
7341 },
7342 Some(tree_sitter_rust::LANGUAGE.into()),
7343 )));
7344 update_test_language_settings(cx, |settings| {
7345 // Enable Prettier formatting for the same buffer, and ensure
7346 // LSP is called instead of Prettier.
7347 settings.defaults.prettier = Some(PrettierSettings {
7348 allowed: true,
7349 ..PrettierSettings::default()
7350 });
7351 });
7352 let mut fake_servers = language_registry.register_fake_lsp(
7353 "Rust",
7354 FakeLspAdapter {
7355 capabilities: lsp::ServerCapabilities {
7356 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7357 ..Default::default()
7358 },
7359 ..Default::default()
7360 },
7361 );
7362
7363 let buffer = project
7364 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
7365 .await
7366 .unwrap();
7367
7368 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
7369 let (editor, cx) =
7370 cx.add_window_view(|cx| build_editor_with_project(project.clone(), buffer, cx));
7371 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7372
7373 cx.executor().start_waiting();
7374 let fake_server = fake_servers.next().await.unwrap();
7375
7376 let format = editor
7377 .update(cx, |editor, cx| {
7378 editor.perform_format(
7379 project.clone(),
7380 FormatTrigger::Manual,
7381 FormatTarget::Buffers,
7382 cx,
7383 )
7384 })
7385 .unwrap();
7386 fake_server
7387 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7388 assert_eq!(
7389 params.text_document.uri,
7390 lsp::Url::from_file_path("/file.rs").unwrap()
7391 );
7392 assert_eq!(params.options.tab_size, 4);
7393 Ok(Some(vec![lsp::TextEdit::new(
7394 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7395 ", ".to_string(),
7396 )]))
7397 })
7398 .next()
7399 .await;
7400 cx.executor().start_waiting();
7401 format.await;
7402 assert_eq!(
7403 editor.update(cx, |editor, cx| editor.text(cx)),
7404 "one, two\nthree\n"
7405 );
7406
7407 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7408 // Ensure we don't lock if formatting hangs.
7409 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7410 assert_eq!(
7411 params.text_document.uri,
7412 lsp::Url::from_file_path("/file.rs").unwrap()
7413 );
7414 futures::future::pending::<()>().await;
7415 unreachable!()
7416 });
7417 let format = editor
7418 .update(cx, |editor, cx| {
7419 editor.perform_format(project, FormatTrigger::Manual, FormatTarget::Buffers, cx)
7420 })
7421 .unwrap();
7422 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7423 cx.executor().start_waiting();
7424 format.await;
7425 assert_eq!(
7426 editor.update(cx, |editor, cx| editor.text(cx)),
7427 "one\ntwo\nthree\n"
7428 );
7429}
7430
7431#[gpui::test]
7432async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
7433 init_test(cx, |_| {});
7434
7435 let mut cx = EditorLspTestContext::new_rust(
7436 lsp::ServerCapabilities {
7437 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7438 ..Default::default()
7439 },
7440 cx,
7441 )
7442 .await;
7443
7444 cx.set_state(indoc! {"
7445 one.twoˇ
7446 "});
7447
7448 // The format request takes a long time. When it completes, it inserts
7449 // a newline and an indent before the `.`
7450 cx.lsp
7451 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
7452 let executor = cx.background_executor().clone();
7453 async move {
7454 executor.timer(Duration::from_millis(100)).await;
7455 Ok(Some(vec![lsp::TextEdit {
7456 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
7457 new_text: "\n ".into(),
7458 }]))
7459 }
7460 });
7461
7462 // Submit a format request.
7463 let format_1 = cx
7464 .update_editor(|editor, cx| editor.format(&Format, cx))
7465 .unwrap();
7466 cx.executor().run_until_parked();
7467
7468 // Submit a second format request.
7469 let format_2 = cx
7470 .update_editor(|editor, cx| editor.format(&Format, cx))
7471 .unwrap();
7472 cx.executor().run_until_parked();
7473
7474 // Wait for both format requests to complete
7475 cx.executor().advance_clock(Duration::from_millis(200));
7476 cx.executor().start_waiting();
7477 format_1.await.unwrap();
7478 cx.executor().start_waiting();
7479 format_2.await.unwrap();
7480
7481 // The formatting edits only happens once.
7482 cx.assert_editor_state(indoc! {"
7483 one
7484 .twoˇ
7485 "});
7486}
7487
7488#[gpui::test]
7489async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
7490 init_test(cx, |settings| {
7491 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
7492 });
7493
7494 let mut cx = EditorLspTestContext::new_rust(
7495 lsp::ServerCapabilities {
7496 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7497 ..Default::default()
7498 },
7499 cx,
7500 )
7501 .await;
7502
7503 // Set up a buffer white some trailing whitespace and no trailing newline.
7504 cx.set_state(
7505 &[
7506 "one ", //
7507 "twoˇ", //
7508 "three ", //
7509 "four", //
7510 ]
7511 .join("\n"),
7512 );
7513
7514 // Submit a format request.
7515 let format = cx
7516 .update_editor(|editor, cx| editor.format(&Format, cx))
7517 .unwrap();
7518
7519 // Record which buffer changes have been sent to the language server
7520 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
7521 cx.lsp
7522 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
7523 let buffer_changes = buffer_changes.clone();
7524 move |params, _| {
7525 buffer_changes.lock().extend(
7526 params
7527 .content_changes
7528 .into_iter()
7529 .map(|e| (e.range.unwrap(), e.text)),
7530 );
7531 }
7532 });
7533
7534 // Handle formatting requests to the language server.
7535 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
7536 let buffer_changes = buffer_changes.clone();
7537 move |_, _| {
7538 // When formatting is requested, trailing whitespace has already been stripped,
7539 // and the trailing newline has already been added.
7540 assert_eq!(
7541 &buffer_changes.lock()[1..],
7542 &[
7543 (
7544 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
7545 "".into()
7546 ),
7547 (
7548 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
7549 "".into()
7550 ),
7551 (
7552 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
7553 "\n".into()
7554 ),
7555 ]
7556 );
7557
7558 // Insert blank lines between each line of the buffer.
7559 async move {
7560 Ok(Some(vec![
7561 lsp::TextEdit {
7562 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
7563 new_text: "\n".into(),
7564 },
7565 lsp::TextEdit {
7566 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
7567 new_text: "\n".into(),
7568 },
7569 ]))
7570 }
7571 }
7572 });
7573
7574 // After formatting the buffer, the trailing whitespace is stripped,
7575 // a newline is appended, and the edits provided by the language server
7576 // have been applied.
7577 format.await.unwrap();
7578 cx.assert_editor_state(
7579 &[
7580 "one", //
7581 "", //
7582 "twoˇ", //
7583 "", //
7584 "three", //
7585 "four", //
7586 "", //
7587 ]
7588 .join("\n"),
7589 );
7590
7591 // Undoing the formatting undoes the trailing whitespace removal, the
7592 // trailing newline, and the LSP edits.
7593 cx.update_buffer(|buffer, cx| buffer.undo(cx));
7594 cx.assert_editor_state(
7595 &[
7596 "one ", //
7597 "twoˇ", //
7598 "three ", //
7599 "four", //
7600 ]
7601 .join("\n"),
7602 );
7603}
7604
7605#[gpui::test]
7606async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
7607 cx: &mut gpui::TestAppContext,
7608) {
7609 init_test(cx, |_| {});
7610
7611 cx.update(|cx| {
7612 cx.update_global::<SettingsStore, _>(|settings, cx| {
7613 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7614 settings.auto_signature_help = Some(true);
7615 });
7616 });
7617 });
7618
7619 let mut cx = EditorLspTestContext::new_rust(
7620 lsp::ServerCapabilities {
7621 signature_help_provider: Some(lsp::SignatureHelpOptions {
7622 ..Default::default()
7623 }),
7624 ..Default::default()
7625 },
7626 cx,
7627 )
7628 .await;
7629
7630 let language = Language::new(
7631 LanguageConfig {
7632 name: "Rust".into(),
7633 brackets: BracketPairConfig {
7634 pairs: vec![
7635 BracketPair {
7636 start: "{".to_string(),
7637 end: "}".to_string(),
7638 close: true,
7639 surround: true,
7640 newline: true,
7641 },
7642 BracketPair {
7643 start: "(".to_string(),
7644 end: ")".to_string(),
7645 close: true,
7646 surround: true,
7647 newline: true,
7648 },
7649 BracketPair {
7650 start: "/*".to_string(),
7651 end: " */".to_string(),
7652 close: true,
7653 surround: true,
7654 newline: true,
7655 },
7656 BracketPair {
7657 start: "[".to_string(),
7658 end: "]".to_string(),
7659 close: false,
7660 surround: false,
7661 newline: true,
7662 },
7663 BracketPair {
7664 start: "\"".to_string(),
7665 end: "\"".to_string(),
7666 close: true,
7667 surround: true,
7668 newline: false,
7669 },
7670 BracketPair {
7671 start: "<".to_string(),
7672 end: ">".to_string(),
7673 close: false,
7674 surround: true,
7675 newline: true,
7676 },
7677 ],
7678 ..Default::default()
7679 },
7680 autoclose_before: "})]".to_string(),
7681 ..Default::default()
7682 },
7683 Some(tree_sitter_rust::LANGUAGE.into()),
7684 );
7685 let language = Arc::new(language);
7686
7687 cx.language_registry().add(language.clone());
7688 cx.update_buffer(|buffer, cx| {
7689 buffer.set_language(Some(language), cx);
7690 });
7691
7692 cx.set_state(
7693 &r#"
7694 fn main() {
7695 sampleˇ
7696 }
7697 "#
7698 .unindent(),
7699 );
7700
7701 cx.update_editor(|view, cx| {
7702 view.handle_input("(", cx);
7703 });
7704 cx.assert_editor_state(
7705 &"
7706 fn main() {
7707 sample(ˇ)
7708 }
7709 "
7710 .unindent(),
7711 );
7712
7713 let mocked_response = lsp::SignatureHelp {
7714 signatures: vec![lsp::SignatureInformation {
7715 label: "fn sample(param1: u8, param2: u8)".to_string(),
7716 documentation: None,
7717 parameters: Some(vec![
7718 lsp::ParameterInformation {
7719 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7720 documentation: None,
7721 },
7722 lsp::ParameterInformation {
7723 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7724 documentation: None,
7725 },
7726 ]),
7727 active_parameter: None,
7728 }],
7729 active_signature: Some(0),
7730 active_parameter: Some(0),
7731 };
7732 handle_signature_help_request(&mut cx, mocked_response).await;
7733
7734 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7735 .await;
7736
7737 cx.editor(|editor, _| {
7738 let signature_help_state = editor.signature_help_state.popover().cloned();
7739 assert!(signature_help_state.is_some());
7740 let ParsedMarkdown {
7741 text, highlights, ..
7742 } = signature_help_state.unwrap().parsed_content;
7743 assert_eq!(text, "param1: u8, param2: u8");
7744 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7745 });
7746}
7747
7748#[gpui::test]
7749async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui::TestAppContext) {
7750 init_test(cx, |_| {});
7751
7752 cx.update(|cx| {
7753 cx.update_global::<SettingsStore, _>(|settings, cx| {
7754 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7755 settings.auto_signature_help = Some(false);
7756 settings.show_signature_help_after_edits = Some(false);
7757 });
7758 });
7759 });
7760
7761 let mut cx = EditorLspTestContext::new_rust(
7762 lsp::ServerCapabilities {
7763 signature_help_provider: Some(lsp::SignatureHelpOptions {
7764 ..Default::default()
7765 }),
7766 ..Default::default()
7767 },
7768 cx,
7769 )
7770 .await;
7771
7772 let language = Language::new(
7773 LanguageConfig {
7774 name: "Rust".into(),
7775 brackets: BracketPairConfig {
7776 pairs: vec![
7777 BracketPair {
7778 start: "{".to_string(),
7779 end: "}".to_string(),
7780 close: true,
7781 surround: true,
7782 newline: true,
7783 },
7784 BracketPair {
7785 start: "(".to_string(),
7786 end: ")".to_string(),
7787 close: true,
7788 surround: true,
7789 newline: true,
7790 },
7791 BracketPair {
7792 start: "/*".to_string(),
7793 end: " */".to_string(),
7794 close: true,
7795 surround: true,
7796 newline: true,
7797 },
7798 BracketPair {
7799 start: "[".to_string(),
7800 end: "]".to_string(),
7801 close: false,
7802 surround: false,
7803 newline: true,
7804 },
7805 BracketPair {
7806 start: "\"".to_string(),
7807 end: "\"".to_string(),
7808 close: true,
7809 surround: true,
7810 newline: false,
7811 },
7812 BracketPair {
7813 start: "<".to_string(),
7814 end: ">".to_string(),
7815 close: false,
7816 surround: true,
7817 newline: true,
7818 },
7819 ],
7820 ..Default::default()
7821 },
7822 autoclose_before: "})]".to_string(),
7823 ..Default::default()
7824 },
7825 Some(tree_sitter_rust::LANGUAGE.into()),
7826 );
7827 let language = Arc::new(language);
7828
7829 cx.language_registry().add(language.clone());
7830 cx.update_buffer(|buffer, cx| {
7831 buffer.set_language(Some(language), cx);
7832 });
7833
7834 // Ensure that signature_help is not called when no signature help is enabled.
7835 cx.set_state(
7836 &r#"
7837 fn main() {
7838 sampleˇ
7839 }
7840 "#
7841 .unindent(),
7842 );
7843 cx.update_editor(|view, cx| {
7844 view.handle_input("(", cx);
7845 });
7846 cx.assert_editor_state(
7847 &"
7848 fn main() {
7849 sample(ˇ)
7850 }
7851 "
7852 .unindent(),
7853 );
7854 cx.editor(|editor, _| {
7855 assert!(editor.signature_help_state.task().is_none());
7856 });
7857
7858 let mocked_response = lsp::SignatureHelp {
7859 signatures: vec![lsp::SignatureInformation {
7860 label: "fn sample(param1: u8, param2: u8)".to_string(),
7861 documentation: None,
7862 parameters: Some(vec![
7863 lsp::ParameterInformation {
7864 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7865 documentation: None,
7866 },
7867 lsp::ParameterInformation {
7868 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7869 documentation: None,
7870 },
7871 ]),
7872 active_parameter: None,
7873 }],
7874 active_signature: Some(0),
7875 active_parameter: Some(0),
7876 };
7877
7878 // Ensure that signature_help is called when enabled afte edits
7879 cx.update(|cx| {
7880 cx.update_global::<SettingsStore, _>(|settings, cx| {
7881 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7882 settings.auto_signature_help = Some(false);
7883 settings.show_signature_help_after_edits = Some(true);
7884 });
7885 });
7886 });
7887 cx.set_state(
7888 &r#"
7889 fn main() {
7890 sampleˇ
7891 }
7892 "#
7893 .unindent(),
7894 );
7895 cx.update_editor(|view, cx| {
7896 view.handle_input("(", cx);
7897 });
7898 cx.assert_editor_state(
7899 &"
7900 fn main() {
7901 sample(ˇ)
7902 }
7903 "
7904 .unindent(),
7905 );
7906 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7907 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7908 .await;
7909 cx.update_editor(|editor, _| {
7910 let signature_help_state = editor.signature_help_state.popover().cloned();
7911 assert!(signature_help_state.is_some());
7912 let ParsedMarkdown {
7913 text, highlights, ..
7914 } = signature_help_state.unwrap().parsed_content;
7915 assert_eq!(text, "param1: u8, param2: u8");
7916 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7917 editor.signature_help_state = SignatureHelpState::default();
7918 });
7919
7920 // Ensure that signature_help is called when auto signature help override is enabled
7921 cx.update(|cx| {
7922 cx.update_global::<SettingsStore, _>(|settings, cx| {
7923 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7924 settings.auto_signature_help = Some(true);
7925 settings.show_signature_help_after_edits = Some(false);
7926 });
7927 });
7928 });
7929 cx.set_state(
7930 &r#"
7931 fn main() {
7932 sampleˇ
7933 }
7934 "#
7935 .unindent(),
7936 );
7937 cx.update_editor(|view, cx| {
7938 view.handle_input("(", cx);
7939 });
7940 cx.assert_editor_state(
7941 &"
7942 fn main() {
7943 sample(ˇ)
7944 }
7945 "
7946 .unindent(),
7947 );
7948 handle_signature_help_request(&mut cx, mocked_response).await;
7949 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7950 .await;
7951 cx.editor(|editor, _| {
7952 let signature_help_state = editor.signature_help_state.popover().cloned();
7953 assert!(signature_help_state.is_some());
7954 let ParsedMarkdown {
7955 text, highlights, ..
7956 } = signature_help_state.unwrap().parsed_content;
7957 assert_eq!(text, "param1: u8, param2: u8");
7958 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7959 });
7960}
7961
7962#[gpui::test]
7963async fn test_signature_help(cx: &mut gpui::TestAppContext) {
7964 init_test(cx, |_| {});
7965 cx.update(|cx| {
7966 cx.update_global::<SettingsStore, _>(|settings, cx| {
7967 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7968 settings.auto_signature_help = Some(true);
7969 });
7970 });
7971 });
7972
7973 let mut cx = EditorLspTestContext::new_rust(
7974 lsp::ServerCapabilities {
7975 signature_help_provider: Some(lsp::SignatureHelpOptions {
7976 ..Default::default()
7977 }),
7978 ..Default::default()
7979 },
7980 cx,
7981 )
7982 .await;
7983
7984 // A test that directly calls `show_signature_help`
7985 cx.update_editor(|editor, cx| {
7986 editor.show_signature_help(&ShowSignatureHelp, cx);
7987 });
7988
7989 let mocked_response = lsp::SignatureHelp {
7990 signatures: vec![lsp::SignatureInformation {
7991 label: "fn sample(param1: u8, param2: u8)".to_string(),
7992 documentation: None,
7993 parameters: Some(vec![
7994 lsp::ParameterInformation {
7995 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7996 documentation: None,
7997 },
7998 lsp::ParameterInformation {
7999 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8000 documentation: None,
8001 },
8002 ]),
8003 active_parameter: None,
8004 }],
8005 active_signature: Some(0),
8006 active_parameter: Some(0),
8007 };
8008 handle_signature_help_request(&mut cx, mocked_response).await;
8009
8010 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8011 .await;
8012
8013 cx.editor(|editor, _| {
8014 let signature_help_state = editor.signature_help_state.popover().cloned();
8015 assert!(signature_help_state.is_some());
8016 let ParsedMarkdown {
8017 text, highlights, ..
8018 } = signature_help_state.unwrap().parsed_content;
8019 assert_eq!(text, "param1: u8, param2: u8");
8020 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
8021 });
8022
8023 // When exiting outside from inside the brackets, `signature_help` is closed.
8024 cx.set_state(indoc! {"
8025 fn main() {
8026 sample(ˇ);
8027 }
8028
8029 fn sample(param1: u8, param2: u8) {}
8030 "});
8031
8032 cx.update_editor(|editor, cx| {
8033 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
8034 });
8035
8036 let mocked_response = lsp::SignatureHelp {
8037 signatures: Vec::new(),
8038 active_signature: None,
8039 active_parameter: None,
8040 };
8041 handle_signature_help_request(&mut cx, mocked_response).await;
8042
8043 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8044 .await;
8045
8046 cx.editor(|editor, _| {
8047 assert!(!editor.signature_help_state.is_shown());
8048 });
8049
8050 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
8051 cx.set_state(indoc! {"
8052 fn main() {
8053 sample(ˇ);
8054 }
8055
8056 fn sample(param1: u8, param2: u8) {}
8057 "});
8058
8059 let mocked_response = lsp::SignatureHelp {
8060 signatures: vec![lsp::SignatureInformation {
8061 label: "fn sample(param1: u8, param2: u8)".to_string(),
8062 documentation: None,
8063 parameters: Some(vec![
8064 lsp::ParameterInformation {
8065 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8066 documentation: None,
8067 },
8068 lsp::ParameterInformation {
8069 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8070 documentation: None,
8071 },
8072 ]),
8073 active_parameter: None,
8074 }],
8075 active_signature: Some(0),
8076 active_parameter: Some(0),
8077 };
8078 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8079 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8080 .await;
8081 cx.editor(|editor, _| {
8082 assert!(editor.signature_help_state.is_shown());
8083 });
8084
8085 // Restore the popover with more parameter input
8086 cx.set_state(indoc! {"
8087 fn main() {
8088 sample(param1, param2ˇ);
8089 }
8090
8091 fn sample(param1: u8, param2: u8) {}
8092 "});
8093
8094 let mocked_response = lsp::SignatureHelp {
8095 signatures: vec![lsp::SignatureInformation {
8096 label: "fn sample(param1: u8, param2: u8)".to_string(),
8097 documentation: None,
8098 parameters: Some(vec![
8099 lsp::ParameterInformation {
8100 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8101 documentation: None,
8102 },
8103 lsp::ParameterInformation {
8104 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8105 documentation: None,
8106 },
8107 ]),
8108 active_parameter: None,
8109 }],
8110 active_signature: Some(0),
8111 active_parameter: Some(1),
8112 };
8113 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8114 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8115 .await;
8116
8117 // When selecting a range, the popover is gone.
8118 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
8119 cx.update_editor(|editor, cx| {
8120 editor.change_selections(None, cx, |s| {
8121 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8122 })
8123 });
8124 cx.assert_editor_state(indoc! {"
8125 fn main() {
8126 sample(param1, «ˇparam2»);
8127 }
8128
8129 fn sample(param1: u8, param2: u8) {}
8130 "});
8131 cx.editor(|editor, _| {
8132 assert!(!editor.signature_help_state.is_shown());
8133 });
8134
8135 // When unselecting again, the popover is back if within the brackets.
8136 cx.update_editor(|editor, cx| {
8137 editor.change_selections(None, cx, |s| {
8138 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8139 })
8140 });
8141 cx.assert_editor_state(indoc! {"
8142 fn main() {
8143 sample(param1, ˇparam2);
8144 }
8145
8146 fn sample(param1: u8, param2: u8) {}
8147 "});
8148 handle_signature_help_request(&mut cx, mocked_response).await;
8149 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8150 .await;
8151 cx.editor(|editor, _| {
8152 assert!(editor.signature_help_state.is_shown());
8153 });
8154
8155 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
8156 cx.update_editor(|editor, cx| {
8157 editor.change_selections(None, cx, |s| {
8158 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
8159 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8160 })
8161 });
8162 cx.assert_editor_state(indoc! {"
8163 fn main() {
8164 sample(param1, ˇparam2);
8165 }
8166
8167 fn sample(param1: u8, param2: u8) {}
8168 "});
8169
8170 let mocked_response = lsp::SignatureHelp {
8171 signatures: vec![lsp::SignatureInformation {
8172 label: "fn sample(param1: u8, param2: u8)".to_string(),
8173 documentation: None,
8174 parameters: Some(vec![
8175 lsp::ParameterInformation {
8176 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8177 documentation: None,
8178 },
8179 lsp::ParameterInformation {
8180 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8181 documentation: None,
8182 },
8183 ]),
8184 active_parameter: None,
8185 }],
8186 active_signature: Some(0),
8187 active_parameter: Some(1),
8188 };
8189 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8190 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8191 .await;
8192 cx.update_editor(|editor, cx| {
8193 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
8194 });
8195 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8196 .await;
8197 cx.update_editor(|editor, cx| {
8198 editor.change_selections(None, cx, |s| {
8199 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8200 })
8201 });
8202 cx.assert_editor_state(indoc! {"
8203 fn main() {
8204 sample(param1, «ˇparam2»);
8205 }
8206
8207 fn sample(param1: u8, param2: u8) {}
8208 "});
8209 cx.update_editor(|editor, cx| {
8210 editor.change_selections(None, cx, |s| {
8211 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8212 })
8213 });
8214 cx.assert_editor_state(indoc! {"
8215 fn main() {
8216 sample(param1, ˇparam2);
8217 }
8218
8219 fn sample(param1: u8, param2: u8) {}
8220 "});
8221 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
8222 .await;
8223}
8224
8225#[gpui::test]
8226async fn test_completion(cx: &mut gpui::TestAppContext) {
8227 init_test(cx, |_| {});
8228
8229 let mut cx = EditorLspTestContext::new_rust(
8230 lsp::ServerCapabilities {
8231 completion_provider: Some(lsp::CompletionOptions {
8232 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
8233 resolve_provider: Some(true),
8234 ..Default::default()
8235 }),
8236 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
8237 ..Default::default()
8238 },
8239 cx,
8240 )
8241 .await;
8242 let counter = Arc::new(AtomicUsize::new(0));
8243
8244 cx.set_state(indoc! {"
8245 oneˇ
8246 two
8247 three
8248 "});
8249 cx.simulate_keystroke(".");
8250 handle_completion_request(
8251 &mut cx,
8252 indoc! {"
8253 one.|<>
8254 two
8255 three
8256 "},
8257 vec!["first_completion", "second_completion"],
8258 counter.clone(),
8259 )
8260 .await;
8261 cx.condition(|editor, _| editor.context_menu_visible())
8262 .await;
8263 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8264
8265 let _handler = handle_signature_help_request(
8266 &mut cx,
8267 lsp::SignatureHelp {
8268 signatures: vec![lsp::SignatureInformation {
8269 label: "test signature".to_string(),
8270 documentation: None,
8271 parameters: Some(vec![lsp::ParameterInformation {
8272 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
8273 documentation: None,
8274 }]),
8275 active_parameter: None,
8276 }],
8277 active_signature: None,
8278 active_parameter: None,
8279 },
8280 );
8281 cx.update_editor(|editor, cx| {
8282 assert!(
8283 !editor.signature_help_state.is_shown(),
8284 "No signature help was called for"
8285 );
8286 editor.show_signature_help(&ShowSignatureHelp, cx);
8287 });
8288 cx.run_until_parked();
8289 cx.update_editor(|editor, _| {
8290 assert!(
8291 !editor.signature_help_state.is_shown(),
8292 "No signature help should be shown when completions menu is open"
8293 );
8294 });
8295
8296 let apply_additional_edits = cx.update_editor(|editor, cx| {
8297 editor.context_menu_next(&Default::default(), cx);
8298 editor
8299 .confirm_completion(&ConfirmCompletion::default(), cx)
8300 .unwrap()
8301 });
8302 cx.assert_editor_state(indoc! {"
8303 one.second_completionˇ
8304 two
8305 three
8306 "});
8307
8308 handle_resolve_completion_request(
8309 &mut cx,
8310 Some(vec![
8311 (
8312 //This overlaps with the primary completion edit which is
8313 //misbehavior from the LSP spec, test that we filter it out
8314 indoc! {"
8315 one.second_ˇcompletion
8316 two
8317 threeˇ
8318 "},
8319 "overlapping additional edit",
8320 ),
8321 (
8322 indoc! {"
8323 one.second_completion
8324 two
8325 threeˇ
8326 "},
8327 "\nadditional edit",
8328 ),
8329 ]),
8330 )
8331 .await;
8332 apply_additional_edits.await.unwrap();
8333 cx.assert_editor_state(indoc! {"
8334 one.second_completionˇ
8335 two
8336 three
8337 additional edit
8338 "});
8339
8340 cx.set_state(indoc! {"
8341 one.second_completion
8342 twoˇ
8343 threeˇ
8344 additional edit
8345 "});
8346 cx.simulate_keystroke(" ");
8347 assert!(cx.editor(|e, _| e.context_menu.borrow_mut().is_none()));
8348 cx.simulate_keystroke("s");
8349 assert!(cx.editor(|e, _| e.context_menu.borrow_mut().is_none()));
8350
8351 cx.assert_editor_state(indoc! {"
8352 one.second_completion
8353 two sˇ
8354 three sˇ
8355 additional edit
8356 "});
8357 handle_completion_request(
8358 &mut cx,
8359 indoc! {"
8360 one.second_completion
8361 two s
8362 three <s|>
8363 additional edit
8364 "},
8365 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8366 counter.clone(),
8367 )
8368 .await;
8369 cx.condition(|editor, _| editor.context_menu_visible())
8370 .await;
8371 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
8372
8373 cx.simulate_keystroke("i");
8374
8375 handle_completion_request(
8376 &mut cx,
8377 indoc! {"
8378 one.second_completion
8379 two si
8380 three <si|>
8381 additional edit
8382 "},
8383 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8384 counter.clone(),
8385 )
8386 .await;
8387 cx.condition(|editor, _| editor.context_menu_visible())
8388 .await;
8389 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
8390
8391 let apply_additional_edits = cx.update_editor(|editor, cx| {
8392 editor
8393 .confirm_completion(&ConfirmCompletion::default(), cx)
8394 .unwrap()
8395 });
8396 cx.assert_editor_state(indoc! {"
8397 one.second_completion
8398 two sixth_completionˇ
8399 three sixth_completionˇ
8400 additional edit
8401 "});
8402
8403 apply_additional_edits.await.unwrap();
8404
8405 update_test_language_settings(&mut cx, |settings| {
8406 settings.defaults.show_completions_on_input = Some(false);
8407 });
8408 cx.set_state("editorˇ");
8409 cx.simulate_keystroke(".");
8410 assert!(cx.editor(|e, _| e.context_menu.borrow_mut().is_none()));
8411 cx.simulate_keystroke("c");
8412 cx.simulate_keystroke("l");
8413 cx.simulate_keystroke("o");
8414 cx.assert_editor_state("editor.cloˇ");
8415 assert!(cx.editor(|e, _| e.context_menu.borrow_mut().is_none()));
8416 cx.update_editor(|editor, cx| {
8417 editor.show_completions(&ShowCompletions { trigger: None }, cx);
8418 });
8419 handle_completion_request(
8420 &mut cx,
8421 "editor.<clo|>",
8422 vec!["close", "clobber"],
8423 counter.clone(),
8424 )
8425 .await;
8426 cx.condition(|editor, _| editor.context_menu_visible())
8427 .await;
8428 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
8429
8430 let apply_additional_edits = cx.update_editor(|editor, cx| {
8431 editor
8432 .confirm_completion(&ConfirmCompletion::default(), cx)
8433 .unwrap()
8434 });
8435 cx.assert_editor_state("editor.closeˇ");
8436 handle_resolve_completion_request(&mut cx, None).await;
8437 apply_additional_edits.await.unwrap();
8438}
8439
8440#[gpui::test]
8441async fn test_multiline_completion(cx: &mut gpui::TestAppContext) {
8442 init_test(cx, |_| {});
8443
8444 let fs = FakeFs::new(cx.executor());
8445 fs.insert_tree(
8446 "/a",
8447 json!({
8448 "main.ts": "a",
8449 }),
8450 )
8451 .await;
8452
8453 let project = Project::test(fs, ["/a".as_ref()], cx).await;
8454 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8455 let typescript_language = Arc::new(Language::new(
8456 LanguageConfig {
8457 name: "TypeScript".into(),
8458 matcher: LanguageMatcher {
8459 path_suffixes: vec!["ts".to_string()],
8460 ..LanguageMatcher::default()
8461 },
8462 line_comments: vec!["// ".into()],
8463 ..LanguageConfig::default()
8464 },
8465 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
8466 ));
8467 language_registry.add(typescript_language.clone());
8468 let mut fake_servers = language_registry.register_fake_lsp(
8469 "TypeScript",
8470 FakeLspAdapter {
8471 capabilities: lsp::ServerCapabilities {
8472 completion_provider: Some(lsp::CompletionOptions {
8473 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
8474 ..lsp::CompletionOptions::default()
8475 }),
8476 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
8477 ..lsp::ServerCapabilities::default()
8478 },
8479 // Emulate vtsls label generation
8480 label_for_completion: Some(Box::new(|item, _| {
8481 let text = if let Some(description) = item
8482 .label_details
8483 .as_ref()
8484 .and_then(|label_details| label_details.description.as_ref())
8485 {
8486 format!("{} {}", item.label, description)
8487 } else if let Some(detail) = &item.detail {
8488 format!("{} {}", item.label, detail)
8489 } else {
8490 item.label.clone()
8491 };
8492 let len = text.len();
8493 Some(language::CodeLabel {
8494 text,
8495 runs: Vec::new(),
8496 filter_range: 0..len,
8497 })
8498 })),
8499 ..FakeLspAdapter::default()
8500 },
8501 );
8502 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
8503 let cx = &mut VisualTestContext::from_window(*workspace, cx);
8504 let worktree_id = workspace
8505 .update(cx, |workspace, cx| {
8506 workspace.project().update(cx, |project, cx| {
8507 project.worktrees(cx).next().unwrap().read(cx).id()
8508 })
8509 })
8510 .unwrap();
8511 let _buffer = project
8512 .update(cx, |project, cx| {
8513 project.open_local_buffer_with_lsp("/a/main.ts", cx)
8514 })
8515 .await
8516 .unwrap();
8517 let editor = workspace
8518 .update(cx, |workspace, cx| {
8519 workspace.open_path((worktree_id, "main.ts"), None, true, cx)
8520 })
8521 .unwrap()
8522 .await
8523 .unwrap()
8524 .downcast::<Editor>()
8525 .unwrap();
8526 let fake_server = fake_servers.next().await.unwrap();
8527
8528 let multiline_label = "StickyHeaderExcerpt {\n excerpt,\n next_excerpt_controls_present,\n next_buffer_row,\n }: StickyHeaderExcerpt<'_>,";
8529 let multiline_label_2 = "a\nb\nc\n";
8530 let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
8531 let multiline_description = "d\ne\nf\n";
8532 let multiline_detail_2 = "g\nh\ni\n";
8533
8534 let mut completion_handle =
8535 fake_server.handle_request::<lsp::request::Completion, _, _>(move |params, _| async move {
8536 Ok(Some(lsp::CompletionResponse::Array(vec![
8537 lsp::CompletionItem {
8538 label: multiline_label.to_string(),
8539 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8540 range: lsp::Range {
8541 start: lsp::Position {
8542 line: params.text_document_position.position.line,
8543 character: params.text_document_position.position.character,
8544 },
8545 end: lsp::Position {
8546 line: params.text_document_position.position.line,
8547 character: params.text_document_position.position.character,
8548 },
8549 },
8550 new_text: "new_text_1".to_string(),
8551 })),
8552 ..lsp::CompletionItem::default()
8553 },
8554 lsp::CompletionItem {
8555 label: "single line label 1".to_string(),
8556 detail: Some(multiline_detail.to_string()),
8557 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8558 range: lsp::Range {
8559 start: lsp::Position {
8560 line: params.text_document_position.position.line,
8561 character: params.text_document_position.position.character,
8562 },
8563 end: lsp::Position {
8564 line: params.text_document_position.position.line,
8565 character: params.text_document_position.position.character,
8566 },
8567 },
8568 new_text: "new_text_2".to_string(),
8569 })),
8570 ..lsp::CompletionItem::default()
8571 },
8572 lsp::CompletionItem {
8573 label: "single line label 2".to_string(),
8574 label_details: Some(lsp::CompletionItemLabelDetails {
8575 description: Some(multiline_description.to_string()),
8576 detail: None,
8577 }),
8578 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8579 range: lsp::Range {
8580 start: lsp::Position {
8581 line: params.text_document_position.position.line,
8582 character: params.text_document_position.position.character,
8583 },
8584 end: lsp::Position {
8585 line: params.text_document_position.position.line,
8586 character: params.text_document_position.position.character,
8587 },
8588 },
8589 new_text: "new_text_2".to_string(),
8590 })),
8591 ..lsp::CompletionItem::default()
8592 },
8593 lsp::CompletionItem {
8594 label: multiline_label_2.to_string(),
8595 detail: Some(multiline_detail_2.to_string()),
8596 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8597 range: lsp::Range {
8598 start: lsp::Position {
8599 line: params.text_document_position.position.line,
8600 character: params.text_document_position.position.character,
8601 },
8602 end: lsp::Position {
8603 line: params.text_document_position.position.line,
8604 character: params.text_document_position.position.character,
8605 },
8606 },
8607 new_text: "new_text_3".to_string(),
8608 })),
8609 ..lsp::CompletionItem::default()
8610 },
8611 lsp::CompletionItem {
8612 label: "Label with many spaces and \t but without newlines".to_string(),
8613 detail: Some(
8614 "Details with many spaces and \t but without newlines".to_string(),
8615 ),
8616 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8617 range: lsp::Range {
8618 start: lsp::Position {
8619 line: params.text_document_position.position.line,
8620 character: params.text_document_position.position.character,
8621 },
8622 end: lsp::Position {
8623 line: params.text_document_position.position.line,
8624 character: params.text_document_position.position.character,
8625 },
8626 },
8627 new_text: "new_text_4".to_string(),
8628 })),
8629 ..lsp::CompletionItem::default()
8630 },
8631 ])))
8632 });
8633
8634 editor.update(cx, |editor, cx| {
8635 editor.focus(cx);
8636 editor.move_to_end(&MoveToEnd, cx);
8637 editor.handle_input(".", cx);
8638 });
8639 cx.run_until_parked();
8640 completion_handle.next().await.unwrap();
8641
8642 editor.update(cx, |editor, _| {
8643 assert!(editor.context_menu_visible());
8644 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
8645 {
8646 let completion_labels = menu
8647 .completions
8648 .borrow()
8649 .iter()
8650 .map(|c| c.label.text.clone())
8651 .collect::<Vec<_>>();
8652 assert_eq!(
8653 completion_labels,
8654 &[
8655 "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
8656 "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
8657 "single line label 2 d e f ",
8658 "a b c g h i ",
8659 "Label with many spaces and \t but without newlines Details with many spaces and \t but without newlines",
8660 ],
8661 "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
8662 );
8663
8664 for completion in menu
8665 .completions
8666 .borrow()
8667 .iter() {
8668 assert_eq!(
8669 completion.label.filter_range,
8670 0..completion.label.text.len(),
8671 "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
8672 );
8673 }
8674
8675 } else {
8676 panic!("expected completion menu to be open");
8677 }
8678 });
8679}
8680
8681#[gpui::test]
8682async fn test_completion_page_up_down_keys(cx: &mut gpui::TestAppContext) {
8683 init_test(cx, |_| {});
8684 let mut cx = EditorLspTestContext::new_rust(
8685 lsp::ServerCapabilities {
8686 completion_provider: Some(lsp::CompletionOptions {
8687 trigger_characters: Some(vec![".".to_string()]),
8688 ..Default::default()
8689 }),
8690 ..Default::default()
8691 },
8692 cx,
8693 )
8694 .await;
8695 cx.lsp
8696 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8697 Ok(Some(lsp::CompletionResponse::Array(vec![
8698 lsp::CompletionItem {
8699 label: "first".into(),
8700 ..Default::default()
8701 },
8702 lsp::CompletionItem {
8703 label: "last".into(),
8704 ..Default::default()
8705 },
8706 ])))
8707 });
8708 cx.set_state("variableˇ");
8709 cx.simulate_keystroke(".");
8710 cx.executor().run_until_parked();
8711
8712 cx.update_editor(|editor, _| {
8713 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
8714 {
8715 assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
8716 } else {
8717 panic!("expected completion menu to be open");
8718 }
8719 });
8720
8721 cx.update_editor(|editor, cx| {
8722 editor.move_page_down(&MovePageDown::default(), cx);
8723 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
8724 {
8725 assert!(
8726 menu.selected_item == 1,
8727 "expected PageDown to select the last item from the context menu"
8728 );
8729 } else {
8730 panic!("expected completion menu to stay open after PageDown");
8731 }
8732 });
8733
8734 cx.update_editor(|editor, cx| {
8735 editor.move_page_up(&MovePageUp::default(), cx);
8736 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
8737 {
8738 assert!(
8739 menu.selected_item == 0,
8740 "expected PageUp to select the first item from the context menu"
8741 );
8742 } else {
8743 panic!("expected completion menu to stay open after PageUp");
8744 }
8745 });
8746}
8747
8748#[gpui::test]
8749async fn test_completion_sort(cx: &mut gpui::TestAppContext) {
8750 init_test(cx, |_| {});
8751 let mut cx = EditorLspTestContext::new_rust(
8752 lsp::ServerCapabilities {
8753 completion_provider: Some(lsp::CompletionOptions {
8754 trigger_characters: Some(vec![".".to_string()]),
8755 ..Default::default()
8756 }),
8757 ..Default::default()
8758 },
8759 cx,
8760 )
8761 .await;
8762 cx.lsp
8763 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8764 Ok(Some(lsp::CompletionResponse::Array(vec![
8765 lsp::CompletionItem {
8766 label: "Range".into(),
8767 sort_text: Some("a".into()),
8768 ..Default::default()
8769 },
8770 lsp::CompletionItem {
8771 label: "r".into(),
8772 sort_text: Some("b".into()),
8773 ..Default::default()
8774 },
8775 lsp::CompletionItem {
8776 label: "ret".into(),
8777 sort_text: Some("c".into()),
8778 ..Default::default()
8779 },
8780 lsp::CompletionItem {
8781 label: "return".into(),
8782 sort_text: Some("d".into()),
8783 ..Default::default()
8784 },
8785 lsp::CompletionItem {
8786 label: "slice".into(),
8787 sort_text: Some("d".into()),
8788 ..Default::default()
8789 },
8790 ])))
8791 });
8792 cx.set_state("rˇ");
8793 cx.executor().run_until_parked();
8794 cx.update_editor(|editor, cx| {
8795 editor.show_completions(
8796 &ShowCompletions {
8797 trigger: Some("r".into()),
8798 },
8799 cx,
8800 );
8801 });
8802 cx.executor().run_until_parked();
8803
8804 cx.update_editor(|editor, _| {
8805 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
8806 {
8807 assert_eq!(
8808 completion_menu_entries(&menu),
8809 &["r", "ret", "Range", "return"]
8810 );
8811 } else {
8812 panic!("expected completion menu to be open");
8813 }
8814 });
8815}
8816
8817#[gpui::test]
8818async fn test_no_duplicated_completion_requests(cx: &mut gpui::TestAppContext) {
8819 init_test(cx, |_| {});
8820
8821 let mut cx = EditorLspTestContext::new_rust(
8822 lsp::ServerCapabilities {
8823 completion_provider: Some(lsp::CompletionOptions {
8824 trigger_characters: Some(vec![".".to_string()]),
8825 resolve_provider: Some(true),
8826 ..Default::default()
8827 }),
8828 ..Default::default()
8829 },
8830 cx,
8831 )
8832 .await;
8833
8834 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
8835 cx.simulate_keystroke(".");
8836 let completion_item = lsp::CompletionItem {
8837 label: "Some".into(),
8838 kind: Some(lsp::CompletionItemKind::SNIPPET),
8839 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
8840 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
8841 kind: lsp::MarkupKind::Markdown,
8842 value: "```rust\nSome(2)\n```".to_string(),
8843 })),
8844 deprecated: Some(false),
8845 sort_text: Some("Some".to_string()),
8846 filter_text: Some("Some".to_string()),
8847 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
8848 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8849 range: lsp::Range {
8850 start: lsp::Position {
8851 line: 0,
8852 character: 22,
8853 },
8854 end: lsp::Position {
8855 line: 0,
8856 character: 22,
8857 },
8858 },
8859 new_text: "Some(2)".to_string(),
8860 })),
8861 additional_text_edits: Some(vec![lsp::TextEdit {
8862 range: lsp::Range {
8863 start: lsp::Position {
8864 line: 0,
8865 character: 20,
8866 },
8867 end: lsp::Position {
8868 line: 0,
8869 character: 22,
8870 },
8871 },
8872 new_text: "".to_string(),
8873 }]),
8874 ..Default::default()
8875 };
8876
8877 let closure_completion_item = completion_item.clone();
8878 let counter = Arc::new(AtomicUsize::new(0));
8879 let counter_clone = counter.clone();
8880 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
8881 let task_completion_item = closure_completion_item.clone();
8882 counter_clone.fetch_add(1, atomic::Ordering::Release);
8883 async move {
8884 Ok(Some(lsp::CompletionResponse::Array(vec![
8885 task_completion_item,
8886 ])))
8887 }
8888 });
8889
8890 cx.condition(|editor, _| editor.context_menu_visible())
8891 .await;
8892 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
8893 assert!(request.next().await.is_some());
8894 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8895
8896 cx.simulate_keystroke("S");
8897 cx.simulate_keystroke("o");
8898 cx.simulate_keystroke("m");
8899 cx.condition(|editor, _| editor.context_menu_visible())
8900 .await;
8901 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
8902 assert!(request.next().await.is_some());
8903 assert!(request.next().await.is_some());
8904 assert!(request.next().await.is_some());
8905 request.close();
8906 assert!(request.next().await.is_none());
8907 assert_eq!(
8908 counter.load(atomic::Ordering::Acquire),
8909 4,
8910 "With the completions menu open, only one LSP request should happen per input"
8911 );
8912}
8913
8914#[gpui::test]
8915async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
8916 init_test(cx, |_| {});
8917 let mut cx = EditorTestContext::new(cx).await;
8918 let language = Arc::new(Language::new(
8919 LanguageConfig {
8920 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
8921 ..Default::default()
8922 },
8923 Some(tree_sitter_rust::LANGUAGE.into()),
8924 ));
8925 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
8926
8927 // If multiple selections intersect a line, the line is only toggled once.
8928 cx.set_state(indoc! {"
8929 fn a() {
8930 «//b();
8931 ˇ»// «c();
8932 //ˇ» d();
8933 }
8934 "});
8935
8936 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8937
8938 cx.assert_editor_state(indoc! {"
8939 fn a() {
8940 «b();
8941 c();
8942 ˇ» d();
8943 }
8944 "});
8945
8946 // The comment prefix is inserted at the same column for every line in a
8947 // selection.
8948 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8949
8950 cx.assert_editor_state(indoc! {"
8951 fn a() {
8952 // «b();
8953 // c();
8954 ˇ»// d();
8955 }
8956 "});
8957
8958 // If a selection ends at the beginning of a line, that line is not toggled.
8959 cx.set_selections_state(indoc! {"
8960 fn a() {
8961 // b();
8962 «// c();
8963 ˇ» // d();
8964 }
8965 "});
8966
8967 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8968
8969 cx.assert_editor_state(indoc! {"
8970 fn a() {
8971 // b();
8972 «c();
8973 ˇ» // d();
8974 }
8975 "});
8976
8977 // If a selection span a single line and is empty, the line is toggled.
8978 cx.set_state(indoc! {"
8979 fn a() {
8980 a();
8981 b();
8982 ˇ
8983 }
8984 "});
8985
8986 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8987
8988 cx.assert_editor_state(indoc! {"
8989 fn a() {
8990 a();
8991 b();
8992 //•ˇ
8993 }
8994 "});
8995
8996 // If a selection span multiple lines, empty lines are not toggled.
8997 cx.set_state(indoc! {"
8998 fn a() {
8999 «a();
9000
9001 c();ˇ»
9002 }
9003 "});
9004
9005 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
9006
9007 cx.assert_editor_state(indoc! {"
9008 fn a() {
9009 // «a();
9010
9011 // c();ˇ»
9012 }
9013 "});
9014
9015 // If a selection includes multiple comment prefixes, all lines are uncommented.
9016 cx.set_state(indoc! {"
9017 fn a() {
9018 «// a();
9019 /// b();
9020 //! c();ˇ»
9021 }
9022 "});
9023
9024 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
9025
9026 cx.assert_editor_state(indoc! {"
9027 fn a() {
9028 «a();
9029 b();
9030 c();ˇ»
9031 }
9032 "});
9033}
9034
9035#[gpui::test]
9036async fn test_toggle_comment_ignore_indent(cx: &mut gpui::TestAppContext) {
9037 init_test(cx, |_| {});
9038 let mut cx = EditorTestContext::new(cx).await;
9039 let language = Arc::new(Language::new(
9040 LanguageConfig {
9041 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
9042 ..Default::default()
9043 },
9044 Some(tree_sitter_rust::LANGUAGE.into()),
9045 ));
9046 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
9047
9048 let toggle_comments = &ToggleComments {
9049 advance_downwards: false,
9050 ignore_indent: true,
9051 };
9052
9053 // If multiple selections intersect a line, the line is only toggled once.
9054 cx.set_state(indoc! {"
9055 fn a() {
9056 // «b();
9057 // c();
9058 // ˇ» d();
9059 }
9060 "});
9061
9062 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
9063
9064 cx.assert_editor_state(indoc! {"
9065 fn a() {
9066 «b();
9067 c();
9068 ˇ» d();
9069 }
9070 "});
9071
9072 // The comment prefix is inserted at the beginning of each line
9073 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
9074
9075 cx.assert_editor_state(indoc! {"
9076 fn a() {
9077 // «b();
9078 // c();
9079 // ˇ» d();
9080 }
9081 "});
9082
9083 // If a selection ends at the beginning of a line, that line is not toggled.
9084 cx.set_selections_state(indoc! {"
9085 fn a() {
9086 // b();
9087 // «c();
9088 ˇ»// d();
9089 }
9090 "});
9091
9092 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
9093
9094 cx.assert_editor_state(indoc! {"
9095 fn a() {
9096 // b();
9097 «c();
9098 ˇ»// d();
9099 }
9100 "});
9101
9102 // If a selection span a single line and is empty, the line is toggled.
9103 cx.set_state(indoc! {"
9104 fn a() {
9105 a();
9106 b();
9107 ˇ
9108 }
9109 "});
9110
9111 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
9112
9113 cx.assert_editor_state(indoc! {"
9114 fn a() {
9115 a();
9116 b();
9117 //ˇ
9118 }
9119 "});
9120
9121 // If a selection span multiple lines, empty lines are not toggled.
9122 cx.set_state(indoc! {"
9123 fn a() {
9124 «a();
9125
9126 c();ˇ»
9127 }
9128 "});
9129
9130 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
9131
9132 cx.assert_editor_state(indoc! {"
9133 fn a() {
9134 // «a();
9135
9136 // c();ˇ»
9137 }
9138 "});
9139
9140 // If a selection includes multiple comment prefixes, all lines are uncommented.
9141 cx.set_state(indoc! {"
9142 fn a() {
9143 // «a();
9144 /// b();
9145 //! c();ˇ»
9146 }
9147 "});
9148
9149 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
9150
9151 cx.assert_editor_state(indoc! {"
9152 fn a() {
9153 «a();
9154 b();
9155 c();ˇ»
9156 }
9157 "});
9158}
9159
9160#[gpui::test]
9161async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
9162 init_test(cx, |_| {});
9163
9164 let language = Arc::new(Language::new(
9165 LanguageConfig {
9166 line_comments: vec!["// ".into()],
9167 ..Default::default()
9168 },
9169 Some(tree_sitter_rust::LANGUAGE.into()),
9170 ));
9171
9172 let mut cx = EditorTestContext::new(cx).await;
9173
9174 cx.language_registry().add(language.clone());
9175 cx.update_buffer(|buffer, cx| {
9176 buffer.set_language(Some(language), cx);
9177 });
9178
9179 let toggle_comments = &ToggleComments {
9180 advance_downwards: true,
9181 ignore_indent: false,
9182 };
9183
9184 // Single cursor on one line -> advance
9185 // Cursor moves horizontally 3 characters as well on non-blank line
9186 cx.set_state(indoc!(
9187 "fn a() {
9188 ˇdog();
9189 cat();
9190 }"
9191 ));
9192 cx.update_editor(|editor, cx| {
9193 editor.toggle_comments(toggle_comments, cx);
9194 });
9195 cx.assert_editor_state(indoc!(
9196 "fn a() {
9197 // dog();
9198 catˇ();
9199 }"
9200 ));
9201
9202 // Single selection on one line -> don't advance
9203 cx.set_state(indoc!(
9204 "fn a() {
9205 «dog()ˇ»;
9206 cat();
9207 }"
9208 ));
9209 cx.update_editor(|editor, cx| {
9210 editor.toggle_comments(toggle_comments, cx);
9211 });
9212 cx.assert_editor_state(indoc!(
9213 "fn a() {
9214 // «dog()ˇ»;
9215 cat();
9216 }"
9217 ));
9218
9219 // Multiple cursors on one line -> advance
9220 cx.set_state(indoc!(
9221 "fn a() {
9222 ˇdˇog();
9223 cat();
9224 }"
9225 ));
9226 cx.update_editor(|editor, cx| {
9227 editor.toggle_comments(toggle_comments, cx);
9228 });
9229 cx.assert_editor_state(indoc!(
9230 "fn a() {
9231 // dog();
9232 catˇ(ˇ);
9233 }"
9234 ));
9235
9236 // Multiple cursors on one line, with selection -> don't advance
9237 cx.set_state(indoc!(
9238 "fn a() {
9239 ˇdˇog«()ˇ»;
9240 cat();
9241 }"
9242 ));
9243 cx.update_editor(|editor, cx| {
9244 editor.toggle_comments(toggle_comments, cx);
9245 });
9246 cx.assert_editor_state(indoc!(
9247 "fn a() {
9248 // ˇdˇog«()ˇ»;
9249 cat();
9250 }"
9251 ));
9252
9253 // Single cursor on one line -> advance
9254 // Cursor moves to column 0 on blank line
9255 cx.set_state(indoc!(
9256 "fn a() {
9257 ˇdog();
9258
9259 cat();
9260 }"
9261 ));
9262 cx.update_editor(|editor, cx| {
9263 editor.toggle_comments(toggle_comments, cx);
9264 });
9265 cx.assert_editor_state(indoc!(
9266 "fn a() {
9267 // dog();
9268 ˇ
9269 cat();
9270 }"
9271 ));
9272
9273 // Single cursor on one line -> advance
9274 // Cursor starts and ends at column 0
9275 cx.set_state(indoc!(
9276 "fn a() {
9277 ˇ dog();
9278 cat();
9279 }"
9280 ));
9281 cx.update_editor(|editor, cx| {
9282 editor.toggle_comments(toggle_comments, cx);
9283 });
9284 cx.assert_editor_state(indoc!(
9285 "fn a() {
9286 // dog();
9287 ˇ cat();
9288 }"
9289 ));
9290}
9291
9292#[gpui::test]
9293async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
9294 init_test(cx, |_| {});
9295
9296 let mut cx = EditorTestContext::new(cx).await;
9297
9298 let html_language = Arc::new(
9299 Language::new(
9300 LanguageConfig {
9301 name: "HTML".into(),
9302 block_comment: Some(("<!-- ".into(), " -->".into())),
9303 ..Default::default()
9304 },
9305 Some(tree_sitter_html::language()),
9306 )
9307 .with_injection_query(
9308 r#"
9309 (script_element
9310 (raw_text) @injection.content
9311 (#set! injection.language "javascript"))
9312 "#,
9313 )
9314 .unwrap(),
9315 );
9316
9317 let javascript_language = Arc::new(Language::new(
9318 LanguageConfig {
9319 name: "JavaScript".into(),
9320 line_comments: vec!["// ".into()],
9321 ..Default::default()
9322 },
9323 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
9324 ));
9325
9326 cx.language_registry().add(html_language.clone());
9327 cx.language_registry().add(javascript_language.clone());
9328 cx.update_buffer(|buffer, cx| {
9329 buffer.set_language(Some(html_language), cx);
9330 });
9331
9332 // Toggle comments for empty selections
9333 cx.set_state(
9334 &r#"
9335 <p>A</p>ˇ
9336 <p>B</p>ˇ
9337 <p>C</p>ˇ
9338 "#
9339 .unindent(),
9340 );
9341 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9342 cx.assert_editor_state(
9343 &r#"
9344 <!-- <p>A</p>ˇ -->
9345 <!-- <p>B</p>ˇ -->
9346 <!-- <p>C</p>ˇ -->
9347 "#
9348 .unindent(),
9349 );
9350 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9351 cx.assert_editor_state(
9352 &r#"
9353 <p>A</p>ˇ
9354 <p>B</p>ˇ
9355 <p>C</p>ˇ
9356 "#
9357 .unindent(),
9358 );
9359
9360 // Toggle comments for mixture of empty and non-empty selections, where
9361 // multiple selections occupy a given line.
9362 cx.set_state(
9363 &r#"
9364 <p>A«</p>
9365 <p>ˇ»B</p>ˇ
9366 <p>C«</p>
9367 <p>ˇ»D</p>ˇ
9368 "#
9369 .unindent(),
9370 );
9371
9372 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9373 cx.assert_editor_state(
9374 &r#"
9375 <!-- <p>A«</p>
9376 <p>ˇ»B</p>ˇ -->
9377 <!-- <p>C«</p>
9378 <p>ˇ»D</p>ˇ -->
9379 "#
9380 .unindent(),
9381 );
9382 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9383 cx.assert_editor_state(
9384 &r#"
9385 <p>A«</p>
9386 <p>ˇ»B</p>ˇ
9387 <p>C«</p>
9388 <p>ˇ»D</p>ˇ
9389 "#
9390 .unindent(),
9391 );
9392
9393 // Toggle comments when different languages are active for different
9394 // selections.
9395 cx.set_state(
9396 &r#"
9397 ˇ<script>
9398 ˇvar x = new Y();
9399 ˇ</script>
9400 "#
9401 .unindent(),
9402 );
9403 cx.executor().run_until_parked();
9404 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9405 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
9406 // Uncommenting and commenting from this position brings in even more wrong artifacts.
9407 cx.assert_editor_state(
9408 &r#"
9409 <!-- ˇ<script> -->
9410 // ˇvar x = new Y();
9411 // ˇ</script>
9412 "#
9413 .unindent(),
9414 );
9415}
9416
9417#[gpui::test]
9418fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
9419 init_test(cx, |_| {});
9420
9421 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9422 let multibuffer = cx.new_model(|cx| {
9423 let mut multibuffer = MultiBuffer::new(ReadWrite);
9424 multibuffer.push_excerpts(
9425 buffer.clone(),
9426 [
9427 ExcerptRange {
9428 context: Point::new(0, 0)..Point::new(0, 4),
9429 primary: None,
9430 },
9431 ExcerptRange {
9432 context: Point::new(1, 0)..Point::new(1, 4),
9433 primary: None,
9434 },
9435 ],
9436 cx,
9437 );
9438 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
9439 multibuffer
9440 });
9441
9442 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
9443 view.update(cx, |view, cx| {
9444 assert_eq!(view.text(cx), "aaaa\nbbbb");
9445 view.change_selections(None, cx, |s| {
9446 s.select_ranges([
9447 Point::new(0, 0)..Point::new(0, 0),
9448 Point::new(1, 0)..Point::new(1, 0),
9449 ])
9450 });
9451
9452 view.handle_input("X", cx);
9453 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
9454 assert_eq!(
9455 view.selections.ranges(cx),
9456 [
9457 Point::new(0, 1)..Point::new(0, 1),
9458 Point::new(1, 1)..Point::new(1, 1),
9459 ]
9460 );
9461
9462 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
9463 view.change_selections(None, cx, |s| {
9464 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
9465 });
9466 view.backspace(&Default::default(), cx);
9467 assert_eq!(view.text(cx), "Xa\nbbb");
9468 assert_eq!(
9469 view.selections.ranges(cx),
9470 [Point::new(1, 0)..Point::new(1, 0)]
9471 );
9472
9473 view.change_selections(None, cx, |s| {
9474 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
9475 });
9476 view.backspace(&Default::default(), cx);
9477 assert_eq!(view.text(cx), "X\nbb");
9478 assert_eq!(
9479 view.selections.ranges(cx),
9480 [Point::new(0, 1)..Point::new(0, 1)]
9481 );
9482 });
9483}
9484
9485#[gpui::test]
9486fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
9487 init_test(cx, |_| {});
9488
9489 let markers = vec![('[', ']').into(), ('(', ')').into()];
9490 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
9491 indoc! {"
9492 [aaaa
9493 (bbbb]
9494 cccc)",
9495 },
9496 markers.clone(),
9497 );
9498 let excerpt_ranges = markers.into_iter().map(|marker| {
9499 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
9500 ExcerptRange {
9501 context,
9502 primary: None,
9503 }
9504 });
9505 let buffer = cx.new_model(|cx| Buffer::local(initial_text, cx));
9506 let multibuffer = cx.new_model(|cx| {
9507 let mut multibuffer = MultiBuffer::new(ReadWrite);
9508 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
9509 multibuffer
9510 });
9511
9512 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
9513 view.update(cx, |view, cx| {
9514 let (expected_text, selection_ranges) = marked_text_ranges(
9515 indoc! {"
9516 aaaa
9517 bˇbbb
9518 bˇbbˇb
9519 cccc"
9520 },
9521 true,
9522 );
9523 assert_eq!(view.text(cx), expected_text);
9524 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
9525
9526 view.handle_input("X", cx);
9527
9528 let (expected_text, expected_selections) = marked_text_ranges(
9529 indoc! {"
9530 aaaa
9531 bXˇbbXb
9532 bXˇbbXˇb
9533 cccc"
9534 },
9535 false,
9536 );
9537 assert_eq!(view.text(cx), expected_text);
9538 assert_eq!(view.selections.ranges(cx), expected_selections);
9539
9540 view.newline(&Newline, cx);
9541 let (expected_text, expected_selections) = marked_text_ranges(
9542 indoc! {"
9543 aaaa
9544 bX
9545 ˇbbX
9546 b
9547 bX
9548 ˇbbX
9549 ˇb
9550 cccc"
9551 },
9552 false,
9553 );
9554 assert_eq!(view.text(cx), expected_text);
9555 assert_eq!(view.selections.ranges(cx), expected_selections);
9556 });
9557}
9558
9559#[gpui::test]
9560fn test_refresh_selections(cx: &mut TestAppContext) {
9561 init_test(cx, |_| {});
9562
9563 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9564 let mut excerpt1_id = None;
9565 let multibuffer = cx.new_model(|cx| {
9566 let mut multibuffer = MultiBuffer::new(ReadWrite);
9567 excerpt1_id = multibuffer
9568 .push_excerpts(
9569 buffer.clone(),
9570 [
9571 ExcerptRange {
9572 context: Point::new(0, 0)..Point::new(1, 4),
9573 primary: None,
9574 },
9575 ExcerptRange {
9576 context: Point::new(1, 0)..Point::new(2, 4),
9577 primary: None,
9578 },
9579 ],
9580 cx,
9581 )
9582 .into_iter()
9583 .next();
9584 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
9585 multibuffer
9586 });
9587
9588 let editor = cx.add_window(|cx| {
9589 let mut editor = build_editor(multibuffer.clone(), cx);
9590 let snapshot = editor.snapshot(cx);
9591 editor.change_selections(None, cx, |s| {
9592 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
9593 });
9594 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
9595 assert_eq!(
9596 editor.selections.ranges(cx),
9597 [
9598 Point::new(1, 3)..Point::new(1, 3),
9599 Point::new(2, 1)..Point::new(2, 1),
9600 ]
9601 );
9602 editor
9603 });
9604
9605 // Refreshing selections is a no-op when excerpts haven't changed.
9606 _ = editor.update(cx, |editor, cx| {
9607 editor.change_selections(None, cx, |s| s.refresh());
9608 assert_eq!(
9609 editor.selections.ranges(cx),
9610 [
9611 Point::new(1, 3)..Point::new(1, 3),
9612 Point::new(2, 1)..Point::new(2, 1),
9613 ]
9614 );
9615 });
9616
9617 multibuffer.update(cx, |multibuffer, cx| {
9618 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
9619 });
9620 _ = editor.update(cx, |editor, cx| {
9621 // Removing an excerpt causes the first selection to become degenerate.
9622 assert_eq!(
9623 editor.selections.ranges(cx),
9624 [
9625 Point::new(0, 0)..Point::new(0, 0),
9626 Point::new(0, 1)..Point::new(0, 1)
9627 ]
9628 );
9629
9630 // Refreshing selections will relocate the first selection to the original buffer
9631 // location.
9632 editor.change_selections(None, cx, |s| s.refresh());
9633 assert_eq!(
9634 editor.selections.ranges(cx),
9635 [
9636 Point::new(0, 1)..Point::new(0, 1),
9637 Point::new(0, 3)..Point::new(0, 3)
9638 ]
9639 );
9640 assert!(editor.selections.pending_anchor().is_some());
9641 });
9642}
9643
9644#[gpui::test]
9645fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
9646 init_test(cx, |_| {});
9647
9648 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9649 let mut excerpt1_id = None;
9650 let multibuffer = cx.new_model(|cx| {
9651 let mut multibuffer = MultiBuffer::new(ReadWrite);
9652 excerpt1_id = multibuffer
9653 .push_excerpts(
9654 buffer.clone(),
9655 [
9656 ExcerptRange {
9657 context: Point::new(0, 0)..Point::new(1, 4),
9658 primary: None,
9659 },
9660 ExcerptRange {
9661 context: Point::new(1, 0)..Point::new(2, 4),
9662 primary: None,
9663 },
9664 ],
9665 cx,
9666 )
9667 .into_iter()
9668 .next();
9669 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
9670 multibuffer
9671 });
9672
9673 let editor = cx.add_window(|cx| {
9674 let mut editor = build_editor(multibuffer.clone(), cx);
9675 let snapshot = editor.snapshot(cx);
9676 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
9677 assert_eq!(
9678 editor.selections.ranges(cx),
9679 [Point::new(1, 3)..Point::new(1, 3)]
9680 );
9681 editor
9682 });
9683
9684 multibuffer.update(cx, |multibuffer, cx| {
9685 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
9686 });
9687 _ = editor.update(cx, |editor, cx| {
9688 assert_eq!(
9689 editor.selections.ranges(cx),
9690 [Point::new(0, 0)..Point::new(0, 0)]
9691 );
9692
9693 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
9694 editor.change_selections(None, cx, |s| s.refresh());
9695 assert_eq!(
9696 editor.selections.ranges(cx),
9697 [Point::new(0, 3)..Point::new(0, 3)]
9698 );
9699 assert!(editor.selections.pending_anchor().is_some());
9700 });
9701}
9702
9703#[gpui::test]
9704async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
9705 init_test(cx, |_| {});
9706
9707 let language = Arc::new(
9708 Language::new(
9709 LanguageConfig {
9710 brackets: BracketPairConfig {
9711 pairs: vec![
9712 BracketPair {
9713 start: "{".to_string(),
9714 end: "}".to_string(),
9715 close: true,
9716 surround: true,
9717 newline: true,
9718 },
9719 BracketPair {
9720 start: "/* ".to_string(),
9721 end: " */".to_string(),
9722 close: true,
9723 surround: true,
9724 newline: true,
9725 },
9726 ],
9727 ..Default::default()
9728 },
9729 ..Default::default()
9730 },
9731 Some(tree_sitter_rust::LANGUAGE.into()),
9732 )
9733 .with_indents_query("")
9734 .unwrap(),
9735 );
9736
9737 let text = concat!(
9738 "{ }\n", //
9739 " x\n", //
9740 " /* */\n", //
9741 "x\n", //
9742 "{{} }\n", //
9743 );
9744
9745 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
9746 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
9747 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
9748 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
9749 .await;
9750
9751 view.update(cx, |view, cx| {
9752 view.change_selections(None, cx, |s| {
9753 s.select_display_ranges([
9754 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
9755 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
9756 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
9757 ])
9758 });
9759 view.newline(&Newline, cx);
9760
9761 assert_eq!(
9762 view.buffer().read(cx).read(cx).text(),
9763 concat!(
9764 "{ \n", // Suppress rustfmt
9765 "\n", //
9766 "}\n", //
9767 " x\n", //
9768 " /* \n", //
9769 " \n", //
9770 " */\n", //
9771 "x\n", //
9772 "{{} \n", //
9773 "}\n", //
9774 )
9775 );
9776 });
9777}
9778
9779#[gpui::test]
9780fn test_highlighted_ranges(cx: &mut TestAppContext) {
9781 init_test(cx, |_| {});
9782
9783 let editor = cx.add_window(|cx| {
9784 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
9785 build_editor(buffer.clone(), cx)
9786 });
9787
9788 _ = editor.update(cx, |editor, cx| {
9789 struct Type1;
9790 struct Type2;
9791
9792 let buffer = editor.buffer.read(cx).snapshot(cx);
9793
9794 let anchor_range =
9795 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
9796
9797 editor.highlight_background::<Type1>(
9798 &[
9799 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
9800 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
9801 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
9802 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
9803 ],
9804 |_| Hsla::red(),
9805 cx,
9806 );
9807 editor.highlight_background::<Type2>(
9808 &[
9809 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
9810 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
9811 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
9812 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
9813 ],
9814 |_| Hsla::green(),
9815 cx,
9816 );
9817
9818 let snapshot = editor.snapshot(cx);
9819 let mut highlighted_ranges = editor.background_highlights_in_range(
9820 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
9821 &snapshot,
9822 cx.theme().colors(),
9823 );
9824 // Enforce a consistent ordering based on color without relying on the ordering of the
9825 // highlight's `TypeId` which is non-executor.
9826 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
9827 assert_eq!(
9828 highlighted_ranges,
9829 &[
9830 (
9831 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
9832 Hsla::red(),
9833 ),
9834 (
9835 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9836 Hsla::red(),
9837 ),
9838 (
9839 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
9840 Hsla::green(),
9841 ),
9842 (
9843 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
9844 Hsla::green(),
9845 ),
9846 ]
9847 );
9848 assert_eq!(
9849 editor.background_highlights_in_range(
9850 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
9851 &snapshot,
9852 cx.theme().colors(),
9853 ),
9854 &[(
9855 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9856 Hsla::red(),
9857 )]
9858 );
9859 });
9860}
9861
9862#[gpui::test]
9863async fn test_following(cx: &mut gpui::TestAppContext) {
9864 init_test(cx, |_| {});
9865
9866 let fs = FakeFs::new(cx.executor());
9867 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9868
9869 let buffer = project.update(cx, |project, cx| {
9870 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
9871 cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
9872 });
9873 let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
9874 let follower = cx.update(|cx| {
9875 cx.open_window(
9876 WindowOptions {
9877 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
9878 gpui::Point::new(px(0.), px(0.)),
9879 gpui::Point::new(px(10.), px(80.)),
9880 ))),
9881 ..Default::default()
9882 },
9883 |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
9884 )
9885 .unwrap()
9886 });
9887
9888 let is_still_following = Rc::new(RefCell::new(true));
9889 let follower_edit_event_count = Rc::new(RefCell::new(0));
9890 let pending_update = Rc::new(RefCell::new(None));
9891 _ = follower.update(cx, {
9892 let update = pending_update.clone();
9893 let is_still_following = is_still_following.clone();
9894 let follower_edit_event_count = follower_edit_event_count.clone();
9895 |_, cx| {
9896 cx.subscribe(
9897 &leader.root_view(cx).unwrap(),
9898 move |_, leader, event, cx| {
9899 leader
9900 .read(cx)
9901 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9902 },
9903 )
9904 .detach();
9905
9906 cx.subscribe(
9907 &follower.root_view(cx).unwrap(),
9908 move |_, _, event: &EditorEvent, _cx| {
9909 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
9910 *is_still_following.borrow_mut() = false;
9911 }
9912
9913 if let EditorEvent::BufferEdited = event {
9914 *follower_edit_event_count.borrow_mut() += 1;
9915 }
9916 },
9917 )
9918 .detach();
9919 }
9920 });
9921
9922 // Update the selections only
9923 _ = leader.update(cx, |leader, cx| {
9924 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9925 });
9926 follower
9927 .update(cx, |follower, cx| {
9928 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9929 })
9930 .unwrap()
9931 .await
9932 .unwrap();
9933 _ = follower.update(cx, |follower, cx| {
9934 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
9935 });
9936 assert!(*is_still_following.borrow());
9937 assert_eq!(*follower_edit_event_count.borrow(), 0);
9938
9939 // Update the scroll position only
9940 _ = leader.update(cx, |leader, cx| {
9941 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9942 });
9943 follower
9944 .update(cx, |follower, cx| {
9945 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9946 })
9947 .unwrap()
9948 .await
9949 .unwrap();
9950 assert_eq!(
9951 follower
9952 .update(cx, |follower, cx| follower.scroll_position(cx))
9953 .unwrap(),
9954 gpui::Point::new(1.5, 3.5)
9955 );
9956 assert!(*is_still_following.borrow());
9957 assert_eq!(*follower_edit_event_count.borrow(), 0);
9958
9959 // Update the selections and scroll position. The follower's scroll position is updated
9960 // via autoscroll, not via the leader's exact scroll position.
9961 _ = leader.update(cx, |leader, cx| {
9962 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
9963 leader.request_autoscroll(Autoscroll::newest(), cx);
9964 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9965 });
9966 follower
9967 .update(cx, |follower, cx| {
9968 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9969 })
9970 .unwrap()
9971 .await
9972 .unwrap();
9973 _ = follower.update(cx, |follower, cx| {
9974 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
9975 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
9976 });
9977 assert!(*is_still_following.borrow());
9978
9979 // Creating a pending selection that precedes another selection
9980 _ = leader.update(cx, |leader, cx| {
9981 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9982 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, cx);
9983 });
9984 follower
9985 .update(cx, |follower, cx| {
9986 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9987 })
9988 .unwrap()
9989 .await
9990 .unwrap();
9991 _ = follower.update(cx, |follower, cx| {
9992 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
9993 });
9994 assert!(*is_still_following.borrow());
9995
9996 // Extend the pending selection so that it surrounds another selection
9997 _ = leader.update(cx, |leader, cx| {
9998 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, cx);
9999 });
10000 follower
10001 .update(cx, |follower, cx| {
10002 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
10003 })
10004 .unwrap()
10005 .await
10006 .unwrap();
10007 _ = follower.update(cx, |follower, cx| {
10008 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
10009 });
10010
10011 // Scrolling locally breaks the follow
10012 _ = follower.update(cx, |follower, cx| {
10013 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
10014 follower.set_scroll_anchor(
10015 ScrollAnchor {
10016 anchor: top_anchor,
10017 offset: gpui::Point::new(0.0, 0.5),
10018 },
10019 cx,
10020 );
10021 });
10022 assert!(!(*is_still_following.borrow()));
10023}
10024
10025#[gpui::test]
10026async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
10027 init_test(cx, |_| {});
10028
10029 let fs = FakeFs::new(cx.executor());
10030 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
10031 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10032 let pane = workspace
10033 .update(cx, |workspace, _| workspace.active_pane().clone())
10034 .unwrap();
10035
10036 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10037
10038 let leader = pane.update(cx, |_, cx| {
10039 let multibuffer = cx.new_model(|_| MultiBuffer::new(ReadWrite));
10040 cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
10041 });
10042
10043 // Start following the editor when it has no excerpts.
10044 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
10045 let follower_1 = cx
10046 .update_window(*workspace.deref(), |_, cx| {
10047 Editor::from_state_proto(
10048 workspace.root_view(cx).unwrap(),
10049 ViewId {
10050 creator: Default::default(),
10051 id: 0,
10052 },
10053 &mut state_message,
10054 cx,
10055 )
10056 })
10057 .unwrap()
10058 .unwrap()
10059 .await
10060 .unwrap();
10061
10062 let update_message = Rc::new(RefCell::new(None));
10063 follower_1.update(cx, {
10064 let update = update_message.clone();
10065 |_, cx| {
10066 cx.subscribe(&leader, move |_, leader, event, cx| {
10067 leader
10068 .read(cx)
10069 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
10070 })
10071 .detach();
10072 }
10073 });
10074
10075 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
10076 (
10077 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
10078 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
10079 )
10080 });
10081
10082 // Insert some excerpts.
10083 leader.update(cx, |leader, cx| {
10084 leader.buffer.update(cx, |multibuffer, cx| {
10085 let excerpt_ids = multibuffer.push_excerpts(
10086 buffer_1.clone(),
10087 [
10088 ExcerptRange {
10089 context: 1..6,
10090 primary: None,
10091 },
10092 ExcerptRange {
10093 context: 12..15,
10094 primary: None,
10095 },
10096 ExcerptRange {
10097 context: 0..3,
10098 primary: None,
10099 },
10100 ],
10101 cx,
10102 );
10103 multibuffer.insert_excerpts_after(
10104 excerpt_ids[0],
10105 buffer_2.clone(),
10106 [
10107 ExcerptRange {
10108 context: 8..12,
10109 primary: None,
10110 },
10111 ExcerptRange {
10112 context: 0..6,
10113 primary: None,
10114 },
10115 ],
10116 cx,
10117 );
10118 });
10119 });
10120
10121 // Apply the update of adding the excerpts.
10122 follower_1
10123 .update(cx, |follower, cx| {
10124 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
10125 })
10126 .await
10127 .unwrap();
10128 assert_eq!(
10129 follower_1.update(cx, |editor, cx| editor.text(cx)),
10130 leader.update(cx, |editor, cx| editor.text(cx))
10131 );
10132 update_message.borrow_mut().take();
10133
10134 // Start following separately after it already has excerpts.
10135 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
10136 let follower_2 = cx
10137 .update_window(*workspace.deref(), |_, cx| {
10138 Editor::from_state_proto(
10139 workspace.root_view(cx).unwrap().clone(),
10140 ViewId {
10141 creator: Default::default(),
10142 id: 0,
10143 },
10144 &mut state_message,
10145 cx,
10146 )
10147 })
10148 .unwrap()
10149 .unwrap()
10150 .await
10151 .unwrap();
10152 assert_eq!(
10153 follower_2.update(cx, |editor, cx| editor.text(cx)),
10154 leader.update(cx, |editor, cx| editor.text(cx))
10155 );
10156
10157 // Remove some excerpts.
10158 leader.update(cx, |leader, cx| {
10159 leader.buffer.update(cx, |multibuffer, cx| {
10160 let excerpt_ids = multibuffer.excerpt_ids();
10161 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
10162 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
10163 });
10164 });
10165
10166 // Apply the update of removing the excerpts.
10167 follower_1
10168 .update(cx, |follower, cx| {
10169 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
10170 })
10171 .await
10172 .unwrap();
10173 follower_2
10174 .update(cx, |follower, cx| {
10175 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
10176 })
10177 .await
10178 .unwrap();
10179 update_message.borrow_mut().take();
10180 assert_eq!(
10181 follower_1.update(cx, |editor, cx| editor.text(cx)),
10182 leader.update(cx, |editor, cx| editor.text(cx))
10183 );
10184}
10185
10186#[gpui::test]
10187async fn go_to_prev_overlapping_diagnostic(
10188 executor: BackgroundExecutor,
10189 cx: &mut gpui::TestAppContext,
10190) {
10191 init_test(cx, |_| {});
10192
10193 let mut cx = EditorTestContext::new(cx).await;
10194 let lsp_store =
10195 cx.update_editor(|editor, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
10196
10197 cx.set_state(indoc! {"
10198 ˇfn func(abc def: i32) -> u32 {
10199 }
10200 "});
10201
10202 cx.update(|cx| {
10203 lsp_store.update(cx, |lsp_store, cx| {
10204 lsp_store
10205 .update_diagnostics(
10206 LanguageServerId(0),
10207 lsp::PublishDiagnosticsParams {
10208 uri: lsp::Url::from_file_path("/root/file").unwrap(),
10209 version: None,
10210 diagnostics: vec![
10211 lsp::Diagnostic {
10212 range: lsp::Range::new(
10213 lsp::Position::new(0, 11),
10214 lsp::Position::new(0, 12),
10215 ),
10216 severity: Some(lsp::DiagnosticSeverity::ERROR),
10217 ..Default::default()
10218 },
10219 lsp::Diagnostic {
10220 range: lsp::Range::new(
10221 lsp::Position::new(0, 12),
10222 lsp::Position::new(0, 15),
10223 ),
10224 severity: Some(lsp::DiagnosticSeverity::ERROR),
10225 ..Default::default()
10226 },
10227 lsp::Diagnostic {
10228 range: lsp::Range::new(
10229 lsp::Position::new(0, 25),
10230 lsp::Position::new(0, 28),
10231 ),
10232 severity: Some(lsp::DiagnosticSeverity::ERROR),
10233 ..Default::default()
10234 },
10235 ],
10236 },
10237 &[],
10238 cx,
10239 )
10240 .unwrap()
10241 });
10242 });
10243
10244 executor.run_until_parked();
10245
10246 cx.update_editor(|editor, cx| {
10247 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
10248 });
10249
10250 cx.assert_editor_state(indoc! {"
10251 fn func(abc def: i32) -> ˇu32 {
10252 }
10253 "});
10254
10255 cx.update_editor(|editor, cx| {
10256 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
10257 });
10258
10259 cx.assert_editor_state(indoc! {"
10260 fn func(abc ˇdef: i32) -> u32 {
10261 }
10262 "});
10263
10264 cx.update_editor(|editor, cx| {
10265 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
10266 });
10267
10268 cx.assert_editor_state(indoc! {"
10269 fn func(abcˇ def: i32) -> u32 {
10270 }
10271 "});
10272
10273 cx.update_editor(|editor, cx| {
10274 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
10275 });
10276
10277 cx.assert_editor_state(indoc! {"
10278 fn func(abc def: i32) -> ˇu32 {
10279 }
10280 "});
10281}
10282
10283#[gpui::test]
10284async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
10285 init_test(cx, |_| {});
10286
10287 let mut cx = EditorTestContext::new(cx).await;
10288
10289 cx.set_state(indoc! {"
10290 fn func(abˇc def: i32) -> u32 {
10291 }
10292 "});
10293 let lsp_store =
10294 cx.update_editor(|editor, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
10295
10296 cx.update(|cx| {
10297 lsp_store.update(cx, |lsp_store, cx| {
10298 lsp_store.update_diagnostics(
10299 LanguageServerId(0),
10300 lsp::PublishDiagnosticsParams {
10301 uri: lsp::Url::from_file_path("/root/file").unwrap(),
10302 version: None,
10303 diagnostics: vec![lsp::Diagnostic {
10304 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
10305 severity: Some(lsp::DiagnosticSeverity::ERROR),
10306 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
10307 ..Default::default()
10308 }],
10309 },
10310 &[],
10311 cx,
10312 )
10313 })
10314 }).unwrap();
10315 cx.run_until_parked();
10316 cx.update_editor(|editor, cx| hover_popover::hover(editor, &Default::default(), cx));
10317 cx.run_until_parked();
10318 cx.update_editor(|editor, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
10319}
10320
10321#[gpui::test]
10322async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
10323 init_test(cx, |_| {});
10324
10325 let mut cx = EditorTestContext::new(cx).await;
10326
10327 let diff_base = r#"
10328 use some::mod;
10329
10330 const A: u32 = 42;
10331
10332 fn main() {
10333 println!("hello");
10334
10335 println!("world");
10336 }
10337 "#
10338 .unindent();
10339
10340 // Edits are modified, removed, modified, added
10341 cx.set_state(
10342 &r#"
10343 use some::modified;
10344
10345 ˇ
10346 fn main() {
10347 println!("hello there");
10348
10349 println!("around the");
10350 println!("world");
10351 }
10352 "#
10353 .unindent(),
10354 );
10355
10356 cx.set_diff_base(&diff_base);
10357 executor.run_until_parked();
10358
10359 cx.update_editor(|editor, cx| {
10360 //Wrap around the bottom of the buffer
10361 for _ in 0..3 {
10362 editor.go_to_next_hunk(&GoToHunk, cx);
10363 }
10364 });
10365
10366 cx.assert_editor_state(
10367 &r#"
10368 ˇuse some::modified;
10369
10370
10371 fn main() {
10372 println!("hello there");
10373
10374 println!("around the");
10375 println!("world");
10376 }
10377 "#
10378 .unindent(),
10379 );
10380
10381 cx.update_editor(|editor, cx| {
10382 //Wrap around the top of the buffer
10383 for _ in 0..2 {
10384 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10385 }
10386 });
10387
10388 cx.assert_editor_state(
10389 &r#"
10390 use some::modified;
10391
10392
10393 fn main() {
10394 ˇ println!("hello there");
10395
10396 println!("around the");
10397 println!("world");
10398 }
10399 "#
10400 .unindent(),
10401 );
10402
10403 cx.update_editor(|editor, cx| {
10404 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10405 });
10406
10407 cx.assert_editor_state(
10408 &r#"
10409 use some::modified;
10410
10411 ˇ
10412 fn main() {
10413 println!("hello there");
10414
10415 println!("around the");
10416 println!("world");
10417 }
10418 "#
10419 .unindent(),
10420 );
10421
10422 cx.update_editor(|editor, cx| {
10423 for _ in 0..3 {
10424 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10425 }
10426 });
10427
10428 cx.assert_editor_state(
10429 &r#"
10430 use some::modified;
10431
10432
10433 fn main() {
10434 ˇ println!("hello there");
10435
10436 println!("around the");
10437 println!("world");
10438 }
10439 "#
10440 .unindent(),
10441 );
10442
10443 cx.update_editor(|editor, cx| {
10444 editor.fold(&Fold, cx);
10445
10446 //Make sure that the fold only gets one hunk
10447 for _ in 0..4 {
10448 editor.go_to_next_hunk(&GoToHunk, cx);
10449 }
10450 });
10451
10452 cx.assert_editor_state(
10453 &r#"
10454 ˇuse some::modified;
10455
10456
10457 fn main() {
10458 println!("hello there");
10459
10460 println!("around the");
10461 println!("world");
10462 }
10463 "#
10464 .unindent(),
10465 );
10466}
10467
10468#[test]
10469fn test_split_words() {
10470 fn split(text: &str) -> Vec<&str> {
10471 split_words(text).collect()
10472 }
10473
10474 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
10475 assert_eq!(split("hello_world"), &["hello_", "world"]);
10476 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
10477 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
10478 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
10479 assert_eq!(split("helloworld"), &["helloworld"]);
10480
10481 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
10482}
10483
10484#[gpui::test]
10485async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
10486 init_test(cx, |_| {});
10487
10488 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
10489 let mut assert = |before, after| {
10490 let _state_context = cx.set_state(before);
10491 cx.run_until_parked();
10492 cx.update_editor(|editor, cx| {
10493 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
10494 });
10495 cx.assert_editor_state(after);
10496 };
10497
10498 // Outside bracket jumps to outside of matching bracket
10499 assert("console.logˇ(var);", "console.log(var)ˇ;");
10500 assert("console.log(var)ˇ;", "console.logˇ(var);");
10501
10502 // Inside bracket jumps to inside of matching bracket
10503 assert("console.log(ˇvar);", "console.log(varˇ);");
10504 assert("console.log(varˇ);", "console.log(ˇvar);");
10505
10506 // When outside a bracket and inside, favor jumping to the inside bracket
10507 assert(
10508 "console.log('foo', [1, 2, 3]ˇ);",
10509 "console.log(ˇ'foo', [1, 2, 3]);",
10510 );
10511 assert(
10512 "console.log(ˇ'foo', [1, 2, 3]);",
10513 "console.log('foo', [1, 2, 3]ˇ);",
10514 );
10515
10516 // Bias forward if two options are equally likely
10517 assert(
10518 "let result = curried_fun()ˇ();",
10519 "let result = curried_fun()()ˇ;",
10520 );
10521
10522 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
10523 assert(
10524 indoc! {"
10525 function test() {
10526 console.log('test')ˇ
10527 }"},
10528 indoc! {"
10529 function test() {
10530 console.logˇ('test')
10531 }"},
10532 );
10533}
10534
10535#[gpui::test]
10536async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
10537 init_test(cx, |_| {});
10538
10539 let fs = FakeFs::new(cx.executor());
10540 fs.insert_tree(
10541 "/a",
10542 json!({
10543 "main.rs": "fn main() { let a = 5; }",
10544 "other.rs": "// Test file",
10545 }),
10546 )
10547 .await;
10548 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10549
10550 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10551 language_registry.add(Arc::new(Language::new(
10552 LanguageConfig {
10553 name: "Rust".into(),
10554 matcher: LanguageMatcher {
10555 path_suffixes: vec!["rs".to_string()],
10556 ..Default::default()
10557 },
10558 brackets: BracketPairConfig {
10559 pairs: vec![BracketPair {
10560 start: "{".to_string(),
10561 end: "}".to_string(),
10562 close: true,
10563 surround: true,
10564 newline: true,
10565 }],
10566 disabled_scopes_by_bracket_ix: Vec::new(),
10567 },
10568 ..Default::default()
10569 },
10570 Some(tree_sitter_rust::LANGUAGE.into()),
10571 )));
10572 let mut fake_servers = language_registry.register_fake_lsp(
10573 "Rust",
10574 FakeLspAdapter {
10575 capabilities: lsp::ServerCapabilities {
10576 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
10577 first_trigger_character: "{".to_string(),
10578 more_trigger_character: None,
10579 }),
10580 ..Default::default()
10581 },
10582 ..Default::default()
10583 },
10584 );
10585
10586 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10587
10588 let cx = &mut VisualTestContext::from_window(*workspace, cx);
10589
10590 let worktree_id = workspace
10591 .update(cx, |workspace, cx| {
10592 workspace.project().update(cx, |project, cx| {
10593 project.worktrees(cx).next().unwrap().read(cx).id()
10594 })
10595 })
10596 .unwrap();
10597
10598 let buffer = project
10599 .update(cx, |project, cx| {
10600 project.open_local_buffer("/a/main.rs", cx)
10601 })
10602 .await
10603 .unwrap();
10604 let editor_handle = workspace
10605 .update(cx, |workspace, cx| {
10606 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
10607 })
10608 .unwrap()
10609 .await
10610 .unwrap()
10611 .downcast::<Editor>()
10612 .unwrap();
10613
10614 cx.executor().start_waiting();
10615 let fake_server = fake_servers.next().await.unwrap();
10616
10617 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
10618 assert_eq!(
10619 params.text_document_position.text_document.uri,
10620 lsp::Url::from_file_path("/a/main.rs").unwrap(),
10621 );
10622 assert_eq!(
10623 params.text_document_position.position,
10624 lsp::Position::new(0, 21),
10625 );
10626
10627 Ok(Some(vec![lsp::TextEdit {
10628 new_text: "]".to_string(),
10629 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10630 }]))
10631 });
10632
10633 editor_handle.update(cx, |editor, cx| {
10634 editor.focus(cx);
10635 editor.change_selections(None, cx, |s| {
10636 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
10637 });
10638 editor.handle_input("{", cx);
10639 });
10640
10641 cx.executor().run_until_parked();
10642
10643 buffer.update(cx, |buffer, _| {
10644 assert_eq!(
10645 buffer.text(),
10646 "fn main() { let a = {5}; }",
10647 "No extra braces from on type formatting should appear in the buffer"
10648 )
10649 });
10650}
10651
10652#[gpui::test]
10653async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
10654 init_test(cx, |_| {});
10655
10656 let fs = FakeFs::new(cx.executor());
10657 fs.insert_tree(
10658 "/a",
10659 json!({
10660 "main.rs": "fn main() { let a = 5; }",
10661 "other.rs": "// Test file",
10662 }),
10663 )
10664 .await;
10665
10666 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10667
10668 let server_restarts = Arc::new(AtomicUsize::new(0));
10669 let closure_restarts = Arc::clone(&server_restarts);
10670 let language_server_name = "test language server";
10671 let language_name: LanguageName = "Rust".into();
10672
10673 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10674 language_registry.add(Arc::new(Language::new(
10675 LanguageConfig {
10676 name: language_name.clone(),
10677 matcher: LanguageMatcher {
10678 path_suffixes: vec!["rs".to_string()],
10679 ..Default::default()
10680 },
10681 ..Default::default()
10682 },
10683 Some(tree_sitter_rust::LANGUAGE.into()),
10684 )));
10685 let mut fake_servers = language_registry.register_fake_lsp(
10686 "Rust",
10687 FakeLspAdapter {
10688 name: language_server_name,
10689 initialization_options: Some(json!({
10690 "testOptionValue": true
10691 })),
10692 initializer: Some(Box::new(move |fake_server| {
10693 let task_restarts = Arc::clone(&closure_restarts);
10694 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
10695 task_restarts.fetch_add(1, atomic::Ordering::Release);
10696 futures::future::ready(Ok(()))
10697 });
10698 })),
10699 ..Default::default()
10700 },
10701 );
10702
10703 let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10704 let _buffer = project
10705 .update(cx, |project, cx| {
10706 project.open_local_buffer_with_lsp("/a/main.rs", cx)
10707 })
10708 .await
10709 .unwrap();
10710 let _fake_server = fake_servers.next().await.unwrap();
10711 update_test_language_settings(cx, |language_settings| {
10712 language_settings.languages.insert(
10713 language_name.clone(),
10714 LanguageSettingsContent {
10715 tab_size: NonZeroU32::new(8),
10716 ..Default::default()
10717 },
10718 );
10719 });
10720 cx.executor().run_until_parked();
10721 assert_eq!(
10722 server_restarts.load(atomic::Ordering::Acquire),
10723 0,
10724 "Should not restart LSP server on an unrelated change"
10725 );
10726
10727 update_test_project_settings(cx, |project_settings| {
10728 project_settings.lsp.insert(
10729 "Some other server name".into(),
10730 LspSettings {
10731 binary: None,
10732 settings: None,
10733 initialization_options: Some(json!({
10734 "some other init value": false
10735 })),
10736 },
10737 );
10738 });
10739 cx.executor().run_until_parked();
10740 assert_eq!(
10741 server_restarts.load(atomic::Ordering::Acquire),
10742 0,
10743 "Should not restart LSP server on an unrelated LSP settings change"
10744 );
10745
10746 update_test_project_settings(cx, |project_settings| {
10747 project_settings.lsp.insert(
10748 language_server_name.into(),
10749 LspSettings {
10750 binary: None,
10751 settings: None,
10752 initialization_options: Some(json!({
10753 "anotherInitValue": false
10754 })),
10755 },
10756 );
10757 });
10758 cx.executor().run_until_parked();
10759 assert_eq!(
10760 server_restarts.load(atomic::Ordering::Acquire),
10761 1,
10762 "Should restart LSP server on a related LSP settings change"
10763 );
10764
10765 update_test_project_settings(cx, |project_settings| {
10766 project_settings.lsp.insert(
10767 language_server_name.into(),
10768 LspSettings {
10769 binary: None,
10770 settings: None,
10771 initialization_options: Some(json!({
10772 "anotherInitValue": false
10773 })),
10774 },
10775 );
10776 });
10777 cx.executor().run_until_parked();
10778 assert_eq!(
10779 server_restarts.load(atomic::Ordering::Acquire),
10780 1,
10781 "Should not restart LSP server on a related LSP settings change that is the same"
10782 );
10783
10784 update_test_project_settings(cx, |project_settings| {
10785 project_settings.lsp.insert(
10786 language_server_name.into(),
10787 LspSettings {
10788 binary: None,
10789 settings: None,
10790 initialization_options: None,
10791 },
10792 );
10793 });
10794 cx.executor().run_until_parked();
10795 assert_eq!(
10796 server_restarts.load(atomic::Ordering::Acquire),
10797 2,
10798 "Should restart LSP server on another related LSP settings change"
10799 );
10800}
10801
10802#[gpui::test]
10803async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
10804 init_test(cx, |_| {});
10805
10806 let mut cx = EditorLspTestContext::new_rust(
10807 lsp::ServerCapabilities {
10808 completion_provider: Some(lsp::CompletionOptions {
10809 trigger_characters: Some(vec![".".to_string()]),
10810 resolve_provider: Some(true),
10811 ..Default::default()
10812 }),
10813 ..Default::default()
10814 },
10815 cx,
10816 )
10817 .await;
10818
10819 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10820 cx.simulate_keystroke(".");
10821 let completion_item = lsp::CompletionItem {
10822 label: "some".into(),
10823 kind: Some(lsp::CompletionItemKind::SNIPPET),
10824 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
10825 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
10826 kind: lsp::MarkupKind::Markdown,
10827 value: "```rust\nSome(2)\n```".to_string(),
10828 })),
10829 deprecated: Some(false),
10830 sort_text: Some("fffffff2".to_string()),
10831 filter_text: Some("some".to_string()),
10832 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
10833 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10834 range: lsp::Range {
10835 start: lsp::Position {
10836 line: 0,
10837 character: 22,
10838 },
10839 end: lsp::Position {
10840 line: 0,
10841 character: 22,
10842 },
10843 },
10844 new_text: "Some(2)".to_string(),
10845 })),
10846 additional_text_edits: Some(vec![lsp::TextEdit {
10847 range: lsp::Range {
10848 start: lsp::Position {
10849 line: 0,
10850 character: 20,
10851 },
10852 end: lsp::Position {
10853 line: 0,
10854 character: 22,
10855 },
10856 },
10857 new_text: "".to_string(),
10858 }]),
10859 ..Default::default()
10860 };
10861
10862 let closure_completion_item = completion_item.clone();
10863 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10864 let task_completion_item = closure_completion_item.clone();
10865 async move {
10866 Ok(Some(lsp::CompletionResponse::Array(vec![
10867 task_completion_item,
10868 ])))
10869 }
10870 });
10871
10872 request.next().await;
10873
10874 cx.condition(|editor, _| editor.context_menu_visible())
10875 .await;
10876 let apply_additional_edits = cx.update_editor(|editor, cx| {
10877 editor
10878 .confirm_completion(&ConfirmCompletion::default(), cx)
10879 .unwrap()
10880 });
10881 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
10882
10883 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
10884 let task_completion_item = completion_item.clone();
10885 async move { Ok(task_completion_item) }
10886 })
10887 .next()
10888 .await
10889 .unwrap();
10890 apply_additional_edits.await.unwrap();
10891 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
10892}
10893
10894#[gpui::test]
10895async fn test_completions_resolve_updates_labels_if_filter_text_matches(
10896 cx: &mut gpui::TestAppContext,
10897) {
10898 init_test(cx, |_| {});
10899
10900 let mut cx = EditorLspTestContext::new_rust(
10901 lsp::ServerCapabilities {
10902 completion_provider: Some(lsp::CompletionOptions {
10903 trigger_characters: Some(vec![".".to_string()]),
10904 resolve_provider: Some(true),
10905 ..Default::default()
10906 }),
10907 ..Default::default()
10908 },
10909 cx,
10910 )
10911 .await;
10912
10913 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10914 cx.simulate_keystroke(".");
10915
10916 let item1 = lsp::CompletionItem {
10917 label: "id".to_string(),
10918 filter_text: Some("id".to_string()),
10919 detail: None,
10920 documentation: None,
10921 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10922 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10923 new_text: ".id".to_string(),
10924 })),
10925 ..lsp::CompletionItem::default()
10926 };
10927
10928 let item2 = lsp::CompletionItem {
10929 label: "other".to_string(),
10930 filter_text: Some("other".to_string()),
10931 detail: None,
10932 documentation: None,
10933 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10934 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10935 new_text: ".other".to_string(),
10936 })),
10937 ..lsp::CompletionItem::default()
10938 };
10939
10940 let item1 = item1.clone();
10941 cx.handle_request::<lsp::request::Completion, _, _>({
10942 let item1 = item1.clone();
10943 move |_, _, _| {
10944 let item1 = item1.clone();
10945 let item2 = item2.clone();
10946 async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
10947 }
10948 })
10949 .next()
10950 .await;
10951
10952 cx.condition(|editor, _| editor.context_menu_visible())
10953 .await;
10954 cx.update_editor(|editor, _| {
10955 let context_menu = editor.context_menu.borrow_mut();
10956 let context_menu = context_menu
10957 .as_ref()
10958 .expect("Should have the context menu deployed");
10959 match context_menu {
10960 CodeContextMenu::Completions(completions_menu) => {
10961 let completions = completions_menu.completions.borrow_mut();
10962 assert_eq!(
10963 completions
10964 .iter()
10965 .map(|completion| &completion.label.text)
10966 .collect::<Vec<_>>(),
10967 vec!["id", "other"]
10968 )
10969 }
10970 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
10971 }
10972 });
10973
10974 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>({
10975 let item1 = item1.clone();
10976 move |_, item_to_resolve, _| {
10977 let item1 = item1.clone();
10978 async move {
10979 if item1 == item_to_resolve {
10980 Ok(lsp::CompletionItem {
10981 label: "method id()".to_string(),
10982 filter_text: Some("id".to_string()),
10983 detail: Some("Now resolved!".to_string()),
10984 documentation: Some(lsp::Documentation::String("Docs".to_string())),
10985 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10986 range: lsp::Range::new(
10987 lsp::Position::new(0, 22),
10988 lsp::Position::new(0, 22),
10989 ),
10990 new_text: ".id".to_string(),
10991 })),
10992 ..lsp::CompletionItem::default()
10993 })
10994 } else {
10995 Ok(item_to_resolve)
10996 }
10997 }
10998 }
10999 })
11000 .next()
11001 .await
11002 .unwrap();
11003 cx.run_until_parked();
11004
11005 cx.update_editor(|editor, cx| {
11006 editor.context_menu_next(&Default::default(), cx);
11007 });
11008
11009 cx.update_editor(|editor, _| {
11010 let context_menu = editor.context_menu.borrow_mut();
11011 let context_menu = context_menu
11012 .as_ref()
11013 .expect("Should have the context menu deployed");
11014 match context_menu {
11015 CodeContextMenu::Completions(completions_menu) => {
11016 let completions = completions_menu.completions.borrow_mut();
11017 assert_eq!(
11018 completions
11019 .iter()
11020 .map(|completion| &completion.label.text)
11021 .collect::<Vec<_>>(),
11022 vec!["method id()", "other"],
11023 "Should update first completion label, but not second as the filter text did not match."
11024 );
11025 }
11026 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
11027 }
11028 });
11029}
11030
11031#[gpui::test]
11032async fn test_completions_resolve_happens_once(cx: &mut gpui::TestAppContext) {
11033 init_test(cx, |_| {});
11034
11035 let mut cx = EditorLspTestContext::new_rust(
11036 lsp::ServerCapabilities {
11037 completion_provider: Some(lsp::CompletionOptions {
11038 trigger_characters: Some(vec![".".to_string()]),
11039 resolve_provider: Some(true),
11040 ..Default::default()
11041 }),
11042 ..Default::default()
11043 },
11044 cx,
11045 )
11046 .await;
11047
11048 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
11049 cx.simulate_keystroke(".");
11050
11051 let unresolved_item_1 = lsp::CompletionItem {
11052 label: "id".to_string(),
11053 filter_text: Some("id".to_string()),
11054 detail: None,
11055 documentation: None,
11056 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11057 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11058 new_text: ".id".to_string(),
11059 })),
11060 ..lsp::CompletionItem::default()
11061 };
11062 let resolved_item_1 = lsp::CompletionItem {
11063 additional_text_edits: Some(vec![lsp::TextEdit {
11064 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
11065 new_text: "!!".to_string(),
11066 }]),
11067 ..unresolved_item_1.clone()
11068 };
11069 let unresolved_item_2 = lsp::CompletionItem {
11070 label: "other".to_string(),
11071 filter_text: Some("other".to_string()),
11072 detail: None,
11073 documentation: None,
11074 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11075 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11076 new_text: ".other".to_string(),
11077 })),
11078 ..lsp::CompletionItem::default()
11079 };
11080 let resolved_item_2 = lsp::CompletionItem {
11081 additional_text_edits: Some(vec![lsp::TextEdit {
11082 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
11083 new_text: "??".to_string(),
11084 }]),
11085 ..unresolved_item_2.clone()
11086 };
11087
11088 let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
11089 let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
11090 cx.lsp
11091 .server
11092 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
11093 let unresolved_item_1 = unresolved_item_1.clone();
11094 let resolved_item_1 = resolved_item_1.clone();
11095 let unresolved_item_2 = unresolved_item_2.clone();
11096 let resolved_item_2 = resolved_item_2.clone();
11097 let resolve_requests_1 = resolve_requests_1.clone();
11098 let resolve_requests_2 = resolve_requests_2.clone();
11099 move |unresolved_request, _| {
11100 let unresolved_item_1 = unresolved_item_1.clone();
11101 let resolved_item_1 = resolved_item_1.clone();
11102 let unresolved_item_2 = unresolved_item_2.clone();
11103 let resolved_item_2 = resolved_item_2.clone();
11104 let resolve_requests_1 = resolve_requests_1.clone();
11105 let resolve_requests_2 = resolve_requests_2.clone();
11106 async move {
11107 if unresolved_request == unresolved_item_1 {
11108 resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
11109 Ok(resolved_item_1.clone())
11110 } else if unresolved_request == unresolved_item_2 {
11111 resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
11112 Ok(resolved_item_2.clone())
11113 } else {
11114 panic!("Unexpected completion item {unresolved_request:?}")
11115 }
11116 }
11117 }
11118 })
11119 .detach();
11120
11121 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
11122 let unresolved_item_1 = unresolved_item_1.clone();
11123 let unresolved_item_2 = unresolved_item_2.clone();
11124 async move {
11125 Ok(Some(lsp::CompletionResponse::Array(vec![
11126 unresolved_item_1,
11127 unresolved_item_2,
11128 ])))
11129 }
11130 })
11131 .next()
11132 .await;
11133
11134 cx.condition(|editor, _| editor.context_menu_visible())
11135 .await;
11136 cx.update_editor(|editor, _| {
11137 let context_menu = editor.context_menu.borrow_mut();
11138 let context_menu = context_menu
11139 .as_ref()
11140 .expect("Should have the context menu deployed");
11141 match context_menu {
11142 CodeContextMenu::Completions(completions_menu) => {
11143 let completions = completions_menu.completions.borrow_mut();
11144 assert_eq!(
11145 completions
11146 .iter()
11147 .map(|completion| &completion.label.text)
11148 .collect::<Vec<_>>(),
11149 vec!["id", "other"]
11150 )
11151 }
11152 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
11153 }
11154 });
11155 cx.run_until_parked();
11156
11157 cx.update_editor(|editor, cx| {
11158 editor.context_menu_next(&ContextMenuNext, cx);
11159 });
11160 cx.run_until_parked();
11161 cx.update_editor(|editor, cx| {
11162 editor.context_menu_prev(&ContextMenuPrev, cx);
11163 });
11164 cx.run_until_parked();
11165 cx.update_editor(|editor, cx| {
11166 editor.context_menu_next(&ContextMenuNext, cx);
11167 });
11168 cx.run_until_parked();
11169 cx.update_editor(|editor, cx| {
11170 editor
11171 .compose_completion(&ComposeCompletion::default(), cx)
11172 .expect("No task returned")
11173 })
11174 .await
11175 .expect("Completion failed");
11176 cx.run_until_parked();
11177
11178 cx.update_editor(|editor, cx| {
11179 assert_eq!(
11180 resolve_requests_1.load(atomic::Ordering::Acquire),
11181 1,
11182 "Should always resolve once despite multiple selections"
11183 );
11184 assert_eq!(
11185 resolve_requests_2.load(atomic::Ordering::Acquire),
11186 1,
11187 "Should always resolve once after multiple selections and applying the completion"
11188 );
11189 assert_eq!(
11190 editor.text(cx),
11191 "fn main() { let a = ??.other; }",
11192 "Should use resolved data when applying the completion"
11193 );
11194 });
11195}
11196
11197#[gpui::test]
11198async fn test_completions_default_resolve_data_handling(cx: &mut gpui::TestAppContext) {
11199 init_test(cx, |_| {});
11200
11201 let item_0 = lsp::CompletionItem {
11202 label: "abs".into(),
11203 insert_text: Some("abs".into()),
11204 data: Some(json!({ "very": "special"})),
11205 insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
11206 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
11207 lsp::InsertReplaceEdit {
11208 new_text: "abs".to_string(),
11209 insert: lsp::Range::default(),
11210 replace: lsp::Range::default(),
11211 },
11212 )),
11213 ..lsp::CompletionItem::default()
11214 };
11215 let items = iter::once(item_0.clone())
11216 .chain((11..51).map(|i| lsp::CompletionItem {
11217 label: format!("item_{}", i),
11218 insert_text: Some(format!("item_{}", i)),
11219 insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
11220 ..lsp::CompletionItem::default()
11221 }))
11222 .collect::<Vec<_>>();
11223
11224 let default_commit_characters = vec!["?".to_string()];
11225 let default_data = json!({ "default": "data"});
11226 let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
11227 let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
11228 let default_edit_range = lsp::Range {
11229 start: lsp::Position {
11230 line: 0,
11231 character: 5,
11232 },
11233 end: lsp::Position {
11234 line: 0,
11235 character: 5,
11236 },
11237 };
11238
11239 let item_0_out = lsp::CompletionItem {
11240 commit_characters: Some(default_commit_characters.clone()),
11241 insert_text_format: Some(default_insert_text_format),
11242 ..item_0
11243 };
11244 let items_out = iter::once(item_0_out)
11245 .chain(items[1..].iter().map(|item| lsp::CompletionItem {
11246 commit_characters: Some(default_commit_characters.clone()),
11247 data: Some(default_data.clone()),
11248 insert_text_mode: Some(default_insert_text_mode),
11249 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11250 range: default_edit_range,
11251 new_text: item.label.clone(),
11252 })),
11253 ..item.clone()
11254 }))
11255 .collect::<Vec<lsp::CompletionItem>>();
11256
11257 let mut cx = EditorLspTestContext::new_rust(
11258 lsp::ServerCapabilities {
11259 completion_provider: Some(lsp::CompletionOptions {
11260 trigger_characters: Some(vec![".".to_string()]),
11261 resolve_provider: Some(true),
11262 ..Default::default()
11263 }),
11264 ..Default::default()
11265 },
11266 cx,
11267 )
11268 .await;
11269
11270 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
11271 cx.simulate_keystroke(".");
11272
11273 let completion_data = default_data.clone();
11274 let completion_characters = default_commit_characters.clone();
11275 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
11276 let default_data = completion_data.clone();
11277 let default_commit_characters = completion_characters.clone();
11278 let items = items.clone();
11279 async move {
11280 Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
11281 items,
11282 item_defaults: Some(lsp::CompletionListItemDefaults {
11283 data: Some(default_data.clone()),
11284 commit_characters: Some(default_commit_characters.clone()),
11285 edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
11286 default_edit_range,
11287 )),
11288 insert_text_format: Some(default_insert_text_format),
11289 insert_text_mode: Some(default_insert_text_mode),
11290 }),
11291 ..lsp::CompletionList::default()
11292 })))
11293 }
11294 })
11295 .next()
11296 .await;
11297
11298 let resolved_items = Arc::new(Mutex::new(Vec::new()));
11299 cx.lsp
11300 .server
11301 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
11302 let closure_resolved_items = resolved_items.clone();
11303 move |item_to_resolve, _| {
11304 let closure_resolved_items = closure_resolved_items.clone();
11305 async move {
11306 closure_resolved_items.lock().push(item_to_resolve.clone());
11307 Ok(item_to_resolve)
11308 }
11309 }
11310 })
11311 .detach();
11312
11313 cx.condition(|editor, _| editor.context_menu_visible())
11314 .await;
11315 cx.run_until_parked();
11316 cx.update_editor(|editor, _| {
11317 let menu = editor.context_menu.borrow_mut();
11318 match menu.as_ref().expect("should have the completions menu") {
11319 CodeContextMenu::Completions(completions_menu) => {
11320 assert_eq!(
11321 completions_menu
11322 .entries
11323 .borrow()
11324 .iter()
11325 .flat_map(|c| match c {
11326 CompletionEntry::Match(mat) => Some(mat.string.clone()),
11327 _ => None,
11328 })
11329 .collect::<Vec<String>>(),
11330 items_out
11331 .iter()
11332 .map(|completion| completion.label.clone())
11333 .collect::<Vec<String>>()
11334 );
11335 }
11336 CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
11337 }
11338 });
11339 // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
11340 // with 4 from the end.
11341 assert_eq!(
11342 *resolved_items.lock(),
11343 [
11344 &items_out[0..16],
11345 &items_out[items_out.len() - 4..items_out.len()]
11346 ]
11347 .concat()
11348 .iter()
11349 .cloned()
11350 .collect::<Vec<lsp::CompletionItem>>()
11351 );
11352 resolved_items.lock().clear();
11353
11354 cx.update_editor(|editor, cx| {
11355 editor.context_menu_prev(&ContextMenuPrev, cx);
11356 });
11357 cx.run_until_parked();
11358 // Completions that have already been resolved are skipped.
11359 assert_eq!(
11360 *resolved_items.lock(),
11361 items_out[items_out.len() - 16..items_out.len() - 4]
11362 .iter()
11363 .cloned()
11364 .collect::<Vec<lsp::CompletionItem>>()
11365 );
11366 resolved_items.lock().clear();
11367}
11368
11369#[gpui::test]
11370async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
11371 init_test(cx, |_| {});
11372
11373 let mut cx = EditorLspTestContext::new(
11374 Language::new(
11375 LanguageConfig {
11376 matcher: LanguageMatcher {
11377 path_suffixes: vec!["jsx".into()],
11378 ..Default::default()
11379 },
11380 overrides: [(
11381 "element".into(),
11382 LanguageConfigOverride {
11383 word_characters: Override::Set(['-'].into_iter().collect()),
11384 ..Default::default()
11385 },
11386 )]
11387 .into_iter()
11388 .collect(),
11389 ..Default::default()
11390 },
11391 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
11392 )
11393 .with_override_query("(jsx_self_closing_element) @element")
11394 .unwrap(),
11395 lsp::ServerCapabilities {
11396 completion_provider: Some(lsp::CompletionOptions {
11397 trigger_characters: Some(vec![":".to_string()]),
11398 ..Default::default()
11399 }),
11400 ..Default::default()
11401 },
11402 cx,
11403 )
11404 .await;
11405
11406 cx.lsp
11407 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
11408 Ok(Some(lsp::CompletionResponse::Array(vec![
11409 lsp::CompletionItem {
11410 label: "bg-blue".into(),
11411 ..Default::default()
11412 },
11413 lsp::CompletionItem {
11414 label: "bg-red".into(),
11415 ..Default::default()
11416 },
11417 lsp::CompletionItem {
11418 label: "bg-yellow".into(),
11419 ..Default::default()
11420 },
11421 ])))
11422 });
11423
11424 cx.set_state(r#"<p class="bgˇ" />"#);
11425
11426 // Trigger completion when typing a dash, because the dash is an extra
11427 // word character in the 'element' scope, which contains the cursor.
11428 cx.simulate_keystroke("-");
11429 cx.executor().run_until_parked();
11430 cx.update_editor(|editor, _| {
11431 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11432 {
11433 assert_eq!(
11434 completion_menu_entries(&menu),
11435 &["bg-red", "bg-blue", "bg-yellow"]
11436 );
11437 } else {
11438 panic!("expected completion menu to be open");
11439 }
11440 });
11441
11442 cx.simulate_keystroke("l");
11443 cx.executor().run_until_parked();
11444 cx.update_editor(|editor, _| {
11445 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11446 {
11447 assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
11448 } else {
11449 panic!("expected completion menu to be open");
11450 }
11451 });
11452
11453 // When filtering completions, consider the character after the '-' to
11454 // be the start of a subword.
11455 cx.set_state(r#"<p class="yelˇ" />"#);
11456 cx.simulate_keystroke("l");
11457 cx.executor().run_until_parked();
11458 cx.update_editor(|editor, _| {
11459 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11460 {
11461 assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
11462 } else {
11463 panic!("expected completion menu to be open");
11464 }
11465 });
11466}
11467
11468fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
11469 let entries = menu.entries.borrow();
11470 entries
11471 .iter()
11472 .flat_map(|e| match e {
11473 CompletionEntry::Match(mat) => Some(mat.string.clone()),
11474 _ => None,
11475 })
11476 .collect()
11477}
11478
11479#[gpui::test]
11480async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
11481 init_test(cx, |settings| {
11482 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
11483 FormatterList(vec![Formatter::Prettier].into()),
11484 ))
11485 });
11486
11487 let fs = FakeFs::new(cx.executor());
11488 fs.insert_file("/file.ts", Default::default()).await;
11489
11490 let project = Project::test(fs, ["/file.ts".as_ref()], cx).await;
11491 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11492
11493 language_registry.add(Arc::new(Language::new(
11494 LanguageConfig {
11495 name: "TypeScript".into(),
11496 matcher: LanguageMatcher {
11497 path_suffixes: vec!["ts".to_string()],
11498 ..Default::default()
11499 },
11500 ..Default::default()
11501 },
11502 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
11503 )));
11504 update_test_language_settings(cx, |settings| {
11505 settings.defaults.prettier = Some(PrettierSettings {
11506 allowed: true,
11507 ..PrettierSettings::default()
11508 });
11509 });
11510
11511 let test_plugin = "test_plugin";
11512 let _ = language_registry.register_fake_lsp(
11513 "TypeScript",
11514 FakeLspAdapter {
11515 prettier_plugins: vec![test_plugin],
11516 ..Default::default()
11517 },
11518 );
11519
11520 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
11521 let buffer = project
11522 .update(cx, |project, cx| project.open_local_buffer("/file.ts", cx))
11523 .await
11524 .unwrap();
11525
11526 let buffer_text = "one\ntwo\nthree\n";
11527 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
11528 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
11529 editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
11530
11531 editor
11532 .update(cx, |editor, cx| {
11533 editor.perform_format(
11534 project.clone(),
11535 FormatTrigger::Manual,
11536 FormatTarget::Buffers,
11537 cx,
11538 )
11539 })
11540 .unwrap()
11541 .await;
11542 assert_eq!(
11543 editor.update(cx, |editor, cx| editor.text(cx)),
11544 buffer_text.to_string() + prettier_format_suffix,
11545 "Test prettier formatting was not applied to the original buffer text",
11546 );
11547
11548 update_test_language_settings(cx, |settings| {
11549 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
11550 });
11551 let format = editor.update(cx, |editor, cx| {
11552 editor.perform_format(
11553 project.clone(),
11554 FormatTrigger::Manual,
11555 FormatTarget::Buffers,
11556 cx,
11557 )
11558 });
11559 format.await.unwrap();
11560 assert_eq!(
11561 editor.update(cx, |editor, cx| editor.text(cx)),
11562 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
11563 "Autoformatting (via test prettier) was not applied to the original buffer text",
11564 );
11565}
11566
11567#[gpui::test]
11568async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
11569 init_test(cx, |_| {});
11570 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11571 let base_text = indoc! {r#"
11572 struct Row;
11573 struct Row1;
11574 struct Row2;
11575
11576 struct Row4;
11577 struct Row5;
11578 struct Row6;
11579
11580 struct Row8;
11581 struct Row9;
11582 struct Row10;"#};
11583
11584 // When addition hunks are not adjacent to carets, no hunk revert is performed
11585 assert_hunk_revert(
11586 indoc! {r#"struct Row;
11587 struct Row1;
11588 struct Row1.1;
11589 struct Row1.2;
11590 struct Row2;ˇ
11591
11592 struct Row4;
11593 struct Row5;
11594 struct Row6;
11595
11596 struct Row8;
11597 ˇstruct Row9;
11598 struct Row9.1;
11599 struct Row9.2;
11600 struct Row9.3;
11601 struct Row10;"#},
11602 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
11603 indoc! {r#"struct Row;
11604 struct Row1;
11605 struct Row1.1;
11606 struct Row1.2;
11607 struct Row2;ˇ
11608
11609 struct Row4;
11610 struct Row5;
11611 struct Row6;
11612
11613 struct Row8;
11614 ˇstruct Row9;
11615 struct Row9.1;
11616 struct Row9.2;
11617 struct Row9.3;
11618 struct Row10;"#},
11619 base_text,
11620 &mut cx,
11621 );
11622 // Same for selections
11623 assert_hunk_revert(
11624 indoc! {r#"struct Row;
11625 struct Row1;
11626 struct Row2;
11627 struct Row2.1;
11628 struct Row2.2;
11629 «ˇ
11630 struct Row4;
11631 struct» Row5;
11632 «struct Row6;
11633 ˇ»
11634 struct Row9.1;
11635 struct Row9.2;
11636 struct Row9.3;
11637 struct Row8;
11638 struct Row9;
11639 struct Row10;"#},
11640 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
11641 indoc! {r#"struct Row;
11642 struct Row1;
11643 struct Row2;
11644 struct Row2.1;
11645 struct Row2.2;
11646 «ˇ
11647 struct Row4;
11648 struct» Row5;
11649 «struct Row6;
11650 ˇ»
11651 struct Row9.1;
11652 struct Row9.2;
11653 struct Row9.3;
11654 struct Row8;
11655 struct Row9;
11656 struct Row10;"#},
11657 base_text,
11658 &mut cx,
11659 );
11660
11661 // When carets and selections intersect the addition hunks, those are reverted.
11662 // Adjacent carets got merged.
11663 assert_hunk_revert(
11664 indoc! {r#"struct Row;
11665 ˇ// something on the top
11666 struct Row1;
11667 struct Row2;
11668 struct Roˇw3.1;
11669 struct Row2.2;
11670 struct Row2.3;ˇ
11671
11672 struct Row4;
11673 struct ˇRow5.1;
11674 struct Row5.2;
11675 struct «Rowˇ»5.3;
11676 struct Row5;
11677 struct Row6;
11678 ˇ
11679 struct Row9.1;
11680 struct «Rowˇ»9.2;
11681 struct «ˇRow»9.3;
11682 struct Row8;
11683 struct Row9;
11684 «ˇ// something on bottom»
11685 struct Row10;"#},
11686 vec![
11687 DiffHunkStatus::Added,
11688 DiffHunkStatus::Added,
11689 DiffHunkStatus::Added,
11690 DiffHunkStatus::Added,
11691 DiffHunkStatus::Added,
11692 ],
11693 indoc! {r#"struct Row;
11694 ˇstruct Row1;
11695 struct Row2;
11696 ˇ
11697 struct Row4;
11698 ˇstruct Row5;
11699 struct Row6;
11700 ˇ
11701 ˇstruct Row8;
11702 struct Row9;
11703 ˇstruct Row10;"#},
11704 base_text,
11705 &mut cx,
11706 );
11707}
11708
11709#[gpui::test]
11710async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
11711 init_test(cx, |_| {});
11712 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11713 let base_text = indoc! {r#"
11714 struct Row;
11715 struct Row1;
11716 struct Row2;
11717
11718 struct Row4;
11719 struct Row5;
11720 struct Row6;
11721
11722 struct Row8;
11723 struct Row9;
11724 struct Row10;"#};
11725
11726 // Modification hunks behave the same as the addition ones.
11727 assert_hunk_revert(
11728 indoc! {r#"struct Row;
11729 struct Row1;
11730 struct Row33;
11731 ˇ
11732 struct Row4;
11733 struct Row5;
11734 struct Row6;
11735 ˇ
11736 struct Row99;
11737 struct Row9;
11738 struct Row10;"#},
11739 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
11740 indoc! {r#"struct Row;
11741 struct Row1;
11742 struct Row33;
11743 ˇ
11744 struct Row4;
11745 struct Row5;
11746 struct Row6;
11747 ˇ
11748 struct Row99;
11749 struct Row9;
11750 struct Row10;"#},
11751 base_text,
11752 &mut cx,
11753 );
11754 assert_hunk_revert(
11755 indoc! {r#"struct Row;
11756 struct Row1;
11757 struct Row33;
11758 «ˇ
11759 struct Row4;
11760 struct» Row5;
11761 «struct Row6;
11762 ˇ»
11763 struct Row99;
11764 struct Row9;
11765 struct Row10;"#},
11766 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
11767 indoc! {r#"struct Row;
11768 struct Row1;
11769 struct Row33;
11770 «ˇ
11771 struct Row4;
11772 struct» Row5;
11773 «struct Row6;
11774 ˇ»
11775 struct Row99;
11776 struct Row9;
11777 struct Row10;"#},
11778 base_text,
11779 &mut cx,
11780 );
11781
11782 assert_hunk_revert(
11783 indoc! {r#"ˇstruct Row1.1;
11784 struct Row1;
11785 «ˇstr»uct Row22;
11786
11787 struct ˇRow44;
11788 struct Row5;
11789 struct «Rˇ»ow66;ˇ
11790
11791 «struˇ»ct Row88;
11792 struct Row9;
11793 struct Row1011;ˇ"#},
11794 vec![
11795 DiffHunkStatus::Modified,
11796 DiffHunkStatus::Modified,
11797 DiffHunkStatus::Modified,
11798 DiffHunkStatus::Modified,
11799 DiffHunkStatus::Modified,
11800 DiffHunkStatus::Modified,
11801 ],
11802 indoc! {r#"struct Row;
11803 ˇstruct Row1;
11804 struct Row2;
11805 ˇ
11806 struct Row4;
11807 ˇstruct Row5;
11808 struct Row6;
11809 ˇ
11810 struct Row8;
11811 ˇstruct Row9;
11812 struct Row10;ˇ"#},
11813 base_text,
11814 &mut cx,
11815 );
11816}
11817
11818#[gpui::test]
11819async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
11820 init_test(cx, |_| {});
11821 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11822 let base_text = indoc! {r#"struct Row;
11823struct Row1;
11824struct Row2;
11825
11826struct Row4;
11827struct Row5;
11828struct Row6;
11829
11830struct Row8;
11831struct Row9;
11832struct Row10;"#};
11833
11834 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
11835 assert_hunk_revert(
11836 indoc! {r#"struct Row;
11837 struct Row2;
11838
11839 ˇstruct Row4;
11840 struct Row5;
11841 struct Row6;
11842 ˇ
11843 struct Row8;
11844 struct Row10;"#},
11845 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11846 indoc! {r#"struct Row;
11847 struct Row2;
11848
11849 ˇstruct Row4;
11850 struct Row5;
11851 struct Row6;
11852 ˇ
11853 struct Row8;
11854 struct Row10;"#},
11855 base_text,
11856 &mut cx,
11857 );
11858 assert_hunk_revert(
11859 indoc! {r#"struct Row;
11860 struct Row2;
11861
11862 «ˇstruct Row4;
11863 struct» Row5;
11864 «struct Row6;
11865 ˇ»
11866 struct Row8;
11867 struct Row10;"#},
11868 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11869 indoc! {r#"struct Row;
11870 struct Row2;
11871
11872 «ˇstruct Row4;
11873 struct» Row5;
11874 «struct Row6;
11875 ˇ»
11876 struct Row8;
11877 struct Row10;"#},
11878 base_text,
11879 &mut cx,
11880 );
11881
11882 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
11883 assert_hunk_revert(
11884 indoc! {r#"struct Row;
11885 ˇstruct Row2;
11886
11887 struct Row4;
11888 struct Row5;
11889 struct Row6;
11890
11891 struct Row8;ˇ
11892 struct Row10;"#},
11893 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11894 indoc! {r#"struct Row;
11895 struct Row1;
11896 ˇstruct Row2;
11897
11898 struct Row4;
11899 struct Row5;
11900 struct Row6;
11901
11902 struct Row8;ˇ
11903 struct Row9;
11904 struct Row10;"#},
11905 base_text,
11906 &mut cx,
11907 );
11908 assert_hunk_revert(
11909 indoc! {r#"struct Row;
11910 struct Row2«ˇ;
11911 struct Row4;
11912 struct» Row5;
11913 «struct Row6;
11914
11915 struct Row8;ˇ»
11916 struct Row10;"#},
11917 vec![
11918 DiffHunkStatus::Removed,
11919 DiffHunkStatus::Removed,
11920 DiffHunkStatus::Removed,
11921 ],
11922 indoc! {r#"struct Row;
11923 struct Row1;
11924 struct Row2«ˇ;
11925
11926 struct Row4;
11927 struct» Row5;
11928 «struct Row6;
11929
11930 struct Row8;ˇ»
11931 struct Row9;
11932 struct Row10;"#},
11933 base_text,
11934 &mut cx,
11935 );
11936}
11937
11938#[gpui::test]
11939async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
11940 init_test(cx, |_| {});
11941
11942 let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
11943 let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
11944 let base_text_3 =
11945 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
11946
11947 let text_1 = edit_first_char_of_every_line(base_text_1);
11948 let text_2 = edit_first_char_of_every_line(base_text_2);
11949 let text_3 = edit_first_char_of_every_line(base_text_3);
11950
11951 let buffer_1 = cx.new_model(|cx| Buffer::local(text_1.clone(), cx));
11952 let buffer_2 = cx.new_model(|cx| Buffer::local(text_2.clone(), cx));
11953 let buffer_3 = cx.new_model(|cx| Buffer::local(text_3.clone(), cx));
11954
11955 let multibuffer = cx.new_model(|cx| {
11956 let mut multibuffer = MultiBuffer::new(ReadWrite);
11957 multibuffer.push_excerpts(
11958 buffer_1.clone(),
11959 [
11960 ExcerptRange {
11961 context: Point::new(0, 0)..Point::new(3, 0),
11962 primary: None,
11963 },
11964 ExcerptRange {
11965 context: Point::new(5, 0)..Point::new(7, 0),
11966 primary: None,
11967 },
11968 ExcerptRange {
11969 context: Point::new(9, 0)..Point::new(10, 4),
11970 primary: None,
11971 },
11972 ],
11973 cx,
11974 );
11975 multibuffer.push_excerpts(
11976 buffer_2.clone(),
11977 [
11978 ExcerptRange {
11979 context: Point::new(0, 0)..Point::new(3, 0),
11980 primary: None,
11981 },
11982 ExcerptRange {
11983 context: Point::new(5, 0)..Point::new(7, 0),
11984 primary: None,
11985 },
11986 ExcerptRange {
11987 context: Point::new(9, 0)..Point::new(10, 4),
11988 primary: None,
11989 },
11990 ],
11991 cx,
11992 );
11993 multibuffer.push_excerpts(
11994 buffer_3.clone(),
11995 [
11996 ExcerptRange {
11997 context: Point::new(0, 0)..Point::new(3, 0),
11998 primary: None,
11999 },
12000 ExcerptRange {
12001 context: Point::new(5, 0)..Point::new(7, 0),
12002 primary: None,
12003 },
12004 ExcerptRange {
12005 context: Point::new(9, 0)..Point::new(10, 4),
12006 primary: None,
12007 },
12008 ],
12009 cx,
12010 );
12011 multibuffer
12012 });
12013
12014 let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
12015 editor.update(cx, |editor, cx| {
12016 for (buffer, diff_base) in [
12017 (buffer_1.clone(), base_text_1),
12018 (buffer_2.clone(), base_text_2),
12019 (buffer_3.clone(), base_text_3),
12020 ] {
12021 let change_set = cx.new_model(|cx| {
12022 BufferChangeSet::new_with_base_text(
12023 diff_base.to_string(),
12024 buffer.read(cx).text_snapshot(),
12025 cx,
12026 )
12027 });
12028 editor.diff_map.add_change_set(change_set, cx)
12029 }
12030 });
12031 cx.executor().run_until_parked();
12032
12033 editor.update(cx, |editor, cx| {
12034 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}");
12035 editor.select_all(&SelectAll, cx);
12036 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
12037 });
12038 cx.executor().run_until_parked();
12039
12040 // When all ranges are selected, all buffer hunks are reverted.
12041 editor.update(cx, |editor, cx| {
12042 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");
12043 });
12044 buffer_1.update(cx, |buffer, _| {
12045 assert_eq!(buffer.text(), base_text_1);
12046 });
12047 buffer_2.update(cx, |buffer, _| {
12048 assert_eq!(buffer.text(), base_text_2);
12049 });
12050 buffer_3.update(cx, |buffer, _| {
12051 assert_eq!(buffer.text(), base_text_3);
12052 });
12053
12054 editor.update(cx, |editor, cx| {
12055 editor.undo(&Default::default(), cx);
12056 });
12057
12058 editor.update(cx, |editor, cx| {
12059 editor.change_selections(None, cx, |s| {
12060 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
12061 });
12062 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
12063 });
12064
12065 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
12066 // but not affect buffer_2 and its related excerpts.
12067 editor.update(cx, |editor, cx| {
12068 assert_eq!(
12069 editor.text(cx),
12070 "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}"
12071 );
12072 });
12073 buffer_1.update(cx, |buffer, _| {
12074 assert_eq!(buffer.text(), base_text_1);
12075 });
12076 buffer_2.update(cx, |buffer, _| {
12077 assert_eq!(
12078 buffer.text(),
12079 "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
12080 );
12081 });
12082 buffer_3.update(cx, |buffer, _| {
12083 assert_eq!(
12084 buffer.text(),
12085 "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
12086 );
12087 });
12088
12089 fn edit_first_char_of_every_line(text: &str) -> String {
12090 text.split('\n')
12091 .map(|line| format!("X{}", &line[1..]))
12092 .collect::<Vec<_>>()
12093 .join("\n")
12094 }
12095}
12096
12097#[gpui::test]
12098async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
12099 init_test(cx, |_| {});
12100
12101 let cols = 4;
12102 let rows = 10;
12103 let sample_text_1 = sample_text(rows, cols, 'a');
12104 assert_eq!(
12105 sample_text_1,
12106 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
12107 );
12108 let sample_text_2 = sample_text(rows, cols, 'l');
12109 assert_eq!(
12110 sample_text_2,
12111 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
12112 );
12113 let sample_text_3 = sample_text(rows, cols, 'v');
12114 assert_eq!(
12115 sample_text_3,
12116 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
12117 );
12118
12119 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
12120 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
12121 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
12122
12123 let multi_buffer = cx.new_model(|cx| {
12124 let mut multibuffer = MultiBuffer::new(ReadWrite);
12125 multibuffer.push_excerpts(
12126 buffer_1.clone(),
12127 [
12128 ExcerptRange {
12129 context: Point::new(0, 0)..Point::new(3, 0),
12130 primary: None,
12131 },
12132 ExcerptRange {
12133 context: Point::new(5, 0)..Point::new(7, 0),
12134 primary: None,
12135 },
12136 ExcerptRange {
12137 context: Point::new(9, 0)..Point::new(10, 4),
12138 primary: None,
12139 },
12140 ],
12141 cx,
12142 );
12143 multibuffer.push_excerpts(
12144 buffer_2.clone(),
12145 [
12146 ExcerptRange {
12147 context: Point::new(0, 0)..Point::new(3, 0),
12148 primary: None,
12149 },
12150 ExcerptRange {
12151 context: Point::new(5, 0)..Point::new(7, 0),
12152 primary: None,
12153 },
12154 ExcerptRange {
12155 context: Point::new(9, 0)..Point::new(10, 4),
12156 primary: None,
12157 },
12158 ],
12159 cx,
12160 );
12161 multibuffer.push_excerpts(
12162 buffer_3.clone(),
12163 [
12164 ExcerptRange {
12165 context: Point::new(0, 0)..Point::new(3, 0),
12166 primary: None,
12167 },
12168 ExcerptRange {
12169 context: Point::new(5, 0)..Point::new(7, 0),
12170 primary: None,
12171 },
12172 ExcerptRange {
12173 context: Point::new(9, 0)..Point::new(10, 4),
12174 primary: None,
12175 },
12176 ],
12177 cx,
12178 );
12179 multibuffer
12180 });
12181
12182 let fs = FakeFs::new(cx.executor());
12183 fs.insert_tree(
12184 "/a",
12185 json!({
12186 "main.rs": sample_text_1,
12187 "other.rs": sample_text_2,
12188 "lib.rs": sample_text_3,
12189 }),
12190 )
12191 .await;
12192 let project = Project::test(fs, ["/a".as_ref()], cx).await;
12193 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
12194 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
12195 let multi_buffer_editor = cx.new_view(|cx| {
12196 Editor::new(
12197 EditorMode::Full,
12198 multi_buffer,
12199 Some(project.clone()),
12200 true,
12201 cx,
12202 )
12203 });
12204 let multibuffer_item_id = workspace
12205 .update(cx, |workspace, cx| {
12206 assert!(
12207 workspace.active_item(cx).is_none(),
12208 "active item should be None before the first item is added"
12209 );
12210 workspace.add_item_to_active_pane(
12211 Box::new(multi_buffer_editor.clone()),
12212 None,
12213 true,
12214 cx,
12215 );
12216 let active_item = workspace
12217 .active_item(cx)
12218 .expect("should have an active item after adding the multi buffer");
12219 assert!(
12220 !active_item.is_singleton(cx),
12221 "A multi buffer was expected to active after adding"
12222 );
12223 active_item.item_id()
12224 })
12225 .unwrap();
12226 cx.executor().run_until_parked();
12227
12228 multi_buffer_editor.update(cx, |editor, cx| {
12229 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
12230 editor.open_excerpts(&OpenExcerpts, cx);
12231 });
12232 cx.executor().run_until_parked();
12233 let first_item_id = workspace
12234 .update(cx, |workspace, cx| {
12235 let active_item = workspace
12236 .active_item(cx)
12237 .expect("should have an active item after navigating into the 1st buffer");
12238 let first_item_id = active_item.item_id();
12239 assert_ne!(
12240 first_item_id, multibuffer_item_id,
12241 "Should navigate into the 1st buffer and activate it"
12242 );
12243 assert!(
12244 active_item.is_singleton(cx),
12245 "New active item should be a singleton buffer"
12246 );
12247 assert_eq!(
12248 active_item
12249 .act_as::<Editor>(cx)
12250 .expect("should have navigated into an editor for the 1st buffer")
12251 .read(cx)
12252 .text(cx),
12253 sample_text_1
12254 );
12255
12256 workspace
12257 .go_back(workspace.active_pane().downgrade(), cx)
12258 .detach_and_log_err(cx);
12259
12260 first_item_id
12261 })
12262 .unwrap();
12263 cx.executor().run_until_parked();
12264 workspace
12265 .update(cx, |workspace, cx| {
12266 let active_item = workspace
12267 .active_item(cx)
12268 .expect("should have an active item after navigating back");
12269 assert_eq!(
12270 active_item.item_id(),
12271 multibuffer_item_id,
12272 "Should navigate back to the multi buffer"
12273 );
12274 assert!(!active_item.is_singleton(cx));
12275 })
12276 .unwrap();
12277
12278 multi_buffer_editor.update(cx, |editor, cx| {
12279 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
12280 s.select_ranges(Some(39..40))
12281 });
12282 editor.open_excerpts(&OpenExcerpts, cx);
12283 });
12284 cx.executor().run_until_parked();
12285 let second_item_id = workspace
12286 .update(cx, |workspace, cx| {
12287 let active_item = workspace
12288 .active_item(cx)
12289 .expect("should have an active item after navigating into the 2nd buffer");
12290 let second_item_id = active_item.item_id();
12291 assert_ne!(
12292 second_item_id, multibuffer_item_id,
12293 "Should navigate away from the multibuffer"
12294 );
12295 assert_ne!(
12296 second_item_id, first_item_id,
12297 "Should navigate into the 2nd buffer and activate it"
12298 );
12299 assert!(
12300 active_item.is_singleton(cx),
12301 "New active item should be a singleton buffer"
12302 );
12303 assert_eq!(
12304 active_item
12305 .act_as::<Editor>(cx)
12306 .expect("should have navigated into an editor")
12307 .read(cx)
12308 .text(cx),
12309 sample_text_2
12310 );
12311
12312 workspace
12313 .go_back(workspace.active_pane().downgrade(), cx)
12314 .detach_and_log_err(cx);
12315
12316 second_item_id
12317 })
12318 .unwrap();
12319 cx.executor().run_until_parked();
12320 workspace
12321 .update(cx, |workspace, cx| {
12322 let active_item = workspace
12323 .active_item(cx)
12324 .expect("should have an active item after navigating back from the 2nd buffer");
12325 assert_eq!(
12326 active_item.item_id(),
12327 multibuffer_item_id,
12328 "Should navigate back from the 2nd buffer to the multi buffer"
12329 );
12330 assert!(!active_item.is_singleton(cx));
12331 })
12332 .unwrap();
12333
12334 multi_buffer_editor.update(cx, |editor, cx| {
12335 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
12336 s.select_ranges(Some(70..70))
12337 });
12338 editor.open_excerpts(&OpenExcerpts, cx);
12339 });
12340 cx.executor().run_until_parked();
12341 workspace
12342 .update(cx, |workspace, cx| {
12343 let active_item = workspace
12344 .active_item(cx)
12345 .expect("should have an active item after navigating into the 3rd buffer");
12346 let third_item_id = active_item.item_id();
12347 assert_ne!(
12348 third_item_id, multibuffer_item_id,
12349 "Should navigate into the 3rd buffer and activate it"
12350 );
12351 assert_ne!(third_item_id, first_item_id);
12352 assert_ne!(third_item_id, second_item_id);
12353 assert!(
12354 active_item.is_singleton(cx),
12355 "New active item should be a singleton buffer"
12356 );
12357 assert_eq!(
12358 active_item
12359 .act_as::<Editor>(cx)
12360 .expect("should have navigated into an editor")
12361 .read(cx)
12362 .text(cx),
12363 sample_text_3
12364 );
12365
12366 workspace
12367 .go_back(workspace.active_pane().downgrade(), cx)
12368 .detach_and_log_err(cx);
12369 })
12370 .unwrap();
12371 cx.executor().run_until_parked();
12372 workspace
12373 .update(cx, |workspace, cx| {
12374 let active_item = workspace
12375 .active_item(cx)
12376 .expect("should have an active item after navigating back from the 3rd buffer");
12377 assert_eq!(
12378 active_item.item_id(),
12379 multibuffer_item_id,
12380 "Should navigate back from the 3rd buffer to the multi buffer"
12381 );
12382 assert!(!active_item.is_singleton(cx));
12383 })
12384 .unwrap();
12385}
12386
12387#[gpui::test]
12388async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
12389 init_test(cx, |_| {});
12390
12391 let mut cx = EditorTestContext::new(cx).await;
12392
12393 let diff_base = r#"
12394 use some::mod;
12395
12396 const A: u32 = 42;
12397
12398 fn main() {
12399 println!("hello");
12400
12401 println!("world");
12402 }
12403 "#
12404 .unindent();
12405
12406 cx.set_state(
12407 &r#"
12408 use some::modified;
12409
12410 ˇ
12411 fn main() {
12412 println!("hello there");
12413
12414 println!("around the");
12415 println!("world");
12416 }
12417 "#
12418 .unindent(),
12419 );
12420
12421 cx.set_diff_base(&diff_base);
12422 executor.run_until_parked();
12423
12424 cx.update_editor(|editor, cx| {
12425 editor.go_to_next_hunk(&GoToHunk, cx);
12426 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
12427 });
12428 executor.run_until_parked();
12429 cx.assert_state_with_diff(
12430 r#"
12431 use some::modified;
12432
12433
12434 fn main() {
12435 - println!("hello");
12436 + ˇ println!("hello there");
12437
12438 println!("around the");
12439 println!("world");
12440 }
12441 "#
12442 .unindent(),
12443 );
12444
12445 cx.update_editor(|editor, cx| {
12446 for _ in 0..3 {
12447 editor.go_to_next_hunk(&GoToHunk, cx);
12448 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
12449 }
12450 });
12451 executor.run_until_parked();
12452 cx.assert_state_with_diff(
12453 r#"
12454 - use some::mod;
12455 + use some::modified;
12456
12457 - const A: u32 = 42;
12458 ˇ
12459 fn main() {
12460 - println!("hello");
12461 + println!("hello there");
12462
12463 + println!("around the");
12464 println!("world");
12465 }
12466 "#
12467 .unindent(),
12468 );
12469
12470 cx.update_editor(|editor, cx| {
12471 editor.cancel(&Cancel, cx);
12472 });
12473
12474 cx.assert_state_with_diff(
12475 r#"
12476 use some::modified;
12477
12478 ˇ
12479 fn main() {
12480 println!("hello there");
12481
12482 println!("around the");
12483 println!("world");
12484 }
12485 "#
12486 .unindent(),
12487 );
12488}
12489
12490#[gpui::test]
12491async fn test_diff_base_change_with_expanded_diff_hunks(
12492 executor: BackgroundExecutor,
12493 cx: &mut gpui::TestAppContext,
12494) {
12495 init_test(cx, |_| {});
12496
12497 let mut cx = EditorTestContext::new(cx).await;
12498
12499 let diff_base = r#"
12500 use some::mod1;
12501 use some::mod2;
12502
12503 const A: u32 = 42;
12504 const B: u32 = 42;
12505 const C: u32 = 42;
12506
12507 fn main() {
12508 println!("hello");
12509
12510 println!("world");
12511 }
12512 "#
12513 .unindent();
12514
12515 cx.set_state(
12516 &r#"
12517 use some::mod2;
12518
12519 const A: u32 = 42;
12520 const C: u32 = 42;
12521
12522 fn main(ˇ) {
12523 //println!("hello");
12524
12525 println!("world");
12526 //
12527 //
12528 }
12529 "#
12530 .unindent(),
12531 );
12532
12533 cx.set_diff_base(&diff_base);
12534 executor.run_until_parked();
12535
12536 cx.update_editor(|editor, cx| {
12537 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12538 });
12539 executor.run_until_parked();
12540 cx.assert_state_with_diff(
12541 r#"
12542 - use some::mod1;
12543 use some::mod2;
12544
12545 const A: u32 = 42;
12546 - const B: u32 = 42;
12547 const C: u32 = 42;
12548
12549 fn main(ˇ) {
12550 - println!("hello");
12551 + //println!("hello");
12552
12553 println!("world");
12554 + //
12555 + //
12556 }
12557 "#
12558 .unindent(),
12559 );
12560
12561 cx.set_diff_base("new diff base!");
12562 executor.run_until_parked();
12563 cx.assert_state_with_diff(
12564 r#"
12565 use some::mod2;
12566
12567 const A: u32 = 42;
12568 const C: u32 = 42;
12569
12570 fn main(ˇ) {
12571 //println!("hello");
12572
12573 println!("world");
12574 //
12575 //
12576 }
12577 "#
12578 .unindent(),
12579 );
12580
12581 cx.update_editor(|editor, cx| {
12582 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12583 });
12584 executor.run_until_parked();
12585 cx.assert_state_with_diff(
12586 r#"
12587 - new diff base!
12588 + use some::mod2;
12589 +
12590 + const A: u32 = 42;
12591 + const C: u32 = 42;
12592 +
12593 + fn main(ˇ) {
12594 + //println!("hello");
12595 +
12596 + println!("world");
12597 + //
12598 + //
12599 + }
12600 "#
12601 .unindent(),
12602 );
12603}
12604
12605#[gpui::test]
12606async fn test_fold_unfold_diff_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
12607 init_test(cx, |_| {});
12608
12609 let mut cx = EditorTestContext::new(cx).await;
12610
12611 let diff_base = r#"
12612 use some::mod1;
12613 use some::mod2;
12614
12615 const A: u32 = 42;
12616 const B: u32 = 42;
12617 const C: u32 = 42;
12618
12619 fn main() {
12620 println!("hello");
12621
12622 println!("world");
12623 }
12624
12625 fn another() {
12626 println!("another");
12627 }
12628
12629 fn another2() {
12630 println!("another2");
12631 }
12632 "#
12633 .unindent();
12634
12635 cx.set_state(
12636 &r#"
12637 «use some::mod2;
12638
12639 const A: u32 = 42;
12640 const C: u32 = 42;
12641
12642 fn main() {
12643 //println!("hello");
12644
12645 println!("world");
12646 //
12647 //ˇ»
12648 }
12649
12650 fn another() {
12651 println!("another");
12652 println!("another");
12653 }
12654
12655 println!("another2");
12656 }
12657 "#
12658 .unindent(),
12659 );
12660
12661 cx.set_diff_base(&diff_base);
12662 executor.run_until_parked();
12663
12664 cx.update_editor(|editor, cx| {
12665 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12666 });
12667 executor.run_until_parked();
12668
12669 cx.assert_state_with_diff(
12670 r#"
12671 - use some::mod1;
12672 «use some::mod2;
12673
12674 const A: u32 = 42;
12675 - const B: u32 = 42;
12676 const C: u32 = 42;
12677
12678 fn main() {
12679 - println!("hello");
12680 + //println!("hello");
12681
12682 println!("world");
12683 + //
12684 + //ˇ»
12685 }
12686
12687 fn another() {
12688 println!("another");
12689 + println!("another");
12690 }
12691
12692 - fn another2() {
12693 println!("another2");
12694 }
12695 "#
12696 .unindent(),
12697 );
12698
12699 // Fold across some of the diff hunks. They should no longer appear expanded.
12700 cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx));
12701 cx.executor().run_until_parked();
12702
12703 // Hunks are not shown if their position is within a fold
12704 cx.assert_state_with_diff(
12705 r#"
12706 «use some::mod2;
12707
12708 const A: u32 = 42;
12709 const C: u32 = 42;
12710
12711 fn main() {
12712 //println!("hello");
12713
12714 println!("world");
12715 //
12716 //ˇ»
12717 }
12718
12719 fn another() {
12720 println!("another");
12721 + println!("another");
12722 }
12723
12724 - fn another2() {
12725 println!("another2");
12726 }
12727 "#
12728 .unindent(),
12729 );
12730
12731 cx.update_editor(|editor, cx| {
12732 editor.select_all(&SelectAll, cx);
12733 editor.unfold_lines(&UnfoldLines, cx);
12734 });
12735 cx.executor().run_until_parked();
12736
12737 // The deletions reappear when unfolding.
12738 cx.assert_state_with_diff(
12739 r#"
12740 - use some::mod1;
12741 «use some::mod2;
12742
12743 const A: u32 = 42;
12744 - const B: u32 = 42;
12745 const C: u32 = 42;
12746
12747 fn main() {
12748 - println!("hello");
12749 + //println!("hello");
12750
12751 println!("world");
12752 + //
12753 + //
12754 }
12755
12756 fn another() {
12757 println!("another");
12758 + println!("another");
12759 }
12760
12761 - fn another2() {
12762 println!("another2");
12763 }
12764 ˇ»"#
12765 .unindent(),
12766 );
12767}
12768
12769#[gpui::test]
12770async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
12771 init_test(cx, |_| {});
12772
12773 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
12774 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
12775 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
12776 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
12777 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
12778 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
12779
12780 let buffer_1 = cx.new_model(|cx| Buffer::local(file_1_new.to_string(), cx));
12781 let buffer_2 = cx.new_model(|cx| Buffer::local(file_2_new.to_string(), cx));
12782 let buffer_3 = cx.new_model(|cx| Buffer::local(file_3_new.to_string(), cx));
12783
12784 let multi_buffer = cx.new_model(|cx| {
12785 let mut multibuffer = MultiBuffer::new(ReadWrite);
12786 multibuffer.push_excerpts(
12787 buffer_1.clone(),
12788 [
12789 ExcerptRange {
12790 context: Point::new(0, 0)..Point::new(3, 0),
12791 primary: None,
12792 },
12793 ExcerptRange {
12794 context: Point::new(5, 0)..Point::new(7, 0),
12795 primary: None,
12796 },
12797 ExcerptRange {
12798 context: Point::new(9, 0)..Point::new(10, 3),
12799 primary: None,
12800 },
12801 ],
12802 cx,
12803 );
12804 multibuffer.push_excerpts(
12805 buffer_2.clone(),
12806 [
12807 ExcerptRange {
12808 context: Point::new(0, 0)..Point::new(3, 0),
12809 primary: None,
12810 },
12811 ExcerptRange {
12812 context: Point::new(5, 0)..Point::new(7, 0),
12813 primary: None,
12814 },
12815 ExcerptRange {
12816 context: Point::new(9, 0)..Point::new(10, 3),
12817 primary: None,
12818 },
12819 ],
12820 cx,
12821 );
12822 multibuffer.push_excerpts(
12823 buffer_3.clone(),
12824 [
12825 ExcerptRange {
12826 context: Point::new(0, 0)..Point::new(3, 0),
12827 primary: None,
12828 },
12829 ExcerptRange {
12830 context: Point::new(5, 0)..Point::new(7, 0),
12831 primary: None,
12832 },
12833 ExcerptRange {
12834 context: Point::new(9, 0)..Point::new(10, 3),
12835 primary: None,
12836 },
12837 ],
12838 cx,
12839 );
12840 multibuffer
12841 });
12842
12843 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
12844 editor
12845 .update(cx, |editor, cx| {
12846 for (buffer, diff_base) in [
12847 (buffer_1.clone(), file_1_old),
12848 (buffer_2.clone(), file_2_old),
12849 (buffer_3.clone(), file_3_old),
12850 ] {
12851 let change_set = cx.new_model(|cx| {
12852 BufferChangeSet::new_with_base_text(
12853 diff_base.to_string(),
12854 buffer.read(cx).text_snapshot(),
12855 cx,
12856 )
12857 });
12858 editor.diff_map.add_change_set(change_set, cx)
12859 }
12860 })
12861 .unwrap();
12862
12863 let mut cx = EditorTestContext::for_editor(editor, cx).await;
12864 cx.run_until_parked();
12865
12866 cx.assert_editor_state(
12867 &"
12868 ˇaaa
12869 ccc
12870 ddd
12871
12872 ggg
12873 hhh
12874
12875
12876 lll
12877 mmm
12878 NNN
12879
12880 qqq
12881 rrr
12882
12883 uuu
12884 111
12885 222
12886 333
12887
12888 666
12889 777
12890
12891 000
12892 !!!"
12893 .unindent(),
12894 );
12895
12896 cx.update_editor(|editor, cx| {
12897 editor.select_all(&SelectAll, cx);
12898 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
12899 });
12900 cx.executor().run_until_parked();
12901
12902 cx.assert_state_with_diff(
12903 "
12904 «aaa
12905 - bbb
12906 ccc
12907 ddd
12908
12909 ggg
12910 hhh
12911
12912
12913 lll
12914 mmm
12915 - nnn
12916 + NNN
12917
12918 qqq
12919 rrr
12920
12921 uuu
12922 111
12923 222
12924 333
12925
12926 + 666
12927 777
12928
12929 000
12930 !!!ˇ»"
12931 .unindent(),
12932 );
12933}
12934
12935#[gpui::test]
12936async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut gpui::TestAppContext) {
12937 init_test(cx, |_| {});
12938
12939 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
12940 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\n";
12941
12942 let buffer = cx.new_model(|cx| Buffer::local(text.to_string(), cx));
12943 let multi_buffer = cx.new_model(|cx| {
12944 let mut multibuffer = MultiBuffer::new(ReadWrite);
12945 multibuffer.push_excerpts(
12946 buffer.clone(),
12947 [
12948 ExcerptRange {
12949 context: Point::new(0, 0)..Point::new(2, 0),
12950 primary: None,
12951 },
12952 ExcerptRange {
12953 context: Point::new(5, 0)..Point::new(7, 0),
12954 primary: None,
12955 },
12956 ],
12957 cx,
12958 );
12959 multibuffer
12960 });
12961
12962 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
12963 editor
12964 .update(cx, |editor, cx| {
12965 let buffer = buffer.read(cx).text_snapshot();
12966 let change_set = cx
12967 .new_model(|cx| BufferChangeSet::new_with_base_text(base.to_string(), buffer, cx));
12968 editor.diff_map.add_change_set(change_set, cx)
12969 })
12970 .unwrap();
12971
12972 let mut cx = EditorTestContext::for_editor(editor, cx).await;
12973 cx.run_until_parked();
12974
12975 cx.update_editor(|editor, cx| editor.expand_all_hunk_diffs(&Default::default(), cx));
12976 cx.executor().run_until_parked();
12977
12978 cx.assert_state_with_diff(
12979 "
12980 ˇaaa
12981 - bbb
12982 + BBB
12983
12984 - ddd
12985 - eee
12986 + EEE
12987 fff
12988 "
12989 .unindent(),
12990 );
12991}
12992
12993#[gpui::test]
12994async fn test_edits_around_expanded_insertion_hunks(
12995 executor: BackgroundExecutor,
12996 cx: &mut gpui::TestAppContext,
12997) {
12998 init_test(cx, |_| {});
12999
13000 let mut cx = EditorTestContext::new(cx).await;
13001
13002 let diff_base = r#"
13003 use some::mod1;
13004 use some::mod2;
13005
13006 const A: u32 = 42;
13007
13008 fn main() {
13009 println!("hello");
13010
13011 println!("world");
13012 }
13013 "#
13014 .unindent();
13015 executor.run_until_parked();
13016 cx.set_state(
13017 &r#"
13018 use some::mod1;
13019 use some::mod2;
13020
13021 const A: u32 = 42;
13022 const B: u32 = 42;
13023 const C: u32 = 42;
13024 ˇ
13025
13026 fn main() {
13027 println!("hello");
13028
13029 println!("world");
13030 }
13031 "#
13032 .unindent(),
13033 );
13034
13035 cx.set_diff_base(&diff_base);
13036 executor.run_until_parked();
13037
13038 cx.update_editor(|editor, cx| {
13039 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
13040 });
13041 executor.run_until_parked();
13042
13043 cx.assert_state_with_diff(
13044 r#"
13045 use some::mod1;
13046 use some::mod2;
13047
13048 const A: u32 = 42;
13049 + const B: u32 = 42;
13050 + const C: u32 = 42;
13051 + ˇ
13052
13053 fn main() {
13054 println!("hello");
13055
13056 println!("world");
13057 }
13058 "#
13059 .unindent(),
13060 );
13061
13062 cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx));
13063 executor.run_until_parked();
13064
13065 cx.assert_state_with_diff(
13066 r#"
13067 use some::mod1;
13068 use some::mod2;
13069
13070 const A: u32 = 42;
13071 + const B: u32 = 42;
13072 + const C: u32 = 42;
13073 + const D: u32 = 42;
13074 + ˇ
13075
13076 fn main() {
13077 println!("hello");
13078
13079 println!("world");
13080 }
13081 "#
13082 .unindent(),
13083 );
13084
13085 cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx));
13086 executor.run_until_parked();
13087
13088 cx.assert_state_with_diff(
13089 r#"
13090 use some::mod1;
13091 use some::mod2;
13092
13093 const A: u32 = 42;
13094 + const B: u32 = 42;
13095 + const C: u32 = 42;
13096 + const D: u32 = 42;
13097 + const E: u32 = 42;
13098 + ˇ
13099
13100 fn main() {
13101 println!("hello");
13102
13103 println!("world");
13104 }
13105 "#
13106 .unindent(),
13107 );
13108
13109 cx.update_editor(|editor, cx| {
13110 editor.delete_line(&DeleteLine, cx);
13111 });
13112 executor.run_until_parked();
13113
13114 cx.assert_state_with_diff(
13115 r#"
13116 use some::mod1;
13117 use some::mod2;
13118
13119 const A: u32 = 42;
13120 + const B: u32 = 42;
13121 + const C: u32 = 42;
13122 + const D: u32 = 42;
13123 + const E: u32 = 42;
13124 ˇ
13125 fn main() {
13126 println!("hello");
13127
13128 println!("world");
13129 }
13130 "#
13131 .unindent(),
13132 );
13133
13134 cx.update_editor(|editor, cx| {
13135 editor.move_up(&MoveUp, cx);
13136 editor.delete_line(&DeleteLine, cx);
13137 editor.move_up(&MoveUp, cx);
13138 editor.delete_line(&DeleteLine, cx);
13139 editor.move_up(&MoveUp, cx);
13140 editor.delete_line(&DeleteLine, cx);
13141 });
13142 executor.run_until_parked();
13143 cx.assert_state_with_diff(
13144 r#"
13145 use some::mod1;
13146 use some::mod2;
13147
13148 const A: u32 = 42;
13149 + const B: u32 = 42;
13150 ˇ
13151 fn main() {
13152 println!("hello");
13153
13154 println!("world");
13155 }
13156 "#
13157 .unindent(),
13158 );
13159
13160 cx.update_editor(|editor, cx| {
13161 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, cx);
13162 editor.delete_line(&DeleteLine, cx);
13163 });
13164 executor.run_until_parked();
13165 cx.assert_state_with_diff(
13166 r#"
13167 use some::mod1;
13168 - use some::mod2;
13169 -
13170 - const A: u32 = 42;
13171 ˇ
13172 fn main() {
13173 println!("hello");
13174
13175 println!("world");
13176 }
13177 "#
13178 .unindent(),
13179 );
13180}
13181
13182#[gpui::test]
13183async fn test_edits_around_expanded_deletion_hunks(
13184 executor: BackgroundExecutor,
13185 cx: &mut gpui::TestAppContext,
13186) {
13187 init_test(cx, |_| {});
13188
13189 let mut cx = EditorTestContext::new(cx).await;
13190
13191 let diff_base = r#"
13192 use some::mod1;
13193 use some::mod2;
13194
13195 const A: u32 = 42;
13196 const B: u32 = 42;
13197 const C: u32 = 42;
13198
13199
13200 fn main() {
13201 println!("hello");
13202
13203 println!("world");
13204 }
13205 "#
13206 .unindent();
13207 executor.run_until_parked();
13208 cx.set_state(
13209 &r#"
13210 use some::mod1;
13211 use some::mod2;
13212
13213 ˇconst B: u32 = 42;
13214 const C: u32 = 42;
13215
13216
13217 fn main() {
13218 println!("hello");
13219
13220 println!("world");
13221 }
13222 "#
13223 .unindent(),
13224 );
13225
13226 cx.set_diff_base(&diff_base);
13227 executor.run_until_parked();
13228
13229 cx.update_editor(|editor, cx| {
13230 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
13231 });
13232 executor.run_until_parked();
13233
13234 cx.assert_state_with_diff(
13235 r#"
13236 use some::mod1;
13237 use some::mod2;
13238
13239 - const A: u32 = 42;
13240 ˇconst B: u32 = 42;
13241 const C: u32 = 42;
13242
13243
13244 fn main() {
13245 println!("hello");
13246
13247 println!("world");
13248 }
13249 "#
13250 .unindent(),
13251 );
13252
13253 cx.update_editor(|editor, cx| {
13254 editor.delete_line(&DeleteLine, cx);
13255 });
13256 executor.run_until_parked();
13257 cx.assert_state_with_diff(
13258 r#"
13259 use some::mod1;
13260 use some::mod2;
13261
13262 - const A: u32 = 42;
13263 - const B: u32 = 42;
13264 ˇconst C: u32 = 42;
13265
13266
13267 fn main() {
13268 println!("hello");
13269
13270 println!("world");
13271 }
13272 "#
13273 .unindent(),
13274 );
13275
13276 cx.update_editor(|editor, cx| {
13277 editor.delete_line(&DeleteLine, cx);
13278 });
13279 executor.run_until_parked();
13280 cx.assert_state_with_diff(
13281 r#"
13282 use some::mod1;
13283 use some::mod2;
13284
13285 - const A: u32 = 42;
13286 - const B: u32 = 42;
13287 - const C: u32 = 42;
13288 ˇ
13289
13290 fn main() {
13291 println!("hello");
13292
13293 println!("world");
13294 }
13295 "#
13296 .unindent(),
13297 );
13298
13299 cx.update_editor(|editor, cx| {
13300 editor.handle_input("replacement", cx);
13301 });
13302 executor.run_until_parked();
13303 cx.assert_state_with_diff(
13304 r#"
13305 use some::mod1;
13306 use some::mod2;
13307
13308 - const A: u32 = 42;
13309 - const B: u32 = 42;
13310 - const C: u32 = 42;
13311 -
13312 + replacementˇ
13313
13314 fn main() {
13315 println!("hello");
13316
13317 println!("world");
13318 }
13319 "#
13320 .unindent(),
13321 );
13322}
13323
13324#[gpui::test]
13325async fn test_edit_after_expanded_modification_hunk(
13326 executor: BackgroundExecutor,
13327 cx: &mut gpui::TestAppContext,
13328) {
13329 init_test(cx, |_| {});
13330
13331 let mut cx = EditorTestContext::new(cx).await;
13332
13333 let diff_base = r#"
13334 use some::mod1;
13335 use some::mod2;
13336
13337 const A: u32 = 42;
13338 const B: u32 = 42;
13339 const C: u32 = 42;
13340 const D: u32 = 42;
13341
13342
13343 fn main() {
13344 println!("hello");
13345
13346 println!("world");
13347 }"#
13348 .unindent();
13349
13350 cx.set_state(
13351 &r#"
13352 use some::mod1;
13353 use some::mod2;
13354
13355 const A: u32 = 42;
13356 const B: u32 = 42;
13357 const C: u32 = 43ˇ
13358 const D: u32 = 42;
13359
13360
13361 fn main() {
13362 println!("hello");
13363
13364 println!("world");
13365 }"#
13366 .unindent(),
13367 );
13368
13369 cx.set_diff_base(&diff_base);
13370 executor.run_until_parked();
13371 cx.update_editor(|editor, cx| {
13372 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
13373 });
13374 executor.run_until_parked();
13375
13376 cx.assert_state_with_diff(
13377 r#"
13378 use some::mod1;
13379 use some::mod2;
13380
13381 const A: u32 = 42;
13382 const B: u32 = 42;
13383 - const C: u32 = 42;
13384 + const C: u32 = 43ˇ
13385 const D: u32 = 42;
13386
13387
13388 fn main() {
13389 println!("hello");
13390
13391 println!("world");
13392 }"#
13393 .unindent(),
13394 );
13395
13396 cx.update_editor(|editor, cx| {
13397 editor.handle_input("\nnew_line\n", cx);
13398 });
13399 executor.run_until_parked();
13400
13401 cx.assert_state_with_diff(
13402 r#"
13403 use some::mod1;
13404 use some::mod2;
13405
13406 const A: u32 = 42;
13407 const B: u32 = 42;
13408 - const C: u32 = 42;
13409 + const C: u32 = 43
13410 + new_line
13411 + ˇ
13412 const D: u32 = 42;
13413
13414
13415 fn main() {
13416 println!("hello");
13417
13418 println!("world");
13419 }"#
13420 .unindent(),
13421 );
13422}
13423
13424async fn setup_indent_guides_editor(
13425 text: &str,
13426 cx: &mut gpui::TestAppContext,
13427) -> (BufferId, EditorTestContext) {
13428 init_test(cx, |_| {});
13429
13430 let mut cx = EditorTestContext::new(cx).await;
13431
13432 let buffer_id = cx.update_editor(|editor, cx| {
13433 editor.set_text(text, cx);
13434 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
13435
13436 buffer_ids[0]
13437 });
13438
13439 (buffer_id, cx)
13440}
13441
13442fn assert_indent_guides(
13443 range: Range<u32>,
13444 expected: Vec<IndentGuide>,
13445 active_indices: Option<Vec<usize>>,
13446 cx: &mut EditorTestContext,
13447) {
13448 let indent_guides = cx.update_editor(|editor, cx| {
13449 let snapshot = editor.snapshot(cx).display_snapshot;
13450 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
13451 editor,
13452 MultiBufferRow(range.start)..MultiBufferRow(range.end),
13453 true,
13454 &snapshot,
13455 cx,
13456 );
13457
13458 indent_guides.sort_by(|a, b| {
13459 a.depth.cmp(&b.depth).then(
13460 a.start_row
13461 .cmp(&b.start_row)
13462 .then(a.end_row.cmp(&b.end_row)),
13463 )
13464 });
13465 indent_guides
13466 });
13467
13468 if let Some(expected) = active_indices {
13469 let active_indices = cx.update_editor(|editor, cx| {
13470 let snapshot = editor.snapshot(cx).display_snapshot;
13471 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, cx)
13472 });
13473
13474 assert_eq!(
13475 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
13476 expected,
13477 "Active indent guide indices do not match"
13478 );
13479 }
13480
13481 let expected: Vec<_> = expected
13482 .into_iter()
13483 .map(|guide| MultiBufferIndentGuide {
13484 multibuffer_row_range: MultiBufferRow(guide.start_row)..MultiBufferRow(guide.end_row),
13485 buffer: guide,
13486 })
13487 .collect();
13488
13489 assert_eq!(indent_guides, expected, "Indent guides do not match");
13490}
13491
13492fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
13493 IndentGuide {
13494 buffer_id,
13495 start_row,
13496 end_row,
13497 depth,
13498 tab_size: 4,
13499 settings: IndentGuideSettings {
13500 enabled: true,
13501 line_width: 1,
13502 active_line_width: 1,
13503 ..Default::default()
13504 },
13505 }
13506}
13507
13508#[gpui::test]
13509async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
13510 let (buffer_id, mut cx) = setup_indent_guides_editor(
13511 &"
13512 fn main() {
13513 let a = 1;
13514 }"
13515 .unindent(),
13516 cx,
13517 )
13518 .await;
13519
13520 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
13521}
13522
13523#[gpui::test]
13524async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) {
13525 let (buffer_id, mut cx) = setup_indent_guides_editor(
13526 &"
13527 fn main() {
13528 let a = 1;
13529 let b = 2;
13530 }"
13531 .unindent(),
13532 cx,
13533 )
13534 .await;
13535
13536 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
13537}
13538
13539#[gpui::test]
13540async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) {
13541 let (buffer_id, mut cx) = setup_indent_guides_editor(
13542 &"
13543 fn main() {
13544 let a = 1;
13545 if a == 3 {
13546 let b = 2;
13547 } else {
13548 let c = 3;
13549 }
13550 }"
13551 .unindent(),
13552 cx,
13553 )
13554 .await;
13555
13556 assert_indent_guides(
13557 0..8,
13558 vec![
13559 indent_guide(buffer_id, 1, 6, 0),
13560 indent_guide(buffer_id, 3, 3, 1),
13561 indent_guide(buffer_id, 5, 5, 1),
13562 ],
13563 None,
13564 &mut cx,
13565 );
13566}
13567
13568#[gpui::test]
13569async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) {
13570 let (buffer_id, mut cx) = setup_indent_guides_editor(
13571 &"
13572 fn main() {
13573 let a = 1;
13574 let b = 2;
13575 let c = 3;
13576 }"
13577 .unindent(),
13578 cx,
13579 )
13580 .await;
13581
13582 assert_indent_guides(
13583 0..5,
13584 vec![
13585 indent_guide(buffer_id, 1, 3, 0),
13586 indent_guide(buffer_id, 2, 2, 1),
13587 ],
13588 None,
13589 &mut cx,
13590 );
13591}
13592
13593#[gpui::test]
13594async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext) {
13595 let (buffer_id, mut cx) = setup_indent_guides_editor(
13596 &"
13597 fn main() {
13598 let a = 1;
13599
13600 let c = 3;
13601 }"
13602 .unindent(),
13603 cx,
13604 )
13605 .await;
13606
13607 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
13608}
13609
13610#[gpui::test]
13611async fn test_indent_guide_complex(cx: &mut gpui::TestAppContext) {
13612 let (buffer_id, mut cx) = setup_indent_guides_editor(
13613 &"
13614 fn main() {
13615 let a = 1;
13616
13617 let c = 3;
13618
13619 if a == 3 {
13620 let b = 2;
13621 } else {
13622 let c = 3;
13623 }
13624 }"
13625 .unindent(),
13626 cx,
13627 )
13628 .await;
13629
13630 assert_indent_guides(
13631 0..11,
13632 vec![
13633 indent_guide(buffer_id, 1, 9, 0),
13634 indent_guide(buffer_id, 6, 6, 1),
13635 indent_guide(buffer_id, 8, 8, 1),
13636 ],
13637 None,
13638 &mut cx,
13639 );
13640}
13641
13642#[gpui::test]
13643async fn test_indent_guide_starts_off_screen(cx: &mut gpui::TestAppContext) {
13644 let (buffer_id, mut cx) = setup_indent_guides_editor(
13645 &"
13646 fn main() {
13647 let a = 1;
13648
13649 let c = 3;
13650
13651 if a == 3 {
13652 let b = 2;
13653 } else {
13654 let c = 3;
13655 }
13656 }"
13657 .unindent(),
13658 cx,
13659 )
13660 .await;
13661
13662 assert_indent_guides(
13663 1..11,
13664 vec![
13665 indent_guide(buffer_id, 1, 9, 0),
13666 indent_guide(buffer_id, 6, 6, 1),
13667 indent_guide(buffer_id, 8, 8, 1),
13668 ],
13669 None,
13670 &mut cx,
13671 );
13672}
13673
13674#[gpui::test]
13675async fn test_indent_guide_ends_off_screen(cx: &mut gpui::TestAppContext) {
13676 let (buffer_id, mut cx) = setup_indent_guides_editor(
13677 &"
13678 fn main() {
13679 let a = 1;
13680
13681 let c = 3;
13682
13683 if a == 3 {
13684 let b = 2;
13685 } else {
13686 let c = 3;
13687 }
13688 }"
13689 .unindent(),
13690 cx,
13691 )
13692 .await;
13693
13694 assert_indent_guides(
13695 1..10,
13696 vec![
13697 indent_guide(buffer_id, 1, 9, 0),
13698 indent_guide(buffer_id, 6, 6, 1),
13699 indent_guide(buffer_id, 8, 8, 1),
13700 ],
13701 None,
13702 &mut cx,
13703 );
13704}
13705
13706#[gpui::test]
13707async fn test_indent_guide_without_brackets(cx: &mut gpui::TestAppContext) {
13708 let (buffer_id, mut cx) = setup_indent_guides_editor(
13709 &"
13710 block1
13711 block2
13712 block3
13713 block4
13714 block2
13715 block1
13716 block1"
13717 .unindent(),
13718 cx,
13719 )
13720 .await;
13721
13722 assert_indent_guides(
13723 1..10,
13724 vec![
13725 indent_guide(buffer_id, 1, 4, 0),
13726 indent_guide(buffer_id, 2, 3, 1),
13727 indent_guide(buffer_id, 3, 3, 2),
13728 ],
13729 None,
13730 &mut cx,
13731 );
13732}
13733
13734#[gpui::test]
13735async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext) {
13736 let (buffer_id, mut cx) = setup_indent_guides_editor(
13737 &"
13738 block1
13739 block2
13740 block3
13741
13742 block1
13743 block1"
13744 .unindent(),
13745 cx,
13746 )
13747 .await;
13748
13749 assert_indent_guides(
13750 0..6,
13751 vec![
13752 indent_guide(buffer_id, 1, 2, 0),
13753 indent_guide(buffer_id, 2, 2, 1),
13754 ],
13755 None,
13756 &mut cx,
13757 );
13758}
13759
13760#[gpui::test]
13761async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext) {
13762 let (buffer_id, mut cx) = setup_indent_guides_editor(
13763 &"
13764 block1
13765
13766
13767
13768 block2
13769 "
13770 .unindent(),
13771 cx,
13772 )
13773 .await;
13774
13775 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
13776}
13777
13778#[gpui::test]
13779async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) {
13780 let (buffer_id, mut cx) = setup_indent_guides_editor(
13781 &"
13782 def a:
13783 \tb = 3
13784 \tif True:
13785 \t\tc = 4
13786 \t\td = 5
13787 \tprint(b)
13788 "
13789 .unindent(),
13790 cx,
13791 )
13792 .await;
13793
13794 assert_indent_guides(
13795 0..6,
13796 vec![
13797 indent_guide(buffer_id, 1, 6, 0),
13798 indent_guide(buffer_id, 3, 4, 1),
13799 ],
13800 None,
13801 &mut cx,
13802 );
13803}
13804
13805#[gpui::test]
13806async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
13807 let (buffer_id, mut cx) = setup_indent_guides_editor(
13808 &"
13809 fn main() {
13810 let a = 1;
13811 }"
13812 .unindent(),
13813 cx,
13814 )
13815 .await;
13816
13817 cx.update_editor(|editor, cx| {
13818 editor.change_selections(None, cx, |s| {
13819 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13820 });
13821 });
13822
13823 assert_indent_guides(
13824 0..3,
13825 vec![indent_guide(buffer_id, 1, 1, 0)],
13826 Some(vec![0]),
13827 &mut cx,
13828 );
13829}
13830
13831#[gpui::test]
13832async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppContext) {
13833 let (buffer_id, mut cx) = setup_indent_guides_editor(
13834 &"
13835 fn main() {
13836 if 1 == 2 {
13837 let a = 1;
13838 }
13839 }"
13840 .unindent(),
13841 cx,
13842 )
13843 .await;
13844
13845 cx.update_editor(|editor, cx| {
13846 editor.change_selections(None, cx, |s| {
13847 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13848 });
13849 });
13850
13851 assert_indent_guides(
13852 0..4,
13853 vec![
13854 indent_guide(buffer_id, 1, 3, 0),
13855 indent_guide(buffer_id, 2, 2, 1),
13856 ],
13857 Some(vec![1]),
13858 &mut cx,
13859 );
13860
13861 cx.update_editor(|editor, cx| {
13862 editor.change_selections(None, cx, |s| {
13863 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13864 });
13865 });
13866
13867 assert_indent_guides(
13868 0..4,
13869 vec![
13870 indent_guide(buffer_id, 1, 3, 0),
13871 indent_guide(buffer_id, 2, 2, 1),
13872 ],
13873 Some(vec![1]),
13874 &mut cx,
13875 );
13876
13877 cx.update_editor(|editor, cx| {
13878 editor.change_selections(None, cx, |s| {
13879 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
13880 });
13881 });
13882
13883 assert_indent_guides(
13884 0..4,
13885 vec![
13886 indent_guide(buffer_id, 1, 3, 0),
13887 indent_guide(buffer_id, 2, 2, 1),
13888 ],
13889 Some(vec![0]),
13890 &mut cx,
13891 );
13892}
13893
13894#[gpui::test]
13895async fn test_active_indent_guide_empty_line(cx: &mut gpui::TestAppContext) {
13896 let (buffer_id, mut cx) = setup_indent_guides_editor(
13897 &"
13898 fn main() {
13899 let a = 1;
13900
13901 let b = 2;
13902 }"
13903 .unindent(),
13904 cx,
13905 )
13906 .await;
13907
13908 cx.update_editor(|editor, cx| {
13909 editor.change_selections(None, cx, |s| {
13910 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13911 });
13912 });
13913
13914 assert_indent_guides(
13915 0..5,
13916 vec![indent_guide(buffer_id, 1, 3, 0)],
13917 Some(vec![0]),
13918 &mut cx,
13919 );
13920}
13921
13922#[gpui::test]
13923async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppContext) {
13924 let (buffer_id, mut cx) = setup_indent_guides_editor(
13925 &"
13926 def m:
13927 a = 1
13928 pass"
13929 .unindent(),
13930 cx,
13931 )
13932 .await;
13933
13934 cx.update_editor(|editor, cx| {
13935 editor.change_selections(None, cx, |s| {
13936 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13937 });
13938 });
13939
13940 assert_indent_guides(
13941 0..3,
13942 vec![indent_guide(buffer_id, 1, 2, 0)],
13943 Some(vec![0]),
13944 &mut cx,
13945 );
13946}
13947
13948#[gpui::test]
13949fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
13950 init_test(cx, |_| {});
13951
13952 let editor = cx.add_window(|cx| {
13953 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
13954 build_editor(buffer, cx)
13955 });
13956
13957 let render_args = Arc::new(Mutex::new(None));
13958 let snapshot = editor
13959 .update(cx, |editor, cx| {
13960 let snapshot = editor.buffer().read(cx).snapshot(cx);
13961 let range =
13962 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
13963
13964 struct RenderArgs {
13965 row: MultiBufferRow,
13966 folded: bool,
13967 callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
13968 }
13969
13970 let crease = Crease::inline(
13971 range,
13972 FoldPlaceholder::test(),
13973 {
13974 let toggle_callback = render_args.clone();
13975 move |row, folded, callback, _cx| {
13976 *toggle_callback.lock() = Some(RenderArgs {
13977 row,
13978 folded,
13979 callback,
13980 });
13981 div()
13982 }
13983 },
13984 |_row, _folded, _cx| div(),
13985 );
13986
13987 editor.insert_creases(Some(crease), cx);
13988 let snapshot = editor.snapshot(cx);
13989 let _div =
13990 snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
13991 snapshot
13992 })
13993 .unwrap();
13994
13995 let render_args = render_args.lock().take().unwrap();
13996 assert_eq!(render_args.row, MultiBufferRow(1));
13997 assert!(!render_args.folded);
13998 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13999
14000 cx.update_window(*editor, |_, cx| (render_args.callback)(true, cx))
14001 .unwrap();
14002 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
14003 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
14004
14005 cx.update_window(*editor, |_, cx| (render_args.callback)(false, cx))
14006 .unwrap();
14007 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
14008 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
14009}
14010
14011#[gpui::test]
14012async fn test_input_text(cx: &mut gpui::TestAppContext) {
14013 init_test(cx, |_| {});
14014 let mut cx = EditorTestContext::new(cx).await;
14015
14016 cx.set_state(
14017 &r#"ˇone
14018 two
14019
14020 three
14021 fourˇ
14022 five
14023
14024 siˇx"#
14025 .unindent(),
14026 );
14027
14028 cx.dispatch_action(HandleInput(String::new()));
14029 cx.assert_editor_state(
14030 &r#"ˇone
14031 two
14032
14033 three
14034 fourˇ
14035 five
14036
14037 siˇx"#
14038 .unindent(),
14039 );
14040
14041 cx.dispatch_action(HandleInput("AAAA".to_string()));
14042 cx.assert_editor_state(
14043 &r#"AAAAˇone
14044 two
14045
14046 three
14047 fourAAAAˇ
14048 five
14049
14050 siAAAAˇx"#
14051 .unindent(),
14052 );
14053}
14054
14055#[gpui::test]
14056async fn test_scroll_cursor_center_top_bottom(cx: &mut gpui::TestAppContext) {
14057 init_test(cx, |_| {});
14058
14059 let mut cx = EditorTestContext::new(cx).await;
14060 cx.set_state(
14061 r#"let foo = 1;
14062let foo = 2;
14063let foo = 3;
14064let fooˇ = 4;
14065let foo = 5;
14066let foo = 6;
14067let foo = 7;
14068let foo = 8;
14069let foo = 9;
14070let foo = 10;
14071let foo = 11;
14072let foo = 12;
14073let foo = 13;
14074let foo = 14;
14075let foo = 15;"#,
14076 );
14077
14078 cx.update_editor(|e, cx| {
14079 assert_eq!(
14080 e.next_scroll_position,
14081 NextScrollCursorCenterTopBottom::Center,
14082 "Default next scroll direction is center",
14083 );
14084
14085 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
14086 assert_eq!(
14087 e.next_scroll_position,
14088 NextScrollCursorCenterTopBottom::Top,
14089 "After center, next scroll direction should be top",
14090 );
14091
14092 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
14093 assert_eq!(
14094 e.next_scroll_position,
14095 NextScrollCursorCenterTopBottom::Bottom,
14096 "After top, next scroll direction should be bottom",
14097 );
14098
14099 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
14100 assert_eq!(
14101 e.next_scroll_position,
14102 NextScrollCursorCenterTopBottom::Center,
14103 "After bottom, scrolling should start over",
14104 );
14105
14106 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
14107 assert_eq!(
14108 e.next_scroll_position,
14109 NextScrollCursorCenterTopBottom::Top,
14110 "Scrolling continues if retriggered fast enough"
14111 );
14112 });
14113
14114 cx.executor()
14115 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
14116 cx.executor().run_until_parked();
14117 cx.update_editor(|e, _| {
14118 assert_eq!(
14119 e.next_scroll_position,
14120 NextScrollCursorCenterTopBottom::Center,
14121 "If scrolling is not triggered fast enough, it should reset"
14122 );
14123 });
14124}
14125
14126#[gpui::test]
14127async fn test_goto_definition_with_find_all_references_fallback(cx: &mut gpui::TestAppContext) {
14128 init_test(cx, |_| {});
14129 let mut cx = EditorLspTestContext::new_rust(
14130 lsp::ServerCapabilities {
14131 definition_provider: Some(lsp::OneOf::Left(true)),
14132 references_provider: Some(lsp::OneOf::Left(true)),
14133 ..lsp::ServerCapabilities::default()
14134 },
14135 cx,
14136 )
14137 .await;
14138
14139 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
14140 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
14141 move |params, _| async move {
14142 if empty_go_to_definition {
14143 Ok(None)
14144 } else {
14145 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
14146 uri: params.text_document_position_params.text_document.uri,
14147 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
14148 })))
14149 }
14150 },
14151 );
14152 let references =
14153 cx.lsp
14154 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
14155 Ok(Some(vec![lsp::Location {
14156 uri: params.text_document_position.text_document.uri,
14157 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
14158 }]))
14159 });
14160 (go_to_definition, references)
14161 };
14162
14163 cx.set_state(
14164 &r#"fn one() {
14165 let mut a = ˇtwo();
14166 }
14167
14168 fn two() {}"#
14169 .unindent(),
14170 );
14171 set_up_lsp_handlers(false, &mut cx);
14172 let navigated = cx
14173 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
14174 .await
14175 .expect("Failed to navigate to definition");
14176 assert_eq!(
14177 navigated,
14178 Navigated::Yes,
14179 "Should have navigated to definition from the GetDefinition response"
14180 );
14181 cx.assert_editor_state(
14182 &r#"fn one() {
14183 let mut a = two();
14184 }
14185
14186 fn «twoˇ»() {}"#
14187 .unindent(),
14188 );
14189
14190 let editors = cx.update_workspace(|workspace, cx| {
14191 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
14192 });
14193 cx.update_editor(|_, test_editor_cx| {
14194 assert_eq!(
14195 editors.len(),
14196 1,
14197 "Initially, only one, test, editor should be open in the workspace"
14198 );
14199 assert_eq!(
14200 test_editor_cx.view(),
14201 editors.last().expect("Asserted len is 1")
14202 );
14203 });
14204
14205 set_up_lsp_handlers(true, &mut cx);
14206 let navigated = cx
14207 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
14208 .await
14209 .expect("Failed to navigate to lookup references");
14210 assert_eq!(
14211 navigated,
14212 Navigated::Yes,
14213 "Should have navigated to references as a fallback after empty GoToDefinition response"
14214 );
14215 // We should not change the selections in the existing file,
14216 // if opening another milti buffer with the references
14217 cx.assert_editor_state(
14218 &r#"fn one() {
14219 let mut a = two();
14220 }
14221
14222 fn «twoˇ»() {}"#
14223 .unindent(),
14224 );
14225 let editors = cx.update_workspace(|workspace, cx| {
14226 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
14227 });
14228 cx.update_editor(|_, test_editor_cx| {
14229 assert_eq!(
14230 editors.len(),
14231 2,
14232 "After falling back to references search, we open a new editor with the results"
14233 );
14234 let references_fallback_text = editors
14235 .into_iter()
14236 .find(|new_editor| new_editor != test_editor_cx.view())
14237 .expect("Should have one non-test editor now")
14238 .read(test_editor_cx)
14239 .text(test_editor_cx);
14240 assert_eq!(
14241 references_fallback_text, "fn one() {\n let mut a = two();\n}",
14242 "Should use the range from the references response and not the GoToDefinition one"
14243 );
14244 });
14245}
14246
14247#[gpui::test]
14248async fn test_find_enclosing_node_with_task(cx: &mut gpui::TestAppContext) {
14249 init_test(cx, |_| {});
14250
14251 let language = Arc::new(Language::new(
14252 LanguageConfig::default(),
14253 Some(tree_sitter_rust::LANGUAGE.into()),
14254 ));
14255
14256 let text = r#"
14257 #[cfg(test)]
14258 mod tests() {
14259 #[test]
14260 fn runnable_1() {
14261 let a = 1;
14262 }
14263
14264 #[test]
14265 fn runnable_2() {
14266 let a = 1;
14267 let b = 2;
14268 }
14269 }
14270 "#
14271 .unindent();
14272
14273 let fs = FakeFs::new(cx.executor());
14274 fs.insert_file("/file.rs", Default::default()).await;
14275
14276 let project = Project::test(fs, ["/a".as_ref()], cx).await;
14277 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
14278 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14279 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
14280 let multi_buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx));
14281
14282 let editor = cx.new_view(|cx| {
14283 Editor::new(
14284 EditorMode::Full,
14285 multi_buffer,
14286 Some(project.clone()),
14287 true,
14288 cx,
14289 )
14290 });
14291
14292 editor.update(cx, |editor, cx| {
14293 editor.tasks.insert(
14294 (buffer.read(cx).remote_id(), 3),
14295 RunnableTasks {
14296 templates: vec![],
14297 offset: MultiBufferOffset(43),
14298 column: 0,
14299 extra_variables: HashMap::default(),
14300 context_range: BufferOffset(43)..BufferOffset(85),
14301 },
14302 );
14303 editor.tasks.insert(
14304 (buffer.read(cx).remote_id(), 8),
14305 RunnableTasks {
14306 templates: vec![],
14307 offset: MultiBufferOffset(86),
14308 column: 0,
14309 extra_variables: HashMap::default(),
14310 context_range: BufferOffset(86)..BufferOffset(191),
14311 },
14312 );
14313
14314 // Test finding task when cursor is inside function body
14315 editor.change_selections(None, cx, |s| {
14316 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
14317 });
14318 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
14319 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
14320
14321 // Test finding task when cursor is on function name
14322 editor.change_selections(None, cx, |s| {
14323 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
14324 });
14325 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
14326 assert_eq!(row, 8, "Should find task when cursor is on function name");
14327 });
14328}
14329
14330#[gpui::test]
14331async fn test_multi_buffer_folding(cx: &mut gpui::TestAppContext) {
14332 init_test(cx, |_| {});
14333
14334 let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
14335 let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
14336 let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
14337
14338 let fs = FakeFs::new(cx.executor());
14339 fs.insert_tree(
14340 "/a",
14341 json!({
14342 "first.rs": sample_text_1,
14343 "second.rs": sample_text_2,
14344 "third.rs": sample_text_3,
14345 }),
14346 )
14347 .await;
14348 let project = Project::test(fs, ["/a".as_ref()], cx).await;
14349 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
14350 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14351 let worktree = project.update(cx, |project, cx| {
14352 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
14353 assert_eq!(worktrees.len(), 1);
14354 worktrees.pop().unwrap()
14355 });
14356 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
14357
14358 let buffer_1 = project
14359 .update(cx, |project, cx| {
14360 project.open_buffer((worktree_id, "first.rs"), cx)
14361 })
14362 .await
14363 .unwrap();
14364 let buffer_2 = project
14365 .update(cx, |project, cx| {
14366 project.open_buffer((worktree_id, "second.rs"), cx)
14367 })
14368 .await
14369 .unwrap();
14370 let buffer_3 = project
14371 .update(cx, |project, cx| {
14372 project.open_buffer((worktree_id, "third.rs"), cx)
14373 })
14374 .await
14375 .unwrap();
14376
14377 let multi_buffer = cx.new_model(|cx| {
14378 let mut multi_buffer = MultiBuffer::new(ReadWrite);
14379 multi_buffer.push_excerpts(
14380 buffer_1.clone(),
14381 [
14382 ExcerptRange {
14383 context: Point::new(0, 0)..Point::new(3, 0),
14384 primary: None,
14385 },
14386 ExcerptRange {
14387 context: Point::new(5, 0)..Point::new(7, 0),
14388 primary: None,
14389 },
14390 ExcerptRange {
14391 context: Point::new(9, 0)..Point::new(10, 4),
14392 primary: None,
14393 },
14394 ],
14395 cx,
14396 );
14397 multi_buffer.push_excerpts(
14398 buffer_2.clone(),
14399 [
14400 ExcerptRange {
14401 context: Point::new(0, 0)..Point::new(3, 0),
14402 primary: None,
14403 },
14404 ExcerptRange {
14405 context: Point::new(5, 0)..Point::new(7, 0),
14406 primary: None,
14407 },
14408 ExcerptRange {
14409 context: Point::new(9, 0)..Point::new(10, 4),
14410 primary: None,
14411 },
14412 ],
14413 cx,
14414 );
14415 multi_buffer.push_excerpts(
14416 buffer_3.clone(),
14417 [
14418 ExcerptRange {
14419 context: Point::new(0, 0)..Point::new(3, 0),
14420 primary: None,
14421 },
14422 ExcerptRange {
14423 context: Point::new(5, 0)..Point::new(7, 0),
14424 primary: None,
14425 },
14426 ExcerptRange {
14427 context: Point::new(9, 0)..Point::new(10, 4),
14428 primary: None,
14429 },
14430 ],
14431 cx,
14432 );
14433 multi_buffer
14434 });
14435 let multi_buffer_editor = cx.new_view(|cx| {
14436 Editor::new(
14437 EditorMode::Full,
14438 multi_buffer,
14439 Some(project.clone()),
14440 true,
14441 cx,
14442 )
14443 });
14444
14445 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";
14446 assert_eq!(
14447 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14448 full_text,
14449 );
14450
14451 multi_buffer_editor.update(cx, |editor, cx| {
14452 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
14453 });
14454 assert_eq!(
14455 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14456 "\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",
14457 "After folding the first buffer, its text should not be displayed"
14458 );
14459
14460 multi_buffer_editor.update(cx, |editor, cx| {
14461 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
14462 });
14463 assert_eq!(
14464 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14465 "\n\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n",
14466 "After folding the second buffer, its text should not be displayed"
14467 );
14468
14469 multi_buffer_editor.update(cx, |editor, cx| {
14470 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
14471 });
14472 assert_eq!(
14473 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14474 "\n\n\n\n\n",
14475 "After folding the third buffer, its text should not be displayed"
14476 );
14477
14478 // Emulate selection inside the fold logic, that should work
14479 multi_buffer_editor.update(cx, |editor, cx| {
14480 editor.snapshot(cx).next_line_boundary(Point::new(0, 4));
14481 });
14482
14483 multi_buffer_editor.update(cx, |editor, cx| {
14484 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
14485 });
14486 assert_eq!(
14487 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14488 "\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n",
14489 "After unfolding the second buffer, its text should be displayed"
14490 );
14491
14492 multi_buffer_editor.update(cx, |editor, cx| {
14493 editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
14494 });
14495 assert_eq!(
14496 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14497 "\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",
14498 "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
14499 );
14500
14501 multi_buffer_editor.update(cx, |editor, cx| {
14502 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
14503 });
14504 assert_eq!(
14505 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14506 full_text,
14507 "After unfolding the all buffers, all original text should be displayed"
14508 );
14509}
14510
14511#[gpui::test]
14512async fn test_multi_buffer_single_excerpts_folding(cx: &mut gpui::TestAppContext) {
14513 init_test(cx, |_| {});
14514
14515 let sample_text_1 = "1111\n2222\n3333".to_string();
14516 let sample_text_2 = "4444\n5555\n6666".to_string();
14517 let sample_text_3 = "7777\n8888\n9999".to_string();
14518
14519 let fs = FakeFs::new(cx.executor());
14520 fs.insert_tree(
14521 "/a",
14522 json!({
14523 "first.rs": sample_text_1,
14524 "second.rs": sample_text_2,
14525 "third.rs": sample_text_3,
14526 }),
14527 )
14528 .await;
14529 let project = Project::test(fs, ["/a".as_ref()], cx).await;
14530 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
14531 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14532 let worktree = project.update(cx, |project, cx| {
14533 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
14534 assert_eq!(worktrees.len(), 1);
14535 worktrees.pop().unwrap()
14536 });
14537 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
14538
14539 let buffer_1 = project
14540 .update(cx, |project, cx| {
14541 project.open_buffer((worktree_id, "first.rs"), cx)
14542 })
14543 .await
14544 .unwrap();
14545 let buffer_2 = project
14546 .update(cx, |project, cx| {
14547 project.open_buffer((worktree_id, "second.rs"), cx)
14548 })
14549 .await
14550 .unwrap();
14551 let buffer_3 = project
14552 .update(cx, |project, cx| {
14553 project.open_buffer((worktree_id, "third.rs"), cx)
14554 })
14555 .await
14556 .unwrap();
14557
14558 let multi_buffer = cx.new_model(|cx| {
14559 let mut multi_buffer = MultiBuffer::new(ReadWrite);
14560 multi_buffer.push_excerpts(
14561 buffer_1.clone(),
14562 [ExcerptRange {
14563 context: Point::new(0, 0)..Point::new(3, 0),
14564 primary: None,
14565 }],
14566 cx,
14567 );
14568 multi_buffer.push_excerpts(
14569 buffer_2.clone(),
14570 [ExcerptRange {
14571 context: Point::new(0, 0)..Point::new(3, 0),
14572 primary: None,
14573 }],
14574 cx,
14575 );
14576 multi_buffer.push_excerpts(
14577 buffer_3.clone(),
14578 [ExcerptRange {
14579 context: Point::new(0, 0)..Point::new(3, 0),
14580 primary: None,
14581 }],
14582 cx,
14583 );
14584 multi_buffer
14585 });
14586
14587 let multi_buffer_editor = cx.new_view(|cx| {
14588 Editor::new(
14589 EditorMode::Full,
14590 multi_buffer,
14591 Some(project.clone()),
14592 true,
14593 cx,
14594 )
14595 });
14596
14597 let full_text = "\n\n\n1111\n2222\n3333\n\n\n\n\n4444\n5555\n6666\n\n\n\n\n7777\n8888\n9999\n";
14598 assert_eq!(
14599 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14600 full_text,
14601 );
14602
14603 multi_buffer_editor.update(cx, |editor, cx| {
14604 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
14605 });
14606 assert_eq!(
14607 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14608 "\n\n\n\n\n4444\n5555\n6666\n\n\n\n\n7777\n8888\n9999\n",
14609 "After folding the first buffer, its text should not be displayed"
14610 );
14611
14612 multi_buffer_editor.update(cx, |editor, cx| {
14613 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
14614 });
14615
14616 assert_eq!(
14617 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14618 "\n\n\n\n\n\n\n7777\n8888\n9999\n",
14619 "After folding the second buffer, its text should not be displayed"
14620 );
14621
14622 multi_buffer_editor.update(cx, |editor, cx| {
14623 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
14624 });
14625 assert_eq!(
14626 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14627 "\n\n\n\n\n",
14628 "After folding the third buffer, its text should not be displayed"
14629 );
14630
14631 multi_buffer_editor.update(cx, |editor, cx| {
14632 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
14633 });
14634 assert_eq!(
14635 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14636 "\n\n\n\n\n4444\n5555\n6666\n\n\n",
14637 "After unfolding the second buffer, its text should be displayed"
14638 );
14639
14640 multi_buffer_editor.update(cx, |editor, cx| {
14641 editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
14642 });
14643 assert_eq!(
14644 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14645 "\n\n\n1111\n2222\n3333\n\n\n\n\n4444\n5555\n6666\n\n\n",
14646 "After unfolding the first buffer, its text should be displayed"
14647 );
14648
14649 multi_buffer_editor.update(cx, |editor, cx| {
14650 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
14651 });
14652 assert_eq!(
14653 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14654 full_text,
14655 "After unfolding all buffers, all original text should be displayed"
14656 );
14657}
14658
14659#[gpui::test]
14660async fn test_multi_buffer_with_single_excerpt_folding(cx: &mut gpui::TestAppContext) {
14661 init_test(cx, |_| {});
14662
14663 let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
14664
14665 let fs = FakeFs::new(cx.executor());
14666 fs.insert_tree(
14667 "/a",
14668 json!({
14669 "main.rs": sample_text,
14670 }),
14671 )
14672 .await;
14673 let project = Project::test(fs, ["/a".as_ref()], cx).await;
14674 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
14675 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14676 let worktree = project.update(cx, |project, cx| {
14677 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
14678 assert_eq!(worktrees.len(), 1);
14679 worktrees.pop().unwrap()
14680 });
14681 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
14682
14683 let buffer_1 = project
14684 .update(cx, |project, cx| {
14685 project.open_buffer((worktree_id, "main.rs"), cx)
14686 })
14687 .await
14688 .unwrap();
14689
14690 let multi_buffer = cx.new_model(|cx| {
14691 let mut multi_buffer = MultiBuffer::new(ReadWrite);
14692 multi_buffer.push_excerpts(
14693 buffer_1.clone(),
14694 [ExcerptRange {
14695 context: Point::new(0, 0)
14696 ..Point::new(
14697 sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
14698 0,
14699 ),
14700 primary: None,
14701 }],
14702 cx,
14703 );
14704 multi_buffer
14705 });
14706 let multi_buffer_editor = cx.new_view(|cx| {
14707 Editor::new(
14708 EditorMode::Full,
14709 multi_buffer,
14710 Some(project.clone()),
14711 true,
14712 cx,
14713 )
14714 });
14715
14716 let selection_range = Point::new(1, 0)..Point::new(2, 0);
14717 multi_buffer_editor.update(cx, |editor, cx| {
14718 enum TestHighlight {}
14719 let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
14720 let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
14721 editor.highlight_text::<TestHighlight>(
14722 vec![highlight_range.clone()],
14723 HighlightStyle::color(Hsla::green()),
14724 cx,
14725 );
14726 editor.change_selections(None, cx, |s| s.select_ranges(Some(highlight_range)));
14727 });
14728
14729 let full_text = format!("\n\n\n{sample_text}\n");
14730 assert_eq!(
14731 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14732 full_text,
14733 );
14734}
14735
14736#[gpui::test]
14737fn test_inline_completion_text(cx: &mut TestAppContext) {
14738 init_test(cx, |_| {});
14739
14740 // Simple insertion
14741 {
14742 let window = cx.add_window(|cx| {
14743 let buffer = MultiBuffer::build_simple("Hello, world!", cx);
14744 Editor::new(EditorMode::Full, buffer, None, true, cx)
14745 });
14746 let cx = &mut VisualTestContext::from_window(*window, cx);
14747
14748 window
14749 .update(cx, |editor, cx| {
14750 let snapshot = editor.snapshot(cx);
14751 let edit_range = snapshot.buffer_snapshot.anchor_after(Point::new(0, 6))
14752 ..snapshot.buffer_snapshot.anchor_before(Point::new(0, 6));
14753 let edits = vec![(edit_range, " beautiful".to_string())];
14754
14755 let InlineCompletionText::Edit { text, highlights } =
14756 inline_completion_edit_text(&snapshot, &edits, false, cx)
14757 else {
14758 panic!("Failed to generate inline completion text");
14759 };
14760
14761 assert_eq!(text, "Hello, beautiful world!");
14762 assert_eq!(highlights.len(), 1);
14763 assert_eq!(highlights[0].0, 6..16);
14764 assert_eq!(
14765 highlights[0].1.background_color,
14766 Some(cx.theme().status().created_background)
14767 );
14768 })
14769 .unwrap();
14770 }
14771
14772 // Replacement
14773 {
14774 let window = cx.add_window(|cx| {
14775 let buffer = MultiBuffer::build_simple("This is a test.", cx);
14776 Editor::new(EditorMode::Full, buffer, None, true, cx)
14777 });
14778 let cx = &mut VisualTestContext::from_window(*window, cx);
14779
14780 window
14781 .update(cx, |editor, cx| {
14782 let snapshot = editor.snapshot(cx);
14783 let edits = vec![(
14784 snapshot.buffer_snapshot.anchor_after(Point::new(0, 0))
14785 ..snapshot.buffer_snapshot.anchor_before(Point::new(0, 4)),
14786 "That".to_string(),
14787 )];
14788
14789 let InlineCompletionText::Edit { text, highlights } =
14790 inline_completion_edit_text(&snapshot, &edits, false, cx)
14791 else {
14792 panic!("Failed to generate inline completion text");
14793 };
14794
14795 assert_eq!(text, "That is a test.");
14796 assert_eq!(highlights.len(), 1);
14797 assert_eq!(highlights[0].0, 0..4);
14798 assert_eq!(
14799 highlights[0].1.background_color,
14800 Some(cx.theme().status().created_background)
14801 );
14802 })
14803 .unwrap();
14804 }
14805
14806 // Multiple edits
14807 {
14808 let window = cx.add_window(|cx| {
14809 let buffer = MultiBuffer::build_simple("Hello, world!", cx);
14810 Editor::new(EditorMode::Full, buffer, None, true, cx)
14811 });
14812 let cx = &mut VisualTestContext::from_window(*window, cx);
14813
14814 window
14815 .update(cx, |editor, cx| {
14816 let snapshot = editor.snapshot(cx);
14817 let edits = vec![
14818 (
14819 snapshot.buffer_snapshot.anchor_after(Point::new(0, 0))
14820 ..snapshot.buffer_snapshot.anchor_before(Point::new(0, 5)),
14821 "Greetings".into(),
14822 ),
14823 (
14824 snapshot.buffer_snapshot.anchor_after(Point::new(0, 12))
14825 ..snapshot.buffer_snapshot.anchor_before(Point::new(0, 12)),
14826 " and universe".into(),
14827 ),
14828 ];
14829
14830 let InlineCompletionText::Edit { text, highlights } =
14831 inline_completion_edit_text(&snapshot, &edits, false, cx)
14832 else {
14833 panic!("Failed to generate inline completion text");
14834 };
14835
14836 assert_eq!(text, "Greetings, world and universe!");
14837 assert_eq!(highlights.len(), 2);
14838 assert_eq!(highlights[0].0, 0..9);
14839 assert_eq!(highlights[1].0, 16..29);
14840 assert_eq!(
14841 highlights[0].1.background_color,
14842 Some(cx.theme().status().created_background)
14843 );
14844 assert_eq!(
14845 highlights[1].1.background_color,
14846 Some(cx.theme().status().created_background)
14847 );
14848 })
14849 .unwrap();
14850 }
14851
14852 // Multiple lines with edits
14853 {
14854 let window = cx.add_window(|cx| {
14855 let buffer =
14856 MultiBuffer::build_simple("First line\nSecond line\nThird line\nFourth line", cx);
14857 Editor::new(EditorMode::Full, buffer, None, true, cx)
14858 });
14859 let cx = &mut VisualTestContext::from_window(*window, cx);
14860
14861 window
14862 .update(cx, |editor, cx| {
14863 let snapshot = editor.snapshot(cx);
14864 let edits = vec![
14865 (
14866 snapshot.buffer_snapshot.anchor_before(Point::new(1, 7))
14867 ..snapshot.buffer_snapshot.anchor_before(Point::new(1, 11)),
14868 "modified".to_string(),
14869 ),
14870 (
14871 snapshot.buffer_snapshot.anchor_before(Point::new(2, 0))
14872 ..snapshot.buffer_snapshot.anchor_before(Point::new(2, 10)),
14873 "New third line".to_string(),
14874 ),
14875 (
14876 snapshot.buffer_snapshot.anchor_before(Point::new(3, 6))
14877 ..snapshot.buffer_snapshot.anchor_before(Point::new(3, 6)),
14878 " updated".to_string(),
14879 ),
14880 ];
14881
14882 let InlineCompletionText::Edit { text, highlights } =
14883 inline_completion_edit_text(&snapshot, &edits, false, cx)
14884 else {
14885 panic!("Failed to generate inline completion text");
14886 };
14887
14888 assert_eq!(text, "Second modified\nNew third line\nFourth updated line");
14889 assert_eq!(highlights.len(), 3);
14890 assert_eq!(highlights[0].0, 7..15); // "modified"
14891 assert_eq!(highlights[1].0, 16..30); // "New third line"
14892 assert_eq!(highlights[2].0, 37..45); // " updated"
14893
14894 for highlight in &highlights {
14895 assert_eq!(
14896 highlight.1.background_color,
14897 Some(cx.theme().status().created_background)
14898 );
14899 }
14900 })
14901 .unwrap();
14902 }
14903}
14904
14905#[gpui::test]
14906fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
14907 init_test(cx, |_| {});
14908
14909 // Deletion
14910 {
14911 let window = cx.add_window(|cx| {
14912 let buffer = MultiBuffer::build_simple("Hello, world!", cx);
14913 Editor::new(EditorMode::Full, buffer, None, true, cx)
14914 });
14915 let cx = &mut VisualTestContext::from_window(*window, cx);
14916
14917 window
14918 .update(cx, |editor, cx| {
14919 let snapshot = editor.snapshot(cx);
14920 let edit_range = snapshot.buffer_snapshot.anchor_after(Point::new(0, 5))
14921 ..snapshot.buffer_snapshot.anchor_before(Point::new(0, 11));
14922 let edits = vec![(edit_range, "".to_string())];
14923
14924 let InlineCompletionText::Edit { text, highlights } =
14925 inline_completion_edit_text(&snapshot, &edits, true, cx)
14926 else {
14927 panic!("Failed to generate inline completion text");
14928 };
14929
14930 assert_eq!(text, "Hello, world!");
14931 assert_eq!(highlights.len(), 1);
14932 assert_eq!(highlights[0].0, 5..11);
14933 assert_eq!(
14934 highlights[0].1.background_color,
14935 Some(cx.theme().status().deleted_background)
14936 );
14937 })
14938 .unwrap();
14939 }
14940
14941 // Insertion
14942 {
14943 let window = cx.add_window(|cx| {
14944 let buffer = MultiBuffer::build_simple("Hello, world!", cx);
14945 Editor::new(EditorMode::Full, buffer, None, true, cx)
14946 });
14947 let cx = &mut VisualTestContext::from_window(*window, cx);
14948
14949 window
14950 .update(cx, |editor, cx| {
14951 let snapshot = editor.snapshot(cx);
14952 let edit_range = snapshot.buffer_snapshot.anchor_after(Point::new(0, 6))
14953 ..snapshot.buffer_snapshot.anchor_before(Point::new(0, 6));
14954 let edits = vec![(edit_range, " digital".to_string())];
14955
14956 let InlineCompletionText::Edit { text, highlights } =
14957 inline_completion_edit_text(&snapshot, &edits, true, cx)
14958 else {
14959 panic!("Failed to generate inline completion text");
14960 };
14961
14962 assert_eq!(text, "Hello, digital world!");
14963 assert_eq!(highlights.len(), 1);
14964 assert_eq!(highlights[0].0, 6..14);
14965 assert_eq!(
14966 highlights[0].1.background_color,
14967 Some(cx.theme().status().created_background)
14968 );
14969 })
14970 .unwrap();
14971 }
14972}
14973
14974#[gpui::test]
14975async fn test_rename_with_duplicate_edits(cx: &mut gpui::TestAppContext) {
14976 init_test(cx, |_| {});
14977 let capabilities = lsp::ServerCapabilities {
14978 rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
14979 prepare_provider: Some(true),
14980 work_done_progress_options: Default::default(),
14981 })),
14982 ..Default::default()
14983 };
14984 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
14985
14986 cx.set_state(indoc! {"
14987 struct Fˇoo {}
14988 "});
14989
14990 cx.update_editor(|editor, cx| {
14991 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
14992 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
14993 editor.highlight_background::<DocumentHighlightRead>(
14994 &[highlight_range],
14995 |c| c.editor_document_highlight_read_background,
14996 cx,
14997 );
14998 });
14999
15000 let mut prepare_rename_handler =
15001 cx.handle_request::<lsp::request::PrepareRenameRequest, _, _>(move |_, _, _| async move {
15002 Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
15003 start: lsp::Position {
15004 line: 0,
15005 character: 7,
15006 },
15007 end: lsp::Position {
15008 line: 0,
15009 character: 10,
15010 },
15011 })))
15012 });
15013 let prepare_rename_task = cx
15014 .update_editor(|e, cx| e.rename(&Rename, cx))
15015 .expect("Prepare rename was not started");
15016 prepare_rename_handler.next().await.unwrap();
15017 prepare_rename_task.await.expect("Prepare rename failed");
15018
15019 let mut rename_handler =
15020 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
15021 let edit = lsp::TextEdit {
15022 range: lsp::Range {
15023 start: lsp::Position {
15024 line: 0,
15025 character: 7,
15026 },
15027 end: lsp::Position {
15028 line: 0,
15029 character: 10,
15030 },
15031 },
15032 new_text: "FooRenamed".to_string(),
15033 };
15034 Ok(Some(lsp::WorkspaceEdit::new(
15035 // Specify the same edit twice
15036 std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
15037 )))
15038 });
15039 let rename_task = cx
15040 .update_editor(|e, cx| e.confirm_rename(&ConfirmRename, cx))
15041 .expect("Confirm rename was not started");
15042 rename_handler.next().await.unwrap();
15043 rename_task.await.expect("Confirm rename failed");
15044 cx.run_until_parked();
15045
15046 // Despite two edits, only one is actually applied as those are identical
15047 cx.assert_editor_state(indoc! {"
15048 struct FooRenamedˇ {}
15049 "});
15050}
15051
15052#[gpui::test]
15053async fn test_rename_without_prepare(cx: &mut gpui::TestAppContext) {
15054 init_test(cx, |_| {});
15055 // These capabilities indicate that the server does not support prepare rename.
15056 let capabilities = lsp::ServerCapabilities {
15057 rename_provider: Some(lsp::OneOf::Left(true)),
15058 ..Default::default()
15059 };
15060 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
15061
15062 cx.set_state(indoc! {"
15063 struct Fˇoo {}
15064 "});
15065
15066 cx.update_editor(|editor, cx| {
15067 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
15068 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
15069 editor.highlight_background::<DocumentHighlightRead>(
15070 &[highlight_range],
15071 |c| c.editor_document_highlight_read_background,
15072 cx,
15073 );
15074 });
15075
15076 cx.update_editor(|e, cx| e.rename(&Rename, cx))
15077 .expect("Prepare rename was not started")
15078 .await
15079 .expect("Prepare rename failed");
15080
15081 let mut rename_handler =
15082 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
15083 let edit = lsp::TextEdit {
15084 range: lsp::Range {
15085 start: lsp::Position {
15086 line: 0,
15087 character: 7,
15088 },
15089 end: lsp::Position {
15090 line: 0,
15091 character: 10,
15092 },
15093 },
15094 new_text: "FooRenamed".to_string(),
15095 };
15096 Ok(Some(lsp::WorkspaceEdit::new(
15097 std::collections::HashMap::from_iter(Some((url, vec![edit]))),
15098 )))
15099 });
15100 let rename_task = cx
15101 .update_editor(|e, cx| e.confirm_rename(&ConfirmRename, cx))
15102 .expect("Confirm rename was not started");
15103 rename_handler.next().await.unwrap();
15104 rename_task.await.expect("Confirm rename failed");
15105 cx.run_until_parked();
15106
15107 // Correct range is renamed, as `surrounding_word` is used to find it.
15108 cx.assert_editor_state(indoc! {"
15109 struct FooRenamedˇ {}
15110 "});
15111}
15112
15113fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
15114 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
15115 point..point
15116}
15117
15118fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
15119 let (text, ranges) = marked_text_ranges(marked_text, true);
15120 assert_eq!(view.text(cx), text);
15121 assert_eq!(
15122 view.selections.ranges(cx),
15123 ranges,
15124 "Assert selections are {}",
15125 marked_text
15126 );
15127}
15128
15129pub fn handle_signature_help_request(
15130 cx: &mut EditorLspTestContext,
15131 mocked_response: lsp::SignatureHelp,
15132) -> impl Future<Output = ()> {
15133 let mut request =
15134 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
15135 let mocked_response = mocked_response.clone();
15136 async move { Ok(Some(mocked_response)) }
15137 });
15138
15139 async move {
15140 request.next().await;
15141 }
15142}
15143
15144/// Handle completion request passing a marked string specifying where the completion
15145/// should be triggered from using '|' character, what range should be replaced, and what completions
15146/// should be returned using '<' and '>' to delimit the range
15147pub fn handle_completion_request(
15148 cx: &mut EditorLspTestContext,
15149 marked_string: &str,
15150 completions: Vec<&'static str>,
15151 counter: Arc<AtomicUsize>,
15152) -> impl Future<Output = ()> {
15153 let complete_from_marker: TextRangeMarker = '|'.into();
15154 let replace_range_marker: TextRangeMarker = ('<', '>').into();
15155 let (_, mut marked_ranges) = marked_text_ranges_by(
15156 marked_string,
15157 vec![complete_from_marker.clone(), replace_range_marker.clone()],
15158 );
15159
15160 let complete_from_position =
15161 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
15162 let replace_range =
15163 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
15164
15165 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
15166 let completions = completions.clone();
15167 counter.fetch_add(1, atomic::Ordering::Release);
15168 async move {
15169 assert_eq!(params.text_document_position.text_document.uri, url.clone());
15170 assert_eq!(
15171 params.text_document_position.position,
15172 complete_from_position
15173 );
15174 Ok(Some(lsp::CompletionResponse::Array(
15175 completions
15176 .iter()
15177 .map(|completion_text| lsp::CompletionItem {
15178 label: completion_text.to_string(),
15179 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15180 range: replace_range,
15181 new_text: completion_text.to_string(),
15182 })),
15183 ..Default::default()
15184 })
15185 .collect(),
15186 )))
15187 }
15188 });
15189
15190 async move {
15191 request.next().await;
15192 }
15193}
15194
15195fn handle_resolve_completion_request(
15196 cx: &mut EditorLspTestContext,
15197 edits: Option<Vec<(&'static str, &'static str)>>,
15198) -> impl Future<Output = ()> {
15199 let edits = edits.map(|edits| {
15200 edits
15201 .iter()
15202 .map(|(marked_string, new_text)| {
15203 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
15204 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
15205 lsp::TextEdit::new(replace_range, new_text.to_string())
15206 })
15207 .collect::<Vec<_>>()
15208 });
15209
15210 let mut request =
15211 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
15212 let edits = edits.clone();
15213 async move {
15214 Ok(lsp::CompletionItem {
15215 additional_text_edits: edits,
15216 ..Default::default()
15217 })
15218 }
15219 });
15220
15221 async move {
15222 request.next().await;
15223 }
15224}
15225
15226pub(crate) fn update_test_language_settings(
15227 cx: &mut TestAppContext,
15228 f: impl Fn(&mut AllLanguageSettingsContent),
15229) {
15230 cx.update(|cx| {
15231 SettingsStore::update_global(cx, |store, cx| {
15232 store.update_user_settings::<AllLanguageSettings>(cx, f);
15233 });
15234 });
15235}
15236
15237pub(crate) fn update_test_project_settings(
15238 cx: &mut TestAppContext,
15239 f: impl Fn(&mut ProjectSettings),
15240) {
15241 cx.update(|cx| {
15242 SettingsStore::update_global(cx, |store, cx| {
15243 store.update_user_settings::<ProjectSettings>(cx, f);
15244 });
15245 });
15246}
15247
15248pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
15249 cx.update(|cx| {
15250 assets::Assets.load_test_fonts(cx);
15251 let store = SettingsStore::test(cx);
15252 cx.set_global(store);
15253 theme::init(theme::LoadThemes::JustBase, cx);
15254 release_channel::init(SemanticVersion::default(), cx);
15255 client::init_settings(cx);
15256 language::init(cx);
15257 Project::init_settings(cx);
15258 workspace::init_settings(cx);
15259 crate::init(cx);
15260 });
15261
15262 update_test_language_settings(cx, f);
15263}
15264
15265#[track_caller]
15266fn assert_hunk_revert(
15267 not_reverted_text_with_selections: &str,
15268 expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
15269 expected_reverted_text_with_selections: &str,
15270 base_text: &str,
15271 cx: &mut EditorLspTestContext,
15272) {
15273 cx.set_state(not_reverted_text_with_selections);
15274 cx.set_diff_base(base_text);
15275 cx.executor().run_until_parked();
15276
15277 let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
15278 let snapshot = editor.snapshot(cx);
15279 let reverted_hunk_statuses = snapshot
15280 .diff_map
15281 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len(), &snapshot.buffer_snapshot)
15282 .map(|hunk| hunk_status(&hunk))
15283 .collect::<Vec<_>>();
15284
15285 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
15286 reverted_hunk_statuses
15287 });
15288 cx.executor().run_until_parked();
15289 cx.assert_editor_state(expected_reverted_text_with_selections);
15290 assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
15291}