1use super::*;
2use crate::{
3 JoinLines,
4 linked_editing_ranges::LinkedEditingRanges,
5 scroll::scroll_amount::ScrollAmount,
6 test::{
7 assert_text_with_selections, build_editor,
8 editor_lsp_test_context::{EditorLspTestContext, git_commit_lang},
9 editor_test_context::EditorTestContext,
10 select_ranges,
11 },
12};
13use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
14use futures::StreamExt;
15use gpui::{
16 BackgroundExecutor, DismissEvent, SemanticVersion, TestAppContext, UpdateGlobal,
17 VisualTestContext, WindowBounds, WindowOptions, div,
18};
19use indoc::indoc;
20use language::{
21 BracketPairConfig,
22 Capability::ReadWrite,
23 FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher, LanguageName,
24 Override, Point,
25 language_settings::{
26 AllLanguageSettings, AllLanguageSettingsContent, CompletionSettings,
27 LanguageSettingsContent, LspInsertMode, PrettierSettings,
28 },
29};
30use language_settings::{Formatter, FormatterList, IndentGuideSettings};
31use lsp::CompletionParams;
32use multi_buffer::{IndentGuide, PathKey};
33use parking_lot::Mutex;
34use pretty_assertions::{assert_eq, assert_ne};
35use project::{
36 FakeFs,
37 debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
38 project_settings::{LspSettings, ProjectSettings},
39};
40use serde_json::{self, json};
41use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
42use std::{
43 iter,
44 sync::atomic::{self, AtomicUsize},
45};
46use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
47use text::ToPoint as _;
48use unindent::Unindent;
49use util::{
50 assert_set_eq, path,
51 test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
52 uri,
53};
54use workspace::{
55 CloseAllItems, CloseInactiveItems, NavigationEntry, ViewId,
56 item::{FollowEvent, FollowableItem, Item, ItemHandle},
57};
58
59#[gpui::test]
60fn test_edit_events(cx: &mut TestAppContext) {
61 init_test(cx, |_| {});
62
63 let buffer = cx.new(|cx| {
64 let mut buffer = language::Buffer::local("123456", cx);
65 buffer.set_group_interval(Duration::from_secs(1));
66 buffer
67 });
68
69 let events = Rc::new(RefCell::new(Vec::new()));
70 let editor1 = cx.add_window({
71 let events = events.clone();
72 |window, cx| {
73 let entity = cx.entity().clone();
74 cx.subscribe_in(
75 &entity,
76 window,
77 move |_, _, event: &EditorEvent, _, _| match event {
78 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
79 EditorEvent::BufferEdited => {
80 events.borrow_mut().push(("editor1", "buffer edited"))
81 }
82 _ => {}
83 },
84 )
85 .detach();
86 Editor::for_buffer(buffer.clone(), None, window, cx)
87 }
88 });
89
90 let editor2 = cx.add_window({
91 let events = events.clone();
92 |window, cx| {
93 cx.subscribe_in(
94 &cx.entity().clone(),
95 window,
96 move |_, _, event: &EditorEvent, _, _| match event {
97 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
98 EditorEvent::BufferEdited => {
99 events.borrow_mut().push(("editor2", "buffer edited"))
100 }
101 _ => {}
102 },
103 )
104 .detach();
105 Editor::for_buffer(buffer.clone(), None, window, cx)
106 }
107 });
108
109 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
110
111 // Mutating editor 1 will emit an `Edited` event only for that editor.
112 _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
113 assert_eq!(
114 mem::take(&mut *events.borrow_mut()),
115 [
116 ("editor1", "edited"),
117 ("editor1", "buffer edited"),
118 ("editor2", "buffer edited"),
119 ]
120 );
121
122 // Mutating editor 2 will emit an `Edited` event only for that editor.
123 _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
124 assert_eq!(
125 mem::take(&mut *events.borrow_mut()),
126 [
127 ("editor2", "edited"),
128 ("editor1", "buffer edited"),
129 ("editor2", "buffer edited"),
130 ]
131 );
132
133 // Undoing on editor 1 will emit an `Edited` event only for that editor.
134 _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
135 assert_eq!(
136 mem::take(&mut *events.borrow_mut()),
137 [
138 ("editor1", "edited"),
139 ("editor1", "buffer edited"),
140 ("editor2", "buffer edited"),
141 ]
142 );
143
144 // Redoing on editor 1 will emit an `Edited` event only for that editor.
145 _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
146 assert_eq!(
147 mem::take(&mut *events.borrow_mut()),
148 [
149 ("editor1", "edited"),
150 ("editor1", "buffer edited"),
151 ("editor2", "buffer edited"),
152 ]
153 );
154
155 // Undoing on editor 2 will emit an `Edited` event only for that editor.
156 _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
157 assert_eq!(
158 mem::take(&mut *events.borrow_mut()),
159 [
160 ("editor2", "edited"),
161 ("editor1", "buffer edited"),
162 ("editor2", "buffer edited"),
163 ]
164 );
165
166 // Redoing on editor 2 will emit an `Edited` event only for that editor.
167 _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
168 assert_eq!(
169 mem::take(&mut *events.borrow_mut()),
170 [
171 ("editor2", "edited"),
172 ("editor1", "buffer edited"),
173 ("editor2", "buffer edited"),
174 ]
175 );
176
177 // No event is emitted when the mutation is a no-op.
178 _ = editor2.update(cx, |editor, window, cx| {
179 editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
180
181 editor.backspace(&Backspace, window, cx);
182 });
183 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
184}
185
186#[gpui::test]
187fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
188 init_test(cx, |_| {});
189
190 let mut now = Instant::now();
191 let group_interval = Duration::from_millis(1);
192 let buffer = cx.new(|cx| {
193 let mut buf = language::Buffer::local("123456", cx);
194 buf.set_group_interval(group_interval);
195 buf
196 });
197 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
198 let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
199
200 _ = editor.update(cx, |editor, window, cx| {
201 editor.start_transaction_at(now, window, cx);
202 editor.change_selections(None, window, cx, |s| s.select_ranges([2..4]));
203
204 editor.insert("cd", window, cx);
205 editor.end_transaction_at(now, cx);
206 assert_eq!(editor.text(cx), "12cd56");
207 assert_eq!(editor.selections.ranges(cx), vec![4..4]);
208
209 editor.start_transaction_at(now, window, cx);
210 editor.change_selections(None, window, cx, |s| s.select_ranges([4..5]));
211 editor.insert("e", window, cx);
212 editor.end_transaction_at(now, cx);
213 assert_eq!(editor.text(cx), "12cde6");
214 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
215
216 now += group_interval + Duration::from_millis(1);
217 editor.change_selections(None, window, cx, |s| s.select_ranges([2..2]));
218
219 // Simulate an edit in another editor
220 buffer.update(cx, |buffer, cx| {
221 buffer.start_transaction_at(now, cx);
222 buffer.edit([(0..1, "a")], None, cx);
223 buffer.edit([(1..1, "b")], None, cx);
224 buffer.end_transaction_at(now, cx);
225 });
226
227 assert_eq!(editor.text(cx), "ab2cde6");
228 assert_eq!(editor.selections.ranges(cx), vec![3..3]);
229
230 // Last transaction happened past the group interval in a different editor.
231 // Undo it individually and don't restore selections.
232 editor.undo(&Undo, window, cx);
233 assert_eq!(editor.text(cx), "12cde6");
234 assert_eq!(editor.selections.ranges(cx), vec![2..2]);
235
236 // First two transactions happened within the group interval in this editor.
237 // Undo them together and restore selections.
238 editor.undo(&Undo, window, cx);
239 editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
240 assert_eq!(editor.text(cx), "123456");
241 assert_eq!(editor.selections.ranges(cx), vec![0..0]);
242
243 // Redo the first two transactions together.
244 editor.redo(&Redo, window, cx);
245 assert_eq!(editor.text(cx), "12cde6");
246 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
247
248 // Redo the last transaction on its own.
249 editor.redo(&Redo, window, cx);
250 assert_eq!(editor.text(cx), "ab2cde6");
251 assert_eq!(editor.selections.ranges(cx), vec![6..6]);
252
253 // Test empty transactions.
254 editor.start_transaction_at(now, window, cx);
255 editor.end_transaction_at(now, cx);
256 editor.undo(&Undo, window, cx);
257 assert_eq!(editor.text(cx), "12cde6");
258 });
259}
260
261#[gpui::test]
262fn test_ime_composition(cx: &mut TestAppContext) {
263 init_test(cx, |_| {});
264
265 let buffer = cx.new(|cx| {
266 let mut buffer = language::Buffer::local("abcde", cx);
267 // Ensure automatic grouping doesn't occur.
268 buffer.set_group_interval(Duration::ZERO);
269 buffer
270 });
271
272 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
273 cx.add_window(|window, cx| {
274 let mut editor = build_editor(buffer.clone(), window, cx);
275
276 // Start a new IME composition.
277 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
278 editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
279 editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
280 assert_eq!(editor.text(cx), "äbcde");
281 assert_eq!(
282 editor.marked_text_ranges(cx),
283 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
284 );
285
286 // Finalize IME composition.
287 editor.replace_text_in_range(None, "ā", window, cx);
288 assert_eq!(editor.text(cx), "ābcde");
289 assert_eq!(editor.marked_text_ranges(cx), None);
290
291 // IME composition edits are grouped and are undone/redone at once.
292 editor.undo(&Default::default(), window, cx);
293 assert_eq!(editor.text(cx), "abcde");
294 assert_eq!(editor.marked_text_ranges(cx), None);
295 editor.redo(&Default::default(), window, cx);
296 assert_eq!(editor.text(cx), "ābcde");
297 assert_eq!(editor.marked_text_ranges(cx), None);
298
299 // Start a new IME composition.
300 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
301 assert_eq!(
302 editor.marked_text_ranges(cx),
303 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
304 );
305
306 // Undoing during an IME composition cancels it.
307 editor.undo(&Default::default(), window, cx);
308 assert_eq!(editor.text(cx), "ābcde");
309 assert_eq!(editor.marked_text_ranges(cx), None);
310
311 // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
312 editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
313 assert_eq!(editor.text(cx), "ābcdè");
314 assert_eq!(
315 editor.marked_text_ranges(cx),
316 Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
317 );
318
319 // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
320 editor.replace_text_in_range(Some(4..999), "ę", window, cx);
321 assert_eq!(editor.text(cx), "ābcdę");
322 assert_eq!(editor.marked_text_ranges(cx), None);
323
324 // Start a new IME composition with multiple cursors.
325 editor.change_selections(None, window, cx, |s| {
326 s.select_ranges([
327 OffsetUtf16(1)..OffsetUtf16(1),
328 OffsetUtf16(3)..OffsetUtf16(3),
329 OffsetUtf16(5)..OffsetUtf16(5),
330 ])
331 });
332 editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
333 assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
334 assert_eq!(
335 editor.marked_text_ranges(cx),
336 Some(vec![
337 OffsetUtf16(0)..OffsetUtf16(3),
338 OffsetUtf16(4)..OffsetUtf16(7),
339 OffsetUtf16(8)..OffsetUtf16(11)
340 ])
341 );
342
343 // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
344 editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
345 assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
346 assert_eq!(
347 editor.marked_text_ranges(cx),
348 Some(vec![
349 OffsetUtf16(1)..OffsetUtf16(2),
350 OffsetUtf16(5)..OffsetUtf16(6),
351 OffsetUtf16(9)..OffsetUtf16(10)
352 ])
353 );
354
355 // Finalize IME composition with multiple cursors.
356 editor.replace_text_in_range(Some(9..10), "2", window, cx);
357 assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
358 assert_eq!(editor.marked_text_ranges(cx), None);
359
360 editor
361 });
362}
363
364#[gpui::test]
365fn test_selection_with_mouse(cx: &mut TestAppContext) {
366 init_test(cx, |_| {});
367
368 let editor = cx.add_window(|window, cx| {
369 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
370 build_editor(buffer, window, cx)
371 });
372
373 _ = editor.update(cx, |editor, window, cx| {
374 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
375 });
376 assert_eq!(
377 editor
378 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
379 .unwrap(),
380 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
381 );
382
383 _ = editor.update(cx, |editor, window, cx| {
384 editor.update_selection(
385 DisplayPoint::new(DisplayRow(3), 3),
386 0,
387 gpui::Point::<f32>::default(),
388 window,
389 cx,
390 );
391 });
392
393 assert_eq!(
394 editor
395 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
396 .unwrap(),
397 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
398 );
399
400 _ = editor.update(cx, |editor, window, cx| {
401 editor.update_selection(
402 DisplayPoint::new(DisplayRow(1), 1),
403 0,
404 gpui::Point::<f32>::default(),
405 window,
406 cx,
407 );
408 });
409
410 assert_eq!(
411 editor
412 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
413 .unwrap(),
414 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
415 );
416
417 _ = editor.update(cx, |editor, window, cx| {
418 editor.end_selection(window, cx);
419 editor.update_selection(
420 DisplayPoint::new(DisplayRow(3), 3),
421 0,
422 gpui::Point::<f32>::default(),
423 window,
424 cx,
425 );
426 });
427
428 assert_eq!(
429 editor
430 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
431 .unwrap(),
432 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
433 );
434
435 _ = editor.update(cx, |editor, window, cx| {
436 editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
437 editor.update_selection(
438 DisplayPoint::new(DisplayRow(0), 0),
439 0,
440 gpui::Point::<f32>::default(),
441 window,
442 cx,
443 );
444 });
445
446 assert_eq!(
447 editor
448 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
449 .unwrap(),
450 [
451 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
452 DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
453 ]
454 );
455
456 _ = editor.update(cx, |editor, window, cx| {
457 editor.end_selection(window, cx);
458 });
459
460 assert_eq!(
461 editor
462 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
463 .unwrap(),
464 [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
465 );
466}
467
468#[gpui::test]
469fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
470 init_test(cx, |_| {});
471
472 let editor = cx.add_window(|window, cx| {
473 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
474 build_editor(buffer, window, cx)
475 });
476
477 _ = editor.update(cx, |editor, window, cx| {
478 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
479 });
480
481 _ = editor.update(cx, |editor, window, cx| {
482 editor.end_selection(window, cx);
483 });
484
485 _ = editor.update(cx, |editor, window, cx| {
486 editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
487 });
488
489 _ = editor.update(cx, |editor, window, cx| {
490 editor.end_selection(window, cx);
491 });
492
493 assert_eq!(
494 editor
495 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
496 .unwrap(),
497 [
498 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
499 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
500 ]
501 );
502
503 _ = editor.update(cx, |editor, window, cx| {
504 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
505 });
506
507 _ = editor.update(cx, |editor, window, cx| {
508 editor.end_selection(window, cx);
509 });
510
511 assert_eq!(
512 editor
513 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
514 .unwrap(),
515 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
516 );
517}
518
519#[gpui::test]
520fn test_canceling_pending_selection(cx: &mut TestAppContext) {
521 init_test(cx, |_| {});
522
523 let editor = cx.add_window(|window, cx| {
524 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
525 build_editor(buffer, window, cx)
526 });
527
528 _ = editor.update(cx, |editor, window, cx| {
529 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
530 assert_eq!(
531 editor.selections.display_ranges(cx),
532 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
533 );
534 });
535
536 _ = editor.update(cx, |editor, window, cx| {
537 editor.update_selection(
538 DisplayPoint::new(DisplayRow(3), 3),
539 0,
540 gpui::Point::<f32>::default(),
541 window,
542 cx,
543 );
544 assert_eq!(
545 editor.selections.display_ranges(cx),
546 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
547 );
548 });
549
550 _ = editor.update(cx, |editor, window, cx| {
551 editor.cancel(&Cancel, window, cx);
552 editor.update_selection(
553 DisplayPoint::new(DisplayRow(1), 1),
554 0,
555 gpui::Point::<f32>::default(),
556 window,
557 cx,
558 );
559 assert_eq!(
560 editor.selections.display_ranges(cx),
561 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
562 );
563 });
564}
565
566#[gpui::test]
567fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
568 init_test(cx, |_| {});
569
570 let editor = cx.add_window(|window, cx| {
571 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
572 build_editor(buffer, window, cx)
573 });
574
575 _ = editor.update(cx, |editor, window, cx| {
576 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
577 assert_eq!(
578 editor.selections.display_ranges(cx),
579 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
580 );
581
582 editor.move_down(&Default::default(), window, cx);
583 assert_eq!(
584 editor.selections.display_ranges(cx),
585 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
586 );
587
588 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
589 assert_eq!(
590 editor.selections.display_ranges(cx),
591 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
592 );
593
594 editor.move_up(&Default::default(), window, cx);
595 assert_eq!(
596 editor.selections.display_ranges(cx),
597 [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
598 );
599 });
600}
601
602#[gpui::test]
603fn test_clone(cx: &mut TestAppContext) {
604 init_test(cx, |_| {});
605
606 let (text, selection_ranges) = marked_text_ranges(
607 indoc! {"
608 one
609 two
610 threeˇ
611 four
612 fiveˇ
613 "},
614 true,
615 );
616
617 let editor = cx.add_window(|window, cx| {
618 let buffer = MultiBuffer::build_simple(&text, cx);
619 build_editor(buffer, window, cx)
620 });
621
622 _ = editor.update(cx, |editor, window, cx| {
623 editor.change_selections(None, window, cx, |s| {
624 s.select_ranges(selection_ranges.clone())
625 });
626 editor.fold_creases(
627 vec![
628 Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
629 Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
630 ],
631 true,
632 window,
633 cx,
634 );
635 });
636
637 let cloned_editor = editor
638 .update(cx, |editor, _, cx| {
639 cx.open_window(Default::default(), |window, cx| {
640 cx.new(|cx| editor.clone(window, cx))
641 })
642 })
643 .unwrap()
644 .unwrap();
645
646 let snapshot = editor
647 .update(cx, |e, window, cx| e.snapshot(window, cx))
648 .unwrap();
649 let cloned_snapshot = cloned_editor
650 .update(cx, |e, window, cx| e.snapshot(window, cx))
651 .unwrap();
652
653 assert_eq!(
654 cloned_editor
655 .update(cx, |e, _, cx| e.display_text(cx))
656 .unwrap(),
657 editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
658 );
659 assert_eq!(
660 cloned_snapshot
661 .folds_in_range(0..text.len())
662 .collect::<Vec<_>>(),
663 snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
664 );
665 assert_set_eq!(
666 cloned_editor
667 .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
668 .unwrap(),
669 editor
670 .update(cx, |editor, _, cx| editor.selections.ranges(cx))
671 .unwrap()
672 );
673 assert_set_eq!(
674 cloned_editor
675 .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
676 .unwrap(),
677 editor
678 .update(cx, |e, _, cx| e.selections.display_ranges(cx))
679 .unwrap()
680 );
681}
682
683#[gpui::test]
684async fn test_navigation_history(cx: &mut TestAppContext) {
685 init_test(cx, |_| {});
686
687 use workspace::item::Item;
688
689 let fs = FakeFs::new(cx.executor());
690 let project = Project::test(fs, [], cx).await;
691 let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
692 let pane = workspace
693 .update(cx, |workspace, _, _| workspace.active_pane().clone())
694 .unwrap();
695
696 _ = workspace.update(cx, |_v, window, cx| {
697 cx.new(|cx| {
698 let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
699 let mut editor = build_editor(buffer.clone(), window, cx);
700 let handle = cx.entity();
701 editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
702
703 fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
704 editor.nav_history.as_mut().unwrap().pop_backward(cx)
705 }
706
707 // Move the cursor a small distance.
708 // Nothing is added to the navigation history.
709 editor.change_selections(None, window, cx, |s| {
710 s.select_display_ranges([
711 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
712 ])
713 });
714 editor.change_selections(None, window, cx, |s| {
715 s.select_display_ranges([
716 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
717 ])
718 });
719 assert!(pop_history(&mut editor, cx).is_none());
720
721 // Move the cursor a large distance.
722 // The history can jump back to the previous position.
723 editor.change_selections(None, window, cx, |s| {
724 s.select_display_ranges([
725 DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
726 ])
727 });
728 let nav_entry = pop_history(&mut editor, cx).unwrap();
729 editor.navigate(nav_entry.data.unwrap(), window, cx);
730 assert_eq!(nav_entry.item.id(), cx.entity_id());
731 assert_eq!(
732 editor.selections.display_ranges(cx),
733 &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
734 );
735 assert!(pop_history(&mut editor, cx).is_none());
736
737 // Move the cursor a small distance via the mouse.
738 // Nothing is added to the navigation history.
739 editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
740 editor.end_selection(window, cx);
741 assert_eq!(
742 editor.selections.display_ranges(cx),
743 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
744 );
745 assert!(pop_history(&mut editor, cx).is_none());
746
747 // Move the cursor a large distance via the mouse.
748 // The history can jump back to the previous position.
749 editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
750 editor.end_selection(window, cx);
751 assert_eq!(
752 editor.selections.display_ranges(cx),
753 &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
754 );
755 let nav_entry = pop_history(&mut editor, cx).unwrap();
756 editor.navigate(nav_entry.data.unwrap(), window, cx);
757 assert_eq!(nav_entry.item.id(), cx.entity_id());
758 assert_eq!(
759 editor.selections.display_ranges(cx),
760 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
761 );
762 assert!(pop_history(&mut editor, cx).is_none());
763
764 // Set scroll position to check later
765 editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), window, cx);
766 let original_scroll_position = editor.scroll_manager.anchor();
767
768 // Jump to the end of the document and adjust scroll
769 editor.move_to_end(&MoveToEnd, window, cx);
770 editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), window, cx);
771 assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
772
773 let nav_entry = pop_history(&mut editor, cx).unwrap();
774 editor.navigate(nav_entry.data.unwrap(), window, cx);
775 assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
776
777 // Ensure we don't panic when navigation data contains invalid anchors *and* points.
778 let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
779 invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
780 let invalid_point = Point::new(9999, 0);
781 editor.navigate(
782 Box::new(NavigationData {
783 cursor_anchor: invalid_anchor,
784 cursor_position: invalid_point,
785 scroll_anchor: ScrollAnchor {
786 anchor: invalid_anchor,
787 offset: Default::default(),
788 },
789 scroll_top_row: invalid_point.row,
790 }),
791 window,
792 cx,
793 );
794 assert_eq!(
795 editor.selections.display_ranges(cx),
796 &[editor.max_point(cx)..editor.max_point(cx)]
797 );
798 assert_eq!(
799 editor.scroll_position(cx),
800 gpui::Point::new(0., editor.max_point(cx).row().as_f32())
801 );
802
803 editor
804 })
805 });
806}
807
808#[gpui::test]
809fn test_cancel(cx: &mut TestAppContext) {
810 init_test(cx, |_| {});
811
812 let editor = cx.add_window(|window, cx| {
813 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
814 build_editor(buffer, window, cx)
815 });
816
817 _ = editor.update(cx, |editor, window, cx| {
818 editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
819 editor.update_selection(
820 DisplayPoint::new(DisplayRow(1), 1),
821 0,
822 gpui::Point::<f32>::default(),
823 window,
824 cx,
825 );
826 editor.end_selection(window, cx);
827
828 editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
829 editor.update_selection(
830 DisplayPoint::new(DisplayRow(0), 3),
831 0,
832 gpui::Point::<f32>::default(),
833 window,
834 cx,
835 );
836 editor.end_selection(window, cx);
837 assert_eq!(
838 editor.selections.display_ranges(cx),
839 [
840 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
841 DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
842 ]
843 );
844 });
845
846 _ = editor.update(cx, |editor, window, cx| {
847 editor.cancel(&Cancel, window, cx);
848 assert_eq!(
849 editor.selections.display_ranges(cx),
850 [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
851 );
852 });
853
854 _ = editor.update(cx, |editor, window, cx| {
855 editor.cancel(&Cancel, window, cx);
856 assert_eq!(
857 editor.selections.display_ranges(cx),
858 [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
859 );
860 });
861}
862
863#[gpui::test]
864fn test_fold_action(cx: &mut TestAppContext) {
865 init_test(cx, |_| {});
866
867 let editor = cx.add_window(|window, cx| {
868 let buffer = MultiBuffer::build_simple(
869 &"
870 impl Foo {
871 // Hello!
872
873 fn a() {
874 1
875 }
876
877 fn b() {
878 2
879 }
880
881 fn c() {
882 3
883 }
884 }
885 "
886 .unindent(),
887 cx,
888 );
889 build_editor(buffer.clone(), window, cx)
890 });
891
892 _ = editor.update(cx, |editor, window, cx| {
893 editor.change_selections(None, window, cx, |s| {
894 s.select_display_ranges([
895 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
896 ]);
897 });
898 editor.fold(&Fold, window, cx);
899 assert_eq!(
900 editor.display_text(cx),
901 "
902 impl Foo {
903 // Hello!
904
905 fn a() {
906 1
907 }
908
909 fn b() {⋯
910 }
911
912 fn c() {⋯
913 }
914 }
915 "
916 .unindent(),
917 );
918
919 editor.fold(&Fold, window, cx);
920 assert_eq!(
921 editor.display_text(cx),
922 "
923 impl Foo {⋯
924 }
925 "
926 .unindent(),
927 );
928
929 editor.unfold_lines(&UnfoldLines, window, cx);
930 assert_eq!(
931 editor.display_text(cx),
932 "
933 impl Foo {
934 // Hello!
935
936 fn a() {
937 1
938 }
939
940 fn b() {⋯
941 }
942
943 fn c() {⋯
944 }
945 }
946 "
947 .unindent(),
948 );
949
950 editor.unfold_lines(&UnfoldLines, window, cx);
951 assert_eq!(
952 editor.display_text(cx),
953 editor.buffer.read(cx).read(cx).text()
954 );
955 });
956}
957
958#[gpui::test]
959fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
960 init_test(cx, |_| {});
961
962 let editor = cx.add_window(|window, cx| {
963 let buffer = MultiBuffer::build_simple(
964 &"
965 class Foo:
966 # Hello!
967
968 def a():
969 print(1)
970
971 def b():
972 print(2)
973
974 def c():
975 print(3)
976 "
977 .unindent(),
978 cx,
979 );
980 build_editor(buffer.clone(), window, cx)
981 });
982
983 _ = editor.update(cx, |editor, window, cx| {
984 editor.change_selections(None, window, cx, |s| {
985 s.select_display_ranges([
986 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
987 ]);
988 });
989 editor.fold(&Fold, window, cx);
990 assert_eq!(
991 editor.display_text(cx),
992 "
993 class Foo:
994 # Hello!
995
996 def a():
997 print(1)
998
999 def b():⋯
1000
1001 def c():⋯
1002 "
1003 .unindent(),
1004 );
1005
1006 editor.fold(&Fold, window, cx);
1007 assert_eq!(
1008 editor.display_text(cx),
1009 "
1010 class Foo:⋯
1011 "
1012 .unindent(),
1013 );
1014
1015 editor.unfold_lines(&UnfoldLines, window, cx);
1016 assert_eq!(
1017 editor.display_text(cx),
1018 "
1019 class Foo:
1020 # Hello!
1021
1022 def a():
1023 print(1)
1024
1025 def b():⋯
1026
1027 def c():⋯
1028 "
1029 .unindent(),
1030 );
1031
1032 editor.unfold_lines(&UnfoldLines, window, cx);
1033 assert_eq!(
1034 editor.display_text(cx),
1035 editor.buffer.read(cx).read(cx).text()
1036 );
1037 });
1038}
1039
1040#[gpui::test]
1041fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
1042 init_test(cx, |_| {});
1043
1044 let editor = cx.add_window(|window, cx| {
1045 let buffer = MultiBuffer::build_simple(
1046 &"
1047 class Foo:
1048 # Hello!
1049
1050 def a():
1051 print(1)
1052
1053 def b():
1054 print(2)
1055
1056
1057 def c():
1058 print(3)
1059
1060
1061 "
1062 .unindent(),
1063 cx,
1064 );
1065 build_editor(buffer.clone(), window, cx)
1066 });
1067
1068 _ = editor.update(cx, |editor, window, cx| {
1069 editor.change_selections(None, window, cx, |s| {
1070 s.select_display_ranges([
1071 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
1072 ]);
1073 });
1074 editor.fold(&Fold, window, cx);
1075 assert_eq!(
1076 editor.display_text(cx),
1077 "
1078 class Foo:
1079 # Hello!
1080
1081 def a():
1082 print(1)
1083
1084 def b():⋯
1085
1086
1087 def c():⋯
1088
1089
1090 "
1091 .unindent(),
1092 );
1093
1094 editor.fold(&Fold, window, cx);
1095 assert_eq!(
1096 editor.display_text(cx),
1097 "
1098 class Foo:⋯
1099
1100
1101 "
1102 .unindent(),
1103 );
1104
1105 editor.unfold_lines(&UnfoldLines, window, cx);
1106 assert_eq!(
1107 editor.display_text(cx),
1108 "
1109 class Foo:
1110 # Hello!
1111
1112 def a():
1113 print(1)
1114
1115 def b():⋯
1116
1117
1118 def c():⋯
1119
1120
1121 "
1122 .unindent(),
1123 );
1124
1125 editor.unfold_lines(&UnfoldLines, window, cx);
1126 assert_eq!(
1127 editor.display_text(cx),
1128 editor.buffer.read(cx).read(cx).text()
1129 );
1130 });
1131}
1132
1133#[gpui::test]
1134fn test_fold_at_level(cx: &mut TestAppContext) {
1135 init_test(cx, |_| {});
1136
1137 let editor = cx.add_window(|window, cx| {
1138 let buffer = MultiBuffer::build_simple(
1139 &"
1140 class Foo:
1141 # Hello!
1142
1143 def a():
1144 print(1)
1145
1146 def b():
1147 print(2)
1148
1149
1150 class Bar:
1151 # World!
1152
1153 def a():
1154 print(1)
1155
1156 def b():
1157 print(2)
1158
1159
1160 "
1161 .unindent(),
1162 cx,
1163 );
1164 build_editor(buffer.clone(), window, cx)
1165 });
1166
1167 _ = editor.update(cx, |editor, window, cx| {
1168 editor.fold_at_level(&FoldAtLevel(2), window, cx);
1169 assert_eq!(
1170 editor.display_text(cx),
1171 "
1172 class Foo:
1173 # Hello!
1174
1175 def a():⋯
1176
1177 def b():⋯
1178
1179
1180 class Bar:
1181 # World!
1182
1183 def a():⋯
1184
1185 def b():⋯
1186
1187
1188 "
1189 .unindent(),
1190 );
1191
1192 editor.fold_at_level(&FoldAtLevel(1), window, cx);
1193 assert_eq!(
1194 editor.display_text(cx),
1195 "
1196 class Foo:⋯
1197
1198
1199 class Bar:⋯
1200
1201
1202 "
1203 .unindent(),
1204 );
1205
1206 editor.unfold_all(&UnfoldAll, window, cx);
1207 editor.fold_at_level(&FoldAtLevel(0), window, cx);
1208 assert_eq!(
1209 editor.display_text(cx),
1210 "
1211 class Foo:
1212 # Hello!
1213
1214 def a():
1215 print(1)
1216
1217 def b():
1218 print(2)
1219
1220
1221 class Bar:
1222 # World!
1223
1224 def a():
1225 print(1)
1226
1227 def b():
1228 print(2)
1229
1230
1231 "
1232 .unindent(),
1233 );
1234
1235 assert_eq!(
1236 editor.display_text(cx),
1237 editor.buffer.read(cx).read(cx).text()
1238 );
1239 });
1240}
1241
1242#[gpui::test]
1243fn test_move_cursor(cx: &mut TestAppContext) {
1244 init_test(cx, |_| {});
1245
1246 let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
1247 let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
1248
1249 buffer.update(cx, |buffer, cx| {
1250 buffer.edit(
1251 vec![
1252 (Point::new(1, 0)..Point::new(1, 0), "\t"),
1253 (Point::new(1, 1)..Point::new(1, 1), "\t"),
1254 ],
1255 None,
1256 cx,
1257 );
1258 });
1259 _ = editor.update(cx, |editor, window, cx| {
1260 assert_eq!(
1261 editor.selections.display_ranges(cx),
1262 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1263 );
1264
1265 editor.move_down(&MoveDown, window, cx);
1266 assert_eq!(
1267 editor.selections.display_ranges(cx),
1268 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1269 );
1270
1271 editor.move_right(&MoveRight, window, cx);
1272 assert_eq!(
1273 editor.selections.display_ranges(cx),
1274 &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
1275 );
1276
1277 editor.move_left(&MoveLeft, window, cx);
1278 assert_eq!(
1279 editor.selections.display_ranges(cx),
1280 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1281 );
1282
1283 editor.move_up(&MoveUp, window, cx);
1284 assert_eq!(
1285 editor.selections.display_ranges(cx),
1286 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1287 );
1288
1289 editor.move_to_end(&MoveToEnd, window, cx);
1290 assert_eq!(
1291 editor.selections.display_ranges(cx),
1292 &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
1293 );
1294
1295 editor.move_to_beginning(&MoveToBeginning, window, cx);
1296 assert_eq!(
1297 editor.selections.display_ranges(cx),
1298 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1299 );
1300
1301 editor.change_selections(None, window, cx, |s| {
1302 s.select_display_ranges([
1303 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
1304 ]);
1305 });
1306 editor.select_to_beginning(&SelectToBeginning, window, cx);
1307 assert_eq!(
1308 editor.selections.display_ranges(cx),
1309 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
1310 );
1311
1312 editor.select_to_end(&SelectToEnd, window, cx);
1313 assert_eq!(
1314 editor.selections.display_ranges(cx),
1315 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
1316 );
1317 });
1318}
1319
1320#[gpui::test]
1321fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
1322 init_test(cx, |_| {});
1323
1324 let editor = cx.add_window(|window, cx| {
1325 let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
1326 build_editor(buffer.clone(), window, cx)
1327 });
1328
1329 assert_eq!('🟥'.len_utf8(), 4);
1330 assert_eq!('α'.len_utf8(), 2);
1331
1332 _ = editor.update(cx, |editor, window, cx| {
1333 editor.fold_creases(
1334 vec![
1335 Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
1336 Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
1337 Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
1338 ],
1339 true,
1340 window,
1341 cx,
1342 );
1343 assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
1344
1345 editor.move_right(&MoveRight, window, cx);
1346 assert_eq!(
1347 editor.selections.display_ranges(cx),
1348 &[empty_range(0, "🟥".len())]
1349 );
1350 editor.move_right(&MoveRight, window, cx);
1351 assert_eq!(
1352 editor.selections.display_ranges(cx),
1353 &[empty_range(0, "🟥🟧".len())]
1354 );
1355 editor.move_right(&MoveRight, window, cx);
1356 assert_eq!(
1357 editor.selections.display_ranges(cx),
1358 &[empty_range(0, "🟥🟧⋯".len())]
1359 );
1360
1361 editor.move_down(&MoveDown, window, cx);
1362 assert_eq!(
1363 editor.selections.display_ranges(cx),
1364 &[empty_range(1, "ab⋯e".len())]
1365 );
1366 editor.move_left(&MoveLeft, window, cx);
1367 assert_eq!(
1368 editor.selections.display_ranges(cx),
1369 &[empty_range(1, "ab⋯".len())]
1370 );
1371 editor.move_left(&MoveLeft, window, cx);
1372 assert_eq!(
1373 editor.selections.display_ranges(cx),
1374 &[empty_range(1, "ab".len())]
1375 );
1376 editor.move_left(&MoveLeft, window, cx);
1377 assert_eq!(
1378 editor.selections.display_ranges(cx),
1379 &[empty_range(1, "a".len())]
1380 );
1381
1382 editor.move_down(&MoveDown, window, cx);
1383 assert_eq!(
1384 editor.selections.display_ranges(cx),
1385 &[empty_range(2, "α".len())]
1386 );
1387 editor.move_right(&MoveRight, window, cx);
1388 assert_eq!(
1389 editor.selections.display_ranges(cx),
1390 &[empty_range(2, "αβ".len())]
1391 );
1392 editor.move_right(&MoveRight, window, cx);
1393 assert_eq!(
1394 editor.selections.display_ranges(cx),
1395 &[empty_range(2, "αβ⋯".len())]
1396 );
1397 editor.move_right(&MoveRight, window, cx);
1398 assert_eq!(
1399 editor.selections.display_ranges(cx),
1400 &[empty_range(2, "αβ⋯ε".len())]
1401 );
1402
1403 editor.move_up(&MoveUp, window, cx);
1404 assert_eq!(
1405 editor.selections.display_ranges(cx),
1406 &[empty_range(1, "ab⋯e".len())]
1407 );
1408 editor.move_down(&MoveDown, window, cx);
1409 assert_eq!(
1410 editor.selections.display_ranges(cx),
1411 &[empty_range(2, "αβ⋯ε".len())]
1412 );
1413 editor.move_up(&MoveUp, window, cx);
1414 assert_eq!(
1415 editor.selections.display_ranges(cx),
1416 &[empty_range(1, "ab⋯e".len())]
1417 );
1418
1419 editor.move_up(&MoveUp, window, cx);
1420 assert_eq!(
1421 editor.selections.display_ranges(cx),
1422 &[empty_range(0, "🟥🟧".len())]
1423 );
1424 editor.move_left(&MoveLeft, window, cx);
1425 assert_eq!(
1426 editor.selections.display_ranges(cx),
1427 &[empty_range(0, "🟥".len())]
1428 );
1429 editor.move_left(&MoveLeft, window, cx);
1430 assert_eq!(
1431 editor.selections.display_ranges(cx),
1432 &[empty_range(0, "".len())]
1433 );
1434 });
1435}
1436
1437#[gpui::test]
1438fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
1439 init_test(cx, |_| {});
1440
1441 let editor = cx.add_window(|window, cx| {
1442 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
1443 build_editor(buffer.clone(), window, cx)
1444 });
1445 _ = editor.update(cx, |editor, window, cx| {
1446 editor.change_selections(None, window, cx, |s| {
1447 s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
1448 });
1449
1450 // moving above start of document should move selection to start of document,
1451 // but the next move down should still be at the original goal_x
1452 editor.move_up(&MoveUp, window, cx);
1453 assert_eq!(
1454 editor.selections.display_ranges(cx),
1455 &[empty_range(0, "".len())]
1456 );
1457
1458 editor.move_down(&MoveDown, window, cx);
1459 assert_eq!(
1460 editor.selections.display_ranges(cx),
1461 &[empty_range(1, "abcd".len())]
1462 );
1463
1464 editor.move_down(&MoveDown, window, cx);
1465 assert_eq!(
1466 editor.selections.display_ranges(cx),
1467 &[empty_range(2, "αβγ".len())]
1468 );
1469
1470 editor.move_down(&MoveDown, window, cx);
1471 assert_eq!(
1472 editor.selections.display_ranges(cx),
1473 &[empty_range(3, "abcd".len())]
1474 );
1475
1476 editor.move_down(&MoveDown, window, cx);
1477 assert_eq!(
1478 editor.selections.display_ranges(cx),
1479 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1480 );
1481
1482 // moving past end of document should not change goal_x
1483 editor.move_down(&MoveDown, window, cx);
1484 assert_eq!(
1485 editor.selections.display_ranges(cx),
1486 &[empty_range(5, "".len())]
1487 );
1488
1489 editor.move_down(&MoveDown, window, cx);
1490 assert_eq!(
1491 editor.selections.display_ranges(cx),
1492 &[empty_range(5, "".len())]
1493 );
1494
1495 editor.move_up(&MoveUp, window, cx);
1496 assert_eq!(
1497 editor.selections.display_ranges(cx),
1498 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1499 );
1500
1501 editor.move_up(&MoveUp, window, cx);
1502 assert_eq!(
1503 editor.selections.display_ranges(cx),
1504 &[empty_range(3, "abcd".len())]
1505 );
1506
1507 editor.move_up(&MoveUp, window, cx);
1508 assert_eq!(
1509 editor.selections.display_ranges(cx),
1510 &[empty_range(2, "αβγ".len())]
1511 );
1512 });
1513}
1514
1515#[gpui::test]
1516fn test_beginning_end_of_line(cx: &mut TestAppContext) {
1517 init_test(cx, |_| {});
1518 let move_to_beg = MoveToBeginningOfLine {
1519 stop_at_soft_wraps: true,
1520 stop_at_indent: true,
1521 };
1522
1523 let delete_to_beg = DeleteToBeginningOfLine {
1524 stop_at_indent: false,
1525 };
1526
1527 let move_to_end = MoveToEndOfLine {
1528 stop_at_soft_wraps: true,
1529 };
1530
1531 let editor = cx.add_window(|window, cx| {
1532 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1533 build_editor(buffer, window, cx)
1534 });
1535 _ = editor.update(cx, |editor, window, cx| {
1536 editor.change_selections(None, window, cx, |s| {
1537 s.select_display_ranges([
1538 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1539 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1540 ]);
1541 });
1542 });
1543
1544 _ = editor.update(cx, |editor, window, cx| {
1545 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1546 assert_eq!(
1547 editor.selections.display_ranges(cx),
1548 &[
1549 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1550 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1551 ]
1552 );
1553 });
1554
1555 _ = editor.update(cx, |editor, window, cx| {
1556 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1557 assert_eq!(
1558 editor.selections.display_ranges(cx),
1559 &[
1560 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1561 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1562 ]
1563 );
1564 });
1565
1566 _ = editor.update(cx, |editor, window, cx| {
1567 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1568 assert_eq!(
1569 editor.selections.display_ranges(cx),
1570 &[
1571 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1572 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1573 ]
1574 );
1575 });
1576
1577 _ = editor.update(cx, |editor, window, cx| {
1578 editor.move_to_end_of_line(&move_to_end, window, cx);
1579 assert_eq!(
1580 editor.selections.display_ranges(cx),
1581 &[
1582 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1583 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1584 ]
1585 );
1586 });
1587
1588 // Moving to the end of line again is a no-op.
1589 _ = editor.update(cx, |editor, window, cx| {
1590 editor.move_to_end_of_line(&move_to_end, window, cx);
1591 assert_eq!(
1592 editor.selections.display_ranges(cx),
1593 &[
1594 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1595 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1596 ]
1597 );
1598 });
1599
1600 _ = editor.update(cx, |editor, window, cx| {
1601 editor.move_left(&MoveLeft, window, cx);
1602 editor.select_to_beginning_of_line(
1603 &SelectToBeginningOfLine {
1604 stop_at_soft_wraps: true,
1605 stop_at_indent: true,
1606 },
1607 window,
1608 cx,
1609 );
1610 assert_eq!(
1611 editor.selections.display_ranges(cx),
1612 &[
1613 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1614 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1615 ]
1616 );
1617 });
1618
1619 _ = editor.update(cx, |editor, window, cx| {
1620 editor.select_to_beginning_of_line(
1621 &SelectToBeginningOfLine {
1622 stop_at_soft_wraps: true,
1623 stop_at_indent: true,
1624 },
1625 window,
1626 cx,
1627 );
1628 assert_eq!(
1629 editor.selections.display_ranges(cx),
1630 &[
1631 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1632 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1633 ]
1634 );
1635 });
1636
1637 _ = editor.update(cx, |editor, window, cx| {
1638 editor.select_to_beginning_of_line(
1639 &SelectToBeginningOfLine {
1640 stop_at_soft_wraps: true,
1641 stop_at_indent: true,
1642 },
1643 window,
1644 cx,
1645 );
1646 assert_eq!(
1647 editor.selections.display_ranges(cx),
1648 &[
1649 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1650 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1651 ]
1652 );
1653 });
1654
1655 _ = editor.update(cx, |editor, window, cx| {
1656 editor.select_to_end_of_line(
1657 &SelectToEndOfLine {
1658 stop_at_soft_wraps: true,
1659 },
1660 window,
1661 cx,
1662 );
1663 assert_eq!(
1664 editor.selections.display_ranges(cx),
1665 &[
1666 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
1667 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
1668 ]
1669 );
1670 });
1671
1672 _ = editor.update(cx, |editor, window, cx| {
1673 editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
1674 assert_eq!(editor.display_text(cx), "ab\n de");
1675 assert_eq!(
1676 editor.selections.display_ranges(cx),
1677 &[
1678 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
1679 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1680 ]
1681 );
1682 });
1683
1684 _ = editor.update(cx, |editor, window, cx| {
1685 editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
1686 assert_eq!(editor.display_text(cx), "\n");
1687 assert_eq!(
1688 editor.selections.display_ranges(cx),
1689 &[
1690 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1691 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1692 ]
1693 );
1694 });
1695}
1696
1697#[gpui::test]
1698fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
1699 init_test(cx, |_| {});
1700 let move_to_beg = MoveToBeginningOfLine {
1701 stop_at_soft_wraps: false,
1702 stop_at_indent: false,
1703 };
1704
1705 let move_to_end = MoveToEndOfLine {
1706 stop_at_soft_wraps: false,
1707 };
1708
1709 let editor = cx.add_window(|window, cx| {
1710 let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
1711 build_editor(buffer, window, cx)
1712 });
1713
1714 _ = editor.update(cx, |editor, window, cx| {
1715 editor.set_wrap_width(Some(140.0.into()), cx);
1716
1717 // We expect the following lines after wrapping
1718 // ```
1719 // thequickbrownfox
1720 // jumpedoverthelazydo
1721 // gs
1722 // ```
1723 // The final `gs` was soft-wrapped onto a new line.
1724 assert_eq!(
1725 "thequickbrownfox\njumpedoverthelaz\nydogs",
1726 editor.display_text(cx),
1727 );
1728
1729 // First, let's assert behavior on the first line, that was not soft-wrapped.
1730 // Start the cursor at the `k` on the first line
1731 editor.change_selections(None, window, cx, |s| {
1732 s.select_display_ranges([
1733 DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
1734 ]);
1735 });
1736
1737 // Moving to the beginning of the line should put us at the beginning of the line.
1738 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1739 assert_eq!(
1740 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
1741 editor.selections.display_ranges(cx)
1742 );
1743
1744 // Moving to the end of the line should put us at the end of the line.
1745 editor.move_to_end_of_line(&move_to_end, window, cx);
1746 assert_eq!(
1747 vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
1748 editor.selections.display_ranges(cx)
1749 );
1750
1751 // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
1752 // Start the cursor at the last line (`y` that was wrapped to a new line)
1753 editor.change_selections(None, window, cx, |s| {
1754 s.select_display_ranges([
1755 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
1756 ]);
1757 });
1758
1759 // Moving to the beginning of the line should put us at the start of the second line of
1760 // display text, i.e., the `j`.
1761 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1762 assert_eq!(
1763 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1764 editor.selections.display_ranges(cx)
1765 );
1766
1767 // Moving to the beginning of the line again should be a no-op.
1768 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1769 assert_eq!(
1770 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1771 editor.selections.display_ranges(cx)
1772 );
1773
1774 // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
1775 // next display line.
1776 editor.move_to_end_of_line(&move_to_end, window, cx);
1777 assert_eq!(
1778 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1779 editor.selections.display_ranges(cx)
1780 );
1781
1782 // Moving to the end of the line again should be a no-op.
1783 editor.move_to_end_of_line(&move_to_end, window, cx);
1784 assert_eq!(
1785 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1786 editor.selections.display_ranges(cx)
1787 );
1788 });
1789}
1790
1791#[gpui::test]
1792fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
1793 init_test(cx, |_| {});
1794
1795 let move_to_beg = MoveToBeginningOfLine {
1796 stop_at_soft_wraps: true,
1797 stop_at_indent: true,
1798 };
1799
1800 let select_to_beg = SelectToBeginningOfLine {
1801 stop_at_soft_wraps: true,
1802 stop_at_indent: true,
1803 };
1804
1805 let delete_to_beg = DeleteToBeginningOfLine {
1806 stop_at_indent: true,
1807 };
1808
1809 let move_to_end = MoveToEndOfLine {
1810 stop_at_soft_wraps: false,
1811 };
1812
1813 let editor = cx.add_window(|window, cx| {
1814 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1815 build_editor(buffer, window, cx)
1816 });
1817
1818 _ = editor.update(cx, |editor, window, cx| {
1819 editor.change_selections(None, window, cx, |s| {
1820 s.select_display_ranges([
1821 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1822 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1823 ]);
1824 });
1825
1826 // Moving to the beginning of the line should put the first cursor at the beginning of the line,
1827 // and the second cursor at the first non-whitespace character in the line.
1828 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1829 assert_eq!(
1830 editor.selections.display_ranges(cx),
1831 &[
1832 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1833 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1834 ]
1835 );
1836
1837 // Moving to the beginning of the line again should be a no-op for the first cursor,
1838 // and should move the second cursor to the beginning of the line.
1839 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1840 assert_eq!(
1841 editor.selections.display_ranges(cx),
1842 &[
1843 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1844 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1845 ]
1846 );
1847
1848 // Moving to the beginning of the line again should still be a no-op for the first cursor,
1849 // and should move the second cursor back to the first non-whitespace character in the line.
1850 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1851 assert_eq!(
1852 editor.selections.display_ranges(cx),
1853 &[
1854 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1855 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1856 ]
1857 );
1858
1859 // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
1860 // and to the first non-whitespace character in the line for the second cursor.
1861 editor.move_to_end_of_line(&move_to_end, window, cx);
1862 editor.move_left(&MoveLeft, window, cx);
1863 editor.select_to_beginning_of_line(&select_to_beg, window, cx);
1864 assert_eq!(
1865 editor.selections.display_ranges(cx),
1866 &[
1867 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1868 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1869 ]
1870 );
1871
1872 // Selecting to the beginning of the line again should be a no-op for the first cursor,
1873 // and should select to the beginning of the line for the second cursor.
1874 editor.select_to_beginning_of_line(&select_to_beg, window, cx);
1875 assert_eq!(
1876 editor.selections.display_ranges(cx),
1877 &[
1878 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1879 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1880 ]
1881 );
1882
1883 // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
1884 // and should delete to the first non-whitespace character in the line for the second cursor.
1885 editor.move_to_end_of_line(&move_to_end, window, cx);
1886 editor.move_left(&MoveLeft, window, cx);
1887 editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
1888 assert_eq!(editor.text(cx), "c\n f");
1889 });
1890}
1891
1892#[gpui::test]
1893fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1894 init_test(cx, |_| {});
1895
1896 let editor = cx.add_window(|window, cx| {
1897 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1898 build_editor(buffer, window, cx)
1899 });
1900 _ = editor.update(cx, |editor, window, cx| {
1901 editor.change_selections(None, window, cx, |s| {
1902 s.select_display_ranges([
1903 DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
1904 DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
1905 ])
1906 });
1907
1908 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1909 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", editor, cx);
1910
1911 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1912 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", editor, cx);
1913
1914 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1915 assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", editor, cx);
1916
1917 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1918 assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", editor, cx);
1919
1920 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1921 assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", editor, cx);
1922
1923 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1924 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", editor, cx);
1925
1926 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1927 assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", editor, cx);
1928
1929 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1930 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", editor, cx);
1931
1932 editor.move_right(&MoveRight, window, cx);
1933 editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
1934 assert_selection_ranges(
1935 "use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}",
1936 editor,
1937 cx,
1938 );
1939
1940 editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
1941 assert_selection_ranges(
1942 "use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}",
1943 editor,
1944 cx,
1945 );
1946
1947 editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
1948 assert_selection_ranges(
1949 "use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}",
1950 editor,
1951 cx,
1952 );
1953 });
1954}
1955
1956#[gpui::test]
1957fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1958 init_test(cx, |_| {});
1959
1960 let editor = cx.add_window(|window, cx| {
1961 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1962 build_editor(buffer, window, cx)
1963 });
1964
1965 _ = editor.update(cx, |editor, window, cx| {
1966 editor.set_wrap_width(Some(140.0.into()), cx);
1967 assert_eq!(
1968 editor.display_text(cx),
1969 "use one::{\n two::three::\n four::five\n};"
1970 );
1971
1972 editor.change_selections(None, window, cx, |s| {
1973 s.select_display_ranges([
1974 DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
1975 ]);
1976 });
1977
1978 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1979 assert_eq!(
1980 editor.selections.display_ranges(cx),
1981 &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
1982 );
1983
1984 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1985 assert_eq!(
1986 editor.selections.display_ranges(cx),
1987 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1988 );
1989
1990 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1991 assert_eq!(
1992 editor.selections.display_ranges(cx),
1993 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1994 );
1995
1996 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1997 assert_eq!(
1998 editor.selections.display_ranges(cx),
1999 &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
2000 );
2001
2002 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
2003 assert_eq!(
2004 editor.selections.display_ranges(cx),
2005 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
2006 );
2007
2008 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
2009 assert_eq!(
2010 editor.selections.display_ranges(cx),
2011 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
2012 );
2013 });
2014}
2015
2016#[gpui::test]
2017async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
2018 init_test(cx, |_| {});
2019 let mut cx = EditorTestContext::new(cx).await;
2020
2021 let line_height = cx.editor(|editor, window, _| {
2022 editor
2023 .style()
2024 .unwrap()
2025 .text
2026 .line_height_in_pixels(window.rem_size())
2027 });
2028 cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
2029
2030 cx.set_state(
2031 &r#"ˇone
2032 two
2033
2034 three
2035 fourˇ
2036 five
2037
2038 six"#
2039 .unindent(),
2040 );
2041
2042 cx.update_editor(|editor, window, cx| {
2043 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
2044 });
2045 cx.assert_editor_state(
2046 &r#"one
2047 two
2048 ˇ
2049 three
2050 four
2051 five
2052 ˇ
2053 six"#
2054 .unindent(),
2055 );
2056
2057 cx.update_editor(|editor, window, cx| {
2058 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
2059 });
2060 cx.assert_editor_state(
2061 &r#"one
2062 two
2063
2064 three
2065 four
2066 five
2067 ˇ
2068 sixˇ"#
2069 .unindent(),
2070 );
2071
2072 cx.update_editor(|editor, window, cx| {
2073 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
2074 });
2075 cx.assert_editor_state(
2076 &r#"one
2077 two
2078
2079 three
2080 four
2081 five
2082
2083 sixˇ"#
2084 .unindent(),
2085 );
2086
2087 cx.update_editor(|editor, window, cx| {
2088 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
2089 });
2090 cx.assert_editor_state(
2091 &r#"one
2092 two
2093
2094 three
2095 four
2096 five
2097 ˇ
2098 six"#
2099 .unindent(),
2100 );
2101
2102 cx.update_editor(|editor, window, cx| {
2103 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
2104 });
2105 cx.assert_editor_state(
2106 &r#"one
2107 two
2108 ˇ
2109 three
2110 four
2111 five
2112
2113 six"#
2114 .unindent(),
2115 );
2116
2117 cx.update_editor(|editor, window, cx| {
2118 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
2119 });
2120 cx.assert_editor_state(
2121 &r#"ˇone
2122 two
2123
2124 three
2125 four
2126 five
2127
2128 six"#
2129 .unindent(),
2130 );
2131}
2132
2133#[gpui::test]
2134async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
2135 init_test(cx, |_| {});
2136 let mut cx = EditorTestContext::new(cx).await;
2137 let line_height = cx.editor(|editor, window, _| {
2138 editor
2139 .style()
2140 .unwrap()
2141 .text
2142 .line_height_in_pixels(window.rem_size())
2143 });
2144 let window = cx.window;
2145 cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
2146
2147 cx.set_state(
2148 r#"ˇone
2149 two
2150 three
2151 four
2152 five
2153 six
2154 seven
2155 eight
2156 nine
2157 ten
2158 "#,
2159 );
2160
2161 cx.update_editor(|editor, window, cx| {
2162 assert_eq!(
2163 editor.snapshot(window, cx).scroll_position(),
2164 gpui::Point::new(0., 0.)
2165 );
2166 editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
2167 assert_eq!(
2168 editor.snapshot(window, cx).scroll_position(),
2169 gpui::Point::new(0., 3.)
2170 );
2171 editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
2172 assert_eq!(
2173 editor.snapshot(window, cx).scroll_position(),
2174 gpui::Point::new(0., 6.)
2175 );
2176 editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
2177 assert_eq!(
2178 editor.snapshot(window, cx).scroll_position(),
2179 gpui::Point::new(0., 3.)
2180 );
2181
2182 editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
2183 assert_eq!(
2184 editor.snapshot(window, cx).scroll_position(),
2185 gpui::Point::new(0., 1.)
2186 );
2187 editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
2188 assert_eq!(
2189 editor.snapshot(window, cx).scroll_position(),
2190 gpui::Point::new(0., 3.)
2191 );
2192 });
2193}
2194
2195#[gpui::test]
2196async fn test_autoscroll(cx: &mut TestAppContext) {
2197 init_test(cx, |_| {});
2198 let mut cx = EditorTestContext::new(cx).await;
2199
2200 let line_height = cx.update_editor(|editor, window, cx| {
2201 editor.set_vertical_scroll_margin(2, cx);
2202 editor
2203 .style()
2204 .unwrap()
2205 .text
2206 .line_height_in_pixels(window.rem_size())
2207 });
2208 let window = cx.window;
2209 cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
2210
2211 cx.set_state(
2212 r#"ˇone
2213 two
2214 three
2215 four
2216 five
2217 six
2218 seven
2219 eight
2220 nine
2221 ten
2222 "#,
2223 );
2224 cx.update_editor(|editor, window, cx| {
2225 assert_eq!(
2226 editor.snapshot(window, cx).scroll_position(),
2227 gpui::Point::new(0., 0.0)
2228 );
2229 });
2230
2231 // Add a cursor below the visible area. Since both cursors cannot fit
2232 // on screen, the editor autoscrolls to reveal the newest cursor, and
2233 // allows the vertical scroll margin below that cursor.
2234 cx.update_editor(|editor, window, cx| {
2235 editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
2236 selections.select_ranges([
2237 Point::new(0, 0)..Point::new(0, 0),
2238 Point::new(6, 0)..Point::new(6, 0),
2239 ]);
2240 })
2241 });
2242 cx.update_editor(|editor, window, cx| {
2243 assert_eq!(
2244 editor.snapshot(window, cx).scroll_position(),
2245 gpui::Point::new(0., 3.0)
2246 );
2247 });
2248
2249 // Move down. The editor cursor scrolls down to track the newest cursor.
2250 cx.update_editor(|editor, window, cx| {
2251 editor.move_down(&Default::default(), window, cx);
2252 });
2253 cx.update_editor(|editor, window, cx| {
2254 assert_eq!(
2255 editor.snapshot(window, cx).scroll_position(),
2256 gpui::Point::new(0., 4.0)
2257 );
2258 });
2259
2260 // Add a cursor above the visible area. Since both cursors fit on screen,
2261 // the editor scrolls to show both.
2262 cx.update_editor(|editor, window, cx| {
2263 editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
2264 selections.select_ranges([
2265 Point::new(1, 0)..Point::new(1, 0),
2266 Point::new(6, 0)..Point::new(6, 0),
2267 ]);
2268 })
2269 });
2270 cx.update_editor(|editor, window, cx| {
2271 assert_eq!(
2272 editor.snapshot(window, cx).scroll_position(),
2273 gpui::Point::new(0., 1.0)
2274 );
2275 });
2276}
2277
2278#[gpui::test]
2279async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
2280 init_test(cx, |_| {});
2281 let mut cx = EditorTestContext::new(cx).await;
2282
2283 let line_height = cx.editor(|editor, window, _cx| {
2284 editor
2285 .style()
2286 .unwrap()
2287 .text
2288 .line_height_in_pixels(window.rem_size())
2289 });
2290 let window = cx.window;
2291 cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
2292 cx.set_state(
2293 &r#"
2294 ˇone
2295 two
2296 threeˇ
2297 four
2298 five
2299 six
2300 seven
2301 eight
2302 nine
2303 ten
2304 "#
2305 .unindent(),
2306 );
2307
2308 cx.update_editor(|editor, window, cx| {
2309 editor.move_page_down(&MovePageDown::default(), window, cx)
2310 });
2311 cx.assert_editor_state(
2312 &r#"
2313 one
2314 two
2315 three
2316 ˇfour
2317 five
2318 sixˇ
2319 seven
2320 eight
2321 nine
2322 ten
2323 "#
2324 .unindent(),
2325 );
2326
2327 cx.update_editor(|editor, window, cx| {
2328 editor.move_page_down(&MovePageDown::default(), window, cx)
2329 });
2330 cx.assert_editor_state(
2331 &r#"
2332 one
2333 two
2334 three
2335 four
2336 five
2337 six
2338 ˇseven
2339 eight
2340 nineˇ
2341 ten
2342 "#
2343 .unindent(),
2344 );
2345
2346 cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
2347 cx.assert_editor_state(
2348 &r#"
2349 one
2350 two
2351 three
2352 ˇfour
2353 five
2354 sixˇ
2355 seven
2356 eight
2357 nine
2358 ten
2359 "#
2360 .unindent(),
2361 );
2362
2363 cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
2364 cx.assert_editor_state(
2365 &r#"
2366 ˇone
2367 two
2368 threeˇ
2369 four
2370 five
2371 six
2372 seven
2373 eight
2374 nine
2375 ten
2376 "#
2377 .unindent(),
2378 );
2379
2380 // Test select collapsing
2381 cx.update_editor(|editor, window, cx| {
2382 editor.move_page_down(&MovePageDown::default(), window, cx);
2383 editor.move_page_down(&MovePageDown::default(), window, cx);
2384 editor.move_page_down(&MovePageDown::default(), window, cx);
2385 });
2386 cx.assert_editor_state(
2387 &r#"
2388 one
2389 two
2390 three
2391 four
2392 five
2393 six
2394 seven
2395 eight
2396 nine
2397 ˇten
2398 ˇ"#
2399 .unindent(),
2400 );
2401}
2402
2403#[gpui::test]
2404async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
2405 init_test(cx, |_| {});
2406 let mut cx = EditorTestContext::new(cx).await;
2407 cx.set_state("one «two threeˇ» four");
2408 cx.update_editor(|editor, window, cx| {
2409 editor.delete_to_beginning_of_line(
2410 &DeleteToBeginningOfLine {
2411 stop_at_indent: false,
2412 },
2413 window,
2414 cx,
2415 );
2416 assert_eq!(editor.text(cx), " four");
2417 });
2418}
2419
2420#[gpui::test]
2421fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
2422 init_test(cx, |_| {});
2423
2424 let editor = cx.add_window(|window, cx| {
2425 let buffer = MultiBuffer::build_simple("one two three four", cx);
2426 build_editor(buffer.clone(), window, cx)
2427 });
2428
2429 _ = editor.update(cx, |editor, window, cx| {
2430 editor.change_selections(None, window, cx, |s| {
2431 s.select_display_ranges([
2432 // an empty selection - the preceding word fragment is deleted
2433 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2434 // characters selected - they are deleted
2435 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
2436 ])
2437 });
2438 editor.delete_to_previous_word_start(
2439 &DeleteToPreviousWordStart {
2440 ignore_newlines: false,
2441 },
2442 window,
2443 cx,
2444 );
2445 assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
2446 });
2447
2448 _ = editor.update(cx, |editor, window, cx| {
2449 editor.change_selections(None, window, cx, |s| {
2450 s.select_display_ranges([
2451 // an empty selection - the following word fragment is deleted
2452 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
2453 // characters selected - they are deleted
2454 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
2455 ])
2456 });
2457 editor.delete_to_next_word_end(
2458 &DeleteToNextWordEnd {
2459 ignore_newlines: false,
2460 },
2461 window,
2462 cx,
2463 );
2464 assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
2465 });
2466}
2467
2468#[gpui::test]
2469fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
2470 init_test(cx, |_| {});
2471
2472 let editor = cx.add_window(|window, cx| {
2473 let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
2474 build_editor(buffer.clone(), window, cx)
2475 });
2476 let del_to_prev_word_start = DeleteToPreviousWordStart {
2477 ignore_newlines: false,
2478 };
2479 let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
2480 ignore_newlines: true,
2481 };
2482
2483 _ = editor.update(cx, |editor, window, cx| {
2484 editor.change_selections(None, window, cx, |s| {
2485 s.select_display_ranges([
2486 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
2487 ])
2488 });
2489 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2490 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
2491 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2492 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
2493 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2494 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
2495 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2496 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
2497 editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
2498 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
2499 editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
2500 assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
2501 });
2502}
2503
2504#[gpui::test]
2505fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
2506 init_test(cx, |_| {});
2507
2508 let editor = cx.add_window(|window, cx| {
2509 let buffer = MultiBuffer::build_simple("\none\n two\nthree\n four", cx);
2510 build_editor(buffer.clone(), window, cx)
2511 });
2512 let del_to_next_word_end = DeleteToNextWordEnd {
2513 ignore_newlines: false,
2514 };
2515 let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
2516 ignore_newlines: true,
2517 };
2518
2519 _ = editor.update(cx, |editor, window, cx| {
2520 editor.change_selections(None, window, cx, |s| {
2521 s.select_display_ranges([
2522 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
2523 ])
2524 });
2525 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2526 assert_eq!(
2527 editor.buffer.read(cx).read(cx).text(),
2528 "one\n two\nthree\n four"
2529 );
2530 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2531 assert_eq!(
2532 editor.buffer.read(cx).read(cx).text(),
2533 "\n two\nthree\n four"
2534 );
2535 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2536 assert_eq!(
2537 editor.buffer.read(cx).read(cx).text(),
2538 "two\nthree\n four"
2539 );
2540 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2541 assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n four");
2542 editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
2543 assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n four");
2544 editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
2545 assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
2546 });
2547}
2548
2549#[gpui::test]
2550fn test_newline(cx: &mut TestAppContext) {
2551 init_test(cx, |_| {});
2552
2553 let editor = cx.add_window(|window, cx| {
2554 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
2555 build_editor(buffer.clone(), window, cx)
2556 });
2557
2558 _ = editor.update(cx, |editor, window, cx| {
2559 editor.change_selections(None, window, cx, |s| {
2560 s.select_display_ranges([
2561 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2562 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
2563 DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
2564 ])
2565 });
2566
2567 editor.newline(&Newline, window, cx);
2568 assert_eq!(editor.text(cx), "aa\naa\n \n bb\n bb\n");
2569 });
2570}
2571
2572#[gpui::test]
2573fn test_newline_with_old_selections(cx: &mut TestAppContext) {
2574 init_test(cx, |_| {});
2575
2576 let editor = cx.add_window(|window, cx| {
2577 let buffer = MultiBuffer::build_simple(
2578 "
2579 a
2580 b(
2581 X
2582 )
2583 c(
2584 X
2585 )
2586 "
2587 .unindent()
2588 .as_str(),
2589 cx,
2590 );
2591 let mut editor = build_editor(buffer.clone(), window, cx);
2592 editor.change_selections(None, window, cx, |s| {
2593 s.select_ranges([
2594 Point::new(2, 4)..Point::new(2, 5),
2595 Point::new(5, 4)..Point::new(5, 5),
2596 ])
2597 });
2598 editor
2599 });
2600
2601 _ = editor.update(cx, |editor, window, cx| {
2602 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2603 editor.buffer.update(cx, |buffer, cx| {
2604 buffer.edit(
2605 [
2606 (Point::new(1, 2)..Point::new(3, 0), ""),
2607 (Point::new(4, 2)..Point::new(6, 0), ""),
2608 ],
2609 None,
2610 cx,
2611 );
2612 assert_eq!(
2613 buffer.read(cx).text(),
2614 "
2615 a
2616 b()
2617 c()
2618 "
2619 .unindent()
2620 );
2621 });
2622 assert_eq!(
2623 editor.selections.ranges(cx),
2624 &[
2625 Point::new(1, 2)..Point::new(1, 2),
2626 Point::new(2, 2)..Point::new(2, 2),
2627 ],
2628 );
2629
2630 editor.newline(&Newline, window, cx);
2631 assert_eq!(
2632 editor.text(cx),
2633 "
2634 a
2635 b(
2636 )
2637 c(
2638 )
2639 "
2640 .unindent()
2641 );
2642
2643 // The selections are moved after the inserted newlines
2644 assert_eq!(
2645 editor.selections.ranges(cx),
2646 &[
2647 Point::new(2, 0)..Point::new(2, 0),
2648 Point::new(4, 0)..Point::new(4, 0),
2649 ],
2650 );
2651 });
2652}
2653
2654#[gpui::test]
2655async fn test_newline_above(cx: &mut TestAppContext) {
2656 init_test(cx, |settings| {
2657 settings.defaults.tab_size = NonZeroU32::new(4)
2658 });
2659
2660 let language = Arc::new(
2661 Language::new(
2662 LanguageConfig::default(),
2663 Some(tree_sitter_rust::LANGUAGE.into()),
2664 )
2665 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2666 .unwrap(),
2667 );
2668
2669 let mut cx = EditorTestContext::new(cx).await;
2670 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2671 cx.set_state(indoc! {"
2672 const a: ˇA = (
2673 (ˇ
2674 «const_functionˇ»(ˇ),
2675 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2676 )ˇ
2677 ˇ);ˇ
2678 "});
2679
2680 cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
2681 cx.assert_editor_state(indoc! {"
2682 ˇ
2683 const a: A = (
2684 ˇ
2685 (
2686 ˇ
2687 ˇ
2688 const_function(),
2689 ˇ
2690 ˇ
2691 ˇ
2692 ˇ
2693 something_else,
2694 ˇ
2695 )
2696 ˇ
2697 ˇ
2698 );
2699 "});
2700}
2701
2702#[gpui::test]
2703async fn test_newline_below(cx: &mut TestAppContext) {
2704 init_test(cx, |settings| {
2705 settings.defaults.tab_size = NonZeroU32::new(4)
2706 });
2707
2708 let language = Arc::new(
2709 Language::new(
2710 LanguageConfig::default(),
2711 Some(tree_sitter_rust::LANGUAGE.into()),
2712 )
2713 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2714 .unwrap(),
2715 );
2716
2717 let mut cx = EditorTestContext::new(cx).await;
2718 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2719 cx.set_state(indoc! {"
2720 const a: ˇA = (
2721 (ˇ
2722 «const_functionˇ»(ˇ),
2723 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2724 )ˇ
2725 ˇ);ˇ
2726 "});
2727
2728 cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
2729 cx.assert_editor_state(indoc! {"
2730 const a: A = (
2731 ˇ
2732 (
2733 ˇ
2734 const_function(),
2735 ˇ
2736 ˇ
2737 something_else,
2738 ˇ
2739 ˇ
2740 ˇ
2741 ˇ
2742 )
2743 ˇ
2744 );
2745 ˇ
2746 ˇ
2747 "});
2748}
2749
2750#[gpui::test]
2751async fn test_newline_comments(cx: &mut TestAppContext) {
2752 init_test(cx, |settings| {
2753 settings.defaults.tab_size = NonZeroU32::new(4)
2754 });
2755
2756 let language = Arc::new(Language::new(
2757 LanguageConfig {
2758 line_comments: vec!["//".into()],
2759 ..LanguageConfig::default()
2760 },
2761 None,
2762 ));
2763 {
2764 let mut cx = EditorTestContext::new(cx).await;
2765 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2766 cx.set_state(indoc! {"
2767 // Fooˇ
2768 "});
2769
2770 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2771 cx.assert_editor_state(indoc! {"
2772 // Foo
2773 //ˇ
2774 "});
2775 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
2776 cx.set_state(indoc! {"
2777 ˇ// Foo
2778 "});
2779 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2780 cx.assert_editor_state(indoc! {"
2781
2782 ˇ// Foo
2783 "});
2784 }
2785 // Ensure that comment continuations can be disabled.
2786 update_test_language_settings(cx, |settings| {
2787 settings.defaults.extend_comment_on_newline = Some(false);
2788 });
2789 let mut cx = EditorTestContext::new(cx).await;
2790 cx.set_state(indoc! {"
2791 // Fooˇ
2792 "});
2793 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2794 cx.assert_editor_state(indoc! {"
2795 // Foo
2796 ˇ
2797 "});
2798}
2799
2800#[gpui::test]
2801fn test_insert_with_old_selections(cx: &mut TestAppContext) {
2802 init_test(cx, |_| {});
2803
2804 let editor = cx.add_window(|window, cx| {
2805 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
2806 let mut editor = build_editor(buffer.clone(), window, cx);
2807 editor.change_selections(None, window, cx, |s| {
2808 s.select_ranges([3..4, 11..12, 19..20])
2809 });
2810 editor
2811 });
2812
2813 _ = editor.update(cx, |editor, window, cx| {
2814 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2815 editor.buffer.update(cx, |buffer, cx| {
2816 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
2817 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
2818 });
2819 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
2820
2821 editor.insert("Z", window, cx);
2822 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
2823
2824 // The selections are moved after the inserted characters
2825 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
2826 });
2827}
2828
2829#[gpui::test]
2830async fn test_tab(cx: &mut TestAppContext) {
2831 init_test(cx, |settings| {
2832 settings.defaults.tab_size = NonZeroU32::new(3)
2833 });
2834
2835 let mut cx = EditorTestContext::new(cx).await;
2836 cx.set_state(indoc! {"
2837 ˇabˇc
2838 ˇ🏀ˇ🏀ˇefg
2839 dˇ
2840 "});
2841 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2842 cx.assert_editor_state(indoc! {"
2843 ˇab ˇc
2844 ˇ🏀 ˇ🏀 ˇefg
2845 d ˇ
2846 "});
2847
2848 cx.set_state(indoc! {"
2849 a
2850 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2851 "});
2852 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2853 cx.assert_editor_state(indoc! {"
2854 a
2855 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2856 "});
2857}
2858
2859#[gpui::test]
2860async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
2861 init_test(cx, |_| {});
2862
2863 let mut cx = EditorTestContext::new(cx).await;
2864 let language = Arc::new(
2865 Language::new(
2866 LanguageConfig::default(),
2867 Some(tree_sitter_rust::LANGUAGE.into()),
2868 )
2869 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2870 .unwrap(),
2871 );
2872 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2873
2874 // when all cursors are to the left of the suggested indent, then auto-indent all.
2875 cx.set_state(indoc! {"
2876 const a: B = (
2877 c(
2878 ˇ
2879 ˇ )
2880 );
2881 "});
2882 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2883 cx.assert_editor_state(indoc! {"
2884 const a: B = (
2885 c(
2886 ˇ
2887 ˇ)
2888 );
2889 "});
2890
2891 // cursors that are already at the suggested indent level do not move
2892 // until other cursors that are to the left of the suggested indent
2893 // auto-indent.
2894 cx.set_state(indoc! {"
2895 ˇ
2896 const a: B = (
2897 c(
2898 d(
2899 ˇ
2900 )
2901 ˇ
2902 ˇ )
2903 );
2904 "});
2905 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2906 cx.assert_editor_state(indoc! {"
2907 ˇ
2908 const a: B = (
2909 c(
2910 d(
2911 ˇ
2912 )
2913 ˇ
2914 ˇ)
2915 );
2916 "});
2917 // once all multi-cursors are at the suggested
2918 // indent level, they all insert a soft tab together.
2919 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2920 cx.assert_editor_state(indoc! {"
2921 ˇ
2922 const a: B = (
2923 c(
2924 d(
2925 ˇ
2926 )
2927 ˇ
2928 ˇ)
2929 );
2930 "});
2931
2932 // handle auto-indent when there are multiple cursors on the same line
2933 cx.set_state(indoc! {"
2934 const a: B = (
2935 c(
2936 ˇ ˇ
2937 ˇ )
2938 );
2939 "});
2940 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2941 cx.assert_editor_state(indoc! {"
2942 const a: B = (
2943 c(
2944 ˇ
2945 ˇ)
2946 );
2947 "});
2948}
2949
2950#[gpui::test]
2951async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
2952 init_test(cx, |settings| {
2953 settings.defaults.tab_size = NonZeroU32::new(3)
2954 });
2955
2956 let mut cx = EditorTestContext::new(cx).await;
2957 cx.set_state(indoc! {"
2958 ˇ
2959 \t ˇ
2960 \t ˇ
2961 \t ˇ
2962 \t \t\t \t \t\t \t\t \t \t ˇ
2963 "});
2964
2965 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2966 cx.assert_editor_state(indoc! {"
2967 ˇ
2968 \t ˇ
2969 \t ˇ
2970 \t ˇ
2971 \t \t\t \t \t\t \t\t \t \t ˇ
2972 "});
2973}
2974
2975#[gpui::test]
2976async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
2977 init_test(cx, |settings| {
2978 settings.defaults.tab_size = NonZeroU32::new(4)
2979 });
2980
2981 let language = Arc::new(
2982 Language::new(
2983 LanguageConfig::default(),
2984 Some(tree_sitter_rust::LANGUAGE.into()),
2985 )
2986 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2987 .unwrap(),
2988 );
2989
2990 let mut cx = EditorTestContext::new(cx).await;
2991 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2992 cx.set_state(indoc! {"
2993 fn a() {
2994 if b {
2995 \t ˇc
2996 }
2997 }
2998 "});
2999
3000 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3001 cx.assert_editor_state(indoc! {"
3002 fn a() {
3003 if b {
3004 ˇc
3005 }
3006 }
3007 "});
3008}
3009
3010#[gpui::test]
3011async fn test_indent_outdent(cx: &mut TestAppContext) {
3012 init_test(cx, |settings| {
3013 settings.defaults.tab_size = NonZeroU32::new(4);
3014 });
3015
3016 let mut cx = EditorTestContext::new(cx).await;
3017
3018 cx.set_state(indoc! {"
3019 «oneˇ» «twoˇ»
3020 three
3021 four
3022 "});
3023 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3024 cx.assert_editor_state(indoc! {"
3025 «oneˇ» «twoˇ»
3026 three
3027 four
3028 "});
3029
3030 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3031 cx.assert_editor_state(indoc! {"
3032 «oneˇ» «twoˇ»
3033 three
3034 four
3035 "});
3036
3037 // select across line ending
3038 cx.set_state(indoc! {"
3039 one two
3040 t«hree
3041 ˇ» four
3042 "});
3043 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3044 cx.assert_editor_state(indoc! {"
3045 one two
3046 t«hree
3047 ˇ» four
3048 "});
3049
3050 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3051 cx.assert_editor_state(indoc! {"
3052 one two
3053 t«hree
3054 ˇ» four
3055 "});
3056
3057 // Ensure that indenting/outdenting works when the cursor is at column 0.
3058 cx.set_state(indoc! {"
3059 one two
3060 ˇthree
3061 four
3062 "});
3063 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3064 cx.assert_editor_state(indoc! {"
3065 one two
3066 ˇthree
3067 four
3068 "});
3069
3070 cx.set_state(indoc! {"
3071 one two
3072 ˇ three
3073 four
3074 "});
3075 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3076 cx.assert_editor_state(indoc! {"
3077 one two
3078 ˇthree
3079 four
3080 "});
3081}
3082
3083#[gpui::test]
3084async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
3085 init_test(cx, |settings| {
3086 settings.defaults.hard_tabs = Some(true);
3087 });
3088
3089 let mut cx = EditorTestContext::new(cx).await;
3090
3091 // select two ranges on one line
3092 cx.set_state(indoc! {"
3093 «oneˇ» «twoˇ»
3094 three
3095 four
3096 "});
3097 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3098 cx.assert_editor_state(indoc! {"
3099 \t«oneˇ» «twoˇ»
3100 three
3101 four
3102 "});
3103 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3104 cx.assert_editor_state(indoc! {"
3105 \t\t«oneˇ» «twoˇ»
3106 three
3107 four
3108 "});
3109 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3110 cx.assert_editor_state(indoc! {"
3111 \t«oneˇ» «twoˇ»
3112 three
3113 four
3114 "});
3115 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3116 cx.assert_editor_state(indoc! {"
3117 «oneˇ» «twoˇ»
3118 three
3119 four
3120 "});
3121
3122 // select across a line ending
3123 cx.set_state(indoc! {"
3124 one two
3125 t«hree
3126 ˇ»four
3127 "});
3128 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3129 cx.assert_editor_state(indoc! {"
3130 one two
3131 \tt«hree
3132 ˇ»four
3133 "});
3134 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3135 cx.assert_editor_state(indoc! {"
3136 one two
3137 \t\tt«hree
3138 ˇ»four
3139 "});
3140 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3141 cx.assert_editor_state(indoc! {"
3142 one two
3143 \tt«hree
3144 ˇ»four
3145 "});
3146 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3147 cx.assert_editor_state(indoc! {"
3148 one two
3149 t«hree
3150 ˇ»four
3151 "});
3152
3153 // Ensure that indenting/outdenting works when the cursor is at column 0.
3154 cx.set_state(indoc! {"
3155 one two
3156 ˇthree
3157 four
3158 "});
3159 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3160 cx.assert_editor_state(indoc! {"
3161 one two
3162 ˇthree
3163 four
3164 "});
3165 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3166 cx.assert_editor_state(indoc! {"
3167 one two
3168 \tˇthree
3169 four
3170 "});
3171 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3172 cx.assert_editor_state(indoc! {"
3173 one two
3174 ˇthree
3175 four
3176 "});
3177}
3178
3179#[gpui::test]
3180fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
3181 init_test(cx, |settings| {
3182 settings.languages.extend([
3183 (
3184 "TOML".into(),
3185 LanguageSettingsContent {
3186 tab_size: NonZeroU32::new(2),
3187 ..Default::default()
3188 },
3189 ),
3190 (
3191 "Rust".into(),
3192 LanguageSettingsContent {
3193 tab_size: NonZeroU32::new(4),
3194 ..Default::default()
3195 },
3196 ),
3197 ]);
3198 });
3199
3200 let toml_language = Arc::new(Language::new(
3201 LanguageConfig {
3202 name: "TOML".into(),
3203 ..Default::default()
3204 },
3205 None,
3206 ));
3207 let rust_language = Arc::new(Language::new(
3208 LanguageConfig {
3209 name: "Rust".into(),
3210 ..Default::default()
3211 },
3212 None,
3213 ));
3214
3215 let toml_buffer =
3216 cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
3217 let rust_buffer =
3218 cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
3219 let multibuffer = cx.new(|cx| {
3220 let mut multibuffer = MultiBuffer::new(ReadWrite);
3221 multibuffer.push_excerpts(
3222 toml_buffer.clone(),
3223 [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
3224 cx,
3225 );
3226 multibuffer.push_excerpts(
3227 rust_buffer.clone(),
3228 [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
3229 cx,
3230 );
3231 multibuffer
3232 });
3233
3234 cx.add_window(|window, cx| {
3235 let mut editor = build_editor(multibuffer, window, cx);
3236
3237 assert_eq!(
3238 editor.text(cx),
3239 indoc! {"
3240 a = 1
3241 b = 2
3242
3243 const c: usize = 3;
3244 "}
3245 );
3246
3247 select_ranges(
3248 &mut editor,
3249 indoc! {"
3250 «aˇ» = 1
3251 b = 2
3252
3253 «const c:ˇ» usize = 3;
3254 "},
3255 window,
3256 cx,
3257 );
3258
3259 editor.tab(&Tab, window, cx);
3260 assert_text_with_selections(
3261 &mut editor,
3262 indoc! {"
3263 «aˇ» = 1
3264 b = 2
3265
3266 «const c:ˇ» usize = 3;
3267 "},
3268 cx,
3269 );
3270 editor.backtab(&Backtab, window, cx);
3271 assert_text_with_selections(
3272 &mut editor,
3273 indoc! {"
3274 «aˇ» = 1
3275 b = 2
3276
3277 «const c:ˇ» usize = 3;
3278 "},
3279 cx,
3280 );
3281
3282 editor
3283 });
3284}
3285
3286#[gpui::test]
3287async fn test_backspace(cx: &mut TestAppContext) {
3288 init_test(cx, |_| {});
3289
3290 let mut cx = EditorTestContext::new(cx).await;
3291
3292 // Basic backspace
3293 cx.set_state(indoc! {"
3294 onˇe two three
3295 fou«rˇ» five six
3296 seven «ˇeight nine
3297 »ten
3298 "});
3299 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3300 cx.assert_editor_state(indoc! {"
3301 oˇe two three
3302 fouˇ five six
3303 seven ˇten
3304 "});
3305
3306 // Test backspace inside and around indents
3307 cx.set_state(indoc! {"
3308 zero
3309 ˇone
3310 ˇtwo
3311 ˇ ˇ ˇ three
3312 ˇ ˇ four
3313 "});
3314 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3315 cx.assert_editor_state(indoc! {"
3316 zero
3317 ˇone
3318 ˇtwo
3319 ˇ threeˇ four
3320 "});
3321}
3322
3323#[gpui::test]
3324async fn test_delete(cx: &mut TestAppContext) {
3325 init_test(cx, |_| {});
3326
3327 let mut cx = EditorTestContext::new(cx).await;
3328 cx.set_state(indoc! {"
3329 onˇe two three
3330 fou«rˇ» five six
3331 seven «ˇeight nine
3332 »ten
3333 "});
3334 cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
3335 cx.assert_editor_state(indoc! {"
3336 onˇ two three
3337 fouˇ five six
3338 seven ˇten
3339 "});
3340}
3341
3342#[gpui::test]
3343fn test_delete_line(cx: &mut TestAppContext) {
3344 init_test(cx, |_| {});
3345
3346 let editor = cx.add_window(|window, cx| {
3347 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3348 build_editor(buffer, window, cx)
3349 });
3350 _ = editor.update(cx, |editor, window, cx| {
3351 editor.change_selections(None, window, cx, |s| {
3352 s.select_display_ranges([
3353 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3354 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3355 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3356 ])
3357 });
3358 editor.delete_line(&DeleteLine, window, cx);
3359 assert_eq!(editor.display_text(cx), "ghi");
3360 assert_eq!(
3361 editor.selections.display_ranges(cx),
3362 vec![
3363 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
3364 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
3365 ]
3366 );
3367 });
3368
3369 let editor = cx.add_window(|window, cx| {
3370 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3371 build_editor(buffer, window, cx)
3372 });
3373 _ = editor.update(cx, |editor, window, cx| {
3374 editor.change_selections(None, window, cx, |s| {
3375 s.select_display_ranges([
3376 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
3377 ])
3378 });
3379 editor.delete_line(&DeleteLine, window, cx);
3380 assert_eq!(editor.display_text(cx), "ghi\n");
3381 assert_eq!(
3382 editor.selections.display_ranges(cx),
3383 vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
3384 );
3385 });
3386}
3387
3388#[gpui::test]
3389fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
3390 init_test(cx, |_| {});
3391
3392 cx.add_window(|window, cx| {
3393 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3394 let mut editor = build_editor(buffer.clone(), window, cx);
3395 let buffer = buffer.read(cx).as_singleton().unwrap();
3396
3397 assert_eq!(
3398 editor.selections.ranges::<Point>(cx),
3399 &[Point::new(0, 0)..Point::new(0, 0)]
3400 );
3401
3402 // When on single line, replace newline at end by space
3403 editor.join_lines(&JoinLines, window, cx);
3404 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3405 assert_eq!(
3406 editor.selections.ranges::<Point>(cx),
3407 &[Point::new(0, 3)..Point::new(0, 3)]
3408 );
3409
3410 // When multiple lines are selected, remove newlines that are spanned by the selection
3411 editor.change_selections(None, window, cx, |s| {
3412 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
3413 });
3414 editor.join_lines(&JoinLines, window, cx);
3415 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
3416 assert_eq!(
3417 editor.selections.ranges::<Point>(cx),
3418 &[Point::new(0, 11)..Point::new(0, 11)]
3419 );
3420
3421 // Undo should be transactional
3422 editor.undo(&Undo, window, cx);
3423 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3424 assert_eq!(
3425 editor.selections.ranges::<Point>(cx),
3426 &[Point::new(0, 5)..Point::new(2, 2)]
3427 );
3428
3429 // When joining an empty line don't insert a space
3430 editor.change_selections(None, window, cx, |s| {
3431 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
3432 });
3433 editor.join_lines(&JoinLines, window, cx);
3434 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
3435 assert_eq!(
3436 editor.selections.ranges::<Point>(cx),
3437 [Point::new(2, 3)..Point::new(2, 3)]
3438 );
3439
3440 // We can remove trailing newlines
3441 editor.join_lines(&JoinLines, window, cx);
3442 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3443 assert_eq!(
3444 editor.selections.ranges::<Point>(cx),
3445 [Point::new(2, 3)..Point::new(2, 3)]
3446 );
3447
3448 // We don't blow up on the last line
3449 editor.join_lines(&JoinLines, window, cx);
3450 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3451 assert_eq!(
3452 editor.selections.ranges::<Point>(cx),
3453 [Point::new(2, 3)..Point::new(2, 3)]
3454 );
3455
3456 // reset to test indentation
3457 editor.buffer.update(cx, |buffer, cx| {
3458 buffer.edit(
3459 [
3460 (Point::new(1, 0)..Point::new(1, 2), " "),
3461 (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
3462 ],
3463 None,
3464 cx,
3465 )
3466 });
3467
3468 // We remove any leading spaces
3469 assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
3470 editor.change_selections(None, window, cx, |s| {
3471 s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
3472 });
3473 editor.join_lines(&JoinLines, window, cx);
3474 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
3475
3476 // We don't insert a space for a line containing only spaces
3477 editor.join_lines(&JoinLines, window, cx);
3478 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
3479
3480 // We ignore any leading tabs
3481 editor.join_lines(&JoinLines, window, cx);
3482 assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
3483
3484 editor
3485 });
3486}
3487
3488#[gpui::test]
3489fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
3490 init_test(cx, |_| {});
3491
3492 cx.add_window(|window, cx| {
3493 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3494 let mut editor = build_editor(buffer.clone(), window, cx);
3495 let buffer = buffer.read(cx).as_singleton().unwrap();
3496
3497 editor.change_selections(None, window, cx, |s| {
3498 s.select_ranges([
3499 Point::new(0, 2)..Point::new(1, 1),
3500 Point::new(1, 2)..Point::new(1, 2),
3501 Point::new(3, 1)..Point::new(3, 2),
3502 ])
3503 });
3504
3505 editor.join_lines(&JoinLines, window, cx);
3506 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
3507
3508 assert_eq!(
3509 editor.selections.ranges::<Point>(cx),
3510 [
3511 Point::new(0, 7)..Point::new(0, 7),
3512 Point::new(1, 3)..Point::new(1, 3)
3513 ]
3514 );
3515 editor
3516 });
3517}
3518
3519#[gpui::test]
3520async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
3521 init_test(cx, |_| {});
3522
3523 let mut cx = EditorTestContext::new(cx).await;
3524
3525 let diff_base = r#"
3526 Line 0
3527 Line 1
3528 Line 2
3529 Line 3
3530 "#
3531 .unindent();
3532
3533 cx.set_state(
3534 &r#"
3535 ˇLine 0
3536 Line 1
3537 Line 2
3538 Line 3
3539 "#
3540 .unindent(),
3541 );
3542
3543 cx.set_head_text(&diff_base);
3544 executor.run_until_parked();
3545
3546 // Join lines
3547 cx.update_editor(|editor, window, cx| {
3548 editor.join_lines(&JoinLines, window, cx);
3549 });
3550 executor.run_until_parked();
3551
3552 cx.assert_editor_state(
3553 &r#"
3554 Line 0ˇ Line 1
3555 Line 2
3556 Line 3
3557 "#
3558 .unindent(),
3559 );
3560 // Join again
3561 cx.update_editor(|editor, window, cx| {
3562 editor.join_lines(&JoinLines, window, cx);
3563 });
3564 executor.run_until_parked();
3565
3566 cx.assert_editor_state(
3567 &r#"
3568 Line 0 Line 1ˇ Line 2
3569 Line 3
3570 "#
3571 .unindent(),
3572 );
3573}
3574
3575#[gpui::test]
3576async fn test_custom_newlines_cause_no_false_positive_diffs(
3577 executor: BackgroundExecutor,
3578 cx: &mut TestAppContext,
3579) {
3580 init_test(cx, |_| {});
3581 let mut cx = EditorTestContext::new(cx).await;
3582 cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
3583 cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
3584 executor.run_until_parked();
3585
3586 cx.update_editor(|editor, window, cx| {
3587 let snapshot = editor.snapshot(window, cx);
3588 assert_eq!(
3589 snapshot
3590 .buffer_snapshot
3591 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
3592 .collect::<Vec<_>>(),
3593 Vec::new(),
3594 "Should not have any diffs for files with custom newlines"
3595 );
3596 });
3597}
3598
3599#[gpui::test]
3600async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
3601 init_test(cx, |_| {});
3602
3603 let mut cx = EditorTestContext::new(cx).await;
3604
3605 // Test sort_lines_case_insensitive()
3606 cx.set_state(indoc! {"
3607 «z
3608 y
3609 x
3610 Z
3611 Y
3612 Xˇ»
3613 "});
3614 cx.update_editor(|e, window, cx| {
3615 e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
3616 });
3617 cx.assert_editor_state(indoc! {"
3618 «x
3619 X
3620 y
3621 Y
3622 z
3623 Zˇ»
3624 "});
3625
3626 // Test reverse_lines()
3627 cx.set_state(indoc! {"
3628 «5
3629 4
3630 3
3631 2
3632 1ˇ»
3633 "});
3634 cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
3635 cx.assert_editor_state(indoc! {"
3636 «1
3637 2
3638 3
3639 4
3640 5ˇ»
3641 "});
3642
3643 // Skip testing shuffle_line()
3644
3645 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
3646 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
3647
3648 // Don't manipulate when cursor is on single line, but expand the selection
3649 cx.set_state(indoc! {"
3650 ddˇdd
3651 ccc
3652 bb
3653 a
3654 "});
3655 cx.update_editor(|e, window, cx| {
3656 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3657 });
3658 cx.assert_editor_state(indoc! {"
3659 «ddddˇ»
3660 ccc
3661 bb
3662 a
3663 "});
3664
3665 // Basic manipulate case
3666 // Start selection moves to column 0
3667 // End of selection shrinks to fit shorter line
3668 cx.set_state(indoc! {"
3669 dd«d
3670 ccc
3671 bb
3672 aaaaaˇ»
3673 "});
3674 cx.update_editor(|e, window, cx| {
3675 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3676 });
3677 cx.assert_editor_state(indoc! {"
3678 «aaaaa
3679 bb
3680 ccc
3681 dddˇ»
3682 "});
3683
3684 // Manipulate case with newlines
3685 cx.set_state(indoc! {"
3686 dd«d
3687 ccc
3688
3689 bb
3690 aaaaa
3691
3692 ˇ»
3693 "});
3694 cx.update_editor(|e, window, cx| {
3695 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3696 });
3697 cx.assert_editor_state(indoc! {"
3698 «
3699
3700 aaaaa
3701 bb
3702 ccc
3703 dddˇ»
3704
3705 "});
3706
3707 // Adding new line
3708 cx.set_state(indoc! {"
3709 aa«a
3710 bbˇ»b
3711 "});
3712 cx.update_editor(|e, window, cx| {
3713 e.manipulate_lines(window, cx, |lines| lines.push("added_line"))
3714 });
3715 cx.assert_editor_state(indoc! {"
3716 «aaa
3717 bbb
3718 added_lineˇ»
3719 "});
3720
3721 // Removing line
3722 cx.set_state(indoc! {"
3723 aa«a
3724 bbbˇ»
3725 "});
3726 cx.update_editor(|e, window, cx| {
3727 e.manipulate_lines(window, cx, |lines| {
3728 lines.pop();
3729 })
3730 });
3731 cx.assert_editor_state(indoc! {"
3732 «aaaˇ»
3733 "});
3734
3735 // Removing all lines
3736 cx.set_state(indoc! {"
3737 aa«a
3738 bbbˇ»
3739 "});
3740 cx.update_editor(|e, window, cx| {
3741 e.manipulate_lines(window, cx, |lines| {
3742 lines.drain(..);
3743 })
3744 });
3745 cx.assert_editor_state(indoc! {"
3746 ˇ
3747 "});
3748}
3749
3750#[gpui::test]
3751async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
3752 init_test(cx, |_| {});
3753
3754 let mut cx = EditorTestContext::new(cx).await;
3755
3756 // Consider continuous selection as single selection
3757 cx.set_state(indoc! {"
3758 Aaa«aa
3759 cˇ»c«c
3760 bb
3761 aaaˇ»aa
3762 "});
3763 cx.update_editor(|e, window, cx| {
3764 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3765 });
3766 cx.assert_editor_state(indoc! {"
3767 «Aaaaa
3768 ccc
3769 bb
3770 aaaaaˇ»
3771 "});
3772
3773 cx.set_state(indoc! {"
3774 Aaa«aa
3775 cˇ»c«c
3776 bb
3777 aaaˇ»aa
3778 "});
3779 cx.update_editor(|e, window, cx| {
3780 e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
3781 });
3782 cx.assert_editor_state(indoc! {"
3783 «Aaaaa
3784 ccc
3785 bbˇ»
3786 "});
3787
3788 // Consider non continuous selection as distinct dedup operations
3789 cx.set_state(indoc! {"
3790 «aaaaa
3791 bb
3792 aaaaa
3793 aaaaaˇ»
3794
3795 aaa«aaˇ»
3796 "});
3797 cx.update_editor(|e, window, cx| {
3798 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3799 });
3800 cx.assert_editor_state(indoc! {"
3801 «aaaaa
3802 bbˇ»
3803
3804 «aaaaaˇ»
3805 "});
3806}
3807
3808#[gpui::test]
3809async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
3810 init_test(cx, |_| {});
3811
3812 let mut cx = EditorTestContext::new(cx).await;
3813
3814 cx.set_state(indoc! {"
3815 «Aaa
3816 aAa
3817 Aaaˇ»
3818 "});
3819 cx.update_editor(|e, window, cx| {
3820 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3821 });
3822 cx.assert_editor_state(indoc! {"
3823 «Aaa
3824 aAaˇ»
3825 "});
3826
3827 cx.set_state(indoc! {"
3828 «Aaa
3829 aAa
3830 aaAˇ»
3831 "});
3832 cx.update_editor(|e, window, cx| {
3833 e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
3834 });
3835 cx.assert_editor_state(indoc! {"
3836 «Aaaˇ»
3837 "});
3838}
3839
3840#[gpui::test]
3841async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
3842 init_test(cx, |_| {});
3843
3844 let mut cx = EditorTestContext::new(cx).await;
3845
3846 // Manipulate with multiple selections on a single line
3847 cx.set_state(indoc! {"
3848 dd«dd
3849 cˇ»c«c
3850 bb
3851 aaaˇ»aa
3852 "});
3853 cx.update_editor(|e, window, cx| {
3854 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3855 });
3856 cx.assert_editor_state(indoc! {"
3857 «aaaaa
3858 bb
3859 ccc
3860 ddddˇ»
3861 "});
3862
3863 // Manipulate with multiple disjoin selections
3864 cx.set_state(indoc! {"
3865 5«
3866 4
3867 3
3868 2
3869 1ˇ»
3870
3871 dd«dd
3872 ccc
3873 bb
3874 aaaˇ»aa
3875 "});
3876 cx.update_editor(|e, window, cx| {
3877 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3878 });
3879 cx.assert_editor_state(indoc! {"
3880 «1
3881 2
3882 3
3883 4
3884 5ˇ»
3885
3886 «aaaaa
3887 bb
3888 ccc
3889 ddddˇ»
3890 "});
3891
3892 // Adding lines on each selection
3893 cx.set_state(indoc! {"
3894 2«
3895 1ˇ»
3896
3897 bb«bb
3898 aaaˇ»aa
3899 "});
3900 cx.update_editor(|e, window, cx| {
3901 e.manipulate_lines(window, cx, |lines| lines.push("added line"))
3902 });
3903 cx.assert_editor_state(indoc! {"
3904 «2
3905 1
3906 added lineˇ»
3907
3908 «bbbb
3909 aaaaa
3910 added lineˇ»
3911 "});
3912
3913 // Removing lines on each selection
3914 cx.set_state(indoc! {"
3915 2«
3916 1ˇ»
3917
3918 bb«bb
3919 aaaˇ»aa
3920 "});
3921 cx.update_editor(|e, window, cx| {
3922 e.manipulate_lines(window, cx, |lines| {
3923 lines.pop();
3924 })
3925 });
3926 cx.assert_editor_state(indoc! {"
3927 «2ˇ»
3928
3929 «bbbbˇ»
3930 "});
3931}
3932
3933#[gpui::test]
3934async fn test_toggle_case(cx: &mut TestAppContext) {
3935 init_test(cx, |_| {});
3936
3937 let mut cx = EditorTestContext::new(cx).await;
3938
3939 // If all lower case -> upper case
3940 cx.set_state(indoc! {"
3941 «hello worldˇ»
3942 "});
3943 cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
3944 cx.assert_editor_state(indoc! {"
3945 «HELLO WORLDˇ»
3946 "});
3947
3948 // If all upper case -> lower case
3949 cx.set_state(indoc! {"
3950 «HELLO WORLDˇ»
3951 "});
3952 cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
3953 cx.assert_editor_state(indoc! {"
3954 «hello worldˇ»
3955 "});
3956
3957 // If any upper case characters are identified -> lower case
3958 // This matches JetBrains IDEs
3959 cx.set_state(indoc! {"
3960 «hEllo worldˇ»
3961 "});
3962 cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
3963 cx.assert_editor_state(indoc! {"
3964 «hello worldˇ»
3965 "});
3966}
3967
3968#[gpui::test]
3969async fn test_manipulate_text(cx: &mut TestAppContext) {
3970 init_test(cx, |_| {});
3971
3972 let mut cx = EditorTestContext::new(cx).await;
3973
3974 // Test convert_to_upper_case()
3975 cx.set_state(indoc! {"
3976 «hello worldˇ»
3977 "});
3978 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3979 cx.assert_editor_state(indoc! {"
3980 «HELLO WORLDˇ»
3981 "});
3982
3983 // Test convert_to_lower_case()
3984 cx.set_state(indoc! {"
3985 «HELLO WORLDˇ»
3986 "});
3987 cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
3988 cx.assert_editor_state(indoc! {"
3989 «hello worldˇ»
3990 "});
3991
3992 // Test multiple line, single selection case
3993 cx.set_state(indoc! {"
3994 «The quick brown
3995 fox jumps over
3996 the lazy dogˇ»
3997 "});
3998 cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
3999 cx.assert_editor_state(indoc! {"
4000 «The Quick Brown
4001 Fox Jumps Over
4002 The Lazy Dogˇ»
4003 "});
4004
4005 // Test multiple line, single selection case
4006 cx.set_state(indoc! {"
4007 «The quick brown
4008 fox jumps over
4009 the lazy dogˇ»
4010 "});
4011 cx.update_editor(|e, window, cx| {
4012 e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
4013 });
4014 cx.assert_editor_state(indoc! {"
4015 «TheQuickBrown
4016 FoxJumpsOver
4017 TheLazyDogˇ»
4018 "});
4019
4020 // From here on out, test more complex cases of manipulate_text()
4021
4022 // Test no selection case - should affect words cursors are in
4023 // Cursor at beginning, middle, and end of word
4024 cx.set_state(indoc! {"
4025 ˇhello big beauˇtiful worldˇ
4026 "});
4027 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
4028 cx.assert_editor_state(indoc! {"
4029 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
4030 "});
4031
4032 // Test multiple selections on a single line and across multiple lines
4033 cx.set_state(indoc! {"
4034 «Theˇ» quick «brown
4035 foxˇ» jumps «overˇ»
4036 the «lazyˇ» dog
4037 "});
4038 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
4039 cx.assert_editor_state(indoc! {"
4040 «THEˇ» quick «BROWN
4041 FOXˇ» jumps «OVERˇ»
4042 the «LAZYˇ» dog
4043 "});
4044
4045 // Test case where text length grows
4046 cx.set_state(indoc! {"
4047 «tschüߡ»
4048 "});
4049 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
4050 cx.assert_editor_state(indoc! {"
4051 «TSCHÜSSˇ»
4052 "});
4053
4054 // Test to make sure we don't crash when text shrinks
4055 cx.set_state(indoc! {"
4056 aaa_bbbˇ
4057 "});
4058 cx.update_editor(|e, window, cx| {
4059 e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
4060 });
4061 cx.assert_editor_state(indoc! {"
4062 «aaaBbbˇ»
4063 "});
4064
4065 // Test to make sure we all aware of the fact that each word can grow and shrink
4066 // Final selections should be aware of this fact
4067 cx.set_state(indoc! {"
4068 aaa_bˇbb bbˇb_ccc ˇccc_ddd
4069 "});
4070 cx.update_editor(|e, window, cx| {
4071 e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
4072 });
4073 cx.assert_editor_state(indoc! {"
4074 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
4075 "});
4076
4077 cx.set_state(indoc! {"
4078 «hElLo, WoRld!ˇ»
4079 "});
4080 cx.update_editor(|e, window, cx| {
4081 e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
4082 });
4083 cx.assert_editor_state(indoc! {"
4084 «HeLlO, wOrLD!ˇ»
4085 "});
4086}
4087
4088#[gpui::test]
4089fn test_duplicate_line(cx: &mut TestAppContext) {
4090 init_test(cx, |_| {});
4091
4092 let editor = cx.add_window(|window, cx| {
4093 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4094 build_editor(buffer, window, cx)
4095 });
4096 _ = editor.update(cx, |editor, window, cx| {
4097 editor.change_selections(None, window, cx, |s| {
4098 s.select_display_ranges([
4099 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4100 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4101 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4102 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
4103 ])
4104 });
4105 editor.duplicate_line_down(&DuplicateLineDown, window, cx);
4106 assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
4107 assert_eq!(
4108 editor.selections.display_ranges(cx),
4109 vec![
4110 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
4111 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
4112 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
4113 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
4114 ]
4115 );
4116 });
4117
4118 let editor = cx.add_window(|window, cx| {
4119 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4120 build_editor(buffer, window, cx)
4121 });
4122 _ = editor.update(cx, |editor, window, cx| {
4123 editor.change_selections(None, window, cx, |s| {
4124 s.select_display_ranges([
4125 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4126 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4127 ])
4128 });
4129 editor.duplicate_line_down(&DuplicateLineDown, window, cx);
4130 assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
4131 assert_eq!(
4132 editor.selections.display_ranges(cx),
4133 vec![
4134 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
4135 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
4136 ]
4137 );
4138 });
4139
4140 // With `move_upwards` the selections stay in place, except for
4141 // the lines inserted above them
4142 let editor = cx.add_window(|window, cx| {
4143 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4144 build_editor(buffer, window, cx)
4145 });
4146 _ = editor.update(cx, |editor, window, cx| {
4147 editor.change_selections(None, window, cx, |s| {
4148 s.select_display_ranges([
4149 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4150 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4151 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4152 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
4153 ])
4154 });
4155 editor.duplicate_line_up(&DuplicateLineUp, window, cx);
4156 assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
4157 assert_eq!(
4158 editor.selections.display_ranges(cx),
4159 vec![
4160 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4161 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4162 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
4163 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
4164 ]
4165 );
4166 });
4167
4168 let editor = cx.add_window(|window, cx| {
4169 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4170 build_editor(buffer, window, cx)
4171 });
4172 _ = editor.update(cx, |editor, window, cx| {
4173 editor.change_selections(None, window, cx, |s| {
4174 s.select_display_ranges([
4175 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4176 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4177 ])
4178 });
4179 editor.duplicate_line_up(&DuplicateLineUp, window, cx);
4180 assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
4181 assert_eq!(
4182 editor.selections.display_ranges(cx),
4183 vec![
4184 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4185 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4186 ]
4187 );
4188 });
4189
4190 let editor = cx.add_window(|window, cx| {
4191 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4192 build_editor(buffer, window, cx)
4193 });
4194 _ = editor.update(cx, |editor, window, cx| {
4195 editor.change_selections(None, window, cx, |s| {
4196 s.select_display_ranges([
4197 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4198 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4199 ])
4200 });
4201 editor.duplicate_selection(&DuplicateSelection, window, cx);
4202 assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
4203 assert_eq!(
4204 editor.selections.display_ranges(cx),
4205 vec![
4206 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4207 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
4208 ]
4209 );
4210 });
4211}
4212
4213#[gpui::test]
4214fn test_move_line_up_down(cx: &mut TestAppContext) {
4215 init_test(cx, |_| {});
4216
4217 let editor = cx.add_window(|window, cx| {
4218 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4219 build_editor(buffer, window, cx)
4220 });
4221 _ = editor.update(cx, |editor, window, cx| {
4222 editor.fold_creases(
4223 vec![
4224 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4225 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4226 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4227 ],
4228 true,
4229 window,
4230 cx,
4231 );
4232 editor.change_selections(None, window, cx, |s| {
4233 s.select_display_ranges([
4234 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4235 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4236 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4237 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
4238 ])
4239 });
4240 assert_eq!(
4241 editor.display_text(cx),
4242 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
4243 );
4244
4245 editor.move_line_up(&MoveLineUp, window, cx);
4246 assert_eq!(
4247 editor.display_text(cx),
4248 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
4249 );
4250 assert_eq!(
4251 editor.selections.display_ranges(cx),
4252 vec![
4253 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4254 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4255 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
4256 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
4257 ]
4258 );
4259 });
4260
4261 _ = editor.update(cx, |editor, window, cx| {
4262 editor.move_line_down(&MoveLineDown, window, cx);
4263 assert_eq!(
4264 editor.display_text(cx),
4265 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
4266 );
4267 assert_eq!(
4268 editor.selections.display_ranges(cx),
4269 vec![
4270 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
4271 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4272 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4273 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
4274 ]
4275 );
4276 });
4277
4278 _ = editor.update(cx, |editor, window, cx| {
4279 editor.move_line_down(&MoveLineDown, window, cx);
4280 assert_eq!(
4281 editor.display_text(cx),
4282 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
4283 );
4284 assert_eq!(
4285 editor.selections.display_ranges(cx),
4286 vec![
4287 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4288 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4289 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4290 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
4291 ]
4292 );
4293 });
4294
4295 _ = editor.update(cx, |editor, window, cx| {
4296 editor.move_line_up(&MoveLineUp, window, cx);
4297 assert_eq!(
4298 editor.display_text(cx),
4299 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
4300 );
4301 assert_eq!(
4302 editor.selections.display_ranges(cx),
4303 vec![
4304 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
4305 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4306 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
4307 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
4308 ]
4309 );
4310 });
4311}
4312
4313#[gpui::test]
4314fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
4315 init_test(cx, |_| {});
4316
4317 let editor = cx.add_window(|window, cx| {
4318 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4319 build_editor(buffer, window, cx)
4320 });
4321 _ = editor.update(cx, |editor, window, cx| {
4322 let snapshot = editor.buffer.read(cx).snapshot(cx);
4323 editor.insert_blocks(
4324 [BlockProperties {
4325 style: BlockStyle::Fixed,
4326 placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
4327 height: Some(1),
4328 render: Arc::new(|_| div().into_any()),
4329 priority: 0,
4330 render_in_minimap: true,
4331 }],
4332 Some(Autoscroll::fit()),
4333 cx,
4334 );
4335 editor.change_selections(None, window, cx, |s| {
4336 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
4337 });
4338 editor.move_line_down(&MoveLineDown, window, cx);
4339 });
4340}
4341
4342#[gpui::test]
4343async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
4344 init_test(cx, |_| {});
4345
4346 let mut cx = EditorTestContext::new(cx).await;
4347 cx.set_state(
4348 &"
4349 ˇzero
4350 one
4351 two
4352 three
4353 four
4354 five
4355 "
4356 .unindent(),
4357 );
4358
4359 // Create a four-line block that replaces three lines of text.
4360 cx.update_editor(|editor, window, cx| {
4361 let snapshot = editor.snapshot(window, cx);
4362 let snapshot = &snapshot.buffer_snapshot;
4363 let placement = BlockPlacement::Replace(
4364 snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
4365 );
4366 editor.insert_blocks(
4367 [BlockProperties {
4368 placement,
4369 height: Some(4),
4370 style: BlockStyle::Sticky,
4371 render: Arc::new(|_| gpui::div().into_any_element()),
4372 priority: 0,
4373 render_in_minimap: true,
4374 }],
4375 None,
4376 cx,
4377 );
4378 });
4379
4380 // Move down so that the cursor touches the block.
4381 cx.update_editor(|editor, window, cx| {
4382 editor.move_down(&Default::default(), window, cx);
4383 });
4384 cx.assert_editor_state(
4385 &"
4386 zero
4387 «one
4388 two
4389 threeˇ»
4390 four
4391 five
4392 "
4393 .unindent(),
4394 );
4395
4396 // Move down past the block.
4397 cx.update_editor(|editor, window, cx| {
4398 editor.move_down(&Default::default(), window, cx);
4399 });
4400 cx.assert_editor_state(
4401 &"
4402 zero
4403 one
4404 two
4405 three
4406 ˇfour
4407 five
4408 "
4409 .unindent(),
4410 );
4411}
4412
4413#[gpui::test]
4414fn test_transpose(cx: &mut TestAppContext) {
4415 init_test(cx, |_| {});
4416
4417 _ = cx.add_window(|window, cx| {
4418 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
4419 editor.set_style(EditorStyle::default(), window, cx);
4420 editor.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
4421 editor.transpose(&Default::default(), window, cx);
4422 assert_eq!(editor.text(cx), "bac");
4423 assert_eq!(editor.selections.ranges(cx), [2..2]);
4424
4425 editor.transpose(&Default::default(), window, cx);
4426 assert_eq!(editor.text(cx), "bca");
4427 assert_eq!(editor.selections.ranges(cx), [3..3]);
4428
4429 editor.transpose(&Default::default(), window, cx);
4430 assert_eq!(editor.text(cx), "bac");
4431 assert_eq!(editor.selections.ranges(cx), [3..3]);
4432
4433 editor
4434 });
4435
4436 _ = cx.add_window(|window, cx| {
4437 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
4438 editor.set_style(EditorStyle::default(), window, cx);
4439 editor.change_selections(None, window, cx, |s| s.select_ranges([3..3]));
4440 editor.transpose(&Default::default(), window, cx);
4441 assert_eq!(editor.text(cx), "acb\nde");
4442 assert_eq!(editor.selections.ranges(cx), [3..3]);
4443
4444 editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
4445 editor.transpose(&Default::default(), window, cx);
4446 assert_eq!(editor.text(cx), "acbd\ne");
4447 assert_eq!(editor.selections.ranges(cx), [5..5]);
4448
4449 editor.transpose(&Default::default(), window, cx);
4450 assert_eq!(editor.text(cx), "acbde\n");
4451 assert_eq!(editor.selections.ranges(cx), [6..6]);
4452
4453 editor.transpose(&Default::default(), window, cx);
4454 assert_eq!(editor.text(cx), "acbd\ne");
4455 assert_eq!(editor.selections.ranges(cx), [6..6]);
4456
4457 editor
4458 });
4459
4460 _ = cx.add_window(|window, cx| {
4461 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
4462 editor.set_style(EditorStyle::default(), window, cx);
4463 editor.change_selections(None, window, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
4464 editor.transpose(&Default::default(), window, cx);
4465 assert_eq!(editor.text(cx), "bacd\ne");
4466 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
4467
4468 editor.transpose(&Default::default(), window, cx);
4469 assert_eq!(editor.text(cx), "bcade\n");
4470 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
4471
4472 editor.transpose(&Default::default(), window, cx);
4473 assert_eq!(editor.text(cx), "bcda\ne");
4474 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4475
4476 editor.transpose(&Default::default(), window, cx);
4477 assert_eq!(editor.text(cx), "bcade\n");
4478 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4479
4480 editor.transpose(&Default::default(), window, cx);
4481 assert_eq!(editor.text(cx), "bcaed\n");
4482 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
4483
4484 editor
4485 });
4486
4487 _ = cx.add_window(|window, cx| {
4488 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
4489 editor.set_style(EditorStyle::default(), window, cx);
4490 editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
4491 editor.transpose(&Default::default(), window, cx);
4492 assert_eq!(editor.text(cx), "🏀🍐✋");
4493 assert_eq!(editor.selections.ranges(cx), [8..8]);
4494
4495 editor.transpose(&Default::default(), window, cx);
4496 assert_eq!(editor.text(cx), "🏀✋🍐");
4497 assert_eq!(editor.selections.ranges(cx), [11..11]);
4498
4499 editor.transpose(&Default::default(), window, cx);
4500 assert_eq!(editor.text(cx), "🏀🍐✋");
4501 assert_eq!(editor.selections.ranges(cx), [11..11]);
4502
4503 editor
4504 });
4505}
4506
4507#[gpui::test]
4508async fn test_rewrap(cx: &mut TestAppContext) {
4509 init_test(cx, |settings| {
4510 settings.languages.extend([
4511 (
4512 "Markdown".into(),
4513 LanguageSettingsContent {
4514 allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
4515 ..Default::default()
4516 },
4517 ),
4518 (
4519 "Plain Text".into(),
4520 LanguageSettingsContent {
4521 allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
4522 ..Default::default()
4523 },
4524 ),
4525 ])
4526 });
4527
4528 let mut cx = EditorTestContext::new(cx).await;
4529
4530 let language_with_c_comments = Arc::new(Language::new(
4531 LanguageConfig {
4532 line_comments: vec!["// ".into()],
4533 ..LanguageConfig::default()
4534 },
4535 None,
4536 ));
4537 let language_with_pound_comments = Arc::new(Language::new(
4538 LanguageConfig {
4539 line_comments: vec!["# ".into()],
4540 ..LanguageConfig::default()
4541 },
4542 None,
4543 ));
4544 let markdown_language = Arc::new(Language::new(
4545 LanguageConfig {
4546 name: "Markdown".into(),
4547 ..LanguageConfig::default()
4548 },
4549 None,
4550 ));
4551 let language_with_doc_comments = Arc::new(Language::new(
4552 LanguageConfig {
4553 line_comments: vec!["// ".into(), "/// ".into()],
4554 ..LanguageConfig::default()
4555 },
4556 Some(tree_sitter_rust::LANGUAGE.into()),
4557 ));
4558
4559 let plaintext_language = Arc::new(Language::new(
4560 LanguageConfig {
4561 name: "Plain Text".into(),
4562 ..LanguageConfig::default()
4563 },
4564 None,
4565 ));
4566
4567 assert_rewrap(
4568 indoc! {"
4569 // ˇ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.
4570 "},
4571 indoc! {"
4572 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4573 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4574 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4575 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4576 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4577 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4578 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4579 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4580 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4581 // porttitor id. Aliquam id accumsan eros.
4582 "},
4583 language_with_c_comments.clone(),
4584 &mut cx,
4585 );
4586
4587 // Test that rewrapping works inside of a selection
4588 assert_rewrap(
4589 indoc! {"
4590 «// 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.ˇ»
4591 "},
4592 indoc! {"
4593 «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4594 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4595 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4596 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4597 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4598 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4599 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4600 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4601 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4602 // porttitor id. Aliquam id accumsan eros.ˇ»
4603 "},
4604 language_with_c_comments.clone(),
4605 &mut cx,
4606 );
4607
4608 // Test that cursors that expand to the same region are collapsed.
4609 assert_rewrap(
4610 indoc! {"
4611 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4612 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4613 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4614 // ˇ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.
4615 "},
4616 indoc! {"
4617 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4618 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4619 // auctor, eu lacinia sapien scelerisque. ˇVivamus sit amet neque et quam
4620 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4621 // Pellentesque odio lectus, iaculis ac volutpat et, ˇblandit quis urna. Sed
4622 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4623 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4624 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4625 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4626 // porttitor id. Aliquam id accumsan eros.
4627 "},
4628 language_with_c_comments.clone(),
4629 &mut cx,
4630 );
4631
4632 // Test that non-contiguous selections are treated separately.
4633 assert_rewrap(
4634 indoc! {"
4635 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4636 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4637 //
4638 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4639 // ˇ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.
4640 "},
4641 indoc! {"
4642 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4643 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4644 // auctor, eu lacinia sapien scelerisque.
4645 //
4646 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
4647 // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4648 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
4649 // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
4650 // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
4651 // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
4652 // vulputate turpis porttitor id. Aliquam id accumsan eros.
4653 "},
4654 language_with_c_comments.clone(),
4655 &mut cx,
4656 );
4657
4658 // Test that different comment prefixes are supported.
4659 assert_rewrap(
4660 indoc! {"
4661 # ˇ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.
4662 "},
4663 indoc! {"
4664 # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4665 # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4666 # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4667 # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4668 # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
4669 # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
4670 # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
4671 # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
4672 # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
4673 # accumsan eros.
4674 "},
4675 language_with_pound_comments.clone(),
4676 &mut cx,
4677 );
4678
4679 // Test that rewrapping is ignored outside of comments in most languages.
4680 assert_rewrap(
4681 indoc! {"
4682 /// Adds two numbers.
4683 /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4684 fn add(a: u32, b: u32) -> u32 {
4685 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ˇ
4686 }
4687 "},
4688 indoc! {"
4689 /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
4690 /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4691 fn add(a: u32, b: u32) -> u32 {
4692 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ˇ
4693 }
4694 "},
4695 language_with_doc_comments.clone(),
4696 &mut cx,
4697 );
4698
4699 // Test that rewrapping works in Markdown and Plain Text languages.
4700 assert_rewrap(
4701 indoc! {"
4702 # Hello
4703
4704 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.
4705 "},
4706 indoc! {"
4707 # Hello
4708
4709 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4710 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4711 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4712 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4713 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4714 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4715 Integer sit amet scelerisque nisi.
4716 "},
4717 markdown_language,
4718 &mut cx,
4719 );
4720
4721 assert_rewrap(
4722 indoc! {"
4723 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.
4724 "},
4725 indoc! {"
4726 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4727 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4728 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4729 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4730 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4731 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4732 Integer sit amet scelerisque nisi.
4733 "},
4734 plaintext_language,
4735 &mut cx,
4736 );
4737
4738 // Test rewrapping unaligned comments in a selection.
4739 assert_rewrap(
4740 indoc! {"
4741 fn foo() {
4742 if true {
4743 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4744 // Praesent semper egestas tellus id dignissim.ˇ»
4745 do_something();
4746 } else {
4747 //
4748 }
4749 }
4750 "},
4751 indoc! {"
4752 fn foo() {
4753 if true {
4754 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4755 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4756 // egestas tellus id dignissim.ˇ»
4757 do_something();
4758 } else {
4759 //
4760 }
4761 }
4762 "},
4763 language_with_doc_comments.clone(),
4764 &mut cx,
4765 );
4766
4767 assert_rewrap(
4768 indoc! {"
4769 fn foo() {
4770 if true {
4771 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4772 // Praesent semper egestas tellus id dignissim.»
4773 do_something();
4774 } else {
4775 //
4776 }
4777
4778 }
4779 "},
4780 indoc! {"
4781 fn foo() {
4782 if true {
4783 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4784 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4785 // egestas tellus id dignissim.»
4786 do_something();
4787 } else {
4788 //
4789 }
4790
4791 }
4792 "},
4793 language_with_doc_comments.clone(),
4794 &mut cx,
4795 );
4796
4797 #[track_caller]
4798 fn assert_rewrap(
4799 unwrapped_text: &str,
4800 wrapped_text: &str,
4801 language: Arc<Language>,
4802 cx: &mut EditorTestContext,
4803 ) {
4804 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4805 cx.set_state(unwrapped_text);
4806 cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
4807 cx.assert_editor_state(wrapped_text);
4808 }
4809}
4810
4811#[gpui::test]
4812async fn test_hard_wrap(cx: &mut TestAppContext) {
4813 init_test(cx, |_| {});
4814 let mut cx = EditorTestContext::new(cx).await;
4815
4816 cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
4817 cx.update_editor(|editor, _, cx| {
4818 editor.set_hard_wrap(Some(14), cx);
4819 });
4820
4821 cx.set_state(indoc!(
4822 "
4823 one two three ˇ
4824 "
4825 ));
4826 cx.simulate_input("four");
4827 cx.run_until_parked();
4828
4829 cx.assert_editor_state(indoc!(
4830 "
4831 one two three
4832 fourˇ
4833 "
4834 ));
4835
4836 cx.update_editor(|editor, window, cx| {
4837 editor.newline(&Default::default(), window, cx);
4838 });
4839 cx.run_until_parked();
4840 cx.assert_editor_state(indoc!(
4841 "
4842 one two three
4843 four
4844 ˇ
4845 "
4846 ));
4847
4848 cx.simulate_input("five");
4849 cx.run_until_parked();
4850 cx.assert_editor_state(indoc!(
4851 "
4852 one two three
4853 four
4854 fiveˇ
4855 "
4856 ));
4857
4858 cx.update_editor(|editor, window, cx| {
4859 editor.newline(&Default::default(), window, cx);
4860 });
4861 cx.run_until_parked();
4862 cx.simulate_input("# ");
4863 cx.run_until_parked();
4864 cx.assert_editor_state(indoc!(
4865 "
4866 one two three
4867 four
4868 five
4869 # ˇ
4870 "
4871 ));
4872
4873 cx.update_editor(|editor, window, cx| {
4874 editor.newline(&Default::default(), window, cx);
4875 });
4876 cx.run_until_parked();
4877 cx.assert_editor_state(indoc!(
4878 "
4879 one two three
4880 four
4881 five
4882 #\x20
4883 #ˇ
4884 "
4885 ));
4886
4887 cx.simulate_input(" 6");
4888 cx.run_until_parked();
4889 cx.assert_editor_state(indoc!(
4890 "
4891 one two three
4892 four
4893 five
4894 #
4895 # 6ˇ
4896 "
4897 ));
4898}
4899
4900#[gpui::test]
4901async fn test_clipboard(cx: &mut TestAppContext) {
4902 init_test(cx, |_| {});
4903
4904 let mut cx = EditorTestContext::new(cx).await;
4905
4906 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
4907 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4908 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
4909
4910 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
4911 cx.set_state("two ˇfour ˇsix ˇ");
4912 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4913 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
4914
4915 // Paste again but with only two cursors. Since the number of cursors doesn't
4916 // match the number of slices in the clipboard, the entire clipboard text
4917 // is pasted at each cursor.
4918 cx.set_state("ˇtwo one✅ four three six five ˇ");
4919 cx.update_editor(|e, window, cx| {
4920 e.handle_input("( ", window, cx);
4921 e.paste(&Paste, window, cx);
4922 e.handle_input(") ", window, cx);
4923 });
4924 cx.assert_editor_state(
4925 &([
4926 "( one✅ ",
4927 "three ",
4928 "five ) ˇtwo one✅ four three six five ( one✅ ",
4929 "three ",
4930 "five ) ˇ",
4931 ]
4932 .join("\n")),
4933 );
4934
4935 // Cut with three selections, one of which is full-line.
4936 cx.set_state(indoc! {"
4937 1«2ˇ»3
4938 4ˇ567
4939 «8ˇ»9"});
4940 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4941 cx.assert_editor_state(indoc! {"
4942 1ˇ3
4943 ˇ9"});
4944
4945 // Paste with three selections, noticing how the copied selection that was full-line
4946 // gets inserted before the second cursor.
4947 cx.set_state(indoc! {"
4948 1ˇ3
4949 9ˇ
4950 «oˇ»ne"});
4951 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4952 cx.assert_editor_state(indoc! {"
4953 12ˇ3
4954 4567
4955 9ˇ
4956 8ˇne"});
4957
4958 // Copy with a single cursor only, which writes the whole line into the clipboard.
4959 cx.set_state(indoc! {"
4960 The quick brown
4961 fox juˇmps over
4962 the lazy dog"});
4963 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
4964 assert_eq!(
4965 cx.read_from_clipboard()
4966 .and_then(|item| item.text().as_deref().map(str::to_string)),
4967 Some("fox jumps over\n".to_string())
4968 );
4969
4970 // Paste with three selections, noticing how the copied full-line selection is inserted
4971 // before the empty selections but replaces the selection that is non-empty.
4972 cx.set_state(indoc! {"
4973 Tˇhe quick brown
4974 «foˇ»x jumps over
4975 tˇhe lazy dog"});
4976 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4977 cx.assert_editor_state(indoc! {"
4978 fox jumps over
4979 Tˇhe quick brown
4980 fox jumps over
4981 ˇx jumps over
4982 fox jumps over
4983 tˇhe lazy dog"});
4984}
4985
4986#[gpui::test]
4987async fn test_copy_trim(cx: &mut TestAppContext) {
4988 init_test(cx, |_| {});
4989
4990 let mut cx = EditorTestContext::new(cx).await;
4991 cx.set_state(
4992 r#" «for selection in selections.iter() {
4993 let mut start = selection.start;
4994 let mut end = selection.end;
4995 let is_entire_line = selection.is_empty();
4996 if is_entire_line {
4997 start = Point::new(start.row, 0);ˇ»
4998 end = cmp::min(max_point, Point::new(end.row + 1, 0));
4999 }
5000 "#,
5001 );
5002 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
5003 assert_eq!(
5004 cx.read_from_clipboard()
5005 .and_then(|item| item.text().as_deref().map(str::to_string)),
5006 Some(
5007 "for selection in selections.iter() {
5008 let mut start = selection.start;
5009 let mut end = selection.end;
5010 let is_entire_line = selection.is_empty();
5011 if is_entire_line {
5012 start = Point::new(start.row, 0);"
5013 .to_string()
5014 ),
5015 "Regular copying preserves all indentation selected",
5016 );
5017 cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
5018 assert_eq!(
5019 cx.read_from_clipboard()
5020 .and_then(|item| item.text().as_deref().map(str::to_string)),
5021 Some(
5022 "for selection in selections.iter() {
5023let mut start = selection.start;
5024let mut end = selection.end;
5025let is_entire_line = selection.is_empty();
5026if is_entire_line {
5027 start = Point::new(start.row, 0);"
5028 .to_string()
5029 ),
5030 "Copying with stripping should strip all leading whitespaces"
5031 );
5032
5033 cx.set_state(
5034 r#" « for selection in selections.iter() {
5035 let mut start = selection.start;
5036 let mut end = selection.end;
5037 let is_entire_line = selection.is_empty();
5038 if is_entire_line {
5039 start = Point::new(start.row, 0);ˇ»
5040 end = cmp::min(max_point, Point::new(end.row + 1, 0));
5041 }
5042 "#,
5043 );
5044 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
5045 assert_eq!(
5046 cx.read_from_clipboard()
5047 .and_then(|item| item.text().as_deref().map(str::to_string)),
5048 Some(
5049 " for selection in selections.iter() {
5050 let mut start = selection.start;
5051 let mut end = selection.end;
5052 let is_entire_line = selection.is_empty();
5053 if is_entire_line {
5054 start = Point::new(start.row, 0);"
5055 .to_string()
5056 ),
5057 "Regular copying preserves all indentation selected",
5058 );
5059 cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
5060 assert_eq!(
5061 cx.read_from_clipboard()
5062 .and_then(|item| item.text().as_deref().map(str::to_string)),
5063 Some(
5064 "for selection in selections.iter() {
5065let mut start = selection.start;
5066let mut end = selection.end;
5067let is_entire_line = selection.is_empty();
5068if is_entire_line {
5069 start = Point::new(start.row, 0);"
5070 .to_string()
5071 ),
5072 "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
5073 );
5074
5075 cx.set_state(
5076 r#" «ˇ for selection in selections.iter() {
5077 let mut start = selection.start;
5078 let mut end = selection.end;
5079 let is_entire_line = selection.is_empty();
5080 if is_entire_line {
5081 start = Point::new(start.row, 0);»
5082 end = cmp::min(max_point, Point::new(end.row + 1, 0));
5083 }
5084 "#,
5085 );
5086 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
5087 assert_eq!(
5088 cx.read_from_clipboard()
5089 .and_then(|item| item.text().as_deref().map(str::to_string)),
5090 Some(
5091 " for selection in selections.iter() {
5092 let mut start = selection.start;
5093 let mut end = selection.end;
5094 let is_entire_line = selection.is_empty();
5095 if is_entire_line {
5096 start = Point::new(start.row, 0);"
5097 .to_string()
5098 ),
5099 "Regular copying for reverse selection works the same",
5100 );
5101 cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
5102 assert_eq!(
5103 cx.read_from_clipboard()
5104 .and_then(|item| item.text().as_deref().map(str::to_string)),
5105 Some(
5106 "for selection in selections.iter() {
5107let mut start = selection.start;
5108let mut end = selection.end;
5109let is_entire_line = selection.is_empty();
5110if is_entire_line {
5111 start = Point::new(start.row, 0);"
5112 .to_string()
5113 ),
5114 "Copying with stripping for reverse selection works the same"
5115 );
5116
5117 cx.set_state(
5118 r#" for selection «in selections.iter() {
5119 let mut start = selection.start;
5120 let mut end = selection.end;
5121 let is_entire_line = selection.is_empty();
5122 if is_entire_line {
5123 start = Point::new(start.row, 0);ˇ»
5124 end = cmp::min(max_point, Point::new(end.row + 1, 0));
5125 }
5126 "#,
5127 );
5128 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
5129 assert_eq!(
5130 cx.read_from_clipboard()
5131 .and_then(|item| item.text().as_deref().map(str::to_string)),
5132 Some(
5133 "in selections.iter() {
5134 let mut start = selection.start;
5135 let mut end = selection.end;
5136 let is_entire_line = selection.is_empty();
5137 if is_entire_line {
5138 start = Point::new(start.row, 0);"
5139 .to_string()
5140 ),
5141 "When selecting past the indent, the copying works as usual",
5142 );
5143 cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
5144 assert_eq!(
5145 cx.read_from_clipboard()
5146 .and_then(|item| item.text().as_deref().map(str::to_string)),
5147 Some(
5148 "in selections.iter() {
5149 let mut start = selection.start;
5150 let mut end = selection.end;
5151 let is_entire_line = selection.is_empty();
5152 if is_entire_line {
5153 start = Point::new(start.row, 0);"
5154 .to_string()
5155 ),
5156 "When selecting past the indent, nothing is trimmed"
5157 );
5158
5159 cx.set_state(
5160 r#" «for selection in selections.iter() {
5161 let mut start = selection.start;
5162
5163 let mut end = selection.end;
5164 let is_entire_line = selection.is_empty();
5165 if is_entire_line {
5166 start = Point::new(start.row, 0);
5167ˇ» end = cmp::min(max_point, Point::new(end.row + 1, 0));
5168 }
5169 "#,
5170 );
5171 cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
5172 assert_eq!(
5173 cx.read_from_clipboard()
5174 .and_then(|item| item.text().as_deref().map(str::to_string)),
5175 Some(
5176 "for selection in selections.iter() {
5177let mut start = selection.start;
5178
5179let mut end = selection.end;
5180let is_entire_line = selection.is_empty();
5181if is_entire_line {
5182 start = Point::new(start.row, 0);
5183"
5184 .to_string()
5185 ),
5186 "Copying with stripping should ignore empty lines"
5187 );
5188}
5189
5190#[gpui::test]
5191async fn test_paste_multiline(cx: &mut TestAppContext) {
5192 init_test(cx, |_| {});
5193
5194 let mut cx = EditorTestContext::new(cx).await;
5195 cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
5196
5197 // Cut an indented block, without the leading whitespace.
5198 cx.set_state(indoc! {"
5199 const a: B = (
5200 c(),
5201 «d(
5202 e,
5203 f
5204 )ˇ»
5205 );
5206 "});
5207 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
5208 cx.assert_editor_state(indoc! {"
5209 const a: B = (
5210 c(),
5211 ˇ
5212 );
5213 "});
5214
5215 // Paste it at the same position.
5216 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
5217 cx.assert_editor_state(indoc! {"
5218 const a: B = (
5219 c(),
5220 d(
5221 e,
5222 f
5223 )ˇ
5224 );
5225 "});
5226
5227 // Paste it at a line with a lower indent level.
5228 cx.set_state(indoc! {"
5229 ˇ
5230 const a: B = (
5231 c(),
5232 );
5233 "});
5234 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
5235 cx.assert_editor_state(indoc! {"
5236 d(
5237 e,
5238 f
5239 )ˇ
5240 const a: B = (
5241 c(),
5242 );
5243 "});
5244
5245 // Cut an indented block, with the leading whitespace.
5246 cx.set_state(indoc! {"
5247 const a: B = (
5248 c(),
5249 « d(
5250 e,
5251 f
5252 )
5253 ˇ»);
5254 "});
5255 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
5256 cx.assert_editor_state(indoc! {"
5257 const a: B = (
5258 c(),
5259 ˇ);
5260 "});
5261
5262 // Paste it at the same position.
5263 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
5264 cx.assert_editor_state(indoc! {"
5265 const a: B = (
5266 c(),
5267 d(
5268 e,
5269 f
5270 )
5271 ˇ);
5272 "});
5273
5274 // Paste it at a line with a higher indent level.
5275 cx.set_state(indoc! {"
5276 const a: B = (
5277 c(),
5278 d(
5279 e,
5280 fˇ
5281 )
5282 );
5283 "});
5284 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
5285 cx.assert_editor_state(indoc! {"
5286 const a: B = (
5287 c(),
5288 d(
5289 e,
5290 f d(
5291 e,
5292 f
5293 )
5294 ˇ
5295 )
5296 );
5297 "});
5298
5299 // Copy an indented block, starting mid-line
5300 cx.set_state(indoc! {"
5301 const a: B = (
5302 c(),
5303 somethin«g(
5304 e,
5305 f
5306 )ˇ»
5307 );
5308 "});
5309 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
5310
5311 // Paste it on a line with a lower indent level
5312 cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
5313 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
5314 cx.assert_editor_state(indoc! {"
5315 const a: B = (
5316 c(),
5317 something(
5318 e,
5319 f
5320 )
5321 );
5322 g(
5323 e,
5324 f
5325 )ˇ"});
5326}
5327
5328#[gpui::test]
5329async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
5330 init_test(cx, |_| {});
5331
5332 cx.write_to_clipboard(ClipboardItem::new_string(
5333 " d(\n e\n );\n".into(),
5334 ));
5335
5336 let mut cx = EditorTestContext::new(cx).await;
5337 cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
5338
5339 cx.set_state(indoc! {"
5340 fn a() {
5341 b();
5342 if c() {
5343 ˇ
5344 }
5345 }
5346 "});
5347
5348 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
5349 cx.assert_editor_state(indoc! {"
5350 fn a() {
5351 b();
5352 if c() {
5353 d(
5354 e
5355 );
5356 ˇ
5357 }
5358 }
5359 "});
5360
5361 cx.set_state(indoc! {"
5362 fn a() {
5363 b();
5364 ˇ
5365 }
5366 "});
5367
5368 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
5369 cx.assert_editor_state(indoc! {"
5370 fn a() {
5371 b();
5372 d(
5373 e
5374 );
5375 ˇ
5376 }
5377 "});
5378}
5379
5380#[gpui::test]
5381fn test_select_all(cx: &mut TestAppContext) {
5382 init_test(cx, |_| {});
5383
5384 let editor = cx.add_window(|window, cx| {
5385 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
5386 build_editor(buffer, window, cx)
5387 });
5388 _ = editor.update(cx, |editor, window, cx| {
5389 editor.select_all(&SelectAll, window, cx);
5390 assert_eq!(
5391 editor.selections.display_ranges(cx),
5392 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
5393 );
5394 });
5395}
5396
5397#[gpui::test]
5398fn test_select_line(cx: &mut TestAppContext) {
5399 init_test(cx, |_| {});
5400
5401 let editor = cx.add_window(|window, cx| {
5402 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
5403 build_editor(buffer, window, cx)
5404 });
5405 _ = editor.update(cx, |editor, window, cx| {
5406 editor.change_selections(None, window, cx, |s| {
5407 s.select_display_ranges([
5408 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5409 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
5410 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
5411 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
5412 ])
5413 });
5414 editor.select_line(&SelectLine, window, cx);
5415 assert_eq!(
5416 editor.selections.display_ranges(cx),
5417 vec![
5418 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
5419 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
5420 ]
5421 );
5422 });
5423
5424 _ = editor.update(cx, |editor, window, cx| {
5425 editor.select_line(&SelectLine, window, cx);
5426 assert_eq!(
5427 editor.selections.display_ranges(cx),
5428 vec![
5429 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
5430 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
5431 ]
5432 );
5433 });
5434
5435 _ = editor.update(cx, |editor, window, cx| {
5436 editor.select_line(&SelectLine, window, cx);
5437 assert_eq!(
5438 editor.selections.display_ranges(cx),
5439 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
5440 );
5441 });
5442}
5443
5444#[gpui::test]
5445async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
5446 init_test(cx, |_| {});
5447 let mut cx = EditorTestContext::new(cx).await;
5448
5449 #[track_caller]
5450 fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
5451 cx.set_state(initial_state);
5452 cx.update_editor(|e, window, cx| {
5453 e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
5454 });
5455 cx.assert_editor_state(expected_state);
5456 }
5457
5458 // Selection starts and ends at the middle of lines, left-to-right
5459 test(
5460 &mut cx,
5461 "aa\nb«ˇb\ncc\ndd\ne»e\nff",
5462 "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
5463 );
5464 // Same thing, right-to-left
5465 test(
5466 &mut cx,
5467 "aa\nb«b\ncc\ndd\neˇ»e\nff",
5468 "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
5469 );
5470
5471 // Whole buffer, left-to-right, last line *doesn't* end with newline
5472 test(
5473 &mut cx,
5474 "«ˇaa\nbb\ncc\ndd\nee\nff»",
5475 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
5476 );
5477 // Same thing, right-to-left
5478 test(
5479 &mut cx,
5480 "«aa\nbb\ncc\ndd\nee\nffˇ»",
5481 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
5482 );
5483
5484 // Whole buffer, left-to-right, last line ends with newline
5485 test(
5486 &mut cx,
5487 "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
5488 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
5489 );
5490 // Same thing, right-to-left
5491 test(
5492 &mut cx,
5493 "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
5494 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
5495 );
5496
5497 // Starts at the end of a line, ends at the start of another
5498 test(
5499 &mut cx,
5500 "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
5501 "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
5502 );
5503}
5504
5505#[gpui::test]
5506async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
5507 init_test(cx, |_| {});
5508
5509 let editor = cx.add_window(|window, cx| {
5510 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
5511 build_editor(buffer, window, cx)
5512 });
5513
5514 // setup
5515 _ = editor.update(cx, |editor, window, cx| {
5516 editor.fold_creases(
5517 vec![
5518 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
5519 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
5520 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
5521 ],
5522 true,
5523 window,
5524 cx,
5525 );
5526 assert_eq!(
5527 editor.display_text(cx),
5528 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
5529 );
5530 });
5531
5532 _ = editor.update(cx, |editor, window, cx| {
5533 editor.change_selections(None, window, cx, |s| {
5534 s.select_display_ranges([
5535 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5536 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
5537 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
5538 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
5539 ])
5540 });
5541 editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
5542 assert_eq!(
5543 editor.display_text(cx),
5544 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
5545 );
5546 });
5547 EditorTestContext::for_editor(editor, cx)
5548 .await
5549 .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
5550
5551 _ = editor.update(cx, |editor, window, cx| {
5552 editor.change_selections(None, window, cx, |s| {
5553 s.select_display_ranges([
5554 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
5555 ])
5556 });
5557 editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
5558 assert_eq!(
5559 editor.display_text(cx),
5560 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
5561 );
5562 assert_eq!(
5563 editor.selections.display_ranges(cx),
5564 [
5565 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
5566 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
5567 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
5568 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
5569 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
5570 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
5571 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
5572 ]
5573 );
5574 });
5575 EditorTestContext::for_editor(editor, cx)
5576 .await
5577 .assert_editor_state(
5578 "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
5579 );
5580}
5581
5582#[gpui::test]
5583async fn test_add_selection_above_below(cx: &mut TestAppContext) {
5584 init_test(cx, |_| {});
5585
5586 let mut cx = EditorTestContext::new(cx).await;
5587
5588 cx.set_state(indoc!(
5589 r#"abc
5590 defˇghi
5591
5592 jk
5593 nlmo
5594 "#
5595 ));
5596
5597 cx.update_editor(|editor, window, cx| {
5598 editor.add_selection_above(&Default::default(), window, cx);
5599 });
5600
5601 cx.assert_editor_state(indoc!(
5602 r#"abcˇ
5603 defˇghi
5604
5605 jk
5606 nlmo
5607 "#
5608 ));
5609
5610 cx.update_editor(|editor, window, cx| {
5611 editor.add_selection_above(&Default::default(), window, cx);
5612 });
5613
5614 cx.assert_editor_state(indoc!(
5615 r#"abcˇ
5616 defˇghi
5617
5618 jk
5619 nlmo
5620 "#
5621 ));
5622
5623 cx.update_editor(|editor, window, cx| {
5624 editor.add_selection_below(&Default::default(), window, cx);
5625 });
5626
5627 cx.assert_editor_state(indoc!(
5628 r#"abc
5629 defˇghi
5630
5631 jk
5632 nlmo
5633 "#
5634 ));
5635
5636 cx.update_editor(|editor, window, cx| {
5637 editor.undo_selection(&Default::default(), window, cx);
5638 });
5639
5640 cx.assert_editor_state(indoc!(
5641 r#"abcˇ
5642 defˇghi
5643
5644 jk
5645 nlmo
5646 "#
5647 ));
5648
5649 cx.update_editor(|editor, window, cx| {
5650 editor.redo_selection(&Default::default(), window, cx);
5651 });
5652
5653 cx.assert_editor_state(indoc!(
5654 r#"abc
5655 defˇghi
5656
5657 jk
5658 nlmo
5659 "#
5660 ));
5661
5662 cx.update_editor(|editor, window, cx| {
5663 editor.add_selection_below(&Default::default(), window, cx);
5664 });
5665
5666 cx.assert_editor_state(indoc!(
5667 r#"abc
5668 defˇghi
5669
5670 jk
5671 nlmˇo
5672 "#
5673 ));
5674
5675 cx.update_editor(|editor, window, cx| {
5676 editor.add_selection_below(&Default::default(), window, cx);
5677 });
5678
5679 cx.assert_editor_state(indoc!(
5680 r#"abc
5681 defˇghi
5682
5683 jk
5684 nlmˇo
5685 "#
5686 ));
5687
5688 // change selections
5689 cx.set_state(indoc!(
5690 r#"abc
5691 def«ˇg»hi
5692
5693 jk
5694 nlmo
5695 "#
5696 ));
5697
5698 cx.update_editor(|editor, window, cx| {
5699 editor.add_selection_below(&Default::default(), window, cx);
5700 });
5701
5702 cx.assert_editor_state(indoc!(
5703 r#"abc
5704 def«ˇg»hi
5705
5706 jk
5707 nlm«ˇo»
5708 "#
5709 ));
5710
5711 cx.update_editor(|editor, window, cx| {
5712 editor.add_selection_below(&Default::default(), window, cx);
5713 });
5714
5715 cx.assert_editor_state(indoc!(
5716 r#"abc
5717 def«ˇg»hi
5718
5719 jk
5720 nlm«ˇo»
5721 "#
5722 ));
5723
5724 cx.update_editor(|editor, window, cx| {
5725 editor.add_selection_above(&Default::default(), window, cx);
5726 });
5727
5728 cx.assert_editor_state(indoc!(
5729 r#"abc
5730 def«ˇg»hi
5731
5732 jk
5733 nlmo
5734 "#
5735 ));
5736
5737 cx.update_editor(|editor, window, cx| {
5738 editor.add_selection_above(&Default::default(), window, cx);
5739 });
5740
5741 cx.assert_editor_state(indoc!(
5742 r#"abc
5743 def«ˇg»hi
5744
5745 jk
5746 nlmo
5747 "#
5748 ));
5749
5750 // Change selections again
5751 cx.set_state(indoc!(
5752 r#"a«bc
5753 defgˇ»hi
5754
5755 jk
5756 nlmo
5757 "#
5758 ));
5759
5760 cx.update_editor(|editor, window, cx| {
5761 editor.add_selection_below(&Default::default(), window, cx);
5762 });
5763
5764 cx.assert_editor_state(indoc!(
5765 r#"a«bcˇ»
5766 d«efgˇ»hi
5767
5768 j«kˇ»
5769 nlmo
5770 "#
5771 ));
5772
5773 cx.update_editor(|editor, window, cx| {
5774 editor.add_selection_below(&Default::default(), window, cx);
5775 });
5776 cx.assert_editor_state(indoc!(
5777 r#"a«bcˇ»
5778 d«efgˇ»hi
5779
5780 j«kˇ»
5781 n«lmoˇ»
5782 "#
5783 ));
5784 cx.update_editor(|editor, window, cx| {
5785 editor.add_selection_above(&Default::default(), window, cx);
5786 });
5787
5788 cx.assert_editor_state(indoc!(
5789 r#"a«bcˇ»
5790 d«efgˇ»hi
5791
5792 j«kˇ»
5793 nlmo
5794 "#
5795 ));
5796
5797 // Change selections again
5798 cx.set_state(indoc!(
5799 r#"abc
5800 d«ˇefghi
5801
5802 jk
5803 nlm»o
5804 "#
5805 ));
5806
5807 cx.update_editor(|editor, window, cx| {
5808 editor.add_selection_above(&Default::default(), window, cx);
5809 });
5810
5811 cx.assert_editor_state(indoc!(
5812 r#"a«ˇbc»
5813 d«ˇef»ghi
5814
5815 j«ˇk»
5816 n«ˇlm»o
5817 "#
5818 ));
5819
5820 cx.update_editor(|editor, window, cx| {
5821 editor.add_selection_below(&Default::default(), window, cx);
5822 });
5823
5824 cx.assert_editor_state(indoc!(
5825 r#"abc
5826 d«ˇef»ghi
5827
5828 j«ˇk»
5829 n«ˇlm»o
5830 "#
5831 ));
5832}
5833
5834#[gpui::test]
5835async fn test_select_next(cx: &mut TestAppContext) {
5836 init_test(cx, |_| {});
5837
5838 let mut cx = EditorTestContext::new(cx).await;
5839 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5840
5841 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5842 .unwrap();
5843 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5844
5845 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5846 .unwrap();
5847 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5848
5849 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5850 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5851
5852 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5853 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5854
5855 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5856 .unwrap();
5857 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5858
5859 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5860 .unwrap();
5861 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5862
5863 // Test selection direction should be preserved
5864 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
5865
5866 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5867 .unwrap();
5868 cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
5869}
5870
5871#[gpui::test]
5872async fn test_select_all_matches(cx: &mut TestAppContext) {
5873 init_test(cx, |_| {});
5874
5875 let mut cx = EditorTestContext::new(cx).await;
5876
5877 // Test caret-only selections
5878 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5879 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5880 .unwrap();
5881 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5882
5883 // Test left-to-right selections
5884 cx.set_state("abc\n«abcˇ»\nabc");
5885 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5886 .unwrap();
5887 cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
5888
5889 // Test right-to-left selections
5890 cx.set_state("abc\n«ˇabc»\nabc");
5891 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5892 .unwrap();
5893 cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
5894
5895 // Test selecting whitespace with caret selection
5896 cx.set_state("abc\nˇ abc\nabc");
5897 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5898 .unwrap();
5899 cx.assert_editor_state("abc\n« ˇ»abc\nabc");
5900
5901 // Test selecting whitespace with left-to-right selection
5902 cx.set_state("abc\n«ˇ »abc\nabc");
5903 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5904 .unwrap();
5905 cx.assert_editor_state("abc\n«ˇ »abc\nabc");
5906
5907 // Test no matches with right-to-left selection
5908 cx.set_state("abc\n« ˇ»abc\nabc");
5909 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5910 .unwrap();
5911 cx.assert_editor_state("abc\n« ˇ»abc\nabc");
5912}
5913
5914#[gpui::test]
5915async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
5916 init_test(cx, |_| {});
5917
5918 let mut cx = EditorTestContext::new(cx).await;
5919
5920 let large_body_1 = "\nd".repeat(200);
5921 let large_body_2 = "\ne".repeat(200);
5922
5923 cx.set_state(&format!(
5924 "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
5925 ));
5926 let initial_scroll_position = cx.update_editor(|editor, _, cx| {
5927 let scroll_position = editor.scroll_position(cx);
5928 assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
5929 scroll_position
5930 });
5931
5932 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5933 .unwrap();
5934 cx.assert_editor_state(&format!(
5935 "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
5936 ));
5937 let scroll_position_after_selection =
5938 cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
5939 assert_eq!(
5940 initial_scroll_position, scroll_position_after_selection,
5941 "Scroll position should not change after selecting all matches"
5942 );
5943}
5944
5945#[gpui::test]
5946async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
5947 init_test(cx, |_| {});
5948
5949 let mut cx = EditorLspTestContext::new_rust(
5950 lsp::ServerCapabilities {
5951 document_formatting_provider: Some(lsp::OneOf::Left(true)),
5952 ..Default::default()
5953 },
5954 cx,
5955 )
5956 .await;
5957
5958 cx.set_state(indoc! {"
5959 line 1
5960 line 2
5961 linˇe 3
5962 line 4
5963 line 5
5964 "});
5965
5966 // Make an edit
5967 cx.update_editor(|editor, window, cx| {
5968 editor.handle_input("X", window, cx);
5969 });
5970
5971 // Move cursor to a different position
5972 cx.update_editor(|editor, window, cx| {
5973 editor.change_selections(None, window, cx, |s| {
5974 s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
5975 });
5976 });
5977
5978 cx.assert_editor_state(indoc! {"
5979 line 1
5980 line 2
5981 linXe 3
5982 line 4
5983 liˇne 5
5984 "});
5985
5986 cx.lsp
5987 .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
5988 Ok(Some(vec![lsp::TextEdit::new(
5989 lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
5990 "PREFIX ".to_string(),
5991 )]))
5992 });
5993
5994 cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
5995 .unwrap()
5996 .await
5997 .unwrap();
5998
5999 cx.assert_editor_state(indoc! {"
6000 PREFIX line 1
6001 line 2
6002 linXe 3
6003 line 4
6004 liˇne 5
6005 "});
6006
6007 // Undo formatting
6008 cx.update_editor(|editor, window, cx| {
6009 editor.undo(&Default::default(), window, cx);
6010 });
6011
6012 // Verify cursor moved back to position after edit
6013 cx.assert_editor_state(indoc! {"
6014 line 1
6015 line 2
6016 linXˇe 3
6017 line 4
6018 line 5
6019 "});
6020}
6021
6022#[gpui::test]
6023async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
6024 init_test(cx, |_| {});
6025
6026 let mut cx = EditorTestContext::new(cx).await;
6027 cx.set_state(
6028 r#"let foo = 2;
6029lˇet foo = 2;
6030let fooˇ = 2;
6031let foo = 2;
6032let foo = ˇ2;"#,
6033 );
6034
6035 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
6036 .unwrap();
6037 cx.assert_editor_state(
6038 r#"let foo = 2;
6039«letˇ» foo = 2;
6040let «fooˇ» = 2;
6041let foo = 2;
6042let foo = «2ˇ»;"#,
6043 );
6044
6045 // noop for multiple selections with different contents
6046 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
6047 .unwrap();
6048 cx.assert_editor_state(
6049 r#"let foo = 2;
6050«letˇ» foo = 2;
6051let «fooˇ» = 2;
6052let foo = 2;
6053let foo = «2ˇ»;"#,
6054 );
6055
6056 // Test last selection direction should be preserved
6057 cx.set_state(
6058 r#"let foo = 2;
6059let foo = 2;
6060let «fooˇ» = 2;
6061let «ˇfoo» = 2;
6062let foo = 2;"#,
6063 );
6064
6065 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
6066 .unwrap();
6067 cx.assert_editor_state(
6068 r#"let foo = 2;
6069let foo = 2;
6070let «fooˇ» = 2;
6071let «ˇfoo» = 2;
6072let «ˇfoo» = 2;"#,
6073 );
6074}
6075
6076#[gpui::test]
6077async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
6078 init_test(cx, |_| {});
6079
6080 let mut cx =
6081 EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
6082
6083 cx.assert_editor_state(indoc! {"
6084 ˇbbb
6085 ccc
6086
6087 bbb
6088 ccc
6089 "});
6090 cx.dispatch_action(SelectPrevious::default());
6091 cx.assert_editor_state(indoc! {"
6092 «bbbˇ»
6093 ccc
6094
6095 bbb
6096 ccc
6097 "});
6098 cx.dispatch_action(SelectPrevious::default());
6099 cx.assert_editor_state(indoc! {"
6100 «bbbˇ»
6101 ccc
6102
6103 «bbbˇ»
6104 ccc
6105 "});
6106}
6107
6108#[gpui::test]
6109async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
6110 init_test(cx, |_| {});
6111
6112 let mut cx = EditorTestContext::new(cx).await;
6113 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
6114
6115 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6116 .unwrap();
6117 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
6118
6119 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6120 .unwrap();
6121 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
6122
6123 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
6124 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
6125
6126 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
6127 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
6128
6129 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6130 .unwrap();
6131 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
6132
6133 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6134 .unwrap();
6135 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
6136}
6137
6138#[gpui::test]
6139async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
6140 init_test(cx, |_| {});
6141
6142 let mut cx = EditorTestContext::new(cx).await;
6143 cx.set_state("aˇ");
6144
6145 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6146 .unwrap();
6147 cx.assert_editor_state("«aˇ»");
6148 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6149 .unwrap();
6150 cx.assert_editor_state("«aˇ»");
6151}
6152
6153#[gpui::test]
6154async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
6155 init_test(cx, |_| {});
6156
6157 let mut cx = EditorTestContext::new(cx).await;
6158 cx.set_state(
6159 r#"let foo = 2;
6160lˇet foo = 2;
6161let fooˇ = 2;
6162let foo = 2;
6163let foo = ˇ2;"#,
6164 );
6165
6166 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6167 .unwrap();
6168 cx.assert_editor_state(
6169 r#"let foo = 2;
6170«letˇ» foo = 2;
6171let «fooˇ» = 2;
6172let foo = 2;
6173let foo = «2ˇ»;"#,
6174 );
6175
6176 // noop for multiple selections with different contents
6177 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6178 .unwrap();
6179 cx.assert_editor_state(
6180 r#"let foo = 2;
6181«letˇ» foo = 2;
6182let «fooˇ» = 2;
6183let foo = 2;
6184let foo = «2ˇ»;"#,
6185 );
6186}
6187
6188#[gpui::test]
6189async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
6190 init_test(cx, |_| {});
6191
6192 let mut cx = EditorTestContext::new(cx).await;
6193 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
6194
6195 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6196 .unwrap();
6197 // selection direction is preserved
6198 cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
6199
6200 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6201 .unwrap();
6202 cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
6203
6204 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
6205 cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
6206
6207 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
6208 cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
6209
6210 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6211 .unwrap();
6212 cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
6213
6214 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6215 .unwrap();
6216 cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
6217}
6218
6219#[gpui::test]
6220async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
6221 init_test(cx, |_| {});
6222
6223 let language = Arc::new(Language::new(
6224 LanguageConfig::default(),
6225 Some(tree_sitter_rust::LANGUAGE.into()),
6226 ));
6227
6228 let text = r#"
6229 use mod1::mod2::{mod3, mod4};
6230
6231 fn fn_1(param1: bool, param2: &str) {
6232 let var1 = "text";
6233 }
6234 "#
6235 .unindent();
6236
6237 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6238 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6239 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6240
6241 editor
6242 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6243 .await;
6244
6245 editor.update_in(cx, |editor, window, cx| {
6246 editor.change_selections(None, window, cx, |s| {
6247 s.select_display_ranges([
6248 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
6249 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
6250 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
6251 ]);
6252 });
6253 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6254 });
6255 editor.update(cx, |editor, cx| {
6256 assert_text_with_selections(
6257 editor,
6258 indoc! {r#"
6259 use mod1::mod2::{mod3, «mod4ˇ»};
6260
6261 fn fn_1«ˇ(param1: bool, param2: &str)» {
6262 let var1 = "«ˇtext»";
6263 }
6264 "#},
6265 cx,
6266 );
6267 });
6268
6269 editor.update_in(cx, |editor, window, cx| {
6270 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6271 });
6272 editor.update(cx, |editor, cx| {
6273 assert_text_with_selections(
6274 editor,
6275 indoc! {r#"
6276 use mod1::mod2::«{mod3, mod4}ˇ»;
6277
6278 «ˇfn fn_1(param1: bool, param2: &str) {
6279 let var1 = "text";
6280 }»
6281 "#},
6282 cx,
6283 );
6284 });
6285
6286 editor.update_in(cx, |editor, window, cx| {
6287 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6288 });
6289 assert_eq!(
6290 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
6291 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
6292 );
6293
6294 // Trying to expand the selected syntax node one more time has no effect.
6295 editor.update_in(cx, |editor, window, cx| {
6296 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6297 });
6298 assert_eq!(
6299 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
6300 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
6301 );
6302
6303 editor.update_in(cx, |editor, window, cx| {
6304 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
6305 });
6306 editor.update(cx, |editor, cx| {
6307 assert_text_with_selections(
6308 editor,
6309 indoc! {r#"
6310 use mod1::mod2::«{mod3, mod4}ˇ»;
6311
6312 «ˇfn fn_1(param1: bool, param2: &str) {
6313 let var1 = "text";
6314 }»
6315 "#},
6316 cx,
6317 );
6318 });
6319
6320 editor.update_in(cx, |editor, window, cx| {
6321 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
6322 });
6323 editor.update(cx, |editor, cx| {
6324 assert_text_with_selections(
6325 editor,
6326 indoc! {r#"
6327 use mod1::mod2::{mod3, «mod4ˇ»};
6328
6329 fn fn_1«ˇ(param1: bool, param2: &str)» {
6330 let var1 = "«ˇtext»";
6331 }
6332 "#},
6333 cx,
6334 );
6335 });
6336
6337 editor.update_in(cx, |editor, window, cx| {
6338 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
6339 });
6340 editor.update(cx, |editor, cx| {
6341 assert_text_with_selections(
6342 editor,
6343 indoc! {r#"
6344 use mod1::mod2::{mod3, mo«ˇ»d4};
6345
6346 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
6347 let var1 = "te«ˇ»xt";
6348 }
6349 "#},
6350 cx,
6351 );
6352 });
6353
6354 // Trying to shrink the selected syntax node one more time has no effect.
6355 editor.update_in(cx, |editor, window, cx| {
6356 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
6357 });
6358 editor.update_in(cx, |editor, _, cx| {
6359 assert_text_with_selections(
6360 editor,
6361 indoc! {r#"
6362 use mod1::mod2::{mod3, mo«ˇ»d4};
6363
6364 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
6365 let var1 = "te«ˇ»xt";
6366 }
6367 "#},
6368 cx,
6369 );
6370 });
6371
6372 // Ensure that we keep expanding the selection if the larger selection starts or ends within
6373 // a fold.
6374 editor.update_in(cx, |editor, window, cx| {
6375 editor.fold_creases(
6376 vec![
6377 Crease::simple(
6378 Point::new(0, 21)..Point::new(0, 24),
6379 FoldPlaceholder::test(),
6380 ),
6381 Crease::simple(
6382 Point::new(3, 20)..Point::new(3, 22),
6383 FoldPlaceholder::test(),
6384 ),
6385 ],
6386 true,
6387 window,
6388 cx,
6389 );
6390 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6391 });
6392 editor.update(cx, |editor, cx| {
6393 assert_text_with_selections(
6394 editor,
6395 indoc! {r#"
6396 use mod1::mod2::«{mod3, mod4}ˇ»;
6397
6398 fn fn_1«ˇ(param1: bool, param2: &str)» {
6399 let var1 = "«ˇtext»";
6400 }
6401 "#},
6402 cx,
6403 );
6404 });
6405}
6406
6407#[gpui::test]
6408async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
6409 init_test(cx, |_| {});
6410
6411 let language = Arc::new(Language::new(
6412 LanguageConfig::default(),
6413 Some(tree_sitter_rust::LANGUAGE.into()),
6414 ));
6415
6416 let text = "let a = 2;";
6417
6418 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6419 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6420 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6421
6422 editor
6423 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6424 .await;
6425
6426 // Test case 1: Cursor at end of word
6427 editor.update_in(cx, |editor, window, cx| {
6428 editor.change_selections(None, window, cx, |s| {
6429 s.select_display_ranges([
6430 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
6431 ]);
6432 });
6433 });
6434 editor.update(cx, |editor, cx| {
6435 assert_text_with_selections(editor, "let aˇ = 2;", cx);
6436 });
6437 editor.update_in(cx, |editor, window, cx| {
6438 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6439 });
6440 editor.update(cx, |editor, cx| {
6441 assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
6442 });
6443 editor.update_in(cx, |editor, window, cx| {
6444 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6445 });
6446 editor.update(cx, |editor, cx| {
6447 assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
6448 });
6449
6450 // Test case 2: Cursor at end of statement
6451 editor.update_in(cx, |editor, window, cx| {
6452 editor.change_selections(None, window, cx, |s| {
6453 s.select_display_ranges([
6454 DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
6455 ]);
6456 });
6457 });
6458 editor.update(cx, |editor, cx| {
6459 assert_text_with_selections(editor, "let a = 2;ˇ", cx);
6460 });
6461 editor.update_in(cx, |editor, window, cx| {
6462 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6463 });
6464 editor.update(cx, |editor, cx| {
6465 assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
6466 });
6467}
6468
6469#[gpui::test]
6470async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
6471 init_test(cx, |_| {});
6472
6473 let language = Arc::new(Language::new(
6474 LanguageConfig::default(),
6475 Some(tree_sitter_rust::LANGUAGE.into()),
6476 ));
6477
6478 let text = r#"
6479 use mod1::mod2::{mod3, mod4};
6480
6481 fn fn_1(param1: bool, param2: &str) {
6482 let var1 = "hello world";
6483 }
6484 "#
6485 .unindent();
6486
6487 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6488 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6489 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6490
6491 editor
6492 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6493 .await;
6494
6495 // Test 1: Cursor on a letter of a string word
6496 editor.update_in(cx, |editor, window, cx| {
6497 editor.change_selections(None, window, cx, |s| {
6498 s.select_display_ranges([
6499 DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
6500 ]);
6501 });
6502 });
6503 editor.update_in(cx, |editor, window, cx| {
6504 assert_text_with_selections(
6505 editor,
6506 indoc! {r#"
6507 use mod1::mod2::{mod3, mod4};
6508
6509 fn fn_1(param1: bool, param2: &str) {
6510 let var1 = "hˇello world";
6511 }
6512 "#},
6513 cx,
6514 );
6515 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6516 assert_text_with_selections(
6517 editor,
6518 indoc! {r#"
6519 use mod1::mod2::{mod3, mod4};
6520
6521 fn fn_1(param1: bool, param2: &str) {
6522 let var1 = "«ˇhello» world";
6523 }
6524 "#},
6525 cx,
6526 );
6527 });
6528
6529 // Test 2: Partial selection within a word
6530 editor.update_in(cx, |editor, window, cx| {
6531 editor.change_selections(None, window, cx, |s| {
6532 s.select_display_ranges([
6533 DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
6534 ]);
6535 });
6536 });
6537 editor.update_in(cx, |editor, window, cx| {
6538 assert_text_with_selections(
6539 editor,
6540 indoc! {r#"
6541 use mod1::mod2::{mod3, mod4};
6542
6543 fn fn_1(param1: bool, param2: &str) {
6544 let var1 = "h«elˇ»lo world";
6545 }
6546 "#},
6547 cx,
6548 );
6549 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6550 assert_text_with_selections(
6551 editor,
6552 indoc! {r#"
6553 use mod1::mod2::{mod3, mod4};
6554
6555 fn fn_1(param1: bool, param2: &str) {
6556 let var1 = "«ˇhello» world";
6557 }
6558 "#},
6559 cx,
6560 );
6561 });
6562
6563 // Test 3: Complete word already selected
6564 editor.update_in(cx, |editor, window, cx| {
6565 editor.change_selections(None, window, cx, |s| {
6566 s.select_display_ranges([
6567 DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
6568 ]);
6569 });
6570 });
6571 editor.update_in(cx, |editor, window, cx| {
6572 assert_text_with_selections(
6573 editor,
6574 indoc! {r#"
6575 use mod1::mod2::{mod3, mod4};
6576
6577 fn fn_1(param1: bool, param2: &str) {
6578 let var1 = "«helloˇ» world";
6579 }
6580 "#},
6581 cx,
6582 );
6583 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6584 assert_text_with_selections(
6585 editor,
6586 indoc! {r#"
6587 use mod1::mod2::{mod3, mod4};
6588
6589 fn fn_1(param1: bool, param2: &str) {
6590 let var1 = "«hello worldˇ»";
6591 }
6592 "#},
6593 cx,
6594 );
6595 });
6596
6597 // Test 4: Selection spanning across words
6598 editor.update_in(cx, |editor, window, cx| {
6599 editor.change_selections(None, window, cx, |s| {
6600 s.select_display_ranges([
6601 DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
6602 ]);
6603 });
6604 });
6605 editor.update_in(cx, |editor, window, cx| {
6606 assert_text_with_selections(
6607 editor,
6608 indoc! {r#"
6609 use mod1::mod2::{mod3, mod4};
6610
6611 fn fn_1(param1: bool, param2: &str) {
6612 let var1 = "hel«lo woˇ»rld";
6613 }
6614 "#},
6615 cx,
6616 );
6617 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6618 assert_text_with_selections(
6619 editor,
6620 indoc! {r#"
6621 use mod1::mod2::{mod3, mod4};
6622
6623 fn fn_1(param1: bool, param2: &str) {
6624 let var1 = "«ˇhello world»";
6625 }
6626 "#},
6627 cx,
6628 );
6629 });
6630
6631 // Test 5: Expansion beyond string
6632 editor.update_in(cx, |editor, window, cx| {
6633 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6634 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6635 assert_text_with_selections(
6636 editor,
6637 indoc! {r#"
6638 use mod1::mod2::{mod3, mod4};
6639
6640 fn fn_1(param1: bool, param2: &str) {
6641 «ˇlet var1 = "hello world";»
6642 }
6643 "#},
6644 cx,
6645 );
6646 });
6647}
6648
6649#[gpui::test]
6650async fn test_fold_function_bodies(cx: &mut TestAppContext) {
6651 init_test(cx, |_| {});
6652
6653 let base_text = r#"
6654 impl A {
6655 // this is an uncommitted comment
6656
6657 fn b() {
6658 c();
6659 }
6660
6661 // this is another uncommitted comment
6662
6663 fn d() {
6664 // e
6665 // f
6666 }
6667 }
6668
6669 fn g() {
6670 // h
6671 }
6672 "#
6673 .unindent();
6674
6675 let text = r#"
6676 ˇimpl A {
6677
6678 fn b() {
6679 c();
6680 }
6681
6682 fn d() {
6683 // e
6684 // f
6685 }
6686 }
6687
6688 fn g() {
6689 // h
6690 }
6691 "#
6692 .unindent();
6693
6694 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
6695 cx.set_state(&text);
6696 cx.set_head_text(&base_text);
6697 cx.update_editor(|editor, window, cx| {
6698 editor.expand_all_diff_hunks(&Default::default(), window, cx);
6699 });
6700
6701 cx.assert_state_with_diff(
6702 "
6703 ˇimpl A {
6704 - // this is an uncommitted comment
6705
6706 fn b() {
6707 c();
6708 }
6709
6710 - // this is another uncommitted comment
6711 -
6712 fn d() {
6713 // e
6714 // f
6715 }
6716 }
6717
6718 fn g() {
6719 // h
6720 }
6721 "
6722 .unindent(),
6723 );
6724
6725 let expected_display_text = "
6726 impl A {
6727 // this is an uncommitted comment
6728
6729 fn b() {
6730 ⋯
6731 }
6732
6733 // this is another uncommitted comment
6734
6735 fn d() {
6736 ⋯
6737 }
6738 }
6739
6740 fn g() {
6741 ⋯
6742 }
6743 "
6744 .unindent();
6745
6746 cx.update_editor(|editor, window, cx| {
6747 editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
6748 assert_eq!(editor.display_text(cx), expected_display_text);
6749 });
6750}
6751
6752#[gpui::test]
6753async fn test_autoindent(cx: &mut TestAppContext) {
6754 init_test(cx, |_| {});
6755
6756 let language = Arc::new(
6757 Language::new(
6758 LanguageConfig {
6759 brackets: BracketPairConfig {
6760 pairs: vec![
6761 BracketPair {
6762 start: "{".to_string(),
6763 end: "}".to_string(),
6764 close: false,
6765 surround: false,
6766 newline: true,
6767 },
6768 BracketPair {
6769 start: "(".to_string(),
6770 end: ")".to_string(),
6771 close: false,
6772 surround: false,
6773 newline: true,
6774 },
6775 ],
6776 ..Default::default()
6777 },
6778 ..Default::default()
6779 },
6780 Some(tree_sitter_rust::LANGUAGE.into()),
6781 )
6782 .with_indents_query(
6783 r#"
6784 (_ "(" ")" @end) @indent
6785 (_ "{" "}" @end) @indent
6786 "#,
6787 )
6788 .unwrap(),
6789 );
6790
6791 let text = "fn a() {}";
6792
6793 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6794 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6795 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6796 editor
6797 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6798 .await;
6799
6800 editor.update_in(cx, |editor, window, cx| {
6801 editor.change_selections(None, window, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
6802 editor.newline(&Newline, window, cx);
6803 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
6804 assert_eq!(
6805 editor.selections.ranges(cx),
6806 &[
6807 Point::new(1, 4)..Point::new(1, 4),
6808 Point::new(3, 4)..Point::new(3, 4),
6809 Point::new(5, 0)..Point::new(5, 0)
6810 ]
6811 );
6812 });
6813}
6814
6815#[gpui::test]
6816async fn test_autoindent_selections(cx: &mut TestAppContext) {
6817 init_test(cx, |_| {});
6818
6819 {
6820 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
6821 cx.set_state(indoc! {"
6822 impl A {
6823
6824 fn b() {}
6825
6826 «fn c() {
6827
6828 }ˇ»
6829 }
6830 "});
6831
6832 cx.update_editor(|editor, window, cx| {
6833 editor.autoindent(&Default::default(), window, cx);
6834 });
6835
6836 cx.assert_editor_state(indoc! {"
6837 impl A {
6838
6839 fn b() {}
6840
6841 «fn c() {
6842
6843 }ˇ»
6844 }
6845 "});
6846 }
6847
6848 {
6849 let mut cx = EditorTestContext::new_multibuffer(
6850 cx,
6851 [indoc! { "
6852 impl A {
6853 «
6854 // a
6855 fn b(){}
6856 »
6857 «
6858 }
6859 fn c(){}
6860 »
6861 "}],
6862 );
6863
6864 let buffer = cx.update_editor(|editor, _, cx| {
6865 let buffer = editor.buffer().update(cx, |buffer, _| {
6866 buffer.all_buffers().iter().next().unwrap().clone()
6867 });
6868 buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
6869 buffer
6870 });
6871
6872 cx.run_until_parked();
6873 cx.update_editor(|editor, window, cx| {
6874 editor.select_all(&Default::default(), window, cx);
6875 editor.autoindent(&Default::default(), window, cx)
6876 });
6877 cx.run_until_parked();
6878
6879 cx.update(|_, cx| {
6880 assert_eq!(
6881 buffer.read(cx).text(),
6882 indoc! { "
6883 impl A {
6884
6885 // a
6886 fn b(){}
6887
6888
6889 }
6890 fn c(){}
6891
6892 " }
6893 )
6894 });
6895 }
6896}
6897
6898#[gpui::test]
6899async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
6900 init_test(cx, |_| {});
6901
6902 let mut cx = EditorTestContext::new(cx).await;
6903
6904 let language = Arc::new(Language::new(
6905 LanguageConfig {
6906 brackets: BracketPairConfig {
6907 pairs: vec![
6908 BracketPair {
6909 start: "{".to_string(),
6910 end: "}".to_string(),
6911 close: true,
6912 surround: true,
6913 newline: true,
6914 },
6915 BracketPair {
6916 start: "(".to_string(),
6917 end: ")".to_string(),
6918 close: true,
6919 surround: true,
6920 newline: true,
6921 },
6922 BracketPair {
6923 start: "/*".to_string(),
6924 end: " */".to_string(),
6925 close: true,
6926 surround: true,
6927 newline: true,
6928 },
6929 BracketPair {
6930 start: "[".to_string(),
6931 end: "]".to_string(),
6932 close: false,
6933 surround: false,
6934 newline: true,
6935 },
6936 BracketPair {
6937 start: "\"".to_string(),
6938 end: "\"".to_string(),
6939 close: true,
6940 surround: true,
6941 newline: false,
6942 },
6943 BracketPair {
6944 start: "<".to_string(),
6945 end: ">".to_string(),
6946 close: false,
6947 surround: true,
6948 newline: true,
6949 },
6950 ],
6951 ..Default::default()
6952 },
6953 autoclose_before: "})]".to_string(),
6954 ..Default::default()
6955 },
6956 Some(tree_sitter_rust::LANGUAGE.into()),
6957 ));
6958
6959 cx.language_registry().add(language.clone());
6960 cx.update_buffer(|buffer, cx| {
6961 buffer.set_language(Some(language), cx);
6962 });
6963
6964 cx.set_state(
6965 &r#"
6966 🏀ˇ
6967 εˇ
6968 ❤️ˇ
6969 "#
6970 .unindent(),
6971 );
6972
6973 // autoclose multiple nested brackets at multiple cursors
6974 cx.update_editor(|editor, window, cx| {
6975 editor.handle_input("{", window, cx);
6976 editor.handle_input("{", window, cx);
6977 editor.handle_input("{", window, cx);
6978 });
6979 cx.assert_editor_state(
6980 &"
6981 🏀{{{ˇ}}}
6982 ε{{{ˇ}}}
6983 ❤️{{{ˇ}}}
6984 "
6985 .unindent(),
6986 );
6987
6988 // insert a different closing bracket
6989 cx.update_editor(|editor, window, cx| {
6990 editor.handle_input(")", window, cx);
6991 });
6992 cx.assert_editor_state(
6993 &"
6994 🏀{{{)ˇ}}}
6995 ε{{{)ˇ}}}
6996 ❤️{{{)ˇ}}}
6997 "
6998 .unindent(),
6999 );
7000
7001 // skip over the auto-closed brackets when typing a closing bracket
7002 cx.update_editor(|editor, window, cx| {
7003 editor.move_right(&MoveRight, window, cx);
7004 editor.handle_input("}", window, cx);
7005 editor.handle_input("}", window, cx);
7006 editor.handle_input("}", window, cx);
7007 });
7008 cx.assert_editor_state(
7009 &"
7010 🏀{{{)}}}}ˇ
7011 ε{{{)}}}}ˇ
7012 ❤️{{{)}}}}ˇ
7013 "
7014 .unindent(),
7015 );
7016
7017 // autoclose multi-character pairs
7018 cx.set_state(
7019 &"
7020 ˇ
7021 ˇ
7022 "
7023 .unindent(),
7024 );
7025 cx.update_editor(|editor, window, cx| {
7026 editor.handle_input("/", window, cx);
7027 editor.handle_input("*", window, cx);
7028 });
7029 cx.assert_editor_state(
7030 &"
7031 /*ˇ */
7032 /*ˇ */
7033 "
7034 .unindent(),
7035 );
7036
7037 // one cursor autocloses a multi-character pair, one cursor
7038 // does not autoclose.
7039 cx.set_state(
7040 &"
7041 /ˇ
7042 ˇ
7043 "
7044 .unindent(),
7045 );
7046 cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
7047 cx.assert_editor_state(
7048 &"
7049 /*ˇ */
7050 *ˇ
7051 "
7052 .unindent(),
7053 );
7054
7055 // Don't autoclose if the next character isn't whitespace and isn't
7056 // listed in the language's "autoclose_before" section.
7057 cx.set_state("ˇa b");
7058 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
7059 cx.assert_editor_state("{ˇa b");
7060
7061 // Don't autoclose if `close` is false for the bracket pair
7062 cx.set_state("ˇ");
7063 cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
7064 cx.assert_editor_state("[ˇ");
7065
7066 // Surround with brackets if text is selected
7067 cx.set_state("«aˇ» b");
7068 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
7069 cx.assert_editor_state("{«aˇ»} b");
7070
7071 // Autoclose when not immediately after a word character
7072 cx.set_state("a ˇ");
7073 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
7074 cx.assert_editor_state("a \"ˇ\"");
7075
7076 // Autoclose pair where the start and end characters are the same
7077 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
7078 cx.assert_editor_state("a \"\"ˇ");
7079
7080 // Don't autoclose when immediately after a word character
7081 cx.set_state("aˇ");
7082 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
7083 cx.assert_editor_state("a\"ˇ");
7084
7085 // Do autoclose when after a non-word character
7086 cx.set_state("{ˇ");
7087 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
7088 cx.assert_editor_state("{\"ˇ\"");
7089
7090 // Non identical pairs autoclose regardless of preceding character
7091 cx.set_state("aˇ");
7092 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
7093 cx.assert_editor_state("a{ˇ}");
7094
7095 // Don't autoclose pair if autoclose is disabled
7096 cx.set_state("ˇ");
7097 cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
7098 cx.assert_editor_state("<ˇ");
7099
7100 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
7101 cx.set_state("«aˇ» b");
7102 cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
7103 cx.assert_editor_state("<«aˇ»> b");
7104}
7105
7106#[gpui::test]
7107async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
7108 init_test(cx, |settings| {
7109 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
7110 });
7111
7112 let mut cx = EditorTestContext::new(cx).await;
7113
7114 let language = Arc::new(Language::new(
7115 LanguageConfig {
7116 brackets: BracketPairConfig {
7117 pairs: vec![
7118 BracketPair {
7119 start: "{".to_string(),
7120 end: "}".to_string(),
7121 close: true,
7122 surround: true,
7123 newline: true,
7124 },
7125 BracketPair {
7126 start: "(".to_string(),
7127 end: ")".to_string(),
7128 close: true,
7129 surround: true,
7130 newline: true,
7131 },
7132 BracketPair {
7133 start: "[".to_string(),
7134 end: "]".to_string(),
7135 close: false,
7136 surround: false,
7137 newline: true,
7138 },
7139 ],
7140 ..Default::default()
7141 },
7142 autoclose_before: "})]".to_string(),
7143 ..Default::default()
7144 },
7145 Some(tree_sitter_rust::LANGUAGE.into()),
7146 ));
7147
7148 cx.language_registry().add(language.clone());
7149 cx.update_buffer(|buffer, cx| {
7150 buffer.set_language(Some(language), cx);
7151 });
7152
7153 cx.set_state(
7154 &"
7155 ˇ
7156 ˇ
7157 ˇ
7158 "
7159 .unindent(),
7160 );
7161
7162 // ensure only matching closing brackets are skipped over
7163 cx.update_editor(|editor, window, cx| {
7164 editor.handle_input("}", window, cx);
7165 editor.move_left(&MoveLeft, window, cx);
7166 editor.handle_input(")", window, cx);
7167 editor.move_left(&MoveLeft, window, cx);
7168 });
7169 cx.assert_editor_state(
7170 &"
7171 ˇ)}
7172 ˇ)}
7173 ˇ)}
7174 "
7175 .unindent(),
7176 );
7177
7178 // skip-over closing brackets at multiple cursors
7179 cx.update_editor(|editor, window, cx| {
7180 editor.handle_input(")", window, cx);
7181 editor.handle_input("}", window, cx);
7182 });
7183 cx.assert_editor_state(
7184 &"
7185 )}ˇ
7186 )}ˇ
7187 )}ˇ
7188 "
7189 .unindent(),
7190 );
7191
7192 // ignore non-close brackets
7193 cx.update_editor(|editor, window, cx| {
7194 editor.handle_input("]", window, cx);
7195 editor.move_left(&MoveLeft, window, cx);
7196 editor.handle_input("]", window, cx);
7197 });
7198 cx.assert_editor_state(
7199 &"
7200 )}]ˇ]
7201 )}]ˇ]
7202 )}]ˇ]
7203 "
7204 .unindent(),
7205 );
7206}
7207
7208#[gpui::test]
7209async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
7210 init_test(cx, |_| {});
7211
7212 let mut cx = EditorTestContext::new(cx).await;
7213
7214 let html_language = Arc::new(
7215 Language::new(
7216 LanguageConfig {
7217 name: "HTML".into(),
7218 brackets: BracketPairConfig {
7219 pairs: vec![
7220 BracketPair {
7221 start: "<".into(),
7222 end: ">".into(),
7223 close: true,
7224 ..Default::default()
7225 },
7226 BracketPair {
7227 start: "{".into(),
7228 end: "}".into(),
7229 close: true,
7230 ..Default::default()
7231 },
7232 BracketPair {
7233 start: "(".into(),
7234 end: ")".into(),
7235 close: true,
7236 ..Default::default()
7237 },
7238 ],
7239 ..Default::default()
7240 },
7241 autoclose_before: "})]>".into(),
7242 ..Default::default()
7243 },
7244 Some(tree_sitter_html::LANGUAGE.into()),
7245 )
7246 .with_injection_query(
7247 r#"
7248 (script_element
7249 (raw_text) @injection.content
7250 (#set! injection.language "javascript"))
7251 "#,
7252 )
7253 .unwrap(),
7254 );
7255
7256 let javascript_language = Arc::new(Language::new(
7257 LanguageConfig {
7258 name: "JavaScript".into(),
7259 brackets: BracketPairConfig {
7260 pairs: vec![
7261 BracketPair {
7262 start: "/*".into(),
7263 end: " */".into(),
7264 close: true,
7265 ..Default::default()
7266 },
7267 BracketPair {
7268 start: "{".into(),
7269 end: "}".into(),
7270 close: true,
7271 ..Default::default()
7272 },
7273 BracketPair {
7274 start: "(".into(),
7275 end: ")".into(),
7276 close: true,
7277 ..Default::default()
7278 },
7279 ],
7280 ..Default::default()
7281 },
7282 autoclose_before: "})]>".into(),
7283 ..Default::default()
7284 },
7285 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
7286 ));
7287
7288 cx.language_registry().add(html_language.clone());
7289 cx.language_registry().add(javascript_language.clone());
7290
7291 cx.update_buffer(|buffer, cx| {
7292 buffer.set_language(Some(html_language), cx);
7293 });
7294
7295 cx.set_state(
7296 &r#"
7297 <body>ˇ
7298 <script>
7299 var x = 1;ˇ
7300 </script>
7301 </body>ˇ
7302 "#
7303 .unindent(),
7304 );
7305
7306 // Precondition: different languages are active at different locations.
7307 cx.update_editor(|editor, window, cx| {
7308 let snapshot = editor.snapshot(window, cx);
7309 let cursors = editor.selections.ranges::<usize>(cx);
7310 let languages = cursors
7311 .iter()
7312 .map(|c| snapshot.language_at(c.start).unwrap().name())
7313 .collect::<Vec<_>>();
7314 assert_eq!(
7315 languages,
7316 &["HTML".into(), "JavaScript".into(), "HTML".into()]
7317 );
7318 });
7319
7320 // Angle brackets autoclose in HTML, but not JavaScript.
7321 cx.update_editor(|editor, window, cx| {
7322 editor.handle_input("<", window, cx);
7323 editor.handle_input("a", window, cx);
7324 });
7325 cx.assert_editor_state(
7326 &r#"
7327 <body><aˇ>
7328 <script>
7329 var x = 1;<aˇ
7330 </script>
7331 </body><aˇ>
7332 "#
7333 .unindent(),
7334 );
7335
7336 // Curly braces and parens autoclose in both HTML and JavaScript.
7337 cx.update_editor(|editor, window, cx| {
7338 editor.handle_input(" b=", window, cx);
7339 editor.handle_input("{", window, cx);
7340 editor.handle_input("c", window, cx);
7341 editor.handle_input("(", window, cx);
7342 });
7343 cx.assert_editor_state(
7344 &r#"
7345 <body><a b={c(ˇ)}>
7346 <script>
7347 var x = 1;<a b={c(ˇ)}
7348 </script>
7349 </body><a b={c(ˇ)}>
7350 "#
7351 .unindent(),
7352 );
7353
7354 // Brackets that were already autoclosed are skipped.
7355 cx.update_editor(|editor, window, cx| {
7356 editor.handle_input(")", window, cx);
7357 editor.handle_input("d", window, cx);
7358 editor.handle_input("}", window, cx);
7359 });
7360 cx.assert_editor_state(
7361 &r#"
7362 <body><a b={c()d}ˇ>
7363 <script>
7364 var x = 1;<a b={c()d}ˇ
7365 </script>
7366 </body><a b={c()d}ˇ>
7367 "#
7368 .unindent(),
7369 );
7370 cx.update_editor(|editor, window, cx| {
7371 editor.handle_input(">", window, cx);
7372 });
7373 cx.assert_editor_state(
7374 &r#"
7375 <body><a b={c()d}>ˇ
7376 <script>
7377 var x = 1;<a b={c()d}>ˇ
7378 </script>
7379 </body><a b={c()d}>ˇ
7380 "#
7381 .unindent(),
7382 );
7383
7384 // Reset
7385 cx.set_state(
7386 &r#"
7387 <body>ˇ
7388 <script>
7389 var x = 1;ˇ
7390 </script>
7391 </body>ˇ
7392 "#
7393 .unindent(),
7394 );
7395
7396 cx.update_editor(|editor, window, cx| {
7397 editor.handle_input("<", window, cx);
7398 });
7399 cx.assert_editor_state(
7400 &r#"
7401 <body><ˇ>
7402 <script>
7403 var x = 1;<ˇ
7404 </script>
7405 </body><ˇ>
7406 "#
7407 .unindent(),
7408 );
7409
7410 // When backspacing, the closing angle brackets are removed.
7411 cx.update_editor(|editor, window, cx| {
7412 editor.backspace(&Backspace, window, cx);
7413 });
7414 cx.assert_editor_state(
7415 &r#"
7416 <body>ˇ
7417 <script>
7418 var x = 1;ˇ
7419 </script>
7420 </body>ˇ
7421 "#
7422 .unindent(),
7423 );
7424
7425 // Block comments autoclose in JavaScript, but not HTML.
7426 cx.update_editor(|editor, window, cx| {
7427 editor.handle_input("/", window, cx);
7428 editor.handle_input("*", window, cx);
7429 });
7430 cx.assert_editor_state(
7431 &r#"
7432 <body>/*ˇ
7433 <script>
7434 var x = 1;/*ˇ */
7435 </script>
7436 </body>/*ˇ
7437 "#
7438 .unindent(),
7439 );
7440}
7441
7442#[gpui::test]
7443async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
7444 init_test(cx, |_| {});
7445
7446 let mut cx = EditorTestContext::new(cx).await;
7447
7448 let rust_language = Arc::new(
7449 Language::new(
7450 LanguageConfig {
7451 name: "Rust".into(),
7452 brackets: serde_json::from_value(json!([
7453 { "start": "{", "end": "}", "close": true, "newline": true },
7454 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
7455 ]))
7456 .unwrap(),
7457 autoclose_before: "})]>".into(),
7458 ..Default::default()
7459 },
7460 Some(tree_sitter_rust::LANGUAGE.into()),
7461 )
7462 .with_override_query("(string_literal) @string")
7463 .unwrap(),
7464 );
7465
7466 cx.language_registry().add(rust_language.clone());
7467 cx.update_buffer(|buffer, cx| {
7468 buffer.set_language(Some(rust_language), cx);
7469 });
7470
7471 cx.set_state(
7472 &r#"
7473 let x = ˇ
7474 "#
7475 .unindent(),
7476 );
7477
7478 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
7479 cx.update_editor(|editor, window, cx| {
7480 editor.handle_input("\"", window, cx);
7481 });
7482 cx.assert_editor_state(
7483 &r#"
7484 let x = "ˇ"
7485 "#
7486 .unindent(),
7487 );
7488
7489 // Inserting another quotation mark. The cursor moves across the existing
7490 // automatically-inserted quotation mark.
7491 cx.update_editor(|editor, window, cx| {
7492 editor.handle_input("\"", window, cx);
7493 });
7494 cx.assert_editor_state(
7495 &r#"
7496 let x = ""ˇ
7497 "#
7498 .unindent(),
7499 );
7500
7501 // Reset
7502 cx.set_state(
7503 &r#"
7504 let x = ˇ
7505 "#
7506 .unindent(),
7507 );
7508
7509 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
7510 cx.update_editor(|editor, window, cx| {
7511 editor.handle_input("\"", window, cx);
7512 editor.handle_input(" ", window, cx);
7513 editor.move_left(&Default::default(), window, cx);
7514 editor.handle_input("\\", window, cx);
7515 editor.handle_input("\"", window, cx);
7516 });
7517 cx.assert_editor_state(
7518 &r#"
7519 let x = "\"ˇ "
7520 "#
7521 .unindent(),
7522 );
7523
7524 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
7525 // mark. Nothing is inserted.
7526 cx.update_editor(|editor, window, cx| {
7527 editor.move_right(&Default::default(), window, cx);
7528 editor.handle_input("\"", window, cx);
7529 });
7530 cx.assert_editor_state(
7531 &r#"
7532 let x = "\" "ˇ
7533 "#
7534 .unindent(),
7535 );
7536}
7537
7538#[gpui::test]
7539async fn test_surround_with_pair(cx: &mut TestAppContext) {
7540 init_test(cx, |_| {});
7541
7542 let language = Arc::new(Language::new(
7543 LanguageConfig {
7544 brackets: BracketPairConfig {
7545 pairs: vec![
7546 BracketPair {
7547 start: "{".to_string(),
7548 end: "}".to_string(),
7549 close: true,
7550 surround: true,
7551 newline: true,
7552 },
7553 BracketPair {
7554 start: "/* ".to_string(),
7555 end: "*/".to_string(),
7556 close: true,
7557 surround: true,
7558 ..Default::default()
7559 },
7560 ],
7561 ..Default::default()
7562 },
7563 ..Default::default()
7564 },
7565 Some(tree_sitter_rust::LANGUAGE.into()),
7566 ));
7567
7568 let text = r#"
7569 a
7570 b
7571 c
7572 "#
7573 .unindent();
7574
7575 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
7576 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7577 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7578 editor
7579 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
7580 .await;
7581
7582 editor.update_in(cx, |editor, window, cx| {
7583 editor.change_selections(None, window, cx, |s| {
7584 s.select_display_ranges([
7585 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
7586 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
7587 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
7588 ])
7589 });
7590
7591 editor.handle_input("{", window, cx);
7592 editor.handle_input("{", window, cx);
7593 editor.handle_input("{", window, cx);
7594 assert_eq!(
7595 editor.text(cx),
7596 "
7597 {{{a}}}
7598 {{{b}}}
7599 {{{c}}}
7600 "
7601 .unindent()
7602 );
7603 assert_eq!(
7604 editor.selections.display_ranges(cx),
7605 [
7606 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
7607 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
7608 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
7609 ]
7610 );
7611
7612 editor.undo(&Undo, window, cx);
7613 editor.undo(&Undo, window, cx);
7614 editor.undo(&Undo, window, cx);
7615 assert_eq!(
7616 editor.text(cx),
7617 "
7618 a
7619 b
7620 c
7621 "
7622 .unindent()
7623 );
7624 assert_eq!(
7625 editor.selections.display_ranges(cx),
7626 [
7627 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
7628 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
7629 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
7630 ]
7631 );
7632
7633 // Ensure inserting the first character of a multi-byte bracket pair
7634 // doesn't surround the selections with the bracket.
7635 editor.handle_input("/", window, cx);
7636 assert_eq!(
7637 editor.text(cx),
7638 "
7639 /
7640 /
7641 /
7642 "
7643 .unindent()
7644 );
7645 assert_eq!(
7646 editor.selections.display_ranges(cx),
7647 [
7648 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
7649 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
7650 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
7651 ]
7652 );
7653
7654 editor.undo(&Undo, window, cx);
7655 assert_eq!(
7656 editor.text(cx),
7657 "
7658 a
7659 b
7660 c
7661 "
7662 .unindent()
7663 );
7664 assert_eq!(
7665 editor.selections.display_ranges(cx),
7666 [
7667 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
7668 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
7669 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
7670 ]
7671 );
7672
7673 // Ensure inserting the last character of a multi-byte bracket pair
7674 // doesn't surround the selections with the bracket.
7675 editor.handle_input("*", window, cx);
7676 assert_eq!(
7677 editor.text(cx),
7678 "
7679 *
7680 *
7681 *
7682 "
7683 .unindent()
7684 );
7685 assert_eq!(
7686 editor.selections.display_ranges(cx),
7687 [
7688 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
7689 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
7690 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
7691 ]
7692 );
7693 });
7694}
7695
7696#[gpui::test]
7697async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
7698 init_test(cx, |_| {});
7699
7700 let language = Arc::new(Language::new(
7701 LanguageConfig {
7702 brackets: BracketPairConfig {
7703 pairs: vec![BracketPair {
7704 start: "{".to_string(),
7705 end: "}".to_string(),
7706 close: true,
7707 surround: true,
7708 newline: true,
7709 }],
7710 ..Default::default()
7711 },
7712 autoclose_before: "}".to_string(),
7713 ..Default::default()
7714 },
7715 Some(tree_sitter_rust::LANGUAGE.into()),
7716 ));
7717
7718 let text = r#"
7719 a
7720 b
7721 c
7722 "#
7723 .unindent();
7724
7725 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
7726 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7727 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7728 editor
7729 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
7730 .await;
7731
7732 editor.update_in(cx, |editor, window, cx| {
7733 editor.change_selections(None, window, cx, |s| {
7734 s.select_ranges([
7735 Point::new(0, 1)..Point::new(0, 1),
7736 Point::new(1, 1)..Point::new(1, 1),
7737 Point::new(2, 1)..Point::new(2, 1),
7738 ])
7739 });
7740
7741 editor.handle_input("{", window, cx);
7742 editor.handle_input("{", window, cx);
7743 editor.handle_input("_", window, cx);
7744 assert_eq!(
7745 editor.text(cx),
7746 "
7747 a{{_}}
7748 b{{_}}
7749 c{{_}}
7750 "
7751 .unindent()
7752 );
7753 assert_eq!(
7754 editor.selections.ranges::<Point>(cx),
7755 [
7756 Point::new(0, 4)..Point::new(0, 4),
7757 Point::new(1, 4)..Point::new(1, 4),
7758 Point::new(2, 4)..Point::new(2, 4)
7759 ]
7760 );
7761
7762 editor.backspace(&Default::default(), window, cx);
7763 editor.backspace(&Default::default(), window, cx);
7764 assert_eq!(
7765 editor.text(cx),
7766 "
7767 a{}
7768 b{}
7769 c{}
7770 "
7771 .unindent()
7772 );
7773 assert_eq!(
7774 editor.selections.ranges::<Point>(cx),
7775 [
7776 Point::new(0, 2)..Point::new(0, 2),
7777 Point::new(1, 2)..Point::new(1, 2),
7778 Point::new(2, 2)..Point::new(2, 2)
7779 ]
7780 );
7781
7782 editor.delete_to_previous_word_start(&Default::default(), window, cx);
7783 assert_eq!(
7784 editor.text(cx),
7785 "
7786 a
7787 b
7788 c
7789 "
7790 .unindent()
7791 );
7792 assert_eq!(
7793 editor.selections.ranges::<Point>(cx),
7794 [
7795 Point::new(0, 1)..Point::new(0, 1),
7796 Point::new(1, 1)..Point::new(1, 1),
7797 Point::new(2, 1)..Point::new(2, 1)
7798 ]
7799 );
7800 });
7801}
7802
7803#[gpui::test]
7804async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
7805 init_test(cx, |settings| {
7806 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
7807 });
7808
7809 let mut cx = EditorTestContext::new(cx).await;
7810
7811 let language = Arc::new(Language::new(
7812 LanguageConfig {
7813 brackets: BracketPairConfig {
7814 pairs: vec![
7815 BracketPair {
7816 start: "{".to_string(),
7817 end: "}".to_string(),
7818 close: true,
7819 surround: true,
7820 newline: true,
7821 },
7822 BracketPair {
7823 start: "(".to_string(),
7824 end: ")".to_string(),
7825 close: true,
7826 surround: true,
7827 newline: true,
7828 },
7829 BracketPair {
7830 start: "[".to_string(),
7831 end: "]".to_string(),
7832 close: false,
7833 surround: true,
7834 newline: true,
7835 },
7836 ],
7837 ..Default::default()
7838 },
7839 autoclose_before: "})]".to_string(),
7840 ..Default::default()
7841 },
7842 Some(tree_sitter_rust::LANGUAGE.into()),
7843 ));
7844
7845 cx.language_registry().add(language.clone());
7846 cx.update_buffer(|buffer, cx| {
7847 buffer.set_language(Some(language), cx);
7848 });
7849
7850 cx.set_state(
7851 &"
7852 {(ˇ)}
7853 [[ˇ]]
7854 {(ˇ)}
7855 "
7856 .unindent(),
7857 );
7858
7859 cx.update_editor(|editor, window, cx| {
7860 editor.backspace(&Default::default(), window, cx);
7861 editor.backspace(&Default::default(), window, cx);
7862 });
7863
7864 cx.assert_editor_state(
7865 &"
7866 ˇ
7867 ˇ]]
7868 ˇ
7869 "
7870 .unindent(),
7871 );
7872
7873 cx.update_editor(|editor, window, cx| {
7874 editor.handle_input("{", window, cx);
7875 editor.handle_input("{", window, cx);
7876 editor.move_right(&MoveRight, window, cx);
7877 editor.move_right(&MoveRight, window, cx);
7878 editor.move_left(&MoveLeft, window, cx);
7879 editor.move_left(&MoveLeft, window, cx);
7880 editor.backspace(&Default::default(), window, cx);
7881 });
7882
7883 cx.assert_editor_state(
7884 &"
7885 {ˇ}
7886 {ˇ}]]
7887 {ˇ}
7888 "
7889 .unindent(),
7890 );
7891
7892 cx.update_editor(|editor, window, cx| {
7893 editor.backspace(&Default::default(), window, cx);
7894 });
7895
7896 cx.assert_editor_state(
7897 &"
7898 ˇ
7899 ˇ]]
7900 ˇ
7901 "
7902 .unindent(),
7903 );
7904}
7905
7906#[gpui::test]
7907async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
7908 init_test(cx, |_| {});
7909
7910 let language = Arc::new(Language::new(
7911 LanguageConfig::default(),
7912 Some(tree_sitter_rust::LANGUAGE.into()),
7913 ));
7914
7915 let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
7916 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7917 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7918 editor
7919 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
7920 .await;
7921
7922 editor.update_in(cx, |editor, window, cx| {
7923 editor.set_auto_replace_emoji_shortcode(true);
7924
7925 editor.handle_input("Hello ", window, cx);
7926 editor.handle_input(":wave", window, cx);
7927 assert_eq!(editor.text(cx), "Hello :wave".unindent());
7928
7929 editor.handle_input(":", window, cx);
7930 assert_eq!(editor.text(cx), "Hello 👋".unindent());
7931
7932 editor.handle_input(" :smile", window, cx);
7933 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
7934
7935 editor.handle_input(":", window, cx);
7936 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
7937
7938 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
7939 editor.handle_input(":wave", window, cx);
7940 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
7941
7942 editor.handle_input(":", window, cx);
7943 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
7944
7945 editor.handle_input(":1", window, cx);
7946 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
7947
7948 editor.handle_input(":", window, cx);
7949 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
7950
7951 // Ensure shortcode does not get replaced when it is part of a word
7952 editor.handle_input(" Test:wave", window, cx);
7953 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
7954
7955 editor.handle_input(":", window, cx);
7956 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
7957
7958 editor.set_auto_replace_emoji_shortcode(false);
7959
7960 // Ensure shortcode does not get replaced when auto replace is off
7961 editor.handle_input(" :wave", window, cx);
7962 assert_eq!(
7963 editor.text(cx),
7964 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
7965 );
7966
7967 editor.handle_input(":", window, cx);
7968 assert_eq!(
7969 editor.text(cx),
7970 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
7971 );
7972 });
7973}
7974
7975#[gpui::test]
7976async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
7977 init_test(cx, |_| {});
7978
7979 let (text, insertion_ranges) = marked_text_ranges(
7980 indoc! {"
7981 ˇ
7982 "},
7983 false,
7984 );
7985
7986 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
7987 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7988
7989 _ = editor.update_in(cx, |editor, window, cx| {
7990 let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
7991
7992 editor
7993 .insert_snippet(&insertion_ranges, snippet, window, cx)
7994 .unwrap();
7995
7996 fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
7997 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
7998 assert_eq!(editor.text(cx), expected_text);
7999 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
8000 }
8001
8002 assert(
8003 editor,
8004 cx,
8005 indoc! {"
8006 type «» =•
8007 "},
8008 );
8009
8010 assert!(editor.context_menu_visible(), "There should be a matches");
8011 });
8012}
8013
8014#[gpui::test]
8015async fn test_snippets(cx: &mut TestAppContext) {
8016 init_test(cx, |_| {});
8017
8018 let (text, insertion_ranges) = marked_text_ranges(
8019 indoc! {"
8020 a.ˇ b
8021 a.ˇ b
8022 a.ˇ b
8023 "},
8024 false,
8025 );
8026
8027 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
8028 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
8029
8030 editor.update_in(cx, |editor, window, cx| {
8031 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
8032
8033 editor
8034 .insert_snippet(&insertion_ranges, snippet, window, cx)
8035 .unwrap();
8036
8037 fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
8038 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
8039 assert_eq!(editor.text(cx), expected_text);
8040 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
8041 }
8042
8043 assert(
8044 editor,
8045 cx,
8046 indoc! {"
8047 a.f(«one», two, «three») b
8048 a.f(«one», two, «three») b
8049 a.f(«one», two, «three») b
8050 "},
8051 );
8052
8053 // Can't move earlier than the first tab stop
8054 assert!(!editor.move_to_prev_snippet_tabstop(window, cx));
8055 assert(
8056 editor,
8057 cx,
8058 indoc! {"
8059 a.f(«one», two, «three») b
8060 a.f(«one», two, «three») b
8061 a.f(«one», two, «three») b
8062 "},
8063 );
8064
8065 assert!(editor.move_to_next_snippet_tabstop(window, cx));
8066 assert(
8067 editor,
8068 cx,
8069 indoc! {"
8070 a.f(one, «two», three) b
8071 a.f(one, «two», three) b
8072 a.f(one, «two», three) b
8073 "},
8074 );
8075
8076 editor.move_to_prev_snippet_tabstop(window, cx);
8077 assert(
8078 editor,
8079 cx,
8080 indoc! {"
8081 a.f(«one», two, «three») b
8082 a.f(«one», two, «three») b
8083 a.f(«one», two, «three») b
8084 "},
8085 );
8086
8087 assert!(editor.move_to_next_snippet_tabstop(window, cx));
8088 assert(
8089 editor,
8090 cx,
8091 indoc! {"
8092 a.f(one, «two», three) b
8093 a.f(one, «two», three) b
8094 a.f(one, «two», three) b
8095 "},
8096 );
8097 assert!(editor.move_to_next_snippet_tabstop(window, cx));
8098 assert(
8099 editor,
8100 cx,
8101 indoc! {"
8102 a.f(one, two, three)ˇ b
8103 a.f(one, two, three)ˇ b
8104 a.f(one, two, three)ˇ b
8105 "},
8106 );
8107
8108 // As soon as the last tab stop is reached, snippet state is gone
8109 editor.move_to_prev_snippet_tabstop(window, cx);
8110 assert(
8111 editor,
8112 cx,
8113 indoc! {"
8114 a.f(one, two, three)ˇ b
8115 a.f(one, two, three)ˇ b
8116 a.f(one, two, three)ˇ b
8117 "},
8118 );
8119 });
8120}
8121
8122#[gpui::test]
8123async fn test_document_format_during_save(cx: &mut TestAppContext) {
8124 init_test(cx, |_| {});
8125
8126 let fs = FakeFs::new(cx.executor());
8127 fs.insert_file(path!("/file.rs"), Default::default()).await;
8128
8129 let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
8130
8131 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8132 language_registry.add(rust_lang());
8133 let mut fake_servers = language_registry.register_fake_lsp(
8134 "Rust",
8135 FakeLspAdapter {
8136 capabilities: lsp::ServerCapabilities {
8137 document_formatting_provider: Some(lsp::OneOf::Left(true)),
8138 ..Default::default()
8139 },
8140 ..Default::default()
8141 },
8142 );
8143
8144 let buffer = project
8145 .update(cx, |project, cx| {
8146 project.open_local_buffer(path!("/file.rs"), cx)
8147 })
8148 .await
8149 .unwrap();
8150
8151 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
8152 let (editor, cx) = cx.add_window_view(|window, cx| {
8153 build_editor_with_project(project.clone(), buffer, window, cx)
8154 });
8155 editor.update_in(cx, |editor, window, cx| {
8156 editor.set_text("one\ntwo\nthree\n", window, cx)
8157 });
8158 assert!(cx.read(|cx| editor.is_dirty(cx)));
8159
8160 cx.executor().start_waiting();
8161 let fake_server = fake_servers.next().await.unwrap();
8162
8163 {
8164 fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
8165 move |params, _| async move {
8166 assert_eq!(
8167 params.text_document.uri,
8168 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
8169 );
8170 assert_eq!(params.options.tab_size, 4);
8171 Ok(Some(vec![lsp::TextEdit::new(
8172 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
8173 ", ".to_string(),
8174 )]))
8175 },
8176 );
8177 let save = editor
8178 .update_in(cx, |editor, window, cx| {
8179 editor.save(true, project.clone(), window, cx)
8180 })
8181 .unwrap();
8182 cx.executor().start_waiting();
8183 save.await;
8184
8185 assert_eq!(
8186 editor.update(cx, |editor, cx| editor.text(cx)),
8187 "one, two\nthree\n"
8188 );
8189 assert!(!cx.read(|cx| editor.is_dirty(cx)));
8190 }
8191
8192 {
8193 editor.update_in(cx, |editor, window, cx| {
8194 editor.set_text("one\ntwo\nthree\n", window, cx)
8195 });
8196 assert!(cx.read(|cx| editor.is_dirty(cx)));
8197
8198 // Ensure we can still save even if formatting hangs.
8199 fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
8200 move |params, _| async move {
8201 assert_eq!(
8202 params.text_document.uri,
8203 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
8204 );
8205 futures::future::pending::<()>().await;
8206 unreachable!()
8207 },
8208 );
8209 let save = editor
8210 .update_in(cx, |editor, window, cx| {
8211 editor.save(true, project.clone(), window, cx)
8212 })
8213 .unwrap();
8214 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
8215 cx.executor().start_waiting();
8216 save.await;
8217 assert_eq!(
8218 editor.update(cx, |editor, cx| editor.text(cx)),
8219 "one\ntwo\nthree\n"
8220 );
8221 }
8222
8223 // For non-dirty buffer, no formatting request should be sent
8224 {
8225 assert!(!cx.read(|cx| editor.is_dirty(cx)));
8226
8227 fake_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
8228 panic!("Should not be invoked on non-dirty buffer");
8229 });
8230 let save = editor
8231 .update_in(cx, |editor, window, cx| {
8232 editor.save(true, project.clone(), window, cx)
8233 })
8234 .unwrap();
8235 cx.executor().start_waiting();
8236 save.await;
8237 }
8238
8239 // Set rust language override and assert overridden tabsize is sent to language server
8240 update_test_language_settings(cx, |settings| {
8241 settings.languages.insert(
8242 "Rust".into(),
8243 LanguageSettingsContent {
8244 tab_size: NonZeroU32::new(8),
8245 ..Default::default()
8246 },
8247 );
8248 });
8249
8250 {
8251 editor.update_in(cx, |editor, window, cx| {
8252 editor.set_text("somehting_new\n", window, cx)
8253 });
8254 assert!(cx.read(|cx| editor.is_dirty(cx)));
8255 let _formatting_request_signal = fake_server
8256 .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
8257 assert_eq!(
8258 params.text_document.uri,
8259 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
8260 );
8261 assert_eq!(params.options.tab_size, 8);
8262 Ok(Some(vec![]))
8263 });
8264 let save = editor
8265 .update_in(cx, |editor, window, cx| {
8266 editor.save(true, project.clone(), window, cx)
8267 })
8268 .unwrap();
8269 cx.executor().start_waiting();
8270 save.await;
8271 }
8272}
8273
8274#[gpui::test]
8275async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
8276 init_test(cx, |_| {});
8277
8278 let cols = 4;
8279 let rows = 10;
8280 let sample_text_1 = sample_text(rows, cols, 'a');
8281 assert_eq!(
8282 sample_text_1,
8283 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
8284 );
8285 let sample_text_2 = sample_text(rows, cols, 'l');
8286 assert_eq!(
8287 sample_text_2,
8288 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
8289 );
8290 let sample_text_3 = sample_text(rows, cols, 'v');
8291 assert_eq!(
8292 sample_text_3,
8293 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
8294 );
8295
8296 let fs = FakeFs::new(cx.executor());
8297 fs.insert_tree(
8298 path!("/a"),
8299 json!({
8300 "main.rs": sample_text_1,
8301 "other.rs": sample_text_2,
8302 "lib.rs": sample_text_3,
8303 }),
8304 )
8305 .await;
8306
8307 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
8308 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
8309 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
8310
8311 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8312 language_registry.add(rust_lang());
8313 let mut fake_servers = language_registry.register_fake_lsp(
8314 "Rust",
8315 FakeLspAdapter {
8316 capabilities: lsp::ServerCapabilities {
8317 document_formatting_provider: Some(lsp::OneOf::Left(true)),
8318 ..Default::default()
8319 },
8320 ..Default::default()
8321 },
8322 );
8323
8324 let worktree = project.update(cx, |project, cx| {
8325 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
8326 assert_eq!(worktrees.len(), 1);
8327 worktrees.pop().unwrap()
8328 });
8329 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
8330
8331 let buffer_1 = project
8332 .update(cx, |project, cx| {
8333 project.open_buffer((worktree_id, "main.rs"), cx)
8334 })
8335 .await
8336 .unwrap();
8337 let buffer_2 = project
8338 .update(cx, |project, cx| {
8339 project.open_buffer((worktree_id, "other.rs"), cx)
8340 })
8341 .await
8342 .unwrap();
8343 let buffer_3 = project
8344 .update(cx, |project, cx| {
8345 project.open_buffer((worktree_id, "lib.rs"), cx)
8346 })
8347 .await
8348 .unwrap();
8349
8350 let multi_buffer = cx.new(|cx| {
8351 let mut multi_buffer = MultiBuffer::new(ReadWrite);
8352 multi_buffer.push_excerpts(
8353 buffer_1.clone(),
8354 [
8355 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
8356 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
8357 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
8358 ],
8359 cx,
8360 );
8361 multi_buffer.push_excerpts(
8362 buffer_2.clone(),
8363 [
8364 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
8365 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
8366 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
8367 ],
8368 cx,
8369 );
8370 multi_buffer.push_excerpts(
8371 buffer_3.clone(),
8372 [
8373 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
8374 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
8375 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
8376 ],
8377 cx,
8378 );
8379 multi_buffer
8380 });
8381 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
8382 Editor::new(
8383 EditorMode::full(),
8384 multi_buffer,
8385 Some(project.clone()),
8386 window,
8387 cx,
8388 )
8389 });
8390
8391 multi_buffer_editor.update_in(cx, |editor, window, cx| {
8392 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
8393 s.select_ranges(Some(1..2))
8394 });
8395 editor.insert("|one|two|three|", window, cx);
8396 });
8397 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
8398 multi_buffer_editor.update_in(cx, |editor, window, cx| {
8399 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
8400 s.select_ranges(Some(60..70))
8401 });
8402 editor.insert("|four|five|six|", window, cx);
8403 });
8404 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
8405
8406 // First two buffers should be edited, but not the third one.
8407 assert_eq!(
8408 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
8409 "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}",
8410 );
8411 buffer_1.update(cx, |buffer, _| {
8412 assert!(buffer.is_dirty());
8413 assert_eq!(
8414 buffer.text(),
8415 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
8416 )
8417 });
8418 buffer_2.update(cx, |buffer, _| {
8419 assert!(buffer.is_dirty());
8420 assert_eq!(
8421 buffer.text(),
8422 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
8423 )
8424 });
8425 buffer_3.update(cx, |buffer, _| {
8426 assert!(!buffer.is_dirty());
8427 assert_eq!(buffer.text(), sample_text_3,)
8428 });
8429 cx.executor().run_until_parked();
8430
8431 cx.executor().start_waiting();
8432 let save = multi_buffer_editor
8433 .update_in(cx, |editor, window, cx| {
8434 editor.save(true, project.clone(), window, cx)
8435 })
8436 .unwrap();
8437
8438 let fake_server = fake_servers.next().await.unwrap();
8439 fake_server
8440 .server
8441 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
8442 Ok(Some(vec![lsp::TextEdit::new(
8443 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
8444 format!("[{} formatted]", params.text_document.uri),
8445 )]))
8446 })
8447 .detach();
8448 save.await;
8449
8450 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
8451 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
8452 assert_eq!(
8453 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
8454 uri!(
8455 "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}"
8456 ),
8457 );
8458 buffer_1.update(cx, |buffer, _| {
8459 assert!(!buffer.is_dirty());
8460 assert_eq!(
8461 buffer.text(),
8462 uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
8463 )
8464 });
8465 buffer_2.update(cx, |buffer, _| {
8466 assert!(!buffer.is_dirty());
8467 assert_eq!(
8468 buffer.text(),
8469 uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
8470 )
8471 });
8472 buffer_3.update(cx, |buffer, _| {
8473 assert!(!buffer.is_dirty());
8474 assert_eq!(buffer.text(), sample_text_3,)
8475 });
8476}
8477
8478#[gpui::test]
8479async fn test_range_format_during_save(cx: &mut TestAppContext) {
8480 init_test(cx, |_| {});
8481
8482 let fs = FakeFs::new(cx.executor());
8483 fs.insert_file(path!("/file.rs"), Default::default()).await;
8484
8485 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
8486
8487 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8488 language_registry.add(rust_lang());
8489 let mut fake_servers = language_registry.register_fake_lsp(
8490 "Rust",
8491 FakeLspAdapter {
8492 capabilities: lsp::ServerCapabilities {
8493 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
8494 ..Default::default()
8495 },
8496 ..Default::default()
8497 },
8498 );
8499
8500 let buffer = project
8501 .update(cx, |project, cx| {
8502 project.open_local_buffer(path!("/file.rs"), cx)
8503 })
8504 .await
8505 .unwrap();
8506
8507 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
8508 let (editor, cx) = cx.add_window_view(|window, cx| {
8509 build_editor_with_project(project.clone(), buffer, window, cx)
8510 });
8511 editor.update_in(cx, |editor, window, cx| {
8512 editor.set_text("one\ntwo\nthree\n", window, cx)
8513 });
8514 assert!(cx.read(|cx| editor.is_dirty(cx)));
8515
8516 cx.executor().start_waiting();
8517 let fake_server = fake_servers.next().await.unwrap();
8518
8519 let save = editor
8520 .update_in(cx, |editor, window, cx| {
8521 editor.save(true, project.clone(), window, cx)
8522 })
8523 .unwrap();
8524 fake_server
8525 .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
8526 assert_eq!(
8527 params.text_document.uri,
8528 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
8529 );
8530 assert_eq!(params.options.tab_size, 4);
8531 Ok(Some(vec![lsp::TextEdit::new(
8532 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
8533 ", ".to_string(),
8534 )]))
8535 })
8536 .next()
8537 .await;
8538 cx.executor().start_waiting();
8539 save.await;
8540 assert_eq!(
8541 editor.update(cx, |editor, cx| editor.text(cx)),
8542 "one, two\nthree\n"
8543 );
8544 assert!(!cx.read(|cx| editor.is_dirty(cx)));
8545
8546 editor.update_in(cx, |editor, window, cx| {
8547 editor.set_text("one\ntwo\nthree\n", window, cx)
8548 });
8549 assert!(cx.read(|cx| editor.is_dirty(cx)));
8550
8551 // Ensure we can still save even if formatting hangs.
8552 fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
8553 move |params, _| async move {
8554 assert_eq!(
8555 params.text_document.uri,
8556 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
8557 );
8558 futures::future::pending::<()>().await;
8559 unreachable!()
8560 },
8561 );
8562 let save = editor
8563 .update_in(cx, |editor, window, cx| {
8564 editor.save(true, project.clone(), window, cx)
8565 })
8566 .unwrap();
8567 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
8568 cx.executor().start_waiting();
8569 save.await;
8570 assert_eq!(
8571 editor.update(cx, |editor, cx| editor.text(cx)),
8572 "one\ntwo\nthree\n"
8573 );
8574 assert!(!cx.read(|cx| editor.is_dirty(cx)));
8575
8576 // For non-dirty buffer, no formatting request should be sent
8577 let save = editor
8578 .update_in(cx, |editor, window, cx| {
8579 editor.save(true, project.clone(), window, cx)
8580 })
8581 .unwrap();
8582 let _pending_format_request = fake_server
8583 .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
8584 panic!("Should not be invoked on non-dirty buffer");
8585 })
8586 .next();
8587 cx.executor().start_waiting();
8588 save.await;
8589
8590 // Set Rust language override and assert overridden tabsize is sent to language server
8591 update_test_language_settings(cx, |settings| {
8592 settings.languages.insert(
8593 "Rust".into(),
8594 LanguageSettingsContent {
8595 tab_size: NonZeroU32::new(8),
8596 ..Default::default()
8597 },
8598 );
8599 });
8600
8601 editor.update_in(cx, |editor, window, cx| {
8602 editor.set_text("somehting_new\n", window, cx)
8603 });
8604 assert!(cx.read(|cx| editor.is_dirty(cx)));
8605 let save = editor
8606 .update_in(cx, |editor, window, cx| {
8607 editor.save(true, project.clone(), window, cx)
8608 })
8609 .unwrap();
8610 fake_server
8611 .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
8612 assert_eq!(
8613 params.text_document.uri,
8614 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
8615 );
8616 assert_eq!(params.options.tab_size, 8);
8617 Ok(Some(vec![]))
8618 })
8619 .next()
8620 .await;
8621 cx.executor().start_waiting();
8622 save.await;
8623}
8624
8625#[gpui::test]
8626async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
8627 init_test(cx, |settings| {
8628 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
8629 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
8630 ))
8631 });
8632
8633 let fs = FakeFs::new(cx.executor());
8634 fs.insert_file(path!("/file.rs"), Default::default()).await;
8635
8636 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
8637
8638 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8639 language_registry.add(Arc::new(Language::new(
8640 LanguageConfig {
8641 name: "Rust".into(),
8642 matcher: LanguageMatcher {
8643 path_suffixes: vec!["rs".to_string()],
8644 ..Default::default()
8645 },
8646 ..LanguageConfig::default()
8647 },
8648 Some(tree_sitter_rust::LANGUAGE.into()),
8649 )));
8650 update_test_language_settings(cx, |settings| {
8651 // Enable Prettier formatting for the same buffer, and ensure
8652 // LSP is called instead of Prettier.
8653 settings.defaults.prettier = Some(PrettierSettings {
8654 allowed: true,
8655 ..PrettierSettings::default()
8656 });
8657 });
8658 let mut fake_servers = language_registry.register_fake_lsp(
8659 "Rust",
8660 FakeLspAdapter {
8661 capabilities: lsp::ServerCapabilities {
8662 document_formatting_provider: Some(lsp::OneOf::Left(true)),
8663 ..Default::default()
8664 },
8665 ..Default::default()
8666 },
8667 );
8668
8669 let buffer = project
8670 .update(cx, |project, cx| {
8671 project.open_local_buffer(path!("/file.rs"), cx)
8672 })
8673 .await
8674 .unwrap();
8675
8676 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
8677 let (editor, cx) = cx.add_window_view(|window, cx| {
8678 build_editor_with_project(project.clone(), buffer, window, cx)
8679 });
8680 editor.update_in(cx, |editor, window, cx| {
8681 editor.set_text("one\ntwo\nthree\n", window, cx)
8682 });
8683
8684 cx.executor().start_waiting();
8685 let fake_server = fake_servers.next().await.unwrap();
8686
8687 let format = editor
8688 .update_in(cx, |editor, window, cx| {
8689 editor.perform_format(
8690 project.clone(),
8691 FormatTrigger::Manual,
8692 FormatTarget::Buffers,
8693 window,
8694 cx,
8695 )
8696 })
8697 .unwrap();
8698 fake_server
8699 .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
8700 assert_eq!(
8701 params.text_document.uri,
8702 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
8703 );
8704 assert_eq!(params.options.tab_size, 4);
8705 Ok(Some(vec![lsp::TextEdit::new(
8706 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
8707 ", ".to_string(),
8708 )]))
8709 })
8710 .next()
8711 .await;
8712 cx.executor().start_waiting();
8713 format.await;
8714 assert_eq!(
8715 editor.update(cx, |editor, cx| editor.text(cx)),
8716 "one, two\nthree\n"
8717 );
8718
8719 editor.update_in(cx, |editor, window, cx| {
8720 editor.set_text("one\ntwo\nthree\n", window, cx)
8721 });
8722 // Ensure we don't lock if formatting hangs.
8723 fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
8724 move |params, _| async move {
8725 assert_eq!(
8726 params.text_document.uri,
8727 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
8728 );
8729 futures::future::pending::<()>().await;
8730 unreachable!()
8731 },
8732 );
8733 let format = editor
8734 .update_in(cx, |editor, window, cx| {
8735 editor.perform_format(
8736 project,
8737 FormatTrigger::Manual,
8738 FormatTarget::Buffers,
8739 window,
8740 cx,
8741 )
8742 })
8743 .unwrap();
8744 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
8745 cx.executor().start_waiting();
8746 format.await;
8747 assert_eq!(
8748 editor.update(cx, |editor, cx| editor.text(cx)),
8749 "one\ntwo\nthree\n"
8750 );
8751}
8752
8753#[gpui::test]
8754async fn test_multiple_formatters(cx: &mut TestAppContext) {
8755 init_test(cx, |settings| {
8756 settings.defaults.remove_trailing_whitespace_on_save = Some(true);
8757 settings.defaults.formatter =
8758 Some(language_settings::SelectedFormatter::List(FormatterList(
8759 vec![
8760 Formatter::LanguageServer { name: None },
8761 Formatter::CodeActions(
8762 [
8763 ("code-action-1".into(), true),
8764 ("code-action-2".into(), true),
8765 ]
8766 .into_iter()
8767 .collect(),
8768 ),
8769 ]
8770 .into(),
8771 )))
8772 });
8773
8774 let fs = FakeFs::new(cx.executor());
8775 fs.insert_file(path!("/file.rs"), "one \ntwo \nthree".into())
8776 .await;
8777
8778 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
8779 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8780 language_registry.add(rust_lang());
8781
8782 let mut fake_servers = language_registry.register_fake_lsp(
8783 "Rust",
8784 FakeLspAdapter {
8785 capabilities: lsp::ServerCapabilities {
8786 document_formatting_provider: Some(lsp::OneOf::Left(true)),
8787 execute_command_provider: Some(lsp::ExecuteCommandOptions {
8788 commands: vec!["the-command-for-code-action-1".into()],
8789 ..Default::default()
8790 }),
8791 code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
8792 ..Default::default()
8793 },
8794 ..Default::default()
8795 },
8796 );
8797
8798 let buffer = project
8799 .update(cx, |project, cx| {
8800 project.open_local_buffer(path!("/file.rs"), cx)
8801 })
8802 .await
8803 .unwrap();
8804
8805 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
8806 let (editor, cx) = cx.add_window_view(|window, cx| {
8807 build_editor_with_project(project.clone(), buffer, window, cx)
8808 });
8809
8810 cx.executor().start_waiting();
8811
8812 let fake_server = fake_servers.next().await.unwrap();
8813 fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
8814 move |_params, _| async move {
8815 Ok(Some(vec![lsp::TextEdit::new(
8816 lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
8817 "applied-formatting\n".to_string(),
8818 )]))
8819 },
8820 );
8821 fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
8822 move |params, _| async move {
8823 assert_eq!(
8824 params.context.only,
8825 Some(vec!["code-action-1".into(), "code-action-2".into()])
8826 );
8827 let uri = lsp::Url::from_file_path(path!("/file.rs")).unwrap();
8828 Ok(Some(vec![
8829 lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
8830 kind: Some("code-action-1".into()),
8831 edit: Some(lsp::WorkspaceEdit::new(
8832 [(
8833 uri.clone(),
8834 vec![lsp::TextEdit::new(
8835 lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
8836 "applied-code-action-1-edit\n".to_string(),
8837 )],
8838 )]
8839 .into_iter()
8840 .collect(),
8841 )),
8842 command: Some(lsp::Command {
8843 command: "the-command-for-code-action-1".into(),
8844 ..Default::default()
8845 }),
8846 ..Default::default()
8847 }),
8848 lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
8849 kind: Some("code-action-2".into()),
8850 edit: Some(lsp::WorkspaceEdit::new(
8851 [(
8852 uri.clone(),
8853 vec![lsp::TextEdit::new(
8854 lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
8855 "applied-code-action-2-edit\n".to_string(),
8856 )],
8857 )]
8858 .into_iter()
8859 .collect(),
8860 )),
8861 ..Default::default()
8862 }),
8863 ]))
8864 },
8865 );
8866
8867 fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
8868 move |params, _| async move { Ok(params) }
8869 });
8870
8871 let command_lock = Arc::new(futures::lock::Mutex::new(()));
8872 fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
8873 let fake = fake_server.clone();
8874 let lock = command_lock.clone();
8875 move |params, _| {
8876 assert_eq!(params.command, "the-command-for-code-action-1");
8877 let fake = fake.clone();
8878 let lock = lock.clone();
8879 async move {
8880 lock.lock().await;
8881 fake.server
8882 .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
8883 label: None,
8884 edit: lsp::WorkspaceEdit {
8885 changes: Some(
8886 [(
8887 lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
8888 vec![lsp::TextEdit {
8889 range: lsp::Range::new(
8890 lsp::Position::new(0, 0),
8891 lsp::Position::new(0, 0),
8892 ),
8893 new_text: "applied-code-action-1-command\n".into(),
8894 }],
8895 )]
8896 .into_iter()
8897 .collect(),
8898 ),
8899 ..Default::default()
8900 },
8901 })
8902 .await
8903 .into_response()
8904 .unwrap();
8905 Ok(Some(json!(null)))
8906 }
8907 }
8908 });
8909
8910 cx.executor().start_waiting();
8911 editor
8912 .update_in(cx, |editor, window, cx| {
8913 editor.perform_format(
8914 project.clone(),
8915 FormatTrigger::Manual,
8916 FormatTarget::Buffers,
8917 window,
8918 cx,
8919 )
8920 })
8921 .unwrap()
8922 .await;
8923 editor.update(cx, |editor, cx| {
8924 assert_eq!(
8925 editor.text(cx),
8926 r#"
8927 applied-code-action-2-edit
8928 applied-code-action-1-command
8929 applied-code-action-1-edit
8930 applied-formatting
8931 one
8932 two
8933 three
8934 "#
8935 .unindent()
8936 );
8937 });
8938
8939 editor.update_in(cx, |editor, window, cx| {
8940 editor.undo(&Default::default(), window, cx);
8941 assert_eq!(editor.text(cx), "one \ntwo \nthree");
8942 });
8943
8944 // Perform a manual edit while waiting for an LSP command
8945 // that's being run as part of a formatting code action.
8946 let lock_guard = command_lock.lock().await;
8947 let format = editor
8948 .update_in(cx, |editor, window, cx| {
8949 editor.perform_format(
8950 project.clone(),
8951 FormatTrigger::Manual,
8952 FormatTarget::Buffers,
8953 window,
8954 cx,
8955 )
8956 })
8957 .unwrap();
8958 cx.run_until_parked();
8959 editor.update(cx, |editor, cx| {
8960 assert_eq!(
8961 editor.text(cx),
8962 r#"
8963 applied-code-action-1-edit
8964 applied-formatting
8965 one
8966 two
8967 three
8968 "#
8969 .unindent()
8970 );
8971
8972 editor.buffer.update(cx, |buffer, cx| {
8973 let ix = buffer.len(cx);
8974 buffer.edit([(ix..ix, "edited\n")], None, cx);
8975 });
8976 });
8977
8978 // Allow the LSP command to proceed. Because the buffer was edited,
8979 // the second code action will not be run.
8980 drop(lock_guard);
8981 format.await;
8982 editor.update_in(cx, |editor, window, cx| {
8983 assert_eq!(
8984 editor.text(cx),
8985 r#"
8986 applied-code-action-1-command
8987 applied-code-action-1-edit
8988 applied-formatting
8989 one
8990 two
8991 three
8992 edited
8993 "#
8994 .unindent()
8995 );
8996
8997 // The manual edit is undone first, because it is the last thing the user did
8998 // (even though the command completed afterwards).
8999 editor.undo(&Default::default(), window, cx);
9000 assert_eq!(
9001 editor.text(cx),
9002 r#"
9003 applied-code-action-1-command
9004 applied-code-action-1-edit
9005 applied-formatting
9006 one
9007 two
9008 three
9009 "#
9010 .unindent()
9011 );
9012
9013 // All the formatting (including the command, which completed after the manual edit)
9014 // is undone together.
9015 editor.undo(&Default::default(), window, cx);
9016 assert_eq!(editor.text(cx), "one \ntwo \nthree");
9017 });
9018}
9019
9020#[gpui::test]
9021async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
9022 init_test(cx, |settings| {
9023 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
9024 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
9025 ))
9026 });
9027
9028 let fs = FakeFs::new(cx.executor());
9029 fs.insert_file(path!("/file.ts"), Default::default()).await;
9030
9031 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
9032
9033 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9034 language_registry.add(Arc::new(Language::new(
9035 LanguageConfig {
9036 name: "TypeScript".into(),
9037 matcher: LanguageMatcher {
9038 path_suffixes: vec!["ts".to_string()],
9039 ..Default::default()
9040 },
9041 ..LanguageConfig::default()
9042 },
9043 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
9044 )));
9045 update_test_language_settings(cx, |settings| {
9046 settings.defaults.prettier = Some(PrettierSettings {
9047 allowed: true,
9048 ..PrettierSettings::default()
9049 });
9050 });
9051 let mut fake_servers = language_registry.register_fake_lsp(
9052 "TypeScript",
9053 FakeLspAdapter {
9054 capabilities: lsp::ServerCapabilities {
9055 code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
9056 ..Default::default()
9057 },
9058 ..Default::default()
9059 },
9060 );
9061
9062 let buffer = project
9063 .update(cx, |project, cx| {
9064 project.open_local_buffer(path!("/file.ts"), cx)
9065 })
9066 .await
9067 .unwrap();
9068
9069 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
9070 let (editor, cx) = cx.add_window_view(|window, cx| {
9071 build_editor_with_project(project.clone(), buffer, window, cx)
9072 });
9073 editor.update_in(cx, |editor, window, cx| {
9074 editor.set_text(
9075 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
9076 window,
9077 cx,
9078 )
9079 });
9080
9081 cx.executor().start_waiting();
9082 let fake_server = fake_servers.next().await.unwrap();
9083
9084 let format = editor
9085 .update_in(cx, |editor, window, cx| {
9086 editor.perform_code_action_kind(
9087 project.clone(),
9088 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
9089 window,
9090 cx,
9091 )
9092 })
9093 .unwrap();
9094 fake_server
9095 .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
9096 assert_eq!(
9097 params.text_document.uri,
9098 lsp::Url::from_file_path(path!("/file.ts")).unwrap()
9099 );
9100 Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
9101 lsp::CodeAction {
9102 title: "Organize Imports".to_string(),
9103 kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
9104 edit: Some(lsp::WorkspaceEdit {
9105 changes: Some(
9106 [(
9107 params.text_document.uri.clone(),
9108 vec![lsp::TextEdit::new(
9109 lsp::Range::new(
9110 lsp::Position::new(1, 0),
9111 lsp::Position::new(2, 0),
9112 ),
9113 "".to_string(),
9114 )],
9115 )]
9116 .into_iter()
9117 .collect(),
9118 ),
9119 ..Default::default()
9120 }),
9121 ..Default::default()
9122 },
9123 )]))
9124 })
9125 .next()
9126 .await;
9127 cx.executor().start_waiting();
9128 format.await;
9129 assert_eq!(
9130 editor.update(cx, |editor, cx| editor.text(cx)),
9131 "import { a } from 'module';\n\nconst x = a;\n"
9132 );
9133
9134 editor.update_in(cx, |editor, window, cx| {
9135 editor.set_text(
9136 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
9137 window,
9138 cx,
9139 )
9140 });
9141 // Ensure we don't lock if code action hangs.
9142 fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
9143 move |params, _| async move {
9144 assert_eq!(
9145 params.text_document.uri,
9146 lsp::Url::from_file_path(path!("/file.ts")).unwrap()
9147 );
9148 futures::future::pending::<()>().await;
9149 unreachable!()
9150 },
9151 );
9152 let format = editor
9153 .update_in(cx, |editor, window, cx| {
9154 editor.perform_code_action_kind(
9155 project,
9156 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
9157 window,
9158 cx,
9159 )
9160 })
9161 .unwrap();
9162 cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
9163 cx.executor().start_waiting();
9164 format.await;
9165 assert_eq!(
9166 editor.update(cx, |editor, cx| editor.text(cx)),
9167 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
9168 );
9169}
9170
9171#[gpui::test]
9172async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
9173 init_test(cx, |_| {});
9174
9175 let mut cx = EditorLspTestContext::new_rust(
9176 lsp::ServerCapabilities {
9177 document_formatting_provider: Some(lsp::OneOf::Left(true)),
9178 ..Default::default()
9179 },
9180 cx,
9181 )
9182 .await;
9183
9184 cx.set_state(indoc! {"
9185 one.twoˇ
9186 "});
9187
9188 // The format request takes a long time. When it completes, it inserts
9189 // a newline and an indent before the `.`
9190 cx.lsp
9191 .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
9192 let executor = cx.background_executor().clone();
9193 async move {
9194 executor.timer(Duration::from_millis(100)).await;
9195 Ok(Some(vec![lsp::TextEdit {
9196 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
9197 new_text: "\n ".into(),
9198 }]))
9199 }
9200 });
9201
9202 // Submit a format request.
9203 let format_1 = cx
9204 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
9205 .unwrap();
9206 cx.executor().run_until_parked();
9207
9208 // Submit a second format request.
9209 let format_2 = cx
9210 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
9211 .unwrap();
9212 cx.executor().run_until_parked();
9213
9214 // Wait for both format requests to complete
9215 cx.executor().advance_clock(Duration::from_millis(200));
9216 cx.executor().start_waiting();
9217 format_1.await.unwrap();
9218 cx.executor().start_waiting();
9219 format_2.await.unwrap();
9220
9221 // The formatting edits only happens once.
9222 cx.assert_editor_state(indoc! {"
9223 one
9224 .twoˇ
9225 "});
9226}
9227
9228#[gpui::test]
9229async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
9230 init_test(cx, |settings| {
9231 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
9232 });
9233
9234 let mut cx = EditorLspTestContext::new_rust(
9235 lsp::ServerCapabilities {
9236 document_formatting_provider: Some(lsp::OneOf::Left(true)),
9237 ..Default::default()
9238 },
9239 cx,
9240 )
9241 .await;
9242
9243 // Set up a buffer white some trailing whitespace and no trailing newline.
9244 cx.set_state(
9245 &[
9246 "one ", //
9247 "twoˇ", //
9248 "three ", //
9249 "four", //
9250 ]
9251 .join("\n"),
9252 );
9253
9254 // Submit a format request.
9255 let format = cx
9256 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
9257 .unwrap();
9258
9259 // Record which buffer changes have been sent to the language server
9260 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
9261 cx.lsp
9262 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
9263 let buffer_changes = buffer_changes.clone();
9264 move |params, _| {
9265 buffer_changes.lock().extend(
9266 params
9267 .content_changes
9268 .into_iter()
9269 .map(|e| (e.range.unwrap(), e.text)),
9270 );
9271 }
9272 });
9273
9274 // Handle formatting requests to the language server.
9275 cx.lsp
9276 .set_request_handler::<lsp::request::Formatting, _, _>({
9277 let buffer_changes = buffer_changes.clone();
9278 move |_, _| {
9279 // When formatting is requested, trailing whitespace has already been stripped,
9280 // and the trailing newline has already been added.
9281 assert_eq!(
9282 &buffer_changes.lock()[1..],
9283 &[
9284 (
9285 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
9286 "".into()
9287 ),
9288 (
9289 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
9290 "".into()
9291 ),
9292 (
9293 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
9294 "\n".into()
9295 ),
9296 ]
9297 );
9298
9299 // Insert blank lines between each line of the buffer.
9300 async move {
9301 Ok(Some(vec![
9302 lsp::TextEdit {
9303 range: lsp::Range::new(
9304 lsp::Position::new(1, 0),
9305 lsp::Position::new(1, 0),
9306 ),
9307 new_text: "\n".into(),
9308 },
9309 lsp::TextEdit {
9310 range: lsp::Range::new(
9311 lsp::Position::new(2, 0),
9312 lsp::Position::new(2, 0),
9313 ),
9314 new_text: "\n".into(),
9315 },
9316 ]))
9317 }
9318 }
9319 });
9320
9321 // After formatting the buffer, the trailing whitespace is stripped,
9322 // a newline is appended, and the edits provided by the language server
9323 // have been applied.
9324 format.await.unwrap();
9325 cx.assert_editor_state(
9326 &[
9327 "one", //
9328 "", //
9329 "twoˇ", //
9330 "", //
9331 "three", //
9332 "four", //
9333 "", //
9334 ]
9335 .join("\n"),
9336 );
9337
9338 // Undoing the formatting undoes the trailing whitespace removal, the
9339 // trailing newline, and the LSP edits.
9340 cx.update_buffer(|buffer, cx| buffer.undo(cx));
9341 cx.assert_editor_state(
9342 &[
9343 "one ", //
9344 "twoˇ", //
9345 "three ", //
9346 "four", //
9347 ]
9348 .join("\n"),
9349 );
9350}
9351
9352#[gpui::test]
9353async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
9354 cx: &mut TestAppContext,
9355) {
9356 init_test(cx, |_| {});
9357
9358 cx.update(|cx| {
9359 cx.update_global::<SettingsStore, _>(|settings, cx| {
9360 settings.update_user_settings::<EditorSettings>(cx, |settings| {
9361 settings.auto_signature_help = Some(true);
9362 });
9363 });
9364 });
9365
9366 let mut cx = EditorLspTestContext::new_rust(
9367 lsp::ServerCapabilities {
9368 signature_help_provider: Some(lsp::SignatureHelpOptions {
9369 ..Default::default()
9370 }),
9371 ..Default::default()
9372 },
9373 cx,
9374 )
9375 .await;
9376
9377 let language = Language::new(
9378 LanguageConfig {
9379 name: "Rust".into(),
9380 brackets: BracketPairConfig {
9381 pairs: vec![
9382 BracketPair {
9383 start: "{".to_string(),
9384 end: "}".to_string(),
9385 close: true,
9386 surround: true,
9387 newline: true,
9388 },
9389 BracketPair {
9390 start: "(".to_string(),
9391 end: ")".to_string(),
9392 close: true,
9393 surround: true,
9394 newline: true,
9395 },
9396 BracketPair {
9397 start: "/*".to_string(),
9398 end: " */".to_string(),
9399 close: true,
9400 surround: true,
9401 newline: true,
9402 },
9403 BracketPair {
9404 start: "[".to_string(),
9405 end: "]".to_string(),
9406 close: false,
9407 surround: false,
9408 newline: true,
9409 },
9410 BracketPair {
9411 start: "\"".to_string(),
9412 end: "\"".to_string(),
9413 close: true,
9414 surround: true,
9415 newline: false,
9416 },
9417 BracketPair {
9418 start: "<".to_string(),
9419 end: ">".to_string(),
9420 close: false,
9421 surround: true,
9422 newline: true,
9423 },
9424 ],
9425 ..Default::default()
9426 },
9427 autoclose_before: "})]".to_string(),
9428 ..Default::default()
9429 },
9430 Some(tree_sitter_rust::LANGUAGE.into()),
9431 );
9432 let language = Arc::new(language);
9433
9434 cx.language_registry().add(language.clone());
9435 cx.update_buffer(|buffer, cx| {
9436 buffer.set_language(Some(language), cx);
9437 });
9438
9439 cx.set_state(
9440 &r#"
9441 fn main() {
9442 sampleˇ
9443 }
9444 "#
9445 .unindent(),
9446 );
9447
9448 cx.update_editor(|editor, window, cx| {
9449 editor.handle_input("(", window, cx);
9450 });
9451 cx.assert_editor_state(
9452 &"
9453 fn main() {
9454 sample(ˇ)
9455 }
9456 "
9457 .unindent(),
9458 );
9459
9460 let mocked_response = lsp::SignatureHelp {
9461 signatures: vec![lsp::SignatureInformation {
9462 label: "fn sample(param1: u8, param2: u8)".to_string(),
9463 documentation: None,
9464 parameters: Some(vec![
9465 lsp::ParameterInformation {
9466 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
9467 documentation: None,
9468 },
9469 lsp::ParameterInformation {
9470 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
9471 documentation: None,
9472 },
9473 ]),
9474 active_parameter: None,
9475 }],
9476 active_signature: Some(0),
9477 active_parameter: Some(0),
9478 };
9479 handle_signature_help_request(&mut cx, mocked_response).await;
9480
9481 cx.condition(|editor, _| editor.signature_help_state.is_shown())
9482 .await;
9483
9484 cx.editor(|editor, _, _| {
9485 let signature_help_state = editor.signature_help_state.popover().cloned();
9486 assert_eq!(
9487 signature_help_state.unwrap().label,
9488 "param1: u8, param2: u8"
9489 );
9490 });
9491}
9492
9493#[gpui::test]
9494async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
9495 init_test(cx, |_| {});
9496
9497 cx.update(|cx| {
9498 cx.update_global::<SettingsStore, _>(|settings, cx| {
9499 settings.update_user_settings::<EditorSettings>(cx, |settings| {
9500 settings.auto_signature_help = Some(false);
9501 settings.show_signature_help_after_edits = Some(false);
9502 });
9503 });
9504 });
9505
9506 let mut cx = EditorLspTestContext::new_rust(
9507 lsp::ServerCapabilities {
9508 signature_help_provider: Some(lsp::SignatureHelpOptions {
9509 ..Default::default()
9510 }),
9511 ..Default::default()
9512 },
9513 cx,
9514 )
9515 .await;
9516
9517 let language = Language::new(
9518 LanguageConfig {
9519 name: "Rust".into(),
9520 brackets: BracketPairConfig {
9521 pairs: vec![
9522 BracketPair {
9523 start: "{".to_string(),
9524 end: "}".to_string(),
9525 close: true,
9526 surround: true,
9527 newline: true,
9528 },
9529 BracketPair {
9530 start: "(".to_string(),
9531 end: ")".to_string(),
9532 close: true,
9533 surround: true,
9534 newline: true,
9535 },
9536 BracketPair {
9537 start: "/*".to_string(),
9538 end: " */".to_string(),
9539 close: true,
9540 surround: true,
9541 newline: true,
9542 },
9543 BracketPair {
9544 start: "[".to_string(),
9545 end: "]".to_string(),
9546 close: false,
9547 surround: false,
9548 newline: true,
9549 },
9550 BracketPair {
9551 start: "\"".to_string(),
9552 end: "\"".to_string(),
9553 close: true,
9554 surround: true,
9555 newline: false,
9556 },
9557 BracketPair {
9558 start: "<".to_string(),
9559 end: ">".to_string(),
9560 close: false,
9561 surround: true,
9562 newline: true,
9563 },
9564 ],
9565 ..Default::default()
9566 },
9567 autoclose_before: "})]".to_string(),
9568 ..Default::default()
9569 },
9570 Some(tree_sitter_rust::LANGUAGE.into()),
9571 );
9572 let language = Arc::new(language);
9573
9574 cx.language_registry().add(language.clone());
9575 cx.update_buffer(|buffer, cx| {
9576 buffer.set_language(Some(language), cx);
9577 });
9578
9579 // Ensure that signature_help is not called when no signature help is enabled.
9580 cx.set_state(
9581 &r#"
9582 fn main() {
9583 sampleˇ
9584 }
9585 "#
9586 .unindent(),
9587 );
9588 cx.update_editor(|editor, window, cx| {
9589 editor.handle_input("(", window, cx);
9590 });
9591 cx.assert_editor_state(
9592 &"
9593 fn main() {
9594 sample(ˇ)
9595 }
9596 "
9597 .unindent(),
9598 );
9599 cx.editor(|editor, _, _| {
9600 assert!(editor.signature_help_state.task().is_none());
9601 });
9602
9603 let mocked_response = lsp::SignatureHelp {
9604 signatures: vec![lsp::SignatureInformation {
9605 label: "fn sample(param1: u8, param2: u8)".to_string(),
9606 documentation: None,
9607 parameters: Some(vec![
9608 lsp::ParameterInformation {
9609 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
9610 documentation: None,
9611 },
9612 lsp::ParameterInformation {
9613 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
9614 documentation: None,
9615 },
9616 ]),
9617 active_parameter: None,
9618 }],
9619 active_signature: Some(0),
9620 active_parameter: Some(0),
9621 };
9622
9623 // Ensure that signature_help is called when enabled afte edits
9624 cx.update(|_, cx| {
9625 cx.update_global::<SettingsStore, _>(|settings, cx| {
9626 settings.update_user_settings::<EditorSettings>(cx, |settings| {
9627 settings.auto_signature_help = Some(false);
9628 settings.show_signature_help_after_edits = Some(true);
9629 });
9630 });
9631 });
9632 cx.set_state(
9633 &r#"
9634 fn main() {
9635 sampleˇ
9636 }
9637 "#
9638 .unindent(),
9639 );
9640 cx.update_editor(|editor, window, cx| {
9641 editor.handle_input("(", window, cx);
9642 });
9643 cx.assert_editor_state(
9644 &"
9645 fn main() {
9646 sample(ˇ)
9647 }
9648 "
9649 .unindent(),
9650 );
9651 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
9652 cx.condition(|editor, _| editor.signature_help_state.is_shown())
9653 .await;
9654 cx.update_editor(|editor, _, _| {
9655 let signature_help_state = editor.signature_help_state.popover().cloned();
9656 assert!(signature_help_state.is_some());
9657 assert_eq!(
9658 signature_help_state.unwrap().label,
9659 "param1: u8, param2: u8"
9660 );
9661 editor.signature_help_state = SignatureHelpState::default();
9662 });
9663
9664 // Ensure that signature_help is called when auto signature help override is enabled
9665 cx.update(|_, cx| {
9666 cx.update_global::<SettingsStore, _>(|settings, cx| {
9667 settings.update_user_settings::<EditorSettings>(cx, |settings| {
9668 settings.auto_signature_help = Some(true);
9669 settings.show_signature_help_after_edits = Some(false);
9670 });
9671 });
9672 });
9673 cx.set_state(
9674 &r#"
9675 fn main() {
9676 sampleˇ
9677 }
9678 "#
9679 .unindent(),
9680 );
9681 cx.update_editor(|editor, window, cx| {
9682 editor.handle_input("(", window, cx);
9683 });
9684 cx.assert_editor_state(
9685 &"
9686 fn main() {
9687 sample(ˇ)
9688 }
9689 "
9690 .unindent(),
9691 );
9692 handle_signature_help_request(&mut cx, mocked_response).await;
9693 cx.condition(|editor, _| editor.signature_help_state.is_shown())
9694 .await;
9695 cx.editor(|editor, _, _| {
9696 let signature_help_state = editor.signature_help_state.popover().cloned();
9697 assert!(signature_help_state.is_some());
9698 assert_eq!(
9699 signature_help_state.unwrap().label,
9700 "param1: u8, param2: u8"
9701 );
9702 });
9703}
9704
9705#[gpui::test]
9706async fn test_signature_help(cx: &mut TestAppContext) {
9707 init_test(cx, |_| {});
9708 cx.update(|cx| {
9709 cx.update_global::<SettingsStore, _>(|settings, cx| {
9710 settings.update_user_settings::<EditorSettings>(cx, |settings| {
9711 settings.auto_signature_help = Some(true);
9712 });
9713 });
9714 });
9715
9716 let mut cx = EditorLspTestContext::new_rust(
9717 lsp::ServerCapabilities {
9718 signature_help_provider: Some(lsp::SignatureHelpOptions {
9719 ..Default::default()
9720 }),
9721 ..Default::default()
9722 },
9723 cx,
9724 )
9725 .await;
9726
9727 // A test that directly calls `show_signature_help`
9728 cx.update_editor(|editor, window, cx| {
9729 editor.show_signature_help(&ShowSignatureHelp, window, cx);
9730 });
9731
9732 let mocked_response = lsp::SignatureHelp {
9733 signatures: vec![lsp::SignatureInformation {
9734 label: "fn sample(param1: u8, param2: u8)".to_string(),
9735 documentation: None,
9736 parameters: Some(vec![
9737 lsp::ParameterInformation {
9738 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
9739 documentation: None,
9740 },
9741 lsp::ParameterInformation {
9742 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
9743 documentation: None,
9744 },
9745 ]),
9746 active_parameter: None,
9747 }],
9748 active_signature: Some(0),
9749 active_parameter: Some(0),
9750 };
9751 handle_signature_help_request(&mut cx, mocked_response).await;
9752
9753 cx.condition(|editor, _| editor.signature_help_state.is_shown())
9754 .await;
9755
9756 cx.editor(|editor, _, _| {
9757 let signature_help_state = editor.signature_help_state.popover().cloned();
9758 assert!(signature_help_state.is_some());
9759 assert_eq!(
9760 signature_help_state.unwrap().label,
9761 "param1: u8, param2: u8"
9762 );
9763 });
9764
9765 // When exiting outside from inside the brackets, `signature_help` is closed.
9766 cx.set_state(indoc! {"
9767 fn main() {
9768 sample(ˇ);
9769 }
9770
9771 fn sample(param1: u8, param2: u8) {}
9772 "});
9773
9774 cx.update_editor(|editor, window, cx| {
9775 editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
9776 });
9777
9778 let mocked_response = lsp::SignatureHelp {
9779 signatures: Vec::new(),
9780 active_signature: None,
9781 active_parameter: None,
9782 };
9783 handle_signature_help_request(&mut cx, mocked_response).await;
9784
9785 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
9786 .await;
9787
9788 cx.editor(|editor, _, _| {
9789 assert!(!editor.signature_help_state.is_shown());
9790 });
9791
9792 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
9793 cx.set_state(indoc! {"
9794 fn main() {
9795 sample(ˇ);
9796 }
9797
9798 fn sample(param1: u8, param2: u8) {}
9799 "});
9800
9801 let mocked_response = lsp::SignatureHelp {
9802 signatures: vec![lsp::SignatureInformation {
9803 label: "fn sample(param1: u8, param2: u8)".to_string(),
9804 documentation: None,
9805 parameters: Some(vec![
9806 lsp::ParameterInformation {
9807 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
9808 documentation: None,
9809 },
9810 lsp::ParameterInformation {
9811 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
9812 documentation: None,
9813 },
9814 ]),
9815 active_parameter: None,
9816 }],
9817 active_signature: Some(0),
9818 active_parameter: Some(0),
9819 };
9820 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
9821 cx.condition(|editor, _| editor.signature_help_state.is_shown())
9822 .await;
9823 cx.editor(|editor, _, _| {
9824 assert!(editor.signature_help_state.is_shown());
9825 });
9826
9827 // Restore the popover with more parameter input
9828 cx.set_state(indoc! {"
9829 fn main() {
9830 sample(param1, param2ˇ);
9831 }
9832
9833 fn sample(param1: u8, param2: u8) {}
9834 "});
9835
9836 let mocked_response = lsp::SignatureHelp {
9837 signatures: vec![lsp::SignatureInformation {
9838 label: "fn sample(param1: u8, param2: u8)".to_string(),
9839 documentation: None,
9840 parameters: Some(vec![
9841 lsp::ParameterInformation {
9842 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
9843 documentation: None,
9844 },
9845 lsp::ParameterInformation {
9846 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
9847 documentation: None,
9848 },
9849 ]),
9850 active_parameter: None,
9851 }],
9852 active_signature: Some(0),
9853 active_parameter: Some(1),
9854 };
9855 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
9856 cx.condition(|editor, _| editor.signature_help_state.is_shown())
9857 .await;
9858
9859 // When selecting a range, the popover is gone.
9860 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
9861 cx.update_editor(|editor, window, cx| {
9862 editor.change_selections(None, window, cx, |s| {
9863 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
9864 })
9865 });
9866 cx.assert_editor_state(indoc! {"
9867 fn main() {
9868 sample(param1, «ˇparam2»);
9869 }
9870
9871 fn sample(param1: u8, param2: u8) {}
9872 "});
9873 cx.editor(|editor, _, _| {
9874 assert!(!editor.signature_help_state.is_shown());
9875 });
9876
9877 // When unselecting again, the popover is back if within the brackets.
9878 cx.update_editor(|editor, window, cx| {
9879 editor.change_selections(None, window, cx, |s| {
9880 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
9881 })
9882 });
9883 cx.assert_editor_state(indoc! {"
9884 fn main() {
9885 sample(param1, ˇparam2);
9886 }
9887
9888 fn sample(param1: u8, param2: u8) {}
9889 "});
9890 handle_signature_help_request(&mut cx, mocked_response).await;
9891 cx.condition(|editor, _| editor.signature_help_state.is_shown())
9892 .await;
9893 cx.editor(|editor, _, _| {
9894 assert!(editor.signature_help_state.is_shown());
9895 });
9896
9897 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
9898 cx.update_editor(|editor, window, cx| {
9899 editor.change_selections(None, window, cx, |s| {
9900 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
9901 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
9902 })
9903 });
9904 cx.assert_editor_state(indoc! {"
9905 fn main() {
9906 sample(param1, ˇparam2);
9907 }
9908
9909 fn sample(param1: u8, param2: u8) {}
9910 "});
9911
9912 let mocked_response = lsp::SignatureHelp {
9913 signatures: vec![lsp::SignatureInformation {
9914 label: "fn sample(param1: u8, param2: u8)".to_string(),
9915 documentation: None,
9916 parameters: Some(vec![
9917 lsp::ParameterInformation {
9918 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
9919 documentation: None,
9920 },
9921 lsp::ParameterInformation {
9922 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
9923 documentation: None,
9924 },
9925 ]),
9926 active_parameter: None,
9927 }],
9928 active_signature: Some(0),
9929 active_parameter: Some(1),
9930 };
9931 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
9932 cx.condition(|editor, _| editor.signature_help_state.is_shown())
9933 .await;
9934 cx.update_editor(|editor, _, cx| {
9935 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
9936 });
9937 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
9938 .await;
9939 cx.update_editor(|editor, window, cx| {
9940 editor.change_selections(None, window, cx, |s| {
9941 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
9942 })
9943 });
9944 cx.assert_editor_state(indoc! {"
9945 fn main() {
9946 sample(param1, «ˇparam2»);
9947 }
9948
9949 fn sample(param1: u8, param2: u8) {}
9950 "});
9951 cx.update_editor(|editor, window, cx| {
9952 editor.change_selections(None, window, cx, |s| {
9953 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
9954 })
9955 });
9956 cx.assert_editor_state(indoc! {"
9957 fn main() {
9958 sample(param1, ˇparam2);
9959 }
9960
9961 fn sample(param1: u8, param2: u8) {}
9962 "});
9963 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
9964 .await;
9965}
9966
9967#[gpui::test]
9968async fn test_completion_mode(cx: &mut TestAppContext) {
9969 init_test(cx, |_| {});
9970 let mut cx = EditorLspTestContext::new_rust(
9971 lsp::ServerCapabilities {
9972 completion_provider: Some(lsp::CompletionOptions {
9973 resolve_provider: Some(true),
9974 ..Default::default()
9975 }),
9976 ..Default::default()
9977 },
9978 cx,
9979 )
9980 .await;
9981
9982 struct Run {
9983 run_description: &'static str,
9984 initial_state: String,
9985 buffer_marked_text: String,
9986 completion_text: &'static str,
9987 expected_with_insert_mode: String,
9988 expected_with_replace_mode: String,
9989 expected_with_replace_subsequence_mode: String,
9990 expected_with_replace_suffix_mode: String,
9991 }
9992
9993 let runs = [
9994 Run {
9995 run_description: "Start of word matches completion text",
9996 initial_state: "before ediˇ after".into(),
9997 buffer_marked_text: "before <edi|> after".into(),
9998 completion_text: "editor",
9999 expected_with_insert_mode: "before editorˇ after".into(),
10000 expected_with_replace_mode: "before editorˇ after".into(),
10001 expected_with_replace_subsequence_mode: "before editorˇ after".into(),
10002 expected_with_replace_suffix_mode: "before editorˇ after".into(),
10003 },
10004 Run {
10005 run_description: "Accept same text at the middle of the word",
10006 initial_state: "before ediˇtor after".into(),
10007 buffer_marked_text: "before <edi|tor> after".into(),
10008 completion_text: "editor",
10009 expected_with_insert_mode: "before editorˇtor after".into(),
10010 expected_with_replace_mode: "before editorˇ after".into(),
10011 expected_with_replace_subsequence_mode: "before editorˇ after".into(),
10012 expected_with_replace_suffix_mode: "before editorˇ after".into(),
10013 },
10014 Run {
10015 run_description: "End of word matches completion text -- cursor at end",
10016 initial_state: "before torˇ after".into(),
10017 buffer_marked_text: "before <tor|> after".into(),
10018 completion_text: "editor",
10019 expected_with_insert_mode: "before editorˇ after".into(),
10020 expected_with_replace_mode: "before editorˇ after".into(),
10021 expected_with_replace_subsequence_mode: "before editorˇ after".into(),
10022 expected_with_replace_suffix_mode: "before editorˇ after".into(),
10023 },
10024 Run {
10025 run_description: "End of word matches completion text -- cursor at start",
10026 initial_state: "before ˇtor after".into(),
10027 buffer_marked_text: "before <|tor> after".into(),
10028 completion_text: "editor",
10029 expected_with_insert_mode: "before editorˇtor after".into(),
10030 expected_with_replace_mode: "before editorˇ after".into(),
10031 expected_with_replace_subsequence_mode: "before editorˇ after".into(),
10032 expected_with_replace_suffix_mode: "before editorˇ after".into(),
10033 },
10034 Run {
10035 run_description: "Prepend text containing whitespace",
10036 initial_state: "pˇfield: bool".into(),
10037 buffer_marked_text: "<p|field>: bool".into(),
10038 completion_text: "pub ",
10039 expected_with_insert_mode: "pub ˇfield: bool".into(),
10040 expected_with_replace_mode: "pub ˇ: bool".into(),
10041 expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
10042 expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
10043 },
10044 Run {
10045 run_description: "Add element to start of list",
10046 initial_state: "[element_ˇelement_2]".into(),
10047 buffer_marked_text: "[<element_|element_2>]".into(),
10048 completion_text: "element_1",
10049 expected_with_insert_mode: "[element_1ˇelement_2]".into(),
10050 expected_with_replace_mode: "[element_1ˇ]".into(),
10051 expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
10052 expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
10053 },
10054 Run {
10055 run_description: "Add element to start of list -- first and second elements are equal",
10056 initial_state: "[elˇelement]".into(),
10057 buffer_marked_text: "[<el|element>]".into(),
10058 completion_text: "element",
10059 expected_with_insert_mode: "[elementˇelement]".into(),
10060 expected_with_replace_mode: "[elementˇ]".into(),
10061 expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
10062 expected_with_replace_suffix_mode: "[elementˇ]".into(),
10063 },
10064 Run {
10065 run_description: "Ends with matching suffix",
10066 initial_state: "SubˇError".into(),
10067 buffer_marked_text: "<Sub|Error>".into(),
10068 completion_text: "SubscriptionError",
10069 expected_with_insert_mode: "SubscriptionErrorˇError".into(),
10070 expected_with_replace_mode: "SubscriptionErrorˇ".into(),
10071 expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
10072 expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
10073 },
10074 Run {
10075 run_description: "Suffix is a subsequence -- contiguous",
10076 initial_state: "SubˇErr".into(),
10077 buffer_marked_text: "<Sub|Err>".into(),
10078 completion_text: "SubscriptionError",
10079 expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
10080 expected_with_replace_mode: "SubscriptionErrorˇ".into(),
10081 expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
10082 expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
10083 },
10084 Run {
10085 run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
10086 initial_state: "Suˇscrirr".into(),
10087 buffer_marked_text: "<Su|scrirr>".into(),
10088 completion_text: "SubscriptionError",
10089 expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
10090 expected_with_replace_mode: "SubscriptionErrorˇ".into(),
10091 expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
10092 expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
10093 },
10094 Run {
10095 run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
10096 initial_state: "foo(indˇix)".into(),
10097 buffer_marked_text: "foo(<ind|ix>)".into(),
10098 completion_text: "node_index",
10099 expected_with_insert_mode: "foo(node_indexˇix)".into(),
10100 expected_with_replace_mode: "foo(node_indexˇ)".into(),
10101 expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
10102 expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
10103 },
10104 ];
10105
10106 for run in runs {
10107 let run_variations = [
10108 (LspInsertMode::Insert, run.expected_with_insert_mode),
10109 (LspInsertMode::Replace, run.expected_with_replace_mode),
10110 (
10111 LspInsertMode::ReplaceSubsequence,
10112 run.expected_with_replace_subsequence_mode,
10113 ),
10114 (
10115 LspInsertMode::ReplaceSuffix,
10116 run.expected_with_replace_suffix_mode,
10117 ),
10118 ];
10119
10120 for (lsp_insert_mode, expected_text) in run_variations {
10121 eprintln!(
10122 "run = {:?}, mode = {lsp_insert_mode:.?}",
10123 run.run_description,
10124 );
10125
10126 update_test_language_settings(&mut cx, |settings| {
10127 settings.defaults.completions = Some(CompletionSettings {
10128 lsp_insert_mode,
10129 words: WordsCompletionMode::Disabled,
10130 lsp: true,
10131 lsp_fetch_timeout_ms: 0,
10132 });
10133 });
10134
10135 cx.set_state(&run.initial_state);
10136 cx.update_editor(|editor, window, cx| {
10137 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10138 });
10139
10140 let counter = Arc::new(AtomicUsize::new(0));
10141 handle_completion_request_with_insert_and_replace(
10142 &mut cx,
10143 &run.buffer_marked_text,
10144 vec![run.completion_text],
10145 counter.clone(),
10146 )
10147 .await;
10148 cx.condition(|editor, _| editor.context_menu_visible())
10149 .await;
10150 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
10151
10152 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10153 editor
10154 .confirm_completion(&ConfirmCompletion::default(), window, cx)
10155 .unwrap()
10156 });
10157 cx.assert_editor_state(&expected_text);
10158 handle_resolve_completion_request(&mut cx, None).await;
10159 apply_additional_edits.await.unwrap();
10160 }
10161 }
10162}
10163
10164#[gpui::test]
10165async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
10166 init_test(cx, |_| {});
10167 let mut cx = EditorLspTestContext::new_rust(
10168 lsp::ServerCapabilities {
10169 completion_provider: Some(lsp::CompletionOptions {
10170 resolve_provider: Some(true),
10171 ..Default::default()
10172 }),
10173 ..Default::default()
10174 },
10175 cx,
10176 )
10177 .await;
10178
10179 let initial_state = "SubˇError";
10180 let buffer_marked_text = "<Sub|Error>";
10181 let completion_text = "SubscriptionError";
10182 let expected_with_insert_mode = "SubscriptionErrorˇError";
10183 let expected_with_replace_mode = "SubscriptionErrorˇ";
10184
10185 update_test_language_settings(&mut cx, |settings| {
10186 settings.defaults.completions = Some(CompletionSettings {
10187 words: WordsCompletionMode::Disabled,
10188 // set the opposite here to ensure that the action is overriding the default behavior
10189 lsp_insert_mode: LspInsertMode::Insert,
10190 lsp: true,
10191 lsp_fetch_timeout_ms: 0,
10192 });
10193 });
10194
10195 cx.set_state(initial_state);
10196 cx.update_editor(|editor, window, cx| {
10197 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10198 });
10199
10200 let counter = Arc::new(AtomicUsize::new(0));
10201 handle_completion_request_with_insert_and_replace(
10202 &mut cx,
10203 &buffer_marked_text,
10204 vec![completion_text],
10205 counter.clone(),
10206 )
10207 .await;
10208 cx.condition(|editor, _| editor.context_menu_visible())
10209 .await;
10210 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
10211
10212 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10213 editor
10214 .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10215 .unwrap()
10216 });
10217 cx.assert_editor_state(&expected_with_replace_mode);
10218 handle_resolve_completion_request(&mut cx, None).await;
10219 apply_additional_edits.await.unwrap();
10220
10221 update_test_language_settings(&mut cx, |settings| {
10222 settings.defaults.completions = Some(CompletionSettings {
10223 words: WordsCompletionMode::Disabled,
10224 // set the opposite here to ensure that the action is overriding the default behavior
10225 lsp_insert_mode: LspInsertMode::Replace,
10226 lsp: true,
10227 lsp_fetch_timeout_ms: 0,
10228 });
10229 });
10230
10231 cx.set_state(initial_state);
10232 cx.update_editor(|editor, window, cx| {
10233 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10234 });
10235 handle_completion_request_with_insert_and_replace(
10236 &mut cx,
10237 &buffer_marked_text,
10238 vec![completion_text],
10239 counter.clone(),
10240 )
10241 .await;
10242 cx.condition(|editor, _| editor.context_menu_visible())
10243 .await;
10244 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
10245
10246 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10247 editor
10248 .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
10249 .unwrap()
10250 });
10251 cx.assert_editor_state(&expected_with_insert_mode);
10252 handle_resolve_completion_request(&mut cx, None).await;
10253 apply_additional_edits.await.unwrap();
10254}
10255
10256#[gpui::test]
10257async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
10258 init_test(cx, |_| {});
10259 let mut cx = EditorLspTestContext::new_rust(
10260 lsp::ServerCapabilities {
10261 completion_provider: Some(lsp::CompletionOptions {
10262 resolve_provider: Some(true),
10263 ..Default::default()
10264 }),
10265 ..Default::default()
10266 },
10267 cx,
10268 )
10269 .await;
10270
10271 // scenario: surrounding text matches completion text
10272 let completion_text = "to_offset";
10273 let initial_state = indoc! {"
10274 1. buf.to_offˇsuffix
10275 2. buf.to_offˇsuf
10276 3. buf.to_offˇfix
10277 4. buf.to_offˇ
10278 5. into_offˇensive
10279 6. ˇsuffix
10280 7. let ˇ //
10281 8. aaˇzz
10282 9. buf.to_off«zzzzzˇ»suffix
10283 10. buf.«ˇzzzzz»suffix
10284 11. to_off«ˇzzzzz»
10285
10286 buf.to_offˇsuffix // newest cursor
10287 "};
10288 let completion_marked_buffer = indoc! {"
10289 1. buf.to_offsuffix
10290 2. buf.to_offsuf
10291 3. buf.to_offfix
10292 4. buf.to_off
10293 5. into_offensive
10294 6. suffix
10295 7. let //
10296 8. aazz
10297 9. buf.to_offzzzzzsuffix
10298 10. buf.zzzzzsuffix
10299 11. to_offzzzzz
10300
10301 buf.<to_off|suffix> // newest cursor
10302 "};
10303 let expected = indoc! {"
10304 1. buf.to_offsetˇ
10305 2. buf.to_offsetˇsuf
10306 3. buf.to_offsetˇfix
10307 4. buf.to_offsetˇ
10308 5. into_offsetˇensive
10309 6. to_offsetˇsuffix
10310 7. let to_offsetˇ //
10311 8. aato_offsetˇzz
10312 9. buf.to_offsetˇ
10313 10. buf.to_offsetˇsuffix
10314 11. to_offsetˇ
10315
10316 buf.to_offsetˇ // newest cursor
10317 "};
10318 cx.set_state(initial_state);
10319 cx.update_editor(|editor, window, cx| {
10320 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10321 });
10322 handle_completion_request_with_insert_and_replace(
10323 &mut cx,
10324 completion_marked_buffer,
10325 vec![completion_text],
10326 Arc::new(AtomicUsize::new(0)),
10327 )
10328 .await;
10329 cx.condition(|editor, _| editor.context_menu_visible())
10330 .await;
10331 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10332 editor
10333 .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10334 .unwrap()
10335 });
10336 cx.assert_editor_state(expected);
10337 handle_resolve_completion_request(&mut cx, None).await;
10338 apply_additional_edits.await.unwrap();
10339
10340 // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
10341 let completion_text = "foo_and_bar";
10342 let initial_state = indoc! {"
10343 1. ooanbˇ
10344 2. zooanbˇ
10345 3. ooanbˇz
10346 4. zooanbˇz
10347 5. ooanˇ
10348 6. oanbˇ
10349
10350 ooanbˇ
10351 "};
10352 let completion_marked_buffer = indoc! {"
10353 1. ooanb
10354 2. zooanb
10355 3. ooanbz
10356 4. zooanbz
10357 5. ooan
10358 6. oanb
10359
10360 <ooanb|>
10361 "};
10362 let expected = indoc! {"
10363 1. foo_and_barˇ
10364 2. zfoo_and_barˇ
10365 3. foo_and_barˇz
10366 4. zfoo_and_barˇz
10367 5. ooanfoo_and_barˇ
10368 6. oanbfoo_and_barˇ
10369
10370 foo_and_barˇ
10371 "};
10372 cx.set_state(initial_state);
10373 cx.update_editor(|editor, window, cx| {
10374 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10375 });
10376 handle_completion_request_with_insert_and_replace(
10377 &mut cx,
10378 completion_marked_buffer,
10379 vec![completion_text],
10380 Arc::new(AtomicUsize::new(0)),
10381 )
10382 .await;
10383 cx.condition(|editor, _| editor.context_menu_visible())
10384 .await;
10385 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10386 editor
10387 .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10388 .unwrap()
10389 });
10390 cx.assert_editor_state(expected);
10391 handle_resolve_completion_request(&mut cx, None).await;
10392 apply_additional_edits.await.unwrap();
10393
10394 // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
10395 // (expects the same as if it was inserted at the end)
10396 let completion_text = "foo_and_bar";
10397 let initial_state = indoc! {"
10398 1. ooˇanb
10399 2. zooˇanb
10400 3. ooˇanbz
10401 4. zooˇanbz
10402
10403 ooˇanb
10404 "};
10405 let completion_marked_buffer = indoc! {"
10406 1. ooanb
10407 2. zooanb
10408 3. ooanbz
10409 4. zooanbz
10410
10411 <oo|anb>
10412 "};
10413 let expected = indoc! {"
10414 1. foo_and_barˇ
10415 2. zfoo_and_barˇ
10416 3. foo_and_barˇz
10417 4. zfoo_and_barˇz
10418
10419 foo_and_barˇ
10420 "};
10421 cx.set_state(initial_state);
10422 cx.update_editor(|editor, window, cx| {
10423 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10424 });
10425 handle_completion_request_with_insert_and_replace(
10426 &mut cx,
10427 completion_marked_buffer,
10428 vec![completion_text],
10429 Arc::new(AtomicUsize::new(0)),
10430 )
10431 .await;
10432 cx.condition(|editor, _| editor.context_menu_visible())
10433 .await;
10434 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10435 editor
10436 .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10437 .unwrap()
10438 });
10439 cx.assert_editor_state(expected);
10440 handle_resolve_completion_request(&mut cx, None).await;
10441 apply_additional_edits.await.unwrap();
10442}
10443
10444// This used to crash
10445#[gpui::test]
10446async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
10447 init_test(cx, |_| {});
10448
10449 let buffer_text = indoc! {"
10450 fn main() {
10451 10.satu;
10452
10453 //
10454 // separate cursors so they open in different excerpts (manually reproducible)
10455 //
10456
10457 10.satu20;
10458 }
10459 "};
10460 let multibuffer_text_with_selections = indoc! {"
10461 fn main() {
10462 10.satuˇ;
10463
10464 //
10465
10466 //
10467
10468 10.satuˇ20;
10469 }
10470 "};
10471 let expected_multibuffer = indoc! {"
10472 fn main() {
10473 10.saturating_sub()ˇ;
10474
10475 //
10476
10477 //
10478
10479 10.saturating_sub()ˇ;
10480 }
10481 "};
10482
10483 let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
10484 let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
10485
10486 let fs = FakeFs::new(cx.executor());
10487 fs.insert_tree(
10488 path!("/a"),
10489 json!({
10490 "main.rs": buffer_text,
10491 }),
10492 )
10493 .await;
10494
10495 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
10496 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10497 language_registry.add(rust_lang());
10498 let mut fake_servers = language_registry.register_fake_lsp(
10499 "Rust",
10500 FakeLspAdapter {
10501 capabilities: lsp::ServerCapabilities {
10502 completion_provider: Some(lsp::CompletionOptions {
10503 resolve_provider: None,
10504 ..lsp::CompletionOptions::default()
10505 }),
10506 ..lsp::ServerCapabilities::default()
10507 },
10508 ..FakeLspAdapter::default()
10509 },
10510 );
10511 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
10512 let cx = &mut VisualTestContext::from_window(*workspace, cx);
10513 let buffer = project
10514 .update(cx, |project, cx| {
10515 project.open_local_buffer(path!("/a/main.rs"), cx)
10516 })
10517 .await
10518 .unwrap();
10519
10520 let multi_buffer = cx.new(|cx| {
10521 let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
10522 multi_buffer.push_excerpts(
10523 buffer.clone(),
10524 [ExcerptRange::new(0..first_excerpt_end)],
10525 cx,
10526 );
10527 multi_buffer.push_excerpts(
10528 buffer.clone(),
10529 [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
10530 cx,
10531 );
10532 multi_buffer
10533 });
10534
10535 let editor = workspace
10536 .update(cx, |_, window, cx| {
10537 cx.new(|cx| {
10538 Editor::new(
10539 EditorMode::Full {
10540 scale_ui_elements_with_buffer_font_size: false,
10541 show_active_line_background: false,
10542 sized_by_content: false,
10543 },
10544 multi_buffer.clone(),
10545 Some(project.clone()),
10546 window,
10547 cx,
10548 )
10549 })
10550 })
10551 .unwrap();
10552
10553 let pane = workspace
10554 .update(cx, |workspace, _, _| workspace.active_pane().clone())
10555 .unwrap();
10556 pane.update_in(cx, |pane, window, cx| {
10557 pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
10558 });
10559
10560 let fake_server = fake_servers.next().await.unwrap();
10561
10562 editor.update_in(cx, |editor, window, cx| {
10563 editor.change_selections(None, window, cx, |s| {
10564 s.select_ranges([
10565 Point::new(1, 11)..Point::new(1, 11),
10566 Point::new(7, 11)..Point::new(7, 11),
10567 ])
10568 });
10569
10570 assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
10571 });
10572
10573 editor.update_in(cx, |editor, window, cx| {
10574 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10575 });
10576
10577 fake_server
10578 .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
10579 let completion_item = lsp::CompletionItem {
10580 label: "saturating_sub()".into(),
10581 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
10582 lsp::InsertReplaceEdit {
10583 new_text: "saturating_sub()".to_owned(),
10584 insert: lsp::Range::new(
10585 lsp::Position::new(7, 7),
10586 lsp::Position::new(7, 11),
10587 ),
10588 replace: lsp::Range::new(
10589 lsp::Position::new(7, 7),
10590 lsp::Position::new(7, 13),
10591 ),
10592 },
10593 )),
10594 ..lsp::CompletionItem::default()
10595 };
10596
10597 Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
10598 })
10599 .next()
10600 .await
10601 .unwrap();
10602
10603 cx.condition(&editor, |editor, _| editor.context_menu_visible())
10604 .await;
10605
10606 editor
10607 .update_in(cx, |editor, window, cx| {
10608 editor
10609 .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10610 .unwrap()
10611 })
10612 .await
10613 .unwrap();
10614
10615 editor.update(cx, |editor, cx| {
10616 assert_text_with_selections(editor, expected_multibuffer, cx);
10617 })
10618}
10619
10620#[gpui::test]
10621async fn test_completion(cx: &mut TestAppContext) {
10622 init_test(cx, |_| {});
10623
10624 let mut cx = EditorLspTestContext::new_rust(
10625 lsp::ServerCapabilities {
10626 completion_provider: Some(lsp::CompletionOptions {
10627 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
10628 resolve_provider: Some(true),
10629 ..Default::default()
10630 }),
10631 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
10632 ..Default::default()
10633 },
10634 cx,
10635 )
10636 .await;
10637 let counter = Arc::new(AtomicUsize::new(0));
10638
10639 cx.set_state(indoc! {"
10640 oneˇ
10641 two
10642 three
10643 "});
10644 cx.simulate_keystroke(".");
10645 handle_completion_request(
10646 &mut cx,
10647 indoc! {"
10648 one.|<>
10649 two
10650 three
10651 "},
10652 vec!["first_completion", "second_completion"],
10653 counter.clone(),
10654 )
10655 .await;
10656 cx.condition(|editor, _| editor.context_menu_visible())
10657 .await;
10658 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
10659
10660 let _handler = handle_signature_help_request(
10661 &mut cx,
10662 lsp::SignatureHelp {
10663 signatures: vec![lsp::SignatureInformation {
10664 label: "test signature".to_string(),
10665 documentation: None,
10666 parameters: Some(vec![lsp::ParameterInformation {
10667 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
10668 documentation: None,
10669 }]),
10670 active_parameter: None,
10671 }],
10672 active_signature: None,
10673 active_parameter: None,
10674 },
10675 );
10676 cx.update_editor(|editor, window, cx| {
10677 assert!(
10678 !editor.signature_help_state.is_shown(),
10679 "No signature help was called for"
10680 );
10681 editor.show_signature_help(&ShowSignatureHelp, window, cx);
10682 });
10683 cx.run_until_parked();
10684 cx.update_editor(|editor, _, _| {
10685 assert!(
10686 !editor.signature_help_state.is_shown(),
10687 "No signature help should be shown when completions menu is open"
10688 );
10689 });
10690
10691 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10692 editor.context_menu_next(&Default::default(), window, cx);
10693 editor
10694 .confirm_completion(&ConfirmCompletion::default(), window, cx)
10695 .unwrap()
10696 });
10697 cx.assert_editor_state(indoc! {"
10698 one.second_completionˇ
10699 two
10700 three
10701 "});
10702
10703 handle_resolve_completion_request(
10704 &mut cx,
10705 Some(vec![
10706 (
10707 //This overlaps with the primary completion edit which is
10708 //misbehavior from the LSP spec, test that we filter it out
10709 indoc! {"
10710 one.second_ˇcompletion
10711 two
10712 threeˇ
10713 "},
10714 "overlapping additional edit",
10715 ),
10716 (
10717 indoc! {"
10718 one.second_completion
10719 two
10720 threeˇ
10721 "},
10722 "\nadditional edit",
10723 ),
10724 ]),
10725 )
10726 .await;
10727 apply_additional_edits.await.unwrap();
10728 cx.assert_editor_state(indoc! {"
10729 one.second_completionˇ
10730 two
10731 three
10732 additional edit
10733 "});
10734
10735 cx.set_state(indoc! {"
10736 one.second_completion
10737 twoˇ
10738 threeˇ
10739 additional edit
10740 "});
10741 cx.simulate_keystroke(" ");
10742 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
10743 cx.simulate_keystroke("s");
10744 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
10745
10746 cx.assert_editor_state(indoc! {"
10747 one.second_completion
10748 two sˇ
10749 three sˇ
10750 additional edit
10751 "});
10752 handle_completion_request(
10753 &mut cx,
10754 indoc! {"
10755 one.second_completion
10756 two s
10757 three <s|>
10758 additional edit
10759 "},
10760 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
10761 counter.clone(),
10762 )
10763 .await;
10764 cx.condition(|editor, _| editor.context_menu_visible())
10765 .await;
10766 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
10767
10768 cx.simulate_keystroke("i");
10769
10770 handle_completion_request(
10771 &mut cx,
10772 indoc! {"
10773 one.second_completion
10774 two si
10775 three <si|>
10776 additional edit
10777 "},
10778 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
10779 counter.clone(),
10780 )
10781 .await;
10782 cx.condition(|editor, _| editor.context_menu_visible())
10783 .await;
10784 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
10785
10786 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10787 editor
10788 .confirm_completion(&ConfirmCompletion::default(), window, cx)
10789 .unwrap()
10790 });
10791 cx.assert_editor_state(indoc! {"
10792 one.second_completion
10793 two sixth_completionˇ
10794 three sixth_completionˇ
10795 additional edit
10796 "});
10797
10798 apply_additional_edits.await.unwrap();
10799
10800 update_test_language_settings(&mut cx, |settings| {
10801 settings.defaults.show_completions_on_input = Some(false);
10802 });
10803 cx.set_state("editorˇ");
10804 cx.simulate_keystroke(".");
10805 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
10806 cx.simulate_keystrokes("c l o");
10807 cx.assert_editor_state("editor.cloˇ");
10808 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
10809 cx.update_editor(|editor, window, cx| {
10810 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10811 });
10812 handle_completion_request(
10813 &mut cx,
10814 "editor.<clo|>",
10815 vec!["close", "clobber"],
10816 counter.clone(),
10817 )
10818 .await;
10819 cx.condition(|editor, _| editor.context_menu_visible())
10820 .await;
10821 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
10822
10823 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10824 editor
10825 .confirm_completion(&ConfirmCompletion::default(), window, cx)
10826 .unwrap()
10827 });
10828 cx.assert_editor_state("editor.closeˇ");
10829 handle_resolve_completion_request(&mut cx, None).await;
10830 apply_additional_edits.await.unwrap();
10831}
10832
10833#[gpui::test]
10834async fn test_word_completion(cx: &mut TestAppContext) {
10835 let lsp_fetch_timeout_ms = 10;
10836 init_test(cx, |language_settings| {
10837 language_settings.defaults.completions = Some(CompletionSettings {
10838 words: WordsCompletionMode::Fallback,
10839 lsp: true,
10840 lsp_fetch_timeout_ms: 10,
10841 lsp_insert_mode: LspInsertMode::Insert,
10842 });
10843 });
10844
10845 let mut cx = EditorLspTestContext::new_rust(
10846 lsp::ServerCapabilities {
10847 completion_provider: Some(lsp::CompletionOptions {
10848 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
10849 ..lsp::CompletionOptions::default()
10850 }),
10851 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
10852 ..lsp::ServerCapabilities::default()
10853 },
10854 cx,
10855 )
10856 .await;
10857
10858 let throttle_completions = Arc::new(AtomicBool::new(false));
10859
10860 let lsp_throttle_completions = throttle_completions.clone();
10861 let _completion_requests_handler =
10862 cx.lsp
10863 .server
10864 .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
10865 let lsp_throttle_completions = lsp_throttle_completions.clone();
10866 let cx = cx.clone();
10867 async move {
10868 if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
10869 cx.background_executor()
10870 .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
10871 .await;
10872 }
10873 Ok(Some(lsp::CompletionResponse::Array(vec![
10874 lsp::CompletionItem {
10875 label: "first".into(),
10876 ..lsp::CompletionItem::default()
10877 },
10878 lsp::CompletionItem {
10879 label: "last".into(),
10880 ..lsp::CompletionItem::default()
10881 },
10882 ])))
10883 }
10884 });
10885
10886 cx.set_state(indoc! {"
10887 oneˇ
10888 two
10889 three
10890 "});
10891 cx.simulate_keystroke(".");
10892 cx.executor().run_until_parked();
10893 cx.condition(|editor, _| editor.context_menu_visible())
10894 .await;
10895 cx.update_editor(|editor, window, cx| {
10896 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10897 {
10898 assert_eq!(
10899 completion_menu_entries(&menu),
10900 &["first", "last"],
10901 "When LSP server is fast to reply, no fallback word completions are used"
10902 );
10903 } else {
10904 panic!("expected completion menu to be open");
10905 }
10906 editor.cancel(&Cancel, window, cx);
10907 });
10908 cx.executor().run_until_parked();
10909 cx.condition(|editor, _| !editor.context_menu_visible())
10910 .await;
10911
10912 throttle_completions.store(true, atomic::Ordering::Release);
10913 cx.simulate_keystroke(".");
10914 cx.executor()
10915 .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
10916 cx.executor().run_until_parked();
10917 cx.condition(|editor, _| editor.context_menu_visible())
10918 .await;
10919 cx.update_editor(|editor, _, _| {
10920 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10921 {
10922 assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
10923 "When LSP server is slow, document words can be shown instead, if configured accordingly");
10924 } else {
10925 panic!("expected completion menu to be open");
10926 }
10927 });
10928}
10929
10930#[gpui::test]
10931async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
10932 init_test(cx, |language_settings| {
10933 language_settings.defaults.completions = Some(CompletionSettings {
10934 words: WordsCompletionMode::Enabled,
10935 lsp: true,
10936 lsp_fetch_timeout_ms: 0,
10937 lsp_insert_mode: LspInsertMode::Insert,
10938 });
10939 });
10940
10941 let mut cx = EditorLspTestContext::new_rust(
10942 lsp::ServerCapabilities {
10943 completion_provider: Some(lsp::CompletionOptions {
10944 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
10945 ..lsp::CompletionOptions::default()
10946 }),
10947 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
10948 ..lsp::ServerCapabilities::default()
10949 },
10950 cx,
10951 )
10952 .await;
10953
10954 let _completion_requests_handler =
10955 cx.lsp
10956 .server
10957 .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
10958 Ok(Some(lsp::CompletionResponse::Array(vec![
10959 lsp::CompletionItem {
10960 label: "first".into(),
10961 ..lsp::CompletionItem::default()
10962 },
10963 lsp::CompletionItem {
10964 label: "last".into(),
10965 ..lsp::CompletionItem::default()
10966 },
10967 ])))
10968 });
10969
10970 cx.set_state(indoc! {"ˇ
10971 first
10972 last
10973 second
10974 "});
10975 cx.simulate_keystroke(".");
10976 cx.executor().run_until_parked();
10977 cx.condition(|editor, _| editor.context_menu_visible())
10978 .await;
10979 cx.update_editor(|editor, _, _| {
10980 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10981 {
10982 assert_eq!(
10983 completion_menu_entries(&menu),
10984 &["first", "last", "second"],
10985 "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
10986 );
10987 } else {
10988 panic!("expected completion menu to be open");
10989 }
10990 });
10991}
10992
10993#[gpui::test]
10994async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
10995 init_test(cx, |language_settings| {
10996 language_settings.defaults.completions = Some(CompletionSettings {
10997 words: WordsCompletionMode::Disabled,
10998 lsp: true,
10999 lsp_fetch_timeout_ms: 0,
11000 lsp_insert_mode: LspInsertMode::Insert,
11001 });
11002 });
11003
11004 let mut cx = EditorLspTestContext::new_rust(
11005 lsp::ServerCapabilities {
11006 completion_provider: Some(lsp::CompletionOptions {
11007 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11008 ..lsp::CompletionOptions::default()
11009 }),
11010 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11011 ..lsp::ServerCapabilities::default()
11012 },
11013 cx,
11014 )
11015 .await;
11016
11017 let _completion_requests_handler =
11018 cx.lsp
11019 .server
11020 .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
11021 panic!("LSP completions should not be queried when dealing with word completions")
11022 });
11023
11024 cx.set_state(indoc! {"ˇ
11025 first
11026 last
11027 second
11028 "});
11029 cx.update_editor(|editor, window, cx| {
11030 editor.show_word_completions(&ShowWordCompletions, window, cx);
11031 });
11032 cx.executor().run_until_parked();
11033 cx.condition(|editor, _| editor.context_menu_visible())
11034 .await;
11035 cx.update_editor(|editor, _, _| {
11036 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11037 {
11038 assert_eq!(
11039 completion_menu_entries(&menu),
11040 &["first", "last", "second"],
11041 "`ShowWordCompletions` action should show word completions"
11042 );
11043 } else {
11044 panic!("expected completion menu to be open");
11045 }
11046 });
11047
11048 cx.simulate_keystroke("l");
11049 cx.executor().run_until_parked();
11050 cx.condition(|editor, _| editor.context_menu_visible())
11051 .await;
11052 cx.update_editor(|editor, _, _| {
11053 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11054 {
11055 assert_eq!(
11056 completion_menu_entries(&menu),
11057 &["last"],
11058 "After showing word completions, further editing should filter them and not query the LSP"
11059 );
11060 } else {
11061 panic!("expected completion menu to be open");
11062 }
11063 });
11064}
11065
11066#[gpui::test]
11067async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
11068 init_test(cx, |language_settings| {
11069 language_settings.defaults.completions = Some(CompletionSettings {
11070 words: WordsCompletionMode::Fallback,
11071 lsp: false,
11072 lsp_fetch_timeout_ms: 0,
11073 lsp_insert_mode: LspInsertMode::Insert,
11074 });
11075 });
11076
11077 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11078
11079 cx.set_state(indoc! {"ˇ
11080 0_usize
11081 let
11082 33
11083 4.5f32
11084 "});
11085 cx.update_editor(|editor, window, cx| {
11086 editor.show_completions(&ShowCompletions::default(), window, cx);
11087 });
11088 cx.executor().run_until_parked();
11089 cx.condition(|editor, _| editor.context_menu_visible())
11090 .await;
11091 cx.update_editor(|editor, window, cx| {
11092 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11093 {
11094 assert_eq!(
11095 completion_menu_entries(&menu),
11096 &["let"],
11097 "With no digits in the completion query, no digits should be in the word completions"
11098 );
11099 } else {
11100 panic!("expected completion menu to be open");
11101 }
11102 editor.cancel(&Cancel, window, cx);
11103 });
11104
11105 cx.set_state(indoc! {"3ˇ
11106 0_usize
11107 let
11108 3
11109 33.35f32
11110 "});
11111 cx.update_editor(|editor, window, cx| {
11112 editor.show_completions(&ShowCompletions::default(), window, cx);
11113 });
11114 cx.executor().run_until_parked();
11115 cx.condition(|editor, _| editor.context_menu_visible())
11116 .await;
11117 cx.update_editor(|editor, _, _| {
11118 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11119 {
11120 assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
11121 return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
11122 } else {
11123 panic!("expected completion menu to be open");
11124 }
11125 });
11126}
11127
11128fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
11129 let position = || lsp::Position {
11130 line: params.text_document_position.position.line,
11131 character: params.text_document_position.position.character,
11132 };
11133 Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11134 range: lsp::Range {
11135 start: position(),
11136 end: position(),
11137 },
11138 new_text: text.to_string(),
11139 }))
11140}
11141
11142#[gpui::test]
11143async fn test_multiline_completion(cx: &mut TestAppContext) {
11144 init_test(cx, |_| {});
11145
11146 let fs = FakeFs::new(cx.executor());
11147 fs.insert_tree(
11148 path!("/a"),
11149 json!({
11150 "main.ts": "a",
11151 }),
11152 )
11153 .await;
11154
11155 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11156 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11157 let typescript_language = Arc::new(Language::new(
11158 LanguageConfig {
11159 name: "TypeScript".into(),
11160 matcher: LanguageMatcher {
11161 path_suffixes: vec!["ts".to_string()],
11162 ..LanguageMatcher::default()
11163 },
11164 line_comments: vec!["// ".into()],
11165 ..LanguageConfig::default()
11166 },
11167 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
11168 ));
11169 language_registry.add(typescript_language.clone());
11170 let mut fake_servers = language_registry.register_fake_lsp(
11171 "TypeScript",
11172 FakeLspAdapter {
11173 capabilities: lsp::ServerCapabilities {
11174 completion_provider: Some(lsp::CompletionOptions {
11175 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11176 ..lsp::CompletionOptions::default()
11177 }),
11178 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11179 ..lsp::ServerCapabilities::default()
11180 },
11181 // Emulate vtsls label generation
11182 label_for_completion: Some(Box::new(|item, _| {
11183 let text = if let Some(description) = item
11184 .label_details
11185 .as_ref()
11186 .and_then(|label_details| label_details.description.as_ref())
11187 {
11188 format!("{} {}", item.label, description)
11189 } else if let Some(detail) = &item.detail {
11190 format!("{} {}", item.label, detail)
11191 } else {
11192 item.label.clone()
11193 };
11194 let len = text.len();
11195 Some(language::CodeLabel {
11196 text,
11197 runs: Vec::new(),
11198 filter_range: 0..len,
11199 })
11200 })),
11201 ..FakeLspAdapter::default()
11202 },
11203 );
11204 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11205 let cx = &mut VisualTestContext::from_window(*workspace, cx);
11206 let worktree_id = workspace
11207 .update(cx, |workspace, _window, cx| {
11208 workspace.project().update(cx, |project, cx| {
11209 project.worktrees(cx).next().unwrap().read(cx).id()
11210 })
11211 })
11212 .unwrap();
11213 let _buffer = project
11214 .update(cx, |project, cx| {
11215 project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
11216 })
11217 .await
11218 .unwrap();
11219 let editor = workspace
11220 .update(cx, |workspace, window, cx| {
11221 workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
11222 })
11223 .unwrap()
11224 .await
11225 .unwrap()
11226 .downcast::<Editor>()
11227 .unwrap();
11228 let fake_server = fake_servers.next().await.unwrap();
11229
11230 let multiline_label = "StickyHeaderExcerpt {\n excerpt,\n next_excerpt_controls_present,\n next_buffer_row,\n }: StickyHeaderExcerpt<'_>,";
11231 let multiline_label_2 = "a\nb\nc\n";
11232 let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
11233 let multiline_description = "d\ne\nf\n";
11234 let multiline_detail_2 = "g\nh\ni\n";
11235
11236 let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
11237 move |params, _| async move {
11238 Ok(Some(lsp::CompletionResponse::Array(vec![
11239 lsp::CompletionItem {
11240 label: multiline_label.to_string(),
11241 text_edit: gen_text_edit(¶ms, "new_text_1"),
11242 ..lsp::CompletionItem::default()
11243 },
11244 lsp::CompletionItem {
11245 label: "single line label 1".to_string(),
11246 detail: Some(multiline_detail.to_string()),
11247 text_edit: gen_text_edit(¶ms, "new_text_2"),
11248 ..lsp::CompletionItem::default()
11249 },
11250 lsp::CompletionItem {
11251 label: "single line label 2".to_string(),
11252 label_details: Some(lsp::CompletionItemLabelDetails {
11253 description: Some(multiline_description.to_string()),
11254 detail: None,
11255 }),
11256 text_edit: gen_text_edit(¶ms, "new_text_2"),
11257 ..lsp::CompletionItem::default()
11258 },
11259 lsp::CompletionItem {
11260 label: multiline_label_2.to_string(),
11261 detail: Some(multiline_detail_2.to_string()),
11262 text_edit: gen_text_edit(¶ms, "new_text_3"),
11263 ..lsp::CompletionItem::default()
11264 },
11265 lsp::CompletionItem {
11266 label: "Label with many spaces and \t but without newlines".to_string(),
11267 detail: Some(
11268 "Details with many spaces and \t but without newlines".to_string(),
11269 ),
11270 text_edit: gen_text_edit(¶ms, "new_text_4"),
11271 ..lsp::CompletionItem::default()
11272 },
11273 ])))
11274 },
11275 );
11276
11277 editor.update_in(cx, |editor, window, cx| {
11278 cx.focus_self(window);
11279 editor.move_to_end(&MoveToEnd, window, cx);
11280 editor.handle_input(".", window, cx);
11281 });
11282 cx.run_until_parked();
11283 completion_handle.next().await.unwrap();
11284
11285 editor.update(cx, |editor, _| {
11286 assert!(editor.context_menu_visible());
11287 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11288 {
11289 let completion_labels = menu
11290 .completions
11291 .borrow()
11292 .iter()
11293 .map(|c| c.label.text.clone())
11294 .collect::<Vec<_>>();
11295 assert_eq!(
11296 completion_labels,
11297 &[
11298 "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
11299 "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
11300 "single line label 2 d e f ",
11301 "a b c g h i ",
11302 "Label with many spaces and \t but without newlines Details with many spaces and \t but without newlines",
11303 ],
11304 "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
11305 );
11306
11307 for completion in menu
11308 .completions
11309 .borrow()
11310 .iter() {
11311 assert_eq!(
11312 completion.label.filter_range,
11313 0..completion.label.text.len(),
11314 "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
11315 );
11316 }
11317 } else {
11318 panic!("expected completion menu to be open");
11319 }
11320 });
11321}
11322
11323#[gpui::test]
11324async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
11325 init_test(cx, |_| {});
11326 let mut cx = EditorLspTestContext::new_rust(
11327 lsp::ServerCapabilities {
11328 completion_provider: Some(lsp::CompletionOptions {
11329 trigger_characters: Some(vec![".".to_string()]),
11330 ..Default::default()
11331 }),
11332 ..Default::default()
11333 },
11334 cx,
11335 )
11336 .await;
11337 cx.lsp
11338 .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
11339 Ok(Some(lsp::CompletionResponse::Array(vec![
11340 lsp::CompletionItem {
11341 label: "first".into(),
11342 ..Default::default()
11343 },
11344 lsp::CompletionItem {
11345 label: "last".into(),
11346 ..Default::default()
11347 },
11348 ])))
11349 });
11350 cx.set_state("variableˇ");
11351 cx.simulate_keystroke(".");
11352 cx.executor().run_until_parked();
11353
11354 cx.update_editor(|editor, _, _| {
11355 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11356 {
11357 assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
11358 } else {
11359 panic!("expected completion menu to be open");
11360 }
11361 });
11362
11363 cx.update_editor(|editor, window, cx| {
11364 editor.move_page_down(&MovePageDown::default(), window, cx);
11365 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11366 {
11367 assert!(
11368 menu.selected_item == 1,
11369 "expected PageDown to select the last item from the context menu"
11370 );
11371 } else {
11372 panic!("expected completion menu to stay open after PageDown");
11373 }
11374 });
11375
11376 cx.update_editor(|editor, window, cx| {
11377 editor.move_page_up(&MovePageUp::default(), window, cx);
11378 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11379 {
11380 assert!(
11381 menu.selected_item == 0,
11382 "expected PageUp to select the first item from the context menu"
11383 );
11384 } else {
11385 panic!("expected completion menu to stay open after PageUp");
11386 }
11387 });
11388}
11389
11390#[gpui::test]
11391async fn test_as_is_completions(cx: &mut TestAppContext) {
11392 init_test(cx, |_| {});
11393 let mut cx = EditorLspTestContext::new_rust(
11394 lsp::ServerCapabilities {
11395 completion_provider: Some(lsp::CompletionOptions {
11396 ..Default::default()
11397 }),
11398 ..Default::default()
11399 },
11400 cx,
11401 )
11402 .await;
11403 cx.lsp
11404 .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
11405 Ok(Some(lsp::CompletionResponse::Array(vec![
11406 lsp::CompletionItem {
11407 label: "unsafe".into(),
11408 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11409 range: lsp::Range {
11410 start: lsp::Position {
11411 line: 1,
11412 character: 2,
11413 },
11414 end: lsp::Position {
11415 line: 1,
11416 character: 3,
11417 },
11418 },
11419 new_text: "unsafe".to_string(),
11420 })),
11421 insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
11422 ..Default::default()
11423 },
11424 ])))
11425 });
11426 cx.set_state("fn a() {}\n nˇ");
11427 cx.executor().run_until_parked();
11428 cx.update_editor(|editor, window, cx| {
11429 editor.show_completions(
11430 &ShowCompletions {
11431 trigger: Some("\n".into()),
11432 },
11433 window,
11434 cx,
11435 );
11436 });
11437 cx.executor().run_until_parked();
11438
11439 cx.update_editor(|editor, window, cx| {
11440 editor.confirm_completion(&Default::default(), window, cx)
11441 });
11442 cx.executor().run_until_parked();
11443 cx.assert_editor_state("fn a() {}\n unsafeˇ");
11444}
11445
11446#[gpui::test]
11447async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
11448 init_test(cx, |_| {});
11449
11450 let mut cx = EditorLspTestContext::new_rust(
11451 lsp::ServerCapabilities {
11452 completion_provider: Some(lsp::CompletionOptions {
11453 trigger_characters: Some(vec![".".to_string()]),
11454 resolve_provider: Some(true),
11455 ..Default::default()
11456 }),
11457 ..Default::default()
11458 },
11459 cx,
11460 )
11461 .await;
11462
11463 cx.set_state("fn main() { let a = 2ˇ; }");
11464 cx.simulate_keystroke(".");
11465 let completion_item = lsp::CompletionItem {
11466 label: "Some".into(),
11467 kind: Some(lsp::CompletionItemKind::SNIPPET),
11468 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
11469 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
11470 kind: lsp::MarkupKind::Markdown,
11471 value: "```rust\nSome(2)\n```".to_string(),
11472 })),
11473 deprecated: Some(false),
11474 sort_text: Some("Some".to_string()),
11475 filter_text: Some("Some".to_string()),
11476 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
11477 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11478 range: lsp::Range {
11479 start: lsp::Position {
11480 line: 0,
11481 character: 22,
11482 },
11483 end: lsp::Position {
11484 line: 0,
11485 character: 22,
11486 },
11487 },
11488 new_text: "Some(2)".to_string(),
11489 })),
11490 additional_text_edits: Some(vec![lsp::TextEdit {
11491 range: lsp::Range {
11492 start: lsp::Position {
11493 line: 0,
11494 character: 20,
11495 },
11496 end: lsp::Position {
11497 line: 0,
11498 character: 22,
11499 },
11500 },
11501 new_text: "".to_string(),
11502 }]),
11503 ..Default::default()
11504 };
11505
11506 let closure_completion_item = completion_item.clone();
11507 let counter = Arc::new(AtomicUsize::new(0));
11508 let counter_clone = counter.clone();
11509 let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
11510 let task_completion_item = closure_completion_item.clone();
11511 counter_clone.fetch_add(1, atomic::Ordering::Release);
11512 async move {
11513 Ok(Some(lsp::CompletionResponse::Array(vec![
11514 task_completion_item,
11515 ])))
11516 }
11517 });
11518
11519 cx.condition(|editor, _| editor.context_menu_visible())
11520 .await;
11521 cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
11522 assert!(request.next().await.is_some());
11523 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11524
11525 cx.simulate_keystrokes("S o m");
11526 cx.condition(|editor, _| editor.context_menu_visible())
11527 .await;
11528 cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
11529 assert!(request.next().await.is_some());
11530 assert!(request.next().await.is_some());
11531 assert!(request.next().await.is_some());
11532 request.close();
11533 assert!(request.next().await.is_none());
11534 assert_eq!(
11535 counter.load(atomic::Ordering::Acquire),
11536 4,
11537 "With the completions menu open, only one LSP request should happen per input"
11538 );
11539}
11540
11541#[gpui::test]
11542async fn test_toggle_comment(cx: &mut TestAppContext) {
11543 init_test(cx, |_| {});
11544 let mut cx = EditorTestContext::new(cx).await;
11545 let language = Arc::new(Language::new(
11546 LanguageConfig {
11547 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
11548 ..Default::default()
11549 },
11550 Some(tree_sitter_rust::LANGUAGE.into()),
11551 ));
11552 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
11553
11554 // If multiple selections intersect a line, the line is only toggled once.
11555 cx.set_state(indoc! {"
11556 fn a() {
11557 «//b();
11558 ˇ»// «c();
11559 //ˇ» d();
11560 }
11561 "});
11562
11563 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
11564
11565 cx.assert_editor_state(indoc! {"
11566 fn a() {
11567 «b();
11568 c();
11569 ˇ» d();
11570 }
11571 "});
11572
11573 // The comment prefix is inserted at the same column for every line in a
11574 // selection.
11575 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
11576
11577 cx.assert_editor_state(indoc! {"
11578 fn a() {
11579 // «b();
11580 // c();
11581 ˇ»// d();
11582 }
11583 "});
11584
11585 // If a selection ends at the beginning of a line, that line is not toggled.
11586 cx.set_selections_state(indoc! {"
11587 fn a() {
11588 // b();
11589 «// c();
11590 ˇ» // d();
11591 }
11592 "});
11593
11594 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
11595
11596 cx.assert_editor_state(indoc! {"
11597 fn a() {
11598 // b();
11599 «c();
11600 ˇ» // d();
11601 }
11602 "});
11603
11604 // If a selection span a single line and is empty, the line is toggled.
11605 cx.set_state(indoc! {"
11606 fn a() {
11607 a();
11608 b();
11609 ˇ
11610 }
11611 "});
11612
11613 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
11614
11615 cx.assert_editor_state(indoc! {"
11616 fn a() {
11617 a();
11618 b();
11619 //•ˇ
11620 }
11621 "});
11622
11623 // If a selection span multiple lines, empty lines are not toggled.
11624 cx.set_state(indoc! {"
11625 fn a() {
11626 «a();
11627
11628 c();ˇ»
11629 }
11630 "});
11631
11632 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
11633
11634 cx.assert_editor_state(indoc! {"
11635 fn a() {
11636 // «a();
11637
11638 // c();ˇ»
11639 }
11640 "});
11641
11642 // If a selection includes multiple comment prefixes, all lines are uncommented.
11643 cx.set_state(indoc! {"
11644 fn a() {
11645 «// a();
11646 /// b();
11647 //! c();ˇ»
11648 }
11649 "});
11650
11651 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
11652
11653 cx.assert_editor_state(indoc! {"
11654 fn a() {
11655 «a();
11656 b();
11657 c();ˇ»
11658 }
11659 "});
11660}
11661
11662#[gpui::test]
11663async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
11664 init_test(cx, |_| {});
11665 let mut cx = EditorTestContext::new(cx).await;
11666 let language = Arc::new(Language::new(
11667 LanguageConfig {
11668 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
11669 ..Default::default()
11670 },
11671 Some(tree_sitter_rust::LANGUAGE.into()),
11672 ));
11673 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
11674
11675 let toggle_comments = &ToggleComments {
11676 advance_downwards: false,
11677 ignore_indent: true,
11678 };
11679
11680 // If multiple selections intersect a line, the line is only toggled once.
11681 cx.set_state(indoc! {"
11682 fn a() {
11683 // «b();
11684 // c();
11685 // ˇ» d();
11686 }
11687 "});
11688
11689 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
11690
11691 cx.assert_editor_state(indoc! {"
11692 fn a() {
11693 «b();
11694 c();
11695 ˇ» d();
11696 }
11697 "});
11698
11699 // The comment prefix is inserted at the beginning of each line
11700 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
11701
11702 cx.assert_editor_state(indoc! {"
11703 fn a() {
11704 // «b();
11705 // c();
11706 // ˇ» d();
11707 }
11708 "});
11709
11710 // If a selection ends at the beginning of a line, that line is not toggled.
11711 cx.set_selections_state(indoc! {"
11712 fn a() {
11713 // b();
11714 // «c();
11715 ˇ»// d();
11716 }
11717 "});
11718
11719 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
11720
11721 cx.assert_editor_state(indoc! {"
11722 fn a() {
11723 // b();
11724 «c();
11725 ˇ»// d();
11726 }
11727 "});
11728
11729 // If a selection span a single line and is empty, the line is toggled.
11730 cx.set_state(indoc! {"
11731 fn a() {
11732 a();
11733 b();
11734 ˇ
11735 }
11736 "});
11737
11738 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
11739
11740 cx.assert_editor_state(indoc! {"
11741 fn a() {
11742 a();
11743 b();
11744 //ˇ
11745 }
11746 "});
11747
11748 // If a selection span multiple lines, empty lines are not toggled.
11749 cx.set_state(indoc! {"
11750 fn a() {
11751 «a();
11752
11753 c();ˇ»
11754 }
11755 "});
11756
11757 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
11758
11759 cx.assert_editor_state(indoc! {"
11760 fn a() {
11761 // «a();
11762
11763 // c();ˇ»
11764 }
11765 "});
11766
11767 // If a selection includes multiple comment prefixes, all lines are uncommented.
11768 cx.set_state(indoc! {"
11769 fn a() {
11770 // «a();
11771 /// b();
11772 //! c();ˇ»
11773 }
11774 "});
11775
11776 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
11777
11778 cx.assert_editor_state(indoc! {"
11779 fn a() {
11780 «a();
11781 b();
11782 c();ˇ»
11783 }
11784 "});
11785}
11786
11787#[gpui::test]
11788async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
11789 init_test(cx, |_| {});
11790
11791 let language = Arc::new(Language::new(
11792 LanguageConfig {
11793 line_comments: vec!["// ".into()],
11794 ..Default::default()
11795 },
11796 Some(tree_sitter_rust::LANGUAGE.into()),
11797 ));
11798
11799 let mut cx = EditorTestContext::new(cx).await;
11800
11801 cx.language_registry().add(language.clone());
11802 cx.update_buffer(|buffer, cx| {
11803 buffer.set_language(Some(language), cx);
11804 });
11805
11806 let toggle_comments = &ToggleComments {
11807 advance_downwards: true,
11808 ignore_indent: false,
11809 };
11810
11811 // Single cursor on one line -> advance
11812 // Cursor moves horizontally 3 characters as well on non-blank line
11813 cx.set_state(indoc!(
11814 "fn a() {
11815 ˇdog();
11816 cat();
11817 }"
11818 ));
11819 cx.update_editor(|editor, window, cx| {
11820 editor.toggle_comments(toggle_comments, window, cx);
11821 });
11822 cx.assert_editor_state(indoc!(
11823 "fn a() {
11824 // dog();
11825 catˇ();
11826 }"
11827 ));
11828
11829 // Single selection on one line -> don't advance
11830 cx.set_state(indoc!(
11831 "fn a() {
11832 «dog()ˇ»;
11833 cat();
11834 }"
11835 ));
11836 cx.update_editor(|editor, window, cx| {
11837 editor.toggle_comments(toggle_comments, window, cx);
11838 });
11839 cx.assert_editor_state(indoc!(
11840 "fn a() {
11841 // «dog()ˇ»;
11842 cat();
11843 }"
11844 ));
11845
11846 // Multiple cursors on one line -> advance
11847 cx.set_state(indoc!(
11848 "fn a() {
11849 ˇdˇog();
11850 cat();
11851 }"
11852 ));
11853 cx.update_editor(|editor, window, cx| {
11854 editor.toggle_comments(toggle_comments, window, cx);
11855 });
11856 cx.assert_editor_state(indoc!(
11857 "fn a() {
11858 // dog();
11859 catˇ(ˇ);
11860 }"
11861 ));
11862
11863 // Multiple cursors on one line, with selection -> don't advance
11864 cx.set_state(indoc!(
11865 "fn a() {
11866 ˇdˇog«()ˇ»;
11867 cat();
11868 }"
11869 ));
11870 cx.update_editor(|editor, window, cx| {
11871 editor.toggle_comments(toggle_comments, window, cx);
11872 });
11873 cx.assert_editor_state(indoc!(
11874 "fn a() {
11875 // ˇdˇog«()ˇ»;
11876 cat();
11877 }"
11878 ));
11879
11880 // Single cursor on one line -> advance
11881 // Cursor moves to column 0 on blank line
11882 cx.set_state(indoc!(
11883 "fn a() {
11884 ˇdog();
11885
11886 cat();
11887 }"
11888 ));
11889 cx.update_editor(|editor, window, cx| {
11890 editor.toggle_comments(toggle_comments, window, cx);
11891 });
11892 cx.assert_editor_state(indoc!(
11893 "fn a() {
11894 // dog();
11895 ˇ
11896 cat();
11897 }"
11898 ));
11899
11900 // Single cursor on one line -> advance
11901 // Cursor starts and ends at column 0
11902 cx.set_state(indoc!(
11903 "fn a() {
11904 ˇ dog();
11905 cat();
11906 }"
11907 ));
11908 cx.update_editor(|editor, window, cx| {
11909 editor.toggle_comments(toggle_comments, window, cx);
11910 });
11911 cx.assert_editor_state(indoc!(
11912 "fn a() {
11913 // dog();
11914 ˇ cat();
11915 }"
11916 ));
11917}
11918
11919#[gpui::test]
11920async fn test_toggle_block_comment(cx: &mut TestAppContext) {
11921 init_test(cx, |_| {});
11922
11923 let mut cx = EditorTestContext::new(cx).await;
11924
11925 let html_language = Arc::new(
11926 Language::new(
11927 LanguageConfig {
11928 name: "HTML".into(),
11929 block_comment: Some(("<!-- ".into(), " -->".into())),
11930 ..Default::default()
11931 },
11932 Some(tree_sitter_html::LANGUAGE.into()),
11933 )
11934 .with_injection_query(
11935 r#"
11936 (script_element
11937 (raw_text) @injection.content
11938 (#set! injection.language "javascript"))
11939 "#,
11940 )
11941 .unwrap(),
11942 );
11943
11944 let javascript_language = Arc::new(Language::new(
11945 LanguageConfig {
11946 name: "JavaScript".into(),
11947 line_comments: vec!["// ".into()],
11948 ..Default::default()
11949 },
11950 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
11951 ));
11952
11953 cx.language_registry().add(html_language.clone());
11954 cx.language_registry().add(javascript_language.clone());
11955 cx.update_buffer(|buffer, cx| {
11956 buffer.set_language(Some(html_language), cx);
11957 });
11958
11959 // Toggle comments for empty selections
11960 cx.set_state(
11961 &r#"
11962 <p>A</p>ˇ
11963 <p>B</p>ˇ
11964 <p>C</p>ˇ
11965 "#
11966 .unindent(),
11967 );
11968 cx.update_editor(|editor, window, cx| {
11969 editor.toggle_comments(&ToggleComments::default(), window, cx)
11970 });
11971 cx.assert_editor_state(
11972 &r#"
11973 <!-- <p>A</p>ˇ -->
11974 <!-- <p>B</p>ˇ -->
11975 <!-- <p>C</p>ˇ -->
11976 "#
11977 .unindent(),
11978 );
11979 cx.update_editor(|editor, window, cx| {
11980 editor.toggle_comments(&ToggleComments::default(), window, cx)
11981 });
11982 cx.assert_editor_state(
11983 &r#"
11984 <p>A</p>ˇ
11985 <p>B</p>ˇ
11986 <p>C</p>ˇ
11987 "#
11988 .unindent(),
11989 );
11990
11991 // Toggle comments for mixture of empty and non-empty selections, where
11992 // multiple selections occupy a given line.
11993 cx.set_state(
11994 &r#"
11995 <p>A«</p>
11996 <p>ˇ»B</p>ˇ
11997 <p>C«</p>
11998 <p>ˇ»D</p>ˇ
11999 "#
12000 .unindent(),
12001 );
12002
12003 cx.update_editor(|editor, window, cx| {
12004 editor.toggle_comments(&ToggleComments::default(), window, cx)
12005 });
12006 cx.assert_editor_state(
12007 &r#"
12008 <!-- <p>A«</p>
12009 <p>ˇ»B</p>ˇ -->
12010 <!-- <p>C«</p>
12011 <p>ˇ»D</p>ˇ -->
12012 "#
12013 .unindent(),
12014 );
12015 cx.update_editor(|editor, window, cx| {
12016 editor.toggle_comments(&ToggleComments::default(), window, cx)
12017 });
12018 cx.assert_editor_state(
12019 &r#"
12020 <p>A«</p>
12021 <p>ˇ»B</p>ˇ
12022 <p>C«</p>
12023 <p>ˇ»D</p>ˇ
12024 "#
12025 .unindent(),
12026 );
12027
12028 // Toggle comments when different languages are active for different
12029 // selections.
12030 cx.set_state(
12031 &r#"
12032 ˇ<script>
12033 ˇvar x = new Y();
12034 ˇ</script>
12035 "#
12036 .unindent(),
12037 );
12038 cx.executor().run_until_parked();
12039 cx.update_editor(|editor, window, cx| {
12040 editor.toggle_comments(&ToggleComments::default(), window, cx)
12041 });
12042 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
12043 // Uncommenting and commenting from this position brings in even more wrong artifacts.
12044 cx.assert_editor_state(
12045 &r#"
12046 <!-- ˇ<script> -->
12047 // ˇvar x = new Y();
12048 <!-- ˇ</script> -->
12049 "#
12050 .unindent(),
12051 );
12052}
12053
12054#[gpui::test]
12055fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
12056 init_test(cx, |_| {});
12057
12058 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
12059 let multibuffer = cx.new(|cx| {
12060 let mut multibuffer = MultiBuffer::new(ReadWrite);
12061 multibuffer.push_excerpts(
12062 buffer.clone(),
12063 [
12064 ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
12065 ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
12066 ],
12067 cx,
12068 );
12069 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
12070 multibuffer
12071 });
12072
12073 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
12074 editor.update_in(cx, |editor, window, cx| {
12075 assert_eq!(editor.text(cx), "aaaa\nbbbb");
12076 editor.change_selections(None, window, cx, |s| {
12077 s.select_ranges([
12078 Point::new(0, 0)..Point::new(0, 0),
12079 Point::new(1, 0)..Point::new(1, 0),
12080 ])
12081 });
12082
12083 editor.handle_input("X", window, cx);
12084 assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
12085 assert_eq!(
12086 editor.selections.ranges(cx),
12087 [
12088 Point::new(0, 1)..Point::new(0, 1),
12089 Point::new(1, 1)..Point::new(1, 1),
12090 ]
12091 );
12092
12093 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
12094 editor.change_selections(None, window, cx, |s| {
12095 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
12096 });
12097 editor.backspace(&Default::default(), window, cx);
12098 assert_eq!(editor.text(cx), "Xa\nbbb");
12099 assert_eq!(
12100 editor.selections.ranges(cx),
12101 [Point::new(1, 0)..Point::new(1, 0)]
12102 );
12103
12104 editor.change_selections(None, window, cx, |s| {
12105 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
12106 });
12107 editor.backspace(&Default::default(), window, cx);
12108 assert_eq!(editor.text(cx), "X\nbb");
12109 assert_eq!(
12110 editor.selections.ranges(cx),
12111 [Point::new(0, 1)..Point::new(0, 1)]
12112 );
12113 });
12114}
12115
12116#[gpui::test]
12117fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
12118 init_test(cx, |_| {});
12119
12120 let markers = vec![('[', ']').into(), ('(', ')').into()];
12121 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
12122 indoc! {"
12123 [aaaa
12124 (bbbb]
12125 cccc)",
12126 },
12127 markers.clone(),
12128 );
12129 let excerpt_ranges = markers.into_iter().map(|marker| {
12130 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
12131 ExcerptRange::new(context.clone())
12132 });
12133 let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
12134 let multibuffer = cx.new(|cx| {
12135 let mut multibuffer = MultiBuffer::new(ReadWrite);
12136 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
12137 multibuffer
12138 });
12139
12140 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
12141 editor.update_in(cx, |editor, window, cx| {
12142 let (expected_text, selection_ranges) = marked_text_ranges(
12143 indoc! {"
12144 aaaa
12145 bˇbbb
12146 bˇbbˇb
12147 cccc"
12148 },
12149 true,
12150 );
12151 assert_eq!(editor.text(cx), expected_text);
12152 editor.change_selections(None, window, cx, |s| s.select_ranges(selection_ranges));
12153
12154 editor.handle_input("X", window, cx);
12155
12156 let (expected_text, expected_selections) = marked_text_ranges(
12157 indoc! {"
12158 aaaa
12159 bXˇbbXb
12160 bXˇbbXˇb
12161 cccc"
12162 },
12163 false,
12164 );
12165 assert_eq!(editor.text(cx), expected_text);
12166 assert_eq!(editor.selections.ranges(cx), expected_selections);
12167
12168 editor.newline(&Newline, window, cx);
12169 let (expected_text, expected_selections) = marked_text_ranges(
12170 indoc! {"
12171 aaaa
12172 bX
12173 ˇbbX
12174 b
12175 bX
12176 ˇbbX
12177 ˇb
12178 cccc"
12179 },
12180 false,
12181 );
12182 assert_eq!(editor.text(cx), expected_text);
12183 assert_eq!(editor.selections.ranges(cx), expected_selections);
12184 });
12185}
12186
12187#[gpui::test]
12188fn test_refresh_selections(cx: &mut TestAppContext) {
12189 init_test(cx, |_| {});
12190
12191 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
12192 let mut excerpt1_id = None;
12193 let multibuffer = cx.new(|cx| {
12194 let mut multibuffer = MultiBuffer::new(ReadWrite);
12195 excerpt1_id = multibuffer
12196 .push_excerpts(
12197 buffer.clone(),
12198 [
12199 ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
12200 ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
12201 ],
12202 cx,
12203 )
12204 .into_iter()
12205 .next();
12206 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
12207 multibuffer
12208 });
12209
12210 let editor = cx.add_window(|window, cx| {
12211 let mut editor = build_editor(multibuffer.clone(), window, cx);
12212 let snapshot = editor.snapshot(window, cx);
12213 editor.change_selections(None, window, cx, |s| {
12214 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
12215 });
12216 editor.begin_selection(
12217 Point::new(2, 1).to_display_point(&snapshot),
12218 true,
12219 1,
12220 window,
12221 cx,
12222 );
12223 assert_eq!(
12224 editor.selections.ranges(cx),
12225 [
12226 Point::new(1, 3)..Point::new(1, 3),
12227 Point::new(2, 1)..Point::new(2, 1),
12228 ]
12229 );
12230 editor
12231 });
12232
12233 // Refreshing selections is a no-op when excerpts haven't changed.
12234 _ = editor.update(cx, |editor, window, cx| {
12235 editor.change_selections(None, window, cx, |s| s.refresh());
12236 assert_eq!(
12237 editor.selections.ranges(cx),
12238 [
12239 Point::new(1, 3)..Point::new(1, 3),
12240 Point::new(2, 1)..Point::new(2, 1),
12241 ]
12242 );
12243 });
12244
12245 multibuffer.update(cx, |multibuffer, cx| {
12246 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
12247 });
12248 _ = editor.update(cx, |editor, window, cx| {
12249 // Removing an excerpt causes the first selection to become degenerate.
12250 assert_eq!(
12251 editor.selections.ranges(cx),
12252 [
12253 Point::new(0, 0)..Point::new(0, 0),
12254 Point::new(0, 1)..Point::new(0, 1)
12255 ]
12256 );
12257
12258 // Refreshing selections will relocate the first selection to the original buffer
12259 // location.
12260 editor.change_selections(None, window, cx, |s| s.refresh());
12261 assert_eq!(
12262 editor.selections.ranges(cx),
12263 [
12264 Point::new(0, 1)..Point::new(0, 1),
12265 Point::new(0, 3)..Point::new(0, 3)
12266 ]
12267 );
12268 assert!(editor.selections.pending_anchor().is_some());
12269 });
12270}
12271
12272#[gpui::test]
12273fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
12274 init_test(cx, |_| {});
12275
12276 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
12277 let mut excerpt1_id = None;
12278 let multibuffer = cx.new(|cx| {
12279 let mut multibuffer = MultiBuffer::new(ReadWrite);
12280 excerpt1_id = multibuffer
12281 .push_excerpts(
12282 buffer.clone(),
12283 [
12284 ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
12285 ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
12286 ],
12287 cx,
12288 )
12289 .into_iter()
12290 .next();
12291 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
12292 multibuffer
12293 });
12294
12295 let editor = cx.add_window(|window, cx| {
12296 let mut editor = build_editor(multibuffer.clone(), window, cx);
12297 let snapshot = editor.snapshot(window, cx);
12298 editor.begin_selection(
12299 Point::new(1, 3).to_display_point(&snapshot),
12300 false,
12301 1,
12302 window,
12303 cx,
12304 );
12305 assert_eq!(
12306 editor.selections.ranges(cx),
12307 [Point::new(1, 3)..Point::new(1, 3)]
12308 );
12309 editor
12310 });
12311
12312 multibuffer.update(cx, |multibuffer, cx| {
12313 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
12314 });
12315 _ = editor.update(cx, |editor, window, cx| {
12316 assert_eq!(
12317 editor.selections.ranges(cx),
12318 [Point::new(0, 0)..Point::new(0, 0)]
12319 );
12320
12321 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
12322 editor.change_selections(None, window, cx, |s| s.refresh());
12323 assert_eq!(
12324 editor.selections.ranges(cx),
12325 [Point::new(0, 3)..Point::new(0, 3)]
12326 );
12327 assert!(editor.selections.pending_anchor().is_some());
12328 });
12329}
12330
12331#[gpui::test]
12332async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
12333 init_test(cx, |_| {});
12334
12335 let language = Arc::new(
12336 Language::new(
12337 LanguageConfig {
12338 brackets: BracketPairConfig {
12339 pairs: vec![
12340 BracketPair {
12341 start: "{".to_string(),
12342 end: "}".to_string(),
12343 close: true,
12344 surround: true,
12345 newline: true,
12346 },
12347 BracketPair {
12348 start: "/* ".to_string(),
12349 end: " */".to_string(),
12350 close: true,
12351 surround: true,
12352 newline: true,
12353 },
12354 ],
12355 ..Default::default()
12356 },
12357 ..Default::default()
12358 },
12359 Some(tree_sitter_rust::LANGUAGE.into()),
12360 )
12361 .with_indents_query("")
12362 .unwrap(),
12363 );
12364
12365 let text = concat!(
12366 "{ }\n", //
12367 " x\n", //
12368 " /* */\n", //
12369 "x\n", //
12370 "{{} }\n", //
12371 );
12372
12373 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
12374 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12375 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
12376 editor
12377 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
12378 .await;
12379
12380 editor.update_in(cx, |editor, window, cx| {
12381 editor.change_selections(None, window, cx, |s| {
12382 s.select_display_ranges([
12383 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
12384 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
12385 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
12386 ])
12387 });
12388 editor.newline(&Newline, window, cx);
12389
12390 assert_eq!(
12391 editor.buffer().read(cx).read(cx).text(),
12392 concat!(
12393 "{ \n", // Suppress rustfmt
12394 "\n", //
12395 "}\n", //
12396 " x\n", //
12397 " /* \n", //
12398 " \n", //
12399 " */\n", //
12400 "x\n", //
12401 "{{} \n", //
12402 "}\n", //
12403 )
12404 );
12405 });
12406}
12407
12408#[gpui::test]
12409fn test_highlighted_ranges(cx: &mut TestAppContext) {
12410 init_test(cx, |_| {});
12411
12412 let editor = cx.add_window(|window, cx| {
12413 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
12414 build_editor(buffer.clone(), window, cx)
12415 });
12416
12417 _ = editor.update(cx, |editor, window, cx| {
12418 struct Type1;
12419 struct Type2;
12420
12421 let buffer = editor.buffer.read(cx).snapshot(cx);
12422
12423 let anchor_range =
12424 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
12425
12426 editor.highlight_background::<Type1>(
12427 &[
12428 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
12429 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
12430 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
12431 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
12432 ],
12433 |_| Hsla::red(),
12434 cx,
12435 );
12436 editor.highlight_background::<Type2>(
12437 &[
12438 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
12439 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
12440 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
12441 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
12442 ],
12443 |_| Hsla::green(),
12444 cx,
12445 );
12446
12447 let snapshot = editor.snapshot(window, cx);
12448 let mut highlighted_ranges = editor.background_highlights_in_range(
12449 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
12450 &snapshot,
12451 cx.theme().colors(),
12452 );
12453 // Enforce a consistent ordering based on color without relying on the ordering of the
12454 // highlight's `TypeId` which is non-executor.
12455 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
12456 assert_eq!(
12457 highlighted_ranges,
12458 &[
12459 (
12460 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
12461 Hsla::red(),
12462 ),
12463 (
12464 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
12465 Hsla::red(),
12466 ),
12467 (
12468 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
12469 Hsla::green(),
12470 ),
12471 (
12472 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
12473 Hsla::green(),
12474 ),
12475 ]
12476 );
12477 assert_eq!(
12478 editor.background_highlights_in_range(
12479 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
12480 &snapshot,
12481 cx.theme().colors(),
12482 ),
12483 &[(
12484 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
12485 Hsla::red(),
12486 )]
12487 );
12488 });
12489}
12490
12491#[gpui::test]
12492async fn test_following(cx: &mut TestAppContext) {
12493 init_test(cx, |_| {});
12494
12495 let fs = FakeFs::new(cx.executor());
12496 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
12497
12498 let buffer = project.update(cx, |project, cx| {
12499 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
12500 cx.new(|cx| MultiBuffer::singleton(buffer, cx))
12501 });
12502 let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
12503 let follower = cx.update(|cx| {
12504 cx.open_window(
12505 WindowOptions {
12506 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
12507 gpui::Point::new(px(0.), px(0.)),
12508 gpui::Point::new(px(10.), px(80.)),
12509 ))),
12510 ..Default::default()
12511 },
12512 |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
12513 )
12514 .unwrap()
12515 });
12516
12517 let is_still_following = Rc::new(RefCell::new(true));
12518 let follower_edit_event_count = Rc::new(RefCell::new(0));
12519 let pending_update = Rc::new(RefCell::new(None));
12520 let leader_entity = leader.root(cx).unwrap();
12521 let follower_entity = follower.root(cx).unwrap();
12522 _ = follower.update(cx, {
12523 let update = pending_update.clone();
12524 let is_still_following = is_still_following.clone();
12525 let follower_edit_event_count = follower_edit_event_count.clone();
12526 |_, window, cx| {
12527 cx.subscribe_in(
12528 &leader_entity,
12529 window,
12530 move |_, leader, event, window, cx| {
12531 leader.read(cx).add_event_to_update_proto(
12532 event,
12533 &mut update.borrow_mut(),
12534 window,
12535 cx,
12536 );
12537 },
12538 )
12539 .detach();
12540
12541 cx.subscribe_in(
12542 &follower_entity,
12543 window,
12544 move |_, _, event: &EditorEvent, _window, _cx| {
12545 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
12546 *is_still_following.borrow_mut() = false;
12547 }
12548
12549 if let EditorEvent::BufferEdited = event {
12550 *follower_edit_event_count.borrow_mut() += 1;
12551 }
12552 },
12553 )
12554 .detach();
12555 }
12556 });
12557
12558 // Update the selections only
12559 _ = leader.update(cx, |leader, window, cx| {
12560 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
12561 });
12562 follower
12563 .update(cx, |follower, window, cx| {
12564 follower.apply_update_proto(
12565 &project,
12566 pending_update.borrow_mut().take().unwrap(),
12567 window,
12568 cx,
12569 )
12570 })
12571 .unwrap()
12572 .await
12573 .unwrap();
12574 _ = follower.update(cx, |follower, _, cx| {
12575 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
12576 });
12577 assert!(*is_still_following.borrow());
12578 assert_eq!(*follower_edit_event_count.borrow(), 0);
12579
12580 // Update the scroll position only
12581 _ = leader.update(cx, |leader, window, cx| {
12582 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
12583 });
12584 follower
12585 .update(cx, |follower, window, cx| {
12586 follower.apply_update_proto(
12587 &project,
12588 pending_update.borrow_mut().take().unwrap(),
12589 window,
12590 cx,
12591 )
12592 })
12593 .unwrap()
12594 .await
12595 .unwrap();
12596 assert_eq!(
12597 follower
12598 .update(cx, |follower, _, cx| follower.scroll_position(cx))
12599 .unwrap(),
12600 gpui::Point::new(1.5, 3.5)
12601 );
12602 assert!(*is_still_following.borrow());
12603 assert_eq!(*follower_edit_event_count.borrow(), 0);
12604
12605 // Update the selections and scroll position. The follower's scroll position is updated
12606 // via autoscroll, not via the leader's exact scroll position.
12607 _ = leader.update(cx, |leader, window, cx| {
12608 leader.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
12609 leader.request_autoscroll(Autoscroll::newest(), cx);
12610 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
12611 });
12612 follower
12613 .update(cx, |follower, window, cx| {
12614 follower.apply_update_proto(
12615 &project,
12616 pending_update.borrow_mut().take().unwrap(),
12617 window,
12618 cx,
12619 )
12620 })
12621 .unwrap()
12622 .await
12623 .unwrap();
12624 _ = follower.update(cx, |follower, _, cx| {
12625 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
12626 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
12627 });
12628 assert!(*is_still_following.borrow());
12629
12630 // Creating a pending selection that precedes another selection
12631 _ = leader.update(cx, |leader, window, cx| {
12632 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
12633 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
12634 });
12635 follower
12636 .update(cx, |follower, window, cx| {
12637 follower.apply_update_proto(
12638 &project,
12639 pending_update.borrow_mut().take().unwrap(),
12640 window,
12641 cx,
12642 )
12643 })
12644 .unwrap()
12645 .await
12646 .unwrap();
12647 _ = follower.update(cx, |follower, _, cx| {
12648 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
12649 });
12650 assert!(*is_still_following.borrow());
12651
12652 // Extend the pending selection so that it surrounds another selection
12653 _ = leader.update(cx, |leader, window, cx| {
12654 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
12655 });
12656 follower
12657 .update(cx, |follower, window, cx| {
12658 follower.apply_update_proto(
12659 &project,
12660 pending_update.borrow_mut().take().unwrap(),
12661 window,
12662 cx,
12663 )
12664 })
12665 .unwrap()
12666 .await
12667 .unwrap();
12668 _ = follower.update(cx, |follower, _, cx| {
12669 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
12670 });
12671
12672 // Scrolling locally breaks the follow
12673 _ = follower.update(cx, |follower, window, cx| {
12674 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
12675 follower.set_scroll_anchor(
12676 ScrollAnchor {
12677 anchor: top_anchor,
12678 offset: gpui::Point::new(0.0, 0.5),
12679 },
12680 window,
12681 cx,
12682 );
12683 });
12684 assert!(!(*is_still_following.borrow()));
12685}
12686
12687#[gpui::test]
12688async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
12689 init_test(cx, |_| {});
12690
12691 let fs = FakeFs::new(cx.executor());
12692 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
12693 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12694 let pane = workspace
12695 .update(cx, |workspace, _, _| workspace.active_pane().clone())
12696 .unwrap();
12697
12698 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
12699
12700 let leader = pane.update_in(cx, |_, window, cx| {
12701 let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
12702 cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
12703 });
12704
12705 // Start following the editor when it has no excerpts.
12706 let mut state_message =
12707 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
12708 let workspace_entity = workspace.root(cx).unwrap();
12709 let follower_1 = cx
12710 .update_window(*workspace.deref(), |_, window, cx| {
12711 Editor::from_state_proto(
12712 workspace_entity,
12713 ViewId {
12714 creator: CollaboratorId::PeerId(PeerId::default()),
12715 id: 0,
12716 },
12717 &mut state_message,
12718 window,
12719 cx,
12720 )
12721 })
12722 .unwrap()
12723 .unwrap()
12724 .await
12725 .unwrap();
12726
12727 let update_message = Rc::new(RefCell::new(None));
12728 follower_1.update_in(cx, {
12729 let update = update_message.clone();
12730 |_, window, cx| {
12731 cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
12732 leader.read(cx).add_event_to_update_proto(
12733 event,
12734 &mut update.borrow_mut(),
12735 window,
12736 cx,
12737 );
12738 })
12739 .detach();
12740 }
12741 });
12742
12743 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
12744 (
12745 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
12746 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
12747 )
12748 });
12749
12750 // Insert some excerpts.
12751 leader.update(cx, |leader, cx| {
12752 leader.buffer.update(cx, |multibuffer, cx| {
12753 multibuffer.set_excerpts_for_path(
12754 PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
12755 buffer_1.clone(),
12756 vec![
12757 Point::row_range(0..3),
12758 Point::row_range(1..6),
12759 Point::row_range(12..15),
12760 ],
12761 0,
12762 cx,
12763 );
12764 multibuffer.set_excerpts_for_path(
12765 PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
12766 buffer_2.clone(),
12767 vec![Point::row_range(0..6), Point::row_range(8..12)],
12768 0,
12769 cx,
12770 );
12771 });
12772 });
12773
12774 // Apply the update of adding the excerpts.
12775 follower_1
12776 .update_in(cx, |follower, window, cx| {
12777 follower.apply_update_proto(
12778 &project,
12779 update_message.borrow().clone().unwrap(),
12780 window,
12781 cx,
12782 )
12783 })
12784 .await
12785 .unwrap();
12786 assert_eq!(
12787 follower_1.update(cx, |editor, cx| editor.text(cx)),
12788 leader.update(cx, |editor, cx| editor.text(cx))
12789 );
12790 update_message.borrow_mut().take();
12791
12792 // Start following separately after it already has excerpts.
12793 let mut state_message =
12794 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
12795 let workspace_entity = workspace.root(cx).unwrap();
12796 let follower_2 = cx
12797 .update_window(*workspace.deref(), |_, window, cx| {
12798 Editor::from_state_proto(
12799 workspace_entity,
12800 ViewId {
12801 creator: CollaboratorId::PeerId(PeerId::default()),
12802 id: 0,
12803 },
12804 &mut state_message,
12805 window,
12806 cx,
12807 )
12808 })
12809 .unwrap()
12810 .unwrap()
12811 .await
12812 .unwrap();
12813 assert_eq!(
12814 follower_2.update(cx, |editor, cx| editor.text(cx)),
12815 leader.update(cx, |editor, cx| editor.text(cx))
12816 );
12817
12818 // Remove some excerpts.
12819 leader.update(cx, |leader, cx| {
12820 leader.buffer.update(cx, |multibuffer, cx| {
12821 let excerpt_ids = multibuffer.excerpt_ids();
12822 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
12823 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
12824 });
12825 });
12826
12827 // Apply the update of removing the excerpts.
12828 follower_1
12829 .update_in(cx, |follower, window, cx| {
12830 follower.apply_update_proto(
12831 &project,
12832 update_message.borrow().clone().unwrap(),
12833 window,
12834 cx,
12835 )
12836 })
12837 .await
12838 .unwrap();
12839 follower_2
12840 .update_in(cx, |follower, window, cx| {
12841 follower.apply_update_proto(
12842 &project,
12843 update_message.borrow().clone().unwrap(),
12844 window,
12845 cx,
12846 )
12847 })
12848 .await
12849 .unwrap();
12850 update_message.borrow_mut().take();
12851 assert_eq!(
12852 follower_1.update(cx, |editor, cx| editor.text(cx)),
12853 leader.update(cx, |editor, cx| editor.text(cx))
12854 );
12855}
12856
12857#[gpui::test]
12858async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
12859 init_test(cx, |_| {});
12860
12861 let mut cx = EditorTestContext::new(cx).await;
12862 let lsp_store =
12863 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
12864
12865 cx.set_state(indoc! {"
12866 ˇfn func(abc def: i32) -> u32 {
12867 }
12868 "});
12869
12870 cx.update(|_, cx| {
12871 lsp_store.update(cx, |lsp_store, cx| {
12872 lsp_store
12873 .update_diagnostics(
12874 LanguageServerId(0),
12875 lsp::PublishDiagnosticsParams {
12876 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
12877 version: None,
12878 diagnostics: vec![
12879 lsp::Diagnostic {
12880 range: lsp::Range::new(
12881 lsp::Position::new(0, 11),
12882 lsp::Position::new(0, 12),
12883 ),
12884 severity: Some(lsp::DiagnosticSeverity::ERROR),
12885 ..Default::default()
12886 },
12887 lsp::Diagnostic {
12888 range: lsp::Range::new(
12889 lsp::Position::new(0, 12),
12890 lsp::Position::new(0, 15),
12891 ),
12892 severity: Some(lsp::DiagnosticSeverity::ERROR),
12893 ..Default::default()
12894 },
12895 lsp::Diagnostic {
12896 range: lsp::Range::new(
12897 lsp::Position::new(0, 25),
12898 lsp::Position::new(0, 28),
12899 ),
12900 severity: Some(lsp::DiagnosticSeverity::ERROR),
12901 ..Default::default()
12902 },
12903 ],
12904 },
12905 &[],
12906 cx,
12907 )
12908 .unwrap()
12909 });
12910 });
12911
12912 executor.run_until_parked();
12913
12914 cx.update_editor(|editor, window, cx| {
12915 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
12916 });
12917
12918 cx.assert_editor_state(indoc! {"
12919 fn func(abc def: i32) -> ˇu32 {
12920 }
12921 "});
12922
12923 cx.update_editor(|editor, window, cx| {
12924 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
12925 });
12926
12927 cx.assert_editor_state(indoc! {"
12928 fn func(abc ˇdef: i32) -> u32 {
12929 }
12930 "});
12931
12932 cx.update_editor(|editor, window, cx| {
12933 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
12934 });
12935
12936 cx.assert_editor_state(indoc! {"
12937 fn func(abcˇ def: i32) -> u32 {
12938 }
12939 "});
12940
12941 cx.update_editor(|editor, window, cx| {
12942 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
12943 });
12944
12945 cx.assert_editor_state(indoc! {"
12946 fn func(abc def: i32) -> ˇu32 {
12947 }
12948 "});
12949}
12950
12951#[gpui::test]
12952async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
12953 init_test(cx, |_| {});
12954
12955 let mut cx = EditorTestContext::new(cx).await;
12956
12957 let diff_base = r#"
12958 use some::mod;
12959
12960 const A: u32 = 42;
12961
12962 fn main() {
12963 println!("hello");
12964
12965 println!("world");
12966 }
12967 "#
12968 .unindent();
12969
12970 // Edits are modified, removed, modified, added
12971 cx.set_state(
12972 &r#"
12973 use some::modified;
12974
12975 ˇ
12976 fn main() {
12977 println!("hello there");
12978
12979 println!("around the");
12980 println!("world");
12981 }
12982 "#
12983 .unindent(),
12984 );
12985
12986 cx.set_head_text(&diff_base);
12987 executor.run_until_parked();
12988
12989 cx.update_editor(|editor, window, cx| {
12990 //Wrap around the bottom of the buffer
12991 for _ in 0..3 {
12992 editor.go_to_next_hunk(&GoToHunk, window, cx);
12993 }
12994 });
12995
12996 cx.assert_editor_state(
12997 &r#"
12998 ˇuse some::modified;
12999
13000
13001 fn main() {
13002 println!("hello there");
13003
13004 println!("around the");
13005 println!("world");
13006 }
13007 "#
13008 .unindent(),
13009 );
13010
13011 cx.update_editor(|editor, window, cx| {
13012 //Wrap around the top of the buffer
13013 for _ in 0..2 {
13014 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
13015 }
13016 });
13017
13018 cx.assert_editor_state(
13019 &r#"
13020 use some::modified;
13021
13022
13023 fn main() {
13024 ˇ println!("hello there");
13025
13026 println!("around the");
13027 println!("world");
13028 }
13029 "#
13030 .unindent(),
13031 );
13032
13033 cx.update_editor(|editor, window, cx| {
13034 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
13035 });
13036
13037 cx.assert_editor_state(
13038 &r#"
13039 use some::modified;
13040
13041 ˇ
13042 fn main() {
13043 println!("hello there");
13044
13045 println!("around the");
13046 println!("world");
13047 }
13048 "#
13049 .unindent(),
13050 );
13051
13052 cx.update_editor(|editor, window, cx| {
13053 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
13054 });
13055
13056 cx.assert_editor_state(
13057 &r#"
13058 ˇuse some::modified;
13059
13060
13061 fn main() {
13062 println!("hello there");
13063
13064 println!("around the");
13065 println!("world");
13066 }
13067 "#
13068 .unindent(),
13069 );
13070
13071 cx.update_editor(|editor, window, cx| {
13072 for _ in 0..2 {
13073 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
13074 }
13075 });
13076
13077 cx.assert_editor_state(
13078 &r#"
13079 use some::modified;
13080
13081
13082 fn main() {
13083 ˇ println!("hello there");
13084
13085 println!("around the");
13086 println!("world");
13087 }
13088 "#
13089 .unindent(),
13090 );
13091
13092 cx.update_editor(|editor, window, cx| {
13093 editor.fold(&Fold, window, cx);
13094 });
13095
13096 cx.update_editor(|editor, window, cx| {
13097 editor.go_to_next_hunk(&GoToHunk, window, cx);
13098 });
13099
13100 cx.assert_editor_state(
13101 &r#"
13102 ˇuse some::modified;
13103
13104
13105 fn main() {
13106 println!("hello there");
13107
13108 println!("around the");
13109 println!("world");
13110 }
13111 "#
13112 .unindent(),
13113 );
13114}
13115
13116#[test]
13117fn test_split_words() {
13118 fn split(text: &str) -> Vec<&str> {
13119 split_words(text).collect()
13120 }
13121
13122 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
13123 assert_eq!(split("hello_world"), &["hello_", "world"]);
13124 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
13125 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
13126 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
13127 assert_eq!(split("helloworld"), &["helloworld"]);
13128
13129 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
13130}
13131
13132#[gpui::test]
13133async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
13134 init_test(cx, |_| {});
13135
13136 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
13137 let mut assert = |before, after| {
13138 let _state_context = cx.set_state(before);
13139 cx.run_until_parked();
13140 cx.update_editor(|editor, window, cx| {
13141 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
13142 });
13143 cx.run_until_parked();
13144 cx.assert_editor_state(after);
13145 };
13146
13147 // Outside bracket jumps to outside of matching bracket
13148 assert("console.logˇ(var);", "console.log(var)ˇ;");
13149 assert("console.log(var)ˇ;", "console.logˇ(var);");
13150
13151 // Inside bracket jumps to inside of matching bracket
13152 assert("console.log(ˇvar);", "console.log(varˇ);");
13153 assert("console.log(varˇ);", "console.log(ˇvar);");
13154
13155 // When outside a bracket and inside, favor jumping to the inside bracket
13156 assert(
13157 "console.log('foo', [1, 2, 3]ˇ);",
13158 "console.log(ˇ'foo', [1, 2, 3]);",
13159 );
13160 assert(
13161 "console.log(ˇ'foo', [1, 2, 3]);",
13162 "console.log('foo', [1, 2, 3]ˇ);",
13163 );
13164
13165 // Bias forward if two options are equally likely
13166 assert(
13167 "let result = curried_fun()ˇ();",
13168 "let result = curried_fun()()ˇ;",
13169 );
13170
13171 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
13172 assert(
13173 indoc! {"
13174 function test() {
13175 console.log('test')ˇ
13176 }"},
13177 indoc! {"
13178 function test() {
13179 console.logˇ('test')
13180 }"},
13181 );
13182}
13183
13184#[gpui::test]
13185async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
13186 init_test(cx, |_| {});
13187
13188 let fs = FakeFs::new(cx.executor());
13189 fs.insert_tree(
13190 path!("/a"),
13191 json!({
13192 "main.rs": "fn main() { let a = 5; }",
13193 "other.rs": "// Test file",
13194 }),
13195 )
13196 .await;
13197 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13198
13199 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13200 language_registry.add(Arc::new(Language::new(
13201 LanguageConfig {
13202 name: "Rust".into(),
13203 matcher: LanguageMatcher {
13204 path_suffixes: vec!["rs".to_string()],
13205 ..Default::default()
13206 },
13207 brackets: BracketPairConfig {
13208 pairs: vec![BracketPair {
13209 start: "{".to_string(),
13210 end: "}".to_string(),
13211 close: true,
13212 surround: true,
13213 newline: true,
13214 }],
13215 disabled_scopes_by_bracket_ix: Vec::new(),
13216 },
13217 ..Default::default()
13218 },
13219 Some(tree_sitter_rust::LANGUAGE.into()),
13220 )));
13221 let mut fake_servers = language_registry.register_fake_lsp(
13222 "Rust",
13223 FakeLspAdapter {
13224 capabilities: lsp::ServerCapabilities {
13225 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
13226 first_trigger_character: "{".to_string(),
13227 more_trigger_character: None,
13228 }),
13229 ..Default::default()
13230 },
13231 ..Default::default()
13232 },
13233 );
13234
13235 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13236
13237 let cx = &mut VisualTestContext::from_window(*workspace, cx);
13238
13239 let worktree_id = workspace
13240 .update(cx, |workspace, _, cx| {
13241 workspace.project().update(cx, |project, cx| {
13242 project.worktrees(cx).next().unwrap().read(cx).id()
13243 })
13244 })
13245 .unwrap();
13246
13247 let buffer = project
13248 .update(cx, |project, cx| {
13249 project.open_local_buffer(path!("/a/main.rs"), cx)
13250 })
13251 .await
13252 .unwrap();
13253 let editor_handle = workspace
13254 .update(cx, |workspace, window, cx| {
13255 workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
13256 })
13257 .unwrap()
13258 .await
13259 .unwrap()
13260 .downcast::<Editor>()
13261 .unwrap();
13262
13263 cx.executor().start_waiting();
13264 let fake_server = fake_servers.next().await.unwrap();
13265
13266 fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
13267 |params, _| async move {
13268 assert_eq!(
13269 params.text_document_position.text_document.uri,
13270 lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
13271 );
13272 assert_eq!(
13273 params.text_document_position.position,
13274 lsp::Position::new(0, 21),
13275 );
13276
13277 Ok(Some(vec![lsp::TextEdit {
13278 new_text: "]".to_string(),
13279 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
13280 }]))
13281 },
13282 );
13283
13284 editor_handle.update_in(cx, |editor, window, cx| {
13285 window.focus(&editor.focus_handle(cx));
13286 editor.change_selections(None, window, cx, |s| {
13287 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
13288 });
13289 editor.handle_input("{", window, cx);
13290 });
13291
13292 cx.executor().run_until_parked();
13293
13294 buffer.update(cx, |buffer, _| {
13295 assert_eq!(
13296 buffer.text(),
13297 "fn main() { let a = {5}; }",
13298 "No extra braces from on type formatting should appear in the buffer"
13299 )
13300 });
13301}
13302
13303#[gpui::test]
13304async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
13305 init_test(cx, |_| {});
13306
13307 let fs = FakeFs::new(cx.executor());
13308 fs.insert_tree(
13309 path!("/a"),
13310 json!({
13311 "main.rs": "fn main() { let a = 5; }",
13312 "other.rs": "// Test file",
13313 }),
13314 )
13315 .await;
13316
13317 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13318
13319 let server_restarts = Arc::new(AtomicUsize::new(0));
13320 let closure_restarts = Arc::clone(&server_restarts);
13321 let language_server_name = "test language server";
13322 let language_name: LanguageName = "Rust".into();
13323
13324 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13325 language_registry.add(Arc::new(Language::new(
13326 LanguageConfig {
13327 name: language_name.clone(),
13328 matcher: LanguageMatcher {
13329 path_suffixes: vec!["rs".to_string()],
13330 ..Default::default()
13331 },
13332 ..Default::default()
13333 },
13334 Some(tree_sitter_rust::LANGUAGE.into()),
13335 )));
13336 let mut fake_servers = language_registry.register_fake_lsp(
13337 "Rust",
13338 FakeLspAdapter {
13339 name: language_server_name,
13340 initialization_options: Some(json!({
13341 "testOptionValue": true
13342 })),
13343 initializer: Some(Box::new(move |fake_server| {
13344 let task_restarts = Arc::clone(&closure_restarts);
13345 fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
13346 task_restarts.fetch_add(1, atomic::Ordering::Release);
13347 futures::future::ready(Ok(()))
13348 });
13349 })),
13350 ..Default::default()
13351 },
13352 );
13353
13354 let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13355 let _buffer = project
13356 .update(cx, |project, cx| {
13357 project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
13358 })
13359 .await
13360 .unwrap();
13361 let _fake_server = fake_servers.next().await.unwrap();
13362 update_test_language_settings(cx, |language_settings| {
13363 language_settings.languages.insert(
13364 language_name.clone(),
13365 LanguageSettingsContent {
13366 tab_size: NonZeroU32::new(8),
13367 ..Default::default()
13368 },
13369 );
13370 });
13371 cx.executor().run_until_parked();
13372 assert_eq!(
13373 server_restarts.load(atomic::Ordering::Acquire),
13374 0,
13375 "Should not restart LSP server on an unrelated change"
13376 );
13377
13378 update_test_project_settings(cx, |project_settings| {
13379 project_settings.lsp.insert(
13380 "Some other server name".into(),
13381 LspSettings {
13382 binary: None,
13383 settings: None,
13384 initialization_options: Some(json!({
13385 "some other init value": false
13386 })),
13387 enable_lsp_tasks: false,
13388 },
13389 );
13390 });
13391 cx.executor().run_until_parked();
13392 assert_eq!(
13393 server_restarts.load(atomic::Ordering::Acquire),
13394 0,
13395 "Should not restart LSP server on an unrelated LSP settings change"
13396 );
13397
13398 update_test_project_settings(cx, |project_settings| {
13399 project_settings.lsp.insert(
13400 language_server_name.into(),
13401 LspSettings {
13402 binary: None,
13403 settings: None,
13404 initialization_options: Some(json!({
13405 "anotherInitValue": false
13406 })),
13407 enable_lsp_tasks: false,
13408 },
13409 );
13410 });
13411 cx.executor().run_until_parked();
13412 assert_eq!(
13413 server_restarts.load(atomic::Ordering::Acquire),
13414 1,
13415 "Should restart LSP server on a related LSP settings change"
13416 );
13417
13418 update_test_project_settings(cx, |project_settings| {
13419 project_settings.lsp.insert(
13420 language_server_name.into(),
13421 LspSettings {
13422 binary: None,
13423 settings: None,
13424 initialization_options: Some(json!({
13425 "anotherInitValue": false
13426 })),
13427 enable_lsp_tasks: false,
13428 },
13429 );
13430 });
13431 cx.executor().run_until_parked();
13432 assert_eq!(
13433 server_restarts.load(atomic::Ordering::Acquire),
13434 1,
13435 "Should not restart LSP server on a related LSP settings change that is the same"
13436 );
13437
13438 update_test_project_settings(cx, |project_settings| {
13439 project_settings.lsp.insert(
13440 language_server_name.into(),
13441 LspSettings {
13442 binary: None,
13443 settings: None,
13444 initialization_options: None,
13445 enable_lsp_tasks: false,
13446 },
13447 );
13448 });
13449 cx.executor().run_until_parked();
13450 assert_eq!(
13451 server_restarts.load(atomic::Ordering::Acquire),
13452 2,
13453 "Should restart LSP server on another related LSP settings change"
13454 );
13455}
13456
13457#[gpui::test]
13458async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
13459 init_test(cx, |_| {});
13460
13461 let mut cx = EditorLspTestContext::new_rust(
13462 lsp::ServerCapabilities {
13463 completion_provider: Some(lsp::CompletionOptions {
13464 trigger_characters: Some(vec![".".to_string()]),
13465 resolve_provider: Some(true),
13466 ..Default::default()
13467 }),
13468 ..Default::default()
13469 },
13470 cx,
13471 )
13472 .await;
13473
13474 cx.set_state("fn main() { let a = 2ˇ; }");
13475 cx.simulate_keystroke(".");
13476 let completion_item = lsp::CompletionItem {
13477 label: "some".into(),
13478 kind: Some(lsp::CompletionItemKind::SNIPPET),
13479 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
13480 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
13481 kind: lsp::MarkupKind::Markdown,
13482 value: "```rust\nSome(2)\n```".to_string(),
13483 })),
13484 deprecated: Some(false),
13485 sort_text: Some("fffffff2".to_string()),
13486 filter_text: Some("some".to_string()),
13487 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
13488 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13489 range: lsp::Range {
13490 start: lsp::Position {
13491 line: 0,
13492 character: 22,
13493 },
13494 end: lsp::Position {
13495 line: 0,
13496 character: 22,
13497 },
13498 },
13499 new_text: "Some(2)".to_string(),
13500 })),
13501 additional_text_edits: Some(vec![lsp::TextEdit {
13502 range: lsp::Range {
13503 start: lsp::Position {
13504 line: 0,
13505 character: 20,
13506 },
13507 end: lsp::Position {
13508 line: 0,
13509 character: 22,
13510 },
13511 },
13512 new_text: "".to_string(),
13513 }]),
13514 ..Default::default()
13515 };
13516
13517 let closure_completion_item = completion_item.clone();
13518 let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
13519 let task_completion_item = closure_completion_item.clone();
13520 async move {
13521 Ok(Some(lsp::CompletionResponse::Array(vec![
13522 task_completion_item,
13523 ])))
13524 }
13525 });
13526
13527 request.next().await;
13528
13529 cx.condition(|editor, _| editor.context_menu_visible())
13530 .await;
13531 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13532 editor
13533 .confirm_completion(&ConfirmCompletion::default(), window, cx)
13534 .unwrap()
13535 });
13536 cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
13537
13538 cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
13539 let task_completion_item = completion_item.clone();
13540 async move { Ok(task_completion_item) }
13541 })
13542 .next()
13543 .await
13544 .unwrap();
13545 apply_additional_edits.await.unwrap();
13546 cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
13547}
13548
13549#[gpui::test]
13550async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
13551 init_test(cx, |_| {});
13552
13553 let mut cx = EditorLspTestContext::new_rust(
13554 lsp::ServerCapabilities {
13555 completion_provider: Some(lsp::CompletionOptions {
13556 trigger_characters: Some(vec![".".to_string()]),
13557 resolve_provider: Some(true),
13558 ..Default::default()
13559 }),
13560 ..Default::default()
13561 },
13562 cx,
13563 )
13564 .await;
13565
13566 cx.set_state("fn main() { let a = 2ˇ; }");
13567 cx.simulate_keystroke(".");
13568
13569 let item1 = lsp::CompletionItem {
13570 label: "method id()".to_string(),
13571 filter_text: Some("id".to_string()),
13572 detail: None,
13573 documentation: None,
13574 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13575 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
13576 new_text: ".id".to_string(),
13577 })),
13578 ..lsp::CompletionItem::default()
13579 };
13580
13581 let item2 = lsp::CompletionItem {
13582 label: "other".to_string(),
13583 filter_text: Some("other".to_string()),
13584 detail: None,
13585 documentation: None,
13586 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13587 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
13588 new_text: ".other".to_string(),
13589 })),
13590 ..lsp::CompletionItem::default()
13591 };
13592
13593 let item1 = item1.clone();
13594 cx.set_request_handler::<lsp::request::Completion, _, _>({
13595 let item1 = item1.clone();
13596 move |_, _, _| {
13597 let item1 = item1.clone();
13598 let item2 = item2.clone();
13599 async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
13600 }
13601 })
13602 .next()
13603 .await;
13604
13605 cx.condition(|editor, _| editor.context_menu_visible())
13606 .await;
13607 cx.update_editor(|editor, _, _| {
13608 let context_menu = editor.context_menu.borrow_mut();
13609 let context_menu = context_menu
13610 .as_ref()
13611 .expect("Should have the context menu deployed");
13612 match context_menu {
13613 CodeContextMenu::Completions(completions_menu) => {
13614 let completions = completions_menu.completions.borrow_mut();
13615 assert_eq!(
13616 completions
13617 .iter()
13618 .map(|completion| &completion.label.text)
13619 .collect::<Vec<_>>(),
13620 vec!["method id()", "other"]
13621 )
13622 }
13623 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
13624 }
13625 });
13626
13627 cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
13628 let item1 = item1.clone();
13629 move |_, item_to_resolve, _| {
13630 let item1 = item1.clone();
13631 async move {
13632 if item1 == item_to_resolve {
13633 Ok(lsp::CompletionItem {
13634 label: "method id()".to_string(),
13635 filter_text: Some("id".to_string()),
13636 detail: Some("Now resolved!".to_string()),
13637 documentation: Some(lsp::Documentation::String("Docs".to_string())),
13638 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13639 range: lsp::Range::new(
13640 lsp::Position::new(0, 22),
13641 lsp::Position::new(0, 22),
13642 ),
13643 new_text: ".id".to_string(),
13644 })),
13645 ..lsp::CompletionItem::default()
13646 })
13647 } else {
13648 Ok(item_to_resolve)
13649 }
13650 }
13651 }
13652 })
13653 .next()
13654 .await
13655 .unwrap();
13656 cx.run_until_parked();
13657
13658 cx.update_editor(|editor, window, cx| {
13659 editor.context_menu_next(&Default::default(), window, cx);
13660 });
13661
13662 cx.update_editor(|editor, _, _| {
13663 let context_menu = editor.context_menu.borrow_mut();
13664 let context_menu = context_menu
13665 .as_ref()
13666 .expect("Should have the context menu deployed");
13667 match context_menu {
13668 CodeContextMenu::Completions(completions_menu) => {
13669 let completions = completions_menu.completions.borrow_mut();
13670 assert_eq!(
13671 completions
13672 .iter()
13673 .map(|completion| &completion.label.text)
13674 .collect::<Vec<_>>(),
13675 vec!["method id() Now resolved!", "other"],
13676 "Should update first completion label, but not second as the filter text did not match."
13677 );
13678 }
13679 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
13680 }
13681 });
13682}
13683
13684#[gpui::test]
13685async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
13686 init_test(cx, |_| {});
13687
13688 let mut cx = EditorLspTestContext::new_rust(
13689 lsp::ServerCapabilities {
13690 completion_provider: Some(lsp::CompletionOptions {
13691 trigger_characters: Some(vec![".".to_string()]),
13692 resolve_provider: Some(true),
13693 ..Default::default()
13694 }),
13695 ..Default::default()
13696 },
13697 cx,
13698 )
13699 .await;
13700
13701 cx.set_state("fn main() { let a = 2ˇ; }");
13702 cx.simulate_keystroke(".");
13703
13704 let unresolved_item_1 = lsp::CompletionItem {
13705 label: "id".to_string(),
13706 filter_text: Some("id".to_string()),
13707 detail: None,
13708 documentation: None,
13709 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13710 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
13711 new_text: ".id".to_string(),
13712 })),
13713 ..lsp::CompletionItem::default()
13714 };
13715 let resolved_item_1 = lsp::CompletionItem {
13716 additional_text_edits: Some(vec![lsp::TextEdit {
13717 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
13718 new_text: "!!".to_string(),
13719 }]),
13720 ..unresolved_item_1.clone()
13721 };
13722 let unresolved_item_2 = lsp::CompletionItem {
13723 label: "other".to_string(),
13724 filter_text: Some("other".to_string()),
13725 detail: None,
13726 documentation: None,
13727 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13728 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
13729 new_text: ".other".to_string(),
13730 })),
13731 ..lsp::CompletionItem::default()
13732 };
13733 let resolved_item_2 = lsp::CompletionItem {
13734 additional_text_edits: Some(vec![lsp::TextEdit {
13735 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
13736 new_text: "??".to_string(),
13737 }]),
13738 ..unresolved_item_2.clone()
13739 };
13740
13741 let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
13742 let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
13743 cx.lsp
13744 .server
13745 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
13746 let unresolved_item_1 = unresolved_item_1.clone();
13747 let resolved_item_1 = resolved_item_1.clone();
13748 let unresolved_item_2 = unresolved_item_2.clone();
13749 let resolved_item_2 = resolved_item_2.clone();
13750 let resolve_requests_1 = resolve_requests_1.clone();
13751 let resolve_requests_2 = resolve_requests_2.clone();
13752 move |unresolved_request, _| {
13753 let unresolved_item_1 = unresolved_item_1.clone();
13754 let resolved_item_1 = resolved_item_1.clone();
13755 let unresolved_item_2 = unresolved_item_2.clone();
13756 let resolved_item_2 = resolved_item_2.clone();
13757 let resolve_requests_1 = resolve_requests_1.clone();
13758 let resolve_requests_2 = resolve_requests_2.clone();
13759 async move {
13760 if unresolved_request == unresolved_item_1 {
13761 resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
13762 Ok(resolved_item_1.clone())
13763 } else if unresolved_request == unresolved_item_2 {
13764 resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
13765 Ok(resolved_item_2.clone())
13766 } else {
13767 panic!("Unexpected completion item {unresolved_request:?}")
13768 }
13769 }
13770 }
13771 })
13772 .detach();
13773
13774 cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
13775 let unresolved_item_1 = unresolved_item_1.clone();
13776 let unresolved_item_2 = unresolved_item_2.clone();
13777 async move {
13778 Ok(Some(lsp::CompletionResponse::Array(vec![
13779 unresolved_item_1,
13780 unresolved_item_2,
13781 ])))
13782 }
13783 })
13784 .next()
13785 .await;
13786
13787 cx.condition(|editor, _| editor.context_menu_visible())
13788 .await;
13789 cx.update_editor(|editor, _, _| {
13790 let context_menu = editor.context_menu.borrow_mut();
13791 let context_menu = context_menu
13792 .as_ref()
13793 .expect("Should have the context menu deployed");
13794 match context_menu {
13795 CodeContextMenu::Completions(completions_menu) => {
13796 let completions = completions_menu.completions.borrow_mut();
13797 assert_eq!(
13798 completions
13799 .iter()
13800 .map(|completion| &completion.label.text)
13801 .collect::<Vec<_>>(),
13802 vec!["id", "other"]
13803 )
13804 }
13805 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
13806 }
13807 });
13808 cx.run_until_parked();
13809
13810 cx.update_editor(|editor, window, cx| {
13811 editor.context_menu_next(&ContextMenuNext, window, cx);
13812 });
13813 cx.run_until_parked();
13814 cx.update_editor(|editor, window, cx| {
13815 editor.context_menu_prev(&ContextMenuPrevious, window, cx);
13816 });
13817 cx.run_until_parked();
13818 cx.update_editor(|editor, window, cx| {
13819 editor.context_menu_next(&ContextMenuNext, window, cx);
13820 });
13821 cx.run_until_parked();
13822 cx.update_editor(|editor, window, cx| {
13823 editor
13824 .compose_completion(&ComposeCompletion::default(), window, cx)
13825 .expect("No task returned")
13826 })
13827 .await
13828 .expect("Completion failed");
13829 cx.run_until_parked();
13830
13831 cx.update_editor(|editor, _, cx| {
13832 assert_eq!(
13833 resolve_requests_1.load(atomic::Ordering::Acquire),
13834 1,
13835 "Should always resolve once despite multiple selections"
13836 );
13837 assert_eq!(
13838 resolve_requests_2.load(atomic::Ordering::Acquire),
13839 1,
13840 "Should always resolve once after multiple selections and applying the completion"
13841 );
13842 assert_eq!(
13843 editor.text(cx),
13844 "fn main() { let a = ??.other; }",
13845 "Should use resolved data when applying the completion"
13846 );
13847 });
13848}
13849
13850#[gpui::test]
13851async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
13852 init_test(cx, |_| {});
13853
13854 let item_0 = lsp::CompletionItem {
13855 label: "abs".into(),
13856 insert_text: Some("abs".into()),
13857 data: Some(json!({ "very": "special"})),
13858 insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
13859 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
13860 lsp::InsertReplaceEdit {
13861 new_text: "abs".to_string(),
13862 insert: lsp::Range::default(),
13863 replace: lsp::Range::default(),
13864 },
13865 )),
13866 ..lsp::CompletionItem::default()
13867 };
13868 let items = iter::once(item_0.clone())
13869 .chain((11..51).map(|i| lsp::CompletionItem {
13870 label: format!("item_{}", i),
13871 insert_text: Some(format!("item_{}", i)),
13872 insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
13873 ..lsp::CompletionItem::default()
13874 }))
13875 .collect::<Vec<_>>();
13876
13877 let default_commit_characters = vec!["?".to_string()];
13878 let default_data = json!({ "default": "data"});
13879 let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
13880 let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
13881 let default_edit_range = lsp::Range {
13882 start: lsp::Position {
13883 line: 0,
13884 character: 5,
13885 },
13886 end: lsp::Position {
13887 line: 0,
13888 character: 5,
13889 },
13890 };
13891
13892 let mut cx = EditorLspTestContext::new_rust(
13893 lsp::ServerCapabilities {
13894 completion_provider: Some(lsp::CompletionOptions {
13895 trigger_characters: Some(vec![".".to_string()]),
13896 resolve_provider: Some(true),
13897 ..Default::default()
13898 }),
13899 ..Default::default()
13900 },
13901 cx,
13902 )
13903 .await;
13904
13905 cx.set_state("fn main() { let a = 2ˇ; }");
13906 cx.simulate_keystroke(".");
13907
13908 let completion_data = default_data.clone();
13909 let completion_characters = default_commit_characters.clone();
13910 let completion_items = items.clone();
13911 cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
13912 let default_data = completion_data.clone();
13913 let default_commit_characters = completion_characters.clone();
13914 let items = completion_items.clone();
13915 async move {
13916 Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13917 items,
13918 item_defaults: Some(lsp::CompletionListItemDefaults {
13919 data: Some(default_data.clone()),
13920 commit_characters: Some(default_commit_characters.clone()),
13921 edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
13922 default_edit_range,
13923 )),
13924 insert_text_format: Some(default_insert_text_format),
13925 insert_text_mode: Some(default_insert_text_mode),
13926 }),
13927 ..lsp::CompletionList::default()
13928 })))
13929 }
13930 })
13931 .next()
13932 .await;
13933
13934 let resolved_items = Arc::new(Mutex::new(Vec::new()));
13935 cx.lsp
13936 .server
13937 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
13938 let closure_resolved_items = resolved_items.clone();
13939 move |item_to_resolve, _| {
13940 let closure_resolved_items = closure_resolved_items.clone();
13941 async move {
13942 closure_resolved_items.lock().push(item_to_resolve.clone());
13943 Ok(item_to_resolve)
13944 }
13945 }
13946 })
13947 .detach();
13948
13949 cx.condition(|editor, _| editor.context_menu_visible())
13950 .await;
13951 cx.run_until_parked();
13952 cx.update_editor(|editor, _, _| {
13953 let menu = editor.context_menu.borrow_mut();
13954 match menu.as_ref().expect("should have the completions menu") {
13955 CodeContextMenu::Completions(completions_menu) => {
13956 assert_eq!(
13957 completions_menu
13958 .entries
13959 .borrow()
13960 .iter()
13961 .map(|mat| mat.string.clone())
13962 .collect::<Vec<String>>(),
13963 items
13964 .iter()
13965 .map(|completion| completion.label.clone())
13966 .collect::<Vec<String>>()
13967 );
13968 }
13969 CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
13970 }
13971 });
13972 // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
13973 // with 4 from the end.
13974 assert_eq!(
13975 *resolved_items.lock(),
13976 [&items[0..16], &items[items.len() - 4..items.len()]]
13977 .concat()
13978 .iter()
13979 .cloned()
13980 .map(|mut item| {
13981 if item.data.is_none() {
13982 item.data = Some(default_data.clone());
13983 }
13984 item
13985 })
13986 .collect::<Vec<lsp::CompletionItem>>(),
13987 "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
13988 );
13989 resolved_items.lock().clear();
13990
13991 cx.update_editor(|editor, window, cx| {
13992 editor.context_menu_prev(&ContextMenuPrevious, window, cx);
13993 });
13994 cx.run_until_parked();
13995 // Completions that have already been resolved are skipped.
13996 assert_eq!(
13997 *resolved_items.lock(),
13998 items[items.len() - 16..items.len() - 4]
13999 .iter()
14000 .cloned()
14001 .map(|mut item| {
14002 if item.data.is_none() {
14003 item.data = Some(default_data.clone());
14004 }
14005 item
14006 })
14007 .collect::<Vec<lsp::CompletionItem>>()
14008 );
14009 resolved_items.lock().clear();
14010}
14011
14012#[gpui::test]
14013async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
14014 init_test(cx, |_| {});
14015
14016 let mut cx = EditorLspTestContext::new(
14017 Language::new(
14018 LanguageConfig {
14019 matcher: LanguageMatcher {
14020 path_suffixes: vec!["jsx".into()],
14021 ..Default::default()
14022 },
14023 overrides: [(
14024 "element".into(),
14025 LanguageConfigOverride {
14026 completion_query_characters: Override::Set(['-'].into_iter().collect()),
14027 ..Default::default()
14028 },
14029 )]
14030 .into_iter()
14031 .collect(),
14032 ..Default::default()
14033 },
14034 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
14035 )
14036 .with_override_query("(jsx_self_closing_element) @element")
14037 .unwrap(),
14038 lsp::ServerCapabilities {
14039 completion_provider: Some(lsp::CompletionOptions {
14040 trigger_characters: Some(vec![":".to_string()]),
14041 ..Default::default()
14042 }),
14043 ..Default::default()
14044 },
14045 cx,
14046 )
14047 .await;
14048
14049 cx.lsp
14050 .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14051 Ok(Some(lsp::CompletionResponse::Array(vec![
14052 lsp::CompletionItem {
14053 label: "bg-blue".into(),
14054 ..Default::default()
14055 },
14056 lsp::CompletionItem {
14057 label: "bg-red".into(),
14058 ..Default::default()
14059 },
14060 lsp::CompletionItem {
14061 label: "bg-yellow".into(),
14062 ..Default::default()
14063 },
14064 ])))
14065 });
14066
14067 cx.set_state(r#"<p class="bgˇ" />"#);
14068
14069 // Trigger completion when typing a dash, because the dash is an extra
14070 // word character in the 'element' scope, which contains the cursor.
14071 cx.simulate_keystroke("-");
14072 cx.executor().run_until_parked();
14073 cx.update_editor(|editor, _, _| {
14074 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14075 {
14076 assert_eq!(
14077 completion_menu_entries(&menu),
14078 &["bg-red", "bg-blue", "bg-yellow"]
14079 );
14080 } else {
14081 panic!("expected completion menu to be open");
14082 }
14083 });
14084
14085 cx.simulate_keystroke("l");
14086 cx.executor().run_until_parked();
14087 cx.update_editor(|editor, _, _| {
14088 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14089 {
14090 assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
14091 } else {
14092 panic!("expected completion menu to be open");
14093 }
14094 });
14095
14096 // When filtering completions, consider the character after the '-' to
14097 // be the start of a subword.
14098 cx.set_state(r#"<p class="yelˇ" />"#);
14099 cx.simulate_keystroke("l");
14100 cx.executor().run_until_parked();
14101 cx.update_editor(|editor, _, _| {
14102 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14103 {
14104 assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
14105 } else {
14106 panic!("expected completion menu to be open");
14107 }
14108 });
14109}
14110
14111fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
14112 let entries = menu.entries.borrow();
14113 entries.iter().map(|mat| mat.string.clone()).collect()
14114}
14115
14116#[gpui::test]
14117async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
14118 init_test(cx, |settings| {
14119 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
14120 FormatterList(vec![Formatter::Prettier].into()),
14121 ))
14122 });
14123
14124 let fs = FakeFs::new(cx.executor());
14125 fs.insert_file(path!("/file.ts"), Default::default()).await;
14126
14127 let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
14128 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14129
14130 language_registry.add(Arc::new(Language::new(
14131 LanguageConfig {
14132 name: "TypeScript".into(),
14133 matcher: LanguageMatcher {
14134 path_suffixes: vec!["ts".to_string()],
14135 ..Default::default()
14136 },
14137 ..Default::default()
14138 },
14139 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
14140 )));
14141 update_test_language_settings(cx, |settings| {
14142 settings.defaults.prettier = Some(PrettierSettings {
14143 allowed: true,
14144 ..PrettierSettings::default()
14145 });
14146 });
14147
14148 let test_plugin = "test_plugin";
14149 let _ = language_registry.register_fake_lsp(
14150 "TypeScript",
14151 FakeLspAdapter {
14152 prettier_plugins: vec![test_plugin],
14153 ..Default::default()
14154 },
14155 );
14156
14157 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
14158 let buffer = project
14159 .update(cx, |project, cx| {
14160 project.open_local_buffer(path!("/file.ts"), cx)
14161 })
14162 .await
14163 .unwrap();
14164
14165 let buffer_text = "one\ntwo\nthree\n";
14166 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
14167 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
14168 editor.update_in(cx, |editor, window, cx| {
14169 editor.set_text(buffer_text, window, cx)
14170 });
14171
14172 editor
14173 .update_in(cx, |editor, window, cx| {
14174 editor.perform_format(
14175 project.clone(),
14176 FormatTrigger::Manual,
14177 FormatTarget::Buffers,
14178 window,
14179 cx,
14180 )
14181 })
14182 .unwrap()
14183 .await;
14184 assert_eq!(
14185 editor.update(cx, |editor, cx| editor.text(cx)),
14186 buffer_text.to_string() + prettier_format_suffix,
14187 "Test prettier formatting was not applied to the original buffer text",
14188 );
14189
14190 update_test_language_settings(cx, |settings| {
14191 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
14192 });
14193 let format = editor.update_in(cx, |editor, window, cx| {
14194 editor.perform_format(
14195 project.clone(),
14196 FormatTrigger::Manual,
14197 FormatTarget::Buffers,
14198 window,
14199 cx,
14200 )
14201 });
14202 format.await.unwrap();
14203 assert_eq!(
14204 editor.update(cx, |editor, cx| editor.text(cx)),
14205 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
14206 "Autoformatting (via test prettier) was not applied to the original buffer text",
14207 );
14208}
14209
14210#[gpui::test]
14211async fn test_addition_reverts(cx: &mut TestAppContext) {
14212 init_test(cx, |_| {});
14213 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14214 let base_text = indoc! {r#"
14215 struct Row;
14216 struct Row1;
14217 struct Row2;
14218
14219 struct Row4;
14220 struct Row5;
14221 struct Row6;
14222
14223 struct Row8;
14224 struct Row9;
14225 struct Row10;"#};
14226
14227 // When addition hunks are not adjacent to carets, no hunk revert is performed
14228 assert_hunk_revert(
14229 indoc! {r#"struct Row;
14230 struct Row1;
14231 struct Row1.1;
14232 struct Row1.2;
14233 struct Row2;ˇ
14234
14235 struct Row4;
14236 struct Row5;
14237 struct Row6;
14238
14239 struct Row8;
14240 ˇstruct Row9;
14241 struct Row9.1;
14242 struct Row9.2;
14243 struct Row9.3;
14244 struct Row10;"#},
14245 vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
14246 indoc! {r#"struct Row;
14247 struct Row1;
14248 struct Row1.1;
14249 struct Row1.2;
14250 struct Row2;ˇ
14251
14252 struct Row4;
14253 struct Row5;
14254 struct Row6;
14255
14256 struct Row8;
14257 ˇstruct Row9;
14258 struct Row9.1;
14259 struct Row9.2;
14260 struct Row9.3;
14261 struct Row10;"#},
14262 base_text,
14263 &mut cx,
14264 );
14265 // Same for selections
14266 assert_hunk_revert(
14267 indoc! {r#"struct Row;
14268 struct Row1;
14269 struct Row2;
14270 struct Row2.1;
14271 struct Row2.2;
14272 «ˇ
14273 struct Row4;
14274 struct» Row5;
14275 «struct Row6;
14276 ˇ»
14277 struct Row9.1;
14278 struct Row9.2;
14279 struct Row9.3;
14280 struct Row8;
14281 struct Row9;
14282 struct Row10;"#},
14283 vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
14284 indoc! {r#"struct Row;
14285 struct Row1;
14286 struct Row2;
14287 struct Row2.1;
14288 struct Row2.2;
14289 «ˇ
14290 struct Row4;
14291 struct» Row5;
14292 «struct Row6;
14293 ˇ»
14294 struct Row9.1;
14295 struct Row9.2;
14296 struct Row9.3;
14297 struct Row8;
14298 struct Row9;
14299 struct Row10;"#},
14300 base_text,
14301 &mut cx,
14302 );
14303
14304 // When carets and selections intersect the addition hunks, those are reverted.
14305 // Adjacent carets got merged.
14306 assert_hunk_revert(
14307 indoc! {r#"struct Row;
14308 ˇ// something on the top
14309 struct Row1;
14310 struct Row2;
14311 struct Roˇw3.1;
14312 struct Row2.2;
14313 struct Row2.3;ˇ
14314
14315 struct Row4;
14316 struct ˇRow5.1;
14317 struct Row5.2;
14318 struct «Rowˇ»5.3;
14319 struct Row5;
14320 struct Row6;
14321 ˇ
14322 struct Row9.1;
14323 struct «Rowˇ»9.2;
14324 struct «ˇRow»9.3;
14325 struct Row8;
14326 struct Row9;
14327 «ˇ// something on bottom»
14328 struct Row10;"#},
14329 vec![
14330 DiffHunkStatusKind::Added,
14331 DiffHunkStatusKind::Added,
14332 DiffHunkStatusKind::Added,
14333 DiffHunkStatusKind::Added,
14334 DiffHunkStatusKind::Added,
14335 ],
14336 indoc! {r#"struct Row;
14337 ˇstruct Row1;
14338 struct Row2;
14339 ˇ
14340 struct Row4;
14341 ˇstruct Row5;
14342 struct Row6;
14343 ˇ
14344 ˇstruct Row8;
14345 struct Row9;
14346 ˇstruct Row10;"#},
14347 base_text,
14348 &mut cx,
14349 );
14350}
14351
14352#[gpui::test]
14353async fn test_modification_reverts(cx: &mut TestAppContext) {
14354 init_test(cx, |_| {});
14355 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14356 let base_text = indoc! {r#"
14357 struct Row;
14358 struct Row1;
14359 struct Row2;
14360
14361 struct Row4;
14362 struct Row5;
14363 struct Row6;
14364
14365 struct Row8;
14366 struct Row9;
14367 struct Row10;"#};
14368
14369 // Modification hunks behave the same as the addition ones.
14370 assert_hunk_revert(
14371 indoc! {r#"struct Row;
14372 struct Row1;
14373 struct Row33;
14374 ˇ
14375 struct Row4;
14376 struct Row5;
14377 struct Row6;
14378 ˇ
14379 struct Row99;
14380 struct Row9;
14381 struct Row10;"#},
14382 vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
14383 indoc! {r#"struct Row;
14384 struct Row1;
14385 struct Row33;
14386 ˇ
14387 struct Row4;
14388 struct Row5;
14389 struct Row6;
14390 ˇ
14391 struct Row99;
14392 struct Row9;
14393 struct Row10;"#},
14394 base_text,
14395 &mut cx,
14396 );
14397 assert_hunk_revert(
14398 indoc! {r#"struct Row;
14399 struct Row1;
14400 struct Row33;
14401 «ˇ
14402 struct Row4;
14403 struct» Row5;
14404 «struct Row6;
14405 ˇ»
14406 struct Row99;
14407 struct Row9;
14408 struct Row10;"#},
14409 vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
14410 indoc! {r#"struct Row;
14411 struct Row1;
14412 struct Row33;
14413 «ˇ
14414 struct Row4;
14415 struct» Row5;
14416 «struct Row6;
14417 ˇ»
14418 struct Row99;
14419 struct Row9;
14420 struct Row10;"#},
14421 base_text,
14422 &mut cx,
14423 );
14424
14425 assert_hunk_revert(
14426 indoc! {r#"ˇstruct Row1.1;
14427 struct Row1;
14428 «ˇstr»uct Row22;
14429
14430 struct ˇRow44;
14431 struct Row5;
14432 struct «Rˇ»ow66;ˇ
14433
14434 «struˇ»ct Row88;
14435 struct Row9;
14436 struct Row1011;ˇ"#},
14437 vec![
14438 DiffHunkStatusKind::Modified,
14439 DiffHunkStatusKind::Modified,
14440 DiffHunkStatusKind::Modified,
14441 DiffHunkStatusKind::Modified,
14442 DiffHunkStatusKind::Modified,
14443 DiffHunkStatusKind::Modified,
14444 ],
14445 indoc! {r#"struct Row;
14446 ˇstruct Row1;
14447 struct Row2;
14448 ˇ
14449 struct Row4;
14450 ˇstruct Row5;
14451 struct Row6;
14452 ˇ
14453 struct Row8;
14454 ˇstruct Row9;
14455 struct Row10;ˇ"#},
14456 base_text,
14457 &mut cx,
14458 );
14459}
14460
14461#[gpui::test]
14462async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
14463 init_test(cx, |_| {});
14464 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14465 let base_text = indoc! {r#"
14466 one
14467
14468 two
14469 three
14470 "#};
14471
14472 cx.set_head_text(base_text);
14473 cx.set_state("\nˇ\n");
14474 cx.executor().run_until_parked();
14475 cx.update_editor(|editor, _window, cx| {
14476 editor.expand_selected_diff_hunks(cx);
14477 });
14478 cx.executor().run_until_parked();
14479 cx.update_editor(|editor, window, cx| {
14480 editor.backspace(&Default::default(), window, cx);
14481 });
14482 cx.run_until_parked();
14483 cx.assert_state_with_diff(
14484 indoc! {r#"
14485
14486 - two
14487 - threeˇ
14488 +
14489 "#}
14490 .to_string(),
14491 );
14492}
14493
14494#[gpui::test]
14495async fn test_deletion_reverts(cx: &mut TestAppContext) {
14496 init_test(cx, |_| {});
14497 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14498 let base_text = indoc! {r#"struct Row;
14499struct Row1;
14500struct Row2;
14501
14502struct Row4;
14503struct Row5;
14504struct Row6;
14505
14506struct Row8;
14507struct Row9;
14508struct Row10;"#};
14509
14510 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
14511 assert_hunk_revert(
14512 indoc! {r#"struct Row;
14513 struct Row2;
14514
14515 ˇstruct Row4;
14516 struct Row5;
14517 struct Row6;
14518 ˇ
14519 struct Row8;
14520 struct Row10;"#},
14521 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
14522 indoc! {r#"struct Row;
14523 struct Row2;
14524
14525 ˇstruct Row4;
14526 struct Row5;
14527 struct Row6;
14528 ˇ
14529 struct Row8;
14530 struct Row10;"#},
14531 base_text,
14532 &mut cx,
14533 );
14534 assert_hunk_revert(
14535 indoc! {r#"struct Row;
14536 struct Row2;
14537
14538 «ˇstruct Row4;
14539 struct» Row5;
14540 «struct Row6;
14541 ˇ»
14542 struct Row8;
14543 struct Row10;"#},
14544 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
14545 indoc! {r#"struct Row;
14546 struct Row2;
14547
14548 «ˇstruct Row4;
14549 struct» Row5;
14550 «struct Row6;
14551 ˇ»
14552 struct Row8;
14553 struct Row10;"#},
14554 base_text,
14555 &mut cx,
14556 );
14557
14558 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
14559 assert_hunk_revert(
14560 indoc! {r#"struct Row;
14561 ˇstruct Row2;
14562
14563 struct Row4;
14564 struct Row5;
14565 struct Row6;
14566
14567 struct Row8;ˇ
14568 struct Row10;"#},
14569 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
14570 indoc! {r#"struct Row;
14571 struct Row1;
14572 ˇstruct Row2;
14573
14574 struct Row4;
14575 struct Row5;
14576 struct Row6;
14577
14578 struct Row8;ˇ
14579 struct Row9;
14580 struct Row10;"#},
14581 base_text,
14582 &mut cx,
14583 );
14584 assert_hunk_revert(
14585 indoc! {r#"struct Row;
14586 struct Row2«ˇ;
14587 struct Row4;
14588 struct» Row5;
14589 «struct Row6;
14590
14591 struct Row8;ˇ»
14592 struct Row10;"#},
14593 vec![
14594 DiffHunkStatusKind::Deleted,
14595 DiffHunkStatusKind::Deleted,
14596 DiffHunkStatusKind::Deleted,
14597 ],
14598 indoc! {r#"struct Row;
14599 struct Row1;
14600 struct Row2«ˇ;
14601
14602 struct Row4;
14603 struct» Row5;
14604 «struct Row6;
14605
14606 struct Row8;ˇ»
14607 struct Row9;
14608 struct Row10;"#},
14609 base_text,
14610 &mut cx,
14611 );
14612}
14613
14614#[gpui::test]
14615async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
14616 init_test(cx, |_| {});
14617
14618 let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
14619 let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
14620 let base_text_3 =
14621 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
14622
14623 let text_1 = edit_first_char_of_every_line(base_text_1);
14624 let text_2 = edit_first_char_of_every_line(base_text_2);
14625 let text_3 = edit_first_char_of_every_line(base_text_3);
14626
14627 let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
14628 let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
14629 let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
14630
14631 let multibuffer = cx.new(|cx| {
14632 let mut multibuffer = MultiBuffer::new(ReadWrite);
14633 multibuffer.push_excerpts(
14634 buffer_1.clone(),
14635 [
14636 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14637 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14638 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
14639 ],
14640 cx,
14641 );
14642 multibuffer.push_excerpts(
14643 buffer_2.clone(),
14644 [
14645 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14646 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14647 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
14648 ],
14649 cx,
14650 );
14651 multibuffer.push_excerpts(
14652 buffer_3.clone(),
14653 [
14654 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14655 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14656 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
14657 ],
14658 cx,
14659 );
14660 multibuffer
14661 });
14662
14663 let fs = FakeFs::new(cx.executor());
14664 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
14665 let (editor, cx) = cx
14666 .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
14667 editor.update_in(cx, |editor, _window, cx| {
14668 for (buffer, diff_base) in [
14669 (buffer_1.clone(), base_text_1),
14670 (buffer_2.clone(), base_text_2),
14671 (buffer_3.clone(), base_text_3),
14672 ] {
14673 let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
14674 editor
14675 .buffer
14676 .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
14677 }
14678 });
14679 cx.executor().run_until_parked();
14680
14681 editor.update_in(cx, |editor, window, cx| {
14682 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}");
14683 editor.select_all(&SelectAll, window, cx);
14684 editor.git_restore(&Default::default(), window, cx);
14685 });
14686 cx.executor().run_until_parked();
14687
14688 // When all ranges are selected, all buffer hunks are reverted.
14689 editor.update(cx, |editor, cx| {
14690 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");
14691 });
14692 buffer_1.update(cx, |buffer, _| {
14693 assert_eq!(buffer.text(), base_text_1);
14694 });
14695 buffer_2.update(cx, |buffer, _| {
14696 assert_eq!(buffer.text(), base_text_2);
14697 });
14698 buffer_3.update(cx, |buffer, _| {
14699 assert_eq!(buffer.text(), base_text_3);
14700 });
14701
14702 editor.update_in(cx, |editor, window, cx| {
14703 editor.undo(&Default::default(), window, cx);
14704 });
14705
14706 editor.update_in(cx, |editor, window, cx| {
14707 editor.change_selections(None, window, cx, |s| {
14708 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
14709 });
14710 editor.git_restore(&Default::default(), window, cx);
14711 });
14712
14713 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
14714 // but not affect buffer_2 and its related excerpts.
14715 editor.update(cx, |editor, cx| {
14716 assert_eq!(
14717 editor.text(cx),
14718 "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}"
14719 );
14720 });
14721 buffer_1.update(cx, |buffer, _| {
14722 assert_eq!(buffer.text(), base_text_1);
14723 });
14724 buffer_2.update(cx, |buffer, _| {
14725 assert_eq!(
14726 buffer.text(),
14727 "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
14728 );
14729 });
14730 buffer_3.update(cx, |buffer, _| {
14731 assert_eq!(
14732 buffer.text(),
14733 "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
14734 );
14735 });
14736
14737 fn edit_first_char_of_every_line(text: &str) -> String {
14738 text.split('\n')
14739 .map(|line| format!("X{}", &line[1..]))
14740 .collect::<Vec<_>>()
14741 .join("\n")
14742 }
14743}
14744
14745#[gpui::test]
14746async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
14747 init_test(cx, |_| {});
14748
14749 let cols = 4;
14750 let rows = 10;
14751 let sample_text_1 = sample_text(rows, cols, 'a');
14752 assert_eq!(
14753 sample_text_1,
14754 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
14755 );
14756 let sample_text_2 = sample_text(rows, cols, 'l');
14757 assert_eq!(
14758 sample_text_2,
14759 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
14760 );
14761 let sample_text_3 = sample_text(rows, cols, 'v');
14762 assert_eq!(
14763 sample_text_3,
14764 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
14765 );
14766
14767 let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
14768 let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
14769 let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
14770
14771 let multi_buffer = cx.new(|cx| {
14772 let mut multibuffer = MultiBuffer::new(ReadWrite);
14773 multibuffer.push_excerpts(
14774 buffer_1.clone(),
14775 [
14776 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14777 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14778 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
14779 ],
14780 cx,
14781 );
14782 multibuffer.push_excerpts(
14783 buffer_2.clone(),
14784 [
14785 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14786 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14787 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
14788 ],
14789 cx,
14790 );
14791 multibuffer.push_excerpts(
14792 buffer_3.clone(),
14793 [
14794 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14795 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14796 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
14797 ],
14798 cx,
14799 );
14800 multibuffer
14801 });
14802
14803 let fs = FakeFs::new(cx.executor());
14804 fs.insert_tree(
14805 "/a",
14806 json!({
14807 "main.rs": sample_text_1,
14808 "other.rs": sample_text_2,
14809 "lib.rs": sample_text_3,
14810 }),
14811 )
14812 .await;
14813 let project = Project::test(fs, ["/a".as_ref()], cx).await;
14814 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14815 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14816 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
14817 Editor::new(
14818 EditorMode::full(),
14819 multi_buffer,
14820 Some(project.clone()),
14821 window,
14822 cx,
14823 )
14824 });
14825 let multibuffer_item_id = workspace
14826 .update(cx, |workspace, window, cx| {
14827 assert!(
14828 workspace.active_item(cx).is_none(),
14829 "active item should be None before the first item is added"
14830 );
14831 workspace.add_item_to_active_pane(
14832 Box::new(multi_buffer_editor.clone()),
14833 None,
14834 true,
14835 window,
14836 cx,
14837 );
14838 let active_item = workspace
14839 .active_item(cx)
14840 .expect("should have an active item after adding the multi buffer");
14841 assert!(
14842 !active_item.is_singleton(cx),
14843 "A multi buffer was expected to active after adding"
14844 );
14845 active_item.item_id()
14846 })
14847 .unwrap();
14848 cx.executor().run_until_parked();
14849
14850 multi_buffer_editor.update_in(cx, |editor, window, cx| {
14851 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
14852 s.select_ranges(Some(1..2))
14853 });
14854 editor.open_excerpts(&OpenExcerpts, window, cx);
14855 });
14856 cx.executor().run_until_parked();
14857 let first_item_id = workspace
14858 .update(cx, |workspace, window, cx| {
14859 let active_item = workspace
14860 .active_item(cx)
14861 .expect("should have an active item after navigating into the 1st buffer");
14862 let first_item_id = active_item.item_id();
14863 assert_ne!(
14864 first_item_id, multibuffer_item_id,
14865 "Should navigate into the 1st buffer and activate it"
14866 );
14867 assert!(
14868 active_item.is_singleton(cx),
14869 "New active item should be a singleton buffer"
14870 );
14871 assert_eq!(
14872 active_item
14873 .act_as::<Editor>(cx)
14874 .expect("should have navigated into an editor for the 1st buffer")
14875 .read(cx)
14876 .text(cx),
14877 sample_text_1
14878 );
14879
14880 workspace
14881 .go_back(workspace.active_pane().downgrade(), window, cx)
14882 .detach_and_log_err(cx);
14883
14884 first_item_id
14885 })
14886 .unwrap();
14887 cx.executor().run_until_parked();
14888 workspace
14889 .update(cx, |workspace, _, cx| {
14890 let active_item = workspace
14891 .active_item(cx)
14892 .expect("should have an active item after navigating back");
14893 assert_eq!(
14894 active_item.item_id(),
14895 multibuffer_item_id,
14896 "Should navigate back to the multi buffer"
14897 );
14898 assert!(!active_item.is_singleton(cx));
14899 })
14900 .unwrap();
14901
14902 multi_buffer_editor.update_in(cx, |editor, window, cx| {
14903 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
14904 s.select_ranges(Some(39..40))
14905 });
14906 editor.open_excerpts(&OpenExcerpts, window, cx);
14907 });
14908 cx.executor().run_until_parked();
14909 let second_item_id = workspace
14910 .update(cx, |workspace, window, cx| {
14911 let active_item = workspace
14912 .active_item(cx)
14913 .expect("should have an active item after navigating into the 2nd buffer");
14914 let second_item_id = active_item.item_id();
14915 assert_ne!(
14916 second_item_id, multibuffer_item_id,
14917 "Should navigate away from the multibuffer"
14918 );
14919 assert_ne!(
14920 second_item_id, first_item_id,
14921 "Should navigate into the 2nd buffer and activate it"
14922 );
14923 assert!(
14924 active_item.is_singleton(cx),
14925 "New active item should be a singleton buffer"
14926 );
14927 assert_eq!(
14928 active_item
14929 .act_as::<Editor>(cx)
14930 .expect("should have navigated into an editor")
14931 .read(cx)
14932 .text(cx),
14933 sample_text_2
14934 );
14935
14936 workspace
14937 .go_back(workspace.active_pane().downgrade(), window, cx)
14938 .detach_and_log_err(cx);
14939
14940 second_item_id
14941 })
14942 .unwrap();
14943 cx.executor().run_until_parked();
14944 workspace
14945 .update(cx, |workspace, _, cx| {
14946 let active_item = workspace
14947 .active_item(cx)
14948 .expect("should have an active item after navigating back from the 2nd buffer");
14949 assert_eq!(
14950 active_item.item_id(),
14951 multibuffer_item_id,
14952 "Should navigate back from the 2nd buffer to the multi buffer"
14953 );
14954 assert!(!active_item.is_singleton(cx));
14955 })
14956 .unwrap();
14957
14958 multi_buffer_editor.update_in(cx, |editor, window, cx| {
14959 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
14960 s.select_ranges(Some(70..70))
14961 });
14962 editor.open_excerpts(&OpenExcerpts, window, cx);
14963 });
14964 cx.executor().run_until_parked();
14965 workspace
14966 .update(cx, |workspace, window, cx| {
14967 let active_item = workspace
14968 .active_item(cx)
14969 .expect("should have an active item after navigating into the 3rd buffer");
14970 let third_item_id = active_item.item_id();
14971 assert_ne!(
14972 third_item_id, multibuffer_item_id,
14973 "Should navigate into the 3rd buffer and activate it"
14974 );
14975 assert_ne!(third_item_id, first_item_id);
14976 assert_ne!(third_item_id, second_item_id);
14977 assert!(
14978 active_item.is_singleton(cx),
14979 "New active item should be a singleton buffer"
14980 );
14981 assert_eq!(
14982 active_item
14983 .act_as::<Editor>(cx)
14984 .expect("should have navigated into an editor")
14985 .read(cx)
14986 .text(cx),
14987 sample_text_3
14988 );
14989
14990 workspace
14991 .go_back(workspace.active_pane().downgrade(), window, cx)
14992 .detach_and_log_err(cx);
14993 })
14994 .unwrap();
14995 cx.executor().run_until_parked();
14996 workspace
14997 .update(cx, |workspace, _, cx| {
14998 let active_item = workspace
14999 .active_item(cx)
15000 .expect("should have an active item after navigating back from the 3rd buffer");
15001 assert_eq!(
15002 active_item.item_id(),
15003 multibuffer_item_id,
15004 "Should navigate back from the 3rd buffer to the multi buffer"
15005 );
15006 assert!(!active_item.is_singleton(cx));
15007 })
15008 .unwrap();
15009}
15010
15011#[gpui::test]
15012async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15013 init_test(cx, |_| {});
15014
15015 let mut cx = EditorTestContext::new(cx).await;
15016
15017 let diff_base = r#"
15018 use some::mod;
15019
15020 const A: u32 = 42;
15021
15022 fn main() {
15023 println!("hello");
15024
15025 println!("world");
15026 }
15027 "#
15028 .unindent();
15029
15030 cx.set_state(
15031 &r#"
15032 use some::modified;
15033
15034 ˇ
15035 fn main() {
15036 println!("hello there");
15037
15038 println!("around the");
15039 println!("world");
15040 }
15041 "#
15042 .unindent(),
15043 );
15044
15045 cx.set_head_text(&diff_base);
15046 executor.run_until_parked();
15047
15048 cx.update_editor(|editor, window, cx| {
15049 editor.go_to_next_hunk(&GoToHunk, window, cx);
15050 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
15051 });
15052 executor.run_until_parked();
15053 cx.assert_state_with_diff(
15054 r#"
15055 use some::modified;
15056
15057
15058 fn main() {
15059 - println!("hello");
15060 + ˇ println!("hello there");
15061
15062 println!("around the");
15063 println!("world");
15064 }
15065 "#
15066 .unindent(),
15067 );
15068
15069 cx.update_editor(|editor, window, cx| {
15070 for _ in 0..2 {
15071 editor.go_to_next_hunk(&GoToHunk, window, cx);
15072 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
15073 }
15074 });
15075 executor.run_until_parked();
15076 cx.assert_state_with_diff(
15077 r#"
15078 - use some::mod;
15079 + ˇuse some::modified;
15080
15081
15082 fn main() {
15083 - println!("hello");
15084 + println!("hello there");
15085
15086 + println!("around the");
15087 println!("world");
15088 }
15089 "#
15090 .unindent(),
15091 );
15092
15093 cx.update_editor(|editor, window, cx| {
15094 editor.go_to_next_hunk(&GoToHunk, window, cx);
15095 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
15096 });
15097 executor.run_until_parked();
15098 cx.assert_state_with_diff(
15099 r#"
15100 - use some::mod;
15101 + use some::modified;
15102
15103 - const A: u32 = 42;
15104 ˇ
15105 fn main() {
15106 - println!("hello");
15107 + println!("hello there");
15108
15109 + println!("around the");
15110 println!("world");
15111 }
15112 "#
15113 .unindent(),
15114 );
15115
15116 cx.update_editor(|editor, window, cx| {
15117 editor.cancel(&Cancel, window, cx);
15118 });
15119
15120 cx.assert_state_with_diff(
15121 r#"
15122 use some::modified;
15123
15124 ˇ
15125 fn main() {
15126 println!("hello there");
15127
15128 println!("around the");
15129 println!("world");
15130 }
15131 "#
15132 .unindent(),
15133 );
15134}
15135
15136#[gpui::test]
15137async fn test_diff_base_change_with_expanded_diff_hunks(
15138 executor: BackgroundExecutor,
15139 cx: &mut TestAppContext,
15140) {
15141 init_test(cx, |_| {});
15142
15143 let mut cx = EditorTestContext::new(cx).await;
15144
15145 let diff_base = r#"
15146 use some::mod1;
15147 use some::mod2;
15148
15149 const A: u32 = 42;
15150 const B: u32 = 42;
15151 const C: u32 = 42;
15152
15153 fn main() {
15154 println!("hello");
15155
15156 println!("world");
15157 }
15158 "#
15159 .unindent();
15160
15161 cx.set_state(
15162 &r#"
15163 use some::mod2;
15164
15165 const A: u32 = 42;
15166 const C: u32 = 42;
15167
15168 fn main(ˇ) {
15169 //println!("hello");
15170
15171 println!("world");
15172 //
15173 //
15174 }
15175 "#
15176 .unindent(),
15177 );
15178
15179 cx.set_head_text(&diff_base);
15180 executor.run_until_parked();
15181
15182 cx.update_editor(|editor, window, cx| {
15183 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15184 });
15185 executor.run_until_parked();
15186 cx.assert_state_with_diff(
15187 r#"
15188 - use some::mod1;
15189 use some::mod2;
15190
15191 const A: u32 = 42;
15192 - const B: u32 = 42;
15193 const C: u32 = 42;
15194
15195 fn main(ˇ) {
15196 - println!("hello");
15197 + //println!("hello");
15198
15199 println!("world");
15200 + //
15201 + //
15202 }
15203 "#
15204 .unindent(),
15205 );
15206
15207 cx.set_head_text("new diff base!");
15208 executor.run_until_parked();
15209 cx.assert_state_with_diff(
15210 r#"
15211 - new diff base!
15212 + use some::mod2;
15213 +
15214 + const A: u32 = 42;
15215 + const C: u32 = 42;
15216 +
15217 + fn main(ˇ) {
15218 + //println!("hello");
15219 +
15220 + println!("world");
15221 + //
15222 + //
15223 + }
15224 "#
15225 .unindent(),
15226 );
15227}
15228
15229#[gpui::test]
15230async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
15231 init_test(cx, |_| {});
15232
15233 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
15234 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
15235 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
15236 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
15237 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
15238 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
15239
15240 let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
15241 let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
15242 let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
15243
15244 let multi_buffer = cx.new(|cx| {
15245 let mut multibuffer = MultiBuffer::new(ReadWrite);
15246 multibuffer.push_excerpts(
15247 buffer_1.clone(),
15248 [
15249 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15250 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15251 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
15252 ],
15253 cx,
15254 );
15255 multibuffer.push_excerpts(
15256 buffer_2.clone(),
15257 [
15258 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15259 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15260 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
15261 ],
15262 cx,
15263 );
15264 multibuffer.push_excerpts(
15265 buffer_3.clone(),
15266 [
15267 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15268 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15269 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
15270 ],
15271 cx,
15272 );
15273 multibuffer
15274 });
15275
15276 let editor =
15277 cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
15278 editor
15279 .update(cx, |editor, _window, cx| {
15280 for (buffer, diff_base) in [
15281 (buffer_1.clone(), file_1_old),
15282 (buffer_2.clone(), file_2_old),
15283 (buffer_3.clone(), file_3_old),
15284 ] {
15285 let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
15286 editor
15287 .buffer
15288 .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
15289 }
15290 })
15291 .unwrap();
15292
15293 let mut cx = EditorTestContext::for_editor(editor, cx).await;
15294 cx.run_until_parked();
15295
15296 cx.assert_editor_state(
15297 &"
15298 ˇaaa
15299 ccc
15300 ddd
15301
15302 ggg
15303 hhh
15304
15305
15306 lll
15307 mmm
15308 NNN
15309
15310 qqq
15311 rrr
15312
15313 uuu
15314 111
15315 222
15316 333
15317
15318 666
15319 777
15320
15321 000
15322 !!!"
15323 .unindent(),
15324 );
15325
15326 cx.update_editor(|editor, window, cx| {
15327 editor.select_all(&SelectAll, window, cx);
15328 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
15329 });
15330 cx.executor().run_until_parked();
15331
15332 cx.assert_state_with_diff(
15333 "
15334 «aaa
15335 - bbb
15336 ccc
15337 ddd
15338
15339 ggg
15340 hhh
15341
15342
15343 lll
15344 mmm
15345 - nnn
15346 + NNN
15347
15348 qqq
15349 rrr
15350
15351 uuu
15352 111
15353 222
15354 333
15355
15356 + 666
15357 777
15358
15359 000
15360 !!!ˇ»"
15361 .unindent(),
15362 );
15363}
15364
15365#[gpui::test]
15366async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
15367 init_test(cx, |_| {});
15368
15369 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
15370 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
15371
15372 let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
15373 let multi_buffer = cx.new(|cx| {
15374 let mut multibuffer = MultiBuffer::new(ReadWrite);
15375 multibuffer.push_excerpts(
15376 buffer.clone(),
15377 [
15378 ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
15379 ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
15380 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
15381 ],
15382 cx,
15383 );
15384 multibuffer
15385 });
15386
15387 let editor =
15388 cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
15389 editor
15390 .update(cx, |editor, _window, cx| {
15391 let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
15392 editor
15393 .buffer
15394 .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
15395 })
15396 .unwrap();
15397
15398 let mut cx = EditorTestContext::for_editor(editor, cx).await;
15399 cx.run_until_parked();
15400
15401 cx.update_editor(|editor, window, cx| {
15402 editor.expand_all_diff_hunks(&Default::default(), window, cx)
15403 });
15404 cx.executor().run_until_parked();
15405
15406 // When the start of a hunk coincides with the start of its excerpt,
15407 // the hunk is expanded. When the start of a a hunk is earlier than
15408 // the start of its excerpt, the hunk is not expanded.
15409 cx.assert_state_with_diff(
15410 "
15411 ˇaaa
15412 - bbb
15413 + BBB
15414
15415 - ddd
15416 - eee
15417 + DDD
15418 + EEE
15419 fff
15420
15421 iii
15422 "
15423 .unindent(),
15424 );
15425}
15426
15427#[gpui::test]
15428async fn test_edits_around_expanded_insertion_hunks(
15429 executor: BackgroundExecutor,
15430 cx: &mut TestAppContext,
15431) {
15432 init_test(cx, |_| {});
15433
15434 let mut cx = EditorTestContext::new(cx).await;
15435
15436 let diff_base = r#"
15437 use some::mod1;
15438 use some::mod2;
15439
15440 const A: u32 = 42;
15441
15442 fn main() {
15443 println!("hello");
15444
15445 println!("world");
15446 }
15447 "#
15448 .unindent();
15449 executor.run_until_parked();
15450 cx.set_state(
15451 &r#"
15452 use some::mod1;
15453 use some::mod2;
15454
15455 const A: u32 = 42;
15456 const B: u32 = 42;
15457 const C: u32 = 42;
15458 ˇ
15459
15460 fn main() {
15461 println!("hello");
15462
15463 println!("world");
15464 }
15465 "#
15466 .unindent(),
15467 );
15468
15469 cx.set_head_text(&diff_base);
15470 executor.run_until_parked();
15471
15472 cx.update_editor(|editor, window, cx| {
15473 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15474 });
15475 executor.run_until_parked();
15476
15477 cx.assert_state_with_diff(
15478 r#"
15479 use some::mod1;
15480 use some::mod2;
15481
15482 const A: u32 = 42;
15483 + const B: u32 = 42;
15484 + const C: u32 = 42;
15485 + ˇ
15486
15487 fn main() {
15488 println!("hello");
15489
15490 println!("world");
15491 }
15492 "#
15493 .unindent(),
15494 );
15495
15496 cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
15497 executor.run_until_parked();
15498
15499 cx.assert_state_with_diff(
15500 r#"
15501 use some::mod1;
15502 use some::mod2;
15503
15504 const A: u32 = 42;
15505 + const B: u32 = 42;
15506 + const C: u32 = 42;
15507 + const D: u32 = 42;
15508 + ˇ
15509
15510 fn main() {
15511 println!("hello");
15512
15513 println!("world");
15514 }
15515 "#
15516 .unindent(),
15517 );
15518
15519 cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
15520 executor.run_until_parked();
15521
15522 cx.assert_state_with_diff(
15523 r#"
15524 use some::mod1;
15525 use some::mod2;
15526
15527 const A: u32 = 42;
15528 + const B: u32 = 42;
15529 + const C: u32 = 42;
15530 + const D: u32 = 42;
15531 + const E: u32 = 42;
15532 + ˇ
15533
15534 fn main() {
15535 println!("hello");
15536
15537 println!("world");
15538 }
15539 "#
15540 .unindent(),
15541 );
15542
15543 cx.update_editor(|editor, window, cx| {
15544 editor.delete_line(&DeleteLine, window, cx);
15545 });
15546 executor.run_until_parked();
15547
15548 cx.assert_state_with_diff(
15549 r#"
15550 use some::mod1;
15551 use some::mod2;
15552
15553 const A: u32 = 42;
15554 + const B: u32 = 42;
15555 + const C: u32 = 42;
15556 + const D: u32 = 42;
15557 + const E: u32 = 42;
15558 ˇ
15559 fn main() {
15560 println!("hello");
15561
15562 println!("world");
15563 }
15564 "#
15565 .unindent(),
15566 );
15567
15568 cx.update_editor(|editor, window, cx| {
15569 editor.move_up(&MoveUp, window, cx);
15570 editor.delete_line(&DeleteLine, window, cx);
15571 editor.move_up(&MoveUp, window, cx);
15572 editor.delete_line(&DeleteLine, window, cx);
15573 editor.move_up(&MoveUp, window, cx);
15574 editor.delete_line(&DeleteLine, window, cx);
15575 });
15576 executor.run_until_parked();
15577 cx.assert_state_with_diff(
15578 r#"
15579 use some::mod1;
15580 use some::mod2;
15581
15582 const A: u32 = 42;
15583 + const B: u32 = 42;
15584 ˇ
15585 fn main() {
15586 println!("hello");
15587
15588 println!("world");
15589 }
15590 "#
15591 .unindent(),
15592 );
15593
15594 cx.update_editor(|editor, window, cx| {
15595 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
15596 editor.delete_line(&DeleteLine, window, cx);
15597 });
15598 executor.run_until_parked();
15599 cx.assert_state_with_diff(
15600 r#"
15601 ˇ
15602 fn main() {
15603 println!("hello");
15604
15605 println!("world");
15606 }
15607 "#
15608 .unindent(),
15609 );
15610}
15611
15612#[gpui::test]
15613async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
15614 init_test(cx, |_| {});
15615
15616 let mut cx = EditorTestContext::new(cx).await;
15617 cx.set_head_text(indoc! { "
15618 one
15619 two
15620 three
15621 four
15622 five
15623 "
15624 });
15625 cx.set_state(indoc! { "
15626 one
15627 ˇthree
15628 five
15629 "});
15630 cx.run_until_parked();
15631 cx.update_editor(|editor, window, cx| {
15632 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
15633 });
15634 cx.assert_state_with_diff(
15635 indoc! { "
15636 one
15637 - two
15638 ˇthree
15639 - four
15640 five
15641 "}
15642 .to_string(),
15643 );
15644 cx.update_editor(|editor, window, cx| {
15645 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
15646 });
15647
15648 cx.assert_state_with_diff(
15649 indoc! { "
15650 one
15651 ˇthree
15652 five
15653 "}
15654 .to_string(),
15655 );
15656
15657 cx.set_state(indoc! { "
15658 one
15659 ˇTWO
15660 three
15661 four
15662 five
15663 "});
15664 cx.run_until_parked();
15665 cx.update_editor(|editor, window, cx| {
15666 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
15667 });
15668
15669 cx.assert_state_with_diff(
15670 indoc! { "
15671 one
15672 - two
15673 + ˇTWO
15674 three
15675 four
15676 five
15677 "}
15678 .to_string(),
15679 );
15680 cx.update_editor(|editor, window, cx| {
15681 editor.move_up(&Default::default(), window, cx);
15682 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
15683 });
15684 cx.assert_state_with_diff(
15685 indoc! { "
15686 one
15687 ˇTWO
15688 three
15689 four
15690 five
15691 "}
15692 .to_string(),
15693 );
15694}
15695
15696#[gpui::test]
15697async fn test_edits_around_expanded_deletion_hunks(
15698 executor: BackgroundExecutor,
15699 cx: &mut TestAppContext,
15700) {
15701 init_test(cx, |_| {});
15702
15703 let mut cx = EditorTestContext::new(cx).await;
15704
15705 let diff_base = r#"
15706 use some::mod1;
15707 use some::mod2;
15708
15709 const A: u32 = 42;
15710 const B: u32 = 42;
15711 const C: u32 = 42;
15712
15713
15714 fn main() {
15715 println!("hello");
15716
15717 println!("world");
15718 }
15719 "#
15720 .unindent();
15721 executor.run_until_parked();
15722 cx.set_state(
15723 &r#"
15724 use some::mod1;
15725 use some::mod2;
15726
15727 ˇconst B: u32 = 42;
15728 const C: u32 = 42;
15729
15730
15731 fn main() {
15732 println!("hello");
15733
15734 println!("world");
15735 }
15736 "#
15737 .unindent(),
15738 );
15739
15740 cx.set_head_text(&diff_base);
15741 executor.run_until_parked();
15742
15743 cx.update_editor(|editor, window, cx| {
15744 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15745 });
15746 executor.run_until_parked();
15747
15748 cx.assert_state_with_diff(
15749 r#"
15750 use some::mod1;
15751 use some::mod2;
15752
15753 - const A: u32 = 42;
15754 ˇconst B: u32 = 42;
15755 const C: u32 = 42;
15756
15757
15758 fn main() {
15759 println!("hello");
15760
15761 println!("world");
15762 }
15763 "#
15764 .unindent(),
15765 );
15766
15767 cx.update_editor(|editor, window, cx| {
15768 editor.delete_line(&DeleteLine, window, cx);
15769 });
15770 executor.run_until_parked();
15771 cx.assert_state_with_diff(
15772 r#"
15773 use some::mod1;
15774 use some::mod2;
15775
15776 - const A: u32 = 42;
15777 - const B: u32 = 42;
15778 ˇconst C: u32 = 42;
15779
15780
15781 fn main() {
15782 println!("hello");
15783
15784 println!("world");
15785 }
15786 "#
15787 .unindent(),
15788 );
15789
15790 cx.update_editor(|editor, window, cx| {
15791 editor.delete_line(&DeleteLine, window, cx);
15792 });
15793 executor.run_until_parked();
15794 cx.assert_state_with_diff(
15795 r#"
15796 use some::mod1;
15797 use some::mod2;
15798
15799 - const A: u32 = 42;
15800 - const B: u32 = 42;
15801 - const C: u32 = 42;
15802 ˇ
15803
15804 fn main() {
15805 println!("hello");
15806
15807 println!("world");
15808 }
15809 "#
15810 .unindent(),
15811 );
15812
15813 cx.update_editor(|editor, window, cx| {
15814 editor.handle_input("replacement", window, cx);
15815 });
15816 executor.run_until_parked();
15817 cx.assert_state_with_diff(
15818 r#"
15819 use some::mod1;
15820 use some::mod2;
15821
15822 - const A: u32 = 42;
15823 - const B: u32 = 42;
15824 - const C: u32 = 42;
15825 -
15826 + replacementˇ
15827
15828 fn main() {
15829 println!("hello");
15830
15831 println!("world");
15832 }
15833 "#
15834 .unindent(),
15835 );
15836}
15837
15838#[gpui::test]
15839async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15840 init_test(cx, |_| {});
15841
15842 let mut cx = EditorTestContext::new(cx).await;
15843
15844 let base_text = r#"
15845 one
15846 two
15847 three
15848 four
15849 five
15850 "#
15851 .unindent();
15852 executor.run_until_parked();
15853 cx.set_state(
15854 &r#"
15855 one
15856 two
15857 fˇour
15858 five
15859 "#
15860 .unindent(),
15861 );
15862
15863 cx.set_head_text(&base_text);
15864 executor.run_until_parked();
15865
15866 cx.update_editor(|editor, window, cx| {
15867 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15868 });
15869 executor.run_until_parked();
15870
15871 cx.assert_state_with_diff(
15872 r#"
15873 one
15874 two
15875 - three
15876 fˇour
15877 five
15878 "#
15879 .unindent(),
15880 );
15881
15882 cx.update_editor(|editor, window, cx| {
15883 editor.backspace(&Backspace, window, cx);
15884 editor.backspace(&Backspace, window, cx);
15885 });
15886 executor.run_until_parked();
15887 cx.assert_state_with_diff(
15888 r#"
15889 one
15890 two
15891 - threeˇ
15892 - four
15893 + our
15894 five
15895 "#
15896 .unindent(),
15897 );
15898}
15899
15900#[gpui::test]
15901async fn test_edit_after_expanded_modification_hunk(
15902 executor: BackgroundExecutor,
15903 cx: &mut TestAppContext,
15904) {
15905 init_test(cx, |_| {});
15906
15907 let mut cx = EditorTestContext::new(cx).await;
15908
15909 let diff_base = r#"
15910 use some::mod1;
15911 use some::mod2;
15912
15913 const A: u32 = 42;
15914 const B: u32 = 42;
15915 const C: u32 = 42;
15916 const D: u32 = 42;
15917
15918
15919 fn main() {
15920 println!("hello");
15921
15922 println!("world");
15923 }"#
15924 .unindent();
15925
15926 cx.set_state(
15927 &r#"
15928 use some::mod1;
15929 use some::mod2;
15930
15931 const A: u32 = 42;
15932 const B: u32 = 42;
15933 const C: u32 = 43ˇ
15934 const D: u32 = 42;
15935
15936
15937 fn main() {
15938 println!("hello");
15939
15940 println!("world");
15941 }"#
15942 .unindent(),
15943 );
15944
15945 cx.set_head_text(&diff_base);
15946 executor.run_until_parked();
15947 cx.update_editor(|editor, window, cx| {
15948 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15949 });
15950 executor.run_until_parked();
15951
15952 cx.assert_state_with_diff(
15953 r#"
15954 use some::mod1;
15955 use some::mod2;
15956
15957 const A: u32 = 42;
15958 const B: u32 = 42;
15959 - const C: u32 = 42;
15960 + const C: u32 = 43ˇ
15961 const D: u32 = 42;
15962
15963
15964 fn main() {
15965 println!("hello");
15966
15967 println!("world");
15968 }"#
15969 .unindent(),
15970 );
15971
15972 cx.update_editor(|editor, window, cx| {
15973 editor.handle_input("\nnew_line\n", window, cx);
15974 });
15975 executor.run_until_parked();
15976
15977 cx.assert_state_with_diff(
15978 r#"
15979 use some::mod1;
15980 use some::mod2;
15981
15982 const A: u32 = 42;
15983 const B: u32 = 42;
15984 - const C: u32 = 42;
15985 + const C: u32 = 43
15986 + new_line
15987 + ˇ
15988 const D: u32 = 42;
15989
15990
15991 fn main() {
15992 println!("hello");
15993
15994 println!("world");
15995 }"#
15996 .unindent(),
15997 );
15998}
15999
16000#[gpui::test]
16001async fn test_stage_and_unstage_added_file_hunk(
16002 executor: BackgroundExecutor,
16003 cx: &mut TestAppContext,
16004) {
16005 init_test(cx, |_| {});
16006
16007 let mut cx = EditorTestContext::new(cx).await;
16008 cx.update_editor(|editor, _, cx| {
16009 editor.set_expand_all_diff_hunks(cx);
16010 });
16011
16012 let working_copy = r#"
16013 ˇfn main() {
16014 println!("hello, world!");
16015 }
16016 "#
16017 .unindent();
16018
16019 cx.set_state(&working_copy);
16020 executor.run_until_parked();
16021
16022 cx.assert_state_with_diff(
16023 r#"
16024 + ˇfn main() {
16025 + println!("hello, world!");
16026 + }
16027 "#
16028 .unindent(),
16029 );
16030 cx.assert_index_text(None);
16031
16032 cx.update_editor(|editor, window, cx| {
16033 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
16034 });
16035 executor.run_until_parked();
16036 cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
16037 cx.assert_state_with_diff(
16038 r#"
16039 + ˇfn main() {
16040 + println!("hello, world!");
16041 + }
16042 "#
16043 .unindent(),
16044 );
16045
16046 cx.update_editor(|editor, window, cx| {
16047 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
16048 });
16049 executor.run_until_parked();
16050 cx.assert_index_text(None);
16051}
16052
16053async fn setup_indent_guides_editor(
16054 text: &str,
16055 cx: &mut TestAppContext,
16056) -> (BufferId, EditorTestContext) {
16057 init_test(cx, |_| {});
16058
16059 let mut cx = EditorTestContext::new(cx).await;
16060
16061 let buffer_id = cx.update_editor(|editor, window, cx| {
16062 editor.set_text(text, window, cx);
16063 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
16064
16065 buffer_ids[0]
16066 });
16067
16068 (buffer_id, cx)
16069}
16070
16071fn assert_indent_guides(
16072 range: Range<u32>,
16073 expected: Vec<IndentGuide>,
16074 active_indices: Option<Vec<usize>>,
16075 cx: &mut EditorTestContext,
16076) {
16077 let indent_guides = cx.update_editor(|editor, window, cx| {
16078 let snapshot = editor.snapshot(window, cx).display_snapshot;
16079 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
16080 editor,
16081 MultiBufferRow(range.start)..MultiBufferRow(range.end),
16082 true,
16083 &snapshot,
16084 cx,
16085 );
16086
16087 indent_guides.sort_by(|a, b| {
16088 a.depth.cmp(&b.depth).then(
16089 a.start_row
16090 .cmp(&b.start_row)
16091 .then(a.end_row.cmp(&b.end_row)),
16092 )
16093 });
16094 indent_guides
16095 });
16096
16097 if let Some(expected) = active_indices {
16098 let active_indices = cx.update_editor(|editor, window, cx| {
16099 let snapshot = editor.snapshot(window, cx).display_snapshot;
16100 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
16101 });
16102
16103 assert_eq!(
16104 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
16105 expected,
16106 "Active indent guide indices do not match"
16107 );
16108 }
16109
16110 assert_eq!(indent_guides, expected, "Indent guides do not match");
16111}
16112
16113fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
16114 IndentGuide {
16115 buffer_id,
16116 start_row: MultiBufferRow(start_row),
16117 end_row: MultiBufferRow(end_row),
16118 depth,
16119 tab_size: 4,
16120 settings: IndentGuideSettings {
16121 enabled: true,
16122 line_width: 1,
16123 active_line_width: 1,
16124 ..Default::default()
16125 },
16126 }
16127}
16128
16129#[gpui::test]
16130async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
16131 let (buffer_id, mut cx) = setup_indent_guides_editor(
16132 &"
16133 fn main() {
16134 let a = 1;
16135 }"
16136 .unindent(),
16137 cx,
16138 )
16139 .await;
16140
16141 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
16142}
16143
16144#[gpui::test]
16145async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
16146 let (buffer_id, mut cx) = setup_indent_guides_editor(
16147 &"
16148 fn main() {
16149 let a = 1;
16150 let b = 2;
16151 }"
16152 .unindent(),
16153 cx,
16154 )
16155 .await;
16156
16157 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
16158}
16159
16160#[gpui::test]
16161async fn test_indent_guide_nested(cx: &mut TestAppContext) {
16162 let (buffer_id, mut cx) = setup_indent_guides_editor(
16163 &"
16164 fn main() {
16165 let a = 1;
16166 if a == 3 {
16167 let b = 2;
16168 } else {
16169 let c = 3;
16170 }
16171 }"
16172 .unindent(),
16173 cx,
16174 )
16175 .await;
16176
16177 assert_indent_guides(
16178 0..8,
16179 vec![
16180 indent_guide(buffer_id, 1, 6, 0),
16181 indent_guide(buffer_id, 3, 3, 1),
16182 indent_guide(buffer_id, 5, 5, 1),
16183 ],
16184 None,
16185 &mut cx,
16186 );
16187}
16188
16189#[gpui::test]
16190async fn test_indent_guide_tab(cx: &mut TestAppContext) {
16191 let (buffer_id, mut cx) = setup_indent_guides_editor(
16192 &"
16193 fn main() {
16194 let a = 1;
16195 let b = 2;
16196 let c = 3;
16197 }"
16198 .unindent(),
16199 cx,
16200 )
16201 .await;
16202
16203 assert_indent_guides(
16204 0..5,
16205 vec![
16206 indent_guide(buffer_id, 1, 3, 0),
16207 indent_guide(buffer_id, 2, 2, 1),
16208 ],
16209 None,
16210 &mut cx,
16211 );
16212}
16213
16214#[gpui::test]
16215async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
16216 let (buffer_id, mut cx) = setup_indent_guides_editor(
16217 &"
16218 fn main() {
16219 let a = 1;
16220
16221 let c = 3;
16222 }"
16223 .unindent(),
16224 cx,
16225 )
16226 .await;
16227
16228 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
16229}
16230
16231#[gpui::test]
16232async fn test_indent_guide_complex(cx: &mut TestAppContext) {
16233 let (buffer_id, mut cx) = setup_indent_guides_editor(
16234 &"
16235 fn main() {
16236 let a = 1;
16237
16238 let c = 3;
16239
16240 if a == 3 {
16241 let b = 2;
16242 } else {
16243 let c = 3;
16244 }
16245 }"
16246 .unindent(),
16247 cx,
16248 )
16249 .await;
16250
16251 assert_indent_guides(
16252 0..11,
16253 vec![
16254 indent_guide(buffer_id, 1, 9, 0),
16255 indent_guide(buffer_id, 6, 6, 1),
16256 indent_guide(buffer_id, 8, 8, 1),
16257 ],
16258 None,
16259 &mut cx,
16260 );
16261}
16262
16263#[gpui::test]
16264async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
16265 let (buffer_id, mut cx) = setup_indent_guides_editor(
16266 &"
16267 fn main() {
16268 let a = 1;
16269
16270 let c = 3;
16271
16272 if a == 3 {
16273 let b = 2;
16274 } else {
16275 let c = 3;
16276 }
16277 }"
16278 .unindent(),
16279 cx,
16280 )
16281 .await;
16282
16283 assert_indent_guides(
16284 1..11,
16285 vec![
16286 indent_guide(buffer_id, 1, 9, 0),
16287 indent_guide(buffer_id, 6, 6, 1),
16288 indent_guide(buffer_id, 8, 8, 1),
16289 ],
16290 None,
16291 &mut cx,
16292 );
16293}
16294
16295#[gpui::test]
16296async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
16297 let (buffer_id, mut cx) = setup_indent_guides_editor(
16298 &"
16299 fn main() {
16300 let a = 1;
16301
16302 let c = 3;
16303
16304 if a == 3 {
16305 let b = 2;
16306 } else {
16307 let c = 3;
16308 }
16309 }"
16310 .unindent(),
16311 cx,
16312 )
16313 .await;
16314
16315 assert_indent_guides(
16316 1..10,
16317 vec![
16318 indent_guide(buffer_id, 1, 9, 0),
16319 indent_guide(buffer_id, 6, 6, 1),
16320 indent_guide(buffer_id, 8, 8, 1),
16321 ],
16322 None,
16323 &mut cx,
16324 );
16325}
16326
16327#[gpui::test]
16328async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
16329 let (buffer_id, mut cx) = setup_indent_guides_editor(
16330 &"
16331 block1
16332 block2
16333 block3
16334 block4
16335 block2
16336 block1
16337 block1"
16338 .unindent(),
16339 cx,
16340 )
16341 .await;
16342
16343 assert_indent_guides(
16344 1..10,
16345 vec![
16346 indent_guide(buffer_id, 1, 4, 0),
16347 indent_guide(buffer_id, 2, 3, 1),
16348 indent_guide(buffer_id, 3, 3, 2),
16349 ],
16350 None,
16351 &mut cx,
16352 );
16353}
16354
16355#[gpui::test]
16356async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
16357 let (buffer_id, mut cx) = setup_indent_guides_editor(
16358 &"
16359 block1
16360 block2
16361 block3
16362
16363 block1
16364 block1"
16365 .unindent(),
16366 cx,
16367 )
16368 .await;
16369
16370 assert_indent_guides(
16371 0..6,
16372 vec![
16373 indent_guide(buffer_id, 1, 2, 0),
16374 indent_guide(buffer_id, 2, 2, 1),
16375 ],
16376 None,
16377 &mut cx,
16378 );
16379}
16380
16381#[gpui::test]
16382async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
16383 let (buffer_id, mut cx) = setup_indent_guides_editor(
16384 &"
16385 block1
16386
16387
16388
16389 block2
16390 "
16391 .unindent(),
16392 cx,
16393 )
16394 .await;
16395
16396 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
16397}
16398
16399#[gpui::test]
16400async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
16401 let (buffer_id, mut cx) = setup_indent_guides_editor(
16402 &"
16403 def a:
16404 \tb = 3
16405 \tif True:
16406 \t\tc = 4
16407 \t\td = 5
16408 \tprint(b)
16409 "
16410 .unindent(),
16411 cx,
16412 )
16413 .await;
16414
16415 assert_indent_guides(
16416 0..6,
16417 vec![
16418 indent_guide(buffer_id, 1, 6, 0),
16419 indent_guide(buffer_id, 3, 4, 1),
16420 ],
16421 None,
16422 &mut cx,
16423 );
16424}
16425
16426#[gpui::test]
16427async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
16428 let (buffer_id, mut cx) = setup_indent_guides_editor(
16429 &"
16430 fn main() {
16431 let a = 1;
16432 }"
16433 .unindent(),
16434 cx,
16435 )
16436 .await;
16437
16438 cx.update_editor(|editor, window, cx| {
16439 editor.change_selections(None, window, cx, |s| {
16440 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
16441 });
16442 });
16443
16444 assert_indent_guides(
16445 0..3,
16446 vec![indent_guide(buffer_id, 1, 1, 0)],
16447 Some(vec![0]),
16448 &mut cx,
16449 );
16450}
16451
16452#[gpui::test]
16453async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
16454 let (buffer_id, mut cx) = setup_indent_guides_editor(
16455 &"
16456 fn main() {
16457 if 1 == 2 {
16458 let a = 1;
16459 }
16460 }"
16461 .unindent(),
16462 cx,
16463 )
16464 .await;
16465
16466 cx.update_editor(|editor, window, cx| {
16467 editor.change_selections(None, window, cx, |s| {
16468 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
16469 });
16470 });
16471
16472 assert_indent_guides(
16473 0..4,
16474 vec![
16475 indent_guide(buffer_id, 1, 3, 0),
16476 indent_guide(buffer_id, 2, 2, 1),
16477 ],
16478 Some(vec![1]),
16479 &mut cx,
16480 );
16481
16482 cx.update_editor(|editor, window, cx| {
16483 editor.change_selections(None, window, cx, |s| {
16484 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
16485 });
16486 });
16487
16488 assert_indent_guides(
16489 0..4,
16490 vec![
16491 indent_guide(buffer_id, 1, 3, 0),
16492 indent_guide(buffer_id, 2, 2, 1),
16493 ],
16494 Some(vec![1]),
16495 &mut cx,
16496 );
16497
16498 cx.update_editor(|editor, window, cx| {
16499 editor.change_selections(None, window, cx, |s| {
16500 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
16501 });
16502 });
16503
16504 assert_indent_guides(
16505 0..4,
16506 vec![
16507 indent_guide(buffer_id, 1, 3, 0),
16508 indent_guide(buffer_id, 2, 2, 1),
16509 ],
16510 Some(vec![0]),
16511 &mut cx,
16512 );
16513}
16514
16515#[gpui::test]
16516async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
16517 let (buffer_id, mut cx) = setup_indent_guides_editor(
16518 &"
16519 fn main() {
16520 let a = 1;
16521
16522 let b = 2;
16523 }"
16524 .unindent(),
16525 cx,
16526 )
16527 .await;
16528
16529 cx.update_editor(|editor, window, cx| {
16530 editor.change_selections(None, window, cx, |s| {
16531 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
16532 });
16533 });
16534
16535 assert_indent_guides(
16536 0..5,
16537 vec![indent_guide(buffer_id, 1, 3, 0)],
16538 Some(vec![0]),
16539 &mut cx,
16540 );
16541}
16542
16543#[gpui::test]
16544async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
16545 let (buffer_id, mut cx) = setup_indent_guides_editor(
16546 &"
16547 def m:
16548 a = 1
16549 pass"
16550 .unindent(),
16551 cx,
16552 )
16553 .await;
16554
16555 cx.update_editor(|editor, window, cx| {
16556 editor.change_selections(None, window, cx, |s| {
16557 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
16558 });
16559 });
16560
16561 assert_indent_guides(
16562 0..3,
16563 vec![indent_guide(buffer_id, 1, 2, 0)],
16564 Some(vec![0]),
16565 &mut cx,
16566 );
16567}
16568
16569#[gpui::test]
16570async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
16571 init_test(cx, |_| {});
16572 let mut cx = EditorTestContext::new(cx).await;
16573 let text = indoc! {
16574 "
16575 impl A {
16576 fn b() {
16577 0;
16578 3;
16579 5;
16580 6;
16581 7;
16582 }
16583 }
16584 "
16585 };
16586 let base_text = indoc! {
16587 "
16588 impl A {
16589 fn b() {
16590 0;
16591 1;
16592 2;
16593 3;
16594 4;
16595 }
16596 fn c() {
16597 5;
16598 6;
16599 7;
16600 }
16601 }
16602 "
16603 };
16604
16605 cx.update_editor(|editor, window, cx| {
16606 editor.set_text(text, window, cx);
16607
16608 editor.buffer().update(cx, |multibuffer, cx| {
16609 let buffer = multibuffer.as_singleton().unwrap();
16610 let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
16611
16612 multibuffer.set_all_diff_hunks_expanded(cx);
16613 multibuffer.add_diff(diff, cx);
16614
16615 buffer.read(cx).remote_id()
16616 })
16617 });
16618 cx.run_until_parked();
16619
16620 cx.assert_state_with_diff(
16621 indoc! { "
16622 impl A {
16623 fn b() {
16624 0;
16625 - 1;
16626 - 2;
16627 3;
16628 - 4;
16629 - }
16630 - fn c() {
16631 5;
16632 6;
16633 7;
16634 }
16635 }
16636 ˇ"
16637 }
16638 .to_string(),
16639 );
16640
16641 let mut actual_guides = cx.update_editor(|editor, window, cx| {
16642 editor
16643 .snapshot(window, cx)
16644 .buffer_snapshot
16645 .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
16646 .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
16647 .collect::<Vec<_>>()
16648 });
16649 actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
16650 assert_eq!(
16651 actual_guides,
16652 vec![
16653 (MultiBufferRow(1)..=MultiBufferRow(12), 0),
16654 (MultiBufferRow(2)..=MultiBufferRow(6), 1),
16655 (MultiBufferRow(9)..=MultiBufferRow(11), 1),
16656 ]
16657 );
16658}
16659
16660#[gpui::test]
16661async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16662 init_test(cx, |_| {});
16663 let mut cx = EditorTestContext::new(cx).await;
16664
16665 let diff_base = r#"
16666 a
16667 b
16668 c
16669 "#
16670 .unindent();
16671
16672 cx.set_state(
16673 &r#"
16674 ˇA
16675 b
16676 C
16677 "#
16678 .unindent(),
16679 );
16680 cx.set_head_text(&diff_base);
16681 cx.update_editor(|editor, window, cx| {
16682 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16683 });
16684 executor.run_until_parked();
16685
16686 let both_hunks_expanded = r#"
16687 - a
16688 + ˇA
16689 b
16690 - c
16691 + C
16692 "#
16693 .unindent();
16694
16695 cx.assert_state_with_diff(both_hunks_expanded.clone());
16696
16697 let hunk_ranges = cx.update_editor(|editor, window, cx| {
16698 let snapshot = editor.snapshot(window, cx);
16699 let hunks = editor
16700 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
16701 .collect::<Vec<_>>();
16702 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
16703 let buffer_id = hunks[0].buffer_id;
16704 hunks
16705 .into_iter()
16706 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
16707 .collect::<Vec<_>>()
16708 });
16709 assert_eq!(hunk_ranges.len(), 2);
16710
16711 cx.update_editor(|editor, _, cx| {
16712 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
16713 });
16714 executor.run_until_parked();
16715
16716 let second_hunk_expanded = r#"
16717 ˇA
16718 b
16719 - c
16720 + C
16721 "#
16722 .unindent();
16723
16724 cx.assert_state_with_diff(second_hunk_expanded);
16725
16726 cx.update_editor(|editor, _, cx| {
16727 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
16728 });
16729 executor.run_until_parked();
16730
16731 cx.assert_state_with_diff(both_hunks_expanded.clone());
16732
16733 cx.update_editor(|editor, _, cx| {
16734 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
16735 });
16736 executor.run_until_parked();
16737
16738 let first_hunk_expanded = r#"
16739 - a
16740 + ˇA
16741 b
16742 C
16743 "#
16744 .unindent();
16745
16746 cx.assert_state_with_diff(first_hunk_expanded);
16747
16748 cx.update_editor(|editor, _, cx| {
16749 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
16750 });
16751 executor.run_until_parked();
16752
16753 cx.assert_state_with_diff(both_hunks_expanded);
16754
16755 cx.set_state(
16756 &r#"
16757 ˇA
16758 b
16759 "#
16760 .unindent(),
16761 );
16762 cx.run_until_parked();
16763
16764 // TODO this cursor position seems bad
16765 cx.assert_state_with_diff(
16766 r#"
16767 - ˇa
16768 + A
16769 b
16770 "#
16771 .unindent(),
16772 );
16773
16774 cx.update_editor(|editor, window, cx| {
16775 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16776 });
16777
16778 cx.assert_state_with_diff(
16779 r#"
16780 - ˇa
16781 + A
16782 b
16783 - c
16784 "#
16785 .unindent(),
16786 );
16787
16788 let hunk_ranges = cx.update_editor(|editor, window, cx| {
16789 let snapshot = editor.snapshot(window, cx);
16790 let hunks = editor
16791 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
16792 .collect::<Vec<_>>();
16793 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
16794 let buffer_id = hunks[0].buffer_id;
16795 hunks
16796 .into_iter()
16797 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
16798 .collect::<Vec<_>>()
16799 });
16800 assert_eq!(hunk_ranges.len(), 2);
16801
16802 cx.update_editor(|editor, _, cx| {
16803 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
16804 });
16805 executor.run_until_parked();
16806
16807 cx.assert_state_with_diff(
16808 r#"
16809 - ˇa
16810 + A
16811 b
16812 "#
16813 .unindent(),
16814 );
16815}
16816
16817#[gpui::test]
16818async fn test_toggle_deletion_hunk_at_start_of_file(
16819 executor: BackgroundExecutor,
16820 cx: &mut TestAppContext,
16821) {
16822 init_test(cx, |_| {});
16823 let mut cx = EditorTestContext::new(cx).await;
16824
16825 let diff_base = r#"
16826 a
16827 b
16828 c
16829 "#
16830 .unindent();
16831
16832 cx.set_state(
16833 &r#"
16834 ˇb
16835 c
16836 "#
16837 .unindent(),
16838 );
16839 cx.set_head_text(&diff_base);
16840 cx.update_editor(|editor, window, cx| {
16841 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16842 });
16843 executor.run_until_parked();
16844
16845 let hunk_expanded = r#"
16846 - a
16847 ˇb
16848 c
16849 "#
16850 .unindent();
16851
16852 cx.assert_state_with_diff(hunk_expanded.clone());
16853
16854 let hunk_ranges = cx.update_editor(|editor, window, cx| {
16855 let snapshot = editor.snapshot(window, cx);
16856 let hunks = editor
16857 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
16858 .collect::<Vec<_>>();
16859 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
16860 let buffer_id = hunks[0].buffer_id;
16861 hunks
16862 .into_iter()
16863 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
16864 .collect::<Vec<_>>()
16865 });
16866 assert_eq!(hunk_ranges.len(), 1);
16867
16868 cx.update_editor(|editor, _, cx| {
16869 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
16870 });
16871 executor.run_until_parked();
16872
16873 let hunk_collapsed = r#"
16874 ˇb
16875 c
16876 "#
16877 .unindent();
16878
16879 cx.assert_state_with_diff(hunk_collapsed);
16880
16881 cx.update_editor(|editor, _, cx| {
16882 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
16883 });
16884 executor.run_until_parked();
16885
16886 cx.assert_state_with_diff(hunk_expanded.clone());
16887}
16888
16889#[gpui::test]
16890async fn test_display_diff_hunks(cx: &mut TestAppContext) {
16891 init_test(cx, |_| {});
16892
16893 let fs = FakeFs::new(cx.executor());
16894 fs.insert_tree(
16895 path!("/test"),
16896 json!({
16897 ".git": {},
16898 "file-1": "ONE\n",
16899 "file-2": "TWO\n",
16900 "file-3": "THREE\n",
16901 }),
16902 )
16903 .await;
16904
16905 fs.set_head_for_repo(
16906 path!("/test/.git").as_ref(),
16907 &[
16908 ("file-1".into(), "one\n".into()),
16909 ("file-2".into(), "two\n".into()),
16910 ("file-3".into(), "three\n".into()),
16911 ],
16912 );
16913
16914 let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
16915 let mut buffers = vec![];
16916 for i in 1..=3 {
16917 let buffer = project
16918 .update(cx, |project, cx| {
16919 let path = format!(path!("/test/file-{}"), i);
16920 project.open_local_buffer(path, cx)
16921 })
16922 .await
16923 .unwrap();
16924 buffers.push(buffer);
16925 }
16926
16927 let multibuffer = cx.new(|cx| {
16928 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
16929 multibuffer.set_all_diff_hunks_expanded(cx);
16930 for buffer in &buffers {
16931 let snapshot = buffer.read(cx).snapshot();
16932 multibuffer.set_excerpts_for_path(
16933 PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
16934 buffer.clone(),
16935 vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
16936 DEFAULT_MULTIBUFFER_CONTEXT,
16937 cx,
16938 );
16939 }
16940 multibuffer
16941 });
16942
16943 let editor = cx.add_window(|window, cx| {
16944 Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
16945 });
16946 cx.run_until_parked();
16947
16948 let snapshot = editor
16949 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
16950 .unwrap();
16951 let hunks = snapshot
16952 .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
16953 .map(|hunk| match hunk {
16954 DisplayDiffHunk::Unfolded {
16955 display_row_range, ..
16956 } => display_row_range,
16957 DisplayDiffHunk::Folded { .. } => unreachable!(),
16958 })
16959 .collect::<Vec<_>>();
16960 assert_eq!(
16961 hunks,
16962 [
16963 DisplayRow(2)..DisplayRow(4),
16964 DisplayRow(7)..DisplayRow(9),
16965 DisplayRow(12)..DisplayRow(14),
16966 ]
16967 );
16968}
16969
16970#[gpui::test]
16971async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
16972 init_test(cx, |_| {});
16973
16974 let mut cx = EditorTestContext::new(cx).await;
16975 cx.set_head_text(indoc! { "
16976 one
16977 two
16978 three
16979 four
16980 five
16981 "
16982 });
16983 cx.set_index_text(indoc! { "
16984 one
16985 two
16986 three
16987 four
16988 five
16989 "
16990 });
16991 cx.set_state(indoc! {"
16992 one
16993 TWO
16994 ˇTHREE
16995 FOUR
16996 five
16997 "});
16998 cx.run_until_parked();
16999 cx.update_editor(|editor, window, cx| {
17000 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
17001 });
17002 cx.run_until_parked();
17003 cx.assert_index_text(Some(indoc! {"
17004 one
17005 TWO
17006 THREE
17007 FOUR
17008 five
17009 "}));
17010 cx.set_state(indoc! { "
17011 one
17012 TWO
17013 ˇTHREE-HUNDRED
17014 FOUR
17015 five
17016 "});
17017 cx.run_until_parked();
17018 cx.update_editor(|editor, window, cx| {
17019 let snapshot = editor.snapshot(window, cx);
17020 let hunks = editor
17021 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
17022 .collect::<Vec<_>>();
17023 assert_eq!(hunks.len(), 1);
17024 assert_eq!(
17025 hunks[0].status(),
17026 DiffHunkStatus {
17027 kind: DiffHunkStatusKind::Modified,
17028 secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
17029 }
17030 );
17031
17032 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
17033 });
17034 cx.run_until_parked();
17035 cx.assert_index_text(Some(indoc! {"
17036 one
17037 TWO
17038 THREE-HUNDRED
17039 FOUR
17040 five
17041 "}));
17042}
17043
17044#[gpui::test]
17045fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
17046 init_test(cx, |_| {});
17047
17048 let editor = cx.add_window(|window, cx| {
17049 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
17050 build_editor(buffer, window, cx)
17051 });
17052
17053 let render_args = Arc::new(Mutex::new(None));
17054 let snapshot = editor
17055 .update(cx, |editor, window, cx| {
17056 let snapshot = editor.buffer().read(cx).snapshot(cx);
17057 let range =
17058 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
17059
17060 struct RenderArgs {
17061 row: MultiBufferRow,
17062 folded: bool,
17063 callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
17064 }
17065
17066 let crease = Crease::inline(
17067 range,
17068 FoldPlaceholder::test(),
17069 {
17070 let toggle_callback = render_args.clone();
17071 move |row, folded, callback, _window, _cx| {
17072 *toggle_callback.lock() = Some(RenderArgs {
17073 row,
17074 folded,
17075 callback,
17076 });
17077 div()
17078 }
17079 },
17080 |_row, _folded, _window, _cx| div(),
17081 );
17082
17083 editor.insert_creases(Some(crease), cx);
17084 let snapshot = editor.snapshot(window, cx);
17085 let _div = snapshot.render_crease_toggle(
17086 MultiBufferRow(1),
17087 false,
17088 cx.entity().clone(),
17089 window,
17090 cx,
17091 );
17092 snapshot
17093 })
17094 .unwrap();
17095
17096 let render_args = render_args.lock().take().unwrap();
17097 assert_eq!(render_args.row, MultiBufferRow(1));
17098 assert!(!render_args.folded);
17099 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
17100
17101 cx.update_window(*editor, |_, window, cx| {
17102 (render_args.callback)(true, window, cx)
17103 })
17104 .unwrap();
17105 let snapshot = editor
17106 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
17107 .unwrap();
17108 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
17109
17110 cx.update_window(*editor, |_, window, cx| {
17111 (render_args.callback)(false, window, cx)
17112 })
17113 .unwrap();
17114 let snapshot = editor
17115 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
17116 .unwrap();
17117 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
17118}
17119
17120#[gpui::test]
17121async fn test_input_text(cx: &mut TestAppContext) {
17122 init_test(cx, |_| {});
17123 let mut cx = EditorTestContext::new(cx).await;
17124
17125 cx.set_state(
17126 &r#"ˇone
17127 two
17128
17129 three
17130 fourˇ
17131 five
17132
17133 siˇx"#
17134 .unindent(),
17135 );
17136
17137 cx.dispatch_action(HandleInput(String::new()));
17138 cx.assert_editor_state(
17139 &r#"ˇone
17140 two
17141
17142 three
17143 fourˇ
17144 five
17145
17146 siˇx"#
17147 .unindent(),
17148 );
17149
17150 cx.dispatch_action(HandleInput("AAAA".to_string()));
17151 cx.assert_editor_state(
17152 &r#"AAAAˇone
17153 two
17154
17155 three
17156 fourAAAAˇ
17157 five
17158
17159 siAAAAˇx"#
17160 .unindent(),
17161 );
17162}
17163
17164#[gpui::test]
17165async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
17166 init_test(cx, |_| {});
17167
17168 let mut cx = EditorTestContext::new(cx).await;
17169 cx.set_state(
17170 r#"let foo = 1;
17171let foo = 2;
17172let foo = 3;
17173let fooˇ = 4;
17174let foo = 5;
17175let foo = 6;
17176let foo = 7;
17177let foo = 8;
17178let foo = 9;
17179let foo = 10;
17180let foo = 11;
17181let foo = 12;
17182let foo = 13;
17183let foo = 14;
17184let foo = 15;"#,
17185 );
17186
17187 cx.update_editor(|e, window, cx| {
17188 assert_eq!(
17189 e.next_scroll_position,
17190 NextScrollCursorCenterTopBottom::Center,
17191 "Default next scroll direction is center",
17192 );
17193
17194 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
17195 assert_eq!(
17196 e.next_scroll_position,
17197 NextScrollCursorCenterTopBottom::Top,
17198 "After center, next scroll direction should be top",
17199 );
17200
17201 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
17202 assert_eq!(
17203 e.next_scroll_position,
17204 NextScrollCursorCenterTopBottom::Bottom,
17205 "After top, next scroll direction should be bottom",
17206 );
17207
17208 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
17209 assert_eq!(
17210 e.next_scroll_position,
17211 NextScrollCursorCenterTopBottom::Center,
17212 "After bottom, scrolling should start over",
17213 );
17214
17215 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
17216 assert_eq!(
17217 e.next_scroll_position,
17218 NextScrollCursorCenterTopBottom::Top,
17219 "Scrolling continues if retriggered fast enough"
17220 );
17221 });
17222
17223 cx.executor()
17224 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
17225 cx.executor().run_until_parked();
17226 cx.update_editor(|e, _, _| {
17227 assert_eq!(
17228 e.next_scroll_position,
17229 NextScrollCursorCenterTopBottom::Center,
17230 "If scrolling is not triggered fast enough, it should reset"
17231 );
17232 });
17233}
17234
17235#[gpui::test]
17236async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
17237 init_test(cx, |_| {});
17238 let mut cx = EditorLspTestContext::new_rust(
17239 lsp::ServerCapabilities {
17240 definition_provider: Some(lsp::OneOf::Left(true)),
17241 references_provider: Some(lsp::OneOf::Left(true)),
17242 ..lsp::ServerCapabilities::default()
17243 },
17244 cx,
17245 )
17246 .await;
17247
17248 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
17249 let go_to_definition = cx
17250 .lsp
17251 .set_request_handler::<lsp::request::GotoDefinition, _, _>(
17252 move |params, _| async move {
17253 if empty_go_to_definition {
17254 Ok(None)
17255 } else {
17256 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
17257 uri: params.text_document_position_params.text_document.uri,
17258 range: lsp::Range::new(
17259 lsp::Position::new(4, 3),
17260 lsp::Position::new(4, 6),
17261 ),
17262 })))
17263 }
17264 },
17265 );
17266 let references = cx
17267 .lsp
17268 .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
17269 Ok(Some(vec![lsp::Location {
17270 uri: params.text_document_position.text_document.uri,
17271 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
17272 }]))
17273 });
17274 (go_to_definition, references)
17275 };
17276
17277 cx.set_state(
17278 &r#"fn one() {
17279 let mut a = ˇtwo();
17280 }
17281
17282 fn two() {}"#
17283 .unindent(),
17284 );
17285 set_up_lsp_handlers(false, &mut cx);
17286 let navigated = cx
17287 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
17288 .await
17289 .expect("Failed to navigate to definition");
17290 assert_eq!(
17291 navigated,
17292 Navigated::Yes,
17293 "Should have navigated to definition from the GetDefinition response"
17294 );
17295 cx.assert_editor_state(
17296 &r#"fn one() {
17297 let mut a = two();
17298 }
17299
17300 fn «twoˇ»() {}"#
17301 .unindent(),
17302 );
17303
17304 let editors = cx.update_workspace(|workspace, _, cx| {
17305 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
17306 });
17307 cx.update_editor(|_, _, test_editor_cx| {
17308 assert_eq!(
17309 editors.len(),
17310 1,
17311 "Initially, only one, test, editor should be open in the workspace"
17312 );
17313 assert_eq!(
17314 test_editor_cx.entity(),
17315 editors.last().expect("Asserted len is 1").clone()
17316 );
17317 });
17318
17319 set_up_lsp_handlers(true, &mut cx);
17320 let navigated = cx
17321 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
17322 .await
17323 .expect("Failed to navigate to lookup references");
17324 assert_eq!(
17325 navigated,
17326 Navigated::Yes,
17327 "Should have navigated to references as a fallback after empty GoToDefinition response"
17328 );
17329 // We should not change the selections in the existing file,
17330 // if opening another milti buffer with the references
17331 cx.assert_editor_state(
17332 &r#"fn one() {
17333 let mut a = two();
17334 }
17335
17336 fn «twoˇ»() {}"#
17337 .unindent(),
17338 );
17339 let editors = cx.update_workspace(|workspace, _, cx| {
17340 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
17341 });
17342 cx.update_editor(|_, _, test_editor_cx| {
17343 assert_eq!(
17344 editors.len(),
17345 2,
17346 "After falling back to references search, we open a new editor with the results"
17347 );
17348 let references_fallback_text = editors
17349 .into_iter()
17350 .find(|new_editor| *new_editor != test_editor_cx.entity())
17351 .expect("Should have one non-test editor now")
17352 .read(test_editor_cx)
17353 .text(test_editor_cx);
17354 assert_eq!(
17355 references_fallback_text, "fn one() {\n let mut a = two();\n}",
17356 "Should use the range from the references response and not the GoToDefinition one"
17357 );
17358 });
17359}
17360
17361#[gpui::test]
17362async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
17363 init_test(cx, |_| {});
17364 cx.update(|cx| {
17365 let mut editor_settings = EditorSettings::get_global(cx).clone();
17366 editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
17367 EditorSettings::override_global(editor_settings, cx);
17368 });
17369 let mut cx = EditorLspTestContext::new_rust(
17370 lsp::ServerCapabilities {
17371 definition_provider: Some(lsp::OneOf::Left(true)),
17372 references_provider: Some(lsp::OneOf::Left(true)),
17373 ..lsp::ServerCapabilities::default()
17374 },
17375 cx,
17376 )
17377 .await;
17378 let original_state = r#"fn one() {
17379 let mut a = ˇtwo();
17380 }
17381
17382 fn two() {}"#
17383 .unindent();
17384 cx.set_state(&original_state);
17385
17386 let mut go_to_definition = cx
17387 .lsp
17388 .set_request_handler::<lsp::request::GotoDefinition, _, _>(
17389 move |_, _| async move { Ok(None) },
17390 );
17391 let _references = cx
17392 .lsp
17393 .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
17394 panic!("Should not call for references with no go to definition fallback")
17395 });
17396
17397 let navigated = cx
17398 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
17399 .await
17400 .expect("Failed to navigate to lookup references");
17401 go_to_definition
17402 .next()
17403 .await
17404 .expect("Should have called the go_to_definition handler");
17405
17406 assert_eq!(
17407 navigated,
17408 Navigated::No,
17409 "Should have navigated to references as a fallback after empty GoToDefinition response"
17410 );
17411 cx.assert_editor_state(&original_state);
17412 let editors = cx.update_workspace(|workspace, _, cx| {
17413 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
17414 });
17415 cx.update_editor(|_, _, _| {
17416 assert_eq!(
17417 editors.len(),
17418 1,
17419 "After unsuccessful fallback, no other editor should have been opened"
17420 );
17421 });
17422}
17423
17424#[gpui::test]
17425async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
17426 init_test(cx, |_| {});
17427
17428 let language = Arc::new(Language::new(
17429 LanguageConfig::default(),
17430 Some(tree_sitter_rust::LANGUAGE.into()),
17431 ));
17432
17433 let text = r#"
17434 #[cfg(test)]
17435 mod tests() {
17436 #[test]
17437 fn runnable_1() {
17438 let a = 1;
17439 }
17440
17441 #[test]
17442 fn runnable_2() {
17443 let a = 1;
17444 let b = 2;
17445 }
17446 }
17447 "#
17448 .unindent();
17449
17450 let fs = FakeFs::new(cx.executor());
17451 fs.insert_file("/file.rs", Default::default()).await;
17452
17453 let project = Project::test(fs, ["/a".as_ref()], cx).await;
17454 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17455 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17456 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
17457 let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
17458
17459 let editor = cx.new_window_entity(|window, cx| {
17460 Editor::new(
17461 EditorMode::full(),
17462 multi_buffer,
17463 Some(project.clone()),
17464 window,
17465 cx,
17466 )
17467 });
17468
17469 editor.update_in(cx, |editor, window, cx| {
17470 let snapshot = editor.buffer().read(cx).snapshot(cx);
17471 editor.tasks.insert(
17472 (buffer.read(cx).remote_id(), 3),
17473 RunnableTasks {
17474 templates: vec![],
17475 offset: snapshot.anchor_before(43),
17476 column: 0,
17477 extra_variables: HashMap::default(),
17478 context_range: BufferOffset(43)..BufferOffset(85),
17479 },
17480 );
17481 editor.tasks.insert(
17482 (buffer.read(cx).remote_id(), 8),
17483 RunnableTasks {
17484 templates: vec![],
17485 offset: snapshot.anchor_before(86),
17486 column: 0,
17487 extra_variables: HashMap::default(),
17488 context_range: BufferOffset(86)..BufferOffset(191),
17489 },
17490 );
17491
17492 // Test finding task when cursor is inside function body
17493 editor.change_selections(None, window, cx, |s| {
17494 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
17495 });
17496 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
17497 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
17498
17499 // Test finding task when cursor is on function name
17500 editor.change_selections(None, window, cx, |s| {
17501 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
17502 });
17503 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
17504 assert_eq!(row, 8, "Should find task when cursor is on function name");
17505 });
17506}
17507
17508#[gpui::test]
17509async fn test_folding_buffers(cx: &mut TestAppContext) {
17510 init_test(cx, |_| {});
17511
17512 let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
17513 let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
17514 let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
17515
17516 let fs = FakeFs::new(cx.executor());
17517 fs.insert_tree(
17518 path!("/a"),
17519 json!({
17520 "first.rs": sample_text_1,
17521 "second.rs": sample_text_2,
17522 "third.rs": sample_text_3,
17523 }),
17524 )
17525 .await;
17526 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17527 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17528 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17529 let worktree = project.update(cx, |project, cx| {
17530 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
17531 assert_eq!(worktrees.len(), 1);
17532 worktrees.pop().unwrap()
17533 });
17534 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
17535
17536 let buffer_1 = project
17537 .update(cx, |project, cx| {
17538 project.open_buffer((worktree_id, "first.rs"), cx)
17539 })
17540 .await
17541 .unwrap();
17542 let buffer_2 = project
17543 .update(cx, |project, cx| {
17544 project.open_buffer((worktree_id, "second.rs"), cx)
17545 })
17546 .await
17547 .unwrap();
17548 let buffer_3 = project
17549 .update(cx, |project, cx| {
17550 project.open_buffer((worktree_id, "third.rs"), cx)
17551 })
17552 .await
17553 .unwrap();
17554
17555 let multi_buffer = cx.new(|cx| {
17556 let mut multi_buffer = MultiBuffer::new(ReadWrite);
17557 multi_buffer.push_excerpts(
17558 buffer_1.clone(),
17559 [
17560 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17561 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17562 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17563 ],
17564 cx,
17565 );
17566 multi_buffer.push_excerpts(
17567 buffer_2.clone(),
17568 [
17569 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17570 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17571 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17572 ],
17573 cx,
17574 );
17575 multi_buffer.push_excerpts(
17576 buffer_3.clone(),
17577 [
17578 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17579 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17580 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17581 ],
17582 cx,
17583 );
17584 multi_buffer
17585 });
17586 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
17587 Editor::new(
17588 EditorMode::full(),
17589 multi_buffer.clone(),
17590 Some(project.clone()),
17591 window,
17592 cx,
17593 )
17594 });
17595
17596 assert_eq!(
17597 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17598 "\n\naaaa\nbbbb\ncccc\n\n\nffff\ngggg\n\n\njjjj\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
17599 );
17600
17601 multi_buffer_editor.update(cx, |editor, cx| {
17602 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
17603 });
17604 assert_eq!(
17605 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17606 "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
17607 "After folding the first buffer, its text should not be displayed"
17608 );
17609
17610 multi_buffer_editor.update(cx, |editor, cx| {
17611 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
17612 });
17613 assert_eq!(
17614 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17615 "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
17616 "After folding the second buffer, its text should not be displayed"
17617 );
17618
17619 multi_buffer_editor.update(cx, |editor, cx| {
17620 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
17621 });
17622 assert_eq!(
17623 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17624 "\n\n\n\n\n",
17625 "After folding the third buffer, its text should not be displayed"
17626 );
17627
17628 // Emulate selection inside the fold logic, that should work
17629 multi_buffer_editor.update_in(cx, |editor, window, cx| {
17630 editor
17631 .snapshot(window, cx)
17632 .next_line_boundary(Point::new(0, 4));
17633 });
17634
17635 multi_buffer_editor.update(cx, |editor, cx| {
17636 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
17637 });
17638 assert_eq!(
17639 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17640 "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
17641 "After unfolding the second buffer, its text should be displayed"
17642 );
17643
17644 // Typing inside of buffer 1 causes that buffer to be unfolded.
17645 multi_buffer_editor.update_in(cx, |editor, window, cx| {
17646 assert_eq!(
17647 multi_buffer
17648 .read(cx)
17649 .snapshot(cx)
17650 .text_for_range(Point::new(1, 0)..Point::new(1, 4))
17651 .collect::<String>(),
17652 "bbbb"
17653 );
17654 editor.change_selections(None, window, cx, |selections| {
17655 selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
17656 });
17657 editor.handle_input("B", window, cx);
17658 });
17659
17660 assert_eq!(
17661 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17662 "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
17663 "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
17664 );
17665
17666 multi_buffer_editor.update(cx, |editor, cx| {
17667 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
17668 });
17669 assert_eq!(
17670 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17671 "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
17672 "After unfolding the all buffers, all original text should be displayed"
17673 );
17674}
17675
17676#[gpui::test]
17677async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
17678 init_test(cx, |_| {});
17679
17680 let sample_text_1 = "1111\n2222\n3333".to_string();
17681 let sample_text_2 = "4444\n5555\n6666".to_string();
17682 let sample_text_3 = "7777\n8888\n9999".to_string();
17683
17684 let fs = FakeFs::new(cx.executor());
17685 fs.insert_tree(
17686 path!("/a"),
17687 json!({
17688 "first.rs": sample_text_1,
17689 "second.rs": sample_text_2,
17690 "third.rs": sample_text_3,
17691 }),
17692 )
17693 .await;
17694 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17695 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17696 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17697 let worktree = project.update(cx, |project, cx| {
17698 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
17699 assert_eq!(worktrees.len(), 1);
17700 worktrees.pop().unwrap()
17701 });
17702 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
17703
17704 let buffer_1 = project
17705 .update(cx, |project, cx| {
17706 project.open_buffer((worktree_id, "first.rs"), cx)
17707 })
17708 .await
17709 .unwrap();
17710 let buffer_2 = project
17711 .update(cx, |project, cx| {
17712 project.open_buffer((worktree_id, "second.rs"), cx)
17713 })
17714 .await
17715 .unwrap();
17716 let buffer_3 = project
17717 .update(cx, |project, cx| {
17718 project.open_buffer((worktree_id, "third.rs"), cx)
17719 })
17720 .await
17721 .unwrap();
17722
17723 let multi_buffer = cx.new(|cx| {
17724 let mut multi_buffer = MultiBuffer::new(ReadWrite);
17725 multi_buffer.push_excerpts(
17726 buffer_1.clone(),
17727 [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
17728 cx,
17729 );
17730 multi_buffer.push_excerpts(
17731 buffer_2.clone(),
17732 [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
17733 cx,
17734 );
17735 multi_buffer.push_excerpts(
17736 buffer_3.clone(),
17737 [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
17738 cx,
17739 );
17740 multi_buffer
17741 });
17742
17743 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
17744 Editor::new(
17745 EditorMode::full(),
17746 multi_buffer,
17747 Some(project.clone()),
17748 window,
17749 cx,
17750 )
17751 });
17752
17753 let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
17754 assert_eq!(
17755 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17756 full_text,
17757 );
17758
17759 multi_buffer_editor.update(cx, |editor, cx| {
17760 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
17761 });
17762 assert_eq!(
17763 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17764 "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
17765 "After folding the first buffer, its text should not be displayed"
17766 );
17767
17768 multi_buffer_editor.update(cx, |editor, cx| {
17769 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
17770 });
17771
17772 assert_eq!(
17773 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17774 "\n\n\n\n\n\n7777\n8888\n9999",
17775 "After folding the second buffer, its text should not be displayed"
17776 );
17777
17778 multi_buffer_editor.update(cx, |editor, cx| {
17779 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
17780 });
17781 assert_eq!(
17782 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17783 "\n\n\n\n\n",
17784 "After folding the third buffer, its text should not be displayed"
17785 );
17786
17787 multi_buffer_editor.update(cx, |editor, cx| {
17788 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
17789 });
17790 assert_eq!(
17791 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17792 "\n\n\n\n4444\n5555\n6666\n\n",
17793 "After unfolding the second buffer, its text should be displayed"
17794 );
17795
17796 multi_buffer_editor.update(cx, |editor, cx| {
17797 editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
17798 });
17799 assert_eq!(
17800 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17801 "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
17802 "After unfolding the first buffer, its text should be displayed"
17803 );
17804
17805 multi_buffer_editor.update(cx, |editor, cx| {
17806 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
17807 });
17808 assert_eq!(
17809 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17810 full_text,
17811 "After unfolding all buffers, all original text should be displayed"
17812 );
17813}
17814
17815#[gpui::test]
17816async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
17817 init_test(cx, |_| {});
17818
17819 let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
17820
17821 let fs = FakeFs::new(cx.executor());
17822 fs.insert_tree(
17823 path!("/a"),
17824 json!({
17825 "main.rs": sample_text,
17826 }),
17827 )
17828 .await;
17829 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17830 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17831 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17832 let worktree = project.update(cx, |project, cx| {
17833 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
17834 assert_eq!(worktrees.len(), 1);
17835 worktrees.pop().unwrap()
17836 });
17837 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
17838
17839 let buffer_1 = project
17840 .update(cx, |project, cx| {
17841 project.open_buffer((worktree_id, "main.rs"), cx)
17842 })
17843 .await
17844 .unwrap();
17845
17846 let multi_buffer = cx.new(|cx| {
17847 let mut multi_buffer = MultiBuffer::new(ReadWrite);
17848 multi_buffer.push_excerpts(
17849 buffer_1.clone(),
17850 [ExcerptRange::new(
17851 Point::new(0, 0)
17852 ..Point::new(
17853 sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
17854 0,
17855 ),
17856 )],
17857 cx,
17858 );
17859 multi_buffer
17860 });
17861 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
17862 Editor::new(
17863 EditorMode::full(),
17864 multi_buffer,
17865 Some(project.clone()),
17866 window,
17867 cx,
17868 )
17869 });
17870
17871 let selection_range = Point::new(1, 0)..Point::new(2, 0);
17872 multi_buffer_editor.update_in(cx, |editor, window, cx| {
17873 enum TestHighlight {}
17874 let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
17875 let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
17876 editor.highlight_text::<TestHighlight>(
17877 vec![highlight_range.clone()],
17878 HighlightStyle::color(Hsla::green()),
17879 cx,
17880 );
17881 editor.change_selections(None, window, cx, |s| s.select_ranges(Some(highlight_range)));
17882 });
17883
17884 let full_text = format!("\n\n{sample_text}");
17885 assert_eq!(
17886 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17887 full_text,
17888 );
17889}
17890
17891#[gpui::test]
17892async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
17893 init_test(cx, |_| {});
17894 cx.update(|cx| {
17895 let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
17896 "keymaps/default-linux.json",
17897 cx,
17898 )
17899 .unwrap();
17900 cx.bind_keys(default_key_bindings);
17901 });
17902
17903 let (editor, cx) = cx.add_window_view(|window, cx| {
17904 let multi_buffer = MultiBuffer::build_multi(
17905 [
17906 ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
17907 ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
17908 ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
17909 ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
17910 ],
17911 cx,
17912 );
17913 let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
17914
17915 let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
17916 // fold all but the second buffer, so that we test navigating between two
17917 // adjacent folded buffers, as well as folded buffers at the start and
17918 // end the multibuffer
17919 editor.fold_buffer(buffer_ids[0], cx);
17920 editor.fold_buffer(buffer_ids[2], cx);
17921 editor.fold_buffer(buffer_ids[3], cx);
17922
17923 editor
17924 });
17925 cx.simulate_resize(size(px(1000.), px(1000.)));
17926
17927 let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
17928 cx.assert_excerpts_with_selections(indoc! {"
17929 [EXCERPT]
17930 ˇ[FOLDED]
17931 [EXCERPT]
17932 a1
17933 b1
17934 [EXCERPT]
17935 [FOLDED]
17936 [EXCERPT]
17937 [FOLDED]
17938 "
17939 });
17940 cx.simulate_keystroke("down");
17941 cx.assert_excerpts_with_selections(indoc! {"
17942 [EXCERPT]
17943 [FOLDED]
17944 [EXCERPT]
17945 ˇa1
17946 b1
17947 [EXCERPT]
17948 [FOLDED]
17949 [EXCERPT]
17950 [FOLDED]
17951 "
17952 });
17953 cx.simulate_keystroke("down");
17954 cx.assert_excerpts_with_selections(indoc! {"
17955 [EXCERPT]
17956 [FOLDED]
17957 [EXCERPT]
17958 a1
17959 ˇb1
17960 [EXCERPT]
17961 [FOLDED]
17962 [EXCERPT]
17963 [FOLDED]
17964 "
17965 });
17966 cx.simulate_keystroke("down");
17967 cx.assert_excerpts_with_selections(indoc! {"
17968 [EXCERPT]
17969 [FOLDED]
17970 [EXCERPT]
17971 a1
17972 b1
17973 ˇ[EXCERPT]
17974 [FOLDED]
17975 [EXCERPT]
17976 [FOLDED]
17977 "
17978 });
17979 cx.simulate_keystroke("down");
17980 cx.assert_excerpts_with_selections(indoc! {"
17981 [EXCERPT]
17982 [FOLDED]
17983 [EXCERPT]
17984 a1
17985 b1
17986 [EXCERPT]
17987 ˇ[FOLDED]
17988 [EXCERPT]
17989 [FOLDED]
17990 "
17991 });
17992 for _ in 0..5 {
17993 cx.simulate_keystroke("down");
17994 cx.assert_excerpts_with_selections(indoc! {"
17995 [EXCERPT]
17996 [FOLDED]
17997 [EXCERPT]
17998 a1
17999 b1
18000 [EXCERPT]
18001 [FOLDED]
18002 [EXCERPT]
18003 ˇ[FOLDED]
18004 "
18005 });
18006 }
18007
18008 cx.simulate_keystroke("up");
18009 cx.assert_excerpts_with_selections(indoc! {"
18010 [EXCERPT]
18011 [FOLDED]
18012 [EXCERPT]
18013 a1
18014 b1
18015 [EXCERPT]
18016 ˇ[FOLDED]
18017 [EXCERPT]
18018 [FOLDED]
18019 "
18020 });
18021 cx.simulate_keystroke("up");
18022 cx.assert_excerpts_with_selections(indoc! {"
18023 [EXCERPT]
18024 [FOLDED]
18025 [EXCERPT]
18026 a1
18027 b1
18028 ˇ[EXCERPT]
18029 [FOLDED]
18030 [EXCERPT]
18031 [FOLDED]
18032 "
18033 });
18034 cx.simulate_keystroke("up");
18035 cx.assert_excerpts_with_selections(indoc! {"
18036 [EXCERPT]
18037 [FOLDED]
18038 [EXCERPT]
18039 a1
18040 ˇb1
18041 [EXCERPT]
18042 [FOLDED]
18043 [EXCERPT]
18044 [FOLDED]
18045 "
18046 });
18047 cx.simulate_keystroke("up");
18048 cx.assert_excerpts_with_selections(indoc! {"
18049 [EXCERPT]
18050 [FOLDED]
18051 [EXCERPT]
18052 ˇa1
18053 b1
18054 [EXCERPT]
18055 [FOLDED]
18056 [EXCERPT]
18057 [FOLDED]
18058 "
18059 });
18060 for _ in 0..5 {
18061 cx.simulate_keystroke("up");
18062 cx.assert_excerpts_with_selections(indoc! {"
18063 [EXCERPT]
18064 ˇ[FOLDED]
18065 [EXCERPT]
18066 a1
18067 b1
18068 [EXCERPT]
18069 [FOLDED]
18070 [EXCERPT]
18071 [FOLDED]
18072 "
18073 });
18074 }
18075}
18076
18077#[gpui::test]
18078async fn test_inline_completion_text(cx: &mut TestAppContext) {
18079 init_test(cx, |_| {});
18080
18081 // Simple insertion
18082 assert_highlighted_edits(
18083 "Hello, world!",
18084 vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
18085 true,
18086 cx,
18087 |highlighted_edits, cx| {
18088 assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
18089 assert_eq!(highlighted_edits.highlights.len(), 1);
18090 assert_eq!(highlighted_edits.highlights[0].0, 6..16);
18091 assert_eq!(
18092 highlighted_edits.highlights[0].1.background_color,
18093 Some(cx.theme().status().created_background)
18094 );
18095 },
18096 )
18097 .await;
18098
18099 // Replacement
18100 assert_highlighted_edits(
18101 "This is a test.",
18102 vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
18103 false,
18104 cx,
18105 |highlighted_edits, cx| {
18106 assert_eq!(highlighted_edits.text, "That is a test.");
18107 assert_eq!(highlighted_edits.highlights.len(), 1);
18108 assert_eq!(highlighted_edits.highlights[0].0, 0..4);
18109 assert_eq!(
18110 highlighted_edits.highlights[0].1.background_color,
18111 Some(cx.theme().status().created_background)
18112 );
18113 },
18114 )
18115 .await;
18116
18117 // Multiple edits
18118 assert_highlighted_edits(
18119 "Hello, world!",
18120 vec![
18121 (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
18122 (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
18123 ],
18124 false,
18125 cx,
18126 |highlighted_edits, cx| {
18127 assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
18128 assert_eq!(highlighted_edits.highlights.len(), 2);
18129 assert_eq!(highlighted_edits.highlights[0].0, 0..9);
18130 assert_eq!(highlighted_edits.highlights[1].0, 16..29);
18131 assert_eq!(
18132 highlighted_edits.highlights[0].1.background_color,
18133 Some(cx.theme().status().created_background)
18134 );
18135 assert_eq!(
18136 highlighted_edits.highlights[1].1.background_color,
18137 Some(cx.theme().status().created_background)
18138 );
18139 },
18140 )
18141 .await;
18142
18143 // Multiple lines with edits
18144 assert_highlighted_edits(
18145 "First line\nSecond line\nThird line\nFourth line",
18146 vec![
18147 (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
18148 (
18149 Point::new(2, 0)..Point::new(2, 10),
18150 "New third line".to_string(),
18151 ),
18152 (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
18153 ],
18154 false,
18155 cx,
18156 |highlighted_edits, cx| {
18157 assert_eq!(
18158 highlighted_edits.text,
18159 "Second modified\nNew third line\nFourth updated line"
18160 );
18161 assert_eq!(highlighted_edits.highlights.len(), 3);
18162 assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
18163 assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
18164 assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
18165 for highlight in &highlighted_edits.highlights {
18166 assert_eq!(
18167 highlight.1.background_color,
18168 Some(cx.theme().status().created_background)
18169 );
18170 }
18171 },
18172 )
18173 .await;
18174}
18175
18176#[gpui::test]
18177async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
18178 init_test(cx, |_| {});
18179
18180 // Deletion
18181 assert_highlighted_edits(
18182 "Hello, world!",
18183 vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
18184 true,
18185 cx,
18186 |highlighted_edits, cx| {
18187 assert_eq!(highlighted_edits.text, "Hello, world!");
18188 assert_eq!(highlighted_edits.highlights.len(), 1);
18189 assert_eq!(highlighted_edits.highlights[0].0, 5..11);
18190 assert_eq!(
18191 highlighted_edits.highlights[0].1.background_color,
18192 Some(cx.theme().status().deleted_background)
18193 );
18194 },
18195 )
18196 .await;
18197
18198 // Insertion
18199 assert_highlighted_edits(
18200 "Hello, world!",
18201 vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
18202 true,
18203 cx,
18204 |highlighted_edits, cx| {
18205 assert_eq!(highlighted_edits.highlights.len(), 1);
18206 assert_eq!(highlighted_edits.highlights[0].0, 6..14);
18207 assert_eq!(
18208 highlighted_edits.highlights[0].1.background_color,
18209 Some(cx.theme().status().created_background)
18210 );
18211 },
18212 )
18213 .await;
18214}
18215
18216async fn assert_highlighted_edits(
18217 text: &str,
18218 edits: Vec<(Range<Point>, String)>,
18219 include_deletions: bool,
18220 cx: &mut TestAppContext,
18221 assertion_fn: impl Fn(HighlightedText, &App),
18222) {
18223 let window = cx.add_window(|window, cx| {
18224 let buffer = MultiBuffer::build_simple(text, cx);
18225 Editor::new(EditorMode::full(), buffer, None, window, cx)
18226 });
18227 let cx = &mut VisualTestContext::from_window(*window, cx);
18228
18229 let (buffer, snapshot) = window
18230 .update(cx, |editor, _window, cx| {
18231 (
18232 editor.buffer().clone(),
18233 editor.buffer().read(cx).snapshot(cx),
18234 )
18235 })
18236 .unwrap();
18237
18238 let edits = edits
18239 .into_iter()
18240 .map(|(range, edit)| {
18241 (
18242 snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
18243 edit,
18244 )
18245 })
18246 .collect::<Vec<_>>();
18247
18248 let text_anchor_edits = edits
18249 .clone()
18250 .into_iter()
18251 .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
18252 .collect::<Vec<_>>();
18253
18254 let edit_preview = window
18255 .update(cx, |_, _window, cx| {
18256 buffer
18257 .read(cx)
18258 .as_singleton()
18259 .unwrap()
18260 .read(cx)
18261 .preview_edits(text_anchor_edits.into(), cx)
18262 })
18263 .unwrap()
18264 .await;
18265
18266 cx.update(|_window, cx| {
18267 let highlighted_edits = inline_completion_edit_text(
18268 &snapshot.as_singleton().unwrap().2,
18269 &edits,
18270 &edit_preview,
18271 include_deletions,
18272 cx,
18273 );
18274 assertion_fn(highlighted_edits, cx)
18275 });
18276}
18277
18278#[track_caller]
18279fn assert_breakpoint(
18280 breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
18281 path: &Arc<Path>,
18282 expected: Vec<(u32, Breakpoint)>,
18283) {
18284 if expected.len() == 0usize {
18285 assert!(!breakpoints.contains_key(path), "{}", path.display());
18286 } else {
18287 let mut breakpoint = breakpoints
18288 .get(path)
18289 .unwrap()
18290 .into_iter()
18291 .map(|breakpoint| {
18292 (
18293 breakpoint.row,
18294 Breakpoint {
18295 message: breakpoint.message.clone(),
18296 state: breakpoint.state,
18297 condition: breakpoint.condition.clone(),
18298 hit_condition: breakpoint.hit_condition.clone(),
18299 },
18300 )
18301 })
18302 .collect::<Vec<_>>();
18303
18304 breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
18305
18306 assert_eq!(expected, breakpoint);
18307 }
18308}
18309
18310fn add_log_breakpoint_at_cursor(
18311 editor: &mut Editor,
18312 log_message: &str,
18313 window: &mut Window,
18314 cx: &mut Context<Editor>,
18315) {
18316 let (anchor, bp) = editor
18317 .breakpoints_at_cursors(window, cx)
18318 .first()
18319 .and_then(|(anchor, bp)| {
18320 if let Some(bp) = bp {
18321 Some((*anchor, bp.clone()))
18322 } else {
18323 None
18324 }
18325 })
18326 .unwrap_or_else(|| {
18327 let cursor_position: Point = editor.selections.newest(cx).head();
18328
18329 let breakpoint_position = editor
18330 .snapshot(window, cx)
18331 .display_snapshot
18332 .buffer_snapshot
18333 .anchor_before(Point::new(cursor_position.row, 0));
18334
18335 (breakpoint_position, Breakpoint::new_log(&log_message))
18336 });
18337
18338 editor.edit_breakpoint_at_anchor(
18339 anchor,
18340 bp,
18341 BreakpointEditAction::EditLogMessage(log_message.into()),
18342 cx,
18343 );
18344}
18345
18346#[gpui::test]
18347async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
18348 init_test(cx, |_| {});
18349
18350 let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
18351 let fs = FakeFs::new(cx.executor());
18352 fs.insert_tree(
18353 path!("/a"),
18354 json!({
18355 "main.rs": sample_text,
18356 }),
18357 )
18358 .await;
18359 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18360 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18361 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18362
18363 let fs = FakeFs::new(cx.executor());
18364 fs.insert_tree(
18365 path!("/a"),
18366 json!({
18367 "main.rs": sample_text,
18368 }),
18369 )
18370 .await;
18371 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18372 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18373 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18374 let worktree_id = workspace
18375 .update(cx, |workspace, _window, cx| {
18376 workspace.project().update(cx, |project, cx| {
18377 project.worktrees(cx).next().unwrap().read(cx).id()
18378 })
18379 })
18380 .unwrap();
18381
18382 let buffer = project
18383 .update(cx, |project, cx| {
18384 project.open_buffer((worktree_id, "main.rs"), cx)
18385 })
18386 .await
18387 .unwrap();
18388
18389 let (editor, cx) = cx.add_window_view(|window, cx| {
18390 Editor::new(
18391 EditorMode::full(),
18392 MultiBuffer::build_from_buffer(buffer, cx),
18393 Some(project.clone()),
18394 window,
18395 cx,
18396 )
18397 });
18398
18399 let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
18400 let abs_path = project.read_with(cx, |project, cx| {
18401 project
18402 .absolute_path(&project_path, cx)
18403 .map(|path_buf| Arc::from(path_buf.to_owned()))
18404 .unwrap()
18405 });
18406
18407 // assert we can add breakpoint on the first line
18408 editor.update_in(cx, |editor, window, cx| {
18409 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18410 editor.move_to_end(&MoveToEnd, window, cx);
18411 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18412 });
18413
18414 let breakpoints = editor.update(cx, |editor, cx| {
18415 editor
18416 .breakpoint_store()
18417 .as_ref()
18418 .unwrap()
18419 .read(cx)
18420 .all_breakpoints(cx)
18421 .clone()
18422 });
18423
18424 assert_eq!(1, breakpoints.len());
18425 assert_breakpoint(
18426 &breakpoints,
18427 &abs_path,
18428 vec![
18429 (0, Breakpoint::new_standard()),
18430 (3, Breakpoint::new_standard()),
18431 ],
18432 );
18433
18434 editor.update_in(cx, |editor, window, cx| {
18435 editor.move_to_beginning(&MoveToBeginning, window, cx);
18436 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18437 });
18438
18439 let breakpoints = editor.update(cx, |editor, cx| {
18440 editor
18441 .breakpoint_store()
18442 .as_ref()
18443 .unwrap()
18444 .read(cx)
18445 .all_breakpoints(cx)
18446 .clone()
18447 });
18448
18449 assert_eq!(1, breakpoints.len());
18450 assert_breakpoint(
18451 &breakpoints,
18452 &abs_path,
18453 vec![(3, Breakpoint::new_standard())],
18454 );
18455
18456 editor.update_in(cx, |editor, window, cx| {
18457 editor.move_to_end(&MoveToEnd, window, cx);
18458 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18459 });
18460
18461 let breakpoints = editor.update(cx, |editor, cx| {
18462 editor
18463 .breakpoint_store()
18464 .as_ref()
18465 .unwrap()
18466 .read(cx)
18467 .all_breakpoints(cx)
18468 .clone()
18469 });
18470
18471 assert_eq!(0, breakpoints.len());
18472 assert_breakpoint(&breakpoints, &abs_path, vec![]);
18473}
18474
18475#[gpui::test]
18476async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
18477 init_test(cx, |_| {});
18478
18479 let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
18480
18481 let fs = FakeFs::new(cx.executor());
18482 fs.insert_tree(
18483 path!("/a"),
18484 json!({
18485 "main.rs": sample_text,
18486 }),
18487 )
18488 .await;
18489 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18490 let (workspace, cx) =
18491 cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
18492
18493 let worktree_id = workspace.update(cx, |workspace, cx| {
18494 workspace.project().update(cx, |project, cx| {
18495 project.worktrees(cx).next().unwrap().read(cx).id()
18496 })
18497 });
18498
18499 let buffer = project
18500 .update(cx, |project, cx| {
18501 project.open_buffer((worktree_id, "main.rs"), cx)
18502 })
18503 .await
18504 .unwrap();
18505
18506 let (editor, cx) = cx.add_window_view(|window, cx| {
18507 Editor::new(
18508 EditorMode::full(),
18509 MultiBuffer::build_from_buffer(buffer, cx),
18510 Some(project.clone()),
18511 window,
18512 cx,
18513 )
18514 });
18515
18516 let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
18517 let abs_path = project.read_with(cx, |project, cx| {
18518 project
18519 .absolute_path(&project_path, cx)
18520 .map(|path_buf| Arc::from(path_buf.to_owned()))
18521 .unwrap()
18522 });
18523
18524 editor.update_in(cx, |editor, window, cx| {
18525 add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
18526 });
18527
18528 let breakpoints = editor.update(cx, |editor, cx| {
18529 editor
18530 .breakpoint_store()
18531 .as_ref()
18532 .unwrap()
18533 .read(cx)
18534 .all_breakpoints(cx)
18535 .clone()
18536 });
18537
18538 assert_breakpoint(
18539 &breakpoints,
18540 &abs_path,
18541 vec![(0, Breakpoint::new_log("hello world"))],
18542 );
18543
18544 // Removing a log message from a log breakpoint should remove it
18545 editor.update_in(cx, |editor, window, cx| {
18546 add_log_breakpoint_at_cursor(editor, "", window, cx);
18547 });
18548
18549 let breakpoints = editor.update(cx, |editor, cx| {
18550 editor
18551 .breakpoint_store()
18552 .as_ref()
18553 .unwrap()
18554 .read(cx)
18555 .all_breakpoints(cx)
18556 .clone()
18557 });
18558
18559 assert_breakpoint(&breakpoints, &abs_path, vec![]);
18560
18561 editor.update_in(cx, |editor, window, cx| {
18562 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18563 editor.move_to_end(&MoveToEnd, window, cx);
18564 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18565 // Not adding a log message to a standard breakpoint shouldn't remove it
18566 add_log_breakpoint_at_cursor(editor, "", window, cx);
18567 });
18568
18569 let breakpoints = editor.update(cx, |editor, cx| {
18570 editor
18571 .breakpoint_store()
18572 .as_ref()
18573 .unwrap()
18574 .read(cx)
18575 .all_breakpoints(cx)
18576 .clone()
18577 });
18578
18579 assert_breakpoint(
18580 &breakpoints,
18581 &abs_path,
18582 vec![
18583 (0, Breakpoint::new_standard()),
18584 (3, Breakpoint::new_standard()),
18585 ],
18586 );
18587
18588 editor.update_in(cx, |editor, window, cx| {
18589 add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
18590 });
18591
18592 let breakpoints = editor.update(cx, |editor, cx| {
18593 editor
18594 .breakpoint_store()
18595 .as_ref()
18596 .unwrap()
18597 .read(cx)
18598 .all_breakpoints(cx)
18599 .clone()
18600 });
18601
18602 assert_breakpoint(
18603 &breakpoints,
18604 &abs_path,
18605 vec![
18606 (0, Breakpoint::new_standard()),
18607 (3, Breakpoint::new_log("hello world")),
18608 ],
18609 );
18610
18611 editor.update_in(cx, |editor, window, cx| {
18612 add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
18613 });
18614
18615 let breakpoints = editor.update(cx, |editor, cx| {
18616 editor
18617 .breakpoint_store()
18618 .as_ref()
18619 .unwrap()
18620 .read(cx)
18621 .all_breakpoints(cx)
18622 .clone()
18623 });
18624
18625 assert_breakpoint(
18626 &breakpoints,
18627 &abs_path,
18628 vec![
18629 (0, Breakpoint::new_standard()),
18630 (3, Breakpoint::new_log("hello Earth!!")),
18631 ],
18632 );
18633}
18634
18635/// This also tests that Editor::breakpoint_at_cursor_head is working properly
18636/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
18637/// or when breakpoints were placed out of order. This tests for a regression too
18638#[gpui::test]
18639async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
18640 init_test(cx, |_| {});
18641
18642 let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
18643 let fs = FakeFs::new(cx.executor());
18644 fs.insert_tree(
18645 path!("/a"),
18646 json!({
18647 "main.rs": sample_text,
18648 }),
18649 )
18650 .await;
18651 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18652 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18653 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18654
18655 let fs = FakeFs::new(cx.executor());
18656 fs.insert_tree(
18657 path!("/a"),
18658 json!({
18659 "main.rs": sample_text,
18660 }),
18661 )
18662 .await;
18663 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18664 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18665 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18666 let worktree_id = workspace
18667 .update(cx, |workspace, _window, cx| {
18668 workspace.project().update(cx, |project, cx| {
18669 project.worktrees(cx).next().unwrap().read(cx).id()
18670 })
18671 })
18672 .unwrap();
18673
18674 let buffer = project
18675 .update(cx, |project, cx| {
18676 project.open_buffer((worktree_id, "main.rs"), cx)
18677 })
18678 .await
18679 .unwrap();
18680
18681 let (editor, cx) = cx.add_window_view(|window, cx| {
18682 Editor::new(
18683 EditorMode::full(),
18684 MultiBuffer::build_from_buffer(buffer, cx),
18685 Some(project.clone()),
18686 window,
18687 cx,
18688 )
18689 });
18690
18691 let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
18692 let abs_path = project.read_with(cx, |project, cx| {
18693 project
18694 .absolute_path(&project_path, cx)
18695 .map(|path_buf| Arc::from(path_buf.to_owned()))
18696 .unwrap()
18697 });
18698
18699 // assert we can add breakpoint on the first line
18700 editor.update_in(cx, |editor, window, cx| {
18701 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18702 editor.move_to_end(&MoveToEnd, window, cx);
18703 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18704 editor.move_up(&MoveUp, window, cx);
18705 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18706 });
18707
18708 let breakpoints = editor.update(cx, |editor, cx| {
18709 editor
18710 .breakpoint_store()
18711 .as_ref()
18712 .unwrap()
18713 .read(cx)
18714 .all_breakpoints(cx)
18715 .clone()
18716 });
18717
18718 assert_eq!(1, breakpoints.len());
18719 assert_breakpoint(
18720 &breakpoints,
18721 &abs_path,
18722 vec![
18723 (0, Breakpoint::new_standard()),
18724 (2, Breakpoint::new_standard()),
18725 (3, Breakpoint::new_standard()),
18726 ],
18727 );
18728
18729 editor.update_in(cx, |editor, window, cx| {
18730 editor.move_to_beginning(&MoveToBeginning, window, cx);
18731 editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
18732 editor.move_to_end(&MoveToEnd, window, cx);
18733 editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
18734 // Disabling a breakpoint that doesn't exist should do nothing
18735 editor.move_up(&MoveUp, window, cx);
18736 editor.move_up(&MoveUp, window, cx);
18737 editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
18738 });
18739
18740 let breakpoints = editor.update(cx, |editor, cx| {
18741 editor
18742 .breakpoint_store()
18743 .as_ref()
18744 .unwrap()
18745 .read(cx)
18746 .all_breakpoints(cx)
18747 .clone()
18748 });
18749
18750 let disable_breakpoint = {
18751 let mut bp = Breakpoint::new_standard();
18752 bp.state = BreakpointState::Disabled;
18753 bp
18754 };
18755
18756 assert_eq!(1, breakpoints.len());
18757 assert_breakpoint(
18758 &breakpoints,
18759 &abs_path,
18760 vec![
18761 (0, disable_breakpoint.clone()),
18762 (2, Breakpoint::new_standard()),
18763 (3, disable_breakpoint.clone()),
18764 ],
18765 );
18766
18767 editor.update_in(cx, |editor, window, cx| {
18768 editor.move_to_beginning(&MoveToBeginning, window, cx);
18769 editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
18770 editor.move_to_end(&MoveToEnd, window, cx);
18771 editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
18772 editor.move_up(&MoveUp, window, cx);
18773 editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
18774 });
18775
18776 let breakpoints = editor.update(cx, |editor, cx| {
18777 editor
18778 .breakpoint_store()
18779 .as_ref()
18780 .unwrap()
18781 .read(cx)
18782 .all_breakpoints(cx)
18783 .clone()
18784 });
18785
18786 assert_eq!(1, breakpoints.len());
18787 assert_breakpoint(
18788 &breakpoints,
18789 &abs_path,
18790 vec![
18791 (0, Breakpoint::new_standard()),
18792 (2, disable_breakpoint),
18793 (3, Breakpoint::new_standard()),
18794 ],
18795 );
18796}
18797
18798#[gpui::test]
18799async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
18800 init_test(cx, |_| {});
18801 let capabilities = lsp::ServerCapabilities {
18802 rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
18803 prepare_provider: Some(true),
18804 work_done_progress_options: Default::default(),
18805 })),
18806 ..Default::default()
18807 };
18808 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
18809
18810 cx.set_state(indoc! {"
18811 struct Fˇoo {}
18812 "});
18813
18814 cx.update_editor(|editor, _, cx| {
18815 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
18816 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
18817 editor.highlight_background::<DocumentHighlightRead>(
18818 &[highlight_range],
18819 |c| c.editor_document_highlight_read_background,
18820 cx,
18821 );
18822 });
18823
18824 let mut prepare_rename_handler = cx
18825 .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
18826 move |_, _, _| async move {
18827 Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
18828 start: lsp::Position {
18829 line: 0,
18830 character: 7,
18831 },
18832 end: lsp::Position {
18833 line: 0,
18834 character: 10,
18835 },
18836 })))
18837 },
18838 );
18839 let prepare_rename_task = cx
18840 .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
18841 .expect("Prepare rename was not started");
18842 prepare_rename_handler.next().await.unwrap();
18843 prepare_rename_task.await.expect("Prepare rename failed");
18844
18845 let mut rename_handler =
18846 cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
18847 let edit = lsp::TextEdit {
18848 range: lsp::Range {
18849 start: lsp::Position {
18850 line: 0,
18851 character: 7,
18852 },
18853 end: lsp::Position {
18854 line: 0,
18855 character: 10,
18856 },
18857 },
18858 new_text: "FooRenamed".to_string(),
18859 };
18860 Ok(Some(lsp::WorkspaceEdit::new(
18861 // Specify the same edit twice
18862 std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
18863 )))
18864 });
18865 let rename_task = cx
18866 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
18867 .expect("Confirm rename was not started");
18868 rename_handler.next().await.unwrap();
18869 rename_task.await.expect("Confirm rename failed");
18870 cx.run_until_parked();
18871
18872 // Despite two edits, only one is actually applied as those are identical
18873 cx.assert_editor_state(indoc! {"
18874 struct FooRenamedˇ {}
18875 "});
18876}
18877
18878#[gpui::test]
18879async fn test_rename_without_prepare(cx: &mut TestAppContext) {
18880 init_test(cx, |_| {});
18881 // These capabilities indicate that the server does not support prepare rename.
18882 let capabilities = lsp::ServerCapabilities {
18883 rename_provider: Some(lsp::OneOf::Left(true)),
18884 ..Default::default()
18885 };
18886 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
18887
18888 cx.set_state(indoc! {"
18889 struct Fˇoo {}
18890 "});
18891
18892 cx.update_editor(|editor, _window, cx| {
18893 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
18894 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
18895 editor.highlight_background::<DocumentHighlightRead>(
18896 &[highlight_range],
18897 |c| c.editor_document_highlight_read_background,
18898 cx,
18899 );
18900 });
18901
18902 cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
18903 .expect("Prepare rename was not started")
18904 .await
18905 .expect("Prepare rename failed");
18906
18907 let mut rename_handler =
18908 cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
18909 let edit = lsp::TextEdit {
18910 range: lsp::Range {
18911 start: lsp::Position {
18912 line: 0,
18913 character: 7,
18914 },
18915 end: lsp::Position {
18916 line: 0,
18917 character: 10,
18918 },
18919 },
18920 new_text: "FooRenamed".to_string(),
18921 };
18922 Ok(Some(lsp::WorkspaceEdit::new(
18923 std::collections::HashMap::from_iter(Some((url, vec![edit]))),
18924 )))
18925 });
18926 let rename_task = cx
18927 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
18928 .expect("Confirm rename was not started");
18929 rename_handler.next().await.unwrap();
18930 rename_task.await.expect("Confirm rename failed");
18931 cx.run_until_parked();
18932
18933 // Correct range is renamed, as `surrounding_word` is used to find it.
18934 cx.assert_editor_state(indoc! {"
18935 struct FooRenamedˇ {}
18936 "});
18937}
18938
18939#[gpui::test]
18940async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
18941 init_test(cx, |_| {});
18942 let mut cx = EditorTestContext::new(cx).await;
18943
18944 let language = Arc::new(
18945 Language::new(
18946 LanguageConfig::default(),
18947 Some(tree_sitter_html::LANGUAGE.into()),
18948 )
18949 .with_brackets_query(
18950 r#"
18951 ("<" @open "/>" @close)
18952 ("</" @open ">" @close)
18953 ("<" @open ">" @close)
18954 ("\"" @open "\"" @close)
18955 ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
18956 "#,
18957 )
18958 .unwrap(),
18959 );
18960 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
18961
18962 cx.set_state(indoc! {"
18963 <span>ˇ</span>
18964 "});
18965 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
18966 cx.assert_editor_state(indoc! {"
18967 <span>
18968 ˇ
18969 </span>
18970 "});
18971
18972 cx.set_state(indoc! {"
18973 <span><span></span>ˇ</span>
18974 "});
18975 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
18976 cx.assert_editor_state(indoc! {"
18977 <span><span></span>
18978 ˇ</span>
18979 "});
18980
18981 cx.set_state(indoc! {"
18982 <span>ˇ
18983 </span>
18984 "});
18985 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
18986 cx.assert_editor_state(indoc! {"
18987 <span>
18988 ˇ
18989 </span>
18990 "});
18991}
18992
18993#[gpui::test(iterations = 10)]
18994async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
18995 init_test(cx, |_| {});
18996
18997 let fs = FakeFs::new(cx.executor());
18998 fs.insert_tree(
18999 path!("/dir"),
19000 json!({
19001 "a.ts": "a",
19002 }),
19003 )
19004 .await;
19005
19006 let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
19007 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19008 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19009
19010 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
19011 language_registry.add(Arc::new(Language::new(
19012 LanguageConfig {
19013 name: "TypeScript".into(),
19014 matcher: LanguageMatcher {
19015 path_suffixes: vec!["ts".to_string()],
19016 ..Default::default()
19017 },
19018 ..Default::default()
19019 },
19020 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
19021 )));
19022 let mut fake_language_servers = language_registry.register_fake_lsp(
19023 "TypeScript",
19024 FakeLspAdapter {
19025 capabilities: lsp::ServerCapabilities {
19026 code_lens_provider: Some(lsp::CodeLensOptions {
19027 resolve_provider: Some(true),
19028 }),
19029 execute_command_provider: Some(lsp::ExecuteCommandOptions {
19030 commands: vec!["_the/command".to_string()],
19031 ..lsp::ExecuteCommandOptions::default()
19032 }),
19033 ..lsp::ServerCapabilities::default()
19034 },
19035 ..FakeLspAdapter::default()
19036 },
19037 );
19038
19039 let (buffer, _handle) = project
19040 .update(cx, |p, cx| {
19041 p.open_local_buffer_with_lsp(path!("/dir/a.ts"), cx)
19042 })
19043 .await
19044 .unwrap();
19045 cx.executor().run_until_parked();
19046
19047 let fake_server = fake_language_servers.next().await.unwrap();
19048
19049 let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
19050 let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
19051 drop(buffer_snapshot);
19052 let actions = cx
19053 .update_window(*workspace, |_, window, cx| {
19054 project.code_actions(&buffer, anchor..anchor, window, cx)
19055 })
19056 .unwrap();
19057
19058 fake_server
19059 .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
19060 Ok(Some(vec![
19061 lsp::CodeLens {
19062 range: lsp::Range::default(),
19063 command: Some(lsp::Command {
19064 title: "Code lens command".to_owned(),
19065 command: "_the/command".to_owned(),
19066 arguments: None,
19067 }),
19068 data: None,
19069 },
19070 lsp::CodeLens {
19071 range: lsp::Range::default(),
19072 command: Some(lsp::Command {
19073 title: "Command not in capabilities".to_owned(),
19074 command: "not in capabilities".to_owned(),
19075 arguments: None,
19076 }),
19077 data: None,
19078 },
19079 lsp::CodeLens {
19080 range: lsp::Range {
19081 start: lsp::Position {
19082 line: 1,
19083 character: 1,
19084 },
19085 end: lsp::Position {
19086 line: 1,
19087 character: 1,
19088 },
19089 },
19090 command: Some(lsp::Command {
19091 title: "Command not in range".to_owned(),
19092 command: "_the/command".to_owned(),
19093 arguments: None,
19094 }),
19095 data: None,
19096 },
19097 ]))
19098 })
19099 .next()
19100 .await;
19101
19102 let actions = actions.await.unwrap();
19103 assert_eq!(
19104 actions.len(),
19105 1,
19106 "Should have only one valid action for the 0..0 range"
19107 );
19108 let action = actions[0].clone();
19109 let apply = project.update(cx, |project, cx| {
19110 project.apply_code_action(buffer.clone(), action, true, cx)
19111 });
19112
19113 // Resolving the code action does not populate its edits. In absence of
19114 // edits, we must execute the given command.
19115 fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
19116 |mut lens, _| async move {
19117 let lens_command = lens.command.as_mut().expect("should have a command");
19118 assert_eq!(lens_command.title, "Code lens command");
19119 lens_command.arguments = Some(vec![json!("the-argument")]);
19120 Ok(lens)
19121 },
19122 );
19123
19124 // While executing the command, the language server sends the editor
19125 // a `workspaceEdit` request.
19126 fake_server
19127 .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
19128 let fake = fake_server.clone();
19129 move |params, _| {
19130 assert_eq!(params.command, "_the/command");
19131 let fake = fake.clone();
19132 async move {
19133 fake.server
19134 .request::<lsp::request::ApplyWorkspaceEdit>(
19135 lsp::ApplyWorkspaceEditParams {
19136 label: None,
19137 edit: lsp::WorkspaceEdit {
19138 changes: Some(
19139 [(
19140 lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
19141 vec![lsp::TextEdit {
19142 range: lsp::Range::new(
19143 lsp::Position::new(0, 0),
19144 lsp::Position::new(0, 0),
19145 ),
19146 new_text: "X".into(),
19147 }],
19148 )]
19149 .into_iter()
19150 .collect(),
19151 ),
19152 ..Default::default()
19153 },
19154 },
19155 )
19156 .await
19157 .into_response()
19158 .unwrap();
19159 Ok(Some(json!(null)))
19160 }
19161 }
19162 })
19163 .next()
19164 .await;
19165
19166 // Applying the code lens command returns a project transaction containing the edits
19167 // sent by the language server in its `workspaceEdit` request.
19168 let transaction = apply.await.unwrap();
19169 assert!(transaction.0.contains_key(&buffer));
19170 buffer.update(cx, |buffer, cx| {
19171 assert_eq!(buffer.text(), "Xa");
19172 buffer.undo(cx);
19173 assert_eq!(buffer.text(), "a");
19174 });
19175}
19176
19177#[gpui::test]
19178async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
19179 init_test(cx, |_| {});
19180
19181 let fs = FakeFs::new(cx.executor());
19182 let main_text = r#"fn main() {
19183println!("1");
19184println!("2");
19185println!("3");
19186println!("4");
19187println!("5");
19188}"#;
19189 let lib_text = "mod foo {}";
19190 fs.insert_tree(
19191 path!("/a"),
19192 json!({
19193 "lib.rs": lib_text,
19194 "main.rs": main_text,
19195 }),
19196 )
19197 .await;
19198
19199 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19200 let (workspace, cx) =
19201 cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
19202 let worktree_id = workspace.update(cx, |workspace, cx| {
19203 workspace.project().update(cx, |project, cx| {
19204 project.worktrees(cx).next().unwrap().read(cx).id()
19205 })
19206 });
19207
19208 let expected_ranges = vec![
19209 Point::new(0, 0)..Point::new(0, 0),
19210 Point::new(1, 0)..Point::new(1, 1),
19211 Point::new(2, 0)..Point::new(2, 2),
19212 Point::new(3, 0)..Point::new(3, 3),
19213 ];
19214
19215 let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
19216 let editor_1 = workspace
19217 .update_in(cx, |workspace, window, cx| {
19218 workspace.open_path(
19219 (worktree_id, "main.rs"),
19220 Some(pane_1.downgrade()),
19221 true,
19222 window,
19223 cx,
19224 )
19225 })
19226 .unwrap()
19227 .await
19228 .downcast::<Editor>()
19229 .unwrap();
19230 pane_1.update(cx, |pane, cx| {
19231 let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19232 open_editor.update(cx, |editor, cx| {
19233 assert_eq!(
19234 editor.display_text(cx),
19235 main_text,
19236 "Original main.rs text on initial open",
19237 );
19238 assert_eq!(
19239 editor
19240 .selections
19241 .all::<Point>(cx)
19242 .into_iter()
19243 .map(|s| s.range())
19244 .collect::<Vec<_>>(),
19245 vec![Point::zero()..Point::zero()],
19246 "Default selections on initial open",
19247 );
19248 })
19249 });
19250 editor_1.update_in(cx, |editor, window, cx| {
19251 editor.change_selections(None, window, cx, |s| {
19252 s.select_ranges(expected_ranges.clone());
19253 });
19254 });
19255
19256 let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
19257 workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
19258 });
19259 let editor_2 = workspace
19260 .update_in(cx, |workspace, window, cx| {
19261 workspace.open_path(
19262 (worktree_id, "main.rs"),
19263 Some(pane_2.downgrade()),
19264 true,
19265 window,
19266 cx,
19267 )
19268 })
19269 .unwrap()
19270 .await
19271 .downcast::<Editor>()
19272 .unwrap();
19273 pane_2.update(cx, |pane, cx| {
19274 let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19275 open_editor.update(cx, |editor, cx| {
19276 assert_eq!(
19277 editor.display_text(cx),
19278 main_text,
19279 "Original main.rs text on initial open in another panel",
19280 );
19281 assert_eq!(
19282 editor
19283 .selections
19284 .all::<Point>(cx)
19285 .into_iter()
19286 .map(|s| s.range())
19287 .collect::<Vec<_>>(),
19288 vec![Point::zero()..Point::zero()],
19289 "Default selections on initial open in another panel",
19290 );
19291 })
19292 });
19293
19294 editor_2.update_in(cx, |editor, window, cx| {
19295 editor.fold_ranges(expected_ranges.clone(), false, window, cx);
19296 });
19297
19298 let _other_editor_1 = workspace
19299 .update_in(cx, |workspace, window, cx| {
19300 workspace.open_path(
19301 (worktree_id, "lib.rs"),
19302 Some(pane_1.downgrade()),
19303 true,
19304 window,
19305 cx,
19306 )
19307 })
19308 .unwrap()
19309 .await
19310 .downcast::<Editor>()
19311 .unwrap();
19312 pane_1
19313 .update_in(cx, |pane, window, cx| {
19314 pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
19315 .unwrap()
19316 })
19317 .await
19318 .unwrap();
19319 drop(editor_1);
19320 pane_1.update(cx, |pane, cx| {
19321 pane.active_item()
19322 .unwrap()
19323 .downcast::<Editor>()
19324 .unwrap()
19325 .update(cx, |editor, cx| {
19326 assert_eq!(
19327 editor.display_text(cx),
19328 lib_text,
19329 "Other file should be open and active",
19330 );
19331 });
19332 assert_eq!(pane.items().count(), 1, "No other editors should be open");
19333 });
19334
19335 let _other_editor_2 = workspace
19336 .update_in(cx, |workspace, window, cx| {
19337 workspace.open_path(
19338 (worktree_id, "lib.rs"),
19339 Some(pane_2.downgrade()),
19340 true,
19341 window,
19342 cx,
19343 )
19344 })
19345 .unwrap()
19346 .await
19347 .downcast::<Editor>()
19348 .unwrap();
19349 pane_2
19350 .update_in(cx, |pane, window, cx| {
19351 pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
19352 .unwrap()
19353 })
19354 .await
19355 .unwrap();
19356 drop(editor_2);
19357 pane_2.update(cx, |pane, cx| {
19358 let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19359 open_editor.update(cx, |editor, cx| {
19360 assert_eq!(
19361 editor.display_text(cx),
19362 lib_text,
19363 "Other file should be open and active in another panel too",
19364 );
19365 });
19366 assert_eq!(
19367 pane.items().count(),
19368 1,
19369 "No other editors should be open in another pane",
19370 );
19371 });
19372
19373 let _editor_1_reopened = workspace
19374 .update_in(cx, |workspace, window, cx| {
19375 workspace.open_path(
19376 (worktree_id, "main.rs"),
19377 Some(pane_1.downgrade()),
19378 true,
19379 window,
19380 cx,
19381 )
19382 })
19383 .unwrap()
19384 .await
19385 .downcast::<Editor>()
19386 .unwrap();
19387 let _editor_2_reopened = workspace
19388 .update_in(cx, |workspace, window, cx| {
19389 workspace.open_path(
19390 (worktree_id, "main.rs"),
19391 Some(pane_2.downgrade()),
19392 true,
19393 window,
19394 cx,
19395 )
19396 })
19397 .unwrap()
19398 .await
19399 .downcast::<Editor>()
19400 .unwrap();
19401 pane_1.update(cx, |pane, cx| {
19402 let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19403 open_editor.update(cx, |editor, cx| {
19404 assert_eq!(
19405 editor.display_text(cx),
19406 main_text,
19407 "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
19408 );
19409 assert_eq!(
19410 editor
19411 .selections
19412 .all::<Point>(cx)
19413 .into_iter()
19414 .map(|s| s.range())
19415 .collect::<Vec<_>>(),
19416 expected_ranges,
19417 "Previous editor in the 1st panel had selections and should get them restored on reopen",
19418 );
19419 })
19420 });
19421 pane_2.update(cx, |pane, cx| {
19422 let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19423 open_editor.update(cx, |editor, cx| {
19424 assert_eq!(
19425 editor.display_text(cx),
19426 r#"fn main() {
19427⋯rintln!("1");
19428⋯intln!("2");
19429⋯ntln!("3");
19430println!("4");
19431println!("5");
19432}"#,
19433 "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
19434 );
19435 assert_eq!(
19436 editor
19437 .selections
19438 .all::<Point>(cx)
19439 .into_iter()
19440 .map(|s| s.range())
19441 .collect::<Vec<_>>(),
19442 vec![Point::zero()..Point::zero()],
19443 "Previous editor in the 2nd pane had no selections changed hence should restore none",
19444 );
19445 })
19446 });
19447}
19448
19449#[gpui::test]
19450async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
19451 init_test(cx, |_| {});
19452
19453 let fs = FakeFs::new(cx.executor());
19454 let main_text = r#"fn main() {
19455println!("1");
19456println!("2");
19457println!("3");
19458println!("4");
19459println!("5");
19460}"#;
19461 let lib_text = "mod foo {}";
19462 fs.insert_tree(
19463 path!("/a"),
19464 json!({
19465 "lib.rs": lib_text,
19466 "main.rs": main_text,
19467 }),
19468 )
19469 .await;
19470
19471 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19472 let (workspace, cx) =
19473 cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
19474 let worktree_id = workspace.update(cx, |workspace, cx| {
19475 workspace.project().update(cx, |project, cx| {
19476 project.worktrees(cx).next().unwrap().read(cx).id()
19477 })
19478 });
19479
19480 let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
19481 let editor = workspace
19482 .update_in(cx, |workspace, window, cx| {
19483 workspace.open_path(
19484 (worktree_id, "main.rs"),
19485 Some(pane.downgrade()),
19486 true,
19487 window,
19488 cx,
19489 )
19490 })
19491 .unwrap()
19492 .await
19493 .downcast::<Editor>()
19494 .unwrap();
19495 pane.update(cx, |pane, cx| {
19496 let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19497 open_editor.update(cx, |editor, cx| {
19498 assert_eq!(
19499 editor.display_text(cx),
19500 main_text,
19501 "Original main.rs text on initial open",
19502 );
19503 })
19504 });
19505 editor.update_in(cx, |editor, window, cx| {
19506 editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
19507 });
19508
19509 cx.update_global(|store: &mut SettingsStore, cx| {
19510 store.update_user_settings::<WorkspaceSettings>(cx, |s| {
19511 s.restore_on_file_reopen = Some(false);
19512 });
19513 });
19514 editor.update_in(cx, |editor, window, cx| {
19515 editor.fold_ranges(
19516 vec![
19517 Point::new(1, 0)..Point::new(1, 1),
19518 Point::new(2, 0)..Point::new(2, 2),
19519 Point::new(3, 0)..Point::new(3, 3),
19520 ],
19521 false,
19522 window,
19523 cx,
19524 );
19525 });
19526 pane.update_in(cx, |pane, window, cx| {
19527 pane.close_all_items(&CloseAllItems::default(), window, cx)
19528 .unwrap()
19529 })
19530 .await
19531 .unwrap();
19532 pane.update(cx, |pane, _| {
19533 assert!(pane.active_item().is_none());
19534 });
19535 cx.update_global(|store: &mut SettingsStore, cx| {
19536 store.update_user_settings::<WorkspaceSettings>(cx, |s| {
19537 s.restore_on_file_reopen = Some(true);
19538 });
19539 });
19540
19541 let _editor_reopened = workspace
19542 .update_in(cx, |workspace, window, cx| {
19543 workspace.open_path(
19544 (worktree_id, "main.rs"),
19545 Some(pane.downgrade()),
19546 true,
19547 window,
19548 cx,
19549 )
19550 })
19551 .unwrap()
19552 .await
19553 .downcast::<Editor>()
19554 .unwrap();
19555 pane.update(cx, |pane, cx| {
19556 let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19557 open_editor.update(cx, |editor, cx| {
19558 assert_eq!(
19559 editor.display_text(cx),
19560 main_text,
19561 "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
19562 );
19563 })
19564 });
19565}
19566
19567#[gpui::test]
19568async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
19569 struct EmptyModalView {
19570 focus_handle: gpui::FocusHandle,
19571 }
19572 impl EventEmitter<DismissEvent> for EmptyModalView {}
19573 impl Render for EmptyModalView {
19574 fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
19575 div()
19576 }
19577 }
19578 impl Focusable for EmptyModalView {
19579 fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
19580 self.focus_handle.clone()
19581 }
19582 }
19583 impl workspace::ModalView for EmptyModalView {}
19584 fn new_empty_modal_view(cx: &App) -> EmptyModalView {
19585 EmptyModalView {
19586 focus_handle: cx.focus_handle(),
19587 }
19588 }
19589
19590 init_test(cx, |_| {});
19591
19592 let fs = FakeFs::new(cx.executor());
19593 let project = Project::test(fs, [], cx).await;
19594 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19595 let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
19596 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19597 let editor = cx.new_window_entity(|window, cx| {
19598 Editor::new(
19599 EditorMode::full(),
19600 buffer,
19601 Some(project.clone()),
19602 window,
19603 cx,
19604 )
19605 });
19606 workspace
19607 .update(cx, |workspace, window, cx| {
19608 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
19609 })
19610 .unwrap();
19611 editor.update_in(cx, |editor, window, cx| {
19612 editor.open_context_menu(&OpenContextMenu, window, cx);
19613 assert!(editor.mouse_context_menu.is_some());
19614 });
19615 workspace
19616 .update(cx, |workspace, window, cx| {
19617 workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
19618 })
19619 .unwrap();
19620 cx.read(|cx| {
19621 assert!(editor.read(cx).mouse_context_menu.is_none());
19622 });
19623}
19624
19625#[gpui::test]
19626async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
19627 init_test(cx, |_| {});
19628
19629 let fs = FakeFs::new(cx.executor());
19630 fs.insert_file(path!("/file.html"), Default::default())
19631 .await;
19632
19633 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
19634
19635 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
19636 let html_language = Arc::new(Language::new(
19637 LanguageConfig {
19638 name: "HTML".into(),
19639 matcher: LanguageMatcher {
19640 path_suffixes: vec!["html".to_string()],
19641 ..LanguageMatcher::default()
19642 },
19643 brackets: BracketPairConfig {
19644 pairs: vec![BracketPair {
19645 start: "<".into(),
19646 end: ">".into(),
19647 close: true,
19648 ..Default::default()
19649 }],
19650 ..Default::default()
19651 },
19652 ..Default::default()
19653 },
19654 Some(tree_sitter_html::LANGUAGE.into()),
19655 ));
19656 language_registry.add(html_language);
19657 let mut fake_servers = language_registry.register_fake_lsp(
19658 "HTML",
19659 FakeLspAdapter {
19660 capabilities: lsp::ServerCapabilities {
19661 completion_provider: Some(lsp::CompletionOptions {
19662 resolve_provider: Some(true),
19663 ..Default::default()
19664 }),
19665 ..Default::default()
19666 },
19667 ..Default::default()
19668 },
19669 );
19670
19671 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19672 let cx = &mut VisualTestContext::from_window(*workspace, cx);
19673
19674 let worktree_id = workspace
19675 .update(cx, |workspace, _window, cx| {
19676 workspace.project().update(cx, |project, cx| {
19677 project.worktrees(cx).next().unwrap().read(cx).id()
19678 })
19679 })
19680 .unwrap();
19681 project
19682 .update(cx, |project, cx| {
19683 project.open_local_buffer_with_lsp(path!("/file.html"), cx)
19684 })
19685 .await
19686 .unwrap();
19687 let editor = workspace
19688 .update(cx, |workspace, window, cx| {
19689 workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
19690 })
19691 .unwrap()
19692 .await
19693 .unwrap()
19694 .downcast::<Editor>()
19695 .unwrap();
19696
19697 let fake_server = fake_servers.next().await.unwrap();
19698 editor.update_in(cx, |editor, window, cx| {
19699 editor.set_text("<ad></ad>", window, cx);
19700 editor.change_selections(None, window, cx, |selections| {
19701 selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
19702 });
19703 let Some((buffer, _)) = editor
19704 .buffer
19705 .read(cx)
19706 .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
19707 else {
19708 panic!("Failed to get buffer for selection position");
19709 };
19710 let buffer = buffer.read(cx);
19711 let buffer_id = buffer.remote_id();
19712 let opening_range =
19713 buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
19714 let closing_range =
19715 buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
19716 let mut linked_ranges = HashMap::default();
19717 linked_ranges.insert(
19718 buffer_id,
19719 vec![(opening_range.clone(), vec![closing_range.clone()])],
19720 );
19721 editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
19722 });
19723 let mut completion_handle =
19724 fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
19725 Ok(Some(lsp::CompletionResponse::Array(vec![
19726 lsp::CompletionItem {
19727 label: "head".to_string(),
19728 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
19729 lsp::InsertReplaceEdit {
19730 new_text: "head".to_string(),
19731 insert: lsp::Range::new(
19732 lsp::Position::new(0, 1),
19733 lsp::Position::new(0, 3),
19734 ),
19735 replace: lsp::Range::new(
19736 lsp::Position::new(0, 1),
19737 lsp::Position::new(0, 3),
19738 ),
19739 },
19740 )),
19741 ..Default::default()
19742 },
19743 ])))
19744 });
19745 editor.update_in(cx, |editor, window, cx| {
19746 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
19747 });
19748 cx.run_until_parked();
19749 completion_handle.next().await.unwrap();
19750 editor.update(cx, |editor, _| {
19751 assert!(
19752 editor.context_menu_visible(),
19753 "Completion menu should be visible"
19754 );
19755 });
19756 editor.update_in(cx, |editor, window, cx| {
19757 editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
19758 });
19759 cx.executor().run_until_parked();
19760 editor.update(cx, |editor, cx| {
19761 assert_eq!(editor.text(cx), "<head></head>");
19762 });
19763}
19764
19765fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
19766 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
19767 point..point
19768}
19769
19770fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
19771 let (text, ranges) = marked_text_ranges(marked_text, true);
19772 assert_eq!(editor.text(cx), text);
19773 assert_eq!(
19774 editor.selections.ranges(cx),
19775 ranges,
19776 "Assert selections are {}",
19777 marked_text
19778 );
19779}
19780
19781pub fn handle_signature_help_request(
19782 cx: &mut EditorLspTestContext,
19783 mocked_response: lsp::SignatureHelp,
19784) -> impl Future<Output = ()> + use<> {
19785 let mut request =
19786 cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
19787 let mocked_response = mocked_response.clone();
19788 async move { Ok(Some(mocked_response)) }
19789 });
19790
19791 async move {
19792 request.next().await;
19793 }
19794}
19795
19796/// Handle completion request passing a marked string specifying where the completion
19797/// should be triggered from using '|' character, what range should be replaced, and what completions
19798/// should be returned using '<' and '>' to delimit the range.
19799///
19800/// Also see `handle_completion_request_with_insert_and_replace`.
19801#[track_caller]
19802pub fn handle_completion_request(
19803 cx: &mut EditorLspTestContext,
19804 marked_string: &str,
19805 completions: Vec<&'static str>,
19806 counter: Arc<AtomicUsize>,
19807) -> impl Future<Output = ()> {
19808 let complete_from_marker: TextRangeMarker = '|'.into();
19809 let replace_range_marker: TextRangeMarker = ('<', '>').into();
19810 let (_, mut marked_ranges) = marked_text_ranges_by(
19811 marked_string,
19812 vec![complete_from_marker.clone(), replace_range_marker.clone()],
19813 );
19814
19815 let complete_from_position =
19816 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
19817 let replace_range =
19818 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
19819
19820 let mut request =
19821 cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
19822 let completions = completions.clone();
19823 counter.fetch_add(1, atomic::Ordering::Release);
19824 async move {
19825 assert_eq!(params.text_document_position.text_document.uri, url.clone());
19826 assert_eq!(
19827 params.text_document_position.position,
19828 complete_from_position
19829 );
19830 Ok(Some(lsp::CompletionResponse::Array(
19831 completions
19832 .iter()
19833 .map(|completion_text| lsp::CompletionItem {
19834 label: completion_text.to_string(),
19835 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
19836 range: replace_range,
19837 new_text: completion_text.to_string(),
19838 })),
19839 ..Default::default()
19840 })
19841 .collect(),
19842 )))
19843 }
19844 });
19845
19846 async move {
19847 request.next().await;
19848 }
19849}
19850
19851/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
19852/// given instead, which also contains an `insert` range.
19853///
19854/// This function uses the cursor position to mimic what Rust-Analyzer provides as the `insert` range,
19855/// that is, `replace_range.start..cursor_pos`.
19856pub fn handle_completion_request_with_insert_and_replace(
19857 cx: &mut EditorLspTestContext,
19858 marked_string: &str,
19859 completions: Vec<&'static str>,
19860 counter: Arc<AtomicUsize>,
19861) -> impl Future<Output = ()> {
19862 let complete_from_marker: TextRangeMarker = '|'.into();
19863 let replace_range_marker: TextRangeMarker = ('<', '>').into();
19864 let (_, mut marked_ranges) = marked_text_ranges_by(
19865 marked_string,
19866 vec![complete_from_marker.clone(), replace_range_marker.clone()],
19867 );
19868
19869 let complete_from_position =
19870 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
19871 let replace_range =
19872 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
19873
19874 let mut request =
19875 cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
19876 let completions = completions.clone();
19877 counter.fetch_add(1, atomic::Ordering::Release);
19878 async move {
19879 assert_eq!(params.text_document_position.text_document.uri, url.clone());
19880 assert_eq!(
19881 params.text_document_position.position, complete_from_position,
19882 "marker `|` position doesn't match",
19883 );
19884 Ok(Some(lsp::CompletionResponse::Array(
19885 completions
19886 .iter()
19887 .map(|completion_text| lsp::CompletionItem {
19888 label: completion_text.to_string(),
19889 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
19890 lsp::InsertReplaceEdit {
19891 insert: lsp::Range {
19892 start: replace_range.start,
19893 end: complete_from_position,
19894 },
19895 replace: replace_range,
19896 new_text: completion_text.to_string(),
19897 },
19898 )),
19899 ..Default::default()
19900 })
19901 .collect(),
19902 )))
19903 }
19904 });
19905
19906 async move {
19907 request.next().await;
19908 }
19909}
19910
19911fn handle_resolve_completion_request(
19912 cx: &mut EditorLspTestContext,
19913 edits: Option<Vec<(&'static str, &'static str)>>,
19914) -> impl Future<Output = ()> {
19915 let edits = edits.map(|edits| {
19916 edits
19917 .iter()
19918 .map(|(marked_string, new_text)| {
19919 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
19920 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
19921 lsp::TextEdit::new(replace_range, new_text.to_string())
19922 })
19923 .collect::<Vec<_>>()
19924 });
19925
19926 let mut request =
19927 cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
19928 let edits = edits.clone();
19929 async move {
19930 Ok(lsp::CompletionItem {
19931 additional_text_edits: edits,
19932 ..Default::default()
19933 })
19934 }
19935 });
19936
19937 async move {
19938 request.next().await;
19939 }
19940}
19941
19942pub(crate) fn update_test_language_settings(
19943 cx: &mut TestAppContext,
19944 f: impl Fn(&mut AllLanguageSettingsContent),
19945) {
19946 cx.update(|cx| {
19947 SettingsStore::update_global(cx, |store, cx| {
19948 store.update_user_settings::<AllLanguageSettings>(cx, f);
19949 });
19950 });
19951}
19952
19953pub(crate) fn update_test_project_settings(
19954 cx: &mut TestAppContext,
19955 f: impl Fn(&mut ProjectSettings),
19956) {
19957 cx.update(|cx| {
19958 SettingsStore::update_global(cx, |store, cx| {
19959 store.update_user_settings::<ProjectSettings>(cx, f);
19960 });
19961 });
19962}
19963
19964pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
19965 cx.update(|cx| {
19966 assets::Assets.load_test_fonts(cx);
19967 let store = SettingsStore::test(cx);
19968 cx.set_global(store);
19969 theme::init(theme::LoadThemes::JustBase, cx);
19970 release_channel::init(SemanticVersion::default(), cx);
19971 client::init_settings(cx);
19972 language::init(cx);
19973 Project::init_settings(cx);
19974 workspace::init_settings(cx);
19975 crate::init(cx);
19976 });
19977
19978 update_test_language_settings(cx, f);
19979}
19980
19981#[track_caller]
19982fn assert_hunk_revert(
19983 not_reverted_text_with_selections: &str,
19984 expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
19985 expected_reverted_text_with_selections: &str,
19986 base_text: &str,
19987 cx: &mut EditorLspTestContext,
19988) {
19989 cx.set_state(not_reverted_text_with_selections);
19990 cx.set_head_text(base_text);
19991 cx.executor().run_until_parked();
19992
19993 let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
19994 let snapshot = editor.snapshot(window, cx);
19995 let reverted_hunk_statuses = snapshot
19996 .buffer_snapshot
19997 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
19998 .map(|hunk| hunk.status().kind)
19999 .collect::<Vec<_>>();
20000
20001 editor.git_restore(&Default::default(), window, cx);
20002 reverted_hunk_statuses
20003 });
20004 cx.executor().run_until_parked();
20005 cx.assert_editor_state(expected_reverted_text_with_selections);
20006 assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
20007}