1use super::*;
2use crate::{
3 JoinLines,
4 scroll::scroll_amount::ScrollAmount,
5 test::{
6 assert_text_with_selections, build_editor,
7 editor_lsp_test_context::{EditorLspTestContext, git_commit_lang},
8 editor_test_context::EditorTestContext,
9 select_ranges,
10 },
11};
12use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
13use futures::StreamExt;
14use gpui::{
15 BackgroundExecutor, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext,
16 WindowBounds, WindowOptions, div,
17};
18use indoc::indoc;
19use language::{
20 BracketPairConfig,
21 Capability::ReadWrite,
22 FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher, LanguageName,
23 Override, Point,
24 language_settings::{
25 AllLanguageSettings, AllLanguageSettingsContent, CompletionSettings,
26 LanguageSettingsContent, LspInsertMode, PrettierSettings,
27 },
28};
29use language_settings::{Formatter, FormatterList, IndentGuideSettings};
30use lsp::CompletionParams;
31use multi_buffer::{IndentGuide, PathKey};
32use parking_lot::Mutex;
33use pretty_assertions::{assert_eq, assert_ne};
34use project::{
35 FakeFs,
36 debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
37 project_settings::{LspSettings, ProjectSettings},
38};
39use serde_json::{self, json};
40use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
41use std::{
42 iter,
43 sync::atomic::{self, AtomicUsize},
44};
45use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
46use text::ToPoint as _;
47use unindent::Unindent;
48use util::{
49 assert_set_eq, path,
50 test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
51 uri,
52};
53use workspace::{
54 CloseAllItems, CloseInactiveItems, NavigationEntry, ViewId,
55 item::{FollowEvent, FollowableItem, Item, ItemHandle},
56};
57
58#[gpui::test]
59fn test_edit_events(cx: &mut TestAppContext) {
60 init_test(cx, |_| {});
61
62 let buffer = cx.new(|cx| {
63 let mut buffer = language::Buffer::local("123456", cx);
64 buffer.set_group_interval(Duration::from_secs(1));
65 buffer
66 });
67
68 let events = Rc::new(RefCell::new(Vec::new()));
69 let editor1 = cx.add_window({
70 let events = events.clone();
71 |window, cx| {
72 let entity = cx.entity().clone();
73 cx.subscribe_in(
74 &entity,
75 window,
76 move |_, _, event: &EditorEvent, _, _| match event {
77 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
78 EditorEvent::BufferEdited => {
79 events.borrow_mut().push(("editor1", "buffer edited"))
80 }
81 _ => {}
82 },
83 )
84 .detach();
85 Editor::for_buffer(buffer.clone(), None, window, cx)
86 }
87 });
88
89 let editor2 = cx.add_window({
90 let events = events.clone();
91 |window, cx| {
92 cx.subscribe_in(
93 &cx.entity().clone(),
94 window,
95 move |_, _, event: &EditorEvent, _, _| match event {
96 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
97 EditorEvent::BufferEdited => {
98 events.borrow_mut().push(("editor2", "buffer edited"))
99 }
100 _ => {}
101 },
102 )
103 .detach();
104 Editor::for_buffer(buffer.clone(), None, window, cx)
105 }
106 });
107
108 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
109
110 // Mutating editor 1 will emit an `Edited` event only for that editor.
111 _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
112 assert_eq!(
113 mem::take(&mut *events.borrow_mut()),
114 [
115 ("editor1", "edited"),
116 ("editor1", "buffer edited"),
117 ("editor2", "buffer edited"),
118 ]
119 );
120
121 // Mutating editor 2 will emit an `Edited` event only for that editor.
122 _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
123 assert_eq!(
124 mem::take(&mut *events.borrow_mut()),
125 [
126 ("editor2", "edited"),
127 ("editor1", "buffer edited"),
128 ("editor2", "buffer edited"),
129 ]
130 );
131
132 // Undoing on editor 1 will emit an `Edited` event only for that editor.
133 _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
134 assert_eq!(
135 mem::take(&mut *events.borrow_mut()),
136 [
137 ("editor1", "edited"),
138 ("editor1", "buffer edited"),
139 ("editor2", "buffer edited"),
140 ]
141 );
142
143 // Redoing on editor 1 will emit an `Edited` event only for that editor.
144 _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
145 assert_eq!(
146 mem::take(&mut *events.borrow_mut()),
147 [
148 ("editor1", "edited"),
149 ("editor1", "buffer edited"),
150 ("editor2", "buffer edited"),
151 ]
152 );
153
154 // Undoing on editor 2 will emit an `Edited` event only for that editor.
155 _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
156 assert_eq!(
157 mem::take(&mut *events.borrow_mut()),
158 [
159 ("editor2", "edited"),
160 ("editor1", "buffer edited"),
161 ("editor2", "buffer edited"),
162 ]
163 );
164
165 // Redoing on editor 2 will emit an `Edited` event only for that editor.
166 _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
167 assert_eq!(
168 mem::take(&mut *events.borrow_mut()),
169 [
170 ("editor2", "edited"),
171 ("editor1", "buffer edited"),
172 ("editor2", "buffer edited"),
173 ]
174 );
175
176 // No event is emitted when the mutation is a no-op.
177 _ = editor2.update(cx, |editor, window, cx| {
178 editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
179
180 editor.backspace(&Backspace, window, cx);
181 });
182 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
183}
184
185#[gpui::test]
186fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
187 init_test(cx, |_| {});
188
189 let mut now = Instant::now();
190 let group_interval = Duration::from_millis(1);
191 let buffer = cx.new(|cx| {
192 let mut buf = language::Buffer::local("123456", cx);
193 buf.set_group_interval(group_interval);
194 buf
195 });
196 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
197 let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
198
199 _ = editor.update(cx, |editor, window, cx| {
200 editor.start_transaction_at(now, window, cx);
201 editor.change_selections(None, window, cx, |s| s.select_ranges([2..4]));
202
203 editor.insert("cd", window, cx);
204 editor.end_transaction_at(now, cx);
205 assert_eq!(editor.text(cx), "12cd56");
206 assert_eq!(editor.selections.ranges(cx), vec![4..4]);
207
208 editor.start_transaction_at(now, window, cx);
209 editor.change_selections(None, window, cx, |s| s.select_ranges([4..5]));
210 editor.insert("e", window, cx);
211 editor.end_transaction_at(now, cx);
212 assert_eq!(editor.text(cx), "12cde6");
213 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
214
215 now += group_interval + Duration::from_millis(1);
216 editor.change_selections(None, window, cx, |s| s.select_ranges([2..2]));
217
218 // Simulate an edit in another editor
219 buffer.update(cx, |buffer, cx| {
220 buffer.start_transaction_at(now, cx);
221 buffer.edit([(0..1, "a")], None, cx);
222 buffer.edit([(1..1, "b")], None, cx);
223 buffer.end_transaction_at(now, cx);
224 });
225
226 assert_eq!(editor.text(cx), "ab2cde6");
227 assert_eq!(editor.selections.ranges(cx), vec![3..3]);
228
229 // Last transaction happened past the group interval in a different editor.
230 // Undo it individually and don't restore selections.
231 editor.undo(&Undo, window, cx);
232 assert_eq!(editor.text(cx), "12cde6");
233 assert_eq!(editor.selections.ranges(cx), vec![2..2]);
234
235 // First two transactions happened within the group interval in this editor.
236 // Undo them together and restore selections.
237 editor.undo(&Undo, window, cx);
238 editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
239 assert_eq!(editor.text(cx), "123456");
240 assert_eq!(editor.selections.ranges(cx), vec![0..0]);
241
242 // Redo the first two transactions together.
243 editor.redo(&Redo, window, cx);
244 assert_eq!(editor.text(cx), "12cde6");
245 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
246
247 // Redo the last transaction on its own.
248 editor.redo(&Redo, window, cx);
249 assert_eq!(editor.text(cx), "ab2cde6");
250 assert_eq!(editor.selections.ranges(cx), vec![6..6]);
251
252 // Test empty transactions.
253 editor.start_transaction_at(now, window, cx);
254 editor.end_transaction_at(now, cx);
255 editor.undo(&Undo, window, cx);
256 assert_eq!(editor.text(cx), "12cde6");
257 });
258}
259
260#[gpui::test]
261fn test_ime_composition(cx: &mut TestAppContext) {
262 init_test(cx, |_| {});
263
264 let buffer = cx.new(|cx| {
265 let mut buffer = language::Buffer::local("abcde", cx);
266 // Ensure automatic grouping doesn't occur.
267 buffer.set_group_interval(Duration::ZERO);
268 buffer
269 });
270
271 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
272 cx.add_window(|window, cx| {
273 let mut editor = build_editor(buffer.clone(), window, cx);
274
275 // Start a new IME composition.
276 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
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 assert_eq!(editor.text(cx), "äbcde");
280 assert_eq!(
281 editor.marked_text_ranges(cx),
282 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
283 );
284
285 // Finalize IME composition.
286 editor.replace_text_in_range(None, "ā", window, cx);
287 assert_eq!(editor.text(cx), "ābcde");
288 assert_eq!(editor.marked_text_ranges(cx), None);
289
290 // IME composition edits are grouped and are undone/redone at once.
291 editor.undo(&Default::default(), window, cx);
292 assert_eq!(editor.text(cx), "abcde");
293 assert_eq!(editor.marked_text_ranges(cx), None);
294 editor.redo(&Default::default(), window, cx);
295 assert_eq!(editor.text(cx), "ābcde");
296 assert_eq!(editor.marked_text_ranges(cx), None);
297
298 // Start a new IME composition.
299 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
300 assert_eq!(
301 editor.marked_text_ranges(cx),
302 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
303 );
304
305 // Undoing during an IME composition cancels it.
306 editor.undo(&Default::default(), window, cx);
307 assert_eq!(editor.text(cx), "ābcde");
308 assert_eq!(editor.marked_text_ranges(cx), None);
309
310 // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
311 editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
312 assert_eq!(editor.text(cx), "ābcdè");
313 assert_eq!(
314 editor.marked_text_ranges(cx),
315 Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
316 );
317
318 // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
319 editor.replace_text_in_range(Some(4..999), "ę", window, cx);
320 assert_eq!(editor.text(cx), "ābcdę");
321 assert_eq!(editor.marked_text_ranges(cx), None);
322
323 // Start a new IME composition with multiple cursors.
324 editor.change_selections(None, window, cx, |s| {
325 s.select_ranges([
326 OffsetUtf16(1)..OffsetUtf16(1),
327 OffsetUtf16(3)..OffsetUtf16(3),
328 OffsetUtf16(5)..OffsetUtf16(5),
329 ])
330 });
331 editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
332 assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
333 assert_eq!(
334 editor.marked_text_ranges(cx),
335 Some(vec![
336 OffsetUtf16(0)..OffsetUtf16(3),
337 OffsetUtf16(4)..OffsetUtf16(7),
338 OffsetUtf16(8)..OffsetUtf16(11)
339 ])
340 );
341
342 // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
343 editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
344 assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
345 assert_eq!(
346 editor.marked_text_ranges(cx),
347 Some(vec![
348 OffsetUtf16(1)..OffsetUtf16(2),
349 OffsetUtf16(5)..OffsetUtf16(6),
350 OffsetUtf16(9)..OffsetUtf16(10)
351 ])
352 );
353
354 // Finalize IME composition with multiple cursors.
355 editor.replace_text_in_range(Some(9..10), "2", window, cx);
356 assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
357 assert_eq!(editor.marked_text_ranges(cx), None);
358
359 editor
360 });
361}
362
363#[gpui::test]
364fn test_selection_with_mouse(cx: &mut TestAppContext) {
365 init_test(cx, |_| {});
366
367 let editor = cx.add_window(|window, cx| {
368 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
369 build_editor(buffer, window, cx)
370 });
371
372 _ = editor.update(cx, |editor, window, cx| {
373 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
374 });
375 assert_eq!(
376 editor
377 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
378 .unwrap(),
379 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
380 );
381
382 _ = editor.update(cx, |editor, window, cx| {
383 editor.update_selection(
384 DisplayPoint::new(DisplayRow(3), 3),
385 0,
386 gpui::Point::<f32>::default(),
387 window,
388 cx,
389 );
390 });
391
392 assert_eq!(
393 editor
394 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
395 .unwrap(),
396 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
397 );
398
399 _ = editor.update(cx, |editor, window, cx| {
400 editor.update_selection(
401 DisplayPoint::new(DisplayRow(1), 1),
402 0,
403 gpui::Point::<f32>::default(),
404 window,
405 cx,
406 );
407 });
408
409 assert_eq!(
410 editor
411 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
412 .unwrap(),
413 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
414 );
415
416 _ = editor.update(cx, |editor, window, cx| {
417 editor.end_selection(window, cx);
418 editor.update_selection(
419 DisplayPoint::new(DisplayRow(3), 3),
420 0,
421 gpui::Point::<f32>::default(),
422 window,
423 cx,
424 );
425 });
426
427 assert_eq!(
428 editor
429 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
430 .unwrap(),
431 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
432 );
433
434 _ = editor.update(cx, |editor, window, cx| {
435 editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
436 editor.update_selection(
437 DisplayPoint::new(DisplayRow(0), 0),
438 0,
439 gpui::Point::<f32>::default(),
440 window,
441 cx,
442 );
443 });
444
445 assert_eq!(
446 editor
447 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
448 .unwrap(),
449 [
450 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
451 DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
452 ]
453 );
454
455 _ = editor.update(cx, |editor, window, cx| {
456 editor.end_selection(window, cx);
457 });
458
459 assert_eq!(
460 editor
461 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
462 .unwrap(),
463 [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
464 );
465}
466
467#[gpui::test]
468fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
469 init_test(cx, |_| {});
470
471 let editor = cx.add_window(|window, cx| {
472 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
473 build_editor(buffer, window, cx)
474 });
475
476 _ = editor.update(cx, |editor, window, cx| {
477 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
478 });
479
480 _ = editor.update(cx, |editor, window, cx| {
481 editor.end_selection(window, cx);
482 });
483
484 _ = editor.update(cx, |editor, window, cx| {
485 editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
486 });
487
488 _ = editor.update(cx, |editor, window, cx| {
489 editor.end_selection(window, cx);
490 });
491
492 assert_eq!(
493 editor
494 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
495 .unwrap(),
496 [
497 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
498 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
499 ]
500 );
501
502 _ = editor.update(cx, |editor, window, cx| {
503 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
504 });
505
506 _ = editor.update(cx, |editor, window, cx| {
507 editor.end_selection(window, cx);
508 });
509
510 assert_eq!(
511 editor
512 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
513 .unwrap(),
514 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
515 );
516}
517
518#[gpui::test]
519fn test_canceling_pending_selection(cx: &mut TestAppContext) {
520 init_test(cx, |_| {});
521
522 let editor = cx.add_window(|window, cx| {
523 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
524 build_editor(buffer, window, cx)
525 });
526
527 _ = editor.update(cx, |editor, window, cx| {
528 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
529 assert_eq!(
530 editor.selections.display_ranges(cx),
531 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
532 );
533 });
534
535 _ = editor.update(cx, |editor, window, cx| {
536 editor.update_selection(
537 DisplayPoint::new(DisplayRow(3), 3),
538 0,
539 gpui::Point::<f32>::default(),
540 window,
541 cx,
542 );
543 assert_eq!(
544 editor.selections.display_ranges(cx),
545 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
546 );
547 });
548
549 _ = editor.update(cx, |editor, window, cx| {
550 editor.cancel(&Cancel, window, cx);
551 editor.update_selection(
552 DisplayPoint::new(DisplayRow(1), 1),
553 0,
554 gpui::Point::<f32>::default(),
555 window,
556 cx,
557 );
558 assert_eq!(
559 editor.selections.display_ranges(cx),
560 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
561 );
562 });
563}
564
565#[gpui::test]
566fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
567 init_test(cx, |_| {});
568
569 let editor = cx.add_window(|window, cx| {
570 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
571 build_editor(buffer, window, cx)
572 });
573
574 _ = editor.update(cx, |editor, window, cx| {
575 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
576 assert_eq!(
577 editor.selections.display_ranges(cx),
578 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
579 );
580
581 editor.move_down(&Default::default(), window, cx);
582 assert_eq!(
583 editor.selections.display_ranges(cx),
584 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
585 );
586
587 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
588 assert_eq!(
589 editor.selections.display_ranges(cx),
590 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
591 );
592
593 editor.move_up(&Default::default(), window, cx);
594 assert_eq!(
595 editor.selections.display_ranges(cx),
596 [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
597 );
598 });
599}
600
601#[gpui::test]
602fn test_clone(cx: &mut TestAppContext) {
603 init_test(cx, |_| {});
604
605 let (text, selection_ranges) = marked_text_ranges(
606 indoc! {"
607 one
608 two
609 threeˇ
610 four
611 fiveˇ
612 "},
613 true,
614 );
615
616 let editor = cx.add_window(|window, cx| {
617 let buffer = MultiBuffer::build_simple(&text, cx);
618 build_editor(buffer, window, cx)
619 });
620
621 _ = editor.update(cx, |editor, window, cx| {
622 editor.change_selections(None, window, cx, |s| {
623 s.select_ranges(selection_ranges.clone())
624 });
625 editor.fold_creases(
626 vec![
627 Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
628 Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
629 ],
630 true,
631 window,
632 cx,
633 );
634 });
635
636 let cloned_editor = editor
637 .update(cx, |editor, _, cx| {
638 cx.open_window(Default::default(), |window, cx| {
639 cx.new(|cx| editor.clone(window, cx))
640 })
641 })
642 .unwrap()
643 .unwrap();
644
645 let snapshot = editor
646 .update(cx, |e, window, cx| e.snapshot(window, cx))
647 .unwrap();
648 let cloned_snapshot = cloned_editor
649 .update(cx, |e, window, cx| e.snapshot(window, cx))
650 .unwrap();
651
652 assert_eq!(
653 cloned_editor
654 .update(cx, |e, _, cx| e.display_text(cx))
655 .unwrap(),
656 editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
657 );
658 assert_eq!(
659 cloned_snapshot
660 .folds_in_range(0..text.len())
661 .collect::<Vec<_>>(),
662 snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
663 );
664 assert_set_eq!(
665 cloned_editor
666 .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
667 .unwrap(),
668 editor
669 .update(cx, |editor, _, cx| editor.selections.ranges(cx))
670 .unwrap()
671 );
672 assert_set_eq!(
673 cloned_editor
674 .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
675 .unwrap(),
676 editor
677 .update(cx, |e, _, cx| e.selections.display_ranges(cx))
678 .unwrap()
679 );
680}
681
682#[gpui::test]
683async fn test_navigation_history(cx: &mut TestAppContext) {
684 init_test(cx, |_| {});
685
686 use workspace::item::Item;
687
688 let fs = FakeFs::new(cx.executor());
689 let project = Project::test(fs, [], cx).await;
690 let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
691 let pane = workspace
692 .update(cx, |workspace, _, _| workspace.active_pane().clone())
693 .unwrap();
694
695 _ = workspace.update(cx, |_v, window, cx| {
696 cx.new(|cx| {
697 let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
698 let mut editor = build_editor(buffer.clone(), window, cx);
699 let handle = cx.entity();
700 editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
701
702 fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
703 editor.nav_history.as_mut().unwrap().pop_backward(cx)
704 }
705
706 // Move the cursor a small distance.
707 // Nothing is added to the navigation history.
708 editor.change_selections(None, window, cx, |s| {
709 s.select_display_ranges([
710 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
711 ])
712 });
713 editor.change_selections(None, window, cx, |s| {
714 s.select_display_ranges([
715 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
716 ])
717 });
718 assert!(pop_history(&mut editor, cx).is_none());
719
720 // Move the cursor a large distance.
721 // The history can jump back to the previous position.
722 editor.change_selections(None, window, cx, |s| {
723 s.select_display_ranges([
724 DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
725 ])
726 });
727 let nav_entry = pop_history(&mut editor, cx).unwrap();
728 editor.navigate(nav_entry.data.unwrap(), window, cx);
729 assert_eq!(nav_entry.item.id(), cx.entity_id());
730 assert_eq!(
731 editor.selections.display_ranges(cx),
732 &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
733 );
734 assert!(pop_history(&mut editor, cx).is_none());
735
736 // Move the cursor a small distance via the mouse.
737 // Nothing is added to the navigation history.
738 editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
739 editor.end_selection(window, cx);
740 assert_eq!(
741 editor.selections.display_ranges(cx),
742 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
743 );
744 assert!(pop_history(&mut editor, cx).is_none());
745
746 // Move the cursor a large distance via the mouse.
747 // The history can jump back to the previous position.
748 editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
749 editor.end_selection(window, cx);
750 assert_eq!(
751 editor.selections.display_ranges(cx),
752 &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
753 );
754 let nav_entry = pop_history(&mut editor, cx).unwrap();
755 editor.navigate(nav_entry.data.unwrap(), window, cx);
756 assert_eq!(nav_entry.item.id(), cx.entity_id());
757 assert_eq!(
758 editor.selections.display_ranges(cx),
759 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
760 );
761 assert!(pop_history(&mut editor, cx).is_none());
762
763 // Set scroll position to check later
764 editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), window, cx);
765 let original_scroll_position = editor.scroll_manager.anchor();
766
767 // Jump to the end of the document and adjust scroll
768 editor.move_to_end(&MoveToEnd, window, cx);
769 editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), window, cx);
770 assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
771
772 let nav_entry = pop_history(&mut editor, cx).unwrap();
773 editor.navigate(nav_entry.data.unwrap(), window, cx);
774 assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
775
776 // Ensure we don't panic when navigation data contains invalid anchors *and* points.
777 let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
778 invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
779 let invalid_point = Point::new(9999, 0);
780 editor.navigate(
781 Box::new(NavigationData {
782 cursor_anchor: invalid_anchor,
783 cursor_position: invalid_point,
784 scroll_anchor: ScrollAnchor {
785 anchor: invalid_anchor,
786 offset: Default::default(),
787 },
788 scroll_top_row: invalid_point.row,
789 }),
790 window,
791 cx,
792 );
793 assert_eq!(
794 editor.selections.display_ranges(cx),
795 &[editor.max_point(cx)..editor.max_point(cx)]
796 );
797 assert_eq!(
798 editor.scroll_position(cx),
799 gpui::Point::new(0., editor.max_point(cx).row().as_f32())
800 );
801
802 editor
803 })
804 });
805}
806
807#[gpui::test]
808fn test_cancel(cx: &mut TestAppContext) {
809 init_test(cx, |_| {});
810
811 let editor = cx.add_window(|window, cx| {
812 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
813 build_editor(buffer, window, cx)
814 });
815
816 _ = editor.update(cx, |editor, window, cx| {
817 editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
818 editor.update_selection(
819 DisplayPoint::new(DisplayRow(1), 1),
820 0,
821 gpui::Point::<f32>::default(),
822 window,
823 cx,
824 );
825 editor.end_selection(window, cx);
826
827 editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
828 editor.update_selection(
829 DisplayPoint::new(DisplayRow(0), 3),
830 0,
831 gpui::Point::<f32>::default(),
832 window,
833 cx,
834 );
835 editor.end_selection(window, cx);
836 assert_eq!(
837 editor.selections.display_ranges(cx),
838 [
839 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
840 DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
841 ]
842 );
843 });
844
845 _ = editor.update(cx, |editor, window, cx| {
846 editor.cancel(&Cancel, window, cx);
847 assert_eq!(
848 editor.selections.display_ranges(cx),
849 [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
850 );
851 });
852
853 _ = editor.update(cx, |editor, window, cx| {
854 editor.cancel(&Cancel, window, cx);
855 assert_eq!(
856 editor.selections.display_ranges(cx),
857 [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
858 );
859 });
860}
861
862#[gpui::test]
863fn test_fold_action(cx: &mut TestAppContext) {
864 init_test(cx, |_| {});
865
866 let editor = cx.add_window(|window, cx| {
867 let buffer = MultiBuffer::build_simple(
868 &"
869 impl Foo {
870 // Hello!
871
872 fn a() {
873 1
874 }
875
876 fn b() {
877 2
878 }
879
880 fn c() {
881 3
882 }
883 }
884 "
885 .unindent(),
886 cx,
887 );
888 build_editor(buffer.clone(), window, cx)
889 });
890
891 _ = editor.update(cx, |editor, window, cx| {
892 editor.change_selections(None, window, cx, |s| {
893 s.select_display_ranges([
894 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
895 ]);
896 });
897 editor.fold(&Fold, window, cx);
898 assert_eq!(
899 editor.display_text(cx),
900 "
901 impl Foo {
902 // Hello!
903
904 fn a() {
905 1
906 }
907
908 fn b() {⋯
909 }
910
911 fn c() {⋯
912 }
913 }
914 "
915 .unindent(),
916 );
917
918 editor.fold(&Fold, window, cx);
919 assert_eq!(
920 editor.display_text(cx),
921 "
922 impl Foo {⋯
923 }
924 "
925 .unindent(),
926 );
927
928 editor.unfold_lines(&UnfoldLines, window, cx);
929 assert_eq!(
930 editor.display_text(cx),
931 "
932 impl Foo {
933 // Hello!
934
935 fn a() {
936 1
937 }
938
939 fn b() {⋯
940 }
941
942 fn c() {⋯
943 }
944 }
945 "
946 .unindent(),
947 );
948
949 editor.unfold_lines(&UnfoldLines, window, cx);
950 assert_eq!(
951 editor.display_text(cx),
952 editor.buffer.read(cx).read(cx).text()
953 );
954 });
955}
956
957#[gpui::test]
958fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
959 init_test(cx, |_| {});
960
961 let editor = cx.add_window(|window, cx| {
962 let buffer = MultiBuffer::build_simple(
963 &"
964 class Foo:
965 # Hello!
966
967 def a():
968 print(1)
969
970 def b():
971 print(2)
972
973 def c():
974 print(3)
975 "
976 .unindent(),
977 cx,
978 );
979 build_editor(buffer.clone(), window, cx)
980 });
981
982 _ = editor.update(cx, |editor, window, cx| {
983 editor.change_selections(None, window, cx, |s| {
984 s.select_display_ranges([
985 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
986 ]);
987 });
988 editor.fold(&Fold, window, cx);
989 assert_eq!(
990 editor.display_text(cx),
991 "
992 class Foo:
993 # Hello!
994
995 def a():
996 print(1)
997
998 def b():⋯
999
1000 def c():⋯
1001 "
1002 .unindent(),
1003 );
1004
1005 editor.fold(&Fold, window, cx);
1006 assert_eq!(
1007 editor.display_text(cx),
1008 "
1009 class Foo:⋯
1010 "
1011 .unindent(),
1012 );
1013
1014 editor.unfold_lines(&UnfoldLines, window, cx);
1015 assert_eq!(
1016 editor.display_text(cx),
1017 "
1018 class Foo:
1019 # Hello!
1020
1021 def a():
1022 print(1)
1023
1024 def b():⋯
1025
1026 def c():⋯
1027 "
1028 .unindent(),
1029 );
1030
1031 editor.unfold_lines(&UnfoldLines, window, cx);
1032 assert_eq!(
1033 editor.display_text(cx),
1034 editor.buffer.read(cx).read(cx).text()
1035 );
1036 });
1037}
1038
1039#[gpui::test]
1040fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
1041 init_test(cx, |_| {});
1042
1043 let editor = cx.add_window(|window, cx| {
1044 let buffer = MultiBuffer::build_simple(
1045 &"
1046 class Foo:
1047 # Hello!
1048
1049 def a():
1050 print(1)
1051
1052 def b():
1053 print(2)
1054
1055
1056 def c():
1057 print(3)
1058
1059
1060 "
1061 .unindent(),
1062 cx,
1063 );
1064 build_editor(buffer.clone(), window, cx)
1065 });
1066
1067 _ = editor.update(cx, |editor, window, cx| {
1068 editor.change_selections(None, window, cx, |s| {
1069 s.select_display_ranges([
1070 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
1071 ]);
1072 });
1073 editor.fold(&Fold, window, cx);
1074 assert_eq!(
1075 editor.display_text(cx),
1076 "
1077 class Foo:
1078 # Hello!
1079
1080 def a():
1081 print(1)
1082
1083 def b():⋯
1084
1085
1086 def c():⋯
1087
1088
1089 "
1090 .unindent(),
1091 );
1092
1093 editor.fold(&Fold, window, cx);
1094 assert_eq!(
1095 editor.display_text(cx),
1096 "
1097 class Foo:⋯
1098
1099
1100 "
1101 .unindent(),
1102 );
1103
1104 editor.unfold_lines(&UnfoldLines, window, cx);
1105 assert_eq!(
1106 editor.display_text(cx),
1107 "
1108 class Foo:
1109 # Hello!
1110
1111 def a():
1112 print(1)
1113
1114 def b():⋯
1115
1116
1117 def c():⋯
1118
1119
1120 "
1121 .unindent(),
1122 );
1123
1124 editor.unfold_lines(&UnfoldLines, window, cx);
1125 assert_eq!(
1126 editor.display_text(cx),
1127 editor.buffer.read(cx).read(cx).text()
1128 );
1129 });
1130}
1131
1132#[gpui::test]
1133fn test_fold_at_level(cx: &mut TestAppContext) {
1134 init_test(cx, |_| {});
1135
1136 let editor = cx.add_window(|window, cx| {
1137 let buffer = MultiBuffer::build_simple(
1138 &"
1139 class Foo:
1140 # Hello!
1141
1142 def a():
1143 print(1)
1144
1145 def b():
1146 print(2)
1147
1148
1149 class Bar:
1150 # World!
1151
1152 def a():
1153 print(1)
1154
1155 def b():
1156 print(2)
1157
1158
1159 "
1160 .unindent(),
1161 cx,
1162 );
1163 build_editor(buffer.clone(), window, cx)
1164 });
1165
1166 _ = editor.update(cx, |editor, window, cx| {
1167 editor.fold_at_level(&FoldAtLevel(2), window, cx);
1168 assert_eq!(
1169 editor.display_text(cx),
1170 "
1171 class Foo:
1172 # Hello!
1173
1174 def a():⋯
1175
1176 def b():⋯
1177
1178
1179 class Bar:
1180 # World!
1181
1182 def a():⋯
1183
1184 def b():⋯
1185
1186
1187 "
1188 .unindent(),
1189 );
1190
1191 editor.fold_at_level(&FoldAtLevel(1), window, cx);
1192 assert_eq!(
1193 editor.display_text(cx),
1194 "
1195 class Foo:⋯
1196
1197
1198 class Bar:⋯
1199
1200
1201 "
1202 .unindent(),
1203 );
1204
1205 editor.unfold_all(&UnfoldAll, window, cx);
1206 editor.fold_at_level(&FoldAtLevel(0), window, cx);
1207 assert_eq!(
1208 editor.display_text(cx),
1209 "
1210 class Foo:
1211 # Hello!
1212
1213 def a():
1214 print(1)
1215
1216 def b():
1217 print(2)
1218
1219
1220 class Bar:
1221 # World!
1222
1223 def a():
1224 print(1)
1225
1226 def b():
1227 print(2)
1228
1229
1230 "
1231 .unindent(),
1232 );
1233
1234 assert_eq!(
1235 editor.display_text(cx),
1236 editor.buffer.read(cx).read(cx).text()
1237 );
1238 });
1239}
1240
1241#[gpui::test]
1242fn test_move_cursor(cx: &mut TestAppContext) {
1243 init_test(cx, |_| {});
1244
1245 let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
1246 let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
1247
1248 buffer.update(cx, |buffer, cx| {
1249 buffer.edit(
1250 vec![
1251 (Point::new(1, 0)..Point::new(1, 0), "\t"),
1252 (Point::new(1, 1)..Point::new(1, 1), "\t"),
1253 ],
1254 None,
1255 cx,
1256 );
1257 });
1258 _ = editor.update(cx, |editor, window, cx| {
1259 assert_eq!(
1260 editor.selections.display_ranges(cx),
1261 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1262 );
1263
1264 editor.move_down(&MoveDown, window, cx);
1265 assert_eq!(
1266 editor.selections.display_ranges(cx),
1267 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1268 );
1269
1270 editor.move_right(&MoveRight, window, cx);
1271 assert_eq!(
1272 editor.selections.display_ranges(cx),
1273 &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
1274 );
1275
1276 editor.move_left(&MoveLeft, window, cx);
1277 assert_eq!(
1278 editor.selections.display_ranges(cx),
1279 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1280 );
1281
1282 editor.move_up(&MoveUp, window, cx);
1283 assert_eq!(
1284 editor.selections.display_ranges(cx),
1285 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1286 );
1287
1288 editor.move_to_end(&MoveToEnd, window, cx);
1289 assert_eq!(
1290 editor.selections.display_ranges(cx),
1291 &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
1292 );
1293
1294 editor.move_to_beginning(&MoveToBeginning, window, cx);
1295 assert_eq!(
1296 editor.selections.display_ranges(cx),
1297 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1298 );
1299
1300 editor.change_selections(None, window, cx, |s| {
1301 s.select_display_ranges([
1302 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
1303 ]);
1304 });
1305 editor.select_to_beginning(&SelectToBeginning, window, cx);
1306 assert_eq!(
1307 editor.selections.display_ranges(cx),
1308 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
1309 );
1310
1311 editor.select_to_end(&SelectToEnd, window, cx);
1312 assert_eq!(
1313 editor.selections.display_ranges(cx),
1314 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
1315 );
1316 });
1317}
1318
1319#[gpui::test]
1320fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
1321 init_test(cx, |_| {});
1322
1323 let editor = cx.add_window(|window, cx| {
1324 let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
1325 build_editor(buffer.clone(), window, cx)
1326 });
1327
1328 assert_eq!('🟥'.len_utf8(), 4);
1329 assert_eq!('α'.len_utf8(), 2);
1330
1331 _ = editor.update(cx, |editor, window, cx| {
1332 editor.fold_creases(
1333 vec![
1334 Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
1335 Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
1336 Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
1337 ],
1338 true,
1339 window,
1340 cx,
1341 );
1342 assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
1343
1344 editor.move_right(&MoveRight, window, cx);
1345 assert_eq!(
1346 editor.selections.display_ranges(cx),
1347 &[empty_range(0, "🟥".len())]
1348 );
1349 editor.move_right(&MoveRight, window, cx);
1350 assert_eq!(
1351 editor.selections.display_ranges(cx),
1352 &[empty_range(0, "🟥🟧".len())]
1353 );
1354 editor.move_right(&MoveRight, window, cx);
1355 assert_eq!(
1356 editor.selections.display_ranges(cx),
1357 &[empty_range(0, "🟥🟧⋯".len())]
1358 );
1359
1360 editor.move_down(&MoveDown, window, cx);
1361 assert_eq!(
1362 editor.selections.display_ranges(cx),
1363 &[empty_range(1, "ab⋯e".len())]
1364 );
1365 editor.move_left(&MoveLeft, window, cx);
1366 assert_eq!(
1367 editor.selections.display_ranges(cx),
1368 &[empty_range(1, "ab⋯".len())]
1369 );
1370 editor.move_left(&MoveLeft, window, cx);
1371 assert_eq!(
1372 editor.selections.display_ranges(cx),
1373 &[empty_range(1, "ab".len())]
1374 );
1375 editor.move_left(&MoveLeft, window, cx);
1376 assert_eq!(
1377 editor.selections.display_ranges(cx),
1378 &[empty_range(1, "a".len())]
1379 );
1380
1381 editor.move_down(&MoveDown, window, cx);
1382 assert_eq!(
1383 editor.selections.display_ranges(cx),
1384 &[empty_range(2, "α".len())]
1385 );
1386 editor.move_right(&MoveRight, window, cx);
1387 assert_eq!(
1388 editor.selections.display_ranges(cx),
1389 &[empty_range(2, "αβ".len())]
1390 );
1391 editor.move_right(&MoveRight, window, cx);
1392 assert_eq!(
1393 editor.selections.display_ranges(cx),
1394 &[empty_range(2, "αβ⋯".len())]
1395 );
1396 editor.move_right(&MoveRight, window, cx);
1397 assert_eq!(
1398 editor.selections.display_ranges(cx),
1399 &[empty_range(2, "αβ⋯ε".len())]
1400 );
1401
1402 editor.move_up(&MoveUp, window, cx);
1403 assert_eq!(
1404 editor.selections.display_ranges(cx),
1405 &[empty_range(1, "ab⋯e".len())]
1406 );
1407 editor.move_down(&MoveDown, window, cx);
1408 assert_eq!(
1409 editor.selections.display_ranges(cx),
1410 &[empty_range(2, "αβ⋯ε".len())]
1411 );
1412 editor.move_up(&MoveUp, window, cx);
1413 assert_eq!(
1414 editor.selections.display_ranges(cx),
1415 &[empty_range(1, "ab⋯e".len())]
1416 );
1417
1418 editor.move_up(&MoveUp, window, cx);
1419 assert_eq!(
1420 editor.selections.display_ranges(cx),
1421 &[empty_range(0, "🟥🟧".len())]
1422 );
1423 editor.move_left(&MoveLeft, window, cx);
1424 assert_eq!(
1425 editor.selections.display_ranges(cx),
1426 &[empty_range(0, "🟥".len())]
1427 );
1428 editor.move_left(&MoveLeft, window, cx);
1429 assert_eq!(
1430 editor.selections.display_ranges(cx),
1431 &[empty_range(0, "".len())]
1432 );
1433 });
1434}
1435
1436#[gpui::test]
1437fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
1438 init_test(cx, |_| {});
1439
1440 let editor = cx.add_window(|window, cx| {
1441 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
1442 build_editor(buffer.clone(), window, cx)
1443 });
1444 _ = editor.update(cx, |editor, window, cx| {
1445 editor.change_selections(None, window, cx, |s| {
1446 s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
1447 });
1448
1449 // moving above start of document should move selection to start of document,
1450 // but the next move down should still be at the original goal_x
1451 editor.move_up(&MoveUp, window, cx);
1452 assert_eq!(
1453 editor.selections.display_ranges(cx),
1454 &[empty_range(0, "".len())]
1455 );
1456
1457 editor.move_down(&MoveDown, window, cx);
1458 assert_eq!(
1459 editor.selections.display_ranges(cx),
1460 &[empty_range(1, "abcd".len())]
1461 );
1462
1463 editor.move_down(&MoveDown, window, cx);
1464 assert_eq!(
1465 editor.selections.display_ranges(cx),
1466 &[empty_range(2, "αβγ".len())]
1467 );
1468
1469 editor.move_down(&MoveDown, window, cx);
1470 assert_eq!(
1471 editor.selections.display_ranges(cx),
1472 &[empty_range(3, "abcd".len())]
1473 );
1474
1475 editor.move_down(&MoveDown, window, cx);
1476 assert_eq!(
1477 editor.selections.display_ranges(cx),
1478 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1479 );
1480
1481 // moving past end of document should not change goal_x
1482 editor.move_down(&MoveDown, window, cx);
1483 assert_eq!(
1484 editor.selections.display_ranges(cx),
1485 &[empty_range(5, "".len())]
1486 );
1487
1488 editor.move_down(&MoveDown, window, cx);
1489 assert_eq!(
1490 editor.selections.display_ranges(cx),
1491 &[empty_range(5, "".len())]
1492 );
1493
1494 editor.move_up(&MoveUp, window, cx);
1495 assert_eq!(
1496 editor.selections.display_ranges(cx),
1497 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1498 );
1499
1500 editor.move_up(&MoveUp, window, cx);
1501 assert_eq!(
1502 editor.selections.display_ranges(cx),
1503 &[empty_range(3, "abcd".len())]
1504 );
1505
1506 editor.move_up(&MoveUp, window, cx);
1507 assert_eq!(
1508 editor.selections.display_ranges(cx),
1509 &[empty_range(2, "αβγ".len())]
1510 );
1511 });
1512}
1513
1514#[gpui::test]
1515fn test_beginning_end_of_line(cx: &mut TestAppContext) {
1516 init_test(cx, |_| {});
1517 let move_to_beg = MoveToBeginningOfLine {
1518 stop_at_soft_wraps: true,
1519 stop_at_indent: true,
1520 };
1521
1522 let delete_to_beg = DeleteToBeginningOfLine {
1523 stop_at_indent: false,
1524 };
1525
1526 let move_to_end = MoveToEndOfLine {
1527 stop_at_soft_wraps: true,
1528 };
1529
1530 let editor = cx.add_window(|window, cx| {
1531 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1532 build_editor(buffer, window, cx)
1533 });
1534 _ = editor.update(cx, |editor, window, cx| {
1535 editor.change_selections(None, window, cx, |s| {
1536 s.select_display_ranges([
1537 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1538 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1539 ]);
1540 });
1541 });
1542
1543 _ = editor.update(cx, |editor, window, cx| {
1544 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1545 assert_eq!(
1546 editor.selections.display_ranges(cx),
1547 &[
1548 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1549 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1550 ]
1551 );
1552 });
1553
1554 _ = editor.update(cx, |editor, window, cx| {
1555 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1556 assert_eq!(
1557 editor.selections.display_ranges(cx),
1558 &[
1559 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1560 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1561 ]
1562 );
1563 });
1564
1565 _ = editor.update(cx, |editor, window, cx| {
1566 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1567 assert_eq!(
1568 editor.selections.display_ranges(cx),
1569 &[
1570 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1571 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1572 ]
1573 );
1574 });
1575
1576 _ = editor.update(cx, |editor, window, cx| {
1577 editor.move_to_end_of_line(&move_to_end, window, cx);
1578 assert_eq!(
1579 editor.selections.display_ranges(cx),
1580 &[
1581 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1582 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1583 ]
1584 );
1585 });
1586
1587 // Moving to the end of line again is a no-op.
1588 _ = editor.update(cx, |editor, window, cx| {
1589 editor.move_to_end_of_line(&move_to_end, window, cx);
1590 assert_eq!(
1591 editor.selections.display_ranges(cx),
1592 &[
1593 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1594 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1595 ]
1596 );
1597 });
1598
1599 _ = editor.update(cx, |editor, window, cx| {
1600 editor.move_left(&MoveLeft, window, cx);
1601 editor.select_to_beginning_of_line(
1602 &SelectToBeginningOfLine {
1603 stop_at_soft_wraps: true,
1604 stop_at_indent: true,
1605 },
1606 window,
1607 cx,
1608 );
1609 assert_eq!(
1610 editor.selections.display_ranges(cx),
1611 &[
1612 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1613 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1614 ]
1615 );
1616 });
1617
1618 _ = editor.update(cx, |editor, window, cx| {
1619 editor.select_to_beginning_of_line(
1620 &SelectToBeginningOfLine {
1621 stop_at_soft_wraps: true,
1622 stop_at_indent: true,
1623 },
1624 window,
1625 cx,
1626 );
1627 assert_eq!(
1628 editor.selections.display_ranges(cx),
1629 &[
1630 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1631 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1632 ]
1633 );
1634 });
1635
1636 _ = editor.update(cx, |editor, window, cx| {
1637 editor.select_to_beginning_of_line(
1638 &SelectToBeginningOfLine {
1639 stop_at_soft_wraps: true,
1640 stop_at_indent: true,
1641 },
1642 window,
1643 cx,
1644 );
1645 assert_eq!(
1646 editor.selections.display_ranges(cx),
1647 &[
1648 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1649 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1650 ]
1651 );
1652 });
1653
1654 _ = editor.update(cx, |editor, window, cx| {
1655 editor.select_to_end_of_line(
1656 &SelectToEndOfLine {
1657 stop_at_soft_wraps: true,
1658 },
1659 window,
1660 cx,
1661 );
1662 assert_eq!(
1663 editor.selections.display_ranges(cx),
1664 &[
1665 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
1666 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
1667 ]
1668 );
1669 });
1670
1671 _ = editor.update(cx, |editor, window, cx| {
1672 editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
1673 assert_eq!(editor.display_text(cx), "ab\n de");
1674 assert_eq!(
1675 editor.selections.display_ranges(cx),
1676 &[
1677 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
1678 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1679 ]
1680 );
1681 });
1682
1683 _ = editor.update(cx, |editor, window, cx| {
1684 editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
1685 assert_eq!(editor.display_text(cx), "\n");
1686 assert_eq!(
1687 editor.selections.display_ranges(cx),
1688 &[
1689 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1690 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1691 ]
1692 );
1693 });
1694}
1695
1696#[gpui::test]
1697fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
1698 init_test(cx, |_| {});
1699 let move_to_beg = MoveToBeginningOfLine {
1700 stop_at_soft_wraps: false,
1701 stop_at_indent: false,
1702 };
1703
1704 let move_to_end = MoveToEndOfLine {
1705 stop_at_soft_wraps: false,
1706 };
1707
1708 let editor = cx.add_window(|window, cx| {
1709 let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
1710 build_editor(buffer, window, cx)
1711 });
1712
1713 _ = editor.update(cx, |editor, window, cx| {
1714 editor.set_wrap_width(Some(140.0.into()), cx);
1715
1716 // We expect the following lines after wrapping
1717 // ```
1718 // thequickbrownfox
1719 // jumpedoverthelazydo
1720 // gs
1721 // ```
1722 // The final `gs` was soft-wrapped onto a new line.
1723 assert_eq!(
1724 "thequickbrownfox\njumpedoverthelaz\nydogs",
1725 editor.display_text(cx),
1726 );
1727
1728 // First, let's assert behavior on the first line, that was not soft-wrapped.
1729 // Start the cursor at the `k` on the first line
1730 editor.change_selections(None, window, cx, |s| {
1731 s.select_display_ranges([
1732 DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
1733 ]);
1734 });
1735
1736 // Moving to the beginning of the line should put us at the beginning of the line.
1737 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1738 assert_eq!(
1739 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
1740 editor.selections.display_ranges(cx)
1741 );
1742
1743 // Moving to the end of the line should put us at the end of the line.
1744 editor.move_to_end_of_line(&move_to_end, window, cx);
1745 assert_eq!(
1746 vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
1747 editor.selections.display_ranges(cx)
1748 );
1749
1750 // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
1751 // Start the cursor at the last line (`y` that was wrapped to a new line)
1752 editor.change_selections(None, window, cx, |s| {
1753 s.select_display_ranges([
1754 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
1755 ]);
1756 });
1757
1758 // Moving to the beginning of the line should put us at the start of the second line of
1759 // display text, i.e., the `j`.
1760 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1761 assert_eq!(
1762 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1763 editor.selections.display_ranges(cx)
1764 );
1765
1766 // Moving to the beginning of the line again should be a no-op.
1767 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1768 assert_eq!(
1769 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1770 editor.selections.display_ranges(cx)
1771 );
1772
1773 // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
1774 // next display line.
1775 editor.move_to_end_of_line(&move_to_end, window, cx);
1776 assert_eq!(
1777 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1778 editor.selections.display_ranges(cx)
1779 );
1780
1781 // Moving to the end of the line again should be a no-op.
1782 editor.move_to_end_of_line(&move_to_end, window, cx);
1783 assert_eq!(
1784 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1785 editor.selections.display_ranges(cx)
1786 );
1787 });
1788}
1789
1790#[gpui::test]
1791fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
1792 init_test(cx, |_| {});
1793
1794 let move_to_beg = MoveToBeginningOfLine {
1795 stop_at_soft_wraps: true,
1796 stop_at_indent: true,
1797 };
1798
1799 let select_to_beg = SelectToBeginningOfLine {
1800 stop_at_soft_wraps: true,
1801 stop_at_indent: true,
1802 };
1803
1804 let delete_to_beg = DeleteToBeginningOfLine {
1805 stop_at_indent: true,
1806 };
1807
1808 let move_to_end = MoveToEndOfLine {
1809 stop_at_soft_wraps: false,
1810 };
1811
1812 let editor = cx.add_window(|window, cx| {
1813 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1814 build_editor(buffer, window, cx)
1815 });
1816
1817 _ = editor.update(cx, |editor, window, cx| {
1818 editor.change_selections(None, window, cx, |s| {
1819 s.select_display_ranges([
1820 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1821 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1822 ]);
1823 });
1824
1825 // Moving to the beginning of the line should put the first cursor at the beginning of the line,
1826 // and the second cursor at the first non-whitespace character in the line.
1827 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1828 assert_eq!(
1829 editor.selections.display_ranges(cx),
1830 &[
1831 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1832 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1833 ]
1834 );
1835
1836 // Moving to the beginning of the line again should be a no-op for the first cursor,
1837 // and should move the second cursor to the beginning of the line.
1838 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1839 assert_eq!(
1840 editor.selections.display_ranges(cx),
1841 &[
1842 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1843 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1844 ]
1845 );
1846
1847 // Moving to the beginning of the line again should still be a no-op for the first cursor,
1848 // and should move the second cursor back to the first non-whitespace character in the line.
1849 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1850 assert_eq!(
1851 editor.selections.display_ranges(cx),
1852 &[
1853 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1854 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1855 ]
1856 );
1857
1858 // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
1859 // and to the first non-whitespace character in the line for the second cursor.
1860 editor.move_to_end_of_line(&move_to_end, window, cx);
1861 editor.move_left(&MoveLeft, window, cx);
1862 editor.select_to_beginning_of_line(&select_to_beg, window, cx);
1863 assert_eq!(
1864 editor.selections.display_ranges(cx),
1865 &[
1866 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1867 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1868 ]
1869 );
1870
1871 // Selecting to the beginning of the line again should be a no-op for the first cursor,
1872 // and should select to the beginning of the line for the second cursor.
1873 editor.select_to_beginning_of_line(&select_to_beg, window, cx);
1874 assert_eq!(
1875 editor.selections.display_ranges(cx),
1876 &[
1877 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1878 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1879 ]
1880 );
1881
1882 // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
1883 // and should delete to the first non-whitespace character in the line for the second cursor.
1884 editor.move_to_end_of_line(&move_to_end, window, cx);
1885 editor.move_left(&MoveLeft, window, cx);
1886 editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
1887 assert_eq!(editor.text(cx), "c\n f");
1888 });
1889}
1890
1891#[gpui::test]
1892fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1893 init_test(cx, |_| {});
1894
1895 let editor = cx.add_window(|window, cx| {
1896 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1897 build_editor(buffer, window, cx)
1898 });
1899 _ = editor.update(cx, |editor, window, cx| {
1900 editor.change_selections(None, window, cx, |s| {
1901 s.select_display_ranges([
1902 DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
1903 DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
1904 ])
1905 });
1906
1907 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1908 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", editor, cx);
1909
1910 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1911 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", editor, cx);
1912
1913 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1914 assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", editor, cx);
1915
1916 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1917 assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", editor, cx);
1918
1919 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1920 assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", editor, cx);
1921
1922 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1923 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", editor, cx);
1924
1925 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1926 assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", editor, cx);
1927
1928 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1929 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", editor, cx);
1930
1931 editor.move_right(&MoveRight, window, cx);
1932 editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
1933 assert_selection_ranges(
1934 "use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}",
1935 editor,
1936 cx,
1937 );
1938
1939 editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
1940 assert_selection_ranges(
1941 "use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}",
1942 editor,
1943 cx,
1944 );
1945
1946 editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
1947 assert_selection_ranges(
1948 "use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}",
1949 editor,
1950 cx,
1951 );
1952 });
1953}
1954
1955#[gpui::test]
1956fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1957 init_test(cx, |_| {});
1958
1959 let editor = cx.add_window(|window, cx| {
1960 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1961 build_editor(buffer, window, cx)
1962 });
1963
1964 _ = editor.update(cx, |editor, window, cx| {
1965 editor.set_wrap_width(Some(140.0.into()), cx);
1966 assert_eq!(
1967 editor.display_text(cx),
1968 "use one::{\n two::three::\n four::five\n};"
1969 );
1970
1971 editor.change_selections(None, window, cx, |s| {
1972 s.select_display_ranges([
1973 DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
1974 ]);
1975 });
1976
1977 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1978 assert_eq!(
1979 editor.selections.display_ranges(cx),
1980 &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
1981 );
1982
1983 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1984 assert_eq!(
1985 editor.selections.display_ranges(cx),
1986 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1987 );
1988
1989 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1990 assert_eq!(
1991 editor.selections.display_ranges(cx),
1992 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1993 );
1994
1995 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1996 assert_eq!(
1997 editor.selections.display_ranges(cx),
1998 &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
1999 );
2000
2001 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
2002 assert_eq!(
2003 editor.selections.display_ranges(cx),
2004 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
2005 );
2006
2007 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
2008 assert_eq!(
2009 editor.selections.display_ranges(cx),
2010 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
2011 );
2012 });
2013}
2014
2015#[gpui::test]
2016async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
2017 init_test(cx, |_| {});
2018 let mut cx = EditorTestContext::new(cx).await;
2019
2020 let line_height = cx.editor(|editor, window, _| {
2021 editor
2022 .style()
2023 .unwrap()
2024 .text
2025 .line_height_in_pixels(window.rem_size())
2026 });
2027 cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
2028
2029 cx.set_state(
2030 &r#"ˇone
2031 two
2032
2033 three
2034 fourˇ
2035 five
2036
2037 six"#
2038 .unindent(),
2039 );
2040
2041 cx.update_editor(|editor, window, cx| {
2042 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
2043 });
2044 cx.assert_editor_state(
2045 &r#"one
2046 two
2047 ˇ
2048 three
2049 four
2050 five
2051 ˇ
2052 six"#
2053 .unindent(),
2054 );
2055
2056 cx.update_editor(|editor, window, cx| {
2057 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
2058 });
2059 cx.assert_editor_state(
2060 &r#"one
2061 two
2062
2063 three
2064 four
2065 five
2066 ˇ
2067 sixˇ"#
2068 .unindent(),
2069 );
2070
2071 cx.update_editor(|editor, window, cx| {
2072 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
2073 });
2074 cx.assert_editor_state(
2075 &r#"one
2076 two
2077
2078 three
2079 four
2080 five
2081
2082 sixˇ"#
2083 .unindent(),
2084 );
2085
2086 cx.update_editor(|editor, window, cx| {
2087 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
2088 });
2089 cx.assert_editor_state(
2090 &r#"one
2091 two
2092
2093 three
2094 four
2095 five
2096 ˇ
2097 six"#
2098 .unindent(),
2099 );
2100
2101 cx.update_editor(|editor, window, cx| {
2102 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
2103 });
2104 cx.assert_editor_state(
2105 &r#"one
2106 two
2107 ˇ
2108 three
2109 four
2110 five
2111
2112 six"#
2113 .unindent(),
2114 );
2115
2116 cx.update_editor(|editor, window, cx| {
2117 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
2118 });
2119 cx.assert_editor_state(
2120 &r#"ˇone
2121 two
2122
2123 three
2124 four
2125 five
2126
2127 six"#
2128 .unindent(),
2129 );
2130}
2131
2132#[gpui::test]
2133async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
2134 init_test(cx, |_| {});
2135 let mut cx = EditorTestContext::new(cx).await;
2136 let line_height = cx.editor(|editor, window, _| {
2137 editor
2138 .style()
2139 .unwrap()
2140 .text
2141 .line_height_in_pixels(window.rem_size())
2142 });
2143 let window = cx.window;
2144 cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
2145
2146 cx.set_state(
2147 r#"ˇone
2148 two
2149 three
2150 four
2151 five
2152 six
2153 seven
2154 eight
2155 nine
2156 ten
2157 "#,
2158 );
2159
2160 cx.update_editor(|editor, window, cx| {
2161 assert_eq!(
2162 editor.snapshot(window, cx).scroll_position(),
2163 gpui::Point::new(0., 0.)
2164 );
2165 editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
2166 assert_eq!(
2167 editor.snapshot(window, cx).scroll_position(),
2168 gpui::Point::new(0., 3.)
2169 );
2170 editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
2171 assert_eq!(
2172 editor.snapshot(window, cx).scroll_position(),
2173 gpui::Point::new(0., 6.)
2174 );
2175 editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
2176 assert_eq!(
2177 editor.snapshot(window, cx).scroll_position(),
2178 gpui::Point::new(0., 3.)
2179 );
2180
2181 editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
2182 assert_eq!(
2183 editor.snapshot(window, cx).scroll_position(),
2184 gpui::Point::new(0., 1.)
2185 );
2186 editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
2187 assert_eq!(
2188 editor.snapshot(window, cx).scroll_position(),
2189 gpui::Point::new(0., 3.)
2190 );
2191 });
2192}
2193
2194#[gpui::test]
2195async fn test_autoscroll(cx: &mut TestAppContext) {
2196 init_test(cx, |_| {});
2197 let mut cx = EditorTestContext::new(cx).await;
2198
2199 let line_height = cx.update_editor(|editor, window, cx| {
2200 editor.set_vertical_scroll_margin(2, cx);
2201 editor
2202 .style()
2203 .unwrap()
2204 .text
2205 .line_height_in_pixels(window.rem_size())
2206 });
2207 let window = cx.window;
2208 cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
2209
2210 cx.set_state(
2211 r#"ˇone
2212 two
2213 three
2214 four
2215 five
2216 six
2217 seven
2218 eight
2219 nine
2220 ten
2221 "#,
2222 );
2223 cx.update_editor(|editor, window, cx| {
2224 assert_eq!(
2225 editor.snapshot(window, cx).scroll_position(),
2226 gpui::Point::new(0., 0.0)
2227 );
2228 });
2229
2230 // Add a cursor below the visible area. Since both cursors cannot fit
2231 // on screen, the editor autoscrolls to reveal the newest cursor, and
2232 // allows the vertical scroll margin below that cursor.
2233 cx.update_editor(|editor, window, cx| {
2234 editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
2235 selections.select_ranges([
2236 Point::new(0, 0)..Point::new(0, 0),
2237 Point::new(6, 0)..Point::new(6, 0),
2238 ]);
2239 })
2240 });
2241 cx.update_editor(|editor, window, cx| {
2242 assert_eq!(
2243 editor.snapshot(window, cx).scroll_position(),
2244 gpui::Point::new(0., 3.0)
2245 );
2246 });
2247
2248 // Move down. The editor cursor scrolls down to track the newest cursor.
2249 cx.update_editor(|editor, window, cx| {
2250 editor.move_down(&Default::default(), window, cx);
2251 });
2252 cx.update_editor(|editor, window, cx| {
2253 assert_eq!(
2254 editor.snapshot(window, cx).scroll_position(),
2255 gpui::Point::new(0., 4.0)
2256 );
2257 });
2258
2259 // Add a cursor above the visible area. Since both cursors fit on screen,
2260 // the editor scrolls to show both.
2261 cx.update_editor(|editor, window, cx| {
2262 editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
2263 selections.select_ranges([
2264 Point::new(1, 0)..Point::new(1, 0),
2265 Point::new(6, 0)..Point::new(6, 0),
2266 ]);
2267 })
2268 });
2269 cx.update_editor(|editor, window, cx| {
2270 assert_eq!(
2271 editor.snapshot(window, cx).scroll_position(),
2272 gpui::Point::new(0., 1.0)
2273 );
2274 });
2275}
2276
2277#[gpui::test]
2278async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
2279 init_test(cx, |_| {});
2280 let mut cx = EditorTestContext::new(cx).await;
2281
2282 let line_height = cx.editor(|editor, window, _cx| {
2283 editor
2284 .style()
2285 .unwrap()
2286 .text
2287 .line_height_in_pixels(window.rem_size())
2288 });
2289 let window = cx.window;
2290 cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
2291 cx.set_state(
2292 &r#"
2293 ˇone
2294 two
2295 threeˇ
2296 four
2297 five
2298 six
2299 seven
2300 eight
2301 nine
2302 ten
2303 "#
2304 .unindent(),
2305 );
2306
2307 cx.update_editor(|editor, window, cx| {
2308 editor.move_page_down(&MovePageDown::default(), window, cx)
2309 });
2310 cx.assert_editor_state(
2311 &r#"
2312 one
2313 two
2314 three
2315 ˇfour
2316 five
2317 sixˇ
2318 seven
2319 eight
2320 nine
2321 ten
2322 "#
2323 .unindent(),
2324 );
2325
2326 cx.update_editor(|editor, window, cx| {
2327 editor.move_page_down(&MovePageDown::default(), window, cx)
2328 });
2329 cx.assert_editor_state(
2330 &r#"
2331 one
2332 two
2333 three
2334 four
2335 five
2336 six
2337 ˇseven
2338 eight
2339 nineˇ
2340 ten
2341 "#
2342 .unindent(),
2343 );
2344
2345 cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
2346 cx.assert_editor_state(
2347 &r#"
2348 one
2349 two
2350 three
2351 ˇfour
2352 five
2353 sixˇ
2354 seven
2355 eight
2356 nine
2357 ten
2358 "#
2359 .unindent(),
2360 );
2361
2362 cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
2363 cx.assert_editor_state(
2364 &r#"
2365 ˇone
2366 two
2367 threeˇ
2368 four
2369 five
2370 six
2371 seven
2372 eight
2373 nine
2374 ten
2375 "#
2376 .unindent(),
2377 );
2378
2379 // Test select collapsing
2380 cx.update_editor(|editor, window, cx| {
2381 editor.move_page_down(&MovePageDown::default(), window, cx);
2382 editor.move_page_down(&MovePageDown::default(), window, cx);
2383 editor.move_page_down(&MovePageDown::default(), window, cx);
2384 });
2385 cx.assert_editor_state(
2386 &r#"
2387 one
2388 two
2389 three
2390 four
2391 five
2392 six
2393 seven
2394 eight
2395 nine
2396 ˇten
2397 ˇ"#
2398 .unindent(),
2399 );
2400}
2401
2402#[gpui::test]
2403async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
2404 init_test(cx, |_| {});
2405 let mut cx = EditorTestContext::new(cx).await;
2406 cx.set_state("one «two threeˇ» four");
2407 cx.update_editor(|editor, window, cx| {
2408 editor.delete_to_beginning_of_line(
2409 &DeleteToBeginningOfLine {
2410 stop_at_indent: false,
2411 },
2412 window,
2413 cx,
2414 );
2415 assert_eq!(editor.text(cx), " four");
2416 });
2417}
2418
2419#[gpui::test]
2420fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
2421 init_test(cx, |_| {});
2422
2423 let editor = cx.add_window(|window, cx| {
2424 let buffer = MultiBuffer::build_simple("one two three four", cx);
2425 build_editor(buffer.clone(), window, cx)
2426 });
2427
2428 _ = editor.update(cx, |editor, window, cx| {
2429 editor.change_selections(None, window, cx, |s| {
2430 s.select_display_ranges([
2431 // an empty selection - the preceding word fragment is deleted
2432 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2433 // characters selected - they are deleted
2434 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
2435 ])
2436 });
2437 editor.delete_to_previous_word_start(
2438 &DeleteToPreviousWordStart {
2439 ignore_newlines: false,
2440 },
2441 window,
2442 cx,
2443 );
2444 assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
2445 });
2446
2447 _ = editor.update(cx, |editor, window, cx| {
2448 editor.change_selections(None, window, cx, |s| {
2449 s.select_display_ranges([
2450 // an empty selection - the following word fragment is deleted
2451 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
2452 // characters selected - they are deleted
2453 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
2454 ])
2455 });
2456 editor.delete_to_next_word_end(
2457 &DeleteToNextWordEnd {
2458 ignore_newlines: false,
2459 },
2460 window,
2461 cx,
2462 );
2463 assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
2464 });
2465}
2466
2467#[gpui::test]
2468fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
2469 init_test(cx, |_| {});
2470
2471 let editor = cx.add_window(|window, cx| {
2472 let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
2473 build_editor(buffer.clone(), window, cx)
2474 });
2475 let del_to_prev_word_start = DeleteToPreviousWordStart {
2476 ignore_newlines: false,
2477 };
2478 let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
2479 ignore_newlines: true,
2480 };
2481
2482 _ = editor.update(cx, |editor, window, cx| {
2483 editor.change_selections(None, window, cx, |s| {
2484 s.select_display_ranges([
2485 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
2486 ])
2487 });
2488 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2489 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
2490 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2491 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
2492 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2493 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
2494 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2495 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
2496 editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
2497 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
2498 editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
2499 assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
2500 });
2501}
2502
2503#[gpui::test]
2504fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
2505 init_test(cx, |_| {});
2506
2507 let editor = cx.add_window(|window, cx| {
2508 let buffer = MultiBuffer::build_simple("\none\n two\nthree\n four", cx);
2509 build_editor(buffer.clone(), window, cx)
2510 });
2511 let del_to_next_word_end = DeleteToNextWordEnd {
2512 ignore_newlines: false,
2513 };
2514 let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
2515 ignore_newlines: true,
2516 };
2517
2518 _ = editor.update(cx, |editor, window, cx| {
2519 editor.change_selections(None, window, cx, |s| {
2520 s.select_display_ranges([
2521 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
2522 ])
2523 });
2524 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2525 assert_eq!(
2526 editor.buffer.read(cx).read(cx).text(),
2527 "one\n two\nthree\n four"
2528 );
2529 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2530 assert_eq!(
2531 editor.buffer.read(cx).read(cx).text(),
2532 "\n two\nthree\n four"
2533 );
2534 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2535 assert_eq!(
2536 editor.buffer.read(cx).read(cx).text(),
2537 "two\nthree\n four"
2538 );
2539 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2540 assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n four");
2541 editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
2542 assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n four");
2543 editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
2544 assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
2545 });
2546}
2547
2548#[gpui::test]
2549fn test_newline(cx: &mut TestAppContext) {
2550 init_test(cx, |_| {});
2551
2552 let editor = cx.add_window(|window, cx| {
2553 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
2554 build_editor(buffer.clone(), window, cx)
2555 });
2556
2557 _ = editor.update(cx, |editor, window, cx| {
2558 editor.change_selections(None, window, cx, |s| {
2559 s.select_display_ranges([
2560 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2561 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
2562 DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
2563 ])
2564 });
2565
2566 editor.newline(&Newline, window, cx);
2567 assert_eq!(editor.text(cx), "aa\naa\n \n bb\n bb\n");
2568 });
2569}
2570
2571#[gpui::test]
2572fn test_newline_with_old_selections(cx: &mut TestAppContext) {
2573 init_test(cx, |_| {});
2574
2575 let editor = cx.add_window(|window, cx| {
2576 let buffer = MultiBuffer::build_simple(
2577 "
2578 a
2579 b(
2580 X
2581 )
2582 c(
2583 X
2584 )
2585 "
2586 .unindent()
2587 .as_str(),
2588 cx,
2589 );
2590 let mut editor = build_editor(buffer.clone(), window, cx);
2591 editor.change_selections(None, window, cx, |s| {
2592 s.select_ranges([
2593 Point::new(2, 4)..Point::new(2, 5),
2594 Point::new(5, 4)..Point::new(5, 5),
2595 ])
2596 });
2597 editor
2598 });
2599
2600 _ = editor.update(cx, |editor, window, cx| {
2601 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2602 editor.buffer.update(cx, |buffer, cx| {
2603 buffer.edit(
2604 [
2605 (Point::new(1, 2)..Point::new(3, 0), ""),
2606 (Point::new(4, 2)..Point::new(6, 0), ""),
2607 ],
2608 None,
2609 cx,
2610 );
2611 assert_eq!(
2612 buffer.read(cx).text(),
2613 "
2614 a
2615 b()
2616 c()
2617 "
2618 .unindent()
2619 );
2620 });
2621 assert_eq!(
2622 editor.selections.ranges(cx),
2623 &[
2624 Point::new(1, 2)..Point::new(1, 2),
2625 Point::new(2, 2)..Point::new(2, 2),
2626 ],
2627 );
2628
2629 editor.newline(&Newline, window, cx);
2630 assert_eq!(
2631 editor.text(cx),
2632 "
2633 a
2634 b(
2635 )
2636 c(
2637 )
2638 "
2639 .unindent()
2640 );
2641
2642 // The selections are moved after the inserted newlines
2643 assert_eq!(
2644 editor.selections.ranges(cx),
2645 &[
2646 Point::new(2, 0)..Point::new(2, 0),
2647 Point::new(4, 0)..Point::new(4, 0),
2648 ],
2649 );
2650 });
2651}
2652
2653#[gpui::test]
2654async fn test_newline_above(cx: &mut TestAppContext) {
2655 init_test(cx, |settings| {
2656 settings.defaults.tab_size = NonZeroU32::new(4)
2657 });
2658
2659 let language = Arc::new(
2660 Language::new(
2661 LanguageConfig::default(),
2662 Some(tree_sitter_rust::LANGUAGE.into()),
2663 )
2664 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2665 .unwrap(),
2666 );
2667
2668 let mut cx = EditorTestContext::new(cx).await;
2669 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2670 cx.set_state(indoc! {"
2671 const a: ˇA = (
2672 (ˇ
2673 «const_functionˇ»(ˇ),
2674 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2675 )ˇ
2676 ˇ);ˇ
2677 "});
2678
2679 cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
2680 cx.assert_editor_state(indoc! {"
2681 ˇ
2682 const a: A = (
2683 ˇ
2684 (
2685 ˇ
2686 ˇ
2687 const_function(),
2688 ˇ
2689 ˇ
2690 ˇ
2691 ˇ
2692 something_else,
2693 ˇ
2694 )
2695 ˇ
2696 ˇ
2697 );
2698 "});
2699}
2700
2701#[gpui::test]
2702async fn test_newline_below(cx: &mut TestAppContext) {
2703 init_test(cx, |settings| {
2704 settings.defaults.tab_size = NonZeroU32::new(4)
2705 });
2706
2707 let language = Arc::new(
2708 Language::new(
2709 LanguageConfig::default(),
2710 Some(tree_sitter_rust::LANGUAGE.into()),
2711 )
2712 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2713 .unwrap(),
2714 );
2715
2716 let mut cx = EditorTestContext::new(cx).await;
2717 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2718 cx.set_state(indoc! {"
2719 const a: ˇA = (
2720 (ˇ
2721 «const_functionˇ»(ˇ),
2722 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2723 )ˇ
2724 ˇ);ˇ
2725 "});
2726
2727 cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
2728 cx.assert_editor_state(indoc! {"
2729 const a: A = (
2730 ˇ
2731 (
2732 ˇ
2733 const_function(),
2734 ˇ
2735 ˇ
2736 something_else,
2737 ˇ
2738 ˇ
2739 ˇ
2740 ˇ
2741 )
2742 ˇ
2743 );
2744 ˇ
2745 ˇ
2746 "});
2747}
2748
2749#[gpui::test]
2750async fn test_newline_comments(cx: &mut TestAppContext) {
2751 init_test(cx, |settings| {
2752 settings.defaults.tab_size = NonZeroU32::new(4)
2753 });
2754
2755 let language = Arc::new(Language::new(
2756 LanguageConfig {
2757 line_comments: vec!["//".into()],
2758 ..LanguageConfig::default()
2759 },
2760 None,
2761 ));
2762 {
2763 let mut cx = EditorTestContext::new(cx).await;
2764 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2765 cx.set_state(indoc! {"
2766 // Fooˇ
2767 "});
2768
2769 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2770 cx.assert_editor_state(indoc! {"
2771 // Foo
2772 //ˇ
2773 "});
2774 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
2775 cx.set_state(indoc! {"
2776 ˇ// Foo
2777 "});
2778 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2779 cx.assert_editor_state(indoc! {"
2780
2781 ˇ// Foo
2782 "});
2783 }
2784 // Ensure that comment continuations can be disabled.
2785 update_test_language_settings(cx, |settings| {
2786 settings.defaults.extend_comment_on_newline = Some(false);
2787 });
2788 let mut cx = EditorTestContext::new(cx).await;
2789 cx.set_state(indoc! {"
2790 // Fooˇ
2791 "});
2792 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2793 cx.assert_editor_state(indoc! {"
2794 // Foo
2795 ˇ
2796 "});
2797}
2798
2799#[gpui::test]
2800fn test_insert_with_old_selections(cx: &mut TestAppContext) {
2801 init_test(cx, |_| {});
2802
2803 let editor = cx.add_window(|window, cx| {
2804 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
2805 let mut editor = build_editor(buffer.clone(), window, cx);
2806 editor.change_selections(None, window, cx, |s| {
2807 s.select_ranges([3..4, 11..12, 19..20])
2808 });
2809 editor
2810 });
2811
2812 _ = editor.update(cx, |editor, window, cx| {
2813 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2814 editor.buffer.update(cx, |buffer, cx| {
2815 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
2816 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
2817 });
2818 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
2819
2820 editor.insert("Z", window, cx);
2821 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
2822
2823 // The selections are moved after the inserted characters
2824 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
2825 });
2826}
2827
2828#[gpui::test]
2829async fn test_tab(cx: &mut TestAppContext) {
2830 init_test(cx, |settings| {
2831 settings.defaults.tab_size = NonZeroU32::new(3)
2832 });
2833
2834 let mut cx = EditorTestContext::new(cx).await;
2835 cx.set_state(indoc! {"
2836 ˇabˇc
2837 ˇ🏀ˇ🏀ˇefg
2838 dˇ
2839 "});
2840 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2841 cx.assert_editor_state(indoc! {"
2842 ˇab ˇc
2843 ˇ🏀 ˇ🏀 ˇefg
2844 d ˇ
2845 "});
2846
2847 cx.set_state(indoc! {"
2848 a
2849 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2850 "});
2851 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2852 cx.assert_editor_state(indoc! {"
2853 a
2854 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2855 "});
2856}
2857
2858#[gpui::test]
2859async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
2860 init_test(cx, |_| {});
2861
2862 let mut cx = EditorTestContext::new(cx).await;
2863 let language = Arc::new(
2864 Language::new(
2865 LanguageConfig::default(),
2866 Some(tree_sitter_rust::LANGUAGE.into()),
2867 )
2868 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2869 .unwrap(),
2870 );
2871 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2872
2873 // cursors that are already at the suggested indent level insert
2874 // a soft tab. cursors that are to the left of the suggested indent
2875 // auto-indent their line.
2876 cx.set_state(indoc! {"
2877 ˇ
2878 const a: B = (
2879 c(
2880 d(
2881 ˇ
2882 )
2883 ˇ
2884 ˇ )
2885 );
2886 "});
2887 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2888 cx.assert_editor_state(indoc! {"
2889 ˇ
2890 const a: B = (
2891 c(
2892 d(
2893 ˇ
2894 )
2895 ˇ
2896 ˇ)
2897 );
2898 "});
2899
2900 // handle auto-indent when there are multiple cursors on the same line
2901 cx.set_state(indoc! {"
2902 const a: B = (
2903 c(
2904 ˇ ˇ
2905 ˇ )
2906 );
2907 "});
2908 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2909 cx.assert_editor_state(indoc! {"
2910 const a: B = (
2911 c(
2912 ˇ
2913 ˇ)
2914 );
2915 "});
2916}
2917
2918#[gpui::test]
2919async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
2920 init_test(cx, |settings| {
2921 settings.defaults.tab_size = NonZeroU32::new(3)
2922 });
2923
2924 let mut cx = EditorTestContext::new(cx).await;
2925 cx.set_state(indoc! {"
2926 ˇ
2927 \t ˇ
2928 \t ˇ
2929 \t ˇ
2930 \t \t\t \t \t\t \t\t \t \t ˇ
2931 "});
2932
2933 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2934 cx.assert_editor_state(indoc! {"
2935 ˇ
2936 \t ˇ
2937 \t ˇ
2938 \t ˇ
2939 \t \t\t \t \t\t \t\t \t \t ˇ
2940 "});
2941}
2942
2943#[gpui::test]
2944async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
2945 init_test(cx, |settings| {
2946 settings.defaults.tab_size = NonZeroU32::new(4)
2947 });
2948
2949 let language = Arc::new(
2950 Language::new(
2951 LanguageConfig::default(),
2952 Some(tree_sitter_rust::LANGUAGE.into()),
2953 )
2954 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2955 .unwrap(),
2956 );
2957
2958 let mut cx = EditorTestContext::new(cx).await;
2959 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2960 cx.set_state(indoc! {"
2961 fn a() {
2962 if b {
2963 \t ˇc
2964 }
2965 }
2966 "});
2967
2968 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2969 cx.assert_editor_state(indoc! {"
2970 fn a() {
2971 if b {
2972 ˇc
2973 }
2974 }
2975 "});
2976}
2977
2978#[gpui::test]
2979async fn test_indent_outdent(cx: &mut TestAppContext) {
2980 init_test(cx, |settings| {
2981 settings.defaults.tab_size = NonZeroU32::new(4);
2982 });
2983
2984 let mut cx = EditorTestContext::new(cx).await;
2985
2986 cx.set_state(indoc! {"
2987 «oneˇ» «twoˇ»
2988 three
2989 four
2990 "});
2991 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2992 cx.assert_editor_state(indoc! {"
2993 «oneˇ» «twoˇ»
2994 three
2995 four
2996 "});
2997
2998 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
2999 cx.assert_editor_state(indoc! {"
3000 «oneˇ» «twoˇ»
3001 three
3002 four
3003 "});
3004
3005 // select across line ending
3006 cx.set_state(indoc! {"
3007 one two
3008 t«hree
3009 ˇ» four
3010 "});
3011 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3012 cx.assert_editor_state(indoc! {"
3013 one two
3014 t«hree
3015 ˇ» four
3016 "});
3017
3018 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3019 cx.assert_editor_state(indoc! {"
3020 one two
3021 t«hree
3022 ˇ» four
3023 "});
3024
3025 // Ensure that indenting/outdenting works when the cursor is at column 0.
3026 cx.set_state(indoc! {"
3027 one two
3028 ˇthree
3029 four
3030 "});
3031 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3032 cx.assert_editor_state(indoc! {"
3033 one two
3034 ˇthree
3035 four
3036 "});
3037
3038 cx.set_state(indoc! {"
3039 one two
3040 ˇ three
3041 four
3042 "});
3043 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3044 cx.assert_editor_state(indoc! {"
3045 one two
3046 ˇthree
3047 four
3048 "});
3049}
3050
3051#[gpui::test]
3052async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
3053 init_test(cx, |settings| {
3054 settings.defaults.hard_tabs = Some(true);
3055 });
3056
3057 let mut cx = EditorTestContext::new(cx).await;
3058
3059 // select two ranges on one line
3060 cx.set_state(indoc! {"
3061 «oneˇ» «twoˇ»
3062 three
3063 four
3064 "});
3065 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3066 cx.assert_editor_state(indoc! {"
3067 \t«oneˇ» «twoˇ»
3068 three
3069 four
3070 "});
3071 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3072 cx.assert_editor_state(indoc! {"
3073 \t\t«oneˇ» «twoˇ»
3074 three
3075 four
3076 "});
3077 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3078 cx.assert_editor_state(indoc! {"
3079 \t«oneˇ» «twoˇ»
3080 three
3081 four
3082 "});
3083 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3084 cx.assert_editor_state(indoc! {"
3085 «oneˇ» «twoˇ»
3086 three
3087 four
3088 "});
3089
3090 // select across a line ending
3091 cx.set_state(indoc! {"
3092 one two
3093 t«hree
3094 ˇ»four
3095 "});
3096 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3097 cx.assert_editor_state(indoc! {"
3098 one two
3099 \tt«hree
3100 ˇ»four
3101 "});
3102 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3103 cx.assert_editor_state(indoc! {"
3104 one two
3105 \t\tt«hree
3106 ˇ»four
3107 "});
3108 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3109 cx.assert_editor_state(indoc! {"
3110 one two
3111 \tt«hree
3112 ˇ»four
3113 "});
3114 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3115 cx.assert_editor_state(indoc! {"
3116 one two
3117 t«hree
3118 ˇ»four
3119 "});
3120
3121 // Ensure that indenting/outdenting works when the cursor is at column 0.
3122 cx.set_state(indoc! {"
3123 one two
3124 ˇthree
3125 four
3126 "});
3127 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3128 cx.assert_editor_state(indoc! {"
3129 one two
3130 ˇthree
3131 four
3132 "});
3133 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3134 cx.assert_editor_state(indoc! {"
3135 one two
3136 \tˇthree
3137 four
3138 "});
3139 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3140 cx.assert_editor_state(indoc! {"
3141 one two
3142 ˇthree
3143 four
3144 "});
3145}
3146
3147#[gpui::test]
3148fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
3149 init_test(cx, |settings| {
3150 settings.languages.extend([
3151 (
3152 "TOML".into(),
3153 LanguageSettingsContent {
3154 tab_size: NonZeroU32::new(2),
3155 ..Default::default()
3156 },
3157 ),
3158 (
3159 "Rust".into(),
3160 LanguageSettingsContent {
3161 tab_size: NonZeroU32::new(4),
3162 ..Default::default()
3163 },
3164 ),
3165 ]);
3166 });
3167
3168 let toml_language = Arc::new(Language::new(
3169 LanguageConfig {
3170 name: "TOML".into(),
3171 ..Default::default()
3172 },
3173 None,
3174 ));
3175 let rust_language = Arc::new(Language::new(
3176 LanguageConfig {
3177 name: "Rust".into(),
3178 ..Default::default()
3179 },
3180 None,
3181 ));
3182
3183 let toml_buffer =
3184 cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
3185 let rust_buffer =
3186 cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
3187 let multibuffer = cx.new(|cx| {
3188 let mut multibuffer = MultiBuffer::new(ReadWrite);
3189 multibuffer.push_excerpts(
3190 toml_buffer.clone(),
3191 [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
3192 cx,
3193 );
3194 multibuffer.push_excerpts(
3195 rust_buffer.clone(),
3196 [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
3197 cx,
3198 );
3199 multibuffer
3200 });
3201
3202 cx.add_window(|window, cx| {
3203 let mut editor = build_editor(multibuffer, window, cx);
3204
3205 assert_eq!(
3206 editor.text(cx),
3207 indoc! {"
3208 a = 1
3209 b = 2
3210
3211 const c: usize = 3;
3212 "}
3213 );
3214
3215 select_ranges(
3216 &mut editor,
3217 indoc! {"
3218 «aˇ» = 1
3219 b = 2
3220
3221 «const c:ˇ» usize = 3;
3222 "},
3223 window,
3224 cx,
3225 );
3226
3227 editor.tab(&Tab, window, cx);
3228 assert_text_with_selections(
3229 &mut editor,
3230 indoc! {"
3231 «aˇ» = 1
3232 b = 2
3233
3234 «const c:ˇ» usize = 3;
3235 "},
3236 cx,
3237 );
3238 editor.backtab(&Backtab, window, cx);
3239 assert_text_with_selections(
3240 &mut editor,
3241 indoc! {"
3242 «aˇ» = 1
3243 b = 2
3244
3245 «const c:ˇ» usize = 3;
3246 "},
3247 cx,
3248 );
3249
3250 editor
3251 });
3252}
3253
3254#[gpui::test]
3255async fn test_backspace(cx: &mut TestAppContext) {
3256 init_test(cx, |_| {});
3257
3258 let mut cx = EditorTestContext::new(cx).await;
3259
3260 // Basic backspace
3261 cx.set_state(indoc! {"
3262 onˇe two three
3263 fou«rˇ» five six
3264 seven «ˇeight nine
3265 »ten
3266 "});
3267 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3268 cx.assert_editor_state(indoc! {"
3269 oˇe two three
3270 fouˇ five six
3271 seven ˇten
3272 "});
3273
3274 // Test backspace inside and around indents
3275 cx.set_state(indoc! {"
3276 zero
3277 ˇone
3278 ˇtwo
3279 ˇ ˇ ˇ three
3280 ˇ ˇ four
3281 "});
3282 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3283 cx.assert_editor_state(indoc! {"
3284 zero
3285 ˇone
3286 ˇtwo
3287 ˇ threeˇ four
3288 "});
3289}
3290
3291#[gpui::test]
3292async fn test_delete(cx: &mut TestAppContext) {
3293 init_test(cx, |_| {});
3294
3295 let mut cx = EditorTestContext::new(cx).await;
3296 cx.set_state(indoc! {"
3297 onˇe two three
3298 fou«rˇ» five six
3299 seven «ˇeight nine
3300 »ten
3301 "});
3302 cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
3303 cx.assert_editor_state(indoc! {"
3304 onˇ two three
3305 fouˇ five six
3306 seven ˇten
3307 "});
3308}
3309
3310#[gpui::test]
3311fn test_delete_line(cx: &mut TestAppContext) {
3312 init_test(cx, |_| {});
3313
3314 let editor = cx.add_window(|window, cx| {
3315 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3316 build_editor(buffer, window, cx)
3317 });
3318 _ = editor.update(cx, |editor, window, cx| {
3319 editor.change_selections(None, window, cx, |s| {
3320 s.select_display_ranges([
3321 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3322 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3323 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3324 ])
3325 });
3326 editor.delete_line(&DeleteLine, window, cx);
3327 assert_eq!(editor.display_text(cx), "ghi");
3328 assert_eq!(
3329 editor.selections.display_ranges(cx),
3330 vec![
3331 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
3332 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
3333 ]
3334 );
3335 });
3336
3337 let editor = cx.add_window(|window, cx| {
3338 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3339 build_editor(buffer, window, cx)
3340 });
3341 _ = editor.update(cx, |editor, window, cx| {
3342 editor.change_selections(None, window, cx, |s| {
3343 s.select_display_ranges([
3344 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
3345 ])
3346 });
3347 editor.delete_line(&DeleteLine, window, cx);
3348 assert_eq!(editor.display_text(cx), "ghi\n");
3349 assert_eq!(
3350 editor.selections.display_ranges(cx),
3351 vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
3352 );
3353 });
3354}
3355
3356#[gpui::test]
3357fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
3358 init_test(cx, |_| {});
3359
3360 cx.add_window(|window, cx| {
3361 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3362 let mut editor = build_editor(buffer.clone(), window, cx);
3363 let buffer = buffer.read(cx).as_singleton().unwrap();
3364
3365 assert_eq!(
3366 editor.selections.ranges::<Point>(cx),
3367 &[Point::new(0, 0)..Point::new(0, 0)]
3368 );
3369
3370 // When on single line, replace newline at end by space
3371 editor.join_lines(&JoinLines, window, cx);
3372 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3373 assert_eq!(
3374 editor.selections.ranges::<Point>(cx),
3375 &[Point::new(0, 3)..Point::new(0, 3)]
3376 );
3377
3378 // When multiple lines are selected, remove newlines that are spanned by the selection
3379 editor.change_selections(None, window, cx, |s| {
3380 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
3381 });
3382 editor.join_lines(&JoinLines, window, cx);
3383 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
3384 assert_eq!(
3385 editor.selections.ranges::<Point>(cx),
3386 &[Point::new(0, 11)..Point::new(0, 11)]
3387 );
3388
3389 // Undo should be transactional
3390 editor.undo(&Undo, window, cx);
3391 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3392 assert_eq!(
3393 editor.selections.ranges::<Point>(cx),
3394 &[Point::new(0, 5)..Point::new(2, 2)]
3395 );
3396
3397 // When joining an empty line don't insert a space
3398 editor.change_selections(None, window, cx, |s| {
3399 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
3400 });
3401 editor.join_lines(&JoinLines, window, cx);
3402 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
3403 assert_eq!(
3404 editor.selections.ranges::<Point>(cx),
3405 [Point::new(2, 3)..Point::new(2, 3)]
3406 );
3407
3408 // We can remove trailing newlines
3409 editor.join_lines(&JoinLines, window, cx);
3410 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3411 assert_eq!(
3412 editor.selections.ranges::<Point>(cx),
3413 [Point::new(2, 3)..Point::new(2, 3)]
3414 );
3415
3416 // We don't blow up on the last line
3417 editor.join_lines(&JoinLines, window, cx);
3418 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3419 assert_eq!(
3420 editor.selections.ranges::<Point>(cx),
3421 [Point::new(2, 3)..Point::new(2, 3)]
3422 );
3423
3424 // reset to test indentation
3425 editor.buffer.update(cx, |buffer, cx| {
3426 buffer.edit(
3427 [
3428 (Point::new(1, 0)..Point::new(1, 2), " "),
3429 (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
3430 ],
3431 None,
3432 cx,
3433 )
3434 });
3435
3436 // We remove any leading spaces
3437 assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
3438 editor.change_selections(None, window, cx, |s| {
3439 s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
3440 });
3441 editor.join_lines(&JoinLines, window, cx);
3442 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
3443
3444 // We don't insert a space for a line containing only spaces
3445 editor.join_lines(&JoinLines, window, cx);
3446 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
3447
3448 // We ignore any leading tabs
3449 editor.join_lines(&JoinLines, window, cx);
3450 assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
3451
3452 editor
3453 });
3454}
3455
3456#[gpui::test]
3457fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
3458 init_test(cx, |_| {});
3459
3460 cx.add_window(|window, cx| {
3461 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3462 let mut editor = build_editor(buffer.clone(), window, cx);
3463 let buffer = buffer.read(cx).as_singleton().unwrap();
3464
3465 editor.change_selections(None, window, cx, |s| {
3466 s.select_ranges([
3467 Point::new(0, 2)..Point::new(1, 1),
3468 Point::new(1, 2)..Point::new(1, 2),
3469 Point::new(3, 1)..Point::new(3, 2),
3470 ])
3471 });
3472
3473 editor.join_lines(&JoinLines, window, cx);
3474 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
3475
3476 assert_eq!(
3477 editor.selections.ranges::<Point>(cx),
3478 [
3479 Point::new(0, 7)..Point::new(0, 7),
3480 Point::new(1, 3)..Point::new(1, 3)
3481 ]
3482 );
3483 editor
3484 });
3485}
3486
3487#[gpui::test]
3488async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
3489 init_test(cx, |_| {});
3490
3491 let mut cx = EditorTestContext::new(cx).await;
3492
3493 let diff_base = r#"
3494 Line 0
3495 Line 1
3496 Line 2
3497 Line 3
3498 "#
3499 .unindent();
3500
3501 cx.set_state(
3502 &r#"
3503 ˇLine 0
3504 Line 1
3505 Line 2
3506 Line 3
3507 "#
3508 .unindent(),
3509 );
3510
3511 cx.set_head_text(&diff_base);
3512 executor.run_until_parked();
3513
3514 // Join lines
3515 cx.update_editor(|editor, window, cx| {
3516 editor.join_lines(&JoinLines, window, cx);
3517 });
3518 executor.run_until_parked();
3519
3520 cx.assert_editor_state(
3521 &r#"
3522 Line 0ˇ Line 1
3523 Line 2
3524 Line 3
3525 "#
3526 .unindent(),
3527 );
3528 // Join again
3529 cx.update_editor(|editor, window, cx| {
3530 editor.join_lines(&JoinLines, window, cx);
3531 });
3532 executor.run_until_parked();
3533
3534 cx.assert_editor_state(
3535 &r#"
3536 Line 0 Line 1ˇ Line 2
3537 Line 3
3538 "#
3539 .unindent(),
3540 );
3541}
3542
3543#[gpui::test]
3544async fn test_custom_newlines_cause_no_false_positive_diffs(
3545 executor: BackgroundExecutor,
3546 cx: &mut TestAppContext,
3547) {
3548 init_test(cx, |_| {});
3549 let mut cx = EditorTestContext::new(cx).await;
3550 cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
3551 cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
3552 executor.run_until_parked();
3553
3554 cx.update_editor(|editor, window, cx| {
3555 let snapshot = editor.snapshot(window, cx);
3556 assert_eq!(
3557 snapshot
3558 .buffer_snapshot
3559 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
3560 .collect::<Vec<_>>(),
3561 Vec::new(),
3562 "Should not have any diffs for files with custom newlines"
3563 );
3564 });
3565}
3566
3567#[gpui::test]
3568async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
3569 init_test(cx, |_| {});
3570
3571 let mut cx = EditorTestContext::new(cx).await;
3572
3573 // Test sort_lines_case_insensitive()
3574 cx.set_state(indoc! {"
3575 «z
3576 y
3577 x
3578 Z
3579 Y
3580 Xˇ»
3581 "});
3582 cx.update_editor(|e, window, cx| {
3583 e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
3584 });
3585 cx.assert_editor_state(indoc! {"
3586 «x
3587 X
3588 y
3589 Y
3590 z
3591 Zˇ»
3592 "});
3593
3594 // Test reverse_lines()
3595 cx.set_state(indoc! {"
3596 «5
3597 4
3598 3
3599 2
3600 1ˇ»
3601 "});
3602 cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
3603 cx.assert_editor_state(indoc! {"
3604 «1
3605 2
3606 3
3607 4
3608 5ˇ»
3609 "});
3610
3611 // Skip testing shuffle_line()
3612
3613 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
3614 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
3615
3616 // Don't manipulate when cursor is on single line, but expand the selection
3617 cx.set_state(indoc! {"
3618 ddˇdd
3619 ccc
3620 bb
3621 a
3622 "});
3623 cx.update_editor(|e, window, cx| {
3624 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3625 });
3626 cx.assert_editor_state(indoc! {"
3627 «ddddˇ»
3628 ccc
3629 bb
3630 a
3631 "});
3632
3633 // Basic manipulate case
3634 // Start selection moves to column 0
3635 // End of selection shrinks to fit shorter line
3636 cx.set_state(indoc! {"
3637 dd«d
3638 ccc
3639 bb
3640 aaaaaˇ»
3641 "});
3642 cx.update_editor(|e, window, cx| {
3643 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3644 });
3645 cx.assert_editor_state(indoc! {"
3646 «aaaaa
3647 bb
3648 ccc
3649 dddˇ»
3650 "});
3651
3652 // Manipulate case with newlines
3653 cx.set_state(indoc! {"
3654 dd«d
3655 ccc
3656
3657 bb
3658 aaaaa
3659
3660 ˇ»
3661 "});
3662 cx.update_editor(|e, window, cx| {
3663 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3664 });
3665 cx.assert_editor_state(indoc! {"
3666 «
3667
3668 aaaaa
3669 bb
3670 ccc
3671 dddˇ»
3672
3673 "});
3674
3675 // Adding new line
3676 cx.set_state(indoc! {"
3677 aa«a
3678 bbˇ»b
3679 "});
3680 cx.update_editor(|e, window, cx| {
3681 e.manipulate_lines(window, cx, |lines| lines.push("added_line"))
3682 });
3683 cx.assert_editor_state(indoc! {"
3684 «aaa
3685 bbb
3686 added_lineˇ»
3687 "});
3688
3689 // Removing line
3690 cx.set_state(indoc! {"
3691 aa«a
3692 bbbˇ»
3693 "});
3694 cx.update_editor(|e, window, cx| {
3695 e.manipulate_lines(window, cx, |lines| {
3696 lines.pop();
3697 })
3698 });
3699 cx.assert_editor_state(indoc! {"
3700 «aaaˇ»
3701 "});
3702
3703 // Removing all lines
3704 cx.set_state(indoc! {"
3705 aa«a
3706 bbbˇ»
3707 "});
3708 cx.update_editor(|e, window, cx| {
3709 e.manipulate_lines(window, cx, |lines| {
3710 lines.drain(..);
3711 })
3712 });
3713 cx.assert_editor_state(indoc! {"
3714 ˇ
3715 "});
3716}
3717
3718#[gpui::test]
3719async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
3720 init_test(cx, |_| {});
3721
3722 let mut cx = EditorTestContext::new(cx).await;
3723
3724 // Consider continuous selection as single selection
3725 cx.set_state(indoc! {"
3726 Aaa«aa
3727 cˇ»c«c
3728 bb
3729 aaaˇ»aa
3730 "});
3731 cx.update_editor(|e, window, cx| {
3732 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3733 });
3734 cx.assert_editor_state(indoc! {"
3735 «Aaaaa
3736 ccc
3737 bb
3738 aaaaaˇ»
3739 "});
3740
3741 cx.set_state(indoc! {"
3742 Aaa«aa
3743 cˇ»c«c
3744 bb
3745 aaaˇ»aa
3746 "});
3747 cx.update_editor(|e, window, cx| {
3748 e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
3749 });
3750 cx.assert_editor_state(indoc! {"
3751 «Aaaaa
3752 ccc
3753 bbˇ»
3754 "});
3755
3756 // Consider non continuous selection as distinct dedup operations
3757 cx.set_state(indoc! {"
3758 «aaaaa
3759 bb
3760 aaaaa
3761 aaaaaˇ»
3762
3763 aaa«aaˇ»
3764 "});
3765 cx.update_editor(|e, window, cx| {
3766 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3767 });
3768 cx.assert_editor_state(indoc! {"
3769 «aaaaa
3770 bbˇ»
3771
3772 «aaaaaˇ»
3773 "});
3774}
3775
3776#[gpui::test]
3777async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
3778 init_test(cx, |_| {});
3779
3780 let mut cx = EditorTestContext::new(cx).await;
3781
3782 cx.set_state(indoc! {"
3783 «Aaa
3784 aAa
3785 Aaaˇ»
3786 "});
3787 cx.update_editor(|e, window, cx| {
3788 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3789 });
3790 cx.assert_editor_state(indoc! {"
3791 «Aaa
3792 aAaˇ»
3793 "});
3794
3795 cx.set_state(indoc! {"
3796 «Aaa
3797 aAa
3798 aaAˇ»
3799 "});
3800 cx.update_editor(|e, window, cx| {
3801 e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
3802 });
3803 cx.assert_editor_state(indoc! {"
3804 «Aaaˇ»
3805 "});
3806}
3807
3808#[gpui::test]
3809async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
3810 init_test(cx, |_| {});
3811
3812 let mut cx = EditorTestContext::new(cx).await;
3813
3814 // Manipulate with multiple selections on a single line
3815 cx.set_state(indoc! {"
3816 dd«dd
3817 cˇ»c«c
3818 bb
3819 aaaˇ»aa
3820 "});
3821 cx.update_editor(|e, window, cx| {
3822 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3823 });
3824 cx.assert_editor_state(indoc! {"
3825 «aaaaa
3826 bb
3827 ccc
3828 ddddˇ»
3829 "});
3830
3831 // Manipulate with multiple disjoin selections
3832 cx.set_state(indoc! {"
3833 5«
3834 4
3835 3
3836 2
3837 1ˇ»
3838
3839 dd«dd
3840 ccc
3841 bb
3842 aaaˇ»aa
3843 "});
3844 cx.update_editor(|e, window, cx| {
3845 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3846 });
3847 cx.assert_editor_state(indoc! {"
3848 «1
3849 2
3850 3
3851 4
3852 5ˇ»
3853
3854 «aaaaa
3855 bb
3856 ccc
3857 ddddˇ»
3858 "});
3859
3860 // Adding lines on each selection
3861 cx.set_state(indoc! {"
3862 2«
3863 1ˇ»
3864
3865 bb«bb
3866 aaaˇ»aa
3867 "});
3868 cx.update_editor(|e, window, cx| {
3869 e.manipulate_lines(window, cx, |lines| lines.push("added line"))
3870 });
3871 cx.assert_editor_state(indoc! {"
3872 «2
3873 1
3874 added lineˇ»
3875
3876 «bbbb
3877 aaaaa
3878 added lineˇ»
3879 "});
3880
3881 // Removing lines on each selection
3882 cx.set_state(indoc! {"
3883 2«
3884 1ˇ»
3885
3886 bb«bb
3887 aaaˇ»aa
3888 "});
3889 cx.update_editor(|e, window, cx| {
3890 e.manipulate_lines(window, cx, |lines| {
3891 lines.pop();
3892 })
3893 });
3894 cx.assert_editor_state(indoc! {"
3895 «2ˇ»
3896
3897 «bbbbˇ»
3898 "});
3899}
3900
3901#[gpui::test]
3902async fn test_toggle_case(cx: &mut TestAppContext) {
3903 init_test(cx, |_| {});
3904
3905 let mut cx = EditorTestContext::new(cx).await;
3906
3907 // If all lower case -> upper case
3908 cx.set_state(indoc! {"
3909 «hello worldˇ»
3910 "});
3911 cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
3912 cx.assert_editor_state(indoc! {"
3913 «HELLO WORLDˇ»
3914 "});
3915
3916 // If all upper case -> lower case
3917 cx.set_state(indoc! {"
3918 «HELLO WORLDˇ»
3919 "});
3920 cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
3921 cx.assert_editor_state(indoc! {"
3922 «hello worldˇ»
3923 "});
3924
3925 // If any upper case characters are identified -> lower case
3926 // This matches JetBrains IDEs
3927 cx.set_state(indoc! {"
3928 «hEllo worldˇ»
3929 "});
3930 cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
3931 cx.assert_editor_state(indoc! {"
3932 «hello worldˇ»
3933 "});
3934}
3935
3936#[gpui::test]
3937async fn test_manipulate_text(cx: &mut TestAppContext) {
3938 init_test(cx, |_| {});
3939
3940 let mut cx = EditorTestContext::new(cx).await;
3941
3942 // Test convert_to_upper_case()
3943 cx.set_state(indoc! {"
3944 «hello worldˇ»
3945 "});
3946 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3947 cx.assert_editor_state(indoc! {"
3948 «HELLO WORLDˇ»
3949 "});
3950
3951 // Test convert_to_lower_case()
3952 cx.set_state(indoc! {"
3953 «HELLO WORLDˇ»
3954 "});
3955 cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
3956 cx.assert_editor_state(indoc! {"
3957 «hello worldˇ»
3958 "});
3959
3960 // Test multiple line, single selection case
3961 cx.set_state(indoc! {"
3962 «The quick brown
3963 fox jumps over
3964 the lazy dogˇ»
3965 "});
3966 cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
3967 cx.assert_editor_state(indoc! {"
3968 «The Quick Brown
3969 Fox Jumps Over
3970 The Lazy Dogˇ»
3971 "});
3972
3973 // Test multiple line, single selection case
3974 cx.set_state(indoc! {"
3975 «The quick brown
3976 fox jumps over
3977 the lazy dogˇ»
3978 "});
3979 cx.update_editor(|e, window, cx| {
3980 e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
3981 });
3982 cx.assert_editor_state(indoc! {"
3983 «TheQuickBrown
3984 FoxJumpsOver
3985 TheLazyDogˇ»
3986 "});
3987
3988 // From here on out, test more complex cases of manipulate_text()
3989
3990 // Test no selection case - should affect words cursors are in
3991 // Cursor at beginning, middle, and end of word
3992 cx.set_state(indoc! {"
3993 ˇhello big beauˇtiful worldˇ
3994 "});
3995 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3996 cx.assert_editor_state(indoc! {"
3997 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3998 "});
3999
4000 // Test multiple selections on a single line and across multiple lines
4001 cx.set_state(indoc! {"
4002 «Theˇ» quick «brown
4003 foxˇ» jumps «overˇ»
4004 the «lazyˇ» dog
4005 "});
4006 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
4007 cx.assert_editor_state(indoc! {"
4008 «THEˇ» quick «BROWN
4009 FOXˇ» jumps «OVERˇ»
4010 the «LAZYˇ» dog
4011 "});
4012
4013 // Test case where text length grows
4014 cx.set_state(indoc! {"
4015 «tschüߡ»
4016 "});
4017 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
4018 cx.assert_editor_state(indoc! {"
4019 «TSCHÜSSˇ»
4020 "});
4021
4022 // Test to make sure we don't crash when text shrinks
4023 cx.set_state(indoc! {"
4024 aaa_bbbˇ
4025 "});
4026 cx.update_editor(|e, window, cx| {
4027 e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
4028 });
4029 cx.assert_editor_state(indoc! {"
4030 «aaaBbbˇ»
4031 "});
4032
4033 // Test to make sure we all aware of the fact that each word can grow and shrink
4034 // Final selections should be aware of this fact
4035 cx.set_state(indoc! {"
4036 aaa_bˇbb bbˇb_ccc ˇccc_ddd
4037 "});
4038 cx.update_editor(|e, window, cx| {
4039 e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
4040 });
4041 cx.assert_editor_state(indoc! {"
4042 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
4043 "});
4044
4045 cx.set_state(indoc! {"
4046 «hElLo, WoRld!ˇ»
4047 "});
4048 cx.update_editor(|e, window, cx| {
4049 e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
4050 });
4051 cx.assert_editor_state(indoc! {"
4052 «HeLlO, wOrLD!ˇ»
4053 "});
4054}
4055
4056#[gpui::test]
4057fn test_duplicate_line(cx: &mut TestAppContext) {
4058 init_test(cx, |_| {});
4059
4060 let editor = cx.add_window(|window, cx| {
4061 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4062 build_editor(buffer, window, cx)
4063 });
4064 _ = editor.update(cx, |editor, window, cx| {
4065 editor.change_selections(None, window, cx, |s| {
4066 s.select_display_ranges([
4067 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4068 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4069 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4070 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
4071 ])
4072 });
4073 editor.duplicate_line_down(&DuplicateLineDown, window, cx);
4074 assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
4075 assert_eq!(
4076 editor.selections.display_ranges(cx),
4077 vec![
4078 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
4079 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
4080 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
4081 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
4082 ]
4083 );
4084 });
4085
4086 let editor = cx.add_window(|window, cx| {
4087 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4088 build_editor(buffer, window, cx)
4089 });
4090 _ = editor.update(cx, |editor, window, cx| {
4091 editor.change_selections(None, window, cx, |s| {
4092 s.select_display_ranges([
4093 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4094 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4095 ])
4096 });
4097 editor.duplicate_line_down(&DuplicateLineDown, window, cx);
4098 assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
4099 assert_eq!(
4100 editor.selections.display_ranges(cx),
4101 vec![
4102 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
4103 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
4104 ]
4105 );
4106 });
4107
4108 // With `move_upwards` the selections stay in place, except for
4109 // the lines inserted above them
4110 let editor = cx.add_window(|window, cx| {
4111 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4112 build_editor(buffer, window, cx)
4113 });
4114 _ = editor.update(cx, |editor, window, cx| {
4115 editor.change_selections(None, window, cx, |s| {
4116 s.select_display_ranges([
4117 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4118 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4119 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4120 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
4121 ])
4122 });
4123 editor.duplicate_line_up(&DuplicateLineUp, window, cx);
4124 assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
4125 assert_eq!(
4126 editor.selections.display_ranges(cx),
4127 vec![
4128 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4129 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4130 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
4131 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
4132 ]
4133 );
4134 });
4135
4136 let editor = cx.add_window(|window, cx| {
4137 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4138 build_editor(buffer, window, cx)
4139 });
4140 _ = editor.update(cx, |editor, window, cx| {
4141 editor.change_selections(None, window, cx, |s| {
4142 s.select_display_ranges([
4143 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4144 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4145 ])
4146 });
4147 editor.duplicate_line_up(&DuplicateLineUp, window, cx);
4148 assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
4149 assert_eq!(
4150 editor.selections.display_ranges(cx),
4151 vec![
4152 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4153 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4154 ]
4155 );
4156 });
4157
4158 let editor = cx.add_window(|window, cx| {
4159 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4160 build_editor(buffer, window, cx)
4161 });
4162 _ = editor.update(cx, |editor, window, cx| {
4163 editor.change_selections(None, window, cx, |s| {
4164 s.select_display_ranges([
4165 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4166 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4167 ])
4168 });
4169 editor.duplicate_selection(&DuplicateSelection, window, cx);
4170 assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
4171 assert_eq!(
4172 editor.selections.display_ranges(cx),
4173 vec![
4174 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4175 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
4176 ]
4177 );
4178 });
4179}
4180
4181#[gpui::test]
4182fn test_move_line_up_down(cx: &mut TestAppContext) {
4183 init_test(cx, |_| {});
4184
4185 let editor = cx.add_window(|window, cx| {
4186 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4187 build_editor(buffer, window, cx)
4188 });
4189 _ = editor.update(cx, |editor, window, cx| {
4190 editor.fold_creases(
4191 vec![
4192 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4193 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4194 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4195 ],
4196 true,
4197 window,
4198 cx,
4199 );
4200 editor.change_selections(None, window, cx, |s| {
4201 s.select_display_ranges([
4202 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4203 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4204 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4205 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
4206 ])
4207 });
4208 assert_eq!(
4209 editor.display_text(cx),
4210 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
4211 );
4212
4213 editor.move_line_up(&MoveLineUp, window, cx);
4214 assert_eq!(
4215 editor.display_text(cx),
4216 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
4217 );
4218 assert_eq!(
4219 editor.selections.display_ranges(cx),
4220 vec![
4221 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4222 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4223 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
4224 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
4225 ]
4226 );
4227 });
4228
4229 _ = editor.update(cx, |editor, window, cx| {
4230 editor.move_line_down(&MoveLineDown, window, cx);
4231 assert_eq!(
4232 editor.display_text(cx),
4233 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
4234 );
4235 assert_eq!(
4236 editor.selections.display_ranges(cx),
4237 vec![
4238 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
4239 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4240 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4241 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
4242 ]
4243 );
4244 });
4245
4246 _ = editor.update(cx, |editor, window, cx| {
4247 editor.move_line_down(&MoveLineDown, window, cx);
4248 assert_eq!(
4249 editor.display_text(cx),
4250 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
4251 );
4252 assert_eq!(
4253 editor.selections.display_ranges(cx),
4254 vec![
4255 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4256 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4257 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4258 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
4259 ]
4260 );
4261 });
4262
4263 _ = editor.update(cx, |editor, window, cx| {
4264 editor.move_line_up(&MoveLineUp, window, cx);
4265 assert_eq!(
4266 editor.display_text(cx),
4267 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
4268 );
4269 assert_eq!(
4270 editor.selections.display_ranges(cx),
4271 vec![
4272 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
4273 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4274 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
4275 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
4276 ]
4277 );
4278 });
4279}
4280
4281#[gpui::test]
4282fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
4283 init_test(cx, |_| {});
4284
4285 let editor = cx.add_window(|window, cx| {
4286 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4287 build_editor(buffer, window, cx)
4288 });
4289 _ = editor.update(cx, |editor, window, cx| {
4290 let snapshot = editor.buffer.read(cx).snapshot(cx);
4291 editor.insert_blocks(
4292 [BlockProperties {
4293 style: BlockStyle::Fixed,
4294 placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
4295 height: Some(1),
4296 render: Arc::new(|_| div().into_any()),
4297 priority: 0,
4298 }],
4299 Some(Autoscroll::fit()),
4300 cx,
4301 );
4302 editor.change_selections(None, window, cx, |s| {
4303 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
4304 });
4305 editor.move_line_down(&MoveLineDown, window, cx);
4306 });
4307}
4308
4309#[gpui::test]
4310async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
4311 init_test(cx, |_| {});
4312
4313 let mut cx = EditorTestContext::new(cx).await;
4314 cx.set_state(
4315 &"
4316 ˇzero
4317 one
4318 two
4319 three
4320 four
4321 five
4322 "
4323 .unindent(),
4324 );
4325
4326 // Create a four-line block that replaces three lines of text.
4327 cx.update_editor(|editor, window, cx| {
4328 let snapshot = editor.snapshot(window, cx);
4329 let snapshot = &snapshot.buffer_snapshot;
4330 let placement = BlockPlacement::Replace(
4331 snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
4332 );
4333 editor.insert_blocks(
4334 [BlockProperties {
4335 placement,
4336 height: Some(4),
4337 style: BlockStyle::Sticky,
4338 render: Arc::new(|_| gpui::div().into_any_element()),
4339 priority: 0,
4340 }],
4341 None,
4342 cx,
4343 );
4344 });
4345
4346 // Move down so that the cursor touches the block.
4347 cx.update_editor(|editor, window, cx| {
4348 editor.move_down(&Default::default(), window, cx);
4349 });
4350 cx.assert_editor_state(
4351 &"
4352 zero
4353 «one
4354 two
4355 threeˇ»
4356 four
4357 five
4358 "
4359 .unindent(),
4360 );
4361
4362 // Move down past the block.
4363 cx.update_editor(|editor, window, cx| {
4364 editor.move_down(&Default::default(), window, cx);
4365 });
4366 cx.assert_editor_state(
4367 &"
4368 zero
4369 one
4370 two
4371 three
4372 ˇfour
4373 five
4374 "
4375 .unindent(),
4376 );
4377}
4378
4379#[gpui::test]
4380fn test_transpose(cx: &mut TestAppContext) {
4381 init_test(cx, |_| {});
4382
4383 _ = cx.add_window(|window, cx| {
4384 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
4385 editor.set_style(EditorStyle::default(), window, cx);
4386 editor.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
4387 editor.transpose(&Default::default(), window, cx);
4388 assert_eq!(editor.text(cx), "bac");
4389 assert_eq!(editor.selections.ranges(cx), [2..2]);
4390
4391 editor.transpose(&Default::default(), window, cx);
4392 assert_eq!(editor.text(cx), "bca");
4393 assert_eq!(editor.selections.ranges(cx), [3..3]);
4394
4395 editor.transpose(&Default::default(), window, cx);
4396 assert_eq!(editor.text(cx), "bac");
4397 assert_eq!(editor.selections.ranges(cx), [3..3]);
4398
4399 editor
4400 });
4401
4402 _ = cx.add_window(|window, cx| {
4403 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
4404 editor.set_style(EditorStyle::default(), window, cx);
4405 editor.change_selections(None, window, cx, |s| s.select_ranges([3..3]));
4406 editor.transpose(&Default::default(), window, cx);
4407 assert_eq!(editor.text(cx), "acb\nde");
4408 assert_eq!(editor.selections.ranges(cx), [3..3]);
4409
4410 editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
4411 editor.transpose(&Default::default(), window, cx);
4412 assert_eq!(editor.text(cx), "acbd\ne");
4413 assert_eq!(editor.selections.ranges(cx), [5..5]);
4414
4415 editor.transpose(&Default::default(), window, cx);
4416 assert_eq!(editor.text(cx), "acbde\n");
4417 assert_eq!(editor.selections.ranges(cx), [6..6]);
4418
4419 editor.transpose(&Default::default(), window, cx);
4420 assert_eq!(editor.text(cx), "acbd\ne");
4421 assert_eq!(editor.selections.ranges(cx), [6..6]);
4422
4423 editor
4424 });
4425
4426 _ = cx.add_window(|window, cx| {
4427 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
4428 editor.set_style(EditorStyle::default(), window, cx);
4429 editor.change_selections(None, window, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
4430 editor.transpose(&Default::default(), window, cx);
4431 assert_eq!(editor.text(cx), "bacd\ne");
4432 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
4433
4434 editor.transpose(&Default::default(), window, cx);
4435 assert_eq!(editor.text(cx), "bcade\n");
4436 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
4437
4438 editor.transpose(&Default::default(), window, cx);
4439 assert_eq!(editor.text(cx), "bcda\ne");
4440 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4441
4442 editor.transpose(&Default::default(), window, cx);
4443 assert_eq!(editor.text(cx), "bcade\n");
4444 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4445
4446 editor.transpose(&Default::default(), window, cx);
4447 assert_eq!(editor.text(cx), "bcaed\n");
4448 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
4449
4450 editor
4451 });
4452
4453 _ = cx.add_window(|window, cx| {
4454 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
4455 editor.set_style(EditorStyle::default(), window, cx);
4456 editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
4457 editor.transpose(&Default::default(), window, cx);
4458 assert_eq!(editor.text(cx), "🏀🍐✋");
4459 assert_eq!(editor.selections.ranges(cx), [8..8]);
4460
4461 editor.transpose(&Default::default(), window, cx);
4462 assert_eq!(editor.text(cx), "🏀✋🍐");
4463 assert_eq!(editor.selections.ranges(cx), [11..11]);
4464
4465 editor.transpose(&Default::default(), window, cx);
4466 assert_eq!(editor.text(cx), "🏀🍐✋");
4467 assert_eq!(editor.selections.ranges(cx), [11..11]);
4468
4469 editor
4470 });
4471}
4472
4473#[gpui::test]
4474async fn test_rewrap(cx: &mut TestAppContext) {
4475 init_test(cx, |settings| {
4476 settings.languages.extend([
4477 (
4478 "Markdown".into(),
4479 LanguageSettingsContent {
4480 allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
4481 ..Default::default()
4482 },
4483 ),
4484 (
4485 "Plain Text".into(),
4486 LanguageSettingsContent {
4487 allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
4488 ..Default::default()
4489 },
4490 ),
4491 ])
4492 });
4493
4494 let mut cx = EditorTestContext::new(cx).await;
4495
4496 let language_with_c_comments = Arc::new(Language::new(
4497 LanguageConfig {
4498 line_comments: vec!["// ".into()],
4499 ..LanguageConfig::default()
4500 },
4501 None,
4502 ));
4503 let language_with_pound_comments = Arc::new(Language::new(
4504 LanguageConfig {
4505 line_comments: vec!["# ".into()],
4506 ..LanguageConfig::default()
4507 },
4508 None,
4509 ));
4510 let markdown_language = Arc::new(Language::new(
4511 LanguageConfig {
4512 name: "Markdown".into(),
4513 ..LanguageConfig::default()
4514 },
4515 None,
4516 ));
4517 let language_with_doc_comments = Arc::new(Language::new(
4518 LanguageConfig {
4519 line_comments: vec!["// ".into(), "/// ".into()],
4520 ..LanguageConfig::default()
4521 },
4522 Some(tree_sitter_rust::LANGUAGE.into()),
4523 ));
4524
4525 let plaintext_language = Arc::new(Language::new(
4526 LanguageConfig {
4527 name: "Plain Text".into(),
4528 ..LanguageConfig::default()
4529 },
4530 None,
4531 ));
4532
4533 assert_rewrap(
4534 indoc! {"
4535 // ˇ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.
4536 "},
4537 indoc! {"
4538 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4539 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4540 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4541 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4542 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4543 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4544 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4545 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4546 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4547 // porttitor id. Aliquam id accumsan eros.
4548 "},
4549 language_with_c_comments.clone(),
4550 &mut cx,
4551 );
4552
4553 // Test that rewrapping works inside of a selection
4554 assert_rewrap(
4555 indoc! {"
4556 «// 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.ˇ»
4557 "},
4558 indoc! {"
4559 «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4560 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4561 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4562 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4563 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4564 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4565 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4566 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4567 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4568 // porttitor id. Aliquam id accumsan eros.ˇ»
4569 "},
4570 language_with_c_comments.clone(),
4571 &mut cx,
4572 );
4573
4574 // Test that cursors that expand to the same region are collapsed.
4575 assert_rewrap(
4576 indoc! {"
4577 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4578 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4579 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4580 // ˇ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.
4581 "},
4582 indoc! {"
4583 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4584 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4585 // auctor, eu lacinia sapien scelerisque. ˇVivamus sit amet neque et quam
4586 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4587 // Pellentesque odio lectus, iaculis ac volutpat et, ˇblandit quis urna. Sed
4588 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4589 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4590 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4591 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4592 // porttitor id. Aliquam id accumsan eros.
4593 "},
4594 language_with_c_comments.clone(),
4595 &mut cx,
4596 );
4597
4598 // Test that non-contiguous selections are treated separately.
4599 assert_rewrap(
4600 indoc! {"
4601 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4602 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4603 //
4604 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4605 // ˇ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.
4606 "},
4607 indoc! {"
4608 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4609 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4610 // auctor, eu lacinia sapien scelerisque.
4611 //
4612 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
4613 // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4614 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
4615 // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
4616 // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
4617 // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
4618 // vulputate turpis porttitor id. Aliquam id accumsan eros.
4619 "},
4620 language_with_c_comments.clone(),
4621 &mut cx,
4622 );
4623
4624 // Test that different comment prefixes are supported.
4625 assert_rewrap(
4626 indoc! {"
4627 # ˇ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.
4628 "},
4629 indoc! {"
4630 # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4631 # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4632 # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4633 # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4634 # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
4635 # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
4636 # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
4637 # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
4638 # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
4639 # accumsan eros.
4640 "},
4641 language_with_pound_comments.clone(),
4642 &mut cx,
4643 );
4644
4645 // Test that rewrapping is ignored outside of comments in most languages.
4646 assert_rewrap(
4647 indoc! {"
4648 /// Adds two numbers.
4649 /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4650 fn add(a: u32, b: u32) -> u32 {
4651 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ˇ
4652 }
4653 "},
4654 indoc! {"
4655 /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
4656 /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4657 fn add(a: u32, b: u32) -> u32 {
4658 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ˇ
4659 }
4660 "},
4661 language_with_doc_comments.clone(),
4662 &mut cx,
4663 );
4664
4665 // Test that rewrapping works in Markdown and Plain Text languages.
4666 assert_rewrap(
4667 indoc! {"
4668 # Hello
4669
4670 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.
4671 "},
4672 indoc! {"
4673 # Hello
4674
4675 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4676 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4677 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4678 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4679 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4680 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4681 Integer sit amet scelerisque nisi.
4682 "},
4683 markdown_language,
4684 &mut cx,
4685 );
4686
4687 assert_rewrap(
4688 indoc! {"
4689 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.
4690 "},
4691 indoc! {"
4692 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4693 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4694 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4695 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4696 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4697 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4698 Integer sit amet scelerisque nisi.
4699 "},
4700 plaintext_language,
4701 &mut cx,
4702 );
4703
4704 // Test rewrapping unaligned comments in a selection.
4705 assert_rewrap(
4706 indoc! {"
4707 fn foo() {
4708 if true {
4709 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4710 // Praesent semper egestas tellus id dignissim.ˇ»
4711 do_something();
4712 } else {
4713 //
4714 }
4715 }
4716 "},
4717 indoc! {"
4718 fn foo() {
4719 if true {
4720 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4721 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4722 // egestas tellus id dignissim.ˇ»
4723 do_something();
4724 } else {
4725 //
4726 }
4727 }
4728 "},
4729 language_with_doc_comments.clone(),
4730 &mut cx,
4731 );
4732
4733 assert_rewrap(
4734 indoc! {"
4735 fn foo() {
4736 if true {
4737 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4738 // Praesent semper egestas tellus id dignissim.»
4739 do_something();
4740 } else {
4741 //
4742 }
4743
4744 }
4745 "},
4746 indoc! {"
4747 fn foo() {
4748 if true {
4749 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4750 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4751 // egestas tellus id dignissim.»
4752 do_something();
4753 } else {
4754 //
4755 }
4756
4757 }
4758 "},
4759 language_with_doc_comments.clone(),
4760 &mut cx,
4761 );
4762
4763 #[track_caller]
4764 fn assert_rewrap(
4765 unwrapped_text: &str,
4766 wrapped_text: &str,
4767 language: Arc<Language>,
4768 cx: &mut EditorTestContext,
4769 ) {
4770 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4771 cx.set_state(unwrapped_text);
4772 cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
4773 cx.assert_editor_state(wrapped_text);
4774 }
4775}
4776
4777#[gpui::test]
4778async fn test_hard_wrap(cx: &mut TestAppContext) {
4779 init_test(cx, |_| {});
4780 let mut cx = EditorTestContext::new(cx).await;
4781
4782 cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
4783 cx.update_editor(|editor, _, cx| {
4784 editor.set_hard_wrap(Some(14), cx);
4785 });
4786
4787 cx.set_state(indoc!(
4788 "
4789 one two three ˇ
4790 "
4791 ));
4792 cx.simulate_input("four");
4793 cx.run_until_parked();
4794
4795 cx.assert_editor_state(indoc!(
4796 "
4797 one two three
4798 fourˇ
4799 "
4800 ));
4801
4802 cx.update_editor(|editor, window, cx| {
4803 editor.newline(&Default::default(), window, cx);
4804 });
4805 cx.run_until_parked();
4806 cx.assert_editor_state(indoc!(
4807 "
4808 one two three
4809 four
4810 ˇ
4811 "
4812 ));
4813
4814 cx.simulate_input("five");
4815 cx.run_until_parked();
4816 cx.assert_editor_state(indoc!(
4817 "
4818 one two three
4819 four
4820 fiveˇ
4821 "
4822 ));
4823
4824 cx.update_editor(|editor, window, cx| {
4825 editor.newline(&Default::default(), window, cx);
4826 });
4827 cx.run_until_parked();
4828 cx.simulate_input("# ");
4829 cx.run_until_parked();
4830 cx.assert_editor_state(indoc!(
4831 "
4832 one two three
4833 four
4834 five
4835 # ˇ
4836 "
4837 ));
4838
4839 cx.update_editor(|editor, window, cx| {
4840 editor.newline(&Default::default(), window, cx);
4841 });
4842 cx.run_until_parked();
4843 cx.assert_editor_state(indoc!(
4844 "
4845 one two three
4846 four
4847 five
4848 #\x20
4849 #ˇ
4850 "
4851 ));
4852
4853 cx.simulate_input(" 6");
4854 cx.run_until_parked();
4855 cx.assert_editor_state(indoc!(
4856 "
4857 one two three
4858 four
4859 five
4860 #
4861 # 6ˇ
4862 "
4863 ));
4864}
4865
4866#[gpui::test]
4867async fn test_clipboard(cx: &mut TestAppContext) {
4868 init_test(cx, |_| {});
4869
4870 let mut cx = EditorTestContext::new(cx).await;
4871
4872 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
4873 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4874 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
4875
4876 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
4877 cx.set_state("two ˇfour ˇsix ˇ");
4878 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4879 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
4880
4881 // Paste again but with only two cursors. Since the number of cursors doesn't
4882 // match the number of slices in the clipboard, the entire clipboard text
4883 // is pasted at each cursor.
4884 cx.set_state("ˇtwo one✅ four three six five ˇ");
4885 cx.update_editor(|e, window, cx| {
4886 e.handle_input("( ", window, cx);
4887 e.paste(&Paste, window, cx);
4888 e.handle_input(") ", window, cx);
4889 });
4890 cx.assert_editor_state(
4891 &([
4892 "( one✅ ",
4893 "three ",
4894 "five ) ˇtwo one✅ four three six five ( one✅ ",
4895 "three ",
4896 "five ) ˇ",
4897 ]
4898 .join("\n")),
4899 );
4900
4901 // Cut with three selections, one of which is full-line.
4902 cx.set_state(indoc! {"
4903 1«2ˇ»3
4904 4ˇ567
4905 «8ˇ»9"});
4906 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4907 cx.assert_editor_state(indoc! {"
4908 1ˇ3
4909 ˇ9"});
4910
4911 // Paste with three selections, noticing how the copied selection that was full-line
4912 // gets inserted before the second cursor.
4913 cx.set_state(indoc! {"
4914 1ˇ3
4915 9ˇ
4916 «oˇ»ne"});
4917 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4918 cx.assert_editor_state(indoc! {"
4919 12ˇ3
4920 4567
4921 9ˇ
4922 8ˇne"});
4923
4924 // Copy with a single cursor only, which writes the whole line into the clipboard.
4925 cx.set_state(indoc! {"
4926 The quick brown
4927 fox juˇmps over
4928 the lazy dog"});
4929 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
4930 assert_eq!(
4931 cx.read_from_clipboard()
4932 .and_then(|item| item.text().as_deref().map(str::to_string)),
4933 Some("fox jumps over\n".to_string())
4934 );
4935
4936 // Paste with three selections, noticing how the copied full-line selection is inserted
4937 // before the empty selections but replaces the selection that is non-empty.
4938 cx.set_state(indoc! {"
4939 Tˇhe quick brown
4940 «foˇ»x jumps over
4941 tˇhe lazy dog"});
4942 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4943 cx.assert_editor_state(indoc! {"
4944 fox jumps over
4945 Tˇhe quick brown
4946 fox jumps over
4947 ˇx jumps over
4948 fox jumps over
4949 tˇhe lazy dog"});
4950}
4951
4952#[gpui::test]
4953async fn test_copy_trim(cx: &mut TestAppContext) {
4954 init_test(cx, |_| {});
4955
4956 let mut cx = EditorTestContext::new(cx).await;
4957 cx.set_state(
4958 r#" «for selection in selections.iter() {
4959 let mut start = selection.start;
4960 let mut end = selection.end;
4961 let is_entire_line = selection.is_empty();
4962 if is_entire_line {
4963 start = Point::new(start.row, 0);ˇ»
4964 end = cmp::min(max_point, Point::new(end.row + 1, 0));
4965 }
4966 "#,
4967 );
4968 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
4969 assert_eq!(
4970 cx.read_from_clipboard()
4971 .and_then(|item| item.text().as_deref().map(str::to_string)),
4972 Some(
4973 "for selection in selections.iter() {
4974 let mut start = selection.start;
4975 let mut end = selection.end;
4976 let is_entire_line = selection.is_empty();
4977 if is_entire_line {
4978 start = Point::new(start.row, 0);"
4979 .to_string()
4980 ),
4981 "Regular copying preserves all indentation selected",
4982 );
4983 cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
4984 assert_eq!(
4985 cx.read_from_clipboard()
4986 .and_then(|item| item.text().as_deref().map(str::to_string)),
4987 Some(
4988 "for selection in selections.iter() {
4989let mut start = selection.start;
4990let mut end = selection.end;
4991let is_entire_line = selection.is_empty();
4992if is_entire_line {
4993 start = Point::new(start.row, 0);"
4994 .to_string()
4995 ),
4996 "Copying with stripping should strip all leading whitespaces"
4997 );
4998
4999 cx.set_state(
5000 r#" « for selection in selections.iter() {
5001 let mut start = selection.start;
5002 let mut end = selection.end;
5003 let is_entire_line = selection.is_empty();
5004 if is_entire_line {
5005 start = Point::new(start.row, 0);ˇ»
5006 end = cmp::min(max_point, Point::new(end.row + 1, 0));
5007 }
5008 "#,
5009 );
5010 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
5011 assert_eq!(
5012 cx.read_from_clipboard()
5013 .and_then(|item| item.text().as_deref().map(str::to_string)),
5014 Some(
5015 " for selection in selections.iter() {
5016 let mut start = selection.start;
5017 let mut end = selection.end;
5018 let is_entire_line = selection.is_empty();
5019 if is_entire_line {
5020 start = Point::new(start.row, 0);"
5021 .to_string()
5022 ),
5023 "Regular copying preserves all indentation selected",
5024 );
5025 cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
5026 assert_eq!(
5027 cx.read_from_clipboard()
5028 .and_then(|item| item.text().as_deref().map(str::to_string)),
5029 Some(
5030 "for selection in selections.iter() {
5031let mut start = selection.start;
5032let mut end = selection.end;
5033let is_entire_line = selection.is_empty();
5034if is_entire_line {
5035 start = Point::new(start.row, 0);"
5036 .to_string()
5037 ),
5038 "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
5039 );
5040
5041 cx.set_state(
5042 r#" «ˇ for selection in selections.iter() {
5043 let mut start = selection.start;
5044 let mut end = selection.end;
5045 let is_entire_line = selection.is_empty();
5046 if is_entire_line {
5047 start = Point::new(start.row, 0);»
5048 end = cmp::min(max_point, Point::new(end.row + 1, 0));
5049 }
5050 "#,
5051 );
5052 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
5053 assert_eq!(
5054 cx.read_from_clipboard()
5055 .and_then(|item| item.text().as_deref().map(str::to_string)),
5056 Some(
5057 " for selection in selections.iter() {
5058 let mut start = selection.start;
5059 let mut end = selection.end;
5060 let is_entire_line = selection.is_empty();
5061 if is_entire_line {
5062 start = Point::new(start.row, 0);"
5063 .to_string()
5064 ),
5065 "Regular copying for reverse selection works the same",
5066 );
5067 cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
5068 assert_eq!(
5069 cx.read_from_clipboard()
5070 .and_then(|item| item.text().as_deref().map(str::to_string)),
5071 Some(
5072 "for selection in selections.iter() {
5073let mut start = selection.start;
5074let mut end = selection.end;
5075let is_entire_line = selection.is_empty();
5076if is_entire_line {
5077 start = Point::new(start.row, 0);"
5078 .to_string()
5079 ),
5080 "Copying with stripping for reverse selection works the same"
5081 );
5082
5083 cx.set_state(
5084 r#" for selection «in selections.iter() {
5085 let mut start = selection.start;
5086 let mut end = selection.end;
5087 let is_entire_line = selection.is_empty();
5088 if is_entire_line {
5089 start = Point::new(start.row, 0);ˇ»
5090 end = cmp::min(max_point, Point::new(end.row + 1, 0));
5091 }
5092 "#,
5093 );
5094 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
5095 assert_eq!(
5096 cx.read_from_clipboard()
5097 .and_then(|item| item.text().as_deref().map(str::to_string)),
5098 Some(
5099 "in selections.iter() {
5100 let mut start = selection.start;
5101 let mut end = selection.end;
5102 let is_entire_line = selection.is_empty();
5103 if is_entire_line {
5104 start = Point::new(start.row, 0);"
5105 .to_string()
5106 ),
5107 "When selecting past the indent, the copying works as usual",
5108 );
5109 cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
5110 assert_eq!(
5111 cx.read_from_clipboard()
5112 .and_then(|item| item.text().as_deref().map(str::to_string)),
5113 Some(
5114 "in selections.iter() {
5115 let mut start = selection.start;
5116 let mut end = selection.end;
5117 let is_entire_line = selection.is_empty();
5118 if is_entire_line {
5119 start = Point::new(start.row, 0);"
5120 .to_string()
5121 ),
5122 "When selecting past the indent, nothing is trimmed"
5123 );
5124}
5125
5126#[gpui::test]
5127async fn test_paste_multiline(cx: &mut TestAppContext) {
5128 init_test(cx, |_| {});
5129
5130 let mut cx = EditorTestContext::new(cx).await;
5131 cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
5132
5133 // Cut an indented block, without the leading whitespace.
5134 cx.set_state(indoc! {"
5135 const a: B = (
5136 c(),
5137 «d(
5138 e,
5139 f
5140 )ˇ»
5141 );
5142 "});
5143 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
5144 cx.assert_editor_state(indoc! {"
5145 const a: B = (
5146 c(),
5147 ˇ
5148 );
5149 "});
5150
5151 // Paste it at the same position.
5152 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
5153 cx.assert_editor_state(indoc! {"
5154 const a: B = (
5155 c(),
5156 d(
5157 e,
5158 f
5159 )ˇ
5160 );
5161 "});
5162
5163 // Paste it at a line with a lower indent level.
5164 cx.set_state(indoc! {"
5165 ˇ
5166 const a: B = (
5167 c(),
5168 );
5169 "});
5170 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
5171 cx.assert_editor_state(indoc! {"
5172 d(
5173 e,
5174 f
5175 )ˇ
5176 const a: B = (
5177 c(),
5178 );
5179 "});
5180
5181 // Cut an indented block, with the leading whitespace.
5182 cx.set_state(indoc! {"
5183 const a: B = (
5184 c(),
5185 « d(
5186 e,
5187 f
5188 )
5189 ˇ»);
5190 "});
5191 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
5192 cx.assert_editor_state(indoc! {"
5193 const a: B = (
5194 c(),
5195 ˇ);
5196 "});
5197
5198 // Paste it at the same position.
5199 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
5200 cx.assert_editor_state(indoc! {"
5201 const a: B = (
5202 c(),
5203 d(
5204 e,
5205 f
5206 )
5207 ˇ);
5208 "});
5209
5210 // Paste it at a line with a higher indent level.
5211 cx.set_state(indoc! {"
5212 const a: B = (
5213 c(),
5214 d(
5215 e,
5216 fˇ
5217 )
5218 );
5219 "});
5220 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
5221 cx.assert_editor_state(indoc! {"
5222 const a: B = (
5223 c(),
5224 d(
5225 e,
5226 f d(
5227 e,
5228 f
5229 )
5230 ˇ
5231 )
5232 );
5233 "});
5234
5235 // Copy an indented block, starting mid-line
5236 cx.set_state(indoc! {"
5237 const a: B = (
5238 c(),
5239 somethin«g(
5240 e,
5241 f
5242 )ˇ»
5243 );
5244 "});
5245 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
5246
5247 // Paste it on a line with a lower indent level
5248 cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
5249 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
5250 cx.assert_editor_state(indoc! {"
5251 const a: B = (
5252 c(),
5253 something(
5254 e,
5255 f
5256 )
5257 );
5258 g(
5259 e,
5260 f
5261 )ˇ"});
5262}
5263
5264#[gpui::test]
5265async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
5266 init_test(cx, |_| {});
5267
5268 cx.write_to_clipboard(ClipboardItem::new_string(
5269 " d(\n e\n );\n".into(),
5270 ));
5271
5272 let mut cx = EditorTestContext::new(cx).await;
5273 cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
5274
5275 cx.set_state(indoc! {"
5276 fn a() {
5277 b();
5278 if c() {
5279 ˇ
5280 }
5281 }
5282 "});
5283
5284 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
5285 cx.assert_editor_state(indoc! {"
5286 fn a() {
5287 b();
5288 if c() {
5289 d(
5290 e
5291 );
5292 ˇ
5293 }
5294 }
5295 "});
5296
5297 cx.set_state(indoc! {"
5298 fn a() {
5299 b();
5300 ˇ
5301 }
5302 "});
5303
5304 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
5305 cx.assert_editor_state(indoc! {"
5306 fn a() {
5307 b();
5308 d(
5309 e
5310 );
5311 ˇ
5312 }
5313 "});
5314}
5315
5316#[gpui::test]
5317fn test_select_all(cx: &mut TestAppContext) {
5318 init_test(cx, |_| {});
5319
5320 let editor = cx.add_window(|window, cx| {
5321 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
5322 build_editor(buffer, window, cx)
5323 });
5324 _ = editor.update(cx, |editor, window, cx| {
5325 editor.select_all(&SelectAll, window, cx);
5326 assert_eq!(
5327 editor.selections.display_ranges(cx),
5328 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
5329 );
5330 });
5331}
5332
5333#[gpui::test]
5334fn test_select_line(cx: &mut TestAppContext) {
5335 init_test(cx, |_| {});
5336
5337 let editor = cx.add_window(|window, cx| {
5338 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
5339 build_editor(buffer, window, cx)
5340 });
5341 _ = editor.update(cx, |editor, window, cx| {
5342 editor.change_selections(None, window, cx, |s| {
5343 s.select_display_ranges([
5344 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5345 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
5346 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
5347 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
5348 ])
5349 });
5350 editor.select_line(&SelectLine, window, cx);
5351 assert_eq!(
5352 editor.selections.display_ranges(cx),
5353 vec![
5354 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
5355 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
5356 ]
5357 );
5358 });
5359
5360 _ = editor.update(cx, |editor, window, cx| {
5361 editor.select_line(&SelectLine, window, cx);
5362 assert_eq!(
5363 editor.selections.display_ranges(cx),
5364 vec![
5365 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
5366 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
5367 ]
5368 );
5369 });
5370
5371 _ = editor.update(cx, |editor, window, cx| {
5372 editor.select_line(&SelectLine, window, cx);
5373 assert_eq!(
5374 editor.selections.display_ranges(cx),
5375 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
5376 );
5377 });
5378}
5379
5380#[gpui::test]
5381async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
5382 init_test(cx, |_| {});
5383 let mut cx = EditorTestContext::new(cx).await;
5384
5385 #[track_caller]
5386 fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
5387 cx.set_state(initial_state);
5388 cx.update_editor(|e, window, cx| {
5389 e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
5390 });
5391 cx.assert_editor_state(expected_state);
5392 }
5393
5394 // Selection starts and ends at the middle of lines, left-to-right
5395 test(
5396 &mut cx,
5397 "aa\nb«ˇb\ncc\ndd\ne»e\nff",
5398 "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
5399 );
5400 // Same thing, right-to-left
5401 test(
5402 &mut cx,
5403 "aa\nb«b\ncc\ndd\neˇ»e\nff",
5404 "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
5405 );
5406
5407 // Whole buffer, left-to-right, last line *doesn't* end with newline
5408 test(
5409 &mut cx,
5410 "«ˇaa\nbb\ncc\ndd\nee\nff»",
5411 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
5412 );
5413 // Same thing, right-to-left
5414 test(
5415 &mut cx,
5416 "«aa\nbb\ncc\ndd\nee\nffˇ»",
5417 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
5418 );
5419
5420 // Whole buffer, left-to-right, last line ends with newline
5421 test(
5422 &mut cx,
5423 "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
5424 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
5425 );
5426 // Same thing, right-to-left
5427 test(
5428 &mut cx,
5429 "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
5430 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
5431 );
5432
5433 // Starts at the end of a line, ends at the start of another
5434 test(
5435 &mut cx,
5436 "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
5437 "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
5438 );
5439}
5440
5441#[gpui::test]
5442async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
5443 init_test(cx, |_| {});
5444
5445 let editor = cx.add_window(|window, cx| {
5446 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
5447 build_editor(buffer, window, cx)
5448 });
5449
5450 // setup
5451 _ = editor.update(cx, |editor, window, cx| {
5452 editor.fold_creases(
5453 vec![
5454 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
5455 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
5456 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
5457 ],
5458 true,
5459 window,
5460 cx,
5461 );
5462 assert_eq!(
5463 editor.display_text(cx),
5464 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
5465 );
5466 });
5467
5468 _ = editor.update(cx, |editor, window, cx| {
5469 editor.change_selections(None, window, cx, |s| {
5470 s.select_display_ranges([
5471 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5472 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
5473 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
5474 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
5475 ])
5476 });
5477 editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
5478 assert_eq!(
5479 editor.display_text(cx),
5480 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
5481 );
5482 });
5483 EditorTestContext::for_editor(editor, cx)
5484 .await
5485 .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
5486
5487 _ = editor.update(cx, |editor, window, cx| {
5488 editor.change_selections(None, window, cx, |s| {
5489 s.select_display_ranges([
5490 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
5491 ])
5492 });
5493 editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
5494 assert_eq!(
5495 editor.display_text(cx),
5496 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
5497 );
5498 assert_eq!(
5499 editor.selections.display_ranges(cx),
5500 [
5501 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
5502 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
5503 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
5504 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
5505 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
5506 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
5507 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
5508 ]
5509 );
5510 });
5511 EditorTestContext::for_editor(editor, cx)
5512 .await
5513 .assert_editor_state(
5514 "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
5515 );
5516}
5517
5518#[gpui::test]
5519async fn test_add_selection_above_below(cx: &mut TestAppContext) {
5520 init_test(cx, |_| {});
5521
5522 let mut cx = EditorTestContext::new(cx).await;
5523
5524 cx.set_state(indoc!(
5525 r#"abc
5526 defˇghi
5527
5528 jk
5529 nlmo
5530 "#
5531 ));
5532
5533 cx.update_editor(|editor, window, cx| {
5534 editor.add_selection_above(&Default::default(), window, cx);
5535 });
5536
5537 cx.assert_editor_state(indoc!(
5538 r#"abcˇ
5539 defˇghi
5540
5541 jk
5542 nlmo
5543 "#
5544 ));
5545
5546 cx.update_editor(|editor, window, cx| {
5547 editor.add_selection_above(&Default::default(), window, cx);
5548 });
5549
5550 cx.assert_editor_state(indoc!(
5551 r#"abcˇ
5552 defˇghi
5553
5554 jk
5555 nlmo
5556 "#
5557 ));
5558
5559 cx.update_editor(|editor, window, cx| {
5560 editor.add_selection_below(&Default::default(), window, cx);
5561 });
5562
5563 cx.assert_editor_state(indoc!(
5564 r#"abc
5565 defˇghi
5566
5567 jk
5568 nlmo
5569 "#
5570 ));
5571
5572 cx.update_editor(|editor, window, cx| {
5573 editor.undo_selection(&Default::default(), window, cx);
5574 });
5575
5576 cx.assert_editor_state(indoc!(
5577 r#"abcˇ
5578 defˇghi
5579
5580 jk
5581 nlmo
5582 "#
5583 ));
5584
5585 cx.update_editor(|editor, window, cx| {
5586 editor.redo_selection(&Default::default(), window, cx);
5587 });
5588
5589 cx.assert_editor_state(indoc!(
5590 r#"abc
5591 defˇghi
5592
5593 jk
5594 nlmo
5595 "#
5596 ));
5597
5598 cx.update_editor(|editor, window, cx| {
5599 editor.add_selection_below(&Default::default(), window, cx);
5600 });
5601
5602 cx.assert_editor_state(indoc!(
5603 r#"abc
5604 defˇghi
5605
5606 jk
5607 nlmˇo
5608 "#
5609 ));
5610
5611 cx.update_editor(|editor, window, cx| {
5612 editor.add_selection_below(&Default::default(), window, cx);
5613 });
5614
5615 cx.assert_editor_state(indoc!(
5616 r#"abc
5617 defˇghi
5618
5619 jk
5620 nlmˇo
5621 "#
5622 ));
5623
5624 // change selections
5625 cx.set_state(indoc!(
5626 r#"abc
5627 def«ˇg»hi
5628
5629 jk
5630 nlmo
5631 "#
5632 ));
5633
5634 cx.update_editor(|editor, window, cx| {
5635 editor.add_selection_below(&Default::default(), window, cx);
5636 });
5637
5638 cx.assert_editor_state(indoc!(
5639 r#"abc
5640 def«ˇg»hi
5641
5642 jk
5643 nlm«ˇo»
5644 "#
5645 ));
5646
5647 cx.update_editor(|editor, window, cx| {
5648 editor.add_selection_below(&Default::default(), window, cx);
5649 });
5650
5651 cx.assert_editor_state(indoc!(
5652 r#"abc
5653 def«ˇg»hi
5654
5655 jk
5656 nlm«ˇo»
5657 "#
5658 ));
5659
5660 cx.update_editor(|editor, window, cx| {
5661 editor.add_selection_above(&Default::default(), window, cx);
5662 });
5663
5664 cx.assert_editor_state(indoc!(
5665 r#"abc
5666 def«ˇg»hi
5667
5668 jk
5669 nlmo
5670 "#
5671 ));
5672
5673 cx.update_editor(|editor, window, cx| {
5674 editor.add_selection_above(&Default::default(), window, cx);
5675 });
5676
5677 cx.assert_editor_state(indoc!(
5678 r#"abc
5679 def«ˇg»hi
5680
5681 jk
5682 nlmo
5683 "#
5684 ));
5685
5686 // Change selections again
5687 cx.set_state(indoc!(
5688 r#"a«bc
5689 defgˇ»hi
5690
5691 jk
5692 nlmo
5693 "#
5694 ));
5695
5696 cx.update_editor(|editor, window, cx| {
5697 editor.add_selection_below(&Default::default(), window, cx);
5698 });
5699
5700 cx.assert_editor_state(indoc!(
5701 r#"a«bcˇ»
5702 d«efgˇ»hi
5703
5704 j«kˇ»
5705 nlmo
5706 "#
5707 ));
5708
5709 cx.update_editor(|editor, window, cx| {
5710 editor.add_selection_below(&Default::default(), window, cx);
5711 });
5712 cx.assert_editor_state(indoc!(
5713 r#"a«bcˇ»
5714 d«efgˇ»hi
5715
5716 j«kˇ»
5717 n«lmoˇ»
5718 "#
5719 ));
5720 cx.update_editor(|editor, window, cx| {
5721 editor.add_selection_above(&Default::default(), window, cx);
5722 });
5723
5724 cx.assert_editor_state(indoc!(
5725 r#"a«bcˇ»
5726 d«efgˇ»hi
5727
5728 j«kˇ»
5729 nlmo
5730 "#
5731 ));
5732
5733 // Change selections again
5734 cx.set_state(indoc!(
5735 r#"abc
5736 d«ˇefghi
5737
5738 jk
5739 nlm»o
5740 "#
5741 ));
5742
5743 cx.update_editor(|editor, window, cx| {
5744 editor.add_selection_above(&Default::default(), window, cx);
5745 });
5746
5747 cx.assert_editor_state(indoc!(
5748 r#"a«ˇbc»
5749 d«ˇef»ghi
5750
5751 j«ˇk»
5752 n«ˇlm»o
5753 "#
5754 ));
5755
5756 cx.update_editor(|editor, window, cx| {
5757 editor.add_selection_below(&Default::default(), window, cx);
5758 });
5759
5760 cx.assert_editor_state(indoc!(
5761 r#"abc
5762 d«ˇef»ghi
5763
5764 j«ˇk»
5765 n«ˇlm»o
5766 "#
5767 ));
5768}
5769
5770#[gpui::test]
5771async fn test_select_next(cx: &mut TestAppContext) {
5772 init_test(cx, |_| {});
5773
5774 let mut cx = EditorTestContext::new(cx).await;
5775 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5776
5777 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5778 .unwrap();
5779 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5780
5781 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5782 .unwrap();
5783 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5784
5785 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5786 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5787
5788 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5789 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5790
5791 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5792 .unwrap();
5793 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5794
5795 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5796 .unwrap();
5797 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5798}
5799
5800#[gpui::test]
5801async fn test_select_all_matches(cx: &mut TestAppContext) {
5802 init_test(cx, |_| {});
5803
5804 let mut cx = EditorTestContext::new(cx).await;
5805
5806 // Test caret-only selections
5807 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5808 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5809 .unwrap();
5810 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5811
5812 // Test left-to-right selections
5813 cx.set_state("abc\n«abcˇ»\nabc");
5814 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5815 .unwrap();
5816 cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
5817
5818 // Test right-to-left selections
5819 cx.set_state("abc\n«ˇabc»\nabc");
5820 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5821 .unwrap();
5822 cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
5823
5824 // Test selecting whitespace with caret selection
5825 cx.set_state("abc\nˇ abc\nabc");
5826 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5827 .unwrap();
5828 cx.assert_editor_state("abc\n« ˇ»abc\nabc");
5829
5830 // Test selecting whitespace with left-to-right selection
5831 cx.set_state("abc\n«ˇ »abc\nabc");
5832 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5833 .unwrap();
5834 cx.assert_editor_state("abc\n«ˇ »abc\nabc");
5835
5836 // Test no matches with right-to-left selection
5837 cx.set_state("abc\n« ˇ»abc\nabc");
5838 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5839 .unwrap();
5840 cx.assert_editor_state("abc\n« ˇ»abc\nabc");
5841}
5842
5843#[gpui::test]
5844async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
5845 init_test(cx, |_| {});
5846
5847 let mut cx = EditorTestContext::new(cx).await;
5848
5849 let large_body_1 = "\nd".repeat(200);
5850 let large_body_2 = "\ne".repeat(200);
5851
5852 cx.set_state(&format!(
5853 "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
5854 ));
5855 let initial_scroll_position = cx.update_editor(|editor, _, cx| {
5856 let scroll_position = editor.scroll_position(cx);
5857 assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
5858 scroll_position
5859 });
5860
5861 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5862 .unwrap();
5863 cx.assert_editor_state(&format!(
5864 "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
5865 ));
5866 let scroll_position_after_selection =
5867 cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
5868 assert_eq!(
5869 initial_scroll_position, scroll_position_after_selection,
5870 "Scroll position should not change after selecting all matches"
5871 );
5872}
5873
5874#[gpui::test]
5875async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
5876 init_test(cx, |_| {});
5877
5878 let mut cx = EditorLspTestContext::new_rust(
5879 lsp::ServerCapabilities {
5880 document_formatting_provider: Some(lsp::OneOf::Left(true)),
5881 ..Default::default()
5882 },
5883 cx,
5884 )
5885 .await;
5886
5887 cx.set_state(indoc! {"
5888 line 1
5889 line 2
5890 linˇe 3
5891 line 4
5892 line 5
5893 "});
5894
5895 // Make an edit
5896 cx.update_editor(|editor, window, cx| {
5897 editor.handle_input("X", window, cx);
5898 });
5899
5900 // Move cursor to a different position
5901 cx.update_editor(|editor, window, cx| {
5902 editor.change_selections(None, window, cx, |s| {
5903 s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
5904 });
5905 });
5906
5907 cx.assert_editor_state(indoc! {"
5908 line 1
5909 line 2
5910 linXe 3
5911 line 4
5912 liˇne 5
5913 "});
5914
5915 cx.lsp
5916 .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
5917 Ok(Some(vec![lsp::TextEdit::new(
5918 lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
5919 "PREFIX ".to_string(),
5920 )]))
5921 });
5922
5923 cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
5924 .unwrap()
5925 .await
5926 .unwrap();
5927
5928 cx.assert_editor_state(indoc! {"
5929 PREFIX line 1
5930 line 2
5931 linXe 3
5932 line 4
5933 liˇne 5
5934 "});
5935
5936 // Undo formatting
5937 cx.update_editor(|editor, window, cx| {
5938 editor.undo(&Default::default(), window, cx);
5939 });
5940
5941 // Verify cursor moved back to position after edit
5942 cx.assert_editor_state(indoc! {"
5943 line 1
5944 line 2
5945 linXˇe 3
5946 line 4
5947 line 5
5948 "});
5949}
5950
5951#[gpui::test]
5952async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
5953 init_test(cx, |_| {});
5954
5955 let mut cx = EditorTestContext::new(cx).await;
5956 cx.set_state(
5957 r#"let foo = 2;
5958lˇet foo = 2;
5959let fooˇ = 2;
5960let foo = 2;
5961let foo = ˇ2;"#,
5962 );
5963
5964 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5965 .unwrap();
5966 cx.assert_editor_state(
5967 r#"let foo = 2;
5968«letˇ» foo = 2;
5969let «fooˇ» = 2;
5970let foo = 2;
5971let foo = «2ˇ»;"#,
5972 );
5973
5974 // noop for multiple selections with different contents
5975 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5976 .unwrap();
5977 cx.assert_editor_state(
5978 r#"let foo = 2;
5979«letˇ» foo = 2;
5980let «fooˇ» = 2;
5981let foo = 2;
5982let foo = «2ˇ»;"#,
5983 );
5984}
5985
5986#[gpui::test]
5987async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
5988 init_test(cx, |_| {});
5989
5990 let mut cx =
5991 EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
5992
5993 cx.assert_editor_state(indoc! {"
5994 ˇbbb
5995 ccc
5996
5997 bbb
5998 ccc
5999 "});
6000 cx.dispatch_action(SelectPrevious::default());
6001 cx.assert_editor_state(indoc! {"
6002 «bbbˇ»
6003 ccc
6004
6005 bbb
6006 ccc
6007 "});
6008 cx.dispatch_action(SelectPrevious::default());
6009 cx.assert_editor_state(indoc! {"
6010 «bbbˇ»
6011 ccc
6012
6013 «bbbˇ»
6014 ccc
6015 "});
6016}
6017
6018#[gpui::test]
6019async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
6020 init_test(cx, |_| {});
6021
6022 let mut cx = EditorTestContext::new(cx).await;
6023 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
6024
6025 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6026 .unwrap();
6027 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
6028
6029 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6030 .unwrap();
6031 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
6032
6033 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
6034 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
6035
6036 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
6037 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
6038
6039 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6040 .unwrap();
6041 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
6042
6043 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6044 .unwrap();
6045 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
6046
6047 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6048 .unwrap();
6049 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
6050}
6051
6052#[gpui::test]
6053async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
6054 init_test(cx, |_| {});
6055
6056 let mut cx = EditorTestContext::new(cx).await;
6057 cx.set_state("aˇ");
6058
6059 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6060 .unwrap();
6061 cx.assert_editor_state("«aˇ»");
6062 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6063 .unwrap();
6064 cx.assert_editor_state("«aˇ»");
6065}
6066
6067#[gpui::test]
6068async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
6069 init_test(cx, |_| {});
6070
6071 let mut cx = EditorTestContext::new(cx).await;
6072 cx.set_state(
6073 r#"let foo = 2;
6074lˇet foo = 2;
6075let fooˇ = 2;
6076let foo = 2;
6077let foo = ˇ2;"#,
6078 );
6079
6080 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6081 .unwrap();
6082 cx.assert_editor_state(
6083 r#"let foo = 2;
6084«letˇ» foo = 2;
6085let «fooˇ» = 2;
6086let foo = 2;
6087let foo = «2ˇ»;"#,
6088 );
6089
6090 // noop for multiple selections with different contents
6091 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6092 .unwrap();
6093 cx.assert_editor_state(
6094 r#"let foo = 2;
6095«letˇ» foo = 2;
6096let «fooˇ» = 2;
6097let foo = 2;
6098let foo = «2ˇ»;"#,
6099 );
6100}
6101
6102#[gpui::test]
6103async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
6104 init_test(cx, |_| {});
6105
6106 let mut cx = EditorTestContext::new(cx).await;
6107 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
6108
6109 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6110 .unwrap();
6111 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
6112
6113 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6114 .unwrap();
6115 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
6116
6117 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
6118 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
6119
6120 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
6121 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
6122
6123 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6124 .unwrap();
6125 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
6126
6127 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6128 .unwrap();
6129 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
6130}
6131
6132#[gpui::test]
6133async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
6134 init_test(cx, |_| {});
6135
6136 let language = Arc::new(Language::new(
6137 LanguageConfig::default(),
6138 Some(tree_sitter_rust::LANGUAGE.into()),
6139 ));
6140
6141 let text = r#"
6142 use mod1::mod2::{mod3, mod4};
6143
6144 fn fn_1(param1: bool, param2: &str) {
6145 let var1 = "text";
6146 }
6147 "#
6148 .unindent();
6149
6150 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6151 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6152 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6153
6154 editor
6155 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6156 .await;
6157
6158 editor.update_in(cx, |editor, window, cx| {
6159 editor.change_selections(None, window, cx, |s| {
6160 s.select_display_ranges([
6161 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
6162 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
6163 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
6164 ]);
6165 });
6166 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6167 });
6168 editor.update(cx, |editor, cx| {
6169 assert_text_with_selections(
6170 editor,
6171 indoc! {r#"
6172 use mod1::mod2::{mod3, «mod4ˇ»};
6173
6174 fn fn_1«ˇ(param1: bool, param2: &str)» {
6175 let var1 = "«ˇtext»";
6176 }
6177 "#},
6178 cx,
6179 );
6180 });
6181
6182 editor.update_in(cx, |editor, window, cx| {
6183 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6184 });
6185 editor.update(cx, |editor, cx| {
6186 assert_text_with_selections(
6187 editor,
6188 indoc! {r#"
6189 use mod1::mod2::«{mod3, mod4}ˇ»;
6190
6191 «ˇfn fn_1(param1: bool, param2: &str) {
6192 let var1 = "text";
6193 }»
6194 "#},
6195 cx,
6196 );
6197 });
6198
6199 editor.update_in(cx, |editor, window, cx| {
6200 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6201 });
6202 assert_eq!(
6203 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
6204 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
6205 );
6206
6207 // Trying to expand the selected syntax node one more time has no effect.
6208 editor.update_in(cx, |editor, window, cx| {
6209 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6210 });
6211 assert_eq!(
6212 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
6213 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
6214 );
6215
6216 editor.update_in(cx, |editor, window, cx| {
6217 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
6218 });
6219 editor.update(cx, |editor, cx| {
6220 assert_text_with_selections(
6221 editor,
6222 indoc! {r#"
6223 use mod1::mod2::«{mod3, mod4}ˇ»;
6224
6225 «ˇfn fn_1(param1: bool, param2: &str) {
6226 let var1 = "text";
6227 }»
6228 "#},
6229 cx,
6230 );
6231 });
6232
6233 editor.update_in(cx, |editor, window, cx| {
6234 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
6235 });
6236 editor.update(cx, |editor, cx| {
6237 assert_text_with_selections(
6238 editor,
6239 indoc! {r#"
6240 use mod1::mod2::{mod3, «mod4ˇ»};
6241
6242 fn fn_1«ˇ(param1: bool, param2: &str)» {
6243 let var1 = "«ˇtext»";
6244 }
6245 "#},
6246 cx,
6247 );
6248 });
6249
6250 editor.update_in(cx, |editor, window, cx| {
6251 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
6252 });
6253 editor.update(cx, |editor, cx| {
6254 assert_text_with_selections(
6255 editor,
6256 indoc! {r#"
6257 use mod1::mod2::{mod3, mo«ˇ»d4};
6258
6259 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
6260 let var1 = "te«ˇ»xt";
6261 }
6262 "#},
6263 cx,
6264 );
6265 });
6266
6267 // Trying to shrink the selected syntax node one more time has no effect.
6268 editor.update_in(cx, |editor, window, cx| {
6269 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
6270 });
6271 editor.update_in(cx, |editor, _, cx| {
6272 assert_text_with_selections(
6273 editor,
6274 indoc! {r#"
6275 use mod1::mod2::{mod3, mo«ˇ»d4};
6276
6277 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
6278 let var1 = "te«ˇ»xt";
6279 }
6280 "#},
6281 cx,
6282 );
6283 });
6284
6285 // Ensure that we keep expanding the selection if the larger selection starts or ends within
6286 // a fold.
6287 editor.update_in(cx, |editor, window, cx| {
6288 editor.fold_creases(
6289 vec![
6290 Crease::simple(
6291 Point::new(0, 21)..Point::new(0, 24),
6292 FoldPlaceholder::test(),
6293 ),
6294 Crease::simple(
6295 Point::new(3, 20)..Point::new(3, 22),
6296 FoldPlaceholder::test(),
6297 ),
6298 ],
6299 true,
6300 window,
6301 cx,
6302 );
6303 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6304 });
6305 editor.update(cx, |editor, cx| {
6306 assert_text_with_selections(
6307 editor,
6308 indoc! {r#"
6309 use mod1::mod2::«{mod3, mod4}ˇ»;
6310
6311 fn fn_1«ˇ(param1: bool, param2: &str)» {
6312 let var1 = "«ˇtext»";
6313 }
6314 "#},
6315 cx,
6316 );
6317 });
6318}
6319
6320#[gpui::test]
6321async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
6322 init_test(cx, |_| {});
6323
6324 let language = Arc::new(Language::new(
6325 LanguageConfig::default(),
6326 Some(tree_sitter_rust::LANGUAGE.into()),
6327 ));
6328
6329 let text = r#"
6330 use mod1::mod2::{mod3, mod4};
6331
6332 fn fn_1(param1: bool, param2: &str) {
6333 let var1 = "hello world";
6334 }
6335 "#
6336 .unindent();
6337
6338 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6339 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6340 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6341
6342 editor
6343 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6344 .await;
6345
6346 // Test 1: Cursor on a letter of a string word
6347 editor.update_in(cx, |editor, window, cx| {
6348 editor.change_selections(None, window, cx, |s| {
6349 s.select_display_ranges([
6350 DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
6351 ]);
6352 });
6353 });
6354 editor.update_in(cx, |editor, window, cx| {
6355 assert_text_with_selections(
6356 editor,
6357 indoc! {r#"
6358 use mod1::mod2::{mod3, mod4};
6359
6360 fn fn_1(param1: bool, param2: &str) {
6361 let var1 = "hˇello world";
6362 }
6363 "#},
6364 cx,
6365 );
6366 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6367 assert_text_with_selections(
6368 editor,
6369 indoc! {r#"
6370 use mod1::mod2::{mod3, mod4};
6371
6372 fn fn_1(param1: bool, param2: &str) {
6373 let var1 = "«ˇhello» world";
6374 }
6375 "#},
6376 cx,
6377 );
6378 });
6379
6380 // Test 2: Partial selection within a word
6381 editor.update_in(cx, |editor, window, cx| {
6382 editor.change_selections(None, window, cx, |s| {
6383 s.select_display_ranges([
6384 DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
6385 ]);
6386 });
6387 });
6388 editor.update_in(cx, |editor, window, cx| {
6389 assert_text_with_selections(
6390 editor,
6391 indoc! {r#"
6392 use mod1::mod2::{mod3, mod4};
6393
6394 fn fn_1(param1: bool, param2: &str) {
6395 let var1 = "h«elˇ»lo world";
6396 }
6397 "#},
6398 cx,
6399 );
6400 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6401 assert_text_with_selections(
6402 editor,
6403 indoc! {r#"
6404 use mod1::mod2::{mod3, mod4};
6405
6406 fn fn_1(param1: bool, param2: &str) {
6407 let var1 = "«ˇhello» world";
6408 }
6409 "#},
6410 cx,
6411 );
6412 });
6413
6414 // Test 3: Complete word already selected
6415 editor.update_in(cx, |editor, window, cx| {
6416 editor.change_selections(None, window, cx, |s| {
6417 s.select_display_ranges([
6418 DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
6419 ]);
6420 });
6421 });
6422 editor.update_in(cx, |editor, window, cx| {
6423 assert_text_with_selections(
6424 editor,
6425 indoc! {r#"
6426 use mod1::mod2::{mod3, mod4};
6427
6428 fn fn_1(param1: bool, param2: &str) {
6429 let var1 = "«helloˇ» world";
6430 }
6431 "#},
6432 cx,
6433 );
6434 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6435 assert_text_with_selections(
6436 editor,
6437 indoc! {r#"
6438 use mod1::mod2::{mod3, mod4};
6439
6440 fn fn_1(param1: bool, param2: &str) {
6441 let var1 = "«hello worldˇ»";
6442 }
6443 "#},
6444 cx,
6445 );
6446 });
6447
6448 // Test 4: Selection spanning across words
6449 editor.update_in(cx, |editor, window, cx| {
6450 editor.change_selections(None, window, cx, |s| {
6451 s.select_display_ranges([
6452 DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
6453 ]);
6454 });
6455 });
6456 editor.update_in(cx, |editor, window, cx| {
6457 assert_text_with_selections(
6458 editor,
6459 indoc! {r#"
6460 use mod1::mod2::{mod3, mod4};
6461
6462 fn fn_1(param1: bool, param2: &str) {
6463 let var1 = "hel«lo woˇ»rld";
6464 }
6465 "#},
6466 cx,
6467 );
6468 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6469 assert_text_with_selections(
6470 editor,
6471 indoc! {r#"
6472 use mod1::mod2::{mod3, mod4};
6473
6474 fn fn_1(param1: bool, param2: &str) {
6475 let var1 = "«ˇhello world»";
6476 }
6477 "#},
6478 cx,
6479 );
6480 });
6481
6482 // Test 5: Expansion beyond string
6483 editor.update_in(cx, |editor, window, cx| {
6484 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6485 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6486 assert_text_with_selections(
6487 editor,
6488 indoc! {r#"
6489 use mod1::mod2::{mod3, mod4};
6490
6491 fn fn_1(param1: bool, param2: &str) {
6492 «ˇlet var1 = "hello world";»
6493 }
6494 "#},
6495 cx,
6496 );
6497 });
6498}
6499
6500#[gpui::test]
6501async fn test_fold_function_bodies(cx: &mut TestAppContext) {
6502 init_test(cx, |_| {});
6503
6504 let base_text = r#"
6505 impl A {
6506 // this is an uncommitted comment
6507
6508 fn b() {
6509 c();
6510 }
6511
6512 // this is another uncommitted comment
6513
6514 fn d() {
6515 // e
6516 // f
6517 }
6518 }
6519
6520 fn g() {
6521 // h
6522 }
6523 "#
6524 .unindent();
6525
6526 let text = r#"
6527 ˇimpl A {
6528
6529 fn b() {
6530 c();
6531 }
6532
6533 fn d() {
6534 // e
6535 // f
6536 }
6537 }
6538
6539 fn g() {
6540 // h
6541 }
6542 "#
6543 .unindent();
6544
6545 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
6546 cx.set_state(&text);
6547 cx.set_head_text(&base_text);
6548 cx.update_editor(|editor, window, cx| {
6549 editor.expand_all_diff_hunks(&Default::default(), window, cx);
6550 });
6551
6552 cx.assert_state_with_diff(
6553 "
6554 ˇimpl A {
6555 - // this is an uncommitted comment
6556
6557 fn b() {
6558 c();
6559 }
6560
6561 - // this is another uncommitted comment
6562 -
6563 fn d() {
6564 // e
6565 // f
6566 }
6567 }
6568
6569 fn g() {
6570 // h
6571 }
6572 "
6573 .unindent(),
6574 );
6575
6576 let expected_display_text = "
6577 impl A {
6578 // this is an uncommitted comment
6579
6580 fn b() {
6581 ⋯
6582 }
6583
6584 // this is another uncommitted comment
6585
6586 fn d() {
6587 ⋯
6588 }
6589 }
6590
6591 fn g() {
6592 ⋯
6593 }
6594 "
6595 .unindent();
6596
6597 cx.update_editor(|editor, window, cx| {
6598 editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
6599 assert_eq!(editor.display_text(cx), expected_display_text);
6600 });
6601}
6602
6603#[gpui::test]
6604async fn test_autoindent(cx: &mut TestAppContext) {
6605 init_test(cx, |_| {});
6606
6607 let language = Arc::new(
6608 Language::new(
6609 LanguageConfig {
6610 brackets: BracketPairConfig {
6611 pairs: vec![
6612 BracketPair {
6613 start: "{".to_string(),
6614 end: "}".to_string(),
6615 close: false,
6616 surround: false,
6617 newline: true,
6618 },
6619 BracketPair {
6620 start: "(".to_string(),
6621 end: ")".to_string(),
6622 close: false,
6623 surround: false,
6624 newline: true,
6625 },
6626 ],
6627 ..Default::default()
6628 },
6629 ..Default::default()
6630 },
6631 Some(tree_sitter_rust::LANGUAGE.into()),
6632 )
6633 .with_indents_query(
6634 r#"
6635 (_ "(" ")" @end) @indent
6636 (_ "{" "}" @end) @indent
6637 "#,
6638 )
6639 .unwrap(),
6640 );
6641
6642 let text = "fn a() {}";
6643
6644 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6645 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6646 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6647 editor
6648 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6649 .await;
6650
6651 editor.update_in(cx, |editor, window, cx| {
6652 editor.change_selections(None, window, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
6653 editor.newline(&Newline, window, cx);
6654 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
6655 assert_eq!(
6656 editor.selections.ranges(cx),
6657 &[
6658 Point::new(1, 4)..Point::new(1, 4),
6659 Point::new(3, 4)..Point::new(3, 4),
6660 Point::new(5, 0)..Point::new(5, 0)
6661 ]
6662 );
6663 });
6664}
6665
6666#[gpui::test]
6667async fn test_autoindent_selections(cx: &mut TestAppContext) {
6668 init_test(cx, |_| {});
6669
6670 {
6671 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
6672 cx.set_state(indoc! {"
6673 impl A {
6674
6675 fn b() {}
6676
6677 «fn c() {
6678
6679 }ˇ»
6680 }
6681 "});
6682
6683 cx.update_editor(|editor, window, cx| {
6684 editor.autoindent(&Default::default(), window, cx);
6685 });
6686
6687 cx.assert_editor_state(indoc! {"
6688 impl A {
6689
6690 fn b() {}
6691
6692 «fn c() {
6693
6694 }ˇ»
6695 }
6696 "});
6697 }
6698
6699 {
6700 let mut cx = EditorTestContext::new_multibuffer(
6701 cx,
6702 [indoc! { "
6703 impl A {
6704 «
6705 // a
6706 fn b(){}
6707 »
6708 «
6709 }
6710 fn c(){}
6711 »
6712 "}],
6713 );
6714
6715 let buffer = cx.update_editor(|editor, _, cx| {
6716 let buffer = editor.buffer().update(cx, |buffer, _| {
6717 buffer.all_buffers().iter().next().unwrap().clone()
6718 });
6719 buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
6720 buffer
6721 });
6722
6723 cx.run_until_parked();
6724 cx.update_editor(|editor, window, cx| {
6725 editor.select_all(&Default::default(), window, cx);
6726 editor.autoindent(&Default::default(), window, cx)
6727 });
6728 cx.run_until_parked();
6729
6730 cx.update(|_, cx| {
6731 assert_eq!(
6732 buffer.read(cx).text(),
6733 indoc! { "
6734 impl A {
6735
6736 // a
6737 fn b(){}
6738
6739
6740 }
6741 fn c(){}
6742
6743 " }
6744 )
6745 });
6746 }
6747}
6748
6749#[gpui::test]
6750async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
6751 init_test(cx, |_| {});
6752
6753 let mut cx = EditorTestContext::new(cx).await;
6754
6755 let language = Arc::new(Language::new(
6756 LanguageConfig {
6757 brackets: BracketPairConfig {
6758 pairs: vec![
6759 BracketPair {
6760 start: "{".to_string(),
6761 end: "}".to_string(),
6762 close: true,
6763 surround: true,
6764 newline: true,
6765 },
6766 BracketPair {
6767 start: "(".to_string(),
6768 end: ")".to_string(),
6769 close: true,
6770 surround: true,
6771 newline: true,
6772 },
6773 BracketPair {
6774 start: "/*".to_string(),
6775 end: " */".to_string(),
6776 close: true,
6777 surround: true,
6778 newline: true,
6779 },
6780 BracketPair {
6781 start: "[".to_string(),
6782 end: "]".to_string(),
6783 close: false,
6784 surround: false,
6785 newline: true,
6786 },
6787 BracketPair {
6788 start: "\"".to_string(),
6789 end: "\"".to_string(),
6790 close: true,
6791 surround: true,
6792 newline: false,
6793 },
6794 BracketPair {
6795 start: "<".to_string(),
6796 end: ">".to_string(),
6797 close: false,
6798 surround: true,
6799 newline: true,
6800 },
6801 ],
6802 ..Default::default()
6803 },
6804 autoclose_before: "})]".to_string(),
6805 ..Default::default()
6806 },
6807 Some(tree_sitter_rust::LANGUAGE.into()),
6808 ));
6809
6810 cx.language_registry().add(language.clone());
6811 cx.update_buffer(|buffer, cx| {
6812 buffer.set_language(Some(language), cx);
6813 });
6814
6815 cx.set_state(
6816 &r#"
6817 🏀ˇ
6818 εˇ
6819 ❤️ˇ
6820 "#
6821 .unindent(),
6822 );
6823
6824 // autoclose multiple nested brackets at multiple cursors
6825 cx.update_editor(|editor, window, cx| {
6826 editor.handle_input("{", window, cx);
6827 editor.handle_input("{", window, cx);
6828 editor.handle_input("{", window, cx);
6829 });
6830 cx.assert_editor_state(
6831 &"
6832 🏀{{{ˇ}}}
6833 ε{{{ˇ}}}
6834 ❤️{{{ˇ}}}
6835 "
6836 .unindent(),
6837 );
6838
6839 // insert a different closing bracket
6840 cx.update_editor(|editor, window, cx| {
6841 editor.handle_input(")", window, cx);
6842 });
6843 cx.assert_editor_state(
6844 &"
6845 🏀{{{)ˇ}}}
6846 ε{{{)ˇ}}}
6847 ❤️{{{)ˇ}}}
6848 "
6849 .unindent(),
6850 );
6851
6852 // skip over the auto-closed brackets when typing a closing bracket
6853 cx.update_editor(|editor, window, cx| {
6854 editor.move_right(&MoveRight, window, cx);
6855 editor.handle_input("}", window, cx);
6856 editor.handle_input("}", window, cx);
6857 editor.handle_input("}", window, cx);
6858 });
6859 cx.assert_editor_state(
6860 &"
6861 🏀{{{)}}}}ˇ
6862 ε{{{)}}}}ˇ
6863 ❤️{{{)}}}}ˇ
6864 "
6865 .unindent(),
6866 );
6867
6868 // autoclose multi-character pairs
6869 cx.set_state(
6870 &"
6871 ˇ
6872 ˇ
6873 "
6874 .unindent(),
6875 );
6876 cx.update_editor(|editor, window, cx| {
6877 editor.handle_input("/", window, cx);
6878 editor.handle_input("*", window, cx);
6879 });
6880 cx.assert_editor_state(
6881 &"
6882 /*ˇ */
6883 /*ˇ */
6884 "
6885 .unindent(),
6886 );
6887
6888 // one cursor autocloses a multi-character pair, one cursor
6889 // does not autoclose.
6890 cx.set_state(
6891 &"
6892 /ˇ
6893 ˇ
6894 "
6895 .unindent(),
6896 );
6897 cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
6898 cx.assert_editor_state(
6899 &"
6900 /*ˇ */
6901 *ˇ
6902 "
6903 .unindent(),
6904 );
6905
6906 // Don't autoclose if the next character isn't whitespace and isn't
6907 // listed in the language's "autoclose_before" section.
6908 cx.set_state("ˇa b");
6909 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
6910 cx.assert_editor_state("{ˇa b");
6911
6912 // Don't autoclose if `close` is false for the bracket pair
6913 cx.set_state("ˇ");
6914 cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
6915 cx.assert_editor_state("[ˇ");
6916
6917 // Surround with brackets if text is selected
6918 cx.set_state("«aˇ» b");
6919 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
6920 cx.assert_editor_state("{«aˇ»} b");
6921
6922 // Autoclose when not immediately after a word character
6923 cx.set_state("a ˇ");
6924 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6925 cx.assert_editor_state("a \"ˇ\"");
6926
6927 // Autoclose pair where the start and end characters are the same
6928 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6929 cx.assert_editor_state("a \"\"ˇ");
6930
6931 // Don't autoclose when immediately after a word character
6932 cx.set_state("aˇ");
6933 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6934 cx.assert_editor_state("a\"ˇ");
6935
6936 // Do autoclose when after a non-word character
6937 cx.set_state("{ˇ");
6938 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6939 cx.assert_editor_state("{\"ˇ\"");
6940
6941 // Non identical pairs autoclose regardless of preceding character
6942 cx.set_state("aˇ");
6943 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
6944 cx.assert_editor_state("a{ˇ}");
6945
6946 // Don't autoclose pair if autoclose is disabled
6947 cx.set_state("ˇ");
6948 cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
6949 cx.assert_editor_state("<ˇ");
6950
6951 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
6952 cx.set_state("«aˇ» b");
6953 cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
6954 cx.assert_editor_state("<«aˇ»> b");
6955}
6956
6957#[gpui::test]
6958async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
6959 init_test(cx, |settings| {
6960 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6961 });
6962
6963 let mut cx = EditorTestContext::new(cx).await;
6964
6965 let language = Arc::new(Language::new(
6966 LanguageConfig {
6967 brackets: BracketPairConfig {
6968 pairs: vec![
6969 BracketPair {
6970 start: "{".to_string(),
6971 end: "}".to_string(),
6972 close: true,
6973 surround: true,
6974 newline: true,
6975 },
6976 BracketPair {
6977 start: "(".to_string(),
6978 end: ")".to_string(),
6979 close: true,
6980 surround: true,
6981 newline: true,
6982 },
6983 BracketPair {
6984 start: "[".to_string(),
6985 end: "]".to_string(),
6986 close: false,
6987 surround: false,
6988 newline: true,
6989 },
6990 ],
6991 ..Default::default()
6992 },
6993 autoclose_before: "})]".to_string(),
6994 ..Default::default()
6995 },
6996 Some(tree_sitter_rust::LANGUAGE.into()),
6997 ));
6998
6999 cx.language_registry().add(language.clone());
7000 cx.update_buffer(|buffer, cx| {
7001 buffer.set_language(Some(language), cx);
7002 });
7003
7004 cx.set_state(
7005 &"
7006 ˇ
7007 ˇ
7008 ˇ
7009 "
7010 .unindent(),
7011 );
7012
7013 // ensure only matching closing brackets are skipped over
7014 cx.update_editor(|editor, window, cx| {
7015 editor.handle_input("}", window, cx);
7016 editor.move_left(&MoveLeft, window, cx);
7017 editor.handle_input(")", window, cx);
7018 editor.move_left(&MoveLeft, window, cx);
7019 });
7020 cx.assert_editor_state(
7021 &"
7022 ˇ)}
7023 ˇ)}
7024 ˇ)}
7025 "
7026 .unindent(),
7027 );
7028
7029 // skip-over closing brackets at multiple cursors
7030 cx.update_editor(|editor, window, cx| {
7031 editor.handle_input(")", window, cx);
7032 editor.handle_input("}", window, cx);
7033 });
7034 cx.assert_editor_state(
7035 &"
7036 )}ˇ
7037 )}ˇ
7038 )}ˇ
7039 "
7040 .unindent(),
7041 );
7042
7043 // ignore non-close brackets
7044 cx.update_editor(|editor, window, cx| {
7045 editor.handle_input("]", window, cx);
7046 editor.move_left(&MoveLeft, window, cx);
7047 editor.handle_input("]", window, cx);
7048 });
7049 cx.assert_editor_state(
7050 &"
7051 )}]ˇ]
7052 )}]ˇ]
7053 )}]ˇ]
7054 "
7055 .unindent(),
7056 );
7057}
7058
7059#[gpui::test]
7060async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
7061 init_test(cx, |_| {});
7062
7063 let mut cx = EditorTestContext::new(cx).await;
7064
7065 let html_language = Arc::new(
7066 Language::new(
7067 LanguageConfig {
7068 name: "HTML".into(),
7069 brackets: BracketPairConfig {
7070 pairs: vec![
7071 BracketPair {
7072 start: "<".into(),
7073 end: ">".into(),
7074 close: true,
7075 ..Default::default()
7076 },
7077 BracketPair {
7078 start: "{".into(),
7079 end: "}".into(),
7080 close: true,
7081 ..Default::default()
7082 },
7083 BracketPair {
7084 start: "(".into(),
7085 end: ")".into(),
7086 close: true,
7087 ..Default::default()
7088 },
7089 ],
7090 ..Default::default()
7091 },
7092 autoclose_before: "})]>".into(),
7093 ..Default::default()
7094 },
7095 Some(tree_sitter_html::LANGUAGE.into()),
7096 )
7097 .with_injection_query(
7098 r#"
7099 (script_element
7100 (raw_text) @injection.content
7101 (#set! injection.language "javascript"))
7102 "#,
7103 )
7104 .unwrap(),
7105 );
7106
7107 let javascript_language = Arc::new(Language::new(
7108 LanguageConfig {
7109 name: "JavaScript".into(),
7110 brackets: BracketPairConfig {
7111 pairs: vec![
7112 BracketPair {
7113 start: "/*".into(),
7114 end: " */".into(),
7115 close: true,
7116 ..Default::default()
7117 },
7118 BracketPair {
7119 start: "{".into(),
7120 end: "}".into(),
7121 close: true,
7122 ..Default::default()
7123 },
7124 BracketPair {
7125 start: "(".into(),
7126 end: ")".into(),
7127 close: true,
7128 ..Default::default()
7129 },
7130 ],
7131 ..Default::default()
7132 },
7133 autoclose_before: "})]>".into(),
7134 ..Default::default()
7135 },
7136 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
7137 ));
7138
7139 cx.language_registry().add(html_language.clone());
7140 cx.language_registry().add(javascript_language.clone());
7141
7142 cx.update_buffer(|buffer, cx| {
7143 buffer.set_language(Some(html_language), cx);
7144 });
7145
7146 cx.set_state(
7147 &r#"
7148 <body>ˇ
7149 <script>
7150 var x = 1;ˇ
7151 </script>
7152 </body>ˇ
7153 "#
7154 .unindent(),
7155 );
7156
7157 // Precondition: different languages are active at different locations.
7158 cx.update_editor(|editor, window, cx| {
7159 let snapshot = editor.snapshot(window, cx);
7160 let cursors = editor.selections.ranges::<usize>(cx);
7161 let languages = cursors
7162 .iter()
7163 .map(|c| snapshot.language_at(c.start).unwrap().name())
7164 .collect::<Vec<_>>();
7165 assert_eq!(
7166 languages,
7167 &["HTML".into(), "JavaScript".into(), "HTML".into()]
7168 );
7169 });
7170
7171 // Angle brackets autoclose in HTML, but not JavaScript.
7172 cx.update_editor(|editor, window, cx| {
7173 editor.handle_input("<", window, cx);
7174 editor.handle_input("a", window, cx);
7175 });
7176 cx.assert_editor_state(
7177 &r#"
7178 <body><aˇ>
7179 <script>
7180 var x = 1;<aˇ
7181 </script>
7182 </body><aˇ>
7183 "#
7184 .unindent(),
7185 );
7186
7187 // Curly braces and parens autoclose in both HTML and JavaScript.
7188 cx.update_editor(|editor, window, cx| {
7189 editor.handle_input(" b=", window, cx);
7190 editor.handle_input("{", window, cx);
7191 editor.handle_input("c", window, cx);
7192 editor.handle_input("(", window, cx);
7193 });
7194 cx.assert_editor_state(
7195 &r#"
7196 <body><a b={c(ˇ)}>
7197 <script>
7198 var x = 1;<a b={c(ˇ)}
7199 </script>
7200 </body><a b={c(ˇ)}>
7201 "#
7202 .unindent(),
7203 );
7204
7205 // Brackets that were already autoclosed are skipped.
7206 cx.update_editor(|editor, window, cx| {
7207 editor.handle_input(")", window, cx);
7208 editor.handle_input("d", window, cx);
7209 editor.handle_input("}", window, cx);
7210 });
7211 cx.assert_editor_state(
7212 &r#"
7213 <body><a b={c()d}ˇ>
7214 <script>
7215 var x = 1;<a b={c()d}ˇ
7216 </script>
7217 </body><a b={c()d}ˇ>
7218 "#
7219 .unindent(),
7220 );
7221 cx.update_editor(|editor, window, cx| {
7222 editor.handle_input(">", window, cx);
7223 });
7224 cx.assert_editor_state(
7225 &r#"
7226 <body><a b={c()d}>ˇ
7227 <script>
7228 var x = 1;<a b={c()d}>ˇ
7229 </script>
7230 </body><a b={c()d}>ˇ
7231 "#
7232 .unindent(),
7233 );
7234
7235 // Reset
7236 cx.set_state(
7237 &r#"
7238 <body>ˇ
7239 <script>
7240 var x = 1;ˇ
7241 </script>
7242 </body>ˇ
7243 "#
7244 .unindent(),
7245 );
7246
7247 cx.update_editor(|editor, window, cx| {
7248 editor.handle_input("<", window, cx);
7249 });
7250 cx.assert_editor_state(
7251 &r#"
7252 <body><ˇ>
7253 <script>
7254 var x = 1;<ˇ
7255 </script>
7256 </body><ˇ>
7257 "#
7258 .unindent(),
7259 );
7260
7261 // When backspacing, the closing angle brackets are removed.
7262 cx.update_editor(|editor, window, cx| {
7263 editor.backspace(&Backspace, window, cx);
7264 });
7265 cx.assert_editor_state(
7266 &r#"
7267 <body>ˇ
7268 <script>
7269 var x = 1;ˇ
7270 </script>
7271 </body>ˇ
7272 "#
7273 .unindent(),
7274 );
7275
7276 // Block comments autoclose in JavaScript, but not HTML.
7277 cx.update_editor(|editor, window, cx| {
7278 editor.handle_input("/", window, cx);
7279 editor.handle_input("*", window, cx);
7280 });
7281 cx.assert_editor_state(
7282 &r#"
7283 <body>/*ˇ
7284 <script>
7285 var x = 1;/*ˇ */
7286 </script>
7287 </body>/*ˇ
7288 "#
7289 .unindent(),
7290 );
7291}
7292
7293#[gpui::test]
7294async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
7295 init_test(cx, |_| {});
7296
7297 let mut cx = EditorTestContext::new(cx).await;
7298
7299 let rust_language = Arc::new(
7300 Language::new(
7301 LanguageConfig {
7302 name: "Rust".into(),
7303 brackets: serde_json::from_value(json!([
7304 { "start": "{", "end": "}", "close": true, "newline": true },
7305 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
7306 ]))
7307 .unwrap(),
7308 autoclose_before: "})]>".into(),
7309 ..Default::default()
7310 },
7311 Some(tree_sitter_rust::LANGUAGE.into()),
7312 )
7313 .with_override_query("(string_literal) @string")
7314 .unwrap(),
7315 );
7316
7317 cx.language_registry().add(rust_language.clone());
7318 cx.update_buffer(|buffer, cx| {
7319 buffer.set_language(Some(rust_language), cx);
7320 });
7321
7322 cx.set_state(
7323 &r#"
7324 let x = ˇ
7325 "#
7326 .unindent(),
7327 );
7328
7329 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
7330 cx.update_editor(|editor, window, cx| {
7331 editor.handle_input("\"", window, cx);
7332 });
7333 cx.assert_editor_state(
7334 &r#"
7335 let x = "ˇ"
7336 "#
7337 .unindent(),
7338 );
7339
7340 // Inserting another quotation mark. The cursor moves across the existing
7341 // automatically-inserted quotation mark.
7342 cx.update_editor(|editor, window, cx| {
7343 editor.handle_input("\"", window, cx);
7344 });
7345 cx.assert_editor_state(
7346 &r#"
7347 let x = ""ˇ
7348 "#
7349 .unindent(),
7350 );
7351
7352 // Reset
7353 cx.set_state(
7354 &r#"
7355 let x = ˇ
7356 "#
7357 .unindent(),
7358 );
7359
7360 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
7361 cx.update_editor(|editor, window, cx| {
7362 editor.handle_input("\"", window, cx);
7363 editor.handle_input(" ", window, cx);
7364 editor.move_left(&Default::default(), window, cx);
7365 editor.handle_input("\\", window, cx);
7366 editor.handle_input("\"", window, cx);
7367 });
7368 cx.assert_editor_state(
7369 &r#"
7370 let x = "\"ˇ "
7371 "#
7372 .unindent(),
7373 );
7374
7375 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
7376 // mark. Nothing is inserted.
7377 cx.update_editor(|editor, window, cx| {
7378 editor.move_right(&Default::default(), window, cx);
7379 editor.handle_input("\"", window, cx);
7380 });
7381 cx.assert_editor_state(
7382 &r#"
7383 let x = "\" "ˇ
7384 "#
7385 .unindent(),
7386 );
7387}
7388
7389#[gpui::test]
7390async fn test_surround_with_pair(cx: &mut TestAppContext) {
7391 init_test(cx, |_| {});
7392
7393 let language = Arc::new(Language::new(
7394 LanguageConfig {
7395 brackets: BracketPairConfig {
7396 pairs: vec![
7397 BracketPair {
7398 start: "{".to_string(),
7399 end: "}".to_string(),
7400 close: true,
7401 surround: true,
7402 newline: true,
7403 },
7404 BracketPair {
7405 start: "/* ".to_string(),
7406 end: "*/".to_string(),
7407 close: true,
7408 surround: true,
7409 ..Default::default()
7410 },
7411 ],
7412 ..Default::default()
7413 },
7414 ..Default::default()
7415 },
7416 Some(tree_sitter_rust::LANGUAGE.into()),
7417 ));
7418
7419 let text = r#"
7420 a
7421 b
7422 c
7423 "#
7424 .unindent();
7425
7426 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
7427 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7428 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7429 editor
7430 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
7431 .await;
7432
7433 editor.update_in(cx, |editor, window, cx| {
7434 editor.change_selections(None, window, cx, |s| {
7435 s.select_display_ranges([
7436 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
7437 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
7438 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
7439 ])
7440 });
7441
7442 editor.handle_input("{", window, cx);
7443 editor.handle_input("{", window, cx);
7444 editor.handle_input("{", window, cx);
7445 assert_eq!(
7446 editor.text(cx),
7447 "
7448 {{{a}}}
7449 {{{b}}}
7450 {{{c}}}
7451 "
7452 .unindent()
7453 );
7454 assert_eq!(
7455 editor.selections.display_ranges(cx),
7456 [
7457 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
7458 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
7459 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
7460 ]
7461 );
7462
7463 editor.undo(&Undo, window, cx);
7464 editor.undo(&Undo, window, cx);
7465 editor.undo(&Undo, window, cx);
7466 assert_eq!(
7467 editor.text(cx),
7468 "
7469 a
7470 b
7471 c
7472 "
7473 .unindent()
7474 );
7475 assert_eq!(
7476 editor.selections.display_ranges(cx),
7477 [
7478 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
7479 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
7480 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
7481 ]
7482 );
7483
7484 // Ensure inserting the first character of a multi-byte bracket pair
7485 // doesn't surround the selections with the bracket.
7486 editor.handle_input("/", window, cx);
7487 assert_eq!(
7488 editor.text(cx),
7489 "
7490 /
7491 /
7492 /
7493 "
7494 .unindent()
7495 );
7496 assert_eq!(
7497 editor.selections.display_ranges(cx),
7498 [
7499 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
7500 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
7501 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
7502 ]
7503 );
7504
7505 editor.undo(&Undo, window, cx);
7506 assert_eq!(
7507 editor.text(cx),
7508 "
7509 a
7510 b
7511 c
7512 "
7513 .unindent()
7514 );
7515 assert_eq!(
7516 editor.selections.display_ranges(cx),
7517 [
7518 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
7519 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
7520 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
7521 ]
7522 );
7523
7524 // Ensure inserting the last character of a multi-byte bracket pair
7525 // doesn't surround the selections with the bracket.
7526 editor.handle_input("*", window, cx);
7527 assert_eq!(
7528 editor.text(cx),
7529 "
7530 *
7531 *
7532 *
7533 "
7534 .unindent()
7535 );
7536 assert_eq!(
7537 editor.selections.display_ranges(cx),
7538 [
7539 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
7540 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
7541 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
7542 ]
7543 );
7544 });
7545}
7546
7547#[gpui::test]
7548async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
7549 init_test(cx, |_| {});
7550
7551 let language = Arc::new(Language::new(
7552 LanguageConfig {
7553 brackets: BracketPairConfig {
7554 pairs: vec![BracketPair {
7555 start: "{".to_string(),
7556 end: "}".to_string(),
7557 close: true,
7558 surround: true,
7559 newline: true,
7560 }],
7561 ..Default::default()
7562 },
7563 autoclose_before: "}".to_string(),
7564 ..Default::default()
7565 },
7566 Some(tree_sitter_rust::LANGUAGE.into()),
7567 ));
7568
7569 let text = r#"
7570 a
7571 b
7572 c
7573 "#
7574 .unindent();
7575
7576 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
7577 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7578 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7579 editor
7580 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
7581 .await;
7582
7583 editor.update_in(cx, |editor, window, cx| {
7584 editor.change_selections(None, window, cx, |s| {
7585 s.select_ranges([
7586 Point::new(0, 1)..Point::new(0, 1),
7587 Point::new(1, 1)..Point::new(1, 1),
7588 Point::new(2, 1)..Point::new(2, 1),
7589 ])
7590 });
7591
7592 editor.handle_input("{", window, cx);
7593 editor.handle_input("{", window, cx);
7594 editor.handle_input("_", window, cx);
7595 assert_eq!(
7596 editor.text(cx),
7597 "
7598 a{{_}}
7599 b{{_}}
7600 c{{_}}
7601 "
7602 .unindent()
7603 );
7604 assert_eq!(
7605 editor.selections.ranges::<Point>(cx),
7606 [
7607 Point::new(0, 4)..Point::new(0, 4),
7608 Point::new(1, 4)..Point::new(1, 4),
7609 Point::new(2, 4)..Point::new(2, 4)
7610 ]
7611 );
7612
7613 editor.backspace(&Default::default(), window, cx);
7614 editor.backspace(&Default::default(), 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.ranges::<Point>(cx),
7626 [
7627 Point::new(0, 2)..Point::new(0, 2),
7628 Point::new(1, 2)..Point::new(1, 2),
7629 Point::new(2, 2)..Point::new(2, 2)
7630 ]
7631 );
7632
7633 editor.delete_to_previous_word_start(&Default::default(), window, cx);
7634 assert_eq!(
7635 editor.text(cx),
7636 "
7637 a
7638 b
7639 c
7640 "
7641 .unindent()
7642 );
7643 assert_eq!(
7644 editor.selections.ranges::<Point>(cx),
7645 [
7646 Point::new(0, 1)..Point::new(0, 1),
7647 Point::new(1, 1)..Point::new(1, 1),
7648 Point::new(2, 1)..Point::new(2, 1)
7649 ]
7650 );
7651 });
7652}
7653
7654#[gpui::test]
7655async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
7656 init_test(cx, |settings| {
7657 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
7658 });
7659
7660 let mut cx = EditorTestContext::new(cx).await;
7661
7662 let language = Arc::new(Language::new(
7663 LanguageConfig {
7664 brackets: BracketPairConfig {
7665 pairs: vec![
7666 BracketPair {
7667 start: "{".to_string(),
7668 end: "}".to_string(),
7669 close: true,
7670 surround: true,
7671 newline: true,
7672 },
7673 BracketPair {
7674 start: "(".to_string(),
7675 end: ")".to_string(),
7676 close: true,
7677 surround: true,
7678 newline: true,
7679 },
7680 BracketPair {
7681 start: "[".to_string(),
7682 end: "]".to_string(),
7683 close: false,
7684 surround: true,
7685 newline: true,
7686 },
7687 ],
7688 ..Default::default()
7689 },
7690 autoclose_before: "})]".to_string(),
7691 ..Default::default()
7692 },
7693 Some(tree_sitter_rust::LANGUAGE.into()),
7694 ));
7695
7696 cx.language_registry().add(language.clone());
7697 cx.update_buffer(|buffer, cx| {
7698 buffer.set_language(Some(language), cx);
7699 });
7700
7701 cx.set_state(
7702 &"
7703 {(ˇ)}
7704 [[ˇ]]
7705 {(ˇ)}
7706 "
7707 .unindent(),
7708 );
7709
7710 cx.update_editor(|editor, window, cx| {
7711 editor.backspace(&Default::default(), window, cx);
7712 editor.backspace(&Default::default(), window, cx);
7713 });
7714
7715 cx.assert_editor_state(
7716 &"
7717 ˇ
7718 ˇ]]
7719 ˇ
7720 "
7721 .unindent(),
7722 );
7723
7724 cx.update_editor(|editor, window, cx| {
7725 editor.handle_input("{", window, cx);
7726 editor.handle_input("{", window, cx);
7727 editor.move_right(&MoveRight, window, cx);
7728 editor.move_right(&MoveRight, window, cx);
7729 editor.move_left(&MoveLeft, window, cx);
7730 editor.move_left(&MoveLeft, window, cx);
7731 editor.backspace(&Default::default(), window, cx);
7732 });
7733
7734 cx.assert_editor_state(
7735 &"
7736 {ˇ}
7737 {ˇ}]]
7738 {ˇ}
7739 "
7740 .unindent(),
7741 );
7742
7743 cx.update_editor(|editor, window, cx| {
7744 editor.backspace(&Default::default(), window, cx);
7745 });
7746
7747 cx.assert_editor_state(
7748 &"
7749 ˇ
7750 ˇ]]
7751 ˇ
7752 "
7753 .unindent(),
7754 );
7755}
7756
7757#[gpui::test]
7758async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
7759 init_test(cx, |_| {});
7760
7761 let language = Arc::new(Language::new(
7762 LanguageConfig::default(),
7763 Some(tree_sitter_rust::LANGUAGE.into()),
7764 ));
7765
7766 let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
7767 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7768 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7769 editor
7770 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
7771 .await;
7772
7773 editor.update_in(cx, |editor, window, cx| {
7774 editor.set_auto_replace_emoji_shortcode(true);
7775
7776 editor.handle_input("Hello ", window, cx);
7777 editor.handle_input(":wave", window, cx);
7778 assert_eq!(editor.text(cx), "Hello :wave".unindent());
7779
7780 editor.handle_input(":", window, cx);
7781 assert_eq!(editor.text(cx), "Hello 👋".unindent());
7782
7783 editor.handle_input(" :smile", window, cx);
7784 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
7785
7786 editor.handle_input(":", window, cx);
7787 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
7788
7789 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
7790 editor.handle_input(":wave", window, cx);
7791 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
7792
7793 editor.handle_input(":", window, cx);
7794 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
7795
7796 editor.handle_input(":1", window, cx);
7797 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
7798
7799 editor.handle_input(":", window, cx);
7800 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
7801
7802 // Ensure shortcode does not get replaced when it is part of a word
7803 editor.handle_input(" Test:wave", window, cx);
7804 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
7805
7806 editor.handle_input(":", window, cx);
7807 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
7808
7809 editor.set_auto_replace_emoji_shortcode(false);
7810
7811 // Ensure shortcode does not get replaced when auto replace is off
7812 editor.handle_input(" :wave", window, cx);
7813 assert_eq!(
7814 editor.text(cx),
7815 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
7816 );
7817
7818 editor.handle_input(":", window, cx);
7819 assert_eq!(
7820 editor.text(cx),
7821 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
7822 );
7823 });
7824}
7825
7826#[gpui::test]
7827async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
7828 init_test(cx, |_| {});
7829
7830 let (text, insertion_ranges) = marked_text_ranges(
7831 indoc! {"
7832 ˇ
7833 "},
7834 false,
7835 );
7836
7837 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
7838 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7839
7840 _ = editor.update_in(cx, |editor, window, cx| {
7841 let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
7842
7843 editor
7844 .insert_snippet(&insertion_ranges, snippet, window, cx)
7845 .unwrap();
7846
7847 fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
7848 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
7849 assert_eq!(editor.text(cx), expected_text);
7850 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
7851 }
7852
7853 assert(
7854 editor,
7855 cx,
7856 indoc! {"
7857 type «» =•
7858 "},
7859 );
7860
7861 assert!(editor.context_menu_visible(), "There should be a matches");
7862 });
7863}
7864
7865#[gpui::test]
7866async fn test_snippets(cx: &mut TestAppContext) {
7867 init_test(cx, |_| {});
7868
7869 let (text, insertion_ranges) = marked_text_ranges(
7870 indoc! {"
7871 a.ˇ b
7872 a.ˇ b
7873 a.ˇ b
7874 "},
7875 false,
7876 );
7877
7878 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
7879 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7880
7881 editor.update_in(cx, |editor, window, cx| {
7882 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
7883
7884 editor
7885 .insert_snippet(&insertion_ranges, snippet, window, cx)
7886 .unwrap();
7887
7888 fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
7889 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
7890 assert_eq!(editor.text(cx), expected_text);
7891 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
7892 }
7893
7894 assert(
7895 editor,
7896 cx,
7897 indoc! {"
7898 a.f(«one», two, «three») b
7899 a.f(«one», two, «three») b
7900 a.f(«one», two, «three») b
7901 "},
7902 );
7903
7904 // Can't move earlier than the first tab stop
7905 assert!(!editor.move_to_prev_snippet_tabstop(window, cx));
7906 assert(
7907 editor,
7908 cx,
7909 indoc! {"
7910 a.f(«one», two, «three») b
7911 a.f(«one», two, «three») b
7912 a.f(«one», two, «three») b
7913 "},
7914 );
7915
7916 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7917 assert(
7918 editor,
7919 cx,
7920 indoc! {"
7921 a.f(one, «two», three) b
7922 a.f(one, «two», three) b
7923 a.f(one, «two», three) b
7924 "},
7925 );
7926
7927 editor.move_to_prev_snippet_tabstop(window, cx);
7928 assert(
7929 editor,
7930 cx,
7931 indoc! {"
7932 a.f(«one», two, «three») b
7933 a.f(«one», two, «three») b
7934 a.f(«one», two, «three») b
7935 "},
7936 );
7937
7938 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7939 assert(
7940 editor,
7941 cx,
7942 indoc! {"
7943 a.f(one, «two», three) b
7944 a.f(one, «two», three) b
7945 a.f(one, «two», three) b
7946 "},
7947 );
7948 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7949 assert(
7950 editor,
7951 cx,
7952 indoc! {"
7953 a.f(one, two, three)ˇ b
7954 a.f(one, two, three)ˇ b
7955 a.f(one, two, three)ˇ b
7956 "},
7957 );
7958
7959 // As soon as the last tab stop is reached, snippet state is gone
7960 editor.move_to_prev_snippet_tabstop(window, cx);
7961 assert(
7962 editor,
7963 cx,
7964 indoc! {"
7965 a.f(one, two, three)ˇ b
7966 a.f(one, two, three)ˇ b
7967 a.f(one, two, three)ˇ b
7968 "},
7969 );
7970 });
7971}
7972
7973#[gpui::test]
7974async fn test_document_format_during_save(cx: &mut TestAppContext) {
7975 init_test(cx, |_| {});
7976
7977 let fs = FakeFs::new(cx.executor());
7978 fs.insert_file(path!("/file.rs"), Default::default()).await;
7979
7980 let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
7981
7982 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7983 language_registry.add(rust_lang());
7984 let mut fake_servers = language_registry.register_fake_lsp(
7985 "Rust",
7986 FakeLspAdapter {
7987 capabilities: lsp::ServerCapabilities {
7988 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7989 ..Default::default()
7990 },
7991 ..Default::default()
7992 },
7993 );
7994
7995 let buffer = project
7996 .update(cx, |project, cx| {
7997 project.open_local_buffer(path!("/file.rs"), cx)
7998 })
7999 .await
8000 .unwrap();
8001
8002 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
8003 let (editor, cx) = cx.add_window_view(|window, cx| {
8004 build_editor_with_project(project.clone(), buffer, window, cx)
8005 });
8006 editor.update_in(cx, |editor, window, cx| {
8007 editor.set_text("one\ntwo\nthree\n", window, cx)
8008 });
8009 assert!(cx.read(|cx| editor.is_dirty(cx)));
8010
8011 cx.executor().start_waiting();
8012 let fake_server = fake_servers.next().await.unwrap();
8013
8014 {
8015 fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
8016 move |params, _| async move {
8017 assert_eq!(
8018 params.text_document.uri,
8019 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
8020 );
8021 assert_eq!(params.options.tab_size, 4);
8022 Ok(Some(vec![lsp::TextEdit::new(
8023 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
8024 ", ".to_string(),
8025 )]))
8026 },
8027 );
8028 let save = editor
8029 .update_in(cx, |editor, window, cx| {
8030 editor.save(true, project.clone(), window, cx)
8031 })
8032 .unwrap();
8033 cx.executor().start_waiting();
8034 save.await;
8035
8036 assert_eq!(
8037 editor.update(cx, |editor, cx| editor.text(cx)),
8038 "one, two\nthree\n"
8039 );
8040 assert!(!cx.read(|cx| editor.is_dirty(cx)));
8041 }
8042
8043 {
8044 editor.update_in(cx, |editor, window, cx| {
8045 editor.set_text("one\ntwo\nthree\n", window, cx)
8046 });
8047 assert!(cx.read(|cx| editor.is_dirty(cx)));
8048
8049 // Ensure we can still save even if formatting hangs.
8050 fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
8051 move |params, _| async move {
8052 assert_eq!(
8053 params.text_document.uri,
8054 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
8055 );
8056 futures::future::pending::<()>().await;
8057 unreachable!()
8058 },
8059 );
8060 let save = editor
8061 .update_in(cx, |editor, window, cx| {
8062 editor.save(true, project.clone(), window, cx)
8063 })
8064 .unwrap();
8065 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
8066 cx.executor().start_waiting();
8067 save.await;
8068 assert_eq!(
8069 editor.update(cx, |editor, cx| editor.text(cx)),
8070 "one\ntwo\nthree\n"
8071 );
8072 }
8073
8074 // For non-dirty buffer, no formatting request should be sent
8075 {
8076 assert!(!cx.read(|cx| editor.is_dirty(cx)));
8077
8078 fake_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
8079 panic!("Should not be invoked on non-dirty buffer");
8080 });
8081 let save = editor
8082 .update_in(cx, |editor, window, cx| {
8083 editor.save(true, project.clone(), window, cx)
8084 })
8085 .unwrap();
8086 cx.executor().start_waiting();
8087 save.await;
8088 }
8089
8090 // Set rust language override and assert overridden tabsize is sent to language server
8091 update_test_language_settings(cx, |settings| {
8092 settings.languages.insert(
8093 "Rust".into(),
8094 LanguageSettingsContent {
8095 tab_size: NonZeroU32::new(8),
8096 ..Default::default()
8097 },
8098 );
8099 });
8100
8101 {
8102 editor.update_in(cx, |editor, window, cx| {
8103 editor.set_text("somehting_new\n", window, cx)
8104 });
8105 assert!(cx.read(|cx| editor.is_dirty(cx)));
8106 let _formatting_request_signal = fake_server
8107 .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
8108 assert_eq!(
8109 params.text_document.uri,
8110 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
8111 );
8112 assert_eq!(params.options.tab_size, 8);
8113 Ok(Some(vec![]))
8114 });
8115 let save = editor
8116 .update_in(cx, |editor, window, cx| {
8117 editor.save(true, project.clone(), window, cx)
8118 })
8119 .unwrap();
8120 cx.executor().start_waiting();
8121 save.await;
8122 }
8123}
8124
8125#[gpui::test]
8126async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
8127 init_test(cx, |_| {});
8128
8129 let cols = 4;
8130 let rows = 10;
8131 let sample_text_1 = sample_text(rows, cols, 'a');
8132 assert_eq!(
8133 sample_text_1,
8134 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
8135 );
8136 let sample_text_2 = sample_text(rows, cols, 'l');
8137 assert_eq!(
8138 sample_text_2,
8139 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
8140 );
8141 let sample_text_3 = sample_text(rows, cols, 'v');
8142 assert_eq!(
8143 sample_text_3,
8144 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
8145 );
8146
8147 let fs = FakeFs::new(cx.executor());
8148 fs.insert_tree(
8149 path!("/a"),
8150 json!({
8151 "main.rs": sample_text_1,
8152 "other.rs": sample_text_2,
8153 "lib.rs": sample_text_3,
8154 }),
8155 )
8156 .await;
8157
8158 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
8159 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
8160 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
8161
8162 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8163 language_registry.add(rust_lang());
8164 let mut fake_servers = language_registry.register_fake_lsp(
8165 "Rust",
8166 FakeLspAdapter {
8167 capabilities: lsp::ServerCapabilities {
8168 document_formatting_provider: Some(lsp::OneOf::Left(true)),
8169 ..Default::default()
8170 },
8171 ..Default::default()
8172 },
8173 );
8174
8175 let worktree = project.update(cx, |project, cx| {
8176 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
8177 assert_eq!(worktrees.len(), 1);
8178 worktrees.pop().unwrap()
8179 });
8180 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
8181
8182 let buffer_1 = project
8183 .update(cx, |project, cx| {
8184 project.open_buffer((worktree_id, "main.rs"), cx)
8185 })
8186 .await
8187 .unwrap();
8188 let buffer_2 = project
8189 .update(cx, |project, cx| {
8190 project.open_buffer((worktree_id, "other.rs"), cx)
8191 })
8192 .await
8193 .unwrap();
8194 let buffer_3 = project
8195 .update(cx, |project, cx| {
8196 project.open_buffer((worktree_id, "lib.rs"), cx)
8197 })
8198 .await
8199 .unwrap();
8200
8201 let multi_buffer = cx.new(|cx| {
8202 let mut multi_buffer = MultiBuffer::new(ReadWrite);
8203 multi_buffer.push_excerpts(
8204 buffer_1.clone(),
8205 [
8206 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
8207 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
8208 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
8209 ],
8210 cx,
8211 );
8212 multi_buffer.push_excerpts(
8213 buffer_2.clone(),
8214 [
8215 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
8216 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
8217 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
8218 ],
8219 cx,
8220 );
8221 multi_buffer.push_excerpts(
8222 buffer_3.clone(),
8223 [
8224 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
8225 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
8226 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
8227 ],
8228 cx,
8229 );
8230 multi_buffer
8231 });
8232 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
8233 Editor::new(
8234 EditorMode::full(),
8235 multi_buffer,
8236 Some(project.clone()),
8237 window,
8238 cx,
8239 )
8240 });
8241
8242 multi_buffer_editor.update_in(cx, |editor, window, cx| {
8243 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
8244 s.select_ranges(Some(1..2))
8245 });
8246 editor.insert("|one|two|three|", window, cx);
8247 });
8248 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
8249 multi_buffer_editor.update_in(cx, |editor, window, cx| {
8250 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
8251 s.select_ranges(Some(60..70))
8252 });
8253 editor.insert("|four|five|six|", window, cx);
8254 });
8255 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
8256
8257 // First two buffers should be edited, but not the third one.
8258 assert_eq!(
8259 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
8260 "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}",
8261 );
8262 buffer_1.update(cx, |buffer, _| {
8263 assert!(buffer.is_dirty());
8264 assert_eq!(
8265 buffer.text(),
8266 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
8267 )
8268 });
8269 buffer_2.update(cx, |buffer, _| {
8270 assert!(buffer.is_dirty());
8271 assert_eq!(
8272 buffer.text(),
8273 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
8274 )
8275 });
8276 buffer_3.update(cx, |buffer, _| {
8277 assert!(!buffer.is_dirty());
8278 assert_eq!(buffer.text(), sample_text_3,)
8279 });
8280 cx.executor().run_until_parked();
8281
8282 cx.executor().start_waiting();
8283 let save = multi_buffer_editor
8284 .update_in(cx, |editor, window, cx| {
8285 editor.save(true, project.clone(), window, cx)
8286 })
8287 .unwrap();
8288
8289 let fake_server = fake_servers.next().await.unwrap();
8290 fake_server
8291 .server
8292 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
8293 Ok(Some(vec![lsp::TextEdit::new(
8294 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
8295 format!("[{} formatted]", params.text_document.uri),
8296 )]))
8297 })
8298 .detach();
8299 save.await;
8300
8301 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
8302 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
8303 assert_eq!(
8304 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
8305 uri!(
8306 "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}"
8307 ),
8308 );
8309 buffer_1.update(cx, |buffer, _| {
8310 assert!(!buffer.is_dirty());
8311 assert_eq!(
8312 buffer.text(),
8313 uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
8314 )
8315 });
8316 buffer_2.update(cx, |buffer, _| {
8317 assert!(!buffer.is_dirty());
8318 assert_eq!(
8319 buffer.text(),
8320 uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
8321 )
8322 });
8323 buffer_3.update(cx, |buffer, _| {
8324 assert!(!buffer.is_dirty());
8325 assert_eq!(buffer.text(), sample_text_3,)
8326 });
8327}
8328
8329#[gpui::test]
8330async fn test_range_format_during_save(cx: &mut TestAppContext) {
8331 init_test(cx, |_| {});
8332
8333 let fs = FakeFs::new(cx.executor());
8334 fs.insert_file(path!("/file.rs"), Default::default()).await;
8335
8336 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
8337
8338 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8339 language_registry.add(rust_lang());
8340 let mut fake_servers = language_registry.register_fake_lsp(
8341 "Rust",
8342 FakeLspAdapter {
8343 capabilities: lsp::ServerCapabilities {
8344 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
8345 ..Default::default()
8346 },
8347 ..Default::default()
8348 },
8349 );
8350
8351 let buffer = project
8352 .update(cx, |project, cx| {
8353 project.open_local_buffer(path!("/file.rs"), cx)
8354 })
8355 .await
8356 .unwrap();
8357
8358 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
8359 let (editor, cx) = cx.add_window_view(|window, cx| {
8360 build_editor_with_project(project.clone(), buffer, window, cx)
8361 });
8362 editor.update_in(cx, |editor, window, cx| {
8363 editor.set_text("one\ntwo\nthree\n", window, cx)
8364 });
8365 assert!(cx.read(|cx| editor.is_dirty(cx)));
8366
8367 cx.executor().start_waiting();
8368 let fake_server = fake_servers.next().await.unwrap();
8369
8370 let save = editor
8371 .update_in(cx, |editor, window, cx| {
8372 editor.save(true, project.clone(), window, cx)
8373 })
8374 .unwrap();
8375 fake_server
8376 .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
8377 assert_eq!(
8378 params.text_document.uri,
8379 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
8380 );
8381 assert_eq!(params.options.tab_size, 4);
8382 Ok(Some(vec![lsp::TextEdit::new(
8383 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
8384 ", ".to_string(),
8385 )]))
8386 })
8387 .next()
8388 .await;
8389 cx.executor().start_waiting();
8390 save.await;
8391 assert_eq!(
8392 editor.update(cx, |editor, cx| editor.text(cx)),
8393 "one, two\nthree\n"
8394 );
8395 assert!(!cx.read(|cx| editor.is_dirty(cx)));
8396
8397 editor.update_in(cx, |editor, window, cx| {
8398 editor.set_text("one\ntwo\nthree\n", window, cx)
8399 });
8400 assert!(cx.read(|cx| editor.is_dirty(cx)));
8401
8402 // Ensure we can still save even if formatting hangs.
8403 fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
8404 move |params, _| async move {
8405 assert_eq!(
8406 params.text_document.uri,
8407 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
8408 );
8409 futures::future::pending::<()>().await;
8410 unreachable!()
8411 },
8412 );
8413 let save = editor
8414 .update_in(cx, |editor, window, cx| {
8415 editor.save(true, project.clone(), window, cx)
8416 })
8417 .unwrap();
8418 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
8419 cx.executor().start_waiting();
8420 save.await;
8421 assert_eq!(
8422 editor.update(cx, |editor, cx| editor.text(cx)),
8423 "one\ntwo\nthree\n"
8424 );
8425 assert!(!cx.read(|cx| editor.is_dirty(cx)));
8426
8427 // For non-dirty buffer, no formatting request should be sent
8428 let save = editor
8429 .update_in(cx, |editor, window, cx| {
8430 editor.save(true, project.clone(), window, cx)
8431 })
8432 .unwrap();
8433 let _pending_format_request = fake_server
8434 .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
8435 panic!("Should not be invoked on non-dirty buffer");
8436 })
8437 .next();
8438 cx.executor().start_waiting();
8439 save.await;
8440
8441 // Set Rust language override and assert overridden tabsize is sent to language server
8442 update_test_language_settings(cx, |settings| {
8443 settings.languages.insert(
8444 "Rust".into(),
8445 LanguageSettingsContent {
8446 tab_size: NonZeroU32::new(8),
8447 ..Default::default()
8448 },
8449 );
8450 });
8451
8452 editor.update_in(cx, |editor, window, cx| {
8453 editor.set_text("somehting_new\n", window, cx)
8454 });
8455 assert!(cx.read(|cx| editor.is_dirty(cx)));
8456 let save = editor
8457 .update_in(cx, |editor, window, cx| {
8458 editor.save(true, project.clone(), window, cx)
8459 })
8460 .unwrap();
8461 fake_server
8462 .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
8463 assert_eq!(
8464 params.text_document.uri,
8465 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
8466 );
8467 assert_eq!(params.options.tab_size, 8);
8468 Ok(Some(vec![]))
8469 })
8470 .next()
8471 .await;
8472 cx.executor().start_waiting();
8473 save.await;
8474}
8475
8476#[gpui::test]
8477async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
8478 init_test(cx, |settings| {
8479 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
8480 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
8481 ))
8482 });
8483
8484 let fs = FakeFs::new(cx.executor());
8485 fs.insert_file(path!("/file.rs"), Default::default()).await;
8486
8487 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
8488
8489 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8490 language_registry.add(Arc::new(Language::new(
8491 LanguageConfig {
8492 name: "Rust".into(),
8493 matcher: LanguageMatcher {
8494 path_suffixes: vec!["rs".to_string()],
8495 ..Default::default()
8496 },
8497 ..LanguageConfig::default()
8498 },
8499 Some(tree_sitter_rust::LANGUAGE.into()),
8500 )));
8501 update_test_language_settings(cx, |settings| {
8502 // Enable Prettier formatting for the same buffer, and ensure
8503 // LSP is called instead of Prettier.
8504 settings.defaults.prettier = Some(PrettierSettings {
8505 allowed: true,
8506 ..PrettierSettings::default()
8507 });
8508 });
8509 let mut fake_servers = language_registry.register_fake_lsp(
8510 "Rust",
8511 FakeLspAdapter {
8512 capabilities: lsp::ServerCapabilities {
8513 document_formatting_provider: Some(lsp::OneOf::Left(true)),
8514 ..Default::default()
8515 },
8516 ..Default::default()
8517 },
8518 );
8519
8520 let buffer = project
8521 .update(cx, |project, cx| {
8522 project.open_local_buffer(path!("/file.rs"), cx)
8523 })
8524 .await
8525 .unwrap();
8526
8527 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
8528 let (editor, cx) = cx.add_window_view(|window, cx| {
8529 build_editor_with_project(project.clone(), buffer, window, cx)
8530 });
8531 editor.update_in(cx, |editor, window, cx| {
8532 editor.set_text("one\ntwo\nthree\n", window, cx)
8533 });
8534
8535 cx.executor().start_waiting();
8536 let fake_server = fake_servers.next().await.unwrap();
8537
8538 let format = editor
8539 .update_in(cx, |editor, window, cx| {
8540 editor.perform_format(
8541 project.clone(),
8542 FormatTrigger::Manual,
8543 FormatTarget::Buffers,
8544 window,
8545 cx,
8546 )
8547 })
8548 .unwrap();
8549 fake_server
8550 .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
8551 assert_eq!(
8552 params.text_document.uri,
8553 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
8554 );
8555 assert_eq!(params.options.tab_size, 4);
8556 Ok(Some(vec![lsp::TextEdit::new(
8557 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
8558 ", ".to_string(),
8559 )]))
8560 })
8561 .next()
8562 .await;
8563 cx.executor().start_waiting();
8564 format.await;
8565 assert_eq!(
8566 editor.update(cx, |editor, cx| editor.text(cx)),
8567 "one, two\nthree\n"
8568 );
8569
8570 editor.update_in(cx, |editor, window, cx| {
8571 editor.set_text("one\ntwo\nthree\n", window, cx)
8572 });
8573 // Ensure we don't lock if formatting hangs.
8574 fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
8575 move |params, _| async move {
8576 assert_eq!(
8577 params.text_document.uri,
8578 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
8579 );
8580 futures::future::pending::<()>().await;
8581 unreachable!()
8582 },
8583 );
8584 let format = editor
8585 .update_in(cx, |editor, window, cx| {
8586 editor.perform_format(
8587 project,
8588 FormatTrigger::Manual,
8589 FormatTarget::Buffers,
8590 window,
8591 cx,
8592 )
8593 })
8594 .unwrap();
8595 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
8596 cx.executor().start_waiting();
8597 format.await;
8598 assert_eq!(
8599 editor.update(cx, |editor, cx| editor.text(cx)),
8600 "one\ntwo\nthree\n"
8601 );
8602}
8603
8604#[gpui::test]
8605async fn test_multiple_formatters(cx: &mut TestAppContext) {
8606 init_test(cx, |settings| {
8607 settings.defaults.remove_trailing_whitespace_on_save = Some(true);
8608 settings.defaults.formatter =
8609 Some(language_settings::SelectedFormatter::List(FormatterList(
8610 vec![
8611 Formatter::LanguageServer { name: None },
8612 Formatter::CodeActions(
8613 [
8614 ("code-action-1".into(), true),
8615 ("code-action-2".into(), true),
8616 ]
8617 .into_iter()
8618 .collect(),
8619 ),
8620 ]
8621 .into(),
8622 )))
8623 });
8624
8625 let fs = FakeFs::new(cx.executor());
8626 fs.insert_file(path!("/file.rs"), "one \ntwo \nthree".into())
8627 .await;
8628
8629 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
8630 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8631 language_registry.add(rust_lang());
8632
8633 let mut fake_servers = language_registry.register_fake_lsp(
8634 "Rust",
8635 FakeLspAdapter {
8636 capabilities: lsp::ServerCapabilities {
8637 document_formatting_provider: Some(lsp::OneOf::Left(true)),
8638 execute_command_provider: Some(lsp::ExecuteCommandOptions {
8639 commands: vec!["the-command-for-code-action-1".into()],
8640 ..Default::default()
8641 }),
8642 code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
8643 ..Default::default()
8644 },
8645 ..Default::default()
8646 },
8647 );
8648
8649 let buffer = project
8650 .update(cx, |project, cx| {
8651 project.open_local_buffer(path!("/file.rs"), cx)
8652 })
8653 .await
8654 .unwrap();
8655
8656 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
8657 let (editor, cx) = cx.add_window_view(|window, cx| {
8658 build_editor_with_project(project.clone(), buffer, window, cx)
8659 });
8660
8661 cx.executor().start_waiting();
8662
8663 let fake_server = fake_servers.next().await.unwrap();
8664 fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
8665 move |_params, _| async move {
8666 Ok(Some(vec![lsp::TextEdit::new(
8667 lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
8668 "applied-formatting\n".to_string(),
8669 )]))
8670 },
8671 );
8672 fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
8673 move |params, _| async move {
8674 assert_eq!(
8675 params.context.only,
8676 Some(vec!["code-action-1".into(), "code-action-2".into()])
8677 );
8678 let uri = lsp::Url::from_file_path(path!("/file.rs")).unwrap();
8679 Ok(Some(vec![
8680 lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
8681 kind: Some("code-action-1".into()),
8682 edit: Some(lsp::WorkspaceEdit::new(
8683 [(
8684 uri.clone(),
8685 vec![lsp::TextEdit::new(
8686 lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
8687 "applied-code-action-1-edit\n".to_string(),
8688 )],
8689 )]
8690 .into_iter()
8691 .collect(),
8692 )),
8693 command: Some(lsp::Command {
8694 command: "the-command-for-code-action-1".into(),
8695 ..Default::default()
8696 }),
8697 ..Default::default()
8698 }),
8699 lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
8700 kind: Some("code-action-2".into()),
8701 edit: Some(lsp::WorkspaceEdit::new(
8702 [(
8703 uri.clone(),
8704 vec![lsp::TextEdit::new(
8705 lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
8706 "applied-code-action-2-edit\n".to_string(),
8707 )],
8708 )]
8709 .into_iter()
8710 .collect(),
8711 )),
8712 ..Default::default()
8713 }),
8714 ]))
8715 },
8716 );
8717
8718 fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
8719 move |params, _| async move { Ok(params) }
8720 });
8721
8722 let command_lock = Arc::new(futures::lock::Mutex::new(()));
8723 fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
8724 let fake = fake_server.clone();
8725 let lock = command_lock.clone();
8726 move |params, _| {
8727 assert_eq!(params.command, "the-command-for-code-action-1");
8728 let fake = fake.clone();
8729 let lock = lock.clone();
8730 async move {
8731 lock.lock().await;
8732 fake.server
8733 .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
8734 label: None,
8735 edit: lsp::WorkspaceEdit {
8736 changes: Some(
8737 [(
8738 lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
8739 vec![lsp::TextEdit {
8740 range: lsp::Range::new(
8741 lsp::Position::new(0, 0),
8742 lsp::Position::new(0, 0),
8743 ),
8744 new_text: "applied-code-action-1-command\n".into(),
8745 }],
8746 )]
8747 .into_iter()
8748 .collect(),
8749 ),
8750 ..Default::default()
8751 },
8752 })
8753 .await
8754 .unwrap();
8755 Ok(Some(json!(null)))
8756 }
8757 }
8758 });
8759
8760 cx.executor().start_waiting();
8761 editor
8762 .update_in(cx, |editor, window, cx| {
8763 editor.perform_format(
8764 project.clone(),
8765 FormatTrigger::Manual,
8766 FormatTarget::Buffers,
8767 window,
8768 cx,
8769 )
8770 })
8771 .unwrap()
8772 .await;
8773 editor.update(cx, |editor, cx| {
8774 assert_eq!(
8775 editor.text(cx),
8776 r#"
8777 applied-code-action-2-edit
8778 applied-code-action-1-command
8779 applied-code-action-1-edit
8780 applied-formatting
8781 one
8782 two
8783 three
8784 "#
8785 .unindent()
8786 );
8787 });
8788
8789 editor.update_in(cx, |editor, window, cx| {
8790 editor.undo(&Default::default(), window, cx);
8791 assert_eq!(editor.text(cx), "one \ntwo \nthree");
8792 });
8793
8794 // Perform a manual edit while waiting for an LSP command
8795 // that's being run as part of a formatting code action.
8796 let lock_guard = command_lock.lock().await;
8797 let format = editor
8798 .update_in(cx, |editor, window, cx| {
8799 editor.perform_format(
8800 project.clone(),
8801 FormatTrigger::Manual,
8802 FormatTarget::Buffers,
8803 window,
8804 cx,
8805 )
8806 })
8807 .unwrap();
8808 cx.run_until_parked();
8809 editor.update(cx, |editor, cx| {
8810 assert_eq!(
8811 editor.text(cx),
8812 r#"
8813 applied-code-action-1-edit
8814 applied-formatting
8815 one
8816 two
8817 three
8818 "#
8819 .unindent()
8820 );
8821
8822 editor.buffer.update(cx, |buffer, cx| {
8823 let ix = buffer.len(cx);
8824 buffer.edit([(ix..ix, "edited\n")], None, cx);
8825 });
8826 });
8827
8828 // Allow the LSP command to proceed. Because the buffer was edited,
8829 // the second code action will not be run.
8830 drop(lock_guard);
8831 format.await;
8832 editor.update_in(cx, |editor, window, cx| {
8833 assert_eq!(
8834 editor.text(cx),
8835 r#"
8836 applied-code-action-1-command
8837 applied-code-action-1-edit
8838 applied-formatting
8839 one
8840 two
8841 three
8842 edited
8843 "#
8844 .unindent()
8845 );
8846
8847 // The manual edit is undone first, because it is the last thing the user did
8848 // (even though the command completed afterwards).
8849 editor.undo(&Default::default(), window, cx);
8850 assert_eq!(
8851 editor.text(cx),
8852 r#"
8853 applied-code-action-1-command
8854 applied-code-action-1-edit
8855 applied-formatting
8856 one
8857 two
8858 three
8859 "#
8860 .unindent()
8861 );
8862
8863 // All the formatting (including the command, which completed after the manual edit)
8864 // is undone together.
8865 editor.undo(&Default::default(), window, cx);
8866 assert_eq!(editor.text(cx), "one \ntwo \nthree");
8867 });
8868}
8869
8870#[gpui::test]
8871async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
8872 init_test(cx, |settings| {
8873 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
8874 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
8875 ))
8876 });
8877
8878 let fs = FakeFs::new(cx.executor());
8879 fs.insert_file(path!("/file.ts"), Default::default()).await;
8880
8881 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
8882
8883 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8884 language_registry.add(Arc::new(Language::new(
8885 LanguageConfig {
8886 name: "TypeScript".into(),
8887 matcher: LanguageMatcher {
8888 path_suffixes: vec!["ts".to_string()],
8889 ..Default::default()
8890 },
8891 ..LanguageConfig::default()
8892 },
8893 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
8894 )));
8895 update_test_language_settings(cx, |settings| {
8896 settings.defaults.prettier = Some(PrettierSettings {
8897 allowed: true,
8898 ..PrettierSettings::default()
8899 });
8900 });
8901 let mut fake_servers = language_registry.register_fake_lsp(
8902 "TypeScript",
8903 FakeLspAdapter {
8904 capabilities: lsp::ServerCapabilities {
8905 code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
8906 ..Default::default()
8907 },
8908 ..Default::default()
8909 },
8910 );
8911
8912 let buffer = project
8913 .update(cx, |project, cx| {
8914 project.open_local_buffer(path!("/file.ts"), cx)
8915 })
8916 .await
8917 .unwrap();
8918
8919 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
8920 let (editor, cx) = cx.add_window_view(|window, cx| {
8921 build_editor_with_project(project.clone(), buffer, window, cx)
8922 });
8923 editor.update_in(cx, |editor, window, cx| {
8924 editor.set_text(
8925 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
8926 window,
8927 cx,
8928 )
8929 });
8930
8931 cx.executor().start_waiting();
8932 let fake_server = fake_servers.next().await.unwrap();
8933
8934 let format = editor
8935 .update_in(cx, |editor, window, cx| {
8936 editor.perform_code_action_kind(
8937 project.clone(),
8938 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
8939 window,
8940 cx,
8941 )
8942 })
8943 .unwrap();
8944 fake_server
8945 .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
8946 assert_eq!(
8947 params.text_document.uri,
8948 lsp::Url::from_file_path(path!("/file.ts")).unwrap()
8949 );
8950 Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
8951 lsp::CodeAction {
8952 title: "Organize Imports".to_string(),
8953 kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
8954 edit: Some(lsp::WorkspaceEdit {
8955 changes: Some(
8956 [(
8957 params.text_document.uri.clone(),
8958 vec![lsp::TextEdit::new(
8959 lsp::Range::new(
8960 lsp::Position::new(1, 0),
8961 lsp::Position::new(2, 0),
8962 ),
8963 "".to_string(),
8964 )],
8965 )]
8966 .into_iter()
8967 .collect(),
8968 ),
8969 ..Default::default()
8970 }),
8971 ..Default::default()
8972 },
8973 )]))
8974 })
8975 .next()
8976 .await;
8977 cx.executor().start_waiting();
8978 format.await;
8979 assert_eq!(
8980 editor.update(cx, |editor, cx| editor.text(cx)),
8981 "import { a } from 'module';\n\nconst x = a;\n"
8982 );
8983
8984 editor.update_in(cx, |editor, window, cx| {
8985 editor.set_text(
8986 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
8987 window,
8988 cx,
8989 )
8990 });
8991 // Ensure we don't lock if code action hangs.
8992 fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
8993 move |params, _| async move {
8994 assert_eq!(
8995 params.text_document.uri,
8996 lsp::Url::from_file_path(path!("/file.ts")).unwrap()
8997 );
8998 futures::future::pending::<()>().await;
8999 unreachable!()
9000 },
9001 );
9002 let format = editor
9003 .update_in(cx, |editor, window, cx| {
9004 editor.perform_code_action_kind(
9005 project,
9006 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
9007 window,
9008 cx,
9009 )
9010 })
9011 .unwrap();
9012 cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
9013 cx.executor().start_waiting();
9014 format.await;
9015 assert_eq!(
9016 editor.update(cx, |editor, cx| editor.text(cx)),
9017 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
9018 );
9019}
9020
9021#[gpui::test]
9022async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
9023 init_test(cx, |_| {});
9024
9025 let mut cx = EditorLspTestContext::new_rust(
9026 lsp::ServerCapabilities {
9027 document_formatting_provider: Some(lsp::OneOf::Left(true)),
9028 ..Default::default()
9029 },
9030 cx,
9031 )
9032 .await;
9033
9034 cx.set_state(indoc! {"
9035 one.twoˇ
9036 "});
9037
9038 // The format request takes a long time. When it completes, it inserts
9039 // a newline and an indent before the `.`
9040 cx.lsp
9041 .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
9042 let executor = cx.background_executor().clone();
9043 async move {
9044 executor.timer(Duration::from_millis(100)).await;
9045 Ok(Some(vec![lsp::TextEdit {
9046 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
9047 new_text: "\n ".into(),
9048 }]))
9049 }
9050 });
9051
9052 // Submit a format request.
9053 let format_1 = cx
9054 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
9055 .unwrap();
9056 cx.executor().run_until_parked();
9057
9058 // Submit a second format request.
9059 let format_2 = cx
9060 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
9061 .unwrap();
9062 cx.executor().run_until_parked();
9063
9064 // Wait for both format requests to complete
9065 cx.executor().advance_clock(Duration::from_millis(200));
9066 cx.executor().start_waiting();
9067 format_1.await.unwrap();
9068 cx.executor().start_waiting();
9069 format_2.await.unwrap();
9070
9071 // The formatting edits only happens once.
9072 cx.assert_editor_state(indoc! {"
9073 one
9074 .twoˇ
9075 "});
9076}
9077
9078#[gpui::test]
9079async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
9080 init_test(cx, |settings| {
9081 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
9082 });
9083
9084 let mut cx = EditorLspTestContext::new_rust(
9085 lsp::ServerCapabilities {
9086 document_formatting_provider: Some(lsp::OneOf::Left(true)),
9087 ..Default::default()
9088 },
9089 cx,
9090 )
9091 .await;
9092
9093 // Set up a buffer white some trailing whitespace and no trailing newline.
9094 cx.set_state(
9095 &[
9096 "one ", //
9097 "twoˇ", //
9098 "three ", //
9099 "four", //
9100 ]
9101 .join("\n"),
9102 );
9103
9104 // Submit a format request.
9105 let format = cx
9106 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
9107 .unwrap();
9108
9109 // Record which buffer changes have been sent to the language server
9110 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
9111 cx.lsp
9112 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
9113 let buffer_changes = buffer_changes.clone();
9114 move |params, _| {
9115 buffer_changes.lock().extend(
9116 params
9117 .content_changes
9118 .into_iter()
9119 .map(|e| (e.range.unwrap(), e.text)),
9120 );
9121 }
9122 });
9123
9124 // Handle formatting requests to the language server.
9125 cx.lsp
9126 .set_request_handler::<lsp::request::Formatting, _, _>({
9127 let buffer_changes = buffer_changes.clone();
9128 move |_, _| {
9129 // When formatting is requested, trailing whitespace has already been stripped,
9130 // and the trailing newline has already been added.
9131 assert_eq!(
9132 &buffer_changes.lock()[1..],
9133 &[
9134 (
9135 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
9136 "".into()
9137 ),
9138 (
9139 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
9140 "".into()
9141 ),
9142 (
9143 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
9144 "\n".into()
9145 ),
9146 ]
9147 );
9148
9149 // Insert blank lines between each line of the buffer.
9150 async move {
9151 Ok(Some(vec![
9152 lsp::TextEdit {
9153 range: lsp::Range::new(
9154 lsp::Position::new(1, 0),
9155 lsp::Position::new(1, 0),
9156 ),
9157 new_text: "\n".into(),
9158 },
9159 lsp::TextEdit {
9160 range: lsp::Range::new(
9161 lsp::Position::new(2, 0),
9162 lsp::Position::new(2, 0),
9163 ),
9164 new_text: "\n".into(),
9165 },
9166 ]))
9167 }
9168 }
9169 });
9170
9171 // After formatting the buffer, the trailing whitespace is stripped,
9172 // a newline is appended, and the edits provided by the language server
9173 // have been applied.
9174 format.await.unwrap();
9175 cx.assert_editor_state(
9176 &[
9177 "one", //
9178 "", //
9179 "twoˇ", //
9180 "", //
9181 "three", //
9182 "four", //
9183 "", //
9184 ]
9185 .join("\n"),
9186 );
9187
9188 // Undoing the formatting undoes the trailing whitespace removal, the
9189 // trailing newline, and the LSP edits.
9190 cx.update_buffer(|buffer, cx| buffer.undo(cx));
9191 cx.assert_editor_state(
9192 &[
9193 "one ", //
9194 "twoˇ", //
9195 "three ", //
9196 "four", //
9197 ]
9198 .join("\n"),
9199 );
9200}
9201
9202#[gpui::test]
9203async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
9204 cx: &mut TestAppContext,
9205) {
9206 init_test(cx, |_| {});
9207
9208 cx.update(|cx| {
9209 cx.update_global::<SettingsStore, _>(|settings, cx| {
9210 settings.update_user_settings::<EditorSettings>(cx, |settings| {
9211 settings.auto_signature_help = Some(true);
9212 });
9213 });
9214 });
9215
9216 let mut cx = EditorLspTestContext::new_rust(
9217 lsp::ServerCapabilities {
9218 signature_help_provider: Some(lsp::SignatureHelpOptions {
9219 ..Default::default()
9220 }),
9221 ..Default::default()
9222 },
9223 cx,
9224 )
9225 .await;
9226
9227 let language = Language::new(
9228 LanguageConfig {
9229 name: "Rust".into(),
9230 brackets: BracketPairConfig {
9231 pairs: vec![
9232 BracketPair {
9233 start: "{".to_string(),
9234 end: "}".to_string(),
9235 close: true,
9236 surround: true,
9237 newline: true,
9238 },
9239 BracketPair {
9240 start: "(".to_string(),
9241 end: ")".to_string(),
9242 close: true,
9243 surround: true,
9244 newline: true,
9245 },
9246 BracketPair {
9247 start: "/*".to_string(),
9248 end: " */".to_string(),
9249 close: true,
9250 surround: true,
9251 newline: true,
9252 },
9253 BracketPair {
9254 start: "[".to_string(),
9255 end: "]".to_string(),
9256 close: false,
9257 surround: false,
9258 newline: true,
9259 },
9260 BracketPair {
9261 start: "\"".to_string(),
9262 end: "\"".to_string(),
9263 close: true,
9264 surround: true,
9265 newline: false,
9266 },
9267 BracketPair {
9268 start: "<".to_string(),
9269 end: ">".to_string(),
9270 close: false,
9271 surround: true,
9272 newline: true,
9273 },
9274 ],
9275 ..Default::default()
9276 },
9277 autoclose_before: "})]".to_string(),
9278 ..Default::default()
9279 },
9280 Some(tree_sitter_rust::LANGUAGE.into()),
9281 );
9282 let language = Arc::new(language);
9283
9284 cx.language_registry().add(language.clone());
9285 cx.update_buffer(|buffer, cx| {
9286 buffer.set_language(Some(language), cx);
9287 });
9288
9289 cx.set_state(
9290 &r#"
9291 fn main() {
9292 sampleˇ
9293 }
9294 "#
9295 .unindent(),
9296 );
9297
9298 cx.update_editor(|editor, window, cx| {
9299 editor.handle_input("(", window, cx);
9300 });
9301 cx.assert_editor_state(
9302 &"
9303 fn main() {
9304 sample(ˇ)
9305 }
9306 "
9307 .unindent(),
9308 );
9309
9310 let mocked_response = lsp::SignatureHelp {
9311 signatures: vec![lsp::SignatureInformation {
9312 label: "fn sample(param1: u8, param2: u8)".to_string(),
9313 documentation: None,
9314 parameters: Some(vec![
9315 lsp::ParameterInformation {
9316 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
9317 documentation: None,
9318 },
9319 lsp::ParameterInformation {
9320 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
9321 documentation: None,
9322 },
9323 ]),
9324 active_parameter: None,
9325 }],
9326 active_signature: Some(0),
9327 active_parameter: Some(0),
9328 };
9329 handle_signature_help_request(&mut cx, mocked_response).await;
9330
9331 cx.condition(|editor, _| editor.signature_help_state.is_shown())
9332 .await;
9333
9334 cx.editor(|editor, _, _| {
9335 let signature_help_state = editor.signature_help_state.popover().cloned();
9336 assert_eq!(
9337 signature_help_state.unwrap().label,
9338 "param1: u8, param2: u8"
9339 );
9340 });
9341}
9342
9343#[gpui::test]
9344async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
9345 init_test(cx, |_| {});
9346
9347 cx.update(|cx| {
9348 cx.update_global::<SettingsStore, _>(|settings, cx| {
9349 settings.update_user_settings::<EditorSettings>(cx, |settings| {
9350 settings.auto_signature_help = Some(false);
9351 settings.show_signature_help_after_edits = Some(false);
9352 });
9353 });
9354 });
9355
9356 let mut cx = EditorLspTestContext::new_rust(
9357 lsp::ServerCapabilities {
9358 signature_help_provider: Some(lsp::SignatureHelpOptions {
9359 ..Default::default()
9360 }),
9361 ..Default::default()
9362 },
9363 cx,
9364 )
9365 .await;
9366
9367 let language = Language::new(
9368 LanguageConfig {
9369 name: "Rust".into(),
9370 brackets: BracketPairConfig {
9371 pairs: vec![
9372 BracketPair {
9373 start: "{".to_string(),
9374 end: "}".to_string(),
9375 close: true,
9376 surround: true,
9377 newline: true,
9378 },
9379 BracketPair {
9380 start: "(".to_string(),
9381 end: ")".to_string(),
9382 close: true,
9383 surround: true,
9384 newline: true,
9385 },
9386 BracketPair {
9387 start: "/*".to_string(),
9388 end: " */".to_string(),
9389 close: true,
9390 surround: true,
9391 newline: true,
9392 },
9393 BracketPair {
9394 start: "[".to_string(),
9395 end: "]".to_string(),
9396 close: false,
9397 surround: false,
9398 newline: true,
9399 },
9400 BracketPair {
9401 start: "\"".to_string(),
9402 end: "\"".to_string(),
9403 close: true,
9404 surround: true,
9405 newline: false,
9406 },
9407 BracketPair {
9408 start: "<".to_string(),
9409 end: ">".to_string(),
9410 close: false,
9411 surround: true,
9412 newline: true,
9413 },
9414 ],
9415 ..Default::default()
9416 },
9417 autoclose_before: "})]".to_string(),
9418 ..Default::default()
9419 },
9420 Some(tree_sitter_rust::LANGUAGE.into()),
9421 );
9422 let language = Arc::new(language);
9423
9424 cx.language_registry().add(language.clone());
9425 cx.update_buffer(|buffer, cx| {
9426 buffer.set_language(Some(language), cx);
9427 });
9428
9429 // Ensure that signature_help is not called when no signature help is enabled.
9430 cx.set_state(
9431 &r#"
9432 fn main() {
9433 sampleˇ
9434 }
9435 "#
9436 .unindent(),
9437 );
9438 cx.update_editor(|editor, window, cx| {
9439 editor.handle_input("(", window, cx);
9440 });
9441 cx.assert_editor_state(
9442 &"
9443 fn main() {
9444 sample(ˇ)
9445 }
9446 "
9447 .unindent(),
9448 );
9449 cx.editor(|editor, _, _| {
9450 assert!(editor.signature_help_state.task().is_none());
9451 });
9452
9453 let mocked_response = lsp::SignatureHelp {
9454 signatures: vec![lsp::SignatureInformation {
9455 label: "fn sample(param1: u8, param2: u8)".to_string(),
9456 documentation: None,
9457 parameters: Some(vec![
9458 lsp::ParameterInformation {
9459 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
9460 documentation: None,
9461 },
9462 lsp::ParameterInformation {
9463 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
9464 documentation: None,
9465 },
9466 ]),
9467 active_parameter: None,
9468 }],
9469 active_signature: Some(0),
9470 active_parameter: Some(0),
9471 };
9472
9473 // Ensure that signature_help is called when enabled afte edits
9474 cx.update(|_, cx| {
9475 cx.update_global::<SettingsStore, _>(|settings, cx| {
9476 settings.update_user_settings::<EditorSettings>(cx, |settings| {
9477 settings.auto_signature_help = Some(false);
9478 settings.show_signature_help_after_edits = Some(true);
9479 });
9480 });
9481 });
9482 cx.set_state(
9483 &r#"
9484 fn main() {
9485 sampleˇ
9486 }
9487 "#
9488 .unindent(),
9489 );
9490 cx.update_editor(|editor, window, cx| {
9491 editor.handle_input("(", window, cx);
9492 });
9493 cx.assert_editor_state(
9494 &"
9495 fn main() {
9496 sample(ˇ)
9497 }
9498 "
9499 .unindent(),
9500 );
9501 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
9502 cx.condition(|editor, _| editor.signature_help_state.is_shown())
9503 .await;
9504 cx.update_editor(|editor, _, _| {
9505 let signature_help_state = editor.signature_help_state.popover().cloned();
9506 assert!(signature_help_state.is_some());
9507 assert_eq!(
9508 signature_help_state.unwrap().label,
9509 "param1: u8, param2: u8"
9510 );
9511 editor.signature_help_state = SignatureHelpState::default();
9512 });
9513
9514 // Ensure that signature_help is called when auto signature help override is enabled
9515 cx.update(|_, cx| {
9516 cx.update_global::<SettingsStore, _>(|settings, cx| {
9517 settings.update_user_settings::<EditorSettings>(cx, |settings| {
9518 settings.auto_signature_help = Some(true);
9519 settings.show_signature_help_after_edits = Some(false);
9520 });
9521 });
9522 });
9523 cx.set_state(
9524 &r#"
9525 fn main() {
9526 sampleˇ
9527 }
9528 "#
9529 .unindent(),
9530 );
9531 cx.update_editor(|editor, window, cx| {
9532 editor.handle_input("(", window, cx);
9533 });
9534 cx.assert_editor_state(
9535 &"
9536 fn main() {
9537 sample(ˇ)
9538 }
9539 "
9540 .unindent(),
9541 );
9542 handle_signature_help_request(&mut cx, mocked_response).await;
9543 cx.condition(|editor, _| editor.signature_help_state.is_shown())
9544 .await;
9545 cx.editor(|editor, _, _| {
9546 let signature_help_state = editor.signature_help_state.popover().cloned();
9547 assert!(signature_help_state.is_some());
9548 assert_eq!(
9549 signature_help_state.unwrap().label,
9550 "param1: u8, param2: u8"
9551 );
9552 });
9553}
9554
9555#[gpui::test]
9556async fn test_signature_help(cx: &mut TestAppContext) {
9557 init_test(cx, |_| {});
9558 cx.update(|cx| {
9559 cx.update_global::<SettingsStore, _>(|settings, cx| {
9560 settings.update_user_settings::<EditorSettings>(cx, |settings| {
9561 settings.auto_signature_help = Some(true);
9562 });
9563 });
9564 });
9565
9566 let mut cx = EditorLspTestContext::new_rust(
9567 lsp::ServerCapabilities {
9568 signature_help_provider: Some(lsp::SignatureHelpOptions {
9569 ..Default::default()
9570 }),
9571 ..Default::default()
9572 },
9573 cx,
9574 )
9575 .await;
9576
9577 // A test that directly calls `show_signature_help`
9578 cx.update_editor(|editor, window, cx| {
9579 editor.show_signature_help(&ShowSignatureHelp, window, cx);
9580 });
9581
9582 let mocked_response = lsp::SignatureHelp {
9583 signatures: vec![lsp::SignatureInformation {
9584 label: "fn sample(param1: u8, param2: u8)".to_string(),
9585 documentation: None,
9586 parameters: Some(vec![
9587 lsp::ParameterInformation {
9588 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
9589 documentation: None,
9590 },
9591 lsp::ParameterInformation {
9592 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
9593 documentation: None,
9594 },
9595 ]),
9596 active_parameter: None,
9597 }],
9598 active_signature: Some(0),
9599 active_parameter: Some(0),
9600 };
9601 handle_signature_help_request(&mut cx, mocked_response).await;
9602
9603 cx.condition(|editor, _| editor.signature_help_state.is_shown())
9604 .await;
9605
9606 cx.editor(|editor, _, _| {
9607 let signature_help_state = editor.signature_help_state.popover().cloned();
9608 assert!(signature_help_state.is_some());
9609 assert_eq!(
9610 signature_help_state.unwrap().label,
9611 "param1: u8, param2: u8"
9612 );
9613 });
9614
9615 // When exiting outside from inside the brackets, `signature_help` is closed.
9616 cx.set_state(indoc! {"
9617 fn main() {
9618 sample(ˇ);
9619 }
9620
9621 fn sample(param1: u8, param2: u8) {}
9622 "});
9623
9624 cx.update_editor(|editor, window, cx| {
9625 editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
9626 });
9627
9628 let mocked_response = lsp::SignatureHelp {
9629 signatures: Vec::new(),
9630 active_signature: None,
9631 active_parameter: None,
9632 };
9633 handle_signature_help_request(&mut cx, mocked_response).await;
9634
9635 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
9636 .await;
9637
9638 cx.editor(|editor, _, _| {
9639 assert!(!editor.signature_help_state.is_shown());
9640 });
9641
9642 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
9643 cx.set_state(indoc! {"
9644 fn main() {
9645 sample(ˇ);
9646 }
9647
9648 fn sample(param1: u8, param2: u8) {}
9649 "});
9650
9651 let mocked_response = lsp::SignatureHelp {
9652 signatures: vec![lsp::SignatureInformation {
9653 label: "fn sample(param1: u8, param2: u8)".to_string(),
9654 documentation: None,
9655 parameters: Some(vec![
9656 lsp::ParameterInformation {
9657 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
9658 documentation: None,
9659 },
9660 lsp::ParameterInformation {
9661 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
9662 documentation: None,
9663 },
9664 ]),
9665 active_parameter: None,
9666 }],
9667 active_signature: Some(0),
9668 active_parameter: Some(0),
9669 };
9670 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
9671 cx.condition(|editor, _| editor.signature_help_state.is_shown())
9672 .await;
9673 cx.editor(|editor, _, _| {
9674 assert!(editor.signature_help_state.is_shown());
9675 });
9676
9677 // Restore the popover with more parameter input
9678 cx.set_state(indoc! {"
9679 fn main() {
9680 sample(param1, param2ˇ);
9681 }
9682
9683 fn sample(param1: u8, param2: u8) {}
9684 "});
9685
9686 let mocked_response = lsp::SignatureHelp {
9687 signatures: vec![lsp::SignatureInformation {
9688 label: "fn sample(param1: u8, param2: u8)".to_string(),
9689 documentation: None,
9690 parameters: Some(vec![
9691 lsp::ParameterInformation {
9692 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
9693 documentation: None,
9694 },
9695 lsp::ParameterInformation {
9696 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
9697 documentation: None,
9698 },
9699 ]),
9700 active_parameter: None,
9701 }],
9702 active_signature: Some(0),
9703 active_parameter: Some(1),
9704 };
9705 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
9706 cx.condition(|editor, _| editor.signature_help_state.is_shown())
9707 .await;
9708
9709 // When selecting a range, the popover is gone.
9710 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
9711 cx.update_editor(|editor, window, cx| {
9712 editor.change_selections(None, window, cx, |s| {
9713 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
9714 })
9715 });
9716 cx.assert_editor_state(indoc! {"
9717 fn main() {
9718 sample(param1, «ˇparam2»);
9719 }
9720
9721 fn sample(param1: u8, param2: u8) {}
9722 "});
9723 cx.editor(|editor, _, _| {
9724 assert!(!editor.signature_help_state.is_shown());
9725 });
9726
9727 // When unselecting again, the popover is back if within the brackets.
9728 cx.update_editor(|editor, window, cx| {
9729 editor.change_selections(None, window, cx, |s| {
9730 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
9731 })
9732 });
9733 cx.assert_editor_state(indoc! {"
9734 fn main() {
9735 sample(param1, ˇparam2);
9736 }
9737
9738 fn sample(param1: u8, param2: u8) {}
9739 "});
9740 handle_signature_help_request(&mut cx, mocked_response).await;
9741 cx.condition(|editor, _| editor.signature_help_state.is_shown())
9742 .await;
9743 cx.editor(|editor, _, _| {
9744 assert!(editor.signature_help_state.is_shown());
9745 });
9746
9747 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
9748 cx.update_editor(|editor, window, cx| {
9749 editor.change_selections(None, window, cx, |s| {
9750 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
9751 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
9752 })
9753 });
9754 cx.assert_editor_state(indoc! {"
9755 fn main() {
9756 sample(param1, ˇparam2);
9757 }
9758
9759 fn sample(param1: u8, param2: u8) {}
9760 "});
9761
9762 let mocked_response = lsp::SignatureHelp {
9763 signatures: vec![lsp::SignatureInformation {
9764 label: "fn sample(param1: u8, param2: u8)".to_string(),
9765 documentation: None,
9766 parameters: Some(vec![
9767 lsp::ParameterInformation {
9768 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
9769 documentation: None,
9770 },
9771 lsp::ParameterInformation {
9772 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
9773 documentation: None,
9774 },
9775 ]),
9776 active_parameter: None,
9777 }],
9778 active_signature: Some(0),
9779 active_parameter: Some(1),
9780 };
9781 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
9782 cx.condition(|editor, _| editor.signature_help_state.is_shown())
9783 .await;
9784 cx.update_editor(|editor, _, cx| {
9785 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
9786 });
9787 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
9788 .await;
9789 cx.update_editor(|editor, window, cx| {
9790 editor.change_selections(None, window, cx, |s| {
9791 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
9792 })
9793 });
9794 cx.assert_editor_state(indoc! {"
9795 fn main() {
9796 sample(param1, «ˇparam2»);
9797 }
9798
9799 fn sample(param1: u8, param2: u8) {}
9800 "});
9801 cx.update_editor(|editor, window, cx| {
9802 editor.change_selections(None, window, cx, |s| {
9803 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
9804 })
9805 });
9806 cx.assert_editor_state(indoc! {"
9807 fn main() {
9808 sample(param1, ˇparam2);
9809 }
9810
9811 fn sample(param1: u8, param2: u8) {}
9812 "});
9813 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
9814 .await;
9815}
9816
9817#[gpui::test]
9818async fn test_completion_mode(cx: &mut TestAppContext) {
9819 init_test(cx, |_| {});
9820 let mut cx = EditorLspTestContext::new_rust(
9821 lsp::ServerCapabilities {
9822 completion_provider: Some(lsp::CompletionOptions {
9823 resolve_provider: Some(true),
9824 ..Default::default()
9825 }),
9826 ..Default::default()
9827 },
9828 cx,
9829 )
9830 .await;
9831
9832 struct Run {
9833 run_description: &'static str,
9834 initial_state: String,
9835 buffer_marked_text: String,
9836 completion_text: &'static str,
9837 expected_with_insert_mode: String,
9838 expected_with_replace_mode: String,
9839 expected_with_replace_subsequence_mode: String,
9840 expected_with_replace_suffix_mode: String,
9841 }
9842
9843 let runs = [
9844 Run {
9845 run_description: "Start of word matches completion text",
9846 initial_state: "before ediˇ after".into(),
9847 buffer_marked_text: "before <edi|> after".into(),
9848 completion_text: "editor",
9849 expected_with_insert_mode: "before editorˇ after".into(),
9850 expected_with_replace_mode: "before editorˇ after".into(),
9851 expected_with_replace_subsequence_mode: "before editorˇ after".into(),
9852 expected_with_replace_suffix_mode: "before editorˇ after".into(),
9853 },
9854 Run {
9855 run_description: "Accept same text at the middle of the word",
9856 initial_state: "before ediˇtor after".into(),
9857 buffer_marked_text: "before <edi|tor> after".into(),
9858 completion_text: "editor",
9859 expected_with_insert_mode: "before editorˇtor after".into(),
9860 expected_with_replace_mode: "before editorˇ after".into(),
9861 expected_with_replace_subsequence_mode: "before editorˇ after".into(),
9862 expected_with_replace_suffix_mode: "before editorˇ after".into(),
9863 },
9864 Run {
9865 run_description: "End of word matches completion text -- cursor at end",
9866 initial_state: "before torˇ after".into(),
9867 buffer_marked_text: "before <tor|> after".into(),
9868 completion_text: "editor",
9869 expected_with_insert_mode: "before editorˇ after".into(),
9870 expected_with_replace_mode: "before editorˇ after".into(),
9871 expected_with_replace_subsequence_mode: "before editorˇ after".into(),
9872 expected_with_replace_suffix_mode: "before editorˇ after".into(),
9873 },
9874 Run {
9875 run_description: "End of word matches completion text -- cursor at start",
9876 initial_state: "before ˇtor after".into(),
9877 buffer_marked_text: "before <|tor> after".into(),
9878 completion_text: "editor",
9879 expected_with_insert_mode: "before editorˇtor after".into(),
9880 expected_with_replace_mode: "before editorˇ after".into(),
9881 expected_with_replace_subsequence_mode: "before editorˇ after".into(),
9882 expected_with_replace_suffix_mode: "before editorˇ after".into(),
9883 },
9884 Run {
9885 run_description: "Prepend text containing whitespace",
9886 initial_state: "pˇfield: bool".into(),
9887 buffer_marked_text: "<p|field>: bool".into(),
9888 completion_text: "pub ",
9889 expected_with_insert_mode: "pub ˇfield: bool".into(),
9890 expected_with_replace_mode: "pub ˇ: bool".into(),
9891 expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
9892 expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
9893 },
9894 Run {
9895 run_description: "Add element to start of list",
9896 initial_state: "[element_ˇelement_2]".into(),
9897 buffer_marked_text: "[<element_|element_2>]".into(),
9898 completion_text: "element_1",
9899 expected_with_insert_mode: "[element_1ˇelement_2]".into(),
9900 expected_with_replace_mode: "[element_1ˇ]".into(),
9901 expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
9902 expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
9903 },
9904 Run {
9905 run_description: "Add element to start of list -- first and second elements are equal",
9906 initial_state: "[elˇelement]".into(),
9907 buffer_marked_text: "[<el|element>]".into(),
9908 completion_text: "element",
9909 expected_with_insert_mode: "[elementˇelement]".into(),
9910 expected_with_replace_mode: "[elementˇ]".into(),
9911 expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
9912 expected_with_replace_suffix_mode: "[elementˇ]".into(),
9913 },
9914 Run {
9915 run_description: "Ends with matching suffix",
9916 initial_state: "SubˇError".into(),
9917 buffer_marked_text: "<Sub|Error>".into(),
9918 completion_text: "SubscriptionError",
9919 expected_with_insert_mode: "SubscriptionErrorˇError".into(),
9920 expected_with_replace_mode: "SubscriptionErrorˇ".into(),
9921 expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
9922 expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
9923 },
9924 Run {
9925 run_description: "Suffix is a subsequence -- contiguous",
9926 initial_state: "SubˇErr".into(),
9927 buffer_marked_text: "<Sub|Err>".into(),
9928 completion_text: "SubscriptionError",
9929 expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
9930 expected_with_replace_mode: "SubscriptionErrorˇ".into(),
9931 expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
9932 expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
9933 },
9934 Run {
9935 run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
9936 initial_state: "Suˇscrirr".into(),
9937 buffer_marked_text: "<Su|scrirr>".into(),
9938 completion_text: "SubscriptionError",
9939 expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
9940 expected_with_replace_mode: "SubscriptionErrorˇ".into(),
9941 expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
9942 expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
9943 },
9944 Run {
9945 run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
9946 initial_state: "foo(indˇix)".into(),
9947 buffer_marked_text: "foo(<ind|ix>)".into(),
9948 completion_text: "node_index",
9949 expected_with_insert_mode: "foo(node_indexˇix)".into(),
9950 expected_with_replace_mode: "foo(node_indexˇ)".into(),
9951 expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
9952 expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
9953 },
9954 ];
9955
9956 for run in runs {
9957 let run_variations = [
9958 (LspInsertMode::Insert, run.expected_with_insert_mode),
9959 (LspInsertMode::Replace, run.expected_with_replace_mode),
9960 (
9961 LspInsertMode::ReplaceSubsequence,
9962 run.expected_with_replace_subsequence_mode,
9963 ),
9964 (
9965 LspInsertMode::ReplaceSuffix,
9966 run.expected_with_replace_suffix_mode,
9967 ),
9968 ];
9969
9970 for (lsp_insert_mode, expected_text) in run_variations {
9971 eprintln!(
9972 "run = {:?}, mode = {lsp_insert_mode:.?}",
9973 run.run_description,
9974 );
9975
9976 update_test_language_settings(&mut cx, |settings| {
9977 settings.defaults.completions = Some(CompletionSettings {
9978 lsp_insert_mode,
9979 words: WordsCompletionMode::Disabled,
9980 lsp: true,
9981 lsp_fetch_timeout_ms: 0,
9982 });
9983 });
9984
9985 cx.set_state(&run.initial_state);
9986 cx.update_editor(|editor, window, cx| {
9987 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
9988 });
9989
9990 let counter = Arc::new(AtomicUsize::new(0));
9991 handle_completion_request_with_insert_and_replace(
9992 &mut cx,
9993 &run.buffer_marked_text,
9994 vec![run.completion_text],
9995 counter.clone(),
9996 )
9997 .await;
9998 cx.condition(|editor, _| editor.context_menu_visible())
9999 .await;
10000 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
10001
10002 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10003 editor
10004 .confirm_completion(&ConfirmCompletion::default(), window, cx)
10005 .unwrap()
10006 });
10007 cx.assert_editor_state(&expected_text);
10008 handle_resolve_completion_request(&mut cx, None).await;
10009 apply_additional_edits.await.unwrap();
10010 }
10011 }
10012}
10013
10014#[gpui::test]
10015async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
10016 init_test(cx, |_| {});
10017 let mut cx = EditorLspTestContext::new_rust(
10018 lsp::ServerCapabilities {
10019 completion_provider: Some(lsp::CompletionOptions {
10020 resolve_provider: Some(true),
10021 ..Default::default()
10022 }),
10023 ..Default::default()
10024 },
10025 cx,
10026 )
10027 .await;
10028
10029 let initial_state = "SubˇError";
10030 let buffer_marked_text = "<Sub|Error>";
10031 let completion_text = "SubscriptionError";
10032 let expected_with_insert_mode = "SubscriptionErrorˇError";
10033 let expected_with_replace_mode = "SubscriptionErrorˇ";
10034
10035 update_test_language_settings(&mut cx, |settings| {
10036 settings.defaults.completions = Some(CompletionSettings {
10037 words: WordsCompletionMode::Disabled,
10038 // set the opposite here to ensure that the action is overriding the default behavior
10039 lsp_insert_mode: LspInsertMode::Insert,
10040 lsp: true,
10041 lsp_fetch_timeout_ms: 0,
10042 });
10043 });
10044
10045 cx.set_state(initial_state);
10046 cx.update_editor(|editor, window, cx| {
10047 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10048 });
10049
10050 let counter = Arc::new(AtomicUsize::new(0));
10051 handle_completion_request_with_insert_and_replace(
10052 &mut cx,
10053 &buffer_marked_text,
10054 vec![completion_text],
10055 counter.clone(),
10056 )
10057 .await;
10058 cx.condition(|editor, _| editor.context_menu_visible())
10059 .await;
10060 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
10061
10062 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10063 editor
10064 .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10065 .unwrap()
10066 });
10067 cx.assert_editor_state(&expected_with_replace_mode);
10068 handle_resolve_completion_request(&mut cx, None).await;
10069 apply_additional_edits.await.unwrap();
10070
10071 update_test_language_settings(&mut cx, |settings| {
10072 settings.defaults.completions = Some(CompletionSettings {
10073 words: WordsCompletionMode::Disabled,
10074 // set the opposite here to ensure that the action is overriding the default behavior
10075 lsp_insert_mode: LspInsertMode::Replace,
10076 lsp: true,
10077 lsp_fetch_timeout_ms: 0,
10078 });
10079 });
10080
10081 cx.set_state(initial_state);
10082 cx.update_editor(|editor, window, cx| {
10083 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10084 });
10085 handle_completion_request_with_insert_and_replace(
10086 &mut cx,
10087 &buffer_marked_text,
10088 vec![completion_text],
10089 counter.clone(),
10090 )
10091 .await;
10092 cx.condition(|editor, _| editor.context_menu_visible())
10093 .await;
10094 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
10095
10096 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10097 editor
10098 .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
10099 .unwrap()
10100 });
10101 cx.assert_editor_state(&expected_with_insert_mode);
10102 handle_resolve_completion_request(&mut cx, None).await;
10103 apply_additional_edits.await.unwrap();
10104}
10105
10106#[gpui::test]
10107async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
10108 init_test(cx, |_| {});
10109 let mut cx = EditorLspTestContext::new_rust(
10110 lsp::ServerCapabilities {
10111 completion_provider: Some(lsp::CompletionOptions {
10112 resolve_provider: Some(true),
10113 ..Default::default()
10114 }),
10115 ..Default::default()
10116 },
10117 cx,
10118 )
10119 .await;
10120
10121 // scenario: surrounding text matches completion text
10122 let completion_text = "to_offset";
10123 let initial_state = indoc! {"
10124 1. buf.to_offˇsuffix
10125 2. buf.to_offˇsuf
10126 3. buf.to_offˇfix
10127 4. buf.to_offˇ
10128 5. into_offˇensive
10129 6. ˇsuffix
10130 7. let ˇ //
10131 8. aaˇzz
10132 9. buf.to_off«zzzzzˇ»suffix
10133 10. buf.«ˇzzzzz»suffix
10134 11. to_off«ˇzzzzz»
10135
10136 buf.to_offˇsuffix // newest cursor
10137 "};
10138 let completion_marked_buffer = indoc! {"
10139 1. buf.to_offsuffix
10140 2. buf.to_offsuf
10141 3. buf.to_offfix
10142 4. buf.to_off
10143 5. into_offensive
10144 6. suffix
10145 7. let //
10146 8. aazz
10147 9. buf.to_offzzzzzsuffix
10148 10. buf.zzzzzsuffix
10149 11. to_offzzzzz
10150
10151 buf.<to_off|suffix> // newest cursor
10152 "};
10153 let expected = indoc! {"
10154 1. buf.to_offsetˇ
10155 2. buf.to_offsetˇsuf
10156 3. buf.to_offsetˇfix
10157 4. buf.to_offsetˇ
10158 5. into_offsetˇensive
10159 6. to_offsetˇsuffix
10160 7. let to_offsetˇ //
10161 8. aato_offsetˇzz
10162 9. buf.to_offsetˇ
10163 10. buf.to_offsetˇsuffix
10164 11. to_offsetˇ
10165
10166 buf.to_offsetˇ // newest cursor
10167 "};
10168 cx.set_state(initial_state);
10169 cx.update_editor(|editor, window, cx| {
10170 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10171 });
10172 handle_completion_request_with_insert_and_replace(
10173 &mut cx,
10174 completion_marked_buffer,
10175 vec![completion_text],
10176 Arc::new(AtomicUsize::new(0)),
10177 )
10178 .await;
10179 cx.condition(|editor, _| editor.context_menu_visible())
10180 .await;
10181 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10182 editor
10183 .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10184 .unwrap()
10185 });
10186 cx.assert_editor_state(expected);
10187 handle_resolve_completion_request(&mut cx, None).await;
10188 apply_additional_edits.await.unwrap();
10189
10190 // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
10191 let completion_text = "foo_and_bar";
10192 let initial_state = indoc! {"
10193 1. ooanbˇ
10194 2. zooanbˇ
10195 3. ooanbˇz
10196 4. zooanbˇz
10197 5. ooanˇ
10198 6. oanbˇ
10199
10200 ooanbˇ
10201 "};
10202 let completion_marked_buffer = indoc! {"
10203 1. ooanb
10204 2. zooanb
10205 3. ooanbz
10206 4. zooanbz
10207 5. ooan
10208 6. oanb
10209
10210 <ooanb|>
10211 "};
10212 let expected = indoc! {"
10213 1. foo_and_barˇ
10214 2. zfoo_and_barˇ
10215 3. foo_and_barˇz
10216 4. zfoo_and_barˇz
10217 5. ooanfoo_and_barˇ
10218 6. oanbfoo_and_barˇ
10219
10220 foo_and_barˇ
10221 "};
10222 cx.set_state(initial_state);
10223 cx.update_editor(|editor, window, cx| {
10224 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10225 });
10226 handle_completion_request_with_insert_and_replace(
10227 &mut cx,
10228 completion_marked_buffer,
10229 vec![completion_text],
10230 Arc::new(AtomicUsize::new(0)),
10231 )
10232 .await;
10233 cx.condition(|editor, _| editor.context_menu_visible())
10234 .await;
10235 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10236 editor
10237 .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10238 .unwrap()
10239 });
10240 cx.assert_editor_state(expected);
10241 handle_resolve_completion_request(&mut cx, None).await;
10242 apply_additional_edits.await.unwrap();
10243
10244 // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
10245 // (expects the same as if it was inserted at the end)
10246 let completion_text = "foo_and_bar";
10247 let initial_state = indoc! {"
10248 1. ooˇanb
10249 2. zooˇanb
10250 3. ooˇanbz
10251 4. zooˇanbz
10252
10253 ooˇanb
10254 "};
10255 let completion_marked_buffer = indoc! {"
10256 1. ooanb
10257 2. zooanb
10258 3. ooanbz
10259 4. zooanbz
10260
10261 <oo|anb>
10262 "};
10263 let expected = indoc! {"
10264 1. foo_and_barˇ
10265 2. zfoo_and_barˇ
10266 3. foo_and_barˇz
10267 4. zfoo_and_barˇz
10268
10269 foo_and_barˇ
10270 "};
10271 cx.set_state(initial_state);
10272 cx.update_editor(|editor, window, cx| {
10273 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10274 });
10275 handle_completion_request_with_insert_and_replace(
10276 &mut cx,
10277 completion_marked_buffer,
10278 vec![completion_text],
10279 Arc::new(AtomicUsize::new(0)),
10280 )
10281 .await;
10282 cx.condition(|editor, _| editor.context_menu_visible())
10283 .await;
10284 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10285 editor
10286 .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10287 .unwrap()
10288 });
10289 cx.assert_editor_state(expected);
10290 handle_resolve_completion_request(&mut cx, None).await;
10291 apply_additional_edits.await.unwrap();
10292}
10293
10294// This used to crash
10295#[gpui::test]
10296async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
10297 init_test(cx, |_| {});
10298
10299 let buffer_text = indoc! {"
10300 fn main() {
10301 10.satu;
10302
10303 //
10304 // separate cursors so they open in different excerpts (manually reproducible)
10305 //
10306
10307 10.satu20;
10308 }
10309 "};
10310 let multibuffer_text_with_selections = indoc! {"
10311 fn main() {
10312 10.satuˇ;
10313
10314 //
10315
10316 //
10317
10318 10.satuˇ20;
10319 }
10320 "};
10321 let expected_multibuffer = indoc! {"
10322 fn main() {
10323 10.saturating_sub()ˇ;
10324
10325 //
10326
10327 //
10328
10329 10.saturating_sub()ˇ;
10330 }
10331 "};
10332
10333 let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
10334 let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
10335
10336 let fs = FakeFs::new(cx.executor());
10337 fs.insert_tree(
10338 path!("/a"),
10339 json!({
10340 "main.rs": buffer_text,
10341 }),
10342 )
10343 .await;
10344
10345 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
10346 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10347 language_registry.add(rust_lang());
10348 let mut fake_servers = language_registry.register_fake_lsp(
10349 "Rust",
10350 FakeLspAdapter {
10351 capabilities: lsp::ServerCapabilities {
10352 completion_provider: Some(lsp::CompletionOptions {
10353 resolve_provider: None,
10354 ..lsp::CompletionOptions::default()
10355 }),
10356 ..lsp::ServerCapabilities::default()
10357 },
10358 ..FakeLspAdapter::default()
10359 },
10360 );
10361 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
10362 let cx = &mut VisualTestContext::from_window(*workspace, cx);
10363 let buffer = project
10364 .update(cx, |project, cx| {
10365 project.open_local_buffer(path!("/a/main.rs"), cx)
10366 })
10367 .await
10368 .unwrap();
10369
10370 let multi_buffer = cx.new(|cx| {
10371 let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
10372 multi_buffer.push_excerpts(
10373 buffer.clone(),
10374 [ExcerptRange::new(0..first_excerpt_end)],
10375 cx,
10376 );
10377 multi_buffer.push_excerpts(
10378 buffer.clone(),
10379 [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
10380 cx,
10381 );
10382 multi_buffer
10383 });
10384
10385 let editor = workspace
10386 .update(cx, |_, window, cx| {
10387 cx.new(|cx| {
10388 Editor::new(
10389 EditorMode::Full {
10390 scale_ui_elements_with_buffer_font_size: false,
10391 show_active_line_background: false,
10392 },
10393 multi_buffer.clone(),
10394 Some(project.clone()),
10395 window,
10396 cx,
10397 )
10398 })
10399 })
10400 .unwrap();
10401
10402 let pane = workspace
10403 .update(cx, |workspace, _, _| workspace.active_pane().clone())
10404 .unwrap();
10405 pane.update_in(cx, |pane, window, cx| {
10406 pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
10407 });
10408
10409 let fake_server = fake_servers.next().await.unwrap();
10410
10411 editor.update_in(cx, |editor, window, cx| {
10412 editor.change_selections(None, window, cx, |s| {
10413 s.select_ranges([
10414 Point::new(1, 11)..Point::new(1, 11),
10415 Point::new(7, 11)..Point::new(7, 11),
10416 ])
10417 });
10418
10419 assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
10420 });
10421
10422 editor.update_in(cx, |editor, window, cx| {
10423 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10424 });
10425
10426 fake_server
10427 .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
10428 let completion_item = lsp::CompletionItem {
10429 label: "saturating_sub()".into(),
10430 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
10431 lsp::InsertReplaceEdit {
10432 new_text: "saturating_sub()".to_owned(),
10433 insert: lsp::Range::new(
10434 lsp::Position::new(7, 7),
10435 lsp::Position::new(7, 11),
10436 ),
10437 replace: lsp::Range::new(
10438 lsp::Position::new(7, 7),
10439 lsp::Position::new(7, 13),
10440 ),
10441 },
10442 )),
10443 ..lsp::CompletionItem::default()
10444 };
10445
10446 Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
10447 })
10448 .next()
10449 .await
10450 .unwrap();
10451
10452 cx.condition(&editor, |editor, _| editor.context_menu_visible())
10453 .await;
10454
10455 editor
10456 .update_in(cx, |editor, window, cx| {
10457 editor
10458 .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10459 .unwrap()
10460 })
10461 .await
10462 .unwrap();
10463
10464 editor.update(cx, |editor, cx| {
10465 assert_text_with_selections(editor, expected_multibuffer, cx);
10466 })
10467}
10468
10469#[gpui::test]
10470async fn test_completion(cx: &mut TestAppContext) {
10471 init_test(cx, |_| {});
10472
10473 let mut cx = EditorLspTestContext::new_rust(
10474 lsp::ServerCapabilities {
10475 completion_provider: Some(lsp::CompletionOptions {
10476 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
10477 resolve_provider: Some(true),
10478 ..Default::default()
10479 }),
10480 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
10481 ..Default::default()
10482 },
10483 cx,
10484 )
10485 .await;
10486 let counter = Arc::new(AtomicUsize::new(0));
10487
10488 cx.set_state(indoc! {"
10489 oneˇ
10490 two
10491 three
10492 "});
10493 cx.simulate_keystroke(".");
10494 handle_completion_request(
10495 &mut cx,
10496 indoc! {"
10497 one.|<>
10498 two
10499 three
10500 "},
10501 vec!["first_completion", "second_completion"],
10502 counter.clone(),
10503 )
10504 .await;
10505 cx.condition(|editor, _| editor.context_menu_visible())
10506 .await;
10507 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
10508
10509 let _handler = handle_signature_help_request(
10510 &mut cx,
10511 lsp::SignatureHelp {
10512 signatures: vec![lsp::SignatureInformation {
10513 label: "test signature".to_string(),
10514 documentation: None,
10515 parameters: Some(vec![lsp::ParameterInformation {
10516 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
10517 documentation: None,
10518 }]),
10519 active_parameter: None,
10520 }],
10521 active_signature: None,
10522 active_parameter: None,
10523 },
10524 );
10525 cx.update_editor(|editor, window, cx| {
10526 assert!(
10527 !editor.signature_help_state.is_shown(),
10528 "No signature help was called for"
10529 );
10530 editor.show_signature_help(&ShowSignatureHelp, window, cx);
10531 });
10532 cx.run_until_parked();
10533 cx.update_editor(|editor, _, _| {
10534 assert!(
10535 !editor.signature_help_state.is_shown(),
10536 "No signature help should be shown when completions menu is open"
10537 );
10538 });
10539
10540 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10541 editor.context_menu_next(&Default::default(), window, cx);
10542 editor
10543 .confirm_completion(&ConfirmCompletion::default(), window, cx)
10544 .unwrap()
10545 });
10546 cx.assert_editor_state(indoc! {"
10547 one.second_completionˇ
10548 two
10549 three
10550 "});
10551
10552 handle_resolve_completion_request(
10553 &mut cx,
10554 Some(vec![
10555 (
10556 //This overlaps with the primary completion edit which is
10557 //misbehavior from the LSP spec, test that we filter it out
10558 indoc! {"
10559 one.second_ˇcompletion
10560 two
10561 threeˇ
10562 "},
10563 "overlapping additional edit",
10564 ),
10565 (
10566 indoc! {"
10567 one.second_completion
10568 two
10569 threeˇ
10570 "},
10571 "\nadditional edit",
10572 ),
10573 ]),
10574 )
10575 .await;
10576 apply_additional_edits.await.unwrap();
10577 cx.assert_editor_state(indoc! {"
10578 one.second_completionˇ
10579 two
10580 three
10581 additional edit
10582 "});
10583
10584 cx.set_state(indoc! {"
10585 one.second_completion
10586 twoˇ
10587 threeˇ
10588 additional edit
10589 "});
10590 cx.simulate_keystroke(" ");
10591 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
10592 cx.simulate_keystroke("s");
10593 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
10594
10595 cx.assert_editor_state(indoc! {"
10596 one.second_completion
10597 two sˇ
10598 three sˇ
10599 additional edit
10600 "});
10601 handle_completion_request(
10602 &mut cx,
10603 indoc! {"
10604 one.second_completion
10605 two s
10606 three <s|>
10607 additional edit
10608 "},
10609 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
10610 counter.clone(),
10611 )
10612 .await;
10613 cx.condition(|editor, _| editor.context_menu_visible())
10614 .await;
10615 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
10616
10617 cx.simulate_keystroke("i");
10618
10619 handle_completion_request(
10620 &mut cx,
10621 indoc! {"
10622 one.second_completion
10623 two si
10624 three <si|>
10625 additional edit
10626 "},
10627 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
10628 counter.clone(),
10629 )
10630 .await;
10631 cx.condition(|editor, _| editor.context_menu_visible())
10632 .await;
10633 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
10634
10635 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10636 editor
10637 .confirm_completion(&ConfirmCompletion::default(), window, cx)
10638 .unwrap()
10639 });
10640 cx.assert_editor_state(indoc! {"
10641 one.second_completion
10642 two sixth_completionˇ
10643 three sixth_completionˇ
10644 additional edit
10645 "});
10646
10647 apply_additional_edits.await.unwrap();
10648
10649 update_test_language_settings(&mut cx, |settings| {
10650 settings.defaults.show_completions_on_input = Some(false);
10651 });
10652 cx.set_state("editorˇ");
10653 cx.simulate_keystroke(".");
10654 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
10655 cx.simulate_keystrokes("c l o");
10656 cx.assert_editor_state("editor.cloˇ");
10657 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
10658 cx.update_editor(|editor, window, cx| {
10659 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10660 });
10661 handle_completion_request(
10662 &mut cx,
10663 "editor.<clo|>",
10664 vec!["close", "clobber"],
10665 counter.clone(),
10666 )
10667 .await;
10668 cx.condition(|editor, _| editor.context_menu_visible())
10669 .await;
10670 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
10671
10672 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10673 editor
10674 .confirm_completion(&ConfirmCompletion::default(), window, cx)
10675 .unwrap()
10676 });
10677 cx.assert_editor_state("editor.closeˇ");
10678 handle_resolve_completion_request(&mut cx, None).await;
10679 apply_additional_edits.await.unwrap();
10680}
10681
10682#[gpui::test]
10683async fn test_word_completion(cx: &mut TestAppContext) {
10684 let lsp_fetch_timeout_ms = 10;
10685 init_test(cx, |language_settings| {
10686 language_settings.defaults.completions = Some(CompletionSettings {
10687 words: WordsCompletionMode::Fallback,
10688 lsp: true,
10689 lsp_fetch_timeout_ms: 10,
10690 lsp_insert_mode: LspInsertMode::Insert,
10691 });
10692 });
10693
10694 let mut cx = EditorLspTestContext::new_rust(
10695 lsp::ServerCapabilities {
10696 completion_provider: Some(lsp::CompletionOptions {
10697 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
10698 ..lsp::CompletionOptions::default()
10699 }),
10700 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
10701 ..lsp::ServerCapabilities::default()
10702 },
10703 cx,
10704 )
10705 .await;
10706
10707 let throttle_completions = Arc::new(AtomicBool::new(false));
10708
10709 let lsp_throttle_completions = throttle_completions.clone();
10710 let _completion_requests_handler =
10711 cx.lsp
10712 .server
10713 .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
10714 let lsp_throttle_completions = lsp_throttle_completions.clone();
10715 let cx = cx.clone();
10716 async move {
10717 if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
10718 cx.background_executor()
10719 .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
10720 .await;
10721 }
10722 Ok(Some(lsp::CompletionResponse::Array(vec![
10723 lsp::CompletionItem {
10724 label: "first".into(),
10725 ..lsp::CompletionItem::default()
10726 },
10727 lsp::CompletionItem {
10728 label: "last".into(),
10729 ..lsp::CompletionItem::default()
10730 },
10731 ])))
10732 }
10733 });
10734
10735 cx.set_state(indoc! {"
10736 oneˇ
10737 two
10738 three
10739 "});
10740 cx.simulate_keystroke(".");
10741 cx.executor().run_until_parked();
10742 cx.condition(|editor, _| editor.context_menu_visible())
10743 .await;
10744 cx.update_editor(|editor, window, cx| {
10745 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10746 {
10747 assert_eq!(
10748 completion_menu_entries(&menu),
10749 &["first", "last"],
10750 "When LSP server is fast to reply, no fallback word completions are used"
10751 );
10752 } else {
10753 panic!("expected completion menu to be open");
10754 }
10755 editor.cancel(&Cancel, window, cx);
10756 });
10757 cx.executor().run_until_parked();
10758 cx.condition(|editor, _| !editor.context_menu_visible())
10759 .await;
10760
10761 throttle_completions.store(true, atomic::Ordering::Release);
10762 cx.simulate_keystroke(".");
10763 cx.executor()
10764 .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
10765 cx.executor().run_until_parked();
10766 cx.condition(|editor, _| editor.context_menu_visible())
10767 .await;
10768 cx.update_editor(|editor, _, _| {
10769 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10770 {
10771 assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
10772 "When LSP server is slow, document words can be shown instead, if configured accordingly");
10773 } else {
10774 panic!("expected completion menu to be open");
10775 }
10776 });
10777}
10778
10779#[gpui::test]
10780async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
10781 init_test(cx, |language_settings| {
10782 language_settings.defaults.completions = Some(CompletionSettings {
10783 words: WordsCompletionMode::Enabled,
10784 lsp: true,
10785 lsp_fetch_timeout_ms: 0,
10786 lsp_insert_mode: LspInsertMode::Insert,
10787 });
10788 });
10789
10790 let mut cx = EditorLspTestContext::new_rust(
10791 lsp::ServerCapabilities {
10792 completion_provider: Some(lsp::CompletionOptions {
10793 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
10794 ..lsp::CompletionOptions::default()
10795 }),
10796 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
10797 ..lsp::ServerCapabilities::default()
10798 },
10799 cx,
10800 )
10801 .await;
10802
10803 let _completion_requests_handler =
10804 cx.lsp
10805 .server
10806 .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
10807 Ok(Some(lsp::CompletionResponse::Array(vec![
10808 lsp::CompletionItem {
10809 label: "first".into(),
10810 ..lsp::CompletionItem::default()
10811 },
10812 lsp::CompletionItem {
10813 label: "last".into(),
10814 ..lsp::CompletionItem::default()
10815 },
10816 ])))
10817 });
10818
10819 cx.set_state(indoc! {"ˇ
10820 first
10821 last
10822 second
10823 "});
10824 cx.simulate_keystroke(".");
10825 cx.executor().run_until_parked();
10826 cx.condition(|editor, _| editor.context_menu_visible())
10827 .await;
10828 cx.update_editor(|editor, _, _| {
10829 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10830 {
10831 assert_eq!(
10832 completion_menu_entries(&menu),
10833 &["first", "last", "second"],
10834 "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
10835 );
10836 } else {
10837 panic!("expected completion menu to be open");
10838 }
10839 });
10840}
10841
10842#[gpui::test]
10843async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
10844 init_test(cx, |language_settings| {
10845 language_settings.defaults.completions = Some(CompletionSettings {
10846 words: WordsCompletionMode::Disabled,
10847 lsp: true,
10848 lsp_fetch_timeout_ms: 0,
10849 lsp_insert_mode: LspInsertMode::Insert,
10850 });
10851 });
10852
10853 let mut cx = EditorLspTestContext::new_rust(
10854 lsp::ServerCapabilities {
10855 completion_provider: Some(lsp::CompletionOptions {
10856 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
10857 ..lsp::CompletionOptions::default()
10858 }),
10859 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
10860 ..lsp::ServerCapabilities::default()
10861 },
10862 cx,
10863 )
10864 .await;
10865
10866 let _completion_requests_handler =
10867 cx.lsp
10868 .server
10869 .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
10870 panic!("LSP completions should not be queried when dealing with word completions")
10871 });
10872
10873 cx.set_state(indoc! {"ˇ
10874 first
10875 last
10876 second
10877 "});
10878 cx.update_editor(|editor, window, cx| {
10879 editor.show_word_completions(&ShowWordCompletions, window, cx);
10880 });
10881 cx.executor().run_until_parked();
10882 cx.condition(|editor, _| editor.context_menu_visible())
10883 .await;
10884 cx.update_editor(|editor, _, _| {
10885 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10886 {
10887 assert_eq!(
10888 completion_menu_entries(&menu),
10889 &["first", "last", "second"],
10890 "`ShowWordCompletions` action should show word completions"
10891 );
10892 } else {
10893 panic!("expected completion menu to be open");
10894 }
10895 });
10896
10897 cx.simulate_keystroke("l");
10898 cx.executor().run_until_parked();
10899 cx.condition(|editor, _| editor.context_menu_visible())
10900 .await;
10901 cx.update_editor(|editor, _, _| {
10902 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10903 {
10904 assert_eq!(
10905 completion_menu_entries(&menu),
10906 &["last"],
10907 "After showing word completions, further editing should filter them and not query the LSP"
10908 );
10909 } else {
10910 panic!("expected completion menu to be open");
10911 }
10912 });
10913}
10914
10915#[gpui::test]
10916async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
10917 init_test(cx, |language_settings| {
10918 language_settings.defaults.completions = Some(CompletionSettings {
10919 words: WordsCompletionMode::Fallback,
10920 lsp: false,
10921 lsp_fetch_timeout_ms: 0,
10922 lsp_insert_mode: LspInsertMode::Insert,
10923 });
10924 });
10925
10926 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10927
10928 cx.set_state(indoc! {"ˇ
10929 0_usize
10930 let
10931 33
10932 4.5f32
10933 "});
10934 cx.update_editor(|editor, window, cx| {
10935 editor.show_completions(&ShowCompletions::default(), window, cx);
10936 });
10937 cx.executor().run_until_parked();
10938 cx.condition(|editor, _| editor.context_menu_visible())
10939 .await;
10940 cx.update_editor(|editor, window, cx| {
10941 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10942 {
10943 assert_eq!(
10944 completion_menu_entries(&menu),
10945 &["let"],
10946 "With no digits in the completion query, no digits should be in the word completions"
10947 );
10948 } else {
10949 panic!("expected completion menu to be open");
10950 }
10951 editor.cancel(&Cancel, window, cx);
10952 });
10953
10954 cx.set_state(indoc! {"3ˇ
10955 0_usize
10956 let
10957 3
10958 33.35f32
10959 "});
10960 cx.update_editor(|editor, window, cx| {
10961 editor.show_completions(&ShowCompletions::default(), window, cx);
10962 });
10963 cx.executor().run_until_parked();
10964 cx.condition(|editor, _| editor.context_menu_visible())
10965 .await;
10966 cx.update_editor(|editor, _, _| {
10967 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10968 {
10969 assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
10970 return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
10971 } else {
10972 panic!("expected completion menu to be open");
10973 }
10974 });
10975}
10976
10977fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
10978 let position = || lsp::Position {
10979 line: params.text_document_position.position.line,
10980 character: params.text_document_position.position.character,
10981 };
10982 Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10983 range: lsp::Range {
10984 start: position(),
10985 end: position(),
10986 },
10987 new_text: text.to_string(),
10988 }))
10989}
10990
10991#[gpui::test]
10992async fn test_multiline_completion(cx: &mut TestAppContext) {
10993 init_test(cx, |_| {});
10994
10995 let fs = FakeFs::new(cx.executor());
10996 fs.insert_tree(
10997 path!("/a"),
10998 json!({
10999 "main.ts": "a",
11000 }),
11001 )
11002 .await;
11003
11004 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11005 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11006 let typescript_language = Arc::new(Language::new(
11007 LanguageConfig {
11008 name: "TypeScript".into(),
11009 matcher: LanguageMatcher {
11010 path_suffixes: vec!["ts".to_string()],
11011 ..LanguageMatcher::default()
11012 },
11013 line_comments: vec!["// ".into()],
11014 ..LanguageConfig::default()
11015 },
11016 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
11017 ));
11018 language_registry.add(typescript_language.clone());
11019 let mut fake_servers = language_registry.register_fake_lsp(
11020 "TypeScript",
11021 FakeLspAdapter {
11022 capabilities: lsp::ServerCapabilities {
11023 completion_provider: Some(lsp::CompletionOptions {
11024 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11025 ..lsp::CompletionOptions::default()
11026 }),
11027 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11028 ..lsp::ServerCapabilities::default()
11029 },
11030 // Emulate vtsls label generation
11031 label_for_completion: Some(Box::new(|item, _| {
11032 let text = if let Some(description) = item
11033 .label_details
11034 .as_ref()
11035 .and_then(|label_details| label_details.description.as_ref())
11036 {
11037 format!("{} {}", item.label, description)
11038 } else if let Some(detail) = &item.detail {
11039 format!("{} {}", item.label, detail)
11040 } else {
11041 item.label.clone()
11042 };
11043 let len = text.len();
11044 Some(language::CodeLabel {
11045 text,
11046 runs: Vec::new(),
11047 filter_range: 0..len,
11048 })
11049 })),
11050 ..FakeLspAdapter::default()
11051 },
11052 );
11053 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11054 let cx = &mut VisualTestContext::from_window(*workspace, cx);
11055 let worktree_id = workspace
11056 .update(cx, |workspace, _window, cx| {
11057 workspace.project().update(cx, |project, cx| {
11058 project.worktrees(cx).next().unwrap().read(cx).id()
11059 })
11060 })
11061 .unwrap();
11062 let _buffer = project
11063 .update(cx, |project, cx| {
11064 project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
11065 })
11066 .await
11067 .unwrap();
11068 let editor = workspace
11069 .update(cx, |workspace, window, cx| {
11070 workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
11071 })
11072 .unwrap()
11073 .await
11074 .unwrap()
11075 .downcast::<Editor>()
11076 .unwrap();
11077 let fake_server = fake_servers.next().await.unwrap();
11078
11079 let multiline_label = "StickyHeaderExcerpt {\n excerpt,\n next_excerpt_controls_present,\n next_buffer_row,\n }: StickyHeaderExcerpt<'_>,";
11080 let multiline_label_2 = "a\nb\nc\n";
11081 let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
11082 let multiline_description = "d\ne\nf\n";
11083 let multiline_detail_2 = "g\nh\ni\n";
11084
11085 let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
11086 move |params, _| async move {
11087 Ok(Some(lsp::CompletionResponse::Array(vec![
11088 lsp::CompletionItem {
11089 label: multiline_label.to_string(),
11090 text_edit: gen_text_edit(¶ms, "new_text_1"),
11091 ..lsp::CompletionItem::default()
11092 },
11093 lsp::CompletionItem {
11094 label: "single line label 1".to_string(),
11095 detail: Some(multiline_detail.to_string()),
11096 text_edit: gen_text_edit(¶ms, "new_text_2"),
11097 ..lsp::CompletionItem::default()
11098 },
11099 lsp::CompletionItem {
11100 label: "single line label 2".to_string(),
11101 label_details: Some(lsp::CompletionItemLabelDetails {
11102 description: Some(multiline_description.to_string()),
11103 detail: None,
11104 }),
11105 text_edit: gen_text_edit(¶ms, "new_text_2"),
11106 ..lsp::CompletionItem::default()
11107 },
11108 lsp::CompletionItem {
11109 label: multiline_label_2.to_string(),
11110 detail: Some(multiline_detail_2.to_string()),
11111 text_edit: gen_text_edit(¶ms, "new_text_3"),
11112 ..lsp::CompletionItem::default()
11113 },
11114 lsp::CompletionItem {
11115 label: "Label with many spaces and \t but without newlines".to_string(),
11116 detail: Some(
11117 "Details with many spaces and \t but without newlines".to_string(),
11118 ),
11119 text_edit: gen_text_edit(¶ms, "new_text_4"),
11120 ..lsp::CompletionItem::default()
11121 },
11122 ])))
11123 },
11124 );
11125
11126 editor.update_in(cx, |editor, window, cx| {
11127 cx.focus_self(window);
11128 editor.move_to_end(&MoveToEnd, window, cx);
11129 editor.handle_input(".", window, cx);
11130 });
11131 cx.run_until_parked();
11132 completion_handle.next().await.unwrap();
11133
11134 editor.update(cx, |editor, _| {
11135 assert!(editor.context_menu_visible());
11136 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11137 {
11138 let completion_labels = menu
11139 .completions
11140 .borrow()
11141 .iter()
11142 .map(|c| c.label.text.clone())
11143 .collect::<Vec<_>>();
11144 assert_eq!(
11145 completion_labels,
11146 &[
11147 "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
11148 "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
11149 "single line label 2 d e f ",
11150 "a b c g h i ",
11151 "Label with many spaces and \t but without newlines Details with many spaces and \t but without newlines",
11152 ],
11153 "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
11154 );
11155
11156 for completion in menu
11157 .completions
11158 .borrow()
11159 .iter() {
11160 assert_eq!(
11161 completion.label.filter_range,
11162 0..completion.label.text.len(),
11163 "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
11164 );
11165 }
11166 } else {
11167 panic!("expected completion menu to be open");
11168 }
11169 });
11170}
11171
11172#[gpui::test]
11173async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
11174 init_test(cx, |_| {});
11175 let mut cx = EditorLspTestContext::new_rust(
11176 lsp::ServerCapabilities {
11177 completion_provider: Some(lsp::CompletionOptions {
11178 trigger_characters: Some(vec![".".to_string()]),
11179 ..Default::default()
11180 }),
11181 ..Default::default()
11182 },
11183 cx,
11184 )
11185 .await;
11186 cx.lsp
11187 .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
11188 Ok(Some(lsp::CompletionResponse::Array(vec![
11189 lsp::CompletionItem {
11190 label: "first".into(),
11191 ..Default::default()
11192 },
11193 lsp::CompletionItem {
11194 label: "last".into(),
11195 ..Default::default()
11196 },
11197 ])))
11198 });
11199 cx.set_state("variableˇ");
11200 cx.simulate_keystroke(".");
11201 cx.executor().run_until_parked();
11202
11203 cx.update_editor(|editor, _, _| {
11204 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11205 {
11206 assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
11207 } else {
11208 panic!("expected completion menu to be open");
11209 }
11210 });
11211
11212 cx.update_editor(|editor, window, cx| {
11213 editor.move_page_down(&MovePageDown::default(), window, cx);
11214 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11215 {
11216 assert!(
11217 menu.selected_item == 1,
11218 "expected PageDown to select the last item from the context menu"
11219 );
11220 } else {
11221 panic!("expected completion menu to stay open after PageDown");
11222 }
11223 });
11224
11225 cx.update_editor(|editor, window, cx| {
11226 editor.move_page_up(&MovePageUp::default(), window, cx);
11227 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11228 {
11229 assert!(
11230 menu.selected_item == 0,
11231 "expected PageUp to select the first item from the context menu"
11232 );
11233 } else {
11234 panic!("expected completion menu to stay open after PageUp");
11235 }
11236 });
11237}
11238
11239#[gpui::test]
11240async fn test_completion_sort(cx: &mut TestAppContext) {
11241 init_test(cx, |_| {});
11242 let mut cx = EditorLspTestContext::new_rust(
11243 lsp::ServerCapabilities {
11244 completion_provider: Some(lsp::CompletionOptions {
11245 trigger_characters: Some(vec![".".to_string()]),
11246 ..Default::default()
11247 }),
11248 ..Default::default()
11249 },
11250 cx,
11251 )
11252 .await;
11253 cx.lsp
11254 .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
11255 Ok(Some(lsp::CompletionResponse::Array(vec![
11256 lsp::CompletionItem {
11257 label: "Range".into(),
11258 sort_text: Some("a".into()),
11259 ..Default::default()
11260 },
11261 lsp::CompletionItem {
11262 label: "r".into(),
11263 sort_text: Some("b".into()),
11264 ..Default::default()
11265 },
11266 lsp::CompletionItem {
11267 label: "ret".into(),
11268 sort_text: Some("c".into()),
11269 ..Default::default()
11270 },
11271 lsp::CompletionItem {
11272 label: "return".into(),
11273 sort_text: Some("d".into()),
11274 ..Default::default()
11275 },
11276 lsp::CompletionItem {
11277 label: "slice".into(),
11278 sort_text: Some("d".into()),
11279 ..Default::default()
11280 },
11281 ])))
11282 });
11283 cx.set_state("rˇ");
11284 cx.executor().run_until_parked();
11285 cx.update_editor(|editor, window, cx| {
11286 editor.show_completions(
11287 &ShowCompletions {
11288 trigger: Some("r".into()),
11289 },
11290 window,
11291 cx,
11292 );
11293 });
11294 cx.executor().run_until_parked();
11295
11296 cx.update_editor(|editor, _, _| {
11297 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11298 {
11299 assert_eq!(
11300 completion_menu_entries(&menu),
11301 &["r", "ret", "Range", "return"]
11302 );
11303 } else {
11304 panic!("expected completion menu to be open");
11305 }
11306 });
11307}
11308
11309#[gpui::test]
11310async fn test_as_is_completions(cx: &mut TestAppContext) {
11311 init_test(cx, |_| {});
11312 let mut cx = EditorLspTestContext::new_rust(
11313 lsp::ServerCapabilities {
11314 completion_provider: Some(lsp::CompletionOptions {
11315 ..Default::default()
11316 }),
11317 ..Default::default()
11318 },
11319 cx,
11320 )
11321 .await;
11322 cx.lsp
11323 .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
11324 Ok(Some(lsp::CompletionResponse::Array(vec![
11325 lsp::CompletionItem {
11326 label: "unsafe".into(),
11327 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11328 range: lsp::Range {
11329 start: lsp::Position {
11330 line: 1,
11331 character: 2,
11332 },
11333 end: lsp::Position {
11334 line: 1,
11335 character: 3,
11336 },
11337 },
11338 new_text: "unsafe".to_string(),
11339 })),
11340 insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
11341 ..Default::default()
11342 },
11343 ])))
11344 });
11345 cx.set_state("fn a() {}\n nˇ");
11346 cx.executor().run_until_parked();
11347 cx.update_editor(|editor, window, cx| {
11348 editor.show_completions(
11349 &ShowCompletions {
11350 trigger: Some("\n".into()),
11351 },
11352 window,
11353 cx,
11354 );
11355 });
11356 cx.executor().run_until_parked();
11357
11358 cx.update_editor(|editor, window, cx| {
11359 editor.confirm_completion(&Default::default(), window, cx)
11360 });
11361 cx.executor().run_until_parked();
11362 cx.assert_editor_state("fn a() {}\n unsafeˇ");
11363}
11364
11365#[gpui::test]
11366async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
11367 init_test(cx, |_| {});
11368
11369 let mut cx = EditorLspTestContext::new_rust(
11370 lsp::ServerCapabilities {
11371 completion_provider: Some(lsp::CompletionOptions {
11372 trigger_characters: Some(vec![".".to_string()]),
11373 resolve_provider: Some(true),
11374 ..Default::default()
11375 }),
11376 ..Default::default()
11377 },
11378 cx,
11379 )
11380 .await;
11381
11382 cx.set_state("fn main() { let a = 2ˇ; }");
11383 cx.simulate_keystroke(".");
11384 let completion_item = lsp::CompletionItem {
11385 label: "Some".into(),
11386 kind: Some(lsp::CompletionItemKind::SNIPPET),
11387 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
11388 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
11389 kind: lsp::MarkupKind::Markdown,
11390 value: "```rust\nSome(2)\n```".to_string(),
11391 })),
11392 deprecated: Some(false),
11393 sort_text: Some("Some".to_string()),
11394 filter_text: Some("Some".to_string()),
11395 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
11396 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11397 range: lsp::Range {
11398 start: lsp::Position {
11399 line: 0,
11400 character: 22,
11401 },
11402 end: lsp::Position {
11403 line: 0,
11404 character: 22,
11405 },
11406 },
11407 new_text: "Some(2)".to_string(),
11408 })),
11409 additional_text_edits: Some(vec![lsp::TextEdit {
11410 range: lsp::Range {
11411 start: lsp::Position {
11412 line: 0,
11413 character: 20,
11414 },
11415 end: lsp::Position {
11416 line: 0,
11417 character: 22,
11418 },
11419 },
11420 new_text: "".to_string(),
11421 }]),
11422 ..Default::default()
11423 };
11424
11425 let closure_completion_item = completion_item.clone();
11426 let counter = Arc::new(AtomicUsize::new(0));
11427 let counter_clone = counter.clone();
11428 let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
11429 let task_completion_item = closure_completion_item.clone();
11430 counter_clone.fetch_add(1, atomic::Ordering::Release);
11431 async move {
11432 Ok(Some(lsp::CompletionResponse::Array(vec![
11433 task_completion_item,
11434 ])))
11435 }
11436 });
11437
11438 cx.condition(|editor, _| editor.context_menu_visible())
11439 .await;
11440 cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
11441 assert!(request.next().await.is_some());
11442 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11443
11444 cx.simulate_keystrokes("S o m");
11445 cx.condition(|editor, _| editor.context_menu_visible())
11446 .await;
11447 cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
11448 assert!(request.next().await.is_some());
11449 assert!(request.next().await.is_some());
11450 assert!(request.next().await.is_some());
11451 request.close();
11452 assert!(request.next().await.is_none());
11453 assert_eq!(
11454 counter.load(atomic::Ordering::Acquire),
11455 4,
11456 "With the completions menu open, only one LSP request should happen per input"
11457 );
11458}
11459
11460#[gpui::test]
11461async fn test_toggle_comment(cx: &mut TestAppContext) {
11462 init_test(cx, |_| {});
11463 let mut cx = EditorTestContext::new(cx).await;
11464 let language = Arc::new(Language::new(
11465 LanguageConfig {
11466 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
11467 ..Default::default()
11468 },
11469 Some(tree_sitter_rust::LANGUAGE.into()),
11470 ));
11471 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
11472
11473 // If multiple selections intersect a line, the line is only toggled once.
11474 cx.set_state(indoc! {"
11475 fn a() {
11476 «//b();
11477 ˇ»// «c();
11478 //ˇ» d();
11479 }
11480 "});
11481
11482 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
11483
11484 cx.assert_editor_state(indoc! {"
11485 fn a() {
11486 «b();
11487 c();
11488 ˇ» d();
11489 }
11490 "});
11491
11492 // The comment prefix is inserted at the same column for every line in a
11493 // selection.
11494 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
11495
11496 cx.assert_editor_state(indoc! {"
11497 fn a() {
11498 // «b();
11499 // c();
11500 ˇ»// d();
11501 }
11502 "});
11503
11504 // If a selection ends at the beginning of a line, that line is not toggled.
11505 cx.set_selections_state(indoc! {"
11506 fn a() {
11507 // b();
11508 «// c();
11509 ˇ» // d();
11510 }
11511 "});
11512
11513 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
11514
11515 cx.assert_editor_state(indoc! {"
11516 fn a() {
11517 // b();
11518 «c();
11519 ˇ» // d();
11520 }
11521 "});
11522
11523 // If a selection span a single line and is empty, the line is toggled.
11524 cx.set_state(indoc! {"
11525 fn a() {
11526 a();
11527 b();
11528 ˇ
11529 }
11530 "});
11531
11532 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
11533
11534 cx.assert_editor_state(indoc! {"
11535 fn a() {
11536 a();
11537 b();
11538 //•ˇ
11539 }
11540 "});
11541
11542 // If a selection span multiple lines, empty lines are not toggled.
11543 cx.set_state(indoc! {"
11544 fn a() {
11545 «a();
11546
11547 c();ˇ»
11548 }
11549 "});
11550
11551 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
11552
11553 cx.assert_editor_state(indoc! {"
11554 fn a() {
11555 // «a();
11556
11557 // c();ˇ»
11558 }
11559 "});
11560
11561 // If a selection includes multiple comment prefixes, all lines are uncommented.
11562 cx.set_state(indoc! {"
11563 fn a() {
11564 «// a();
11565 /// b();
11566 //! c();ˇ»
11567 }
11568 "});
11569
11570 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
11571
11572 cx.assert_editor_state(indoc! {"
11573 fn a() {
11574 «a();
11575 b();
11576 c();ˇ»
11577 }
11578 "});
11579}
11580
11581#[gpui::test]
11582async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
11583 init_test(cx, |_| {});
11584 let mut cx = EditorTestContext::new(cx).await;
11585 let language = Arc::new(Language::new(
11586 LanguageConfig {
11587 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
11588 ..Default::default()
11589 },
11590 Some(tree_sitter_rust::LANGUAGE.into()),
11591 ));
11592 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
11593
11594 let toggle_comments = &ToggleComments {
11595 advance_downwards: false,
11596 ignore_indent: true,
11597 };
11598
11599 // If multiple selections intersect a line, the line is only toggled once.
11600 cx.set_state(indoc! {"
11601 fn a() {
11602 // «b();
11603 // c();
11604 // ˇ» d();
11605 }
11606 "});
11607
11608 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
11609
11610 cx.assert_editor_state(indoc! {"
11611 fn a() {
11612 «b();
11613 c();
11614 ˇ» d();
11615 }
11616 "});
11617
11618 // The comment prefix is inserted at the beginning of each line
11619 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
11620
11621 cx.assert_editor_state(indoc! {"
11622 fn a() {
11623 // «b();
11624 // c();
11625 // ˇ» d();
11626 }
11627 "});
11628
11629 // If a selection ends at the beginning of a line, that line is not toggled.
11630 cx.set_selections_state(indoc! {"
11631 fn a() {
11632 // b();
11633 // «c();
11634 ˇ»// d();
11635 }
11636 "});
11637
11638 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
11639
11640 cx.assert_editor_state(indoc! {"
11641 fn a() {
11642 // b();
11643 «c();
11644 ˇ»// d();
11645 }
11646 "});
11647
11648 // If a selection span a single line and is empty, the line is toggled.
11649 cx.set_state(indoc! {"
11650 fn a() {
11651 a();
11652 b();
11653 ˇ
11654 }
11655 "});
11656
11657 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
11658
11659 cx.assert_editor_state(indoc! {"
11660 fn a() {
11661 a();
11662 b();
11663 //ˇ
11664 }
11665 "});
11666
11667 // If a selection span multiple lines, empty lines are not toggled.
11668 cx.set_state(indoc! {"
11669 fn a() {
11670 «a();
11671
11672 c();ˇ»
11673 }
11674 "});
11675
11676 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
11677
11678 cx.assert_editor_state(indoc! {"
11679 fn a() {
11680 // «a();
11681
11682 // c();ˇ»
11683 }
11684 "});
11685
11686 // If a selection includes multiple comment prefixes, all lines are uncommented.
11687 cx.set_state(indoc! {"
11688 fn a() {
11689 // «a();
11690 /// b();
11691 //! c();ˇ»
11692 }
11693 "});
11694
11695 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
11696
11697 cx.assert_editor_state(indoc! {"
11698 fn a() {
11699 «a();
11700 b();
11701 c();ˇ»
11702 }
11703 "});
11704}
11705
11706#[gpui::test]
11707async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
11708 init_test(cx, |_| {});
11709
11710 let language = Arc::new(Language::new(
11711 LanguageConfig {
11712 line_comments: vec!["// ".into()],
11713 ..Default::default()
11714 },
11715 Some(tree_sitter_rust::LANGUAGE.into()),
11716 ));
11717
11718 let mut cx = EditorTestContext::new(cx).await;
11719
11720 cx.language_registry().add(language.clone());
11721 cx.update_buffer(|buffer, cx| {
11722 buffer.set_language(Some(language), cx);
11723 });
11724
11725 let toggle_comments = &ToggleComments {
11726 advance_downwards: true,
11727 ignore_indent: false,
11728 };
11729
11730 // Single cursor on one line -> advance
11731 // Cursor moves horizontally 3 characters as well on non-blank line
11732 cx.set_state(indoc!(
11733 "fn a() {
11734 ˇdog();
11735 cat();
11736 }"
11737 ));
11738 cx.update_editor(|editor, window, cx| {
11739 editor.toggle_comments(toggle_comments, window, cx);
11740 });
11741 cx.assert_editor_state(indoc!(
11742 "fn a() {
11743 // dog();
11744 catˇ();
11745 }"
11746 ));
11747
11748 // Single selection on one line -> don't advance
11749 cx.set_state(indoc!(
11750 "fn a() {
11751 «dog()ˇ»;
11752 cat();
11753 }"
11754 ));
11755 cx.update_editor(|editor, window, cx| {
11756 editor.toggle_comments(toggle_comments, window, cx);
11757 });
11758 cx.assert_editor_state(indoc!(
11759 "fn a() {
11760 // «dog()ˇ»;
11761 cat();
11762 }"
11763 ));
11764
11765 // Multiple cursors on one line -> advance
11766 cx.set_state(indoc!(
11767 "fn a() {
11768 ˇdˇog();
11769 cat();
11770 }"
11771 ));
11772 cx.update_editor(|editor, window, cx| {
11773 editor.toggle_comments(toggle_comments, window, cx);
11774 });
11775 cx.assert_editor_state(indoc!(
11776 "fn a() {
11777 // dog();
11778 catˇ(ˇ);
11779 }"
11780 ));
11781
11782 // Multiple cursors on one line, with selection -> don't advance
11783 cx.set_state(indoc!(
11784 "fn a() {
11785 ˇdˇog«()ˇ»;
11786 cat();
11787 }"
11788 ));
11789 cx.update_editor(|editor, window, cx| {
11790 editor.toggle_comments(toggle_comments, window, cx);
11791 });
11792 cx.assert_editor_state(indoc!(
11793 "fn a() {
11794 // ˇdˇog«()ˇ»;
11795 cat();
11796 }"
11797 ));
11798
11799 // Single cursor on one line -> advance
11800 // Cursor moves to column 0 on blank line
11801 cx.set_state(indoc!(
11802 "fn a() {
11803 ˇdog();
11804
11805 cat();
11806 }"
11807 ));
11808 cx.update_editor(|editor, window, cx| {
11809 editor.toggle_comments(toggle_comments, window, cx);
11810 });
11811 cx.assert_editor_state(indoc!(
11812 "fn a() {
11813 // dog();
11814 ˇ
11815 cat();
11816 }"
11817 ));
11818
11819 // Single cursor on one line -> advance
11820 // Cursor starts and ends at column 0
11821 cx.set_state(indoc!(
11822 "fn a() {
11823 ˇ dog();
11824 cat();
11825 }"
11826 ));
11827 cx.update_editor(|editor, window, cx| {
11828 editor.toggle_comments(toggle_comments, window, cx);
11829 });
11830 cx.assert_editor_state(indoc!(
11831 "fn a() {
11832 // dog();
11833 ˇ cat();
11834 }"
11835 ));
11836}
11837
11838#[gpui::test]
11839async fn test_toggle_block_comment(cx: &mut TestAppContext) {
11840 init_test(cx, |_| {});
11841
11842 let mut cx = EditorTestContext::new(cx).await;
11843
11844 let html_language = Arc::new(
11845 Language::new(
11846 LanguageConfig {
11847 name: "HTML".into(),
11848 block_comment: Some(("<!-- ".into(), " -->".into())),
11849 ..Default::default()
11850 },
11851 Some(tree_sitter_html::LANGUAGE.into()),
11852 )
11853 .with_injection_query(
11854 r#"
11855 (script_element
11856 (raw_text) @injection.content
11857 (#set! injection.language "javascript"))
11858 "#,
11859 )
11860 .unwrap(),
11861 );
11862
11863 let javascript_language = Arc::new(Language::new(
11864 LanguageConfig {
11865 name: "JavaScript".into(),
11866 line_comments: vec!["// ".into()],
11867 ..Default::default()
11868 },
11869 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
11870 ));
11871
11872 cx.language_registry().add(html_language.clone());
11873 cx.language_registry().add(javascript_language.clone());
11874 cx.update_buffer(|buffer, cx| {
11875 buffer.set_language(Some(html_language), cx);
11876 });
11877
11878 // Toggle comments for empty selections
11879 cx.set_state(
11880 &r#"
11881 <p>A</p>ˇ
11882 <p>B</p>ˇ
11883 <p>C</p>ˇ
11884 "#
11885 .unindent(),
11886 );
11887 cx.update_editor(|editor, window, cx| {
11888 editor.toggle_comments(&ToggleComments::default(), window, cx)
11889 });
11890 cx.assert_editor_state(
11891 &r#"
11892 <!-- <p>A</p>ˇ -->
11893 <!-- <p>B</p>ˇ -->
11894 <!-- <p>C</p>ˇ -->
11895 "#
11896 .unindent(),
11897 );
11898 cx.update_editor(|editor, window, cx| {
11899 editor.toggle_comments(&ToggleComments::default(), window, cx)
11900 });
11901 cx.assert_editor_state(
11902 &r#"
11903 <p>A</p>ˇ
11904 <p>B</p>ˇ
11905 <p>C</p>ˇ
11906 "#
11907 .unindent(),
11908 );
11909
11910 // Toggle comments for mixture of empty and non-empty selections, where
11911 // multiple selections occupy a given line.
11912 cx.set_state(
11913 &r#"
11914 <p>A«</p>
11915 <p>ˇ»B</p>ˇ
11916 <p>C«</p>
11917 <p>ˇ»D</p>ˇ
11918 "#
11919 .unindent(),
11920 );
11921
11922 cx.update_editor(|editor, window, cx| {
11923 editor.toggle_comments(&ToggleComments::default(), window, cx)
11924 });
11925 cx.assert_editor_state(
11926 &r#"
11927 <!-- <p>A«</p>
11928 <p>ˇ»B</p>ˇ -->
11929 <!-- <p>C«</p>
11930 <p>ˇ»D</p>ˇ -->
11931 "#
11932 .unindent(),
11933 );
11934 cx.update_editor(|editor, window, cx| {
11935 editor.toggle_comments(&ToggleComments::default(), window, cx)
11936 });
11937 cx.assert_editor_state(
11938 &r#"
11939 <p>A«</p>
11940 <p>ˇ»B</p>ˇ
11941 <p>C«</p>
11942 <p>ˇ»D</p>ˇ
11943 "#
11944 .unindent(),
11945 );
11946
11947 // Toggle comments when different languages are active for different
11948 // selections.
11949 cx.set_state(
11950 &r#"
11951 ˇ<script>
11952 ˇvar x = new Y();
11953 ˇ</script>
11954 "#
11955 .unindent(),
11956 );
11957 cx.executor().run_until_parked();
11958 cx.update_editor(|editor, window, cx| {
11959 editor.toggle_comments(&ToggleComments::default(), window, cx)
11960 });
11961 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
11962 // Uncommenting and commenting from this position brings in even more wrong artifacts.
11963 cx.assert_editor_state(
11964 &r#"
11965 <!-- ˇ<script> -->
11966 // ˇvar x = new Y();
11967 <!-- ˇ</script> -->
11968 "#
11969 .unindent(),
11970 );
11971}
11972
11973#[gpui::test]
11974fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
11975 init_test(cx, |_| {});
11976
11977 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
11978 let multibuffer = cx.new(|cx| {
11979 let mut multibuffer = MultiBuffer::new(ReadWrite);
11980 multibuffer.push_excerpts(
11981 buffer.clone(),
11982 [
11983 ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
11984 ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
11985 ],
11986 cx,
11987 );
11988 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
11989 multibuffer
11990 });
11991
11992 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
11993 editor.update_in(cx, |editor, window, cx| {
11994 assert_eq!(editor.text(cx), "aaaa\nbbbb");
11995 editor.change_selections(None, window, cx, |s| {
11996 s.select_ranges([
11997 Point::new(0, 0)..Point::new(0, 0),
11998 Point::new(1, 0)..Point::new(1, 0),
11999 ])
12000 });
12001
12002 editor.handle_input("X", window, cx);
12003 assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
12004 assert_eq!(
12005 editor.selections.ranges(cx),
12006 [
12007 Point::new(0, 1)..Point::new(0, 1),
12008 Point::new(1, 1)..Point::new(1, 1),
12009 ]
12010 );
12011
12012 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
12013 editor.change_selections(None, window, cx, |s| {
12014 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
12015 });
12016 editor.backspace(&Default::default(), window, cx);
12017 assert_eq!(editor.text(cx), "Xa\nbbb");
12018 assert_eq!(
12019 editor.selections.ranges(cx),
12020 [Point::new(1, 0)..Point::new(1, 0)]
12021 );
12022
12023 editor.change_selections(None, window, cx, |s| {
12024 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
12025 });
12026 editor.backspace(&Default::default(), window, cx);
12027 assert_eq!(editor.text(cx), "X\nbb");
12028 assert_eq!(
12029 editor.selections.ranges(cx),
12030 [Point::new(0, 1)..Point::new(0, 1)]
12031 );
12032 });
12033}
12034
12035#[gpui::test]
12036fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
12037 init_test(cx, |_| {});
12038
12039 let markers = vec![('[', ']').into(), ('(', ')').into()];
12040 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
12041 indoc! {"
12042 [aaaa
12043 (bbbb]
12044 cccc)",
12045 },
12046 markers.clone(),
12047 );
12048 let excerpt_ranges = markers.into_iter().map(|marker| {
12049 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
12050 ExcerptRange::new(context.clone())
12051 });
12052 let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
12053 let multibuffer = cx.new(|cx| {
12054 let mut multibuffer = MultiBuffer::new(ReadWrite);
12055 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
12056 multibuffer
12057 });
12058
12059 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
12060 editor.update_in(cx, |editor, window, cx| {
12061 let (expected_text, selection_ranges) = marked_text_ranges(
12062 indoc! {"
12063 aaaa
12064 bˇbbb
12065 bˇbbˇb
12066 cccc"
12067 },
12068 true,
12069 );
12070 assert_eq!(editor.text(cx), expected_text);
12071 editor.change_selections(None, window, cx, |s| s.select_ranges(selection_ranges));
12072
12073 editor.handle_input("X", window, cx);
12074
12075 let (expected_text, expected_selections) = marked_text_ranges(
12076 indoc! {"
12077 aaaa
12078 bXˇbbXb
12079 bXˇbbXˇb
12080 cccc"
12081 },
12082 false,
12083 );
12084 assert_eq!(editor.text(cx), expected_text);
12085 assert_eq!(editor.selections.ranges(cx), expected_selections);
12086
12087 editor.newline(&Newline, window, cx);
12088 let (expected_text, expected_selections) = marked_text_ranges(
12089 indoc! {"
12090 aaaa
12091 bX
12092 ˇbbX
12093 b
12094 bX
12095 ˇbbX
12096 ˇb
12097 cccc"
12098 },
12099 false,
12100 );
12101 assert_eq!(editor.text(cx), expected_text);
12102 assert_eq!(editor.selections.ranges(cx), expected_selections);
12103 });
12104}
12105
12106#[gpui::test]
12107fn test_refresh_selections(cx: &mut TestAppContext) {
12108 init_test(cx, |_| {});
12109
12110 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
12111 let mut excerpt1_id = None;
12112 let multibuffer = cx.new(|cx| {
12113 let mut multibuffer = MultiBuffer::new(ReadWrite);
12114 excerpt1_id = multibuffer
12115 .push_excerpts(
12116 buffer.clone(),
12117 [
12118 ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
12119 ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
12120 ],
12121 cx,
12122 )
12123 .into_iter()
12124 .next();
12125 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
12126 multibuffer
12127 });
12128
12129 let editor = cx.add_window(|window, cx| {
12130 let mut editor = build_editor(multibuffer.clone(), window, cx);
12131 let snapshot = editor.snapshot(window, cx);
12132 editor.change_selections(None, window, cx, |s| {
12133 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
12134 });
12135 editor.begin_selection(
12136 Point::new(2, 1).to_display_point(&snapshot),
12137 true,
12138 1,
12139 window,
12140 cx,
12141 );
12142 assert_eq!(
12143 editor.selections.ranges(cx),
12144 [
12145 Point::new(1, 3)..Point::new(1, 3),
12146 Point::new(2, 1)..Point::new(2, 1),
12147 ]
12148 );
12149 editor
12150 });
12151
12152 // Refreshing selections is a no-op when excerpts haven't changed.
12153 _ = editor.update(cx, |editor, window, cx| {
12154 editor.change_selections(None, window, cx, |s| s.refresh());
12155 assert_eq!(
12156 editor.selections.ranges(cx),
12157 [
12158 Point::new(1, 3)..Point::new(1, 3),
12159 Point::new(2, 1)..Point::new(2, 1),
12160 ]
12161 );
12162 });
12163
12164 multibuffer.update(cx, |multibuffer, cx| {
12165 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
12166 });
12167 _ = editor.update(cx, |editor, window, cx| {
12168 // Removing an excerpt causes the first selection to become degenerate.
12169 assert_eq!(
12170 editor.selections.ranges(cx),
12171 [
12172 Point::new(0, 0)..Point::new(0, 0),
12173 Point::new(0, 1)..Point::new(0, 1)
12174 ]
12175 );
12176
12177 // Refreshing selections will relocate the first selection to the original buffer
12178 // location.
12179 editor.change_selections(None, window, cx, |s| s.refresh());
12180 assert_eq!(
12181 editor.selections.ranges(cx),
12182 [
12183 Point::new(0, 1)..Point::new(0, 1),
12184 Point::new(0, 3)..Point::new(0, 3)
12185 ]
12186 );
12187 assert!(editor.selections.pending_anchor().is_some());
12188 });
12189}
12190
12191#[gpui::test]
12192fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
12193 init_test(cx, |_| {});
12194
12195 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
12196 let mut excerpt1_id = None;
12197 let multibuffer = cx.new(|cx| {
12198 let mut multibuffer = MultiBuffer::new(ReadWrite);
12199 excerpt1_id = multibuffer
12200 .push_excerpts(
12201 buffer.clone(),
12202 [
12203 ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
12204 ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
12205 ],
12206 cx,
12207 )
12208 .into_iter()
12209 .next();
12210 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
12211 multibuffer
12212 });
12213
12214 let editor = cx.add_window(|window, cx| {
12215 let mut editor = build_editor(multibuffer.clone(), window, cx);
12216 let snapshot = editor.snapshot(window, cx);
12217 editor.begin_selection(
12218 Point::new(1, 3).to_display_point(&snapshot),
12219 false,
12220 1,
12221 window,
12222 cx,
12223 );
12224 assert_eq!(
12225 editor.selections.ranges(cx),
12226 [Point::new(1, 3)..Point::new(1, 3)]
12227 );
12228 editor
12229 });
12230
12231 multibuffer.update(cx, |multibuffer, cx| {
12232 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
12233 });
12234 _ = editor.update(cx, |editor, window, cx| {
12235 assert_eq!(
12236 editor.selections.ranges(cx),
12237 [Point::new(0, 0)..Point::new(0, 0)]
12238 );
12239
12240 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
12241 editor.change_selections(None, window, cx, |s| s.refresh());
12242 assert_eq!(
12243 editor.selections.ranges(cx),
12244 [Point::new(0, 3)..Point::new(0, 3)]
12245 );
12246 assert!(editor.selections.pending_anchor().is_some());
12247 });
12248}
12249
12250#[gpui::test]
12251async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
12252 init_test(cx, |_| {});
12253
12254 let language = Arc::new(
12255 Language::new(
12256 LanguageConfig {
12257 brackets: BracketPairConfig {
12258 pairs: vec![
12259 BracketPair {
12260 start: "{".to_string(),
12261 end: "}".to_string(),
12262 close: true,
12263 surround: true,
12264 newline: true,
12265 },
12266 BracketPair {
12267 start: "/* ".to_string(),
12268 end: " */".to_string(),
12269 close: true,
12270 surround: true,
12271 newline: true,
12272 },
12273 ],
12274 ..Default::default()
12275 },
12276 ..Default::default()
12277 },
12278 Some(tree_sitter_rust::LANGUAGE.into()),
12279 )
12280 .with_indents_query("")
12281 .unwrap(),
12282 );
12283
12284 let text = concat!(
12285 "{ }\n", //
12286 " x\n", //
12287 " /* */\n", //
12288 "x\n", //
12289 "{{} }\n", //
12290 );
12291
12292 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
12293 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12294 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
12295 editor
12296 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
12297 .await;
12298
12299 editor.update_in(cx, |editor, window, cx| {
12300 editor.change_selections(None, window, cx, |s| {
12301 s.select_display_ranges([
12302 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
12303 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
12304 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
12305 ])
12306 });
12307 editor.newline(&Newline, window, cx);
12308
12309 assert_eq!(
12310 editor.buffer().read(cx).read(cx).text(),
12311 concat!(
12312 "{ \n", // Suppress rustfmt
12313 "\n", //
12314 "}\n", //
12315 " x\n", //
12316 " /* \n", //
12317 " \n", //
12318 " */\n", //
12319 "x\n", //
12320 "{{} \n", //
12321 "}\n", //
12322 )
12323 );
12324 });
12325}
12326
12327#[gpui::test]
12328fn test_highlighted_ranges(cx: &mut TestAppContext) {
12329 init_test(cx, |_| {});
12330
12331 let editor = cx.add_window(|window, cx| {
12332 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
12333 build_editor(buffer.clone(), window, cx)
12334 });
12335
12336 _ = editor.update(cx, |editor, window, cx| {
12337 struct Type1;
12338 struct Type2;
12339
12340 let buffer = editor.buffer.read(cx).snapshot(cx);
12341
12342 let anchor_range =
12343 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
12344
12345 editor.highlight_background::<Type1>(
12346 &[
12347 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
12348 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
12349 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
12350 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
12351 ],
12352 |_| Hsla::red(),
12353 cx,
12354 );
12355 editor.highlight_background::<Type2>(
12356 &[
12357 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
12358 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
12359 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
12360 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
12361 ],
12362 |_| Hsla::green(),
12363 cx,
12364 );
12365
12366 let snapshot = editor.snapshot(window, cx);
12367 let mut highlighted_ranges = editor.background_highlights_in_range(
12368 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
12369 &snapshot,
12370 cx.theme().colors(),
12371 );
12372 // Enforce a consistent ordering based on color without relying on the ordering of the
12373 // highlight's `TypeId` which is non-executor.
12374 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
12375 assert_eq!(
12376 highlighted_ranges,
12377 &[
12378 (
12379 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
12380 Hsla::red(),
12381 ),
12382 (
12383 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
12384 Hsla::red(),
12385 ),
12386 (
12387 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
12388 Hsla::green(),
12389 ),
12390 (
12391 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
12392 Hsla::green(),
12393 ),
12394 ]
12395 );
12396 assert_eq!(
12397 editor.background_highlights_in_range(
12398 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
12399 &snapshot,
12400 cx.theme().colors(),
12401 ),
12402 &[(
12403 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
12404 Hsla::red(),
12405 )]
12406 );
12407 });
12408}
12409
12410#[gpui::test]
12411async fn test_following(cx: &mut TestAppContext) {
12412 init_test(cx, |_| {});
12413
12414 let fs = FakeFs::new(cx.executor());
12415 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
12416
12417 let buffer = project.update(cx, |project, cx| {
12418 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
12419 cx.new(|cx| MultiBuffer::singleton(buffer, cx))
12420 });
12421 let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
12422 let follower = cx.update(|cx| {
12423 cx.open_window(
12424 WindowOptions {
12425 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
12426 gpui::Point::new(px(0.), px(0.)),
12427 gpui::Point::new(px(10.), px(80.)),
12428 ))),
12429 ..Default::default()
12430 },
12431 |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
12432 )
12433 .unwrap()
12434 });
12435
12436 let is_still_following = Rc::new(RefCell::new(true));
12437 let follower_edit_event_count = Rc::new(RefCell::new(0));
12438 let pending_update = Rc::new(RefCell::new(None));
12439 let leader_entity = leader.root(cx).unwrap();
12440 let follower_entity = follower.root(cx).unwrap();
12441 _ = follower.update(cx, {
12442 let update = pending_update.clone();
12443 let is_still_following = is_still_following.clone();
12444 let follower_edit_event_count = follower_edit_event_count.clone();
12445 |_, window, cx| {
12446 cx.subscribe_in(
12447 &leader_entity,
12448 window,
12449 move |_, leader, event, window, cx| {
12450 leader.read(cx).add_event_to_update_proto(
12451 event,
12452 &mut update.borrow_mut(),
12453 window,
12454 cx,
12455 );
12456 },
12457 )
12458 .detach();
12459
12460 cx.subscribe_in(
12461 &follower_entity,
12462 window,
12463 move |_, _, event: &EditorEvent, _window, _cx| {
12464 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
12465 *is_still_following.borrow_mut() = false;
12466 }
12467
12468 if let EditorEvent::BufferEdited = event {
12469 *follower_edit_event_count.borrow_mut() += 1;
12470 }
12471 },
12472 )
12473 .detach();
12474 }
12475 });
12476
12477 // Update the selections only
12478 _ = leader.update(cx, |leader, window, cx| {
12479 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
12480 });
12481 follower
12482 .update(cx, |follower, window, cx| {
12483 follower.apply_update_proto(
12484 &project,
12485 pending_update.borrow_mut().take().unwrap(),
12486 window,
12487 cx,
12488 )
12489 })
12490 .unwrap()
12491 .await
12492 .unwrap();
12493 _ = follower.update(cx, |follower, _, cx| {
12494 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
12495 });
12496 assert!(*is_still_following.borrow());
12497 assert_eq!(*follower_edit_event_count.borrow(), 0);
12498
12499 // Update the scroll position only
12500 _ = leader.update(cx, |leader, window, cx| {
12501 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
12502 });
12503 follower
12504 .update(cx, |follower, window, cx| {
12505 follower.apply_update_proto(
12506 &project,
12507 pending_update.borrow_mut().take().unwrap(),
12508 window,
12509 cx,
12510 )
12511 })
12512 .unwrap()
12513 .await
12514 .unwrap();
12515 assert_eq!(
12516 follower
12517 .update(cx, |follower, _, cx| follower.scroll_position(cx))
12518 .unwrap(),
12519 gpui::Point::new(1.5, 3.5)
12520 );
12521 assert!(*is_still_following.borrow());
12522 assert_eq!(*follower_edit_event_count.borrow(), 0);
12523
12524 // Update the selections and scroll position. The follower's scroll position is updated
12525 // via autoscroll, not via the leader's exact scroll position.
12526 _ = leader.update(cx, |leader, window, cx| {
12527 leader.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
12528 leader.request_autoscroll(Autoscroll::newest(), cx);
12529 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
12530 });
12531 follower
12532 .update(cx, |follower, window, cx| {
12533 follower.apply_update_proto(
12534 &project,
12535 pending_update.borrow_mut().take().unwrap(),
12536 window,
12537 cx,
12538 )
12539 })
12540 .unwrap()
12541 .await
12542 .unwrap();
12543 _ = follower.update(cx, |follower, _, cx| {
12544 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
12545 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
12546 });
12547 assert!(*is_still_following.borrow());
12548
12549 // Creating a pending selection that precedes another selection
12550 _ = leader.update(cx, |leader, window, cx| {
12551 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
12552 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
12553 });
12554 follower
12555 .update(cx, |follower, window, cx| {
12556 follower.apply_update_proto(
12557 &project,
12558 pending_update.borrow_mut().take().unwrap(),
12559 window,
12560 cx,
12561 )
12562 })
12563 .unwrap()
12564 .await
12565 .unwrap();
12566 _ = follower.update(cx, |follower, _, cx| {
12567 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
12568 });
12569 assert!(*is_still_following.borrow());
12570
12571 // Extend the pending selection so that it surrounds another selection
12572 _ = leader.update(cx, |leader, window, cx| {
12573 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
12574 });
12575 follower
12576 .update(cx, |follower, window, cx| {
12577 follower.apply_update_proto(
12578 &project,
12579 pending_update.borrow_mut().take().unwrap(),
12580 window,
12581 cx,
12582 )
12583 })
12584 .unwrap()
12585 .await
12586 .unwrap();
12587 _ = follower.update(cx, |follower, _, cx| {
12588 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
12589 });
12590
12591 // Scrolling locally breaks the follow
12592 _ = follower.update(cx, |follower, window, cx| {
12593 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
12594 follower.set_scroll_anchor(
12595 ScrollAnchor {
12596 anchor: top_anchor,
12597 offset: gpui::Point::new(0.0, 0.5),
12598 },
12599 window,
12600 cx,
12601 );
12602 });
12603 assert!(!(*is_still_following.borrow()));
12604}
12605
12606#[gpui::test]
12607async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
12608 init_test(cx, |_| {});
12609
12610 let fs = FakeFs::new(cx.executor());
12611 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
12612 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12613 let pane = workspace
12614 .update(cx, |workspace, _, _| workspace.active_pane().clone())
12615 .unwrap();
12616
12617 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
12618
12619 let leader = pane.update_in(cx, |_, window, cx| {
12620 let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
12621 cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
12622 });
12623
12624 // Start following the editor when it has no excerpts.
12625 let mut state_message =
12626 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
12627 let workspace_entity = workspace.root(cx).unwrap();
12628 let follower_1 = cx
12629 .update_window(*workspace.deref(), |_, window, cx| {
12630 Editor::from_state_proto(
12631 workspace_entity,
12632 ViewId {
12633 creator: Default::default(),
12634 id: 0,
12635 },
12636 &mut state_message,
12637 window,
12638 cx,
12639 )
12640 })
12641 .unwrap()
12642 .unwrap()
12643 .await
12644 .unwrap();
12645
12646 let update_message = Rc::new(RefCell::new(None));
12647 follower_1.update_in(cx, {
12648 let update = update_message.clone();
12649 |_, window, cx| {
12650 cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
12651 leader.read(cx).add_event_to_update_proto(
12652 event,
12653 &mut update.borrow_mut(),
12654 window,
12655 cx,
12656 );
12657 })
12658 .detach();
12659 }
12660 });
12661
12662 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
12663 (
12664 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
12665 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
12666 )
12667 });
12668
12669 // Insert some excerpts.
12670 leader.update(cx, |leader, cx| {
12671 leader.buffer.update(cx, |multibuffer, cx| {
12672 let excerpt_ids = multibuffer.push_excerpts(
12673 buffer_1.clone(),
12674 [
12675 ExcerptRange::new(1..6),
12676 ExcerptRange::new(12..15),
12677 ExcerptRange::new(0..3),
12678 ],
12679 cx,
12680 );
12681 multibuffer.insert_excerpts_after(
12682 excerpt_ids[0],
12683 buffer_2.clone(),
12684 [ExcerptRange::new(8..12), ExcerptRange::new(0..6)],
12685 cx,
12686 );
12687 });
12688 });
12689
12690 // Apply the update of adding the excerpts.
12691 follower_1
12692 .update_in(cx, |follower, window, cx| {
12693 follower.apply_update_proto(
12694 &project,
12695 update_message.borrow().clone().unwrap(),
12696 window,
12697 cx,
12698 )
12699 })
12700 .await
12701 .unwrap();
12702 assert_eq!(
12703 follower_1.update(cx, |editor, cx| editor.text(cx)),
12704 leader.update(cx, |editor, cx| editor.text(cx))
12705 );
12706 update_message.borrow_mut().take();
12707
12708 // Start following separately after it already has excerpts.
12709 let mut state_message =
12710 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
12711 let workspace_entity = workspace.root(cx).unwrap();
12712 let follower_2 = cx
12713 .update_window(*workspace.deref(), |_, window, cx| {
12714 Editor::from_state_proto(
12715 workspace_entity,
12716 ViewId {
12717 creator: Default::default(),
12718 id: 0,
12719 },
12720 &mut state_message,
12721 window,
12722 cx,
12723 )
12724 })
12725 .unwrap()
12726 .unwrap()
12727 .await
12728 .unwrap();
12729 assert_eq!(
12730 follower_2.update(cx, |editor, cx| editor.text(cx)),
12731 leader.update(cx, |editor, cx| editor.text(cx))
12732 );
12733
12734 // Remove some excerpts.
12735 leader.update(cx, |leader, cx| {
12736 leader.buffer.update(cx, |multibuffer, cx| {
12737 let excerpt_ids = multibuffer.excerpt_ids();
12738 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
12739 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
12740 });
12741 });
12742
12743 // Apply the update of removing the excerpts.
12744 follower_1
12745 .update_in(cx, |follower, window, cx| {
12746 follower.apply_update_proto(
12747 &project,
12748 update_message.borrow().clone().unwrap(),
12749 window,
12750 cx,
12751 )
12752 })
12753 .await
12754 .unwrap();
12755 follower_2
12756 .update_in(cx, |follower, window, cx| {
12757 follower.apply_update_proto(
12758 &project,
12759 update_message.borrow().clone().unwrap(),
12760 window,
12761 cx,
12762 )
12763 })
12764 .await
12765 .unwrap();
12766 update_message.borrow_mut().take();
12767 assert_eq!(
12768 follower_1.update(cx, |editor, cx| editor.text(cx)),
12769 leader.update(cx, |editor, cx| editor.text(cx))
12770 );
12771}
12772
12773#[gpui::test]
12774async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
12775 init_test(cx, |_| {});
12776
12777 let mut cx = EditorTestContext::new(cx).await;
12778 let lsp_store =
12779 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
12780
12781 cx.set_state(indoc! {"
12782 ˇfn func(abc def: i32) -> u32 {
12783 }
12784 "});
12785
12786 cx.update(|_, cx| {
12787 lsp_store.update(cx, |lsp_store, cx| {
12788 lsp_store
12789 .update_diagnostics(
12790 LanguageServerId(0),
12791 lsp::PublishDiagnosticsParams {
12792 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
12793 version: None,
12794 diagnostics: vec![
12795 lsp::Diagnostic {
12796 range: lsp::Range::new(
12797 lsp::Position::new(0, 11),
12798 lsp::Position::new(0, 12),
12799 ),
12800 severity: Some(lsp::DiagnosticSeverity::ERROR),
12801 ..Default::default()
12802 },
12803 lsp::Diagnostic {
12804 range: lsp::Range::new(
12805 lsp::Position::new(0, 12),
12806 lsp::Position::new(0, 15),
12807 ),
12808 severity: Some(lsp::DiagnosticSeverity::ERROR),
12809 ..Default::default()
12810 },
12811 lsp::Diagnostic {
12812 range: lsp::Range::new(
12813 lsp::Position::new(0, 25),
12814 lsp::Position::new(0, 28),
12815 ),
12816 severity: Some(lsp::DiagnosticSeverity::ERROR),
12817 ..Default::default()
12818 },
12819 ],
12820 },
12821 &[],
12822 cx,
12823 )
12824 .unwrap()
12825 });
12826 });
12827
12828 executor.run_until_parked();
12829
12830 cx.update_editor(|editor, window, cx| {
12831 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
12832 });
12833
12834 cx.assert_editor_state(indoc! {"
12835 fn func(abc def: i32) -> ˇu32 {
12836 }
12837 "});
12838
12839 cx.update_editor(|editor, window, cx| {
12840 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
12841 });
12842
12843 cx.assert_editor_state(indoc! {"
12844 fn func(abc ˇdef: i32) -> u32 {
12845 }
12846 "});
12847
12848 cx.update_editor(|editor, window, cx| {
12849 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
12850 });
12851
12852 cx.assert_editor_state(indoc! {"
12853 fn func(abcˇ def: i32) -> u32 {
12854 }
12855 "});
12856
12857 cx.update_editor(|editor, window, cx| {
12858 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
12859 });
12860
12861 cx.assert_editor_state(indoc! {"
12862 fn func(abc def: i32) -> ˇu32 {
12863 }
12864 "});
12865}
12866
12867#[gpui::test]
12868async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
12869 init_test(cx, |_| {});
12870
12871 let mut cx = EditorTestContext::new(cx).await;
12872
12873 cx.set_state(indoc! {"
12874 fn func(abˇc def: i32) -> u32 {
12875 }
12876 "});
12877 let lsp_store =
12878 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
12879
12880 cx.update(|_, cx| {
12881 lsp_store.update(cx, |lsp_store, cx| {
12882 lsp_store.update_diagnostics(
12883 LanguageServerId(0),
12884 lsp::PublishDiagnosticsParams {
12885 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
12886 version: None,
12887 diagnostics: vec![lsp::Diagnostic {
12888 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
12889 severity: Some(lsp::DiagnosticSeverity::ERROR),
12890 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
12891 ..Default::default()
12892 }],
12893 },
12894 &[],
12895 cx,
12896 )
12897 })
12898 }).unwrap();
12899 cx.run_until_parked();
12900 cx.update_editor(|editor, window, cx| {
12901 hover_popover::hover(editor, &Default::default(), window, cx)
12902 });
12903 cx.run_until_parked();
12904 cx.update_editor(|editor, _, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
12905}
12906
12907#[gpui::test]
12908async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
12909 init_test(cx, |_| {});
12910
12911 let mut cx = EditorTestContext::new(cx).await;
12912
12913 let diff_base = r#"
12914 use some::mod;
12915
12916 const A: u32 = 42;
12917
12918 fn main() {
12919 println!("hello");
12920
12921 println!("world");
12922 }
12923 "#
12924 .unindent();
12925
12926 // Edits are modified, removed, modified, added
12927 cx.set_state(
12928 &r#"
12929 use some::modified;
12930
12931 ˇ
12932 fn main() {
12933 println!("hello there");
12934
12935 println!("around the");
12936 println!("world");
12937 }
12938 "#
12939 .unindent(),
12940 );
12941
12942 cx.set_head_text(&diff_base);
12943 executor.run_until_parked();
12944
12945 cx.update_editor(|editor, window, cx| {
12946 //Wrap around the bottom of the buffer
12947 for _ in 0..3 {
12948 editor.go_to_next_hunk(&GoToHunk, window, cx);
12949 }
12950 });
12951
12952 cx.assert_editor_state(
12953 &r#"
12954 ˇuse some::modified;
12955
12956
12957 fn main() {
12958 println!("hello there");
12959
12960 println!("around the");
12961 println!("world");
12962 }
12963 "#
12964 .unindent(),
12965 );
12966
12967 cx.update_editor(|editor, window, cx| {
12968 //Wrap around the top of the buffer
12969 for _ in 0..2 {
12970 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
12971 }
12972 });
12973
12974 cx.assert_editor_state(
12975 &r#"
12976 use some::modified;
12977
12978
12979 fn main() {
12980 ˇ println!("hello there");
12981
12982 println!("around the");
12983 println!("world");
12984 }
12985 "#
12986 .unindent(),
12987 );
12988
12989 cx.update_editor(|editor, window, cx| {
12990 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
12991 });
12992
12993 cx.assert_editor_state(
12994 &r#"
12995 use some::modified;
12996
12997 ˇ
12998 fn main() {
12999 println!("hello there");
13000
13001 println!("around the");
13002 println!("world");
13003 }
13004 "#
13005 .unindent(),
13006 );
13007
13008 cx.update_editor(|editor, window, cx| {
13009 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
13010 });
13011
13012 cx.assert_editor_state(
13013 &r#"
13014 ˇuse some::modified;
13015
13016
13017 fn main() {
13018 println!("hello there");
13019
13020 println!("around the");
13021 println!("world");
13022 }
13023 "#
13024 .unindent(),
13025 );
13026
13027 cx.update_editor(|editor, window, cx| {
13028 for _ in 0..2 {
13029 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
13030 }
13031 });
13032
13033 cx.assert_editor_state(
13034 &r#"
13035 use some::modified;
13036
13037
13038 fn main() {
13039 ˇ println!("hello there");
13040
13041 println!("around the");
13042 println!("world");
13043 }
13044 "#
13045 .unindent(),
13046 );
13047
13048 cx.update_editor(|editor, window, cx| {
13049 editor.fold(&Fold, window, cx);
13050 });
13051
13052 cx.update_editor(|editor, window, cx| {
13053 editor.go_to_next_hunk(&GoToHunk, 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
13072#[test]
13073fn test_split_words() {
13074 fn split(text: &str) -> Vec<&str> {
13075 split_words(text).collect()
13076 }
13077
13078 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
13079 assert_eq!(split("hello_world"), &["hello_", "world"]);
13080 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
13081 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
13082 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
13083 assert_eq!(split("helloworld"), &["helloworld"]);
13084
13085 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
13086}
13087
13088#[gpui::test]
13089async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
13090 init_test(cx, |_| {});
13091
13092 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
13093 let mut assert = |before, after| {
13094 let _state_context = cx.set_state(before);
13095 cx.run_until_parked();
13096 cx.update_editor(|editor, window, cx| {
13097 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
13098 });
13099 cx.run_until_parked();
13100 cx.assert_editor_state(after);
13101 };
13102
13103 // Outside bracket jumps to outside of matching bracket
13104 assert("console.logˇ(var);", "console.log(var)ˇ;");
13105 assert("console.log(var)ˇ;", "console.logˇ(var);");
13106
13107 // Inside bracket jumps to inside of matching bracket
13108 assert("console.log(ˇvar);", "console.log(varˇ);");
13109 assert("console.log(varˇ);", "console.log(ˇvar);");
13110
13111 // When outside a bracket and inside, favor jumping to the inside bracket
13112 assert(
13113 "console.log('foo', [1, 2, 3]ˇ);",
13114 "console.log(ˇ'foo', [1, 2, 3]);",
13115 );
13116 assert(
13117 "console.log(ˇ'foo', [1, 2, 3]);",
13118 "console.log('foo', [1, 2, 3]ˇ);",
13119 );
13120
13121 // Bias forward if two options are equally likely
13122 assert(
13123 "let result = curried_fun()ˇ();",
13124 "let result = curried_fun()()ˇ;",
13125 );
13126
13127 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
13128 assert(
13129 indoc! {"
13130 function test() {
13131 console.log('test')ˇ
13132 }"},
13133 indoc! {"
13134 function test() {
13135 console.logˇ('test')
13136 }"},
13137 );
13138}
13139
13140#[gpui::test]
13141async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
13142 init_test(cx, |_| {});
13143
13144 let fs = FakeFs::new(cx.executor());
13145 fs.insert_tree(
13146 path!("/a"),
13147 json!({
13148 "main.rs": "fn main() { let a = 5; }",
13149 "other.rs": "// Test file",
13150 }),
13151 )
13152 .await;
13153 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13154
13155 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13156 language_registry.add(Arc::new(Language::new(
13157 LanguageConfig {
13158 name: "Rust".into(),
13159 matcher: LanguageMatcher {
13160 path_suffixes: vec!["rs".to_string()],
13161 ..Default::default()
13162 },
13163 brackets: BracketPairConfig {
13164 pairs: vec![BracketPair {
13165 start: "{".to_string(),
13166 end: "}".to_string(),
13167 close: true,
13168 surround: true,
13169 newline: true,
13170 }],
13171 disabled_scopes_by_bracket_ix: Vec::new(),
13172 },
13173 ..Default::default()
13174 },
13175 Some(tree_sitter_rust::LANGUAGE.into()),
13176 )));
13177 let mut fake_servers = language_registry.register_fake_lsp(
13178 "Rust",
13179 FakeLspAdapter {
13180 capabilities: lsp::ServerCapabilities {
13181 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
13182 first_trigger_character: "{".to_string(),
13183 more_trigger_character: None,
13184 }),
13185 ..Default::default()
13186 },
13187 ..Default::default()
13188 },
13189 );
13190
13191 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13192
13193 let cx = &mut VisualTestContext::from_window(*workspace, cx);
13194
13195 let worktree_id = workspace
13196 .update(cx, |workspace, _, cx| {
13197 workspace.project().update(cx, |project, cx| {
13198 project.worktrees(cx).next().unwrap().read(cx).id()
13199 })
13200 })
13201 .unwrap();
13202
13203 let buffer = project
13204 .update(cx, |project, cx| {
13205 project.open_local_buffer(path!("/a/main.rs"), cx)
13206 })
13207 .await
13208 .unwrap();
13209 let editor_handle = workspace
13210 .update(cx, |workspace, window, cx| {
13211 workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
13212 })
13213 .unwrap()
13214 .await
13215 .unwrap()
13216 .downcast::<Editor>()
13217 .unwrap();
13218
13219 cx.executor().start_waiting();
13220 let fake_server = fake_servers.next().await.unwrap();
13221
13222 fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
13223 |params, _| async move {
13224 assert_eq!(
13225 params.text_document_position.text_document.uri,
13226 lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
13227 );
13228 assert_eq!(
13229 params.text_document_position.position,
13230 lsp::Position::new(0, 21),
13231 );
13232
13233 Ok(Some(vec![lsp::TextEdit {
13234 new_text: "]".to_string(),
13235 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
13236 }]))
13237 },
13238 );
13239
13240 editor_handle.update_in(cx, |editor, window, cx| {
13241 window.focus(&editor.focus_handle(cx));
13242 editor.change_selections(None, window, cx, |s| {
13243 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
13244 });
13245 editor.handle_input("{", window, cx);
13246 });
13247
13248 cx.executor().run_until_parked();
13249
13250 buffer.update(cx, |buffer, _| {
13251 assert_eq!(
13252 buffer.text(),
13253 "fn main() { let a = {5}; }",
13254 "No extra braces from on type formatting should appear in the buffer"
13255 )
13256 });
13257}
13258
13259#[gpui::test]
13260async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
13261 init_test(cx, |_| {});
13262
13263 let fs = FakeFs::new(cx.executor());
13264 fs.insert_tree(
13265 path!("/a"),
13266 json!({
13267 "main.rs": "fn main() { let a = 5; }",
13268 "other.rs": "// Test file",
13269 }),
13270 )
13271 .await;
13272
13273 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13274
13275 let server_restarts = Arc::new(AtomicUsize::new(0));
13276 let closure_restarts = Arc::clone(&server_restarts);
13277 let language_server_name = "test language server";
13278 let language_name: LanguageName = "Rust".into();
13279
13280 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13281 language_registry.add(Arc::new(Language::new(
13282 LanguageConfig {
13283 name: language_name.clone(),
13284 matcher: LanguageMatcher {
13285 path_suffixes: vec!["rs".to_string()],
13286 ..Default::default()
13287 },
13288 ..Default::default()
13289 },
13290 Some(tree_sitter_rust::LANGUAGE.into()),
13291 )));
13292 let mut fake_servers = language_registry.register_fake_lsp(
13293 "Rust",
13294 FakeLspAdapter {
13295 name: language_server_name,
13296 initialization_options: Some(json!({
13297 "testOptionValue": true
13298 })),
13299 initializer: Some(Box::new(move |fake_server| {
13300 let task_restarts = Arc::clone(&closure_restarts);
13301 fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
13302 task_restarts.fetch_add(1, atomic::Ordering::Release);
13303 futures::future::ready(Ok(()))
13304 });
13305 })),
13306 ..Default::default()
13307 },
13308 );
13309
13310 let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13311 let _buffer = project
13312 .update(cx, |project, cx| {
13313 project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
13314 })
13315 .await
13316 .unwrap();
13317 let _fake_server = fake_servers.next().await.unwrap();
13318 update_test_language_settings(cx, |language_settings| {
13319 language_settings.languages.insert(
13320 language_name.clone(),
13321 LanguageSettingsContent {
13322 tab_size: NonZeroU32::new(8),
13323 ..Default::default()
13324 },
13325 );
13326 });
13327 cx.executor().run_until_parked();
13328 assert_eq!(
13329 server_restarts.load(atomic::Ordering::Acquire),
13330 0,
13331 "Should not restart LSP server on an unrelated change"
13332 );
13333
13334 update_test_project_settings(cx, |project_settings| {
13335 project_settings.lsp.insert(
13336 "Some other server name".into(),
13337 LspSettings {
13338 binary: None,
13339 settings: None,
13340 initialization_options: Some(json!({
13341 "some other init value": false
13342 })),
13343 enable_lsp_tasks: false,
13344 },
13345 );
13346 });
13347 cx.executor().run_until_parked();
13348 assert_eq!(
13349 server_restarts.load(atomic::Ordering::Acquire),
13350 0,
13351 "Should not restart LSP server on an unrelated LSP settings change"
13352 );
13353
13354 update_test_project_settings(cx, |project_settings| {
13355 project_settings.lsp.insert(
13356 language_server_name.into(),
13357 LspSettings {
13358 binary: None,
13359 settings: None,
13360 initialization_options: Some(json!({
13361 "anotherInitValue": false
13362 })),
13363 enable_lsp_tasks: false,
13364 },
13365 );
13366 });
13367 cx.executor().run_until_parked();
13368 assert_eq!(
13369 server_restarts.load(atomic::Ordering::Acquire),
13370 1,
13371 "Should restart LSP server on a related LSP settings change"
13372 );
13373
13374 update_test_project_settings(cx, |project_settings| {
13375 project_settings.lsp.insert(
13376 language_server_name.into(),
13377 LspSettings {
13378 binary: None,
13379 settings: None,
13380 initialization_options: Some(json!({
13381 "anotherInitValue": false
13382 })),
13383 enable_lsp_tasks: false,
13384 },
13385 );
13386 });
13387 cx.executor().run_until_parked();
13388 assert_eq!(
13389 server_restarts.load(atomic::Ordering::Acquire),
13390 1,
13391 "Should not restart LSP server on a related LSP settings change that is the same"
13392 );
13393
13394 update_test_project_settings(cx, |project_settings| {
13395 project_settings.lsp.insert(
13396 language_server_name.into(),
13397 LspSettings {
13398 binary: None,
13399 settings: None,
13400 initialization_options: None,
13401 enable_lsp_tasks: false,
13402 },
13403 );
13404 });
13405 cx.executor().run_until_parked();
13406 assert_eq!(
13407 server_restarts.load(atomic::Ordering::Acquire),
13408 2,
13409 "Should restart LSP server on another related LSP settings change"
13410 );
13411}
13412
13413#[gpui::test]
13414async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
13415 init_test(cx, |_| {});
13416
13417 let mut cx = EditorLspTestContext::new_rust(
13418 lsp::ServerCapabilities {
13419 completion_provider: Some(lsp::CompletionOptions {
13420 trigger_characters: Some(vec![".".to_string()]),
13421 resolve_provider: Some(true),
13422 ..Default::default()
13423 }),
13424 ..Default::default()
13425 },
13426 cx,
13427 )
13428 .await;
13429
13430 cx.set_state("fn main() { let a = 2ˇ; }");
13431 cx.simulate_keystroke(".");
13432 let completion_item = lsp::CompletionItem {
13433 label: "some".into(),
13434 kind: Some(lsp::CompletionItemKind::SNIPPET),
13435 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
13436 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
13437 kind: lsp::MarkupKind::Markdown,
13438 value: "```rust\nSome(2)\n```".to_string(),
13439 })),
13440 deprecated: Some(false),
13441 sort_text: Some("fffffff2".to_string()),
13442 filter_text: Some("some".to_string()),
13443 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
13444 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13445 range: lsp::Range {
13446 start: lsp::Position {
13447 line: 0,
13448 character: 22,
13449 },
13450 end: lsp::Position {
13451 line: 0,
13452 character: 22,
13453 },
13454 },
13455 new_text: "Some(2)".to_string(),
13456 })),
13457 additional_text_edits: Some(vec![lsp::TextEdit {
13458 range: lsp::Range {
13459 start: lsp::Position {
13460 line: 0,
13461 character: 20,
13462 },
13463 end: lsp::Position {
13464 line: 0,
13465 character: 22,
13466 },
13467 },
13468 new_text: "".to_string(),
13469 }]),
13470 ..Default::default()
13471 };
13472
13473 let closure_completion_item = completion_item.clone();
13474 let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
13475 let task_completion_item = closure_completion_item.clone();
13476 async move {
13477 Ok(Some(lsp::CompletionResponse::Array(vec![
13478 task_completion_item,
13479 ])))
13480 }
13481 });
13482
13483 request.next().await;
13484
13485 cx.condition(|editor, _| editor.context_menu_visible())
13486 .await;
13487 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13488 editor
13489 .confirm_completion(&ConfirmCompletion::default(), window, cx)
13490 .unwrap()
13491 });
13492 cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
13493
13494 cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
13495 let task_completion_item = completion_item.clone();
13496 async move { Ok(task_completion_item) }
13497 })
13498 .next()
13499 .await
13500 .unwrap();
13501 apply_additional_edits.await.unwrap();
13502 cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
13503}
13504
13505#[gpui::test]
13506async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
13507 init_test(cx, |_| {});
13508
13509 let mut cx = EditorLspTestContext::new_rust(
13510 lsp::ServerCapabilities {
13511 completion_provider: Some(lsp::CompletionOptions {
13512 trigger_characters: Some(vec![".".to_string()]),
13513 resolve_provider: Some(true),
13514 ..Default::default()
13515 }),
13516 ..Default::default()
13517 },
13518 cx,
13519 )
13520 .await;
13521
13522 cx.set_state("fn main() { let a = 2ˇ; }");
13523 cx.simulate_keystroke(".");
13524
13525 let item1 = lsp::CompletionItem {
13526 label: "method id()".to_string(),
13527 filter_text: Some("id".to_string()),
13528 detail: None,
13529 documentation: None,
13530 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13531 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
13532 new_text: ".id".to_string(),
13533 })),
13534 ..lsp::CompletionItem::default()
13535 };
13536
13537 let item2 = lsp::CompletionItem {
13538 label: "other".to_string(),
13539 filter_text: Some("other".to_string()),
13540 detail: None,
13541 documentation: None,
13542 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13543 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
13544 new_text: ".other".to_string(),
13545 })),
13546 ..lsp::CompletionItem::default()
13547 };
13548
13549 let item1 = item1.clone();
13550 cx.set_request_handler::<lsp::request::Completion, _, _>({
13551 let item1 = item1.clone();
13552 move |_, _, _| {
13553 let item1 = item1.clone();
13554 let item2 = item2.clone();
13555 async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
13556 }
13557 })
13558 .next()
13559 .await;
13560
13561 cx.condition(|editor, _| editor.context_menu_visible())
13562 .await;
13563 cx.update_editor(|editor, _, _| {
13564 let context_menu = editor.context_menu.borrow_mut();
13565 let context_menu = context_menu
13566 .as_ref()
13567 .expect("Should have the context menu deployed");
13568 match context_menu {
13569 CodeContextMenu::Completions(completions_menu) => {
13570 let completions = completions_menu.completions.borrow_mut();
13571 assert_eq!(
13572 completions
13573 .iter()
13574 .map(|completion| &completion.label.text)
13575 .collect::<Vec<_>>(),
13576 vec!["method id()", "other"]
13577 )
13578 }
13579 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
13580 }
13581 });
13582
13583 cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
13584 let item1 = item1.clone();
13585 move |_, item_to_resolve, _| {
13586 let item1 = item1.clone();
13587 async move {
13588 if item1 == item_to_resolve {
13589 Ok(lsp::CompletionItem {
13590 label: "method id()".to_string(),
13591 filter_text: Some("id".to_string()),
13592 detail: Some("Now resolved!".to_string()),
13593 documentation: Some(lsp::Documentation::String("Docs".to_string())),
13594 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13595 range: lsp::Range::new(
13596 lsp::Position::new(0, 22),
13597 lsp::Position::new(0, 22),
13598 ),
13599 new_text: ".id".to_string(),
13600 })),
13601 ..lsp::CompletionItem::default()
13602 })
13603 } else {
13604 Ok(item_to_resolve)
13605 }
13606 }
13607 }
13608 })
13609 .next()
13610 .await
13611 .unwrap();
13612 cx.run_until_parked();
13613
13614 cx.update_editor(|editor, window, cx| {
13615 editor.context_menu_next(&Default::default(), window, cx);
13616 });
13617
13618 cx.update_editor(|editor, _, _| {
13619 let context_menu = editor.context_menu.borrow_mut();
13620 let context_menu = context_menu
13621 .as_ref()
13622 .expect("Should have the context menu deployed");
13623 match context_menu {
13624 CodeContextMenu::Completions(completions_menu) => {
13625 let completions = completions_menu.completions.borrow_mut();
13626 assert_eq!(
13627 completions
13628 .iter()
13629 .map(|completion| &completion.label.text)
13630 .collect::<Vec<_>>(),
13631 vec!["method id() Now resolved!", "other"],
13632 "Should update first completion label, but not second as the filter text did not match."
13633 );
13634 }
13635 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
13636 }
13637 });
13638}
13639
13640#[gpui::test]
13641async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
13642 init_test(cx, |_| {});
13643
13644 let mut cx = EditorLspTestContext::new_rust(
13645 lsp::ServerCapabilities {
13646 completion_provider: Some(lsp::CompletionOptions {
13647 trigger_characters: Some(vec![".".to_string()]),
13648 resolve_provider: Some(true),
13649 ..Default::default()
13650 }),
13651 ..Default::default()
13652 },
13653 cx,
13654 )
13655 .await;
13656
13657 cx.set_state("fn main() { let a = 2ˇ; }");
13658 cx.simulate_keystroke(".");
13659
13660 let unresolved_item_1 = lsp::CompletionItem {
13661 label: "id".to_string(),
13662 filter_text: Some("id".to_string()),
13663 detail: None,
13664 documentation: None,
13665 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13666 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
13667 new_text: ".id".to_string(),
13668 })),
13669 ..lsp::CompletionItem::default()
13670 };
13671 let resolved_item_1 = lsp::CompletionItem {
13672 additional_text_edits: Some(vec![lsp::TextEdit {
13673 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
13674 new_text: "!!".to_string(),
13675 }]),
13676 ..unresolved_item_1.clone()
13677 };
13678 let unresolved_item_2 = lsp::CompletionItem {
13679 label: "other".to_string(),
13680 filter_text: Some("other".to_string()),
13681 detail: None,
13682 documentation: None,
13683 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13684 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
13685 new_text: ".other".to_string(),
13686 })),
13687 ..lsp::CompletionItem::default()
13688 };
13689 let resolved_item_2 = lsp::CompletionItem {
13690 additional_text_edits: Some(vec![lsp::TextEdit {
13691 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
13692 new_text: "??".to_string(),
13693 }]),
13694 ..unresolved_item_2.clone()
13695 };
13696
13697 let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
13698 let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
13699 cx.lsp
13700 .server
13701 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
13702 let unresolved_item_1 = unresolved_item_1.clone();
13703 let resolved_item_1 = resolved_item_1.clone();
13704 let unresolved_item_2 = unresolved_item_2.clone();
13705 let resolved_item_2 = resolved_item_2.clone();
13706 let resolve_requests_1 = resolve_requests_1.clone();
13707 let resolve_requests_2 = resolve_requests_2.clone();
13708 move |unresolved_request, _| {
13709 let unresolved_item_1 = unresolved_item_1.clone();
13710 let resolved_item_1 = resolved_item_1.clone();
13711 let unresolved_item_2 = unresolved_item_2.clone();
13712 let resolved_item_2 = resolved_item_2.clone();
13713 let resolve_requests_1 = resolve_requests_1.clone();
13714 let resolve_requests_2 = resolve_requests_2.clone();
13715 async move {
13716 if unresolved_request == unresolved_item_1 {
13717 resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
13718 Ok(resolved_item_1.clone())
13719 } else if unresolved_request == unresolved_item_2 {
13720 resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
13721 Ok(resolved_item_2.clone())
13722 } else {
13723 panic!("Unexpected completion item {unresolved_request:?}")
13724 }
13725 }
13726 }
13727 })
13728 .detach();
13729
13730 cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
13731 let unresolved_item_1 = unresolved_item_1.clone();
13732 let unresolved_item_2 = unresolved_item_2.clone();
13733 async move {
13734 Ok(Some(lsp::CompletionResponse::Array(vec![
13735 unresolved_item_1,
13736 unresolved_item_2,
13737 ])))
13738 }
13739 })
13740 .next()
13741 .await;
13742
13743 cx.condition(|editor, _| editor.context_menu_visible())
13744 .await;
13745 cx.update_editor(|editor, _, _| {
13746 let context_menu = editor.context_menu.borrow_mut();
13747 let context_menu = context_menu
13748 .as_ref()
13749 .expect("Should have the context menu deployed");
13750 match context_menu {
13751 CodeContextMenu::Completions(completions_menu) => {
13752 let completions = completions_menu.completions.borrow_mut();
13753 assert_eq!(
13754 completions
13755 .iter()
13756 .map(|completion| &completion.label.text)
13757 .collect::<Vec<_>>(),
13758 vec!["id", "other"]
13759 )
13760 }
13761 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
13762 }
13763 });
13764 cx.run_until_parked();
13765
13766 cx.update_editor(|editor, window, cx| {
13767 editor.context_menu_next(&ContextMenuNext, window, cx);
13768 });
13769 cx.run_until_parked();
13770 cx.update_editor(|editor, window, cx| {
13771 editor.context_menu_prev(&ContextMenuPrevious, window, cx);
13772 });
13773 cx.run_until_parked();
13774 cx.update_editor(|editor, window, cx| {
13775 editor.context_menu_next(&ContextMenuNext, window, cx);
13776 });
13777 cx.run_until_parked();
13778 cx.update_editor(|editor, window, cx| {
13779 editor
13780 .compose_completion(&ComposeCompletion::default(), window, cx)
13781 .expect("No task returned")
13782 })
13783 .await
13784 .expect("Completion failed");
13785 cx.run_until_parked();
13786
13787 cx.update_editor(|editor, _, cx| {
13788 assert_eq!(
13789 resolve_requests_1.load(atomic::Ordering::Acquire),
13790 1,
13791 "Should always resolve once despite multiple selections"
13792 );
13793 assert_eq!(
13794 resolve_requests_2.load(atomic::Ordering::Acquire),
13795 1,
13796 "Should always resolve once after multiple selections and applying the completion"
13797 );
13798 assert_eq!(
13799 editor.text(cx),
13800 "fn main() { let a = ??.other; }",
13801 "Should use resolved data when applying the completion"
13802 );
13803 });
13804}
13805
13806#[gpui::test]
13807async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
13808 init_test(cx, |_| {});
13809
13810 let item_0 = lsp::CompletionItem {
13811 label: "abs".into(),
13812 insert_text: Some("abs".into()),
13813 data: Some(json!({ "very": "special"})),
13814 insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
13815 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
13816 lsp::InsertReplaceEdit {
13817 new_text: "abs".to_string(),
13818 insert: lsp::Range::default(),
13819 replace: lsp::Range::default(),
13820 },
13821 )),
13822 ..lsp::CompletionItem::default()
13823 };
13824 let items = iter::once(item_0.clone())
13825 .chain((11..51).map(|i| lsp::CompletionItem {
13826 label: format!("item_{}", i),
13827 insert_text: Some(format!("item_{}", i)),
13828 insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
13829 ..lsp::CompletionItem::default()
13830 }))
13831 .collect::<Vec<_>>();
13832
13833 let default_commit_characters = vec!["?".to_string()];
13834 let default_data = json!({ "default": "data"});
13835 let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
13836 let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
13837 let default_edit_range = lsp::Range {
13838 start: lsp::Position {
13839 line: 0,
13840 character: 5,
13841 },
13842 end: lsp::Position {
13843 line: 0,
13844 character: 5,
13845 },
13846 };
13847
13848 let mut cx = EditorLspTestContext::new_rust(
13849 lsp::ServerCapabilities {
13850 completion_provider: Some(lsp::CompletionOptions {
13851 trigger_characters: Some(vec![".".to_string()]),
13852 resolve_provider: Some(true),
13853 ..Default::default()
13854 }),
13855 ..Default::default()
13856 },
13857 cx,
13858 )
13859 .await;
13860
13861 cx.set_state("fn main() { let a = 2ˇ; }");
13862 cx.simulate_keystroke(".");
13863
13864 let completion_data = default_data.clone();
13865 let completion_characters = default_commit_characters.clone();
13866 let completion_items = items.clone();
13867 cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
13868 let default_data = completion_data.clone();
13869 let default_commit_characters = completion_characters.clone();
13870 let items = completion_items.clone();
13871 async move {
13872 Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13873 items,
13874 item_defaults: Some(lsp::CompletionListItemDefaults {
13875 data: Some(default_data.clone()),
13876 commit_characters: Some(default_commit_characters.clone()),
13877 edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
13878 default_edit_range,
13879 )),
13880 insert_text_format: Some(default_insert_text_format),
13881 insert_text_mode: Some(default_insert_text_mode),
13882 }),
13883 ..lsp::CompletionList::default()
13884 })))
13885 }
13886 })
13887 .next()
13888 .await;
13889
13890 let resolved_items = Arc::new(Mutex::new(Vec::new()));
13891 cx.lsp
13892 .server
13893 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
13894 let closure_resolved_items = resolved_items.clone();
13895 move |item_to_resolve, _| {
13896 let closure_resolved_items = closure_resolved_items.clone();
13897 async move {
13898 closure_resolved_items.lock().push(item_to_resolve.clone());
13899 Ok(item_to_resolve)
13900 }
13901 }
13902 })
13903 .detach();
13904
13905 cx.condition(|editor, _| editor.context_menu_visible())
13906 .await;
13907 cx.run_until_parked();
13908 cx.update_editor(|editor, _, _| {
13909 let menu = editor.context_menu.borrow_mut();
13910 match menu.as_ref().expect("should have the completions menu") {
13911 CodeContextMenu::Completions(completions_menu) => {
13912 assert_eq!(
13913 completions_menu
13914 .entries
13915 .borrow()
13916 .iter()
13917 .map(|mat| mat.string.clone())
13918 .collect::<Vec<String>>(),
13919 items
13920 .iter()
13921 .map(|completion| completion.label.clone())
13922 .collect::<Vec<String>>()
13923 );
13924 }
13925 CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
13926 }
13927 });
13928 // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
13929 // with 4 from the end.
13930 assert_eq!(
13931 *resolved_items.lock(),
13932 [&items[0..16], &items[items.len() - 4..items.len()]]
13933 .concat()
13934 .iter()
13935 .cloned()
13936 .map(|mut item| {
13937 if item.data.is_none() {
13938 item.data = Some(default_data.clone());
13939 }
13940 item
13941 })
13942 .collect::<Vec<lsp::CompletionItem>>(),
13943 "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
13944 );
13945 resolved_items.lock().clear();
13946
13947 cx.update_editor(|editor, window, cx| {
13948 editor.context_menu_prev(&ContextMenuPrevious, window, cx);
13949 });
13950 cx.run_until_parked();
13951 // Completions that have already been resolved are skipped.
13952 assert_eq!(
13953 *resolved_items.lock(),
13954 items[items.len() - 16..items.len() - 4]
13955 .iter()
13956 .cloned()
13957 .map(|mut item| {
13958 if item.data.is_none() {
13959 item.data = Some(default_data.clone());
13960 }
13961 item
13962 })
13963 .collect::<Vec<lsp::CompletionItem>>()
13964 );
13965 resolved_items.lock().clear();
13966}
13967
13968#[gpui::test]
13969async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
13970 init_test(cx, |_| {});
13971
13972 let mut cx = EditorLspTestContext::new(
13973 Language::new(
13974 LanguageConfig {
13975 matcher: LanguageMatcher {
13976 path_suffixes: vec!["jsx".into()],
13977 ..Default::default()
13978 },
13979 overrides: [(
13980 "element".into(),
13981 LanguageConfigOverride {
13982 completion_query_characters: Override::Set(['-'].into_iter().collect()),
13983 ..Default::default()
13984 },
13985 )]
13986 .into_iter()
13987 .collect(),
13988 ..Default::default()
13989 },
13990 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
13991 )
13992 .with_override_query("(jsx_self_closing_element) @element")
13993 .unwrap(),
13994 lsp::ServerCapabilities {
13995 completion_provider: Some(lsp::CompletionOptions {
13996 trigger_characters: Some(vec![":".to_string()]),
13997 ..Default::default()
13998 }),
13999 ..Default::default()
14000 },
14001 cx,
14002 )
14003 .await;
14004
14005 cx.lsp
14006 .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14007 Ok(Some(lsp::CompletionResponse::Array(vec![
14008 lsp::CompletionItem {
14009 label: "bg-blue".into(),
14010 ..Default::default()
14011 },
14012 lsp::CompletionItem {
14013 label: "bg-red".into(),
14014 ..Default::default()
14015 },
14016 lsp::CompletionItem {
14017 label: "bg-yellow".into(),
14018 ..Default::default()
14019 },
14020 ])))
14021 });
14022
14023 cx.set_state(r#"<p class="bgˇ" />"#);
14024
14025 // Trigger completion when typing a dash, because the dash is an extra
14026 // word character in the 'element' scope, which contains the cursor.
14027 cx.simulate_keystroke("-");
14028 cx.executor().run_until_parked();
14029 cx.update_editor(|editor, _, _| {
14030 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14031 {
14032 assert_eq!(
14033 completion_menu_entries(&menu),
14034 &["bg-red", "bg-blue", "bg-yellow"]
14035 );
14036 } else {
14037 panic!("expected completion menu to be open");
14038 }
14039 });
14040
14041 cx.simulate_keystroke("l");
14042 cx.executor().run_until_parked();
14043 cx.update_editor(|editor, _, _| {
14044 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14045 {
14046 assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
14047 } else {
14048 panic!("expected completion menu to be open");
14049 }
14050 });
14051
14052 // When filtering completions, consider the character after the '-' to
14053 // be the start of a subword.
14054 cx.set_state(r#"<p class="yelˇ" />"#);
14055 cx.simulate_keystroke("l");
14056 cx.executor().run_until_parked();
14057 cx.update_editor(|editor, _, _| {
14058 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14059 {
14060 assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
14061 } else {
14062 panic!("expected completion menu to be open");
14063 }
14064 });
14065}
14066
14067fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
14068 let entries = menu.entries.borrow();
14069 entries.iter().map(|mat| mat.string.clone()).collect()
14070}
14071
14072#[gpui::test]
14073async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
14074 init_test(cx, |settings| {
14075 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
14076 FormatterList(vec![Formatter::Prettier].into()),
14077 ))
14078 });
14079
14080 let fs = FakeFs::new(cx.executor());
14081 fs.insert_file(path!("/file.ts"), Default::default()).await;
14082
14083 let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
14084 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14085
14086 language_registry.add(Arc::new(Language::new(
14087 LanguageConfig {
14088 name: "TypeScript".into(),
14089 matcher: LanguageMatcher {
14090 path_suffixes: vec!["ts".to_string()],
14091 ..Default::default()
14092 },
14093 ..Default::default()
14094 },
14095 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
14096 )));
14097 update_test_language_settings(cx, |settings| {
14098 settings.defaults.prettier = Some(PrettierSettings {
14099 allowed: true,
14100 ..PrettierSettings::default()
14101 });
14102 });
14103
14104 let test_plugin = "test_plugin";
14105 let _ = language_registry.register_fake_lsp(
14106 "TypeScript",
14107 FakeLspAdapter {
14108 prettier_plugins: vec![test_plugin],
14109 ..Default::default()
14110 },
14111 );
14112
14113 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
14114 let buffer = project
14115 .update(cx, |project, cx| {
14116 project.open_local_buffer(path!("/file.ts"), cx)
14117 })
14118 .await
14119 .unwrap();
14120
14121 let buffer_text = "one\ntwo\nthree\n";
14122 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
14123 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
14124 editor.update_in(cx, |editor, window, cx| {
14125 editor.set_text(buffer_text, window, cx)
14126 });
14127
14128 editor
14129 .update_in(cx, |editor, window, cx| {
14130 editor.perform_format(
14131 project.clone(),
14132 FormatTrigger::Manual,
14133 FormatTarget::Buffers,
14134 window,
14135 cx,
14136 )
14137 })
14138 .unwrap()
14139 .await;
14140 assert_eq!(
14141 editor.update(cx, |editor, cx| editor.text(cx)),
14142 buffer_text.to_string() + prettier_format_suffix,
14143 "Test prettier formatting was not applied to the original buffer text",
14144 );
14145
14146 update_test_language_settings(cx, |settings| {
14147 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
14148 });
14149 let format = editor.update_in(cx, |editor, window, cx| {
14150 editor.perform_format(
14151 project.clone(),
14152 FormatTrigger::Manual,
14153 FormatTarget::Buffers,
14154 window,
14155 cx,
14156 )
14157 });
14158 format.await.unwrap();
14159 assert_eq!(
14160 editor.update(cx, |editor, cx| editor.text(cx)),
14161 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
14162 "Autoformatting (via test prettier) was not applied to the original buffer text",
14163 );
14164}
14165
14166#[gpui::test]
14167async fn test_addition_reverts(cx: &mut TestAppContext) {
14168 init_test(cx, |_| {});
14169 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14170 let base_text = indoc! {r#"
14171 struct Row;
14172 struct Row1;
14173 struct Row2;
14174
14175 struct Row4;
14176 struct Row5;
14177 struct Row6;
14178
14179 struct Row8;
14180 struct Row9;
14181 struct Row10;"#};
14182
14183 // When addition hunks are not adjacent to carets, no hunk revert is performed
14184 assert_hunk_revert(
14185 indoc! {r#"struct Row;
14186 struct Row1;
14187 struct Row1.1;
14188 struct Row1.2;
14189 struct Row2;ˇ
14190
14191 struct Row4;
14192 struct Row5;
14193 struct Row6;
14194
14195 struct Row8;
14196 ˇstruct Row9;
14197 struct Row9.1;
14198 struct Row9.2;
14199 struct Row9.3;
14200 struct Row10;"#},
14201 vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
14202 indoc! {r#"struct Row;
14203 struct Row1;
14204 struct Row1.1;
14205 struct Row1.2;
14206 struct Row2;ˇ
14207
14208 struct Row4;
14209 struct Row5;
14210 struct Row6;
14211
14212 struct Row8;
14213 ˇstruct Row9;
14214 struct Row9.1;
14215 struct Row9.2;
14216 struct Row9.3;
14217 struct Row10;"#},
14218 base_text,
14219 &mut cx,
14220 );
14221 // Same for selections
14222 assert_hunk_revert(
14223 indoc! {r#"struct Row;
14224 struct Row1;
14225 struct Row2;
14226 struct Row2.1;
14227 struct Row2.2;
14228 «ˇ
14229 struct Row4;
14230 struct» Row5;
14231 «struct Row6;
14232 ˇ»
14233 struct Row9.1;
14234 struct Row9.2;
14235 struct Row9.3;
14236 struct Row8;
14237 struct Row9;
14238 struct Row10;"#},
14239 vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
14240 indoc! {r#"struct Row;
14241 struct Row1;
14242 struct Row2;
14243 struct Row2.1;
14244 struct Row2.2;
14245 «ˇ
14246 struct Row4;
14247 struct» Row5;
14248 «struct Row6;
14249 ˇ»
14250 struct Row9.1;
14251 struct Row9.2;
14252 struct Row9.3;
14253 struct Row8;
14254 struct Row9;
14255 struct Row10;"#},
14256 base_text,
14257 &mut cx,
14258 );
14259
14260 // When carets and selections intersect the addition hunks, those are reverted.
14261 // Adjacent carets got merged.
14262 assert_hunk_revert(
14263 indoc! {r#"struct Row;
14264 ˇ// something on the top
14265 struct Row1;
14266 struct Row2;
14267 struct Roˇw3.1;
14268 struct Row2.2;
14269 struct Row2.3;ˇ
14270
14271 struct Row4;
14272 struct ˇRow5.1;
14273 struct Row5.2;
14274 struct «Rowˇ»5.3;
14275 struct Row5;
14276 struct Row6;
14277 ˇ
14278 struct Row9.1;
14279 struct «Rowˇ»9.2;
14280 struct «ˇRow»9.3;
14281 struct Row8;
14282 struct Row9;
14283 «ˇ// something on bottom»
14284 struct Row10;"#},
14285 vec![
14286 DiffHunkStatusKind::Added,
14287 DiffHunkStatusKind::Added,
14288 DiffHunkStatusKind::Added,
14289 DiffHunkStatusKind::Added,
14290 DiffHunkStatusKind::Added,
14291 ],
14292 indoc! {r#"struct Row;
14293 ˇstruct Row1;
14294 struct Row2;
14295 ˇ
14296 struct Row4;
14297 ˇstruct Row5;
14298 struct Row6;
14299 ˇ
14300 ˇstruct Row8;
14301 struct Row9;
14302 ˇstruct Row10;"#},
14303 base_text,
14304 &mut cx,
14305 );
14306}
14307
14308#[gpui::test]
14309async fn test_modification_reverts(cx: &mut TestAppContext) {
14310 init_test(cx, |_| {});
14311 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14312 let base_text = indoc! {r#"
14313 struct Row;
14314 struct Row1;
14315 struct Row2;
14316
14317 struct Row4;
14318 struct Row5;
14319 struct Row6;
14320
14321 struct Row8;
14322 struct Row9;
14323 struct Row10;"#};
14324
14325 // Modification hunks behave the same as the addition ones.
14326 assert_hunk_revert(
14327 indoc! {r#"struct Row;
14328 struct Row1;
14329 struct Row33;
14330 ˇ
14331 struct Row4;
14332 struct Row5;
14333 struct Row6;
14334 ˇ
14335 struct Row99;
14336 struct Row9;
14337 struct Row10;"#},
14338 vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
14339 indoc! {r#"struct Row;
14340 struct Row1;
14341 struct Row33;
14342 ˇ
14343 struct Row4;
14344 struct Row5;
14345 struct Row6;
14346 ˇ
14347 struct Row99;
14348 struct Row9;
14349 struct Row10;"#},
14350 base_text,
14351 &mut cx,
14352 );
14353 assert_hunk_revert(
14354 indoc! {r#"struct Row;
14355 struct Row1;
14356 struct Row33;
14357 «ˇ
14358 struct Row4;
14359 struct» Row5;
14360 «struct Row6;
14361 ˇ»
14362 struct Row99;
14363 struct Row9;
14364 struct Row10;"#},
14365 vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
14366 indoc! {r#"struct Row;
14367 struct Row1;
14368 struct Row33;
14369 «ˇ
14370 struct Row4;
14371 struct» Row5;
14372 «struct Row6;
14373 ˇ»
14374 struct Row99;
14375 struct Row9;
14376 struct Row10;"#},
14377 base_text,
14378 &mut cx,
14379 );
14380
14381 assert_hunk_revert(
14382 indoc! {r#"ˇstruct Row1.1;
14383 struct Row1;
14384 «ˇstr»uct Row22;
14385
14386 struct ˇRow44;
14387 struct Row5;
14388 struct «Rˇ»ow66;ˇ
14389
14390 «struˇ»ct Row88;
14391 struct Row9;
14392 struct Row1011;ˇ"#},
14393 vec![
14394 DiffHunkStatusKind::Modified,
14395 DiffHunkStatusKind::Modified,
14396 DiffHunkStatusKind::Modified,
14397 DiffHunkStatusKind::Modified,
14398 DiffHunkStatusKind::Modified,
14399 DiffHunkStatusKind::Modified,
14400 ],
14401 indoc! {r#"struct Row;
14402 ˇstruct Row1;
14403 struct Row2;
14404 ˇ
14405 struct Row4;
14406 ˇstruct Row5;
14407 struct Row6;
14408 ˇ
14409 struct Row8;
14410 ˇstruct Row9;
14411 struct Row10;ˇ"#},
14412 base_text,
14413 &mut cx,
14414 );
14415}
14416
14417#[gpui::test]
14418async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
14419 init_test(cx, |_| {});
14420 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14421 let base_text = indoc! {r#"
14422 one
14423
14424 two
14425 three
14426 "#};
14427
14428 cx.set_head_text(base_text);
14429 cx.set_state("\nˇ\n");
14430 cx.executor().run_until_parked();
14431 cx.update_editor(|editor, _window, cx| {
14432 editor.expand_selected_diff_hunks(cx);
14433 });
14434 cx.executor().run_until_parked();
14435 cx.update_editor(|editor, window, cx| {
14436 editor.backspace(&Default::default(), window, cx);
14437 });
14438 cx.run_until_parked();
14439 cx.assert_state_with_diff(
14440 indoc! {r#"
14441
14442 - two
14443 - threeˇ
14444 +
14445 "#}
14446 .to_string(),
14447 );
14448}
14449
14450#[gpui::test]
14451async fn test_deletion_reverts(cx: &mut TestAppContext) {
14452 init_test(cx, |_| {});
14453 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14454 let base_text = indoc! {r#"struct Row;
14455struct Row1;
14456struct Row2;
14457
14458struct Row4;
14459struct Row5;
14460struct Row6;
14461
14462struct Row8;
14463struct Row9;
14464struct Row10;"#};
14465
14466 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
14467 assert_hunk_revert(
14468 indoc! {r#"struct Row;
14469 struct Row2;
14470
14471 ˇstruct Row4;
14472 struct Row5;
14473 struct Row6;
14474 ˇ
14475 struct Row8;
14476 struct Row10;"#},
14477 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
14478 indoc! {r#"struct Row;
14479 struct Row2;
14480
14481 ˇstruct Row4;
14482 struct Row5;
14483 struct Row6;
14484 ˇ
14485 struct Row8;
14486 struct Row10;"#},
14487 base_text,
14488 &mut cx,
14489 );
14490 assert_hunk_revert(
14491 indoc! {r#"struct Row;
14492 struct Row2;
14493
14494 «ˇstruct Row4;
14495 struct» Row5;
14496 «struct Row6;
14497 ˇ»
14498 struct Row8;
14499 struct Row10;"#},
14500 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
14501 indoc! {r#"struct Row;
14502 struct Row2;
14503
14504 «ˇstruct Row4;
14505 struct» Row5;
14506 «struct Row6;
14507 ˇ»
14508 struct Row8;
14509 struct Row10;"#},
14510 base_text,
14511 &mut cx,
14512 );
14513
14514 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
14515 assert_hunk_revert(
14516 indoc! {r#"struct Row;
14517 ˇstruct Row2;
14518
14519 struct Row4;
14520 struct Row5;
14521 struct Row6;
14522
14523 struct Row8;ˇ
14524 struct Row10;"#},
14525 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
14526 indoc! {r#"struct Row;
14527 struct Row1;
14528 ˇstruct Row2;
14529
14530 struct Row4;
14531 struct Row5;
14532 struct Row6;
14533
14534 struct Row8;ˇ
14535 struct Row9;
14536 struct Row10;"#},
14537 base_text,
14538 &mut cx,
14539 );
14540 assert_hunk_revert(
14541 indoc! {r#"struct Row;
14542 struct Row2«ˇ;
14543 struct Row4;
14544 struct» Row5;
14545 «struct Row6;
14546
14547 struct Row8;ˇ»
14548 struct Row10;"#},
14549 vec![
14550 DiffHunkStatusKind::Deleted,
14551 DiffHunkStatusKind::Deleted,
14552 DiffHunkStatusKind::Deleted,
14553 ],
14554 indoc! {r#"struct Row;
14555 struct Row1;
14556 struct Row2«ˇ;
14557
14558 struct Row4;
14559 struct» Row5;
14560 «struct Row6;
14561
14562 struct Row8;ˇ»
14563 struct Row9;
14564 struct Row10;"#},
14565 base_text,
14566 &mut cx,
14567 );
14568}
14569
14570#[gpui::test]
14571async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
14572 init_test(cx, |_| {});
14573
14574 let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
14575 let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
14576 let base_text_3 =
14577 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
14578
14579 let text_1 = edit_first_char_of_every_line(base_text_1);
14580 let text_2 = edit_first_char_of_every_line(base_text_2);
14581 let text_3 = edit_first_char_of_every_line(base_text_3);
14582
14583 let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
14584 let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
14585 let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
14586
14587 let multibuffer = cx.new(|cx| {
14588 let mut multibuffer = MultiBuffer::new(ReadWrite);
14589 multibuffer.push_excerpts(
14590 buffer_1.clone(),
14591 [
14592 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14593 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14594 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
14595 ],
14596 cx,
14597 );
14598 multibuffer.push_excerpts(
14599 buffer_2.clone(),
14600 [
14601 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14602 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14603 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
14604 ],
14605 cx,
14606 );
14607 multibuffer.push_excerpts(
14608 buffer_3.clone(),
14609 [
14610 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14611 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14612 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
14613 ],
14614 cx,
14615 );
14616 multibuffer
14617 });
14618
14619 let fs = FakeFs::new(cx.executor());
14620 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
14621 let (editor, cx) = cx
14622 .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
14623 editor.update_in(cx, |editor, _window, cx| {
14624 for (buffer, diff_base) in [
14625 (buffer_1.clone(), base_text_1),
14626 (buffer_2.clone(), base_text_2),
14627 (buffer_3.clone(), base_text_3),
14628 ] {
14629 let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
14630 editor
14631 .buffer
14632 .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
14633 }
14634 });
14635 cx.executor().run_until_parked();
14636
14637 editor.update_in(cx, |editor, window, cx| {
14638 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}");
14639 editor.select_all(&SelectAll, window, cx);
14640 editor.git_restore(&Default::default(), window, cx);
14641 });
14642 cx.executor().run_until_parked();
14643
14644 // When all ranges are selected, all buffer hunks are reverted.
14645 editor.update(cx, |editor, cx| {
14646 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");
14647 });
14648 buffer_1.update(cx, |buffer, _| {
14649 assert_eq!(buffer.text(), base_text_1);
14650 });
14651 buffer_2.update(cx, |buffer, _| {
14652 assert_eq!(buffer.text(), base_text_2);
14653 });
14654 buffer_3.update(cx, |buffer, _| {
14655 assert_eq!(buffer.text(), base_text_3);
14656 });
14657
14658 editor.update_in(cx, |editor, window, cx| {
14659 editor.undo(&Default::default(), window, cx);
14660 });
14661
14662 editor.update_in(cx, |editor, window, cx| {
14663 editor.change_selections(None, window, cx, |s| {
14664 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
14665 });
14666 editor.git_restore(&Default::default(), window, cx);
14667 });
14668
14669 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
14670 // but not affect buffer_2 and its related excerpts.
14671 editor.update(cx, |editor, cx| {
14672 assert_eq!(
14673 editor.text(cx),
14674 "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}"
14675 );
14676 });
14677 buffer_1.update(cx, |buffer, _| {
14678 assert_eq!(buffer.text(), base_text_1);
14679 });
14680 buffer_2.update(cx, |buffer, _| {
14681 assert_eq!(
14682 buffer.text(),
14683 "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
14684 );
14685 });
14686 buffer_3.update(cx, |buffer, _| {
14687 assert_eq!(
14688 buffer.text(),
14689 "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
14690 );
14691 });
14692
14693 fn edit_first_char_of_every_line(text: &str) -> String {
14694 text.split('\n')
14695 .map(|line| format!("X{}", &line[1..]))
14696 .collect::<Vec<_>>()
14697 .join("\n")
14698 }
14699}
14700
14701#[gpui::test]
14702async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
14703 init_test(cx, |_| {});
14704
14705 let cols = 4;
14706 let rows = 10;
14707 let sample_text_1 = sample_text(rows, cols, 'a');
14708 assert_eq!(
14709 sample_text_1,
14710 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
14711 );
14712 let sample_text_2 = sample_text(rows, cols, 'l');
14713 assert_eq!(
14714 sample_text_2,
14715 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
14716 );
14717 let sample_text_3 = sample_text(rows, cols, 'v');
14718 assert_eq!(
14719 sample_text_3,
14720 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
14721 );
14722
14723 let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
14724 let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
14725 let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
14726
14727 let multi_buffer = cx.new(|cx| {
14728 let mut multibuffer = MultiBuffer::new(ReadWrite);
14729 multibuffer.push_excerpts(
14730 buffer_1.clone(),
14731 [
14732 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14733 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14734 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
14735 ],
14736 cx,
14737 );
14738 multibuffer.push_excerpts(
14739 buffer_2.clone(),
14740 [
14741 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14742 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14743 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
14744 ],
14745 cx,
14746 );
14747 multibuffer.push_excerpts(
14748 buffer_3.clone(),
14749 [
14750 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14751 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14752 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
14753 ],
14754 cx,
14755 );
14756 multibuffer
14757 });
14758
14759 let fs = FakeFs::new(cx.executor());
14760 fs.insert_tree(
14761 "/a",
14762 json!({
14763 "main.rs": sample_text_1,
14764 "other.rs": sample_text_2,
14765 "lib.rs": sample_text_3,
14766 }),
14767 )
14768 .await;
14769 let project = Project::test(fs, ["/a".as_ref()], cx).await;
14770 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14771 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14772 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
14773 Editor::new(
14774 EditorMode::full(),
14775 multi_buffer,
14776 Some(project.clone()),
14777 window,
14778 cx,
14779 )
14780 });
14781 let multibuffer_item_id = workspace
14782 .update(cx, |workspace, window, cx| {
14783 assert!(
14784 workspace.active_item(cx).is_none(),
14785 "active item should be None before the first item is added"
14786 );
14787 workspace.add_item_to_active_pane(
14788 Box::new(multi_buffer_editor.clone()),
14789 None,
14790 true,
14791 window,
14792 cx,
14793 );
14794 let active_item = workspace
14795 .active_item(cx)
14796 .expect("should have an active item after adding the multi buffer");
14797 assert!(
14798 !active_item.is_singleton(cx),
14799 "A multi buffer was expected to active after adding"
14800 );
14801 active_item.item_id()
14802 })
14803 .unwrap();
14804 cx.executor().run_until_parked();
14805
14806 multi_buffer_editor.update_in(cx, |editor, window, cx| {
14807 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
14808 s.select_ranges(Some(1..2))
14809 });
14810 editor.open_excerpts(&OpenExcerpts, window, cx);
14811 });
14812 cx.executor().run_until_parked();
14813 let first_item_id = workspace
14814 .update(cx, |workspace, window, cx| {
14815 let active_item = workspace
14816 .active_item(cx)
14817 .expect("should have an active item after navigating into the 1st buffer");
14818 let first_item_id = active_item.item_id();
14819 assert_ne!(
14820 first_item_id, multibuffer_item_id,
14821 "Should navigate into the 1st buffer and activate it"
14822 );
14823 assert!(
14824 active_item.is_singleton(cx),
14825 "New active item should be a singleton buffer"
14826 );
14827 assert_eq!(
14828 active_item
14829 .act_as::<Editor>(cx)
14830 .expect("should have navigated into an editor for the 1st buffer")
14831 .read(cx)
14832 .text(cx),
14833 sample_text_1
14834 );
14835
14836 workspace
14837 .go_back(workspace.active_pane().downgrade(), window, cx)
14838 .detach_and_log_err(cx);
14839
14840 first_item_id
14841 })
14842 .unwrap();
14843 cx.executor().run_until_parked();
14844 workspace
14845 .update(cx, |workspace, _, cx| {
14846 let active_item = workspace
14847 .active_item(cx)
14848 .expect("should have an active item after navigating back");
14849 assert_eq!(
14850 active_item.item_id(),
14851 multibuffer_item_id,
14852 "Should navigate back to the multi buffer"
14853 );
14854 assert!(!active_item.is_singleton(cx));
14855 })
14856 .unwrap();
14857
14858 multi_buffer_editor.update_in(cx, |editor, window, cx| {
14859 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
14860 s.select_ranges(Some(39..40))
14861 });
14862 editor.open_excerpts(&OpenExcerpts, window, cx);
14863 });
14864 cx.executor().run_until_parked();
14865 let second_item_id = workspace
14866 .update(cx, |workspace, window, cx| {
14867 let active_item = workspace
14868 .active_item(cx)
14869 .expect("should have an active item after navigating into the 2nd buffer");
14870 let second_item_id = active_item.item_id();
14871 assert_ne!(
14872 second_item_id, multibuffer_item_id,
14873 "Should navigate away from the multibuffer"
14874 );
14875 assert_ne!(
14876 second_item_id, first_item_id,
14877 "Should navigate into the 2nd buffer and activate it"
14878 );
14879 assert!(
14880 active_item.is_singleton(cx),
14881 "New active item should be a singleton buffer"
14882 );
14883 assert_eq!(
14884 active_item
14885 .act_as::<Editor>(cx)
14886 .expect("should have navigated into an editor")
14887 .read(cx)
14888 .text(cx),
14889 sample_text_2
14890 );
14891
14892 workspace
14893 .go_back(workspace.active_pane().downgrade(), window, cx)
14894 .detach_and_log_err(cx);
14895
14896 second_item_id
14897 })
14898 .unwrap();
14899 cx.executor().run_until_parked();
14900 workspace
14901 .update(cx, |workspace, _, cx| {
14902 let active_item = workspace
14903 .active_item(cx)
14904 .expect("should have an active item after navigating back from the 2nd buffer");
14905 assert_eq!(
14906 active_item.item_id(),
14907 multibuffer_item_id,
14908 "Should navigate back from the 2nd buffer to the multi buffer"
14909 );
14910 assert!(!active_item.is_singleton(cx));
14911 })
14912 .unwrap();
14913
14914 multi_buffer_editor.update_in(cx, |editor, window, cx| {
14915 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
14916 s.select_ranges(Some(70..70))
14917 });
14918 editor.open_excerpts(&OpenExcerpts, window, cx);
14919 });
14920 cx.executor().run_until_parked();
14921 workspace
14922 .update(cx, |workspace, window, cx| {
14923 let active_item = workspace
14924 .active_item(cx)
14925 .expect("should have an active item after navigating into the 3rd buffer");
14926 let third_item_id = active_item.item_id();
14927 assert_ne!(
14928 third_item_id, multibuffer_item_id,
14929 "Should navigate into the 3rd buffer and activate it"
14930 );
14931 assert_ne!(third_item_id, first_item_id);
14932 assert_ne!(third_item_id, second_item_id);
14933 assert!(
14934 active_item.is_singleton(cx),
14935 "New active item should be a singleton buffer"
14936 );
14937 assert_eq!(
14938 active_item
14939 .act_as::<Editor>(cx)
14940 .expect("should have navigated into an editor")
14941 .read(cx)
14942 .text(cx),
14943 sample_text_3
14944 );
14945
14946 workspace
14947 .go_back(workspace.active_pane().downgrade(), window, cx)
14948 .detach_and_log_err(cx);
14949 })
14950 .unwrap();
14951 cx.executor().run_until_parked();
14952 workspace
14953 .update(cx, |workspace, _, cx| {
14954 let active_item = workspace
14955 .active_item(cx)
14956 .expect("should have an active item after navigating back from the 3rd buffer");
14957 assert_eq!(
14958 active_item.item_id(),
14959 multibuffer_item_id,
14960 "Should navigate back from the 3rd buffer to the multi buffer"
14961 );
14962 assert!(!active_item.is_singleton(cx));
14963 })
14964 .unwrap();
14965}
14966
14967#[gpui::test]
14968async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14969 init_test(cx, |_| {});
14970
14971 let mut cx = EditorTestContext::new(cx).await;
14972
14973 let diff_base = r#"
14974 use some::mod;
14975
14976 const A: u32 = 42;
14977
14978 fn main() {
14979 println!("hello");
14980
14981 println!("world");
14982 }
14983 "#
14984 .unindent();
14985
14986 cx.set_state(
14987 &r#"
14988 use some::modified;
14989
14990 ˇ
14991 fn main() {
14992 println!("hello there");
14993
14994 println!("around the");
14995 println!("world");
14996 }
14997 "#
14998 .unindent(),
14999 );
15000
15001 cx.set_head_text(&diff_base);
15002 executor.run_until_parked();
15003
15004 cx.update_editor(|editor, window, cx| {
15005 editor.go_to_next_hunk(&GoToHunk, window, cx);
15006 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
15007 });
15008 executor.run_until_parked();
15009 cx.assert_state_with_diff(
15010 r#"
15011 use some::modified;
15012
15013
15014 fn main() {
15015 - println!("hello");
15016 + ˇ println!("hello there");
15017
15018 println!("around the");
15019 println!("world");
15020 }
15021 "#
15022 .unindent(),
15023 );
15024
15025 cx.update_editor(|editor, window, cx| {
15026 for _ in 0..2 {
15027 editor.go_to_next_hunk(&GoToHunk, window, cx);
15028 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
15029 }
15030 });
15031 executor.run_until_parked();
15032 cx.assert_state_with_diff(
15033 r#"
15034 - use some::mod;
15035 + ˇuse some::modified;
15036
15037
15038 fn main() {
15039 - println!("hello");
15040 + println!("hello there");
15041
15042 + println!("around the");
15043 println!("world");
15044 }
15045 "#
15046 .unindent(),
15047 );
15048
15049 cx.update_editor(|editor, window, cx| {
15050 editor.go_to_next_hunk(&GoToHunk, window, cx);
15051 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
15052 });
15053 executor.run_until_parked();
15054 cx.assert_state_with_diff(
15055 r#"
15056 - use some::mod;
15057 + use some::modified;
15058
15059 - const A: u32 = 42;
15060 ˇ
15061 fn main() {
15062 - println!("hello");
15063 + println!("hello there");
15064
15065 + println!("around the");
15066 println!("world");
15067 }
15068 "#
15069 .unindent(),
15070 );
15071
15072 cx.update_editor(|editor, window, cx| {
15073 editor.cancel(&Cancel, window, cx);
15074 });
15075
15076 cx.assert_state_with_diff(
15077 r#"
15078 use some::modified;
15079
15080 ˇ
15081 fn main() {
15082 println!("hello there");
15083
15084 println!("around the");
15085 println!("world");
15086 }
15087 "#
15088 .unindent(),
15089 );
15090}
15091
15092#[gpui::test]
15093async fn test_diff_base_change_with_expanded_diff_hunks(
15094 executor: BackgroundExecutor,
15095 cx: &mut TestAppContext,
15096) {
15097 init_test(cx, |_| {});
15098
15099 let mut cx = EditorTestContext::new(cx).await;
15100
15101 let diff_base = r#"
15102 use some::mod1;
15103 use some::mod2;
15104
15105 const A: u32 = 42;
15106 const B: u32 = 42;
15107 const C: u32 = 42;
15108
15109 fn main() {
15110 println!("hello");
15111
15112 println!("world");
15113 }
15114 "#
15115 .unindent();
15116
15117 cx.set_state(
15118 &r#"
15119 use some::mod2;
15120
15121 const A: u32 = 42;
15122 const C: u32 = 42;
15123
15124 fn main(ˇ) {
15125 //println!("hello");
15126
15127 println!("world");
15128 //
15129 //
15130 }
15131 "#
15132 .unindent(),
15133 );
15134
15135 cx.set_head_text(&diff_base);
15136 executor.run_until_parked();
15137
15138 cx.update_editor(|editor, window, cx| {
15139 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15140 });
15141 executor.run_until_parked();
15142 cx.assert_state_with_diff(
15143 r#"
15144 - use some::mod1;
15145 use some::mod2;
15146
15147 const A: u32 = 42;
15148 - const B: u32 = 42;
15149 const C: u32 = 42;
15150
15151 fn main(ˇ) {
15152 - println!("hello");
15153 + //println!("hello");
15154
15155 println!("world");
15156 + //
15157 + //
15158 }
15159 "#
15160 .unindent(),
15161 );
15162
15163 cx.set_head_text("new diff base!");
15164 executor.run_until_parked();
15165 cx.assert_state_with_diff(
15166 r#"
15167 - new diff base!
15168 + use some::mod2;
15169 +
15170 + const A: u32 = 42;
15171 + const C: u32 = 42;
15172 +
15173 + fn main(ˇ) {
15174 + //println!("hello");
15175 +
15176 + println!("world");
15177 + //
15178 + //
15179 + }
15180 "#
15181 .unindent(),
15182 );
15183}
15184
15185#[gpui::test]
15186async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
15187 init_test(cx, |_| {});
15188
15189 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
15190 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
15191 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
15192 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
15193 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
15194 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
15195
15196 let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
15197 let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
15198 let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
15199
15200 let multi_buffer = cx.new(|cx| {
15201 let mut multibuffer = MultiBuffer::new(ReadWrite);
15202 multibuffer.push_excerpts(
15203 buffer_1.clone(),
15204 [
15205 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15206 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15207 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
15208 ],
15209 cx,
15210 );
15211 multibuffer.push_excerpts(
15212 buffer_2.clone(),
15213 [
15214 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15215 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15216 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
15217 ],
15218 cx,
15219 );
15220 multibuffer.push_excerpts(
15221 buffer_3.clone(),
15222 [
15223 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15224 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15225 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
15226 ],
15227 cx,
15228 );
15229 multibuffer
15230 });
15231
15232 let editor =
15233 cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
15234 editor
15235 .update(cx, |editor, _window, cx| {
15236 for (buffer, diff_base) in [
15237 (buffer_1.clone(), file_1_old),
15238 (buffer_2.clone(), file_2_old),
15239 (buffer_3.clone(), file_3_old),
15240 ] {
15241 let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
15242 editor
15243 .buffer
15244 .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
15245 }
15246 })
15247 .unwrap();
15248
15249 let mut cx = EditorTestContext::for_editor(editor, cx).await;
15250 cx.run_until_parked();
15251
15252 cx.assert_editor_state(
15253 &"
15254 ˇaaa
15255 ccc
15256 ddd
15257
15258 ggg
15259 hhh
15260
15261
15262 lll
15263 mmm
15264 NNN
15265
15266 qqq
15267 rrr
15268
15269 uuu
15270 111
15271 222
15272 333
15273
15274 666
15275 777
15276
15277 000
15278 !!!"
15279 .unindent(),
15280 );
15281
15282 cx.update_editor(|editor, window, cx| {
15283 editor.select_all(&SelectAll, window, cx);
15284 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
15285 });
15286 cx.executor().run_until_parked();
15287
15288 cx.assert_state_with_diff(
15289 "
15290 «aaa
15291 - bbb
15292 ccc
15293 ddd
15294
15295 ggg
15296 hhh
15297
15298
15299 lll
15300 mmm
15301 - nnn
15302 + NNN
15303
15304 qqq
15305 rrr
15306
15307 uuu
15308 111
15309 222
15310 333
15311
15312 + 666
15313 777
15314
15315 000
15316 !!!ˇ»"
15317 .unindent(),
15318 );
15319}
15320
15321#[gpui::test]
15322async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
15323 init_test(cx, |_| {});
15324
15325 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
15326 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
15327
15328 let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
15329 let multi_buffer = cx.new(|cx| {
15330 let mut multibuffer = MultiBuffer::new(ReadWrite);
15331 multibuffer.push_excerpts(
15332 buffer.clone(),
15333 [
15334 ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
15335 ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
15336 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
15337 ],
15338 cx,
15339 );
15340 multibuffer
15341 });
15342
15343 let editor =
15344 cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
15345 editor
15346 .update(cx, |editor, _window, cx| {
15347 let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
15348 editor
15349 .buffer
15350 .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
15351 })
15352 .unwrap();
15353
15354 let mut cx = EditorTestContext::for_editor(editor, cx).await;
15355 cx.run_until_parked();
15356
15357 cx.update_editor(|editor, window, cx| {
15358 editor.expand_all_diff_hunks(&Default::default(), window, cx)
15359 });
15360 cx.executor().run_until_parked();
15361
15362 // When the start of a hunk coincides with the start of its excerpt,
15363 // the hunk is expanded. When the start of a a hunk is earlier than
15364 // the start of its excerpt, the hunk is not expanded.
15365 cx.assert_state_with_diff(
15366 "
15367 ˇaaa
15368 - bbb
15369 + BBB
15370
15371 - ddd
15372 - eee
15373 + DDD
15374 + EEE
15375 fff
15376
15377 iii
15378 "
15379 .unindent(),
15380 );
15381}
15382
15383#[gpui::test]
15384async fn test_edits_around_expanded_insertion_hunks(
15385 executor: BackgroundExecutor,
15386 cx: &mut TestAppContext,
15387) {
15388 init_test(cx, |_| {});
15389
15390 let mut cx = EditorTestContext::new(cx).await;
15391
15392 let diff_base = r#"
15393 use some::mod1;
15394 use some::mod2;
15395
15396 const A: u32 = 42;
15397
15398 fn main() {
15399 println!("hello");
15400
15401 println!("world");
15402 }
15403 "#
15404 .unindent();
15405 executor.run_until_parked();
15406 cx.set_state(
15407 &r#"
15408 use some::mod1;
15409 use some::mod2;
15410
15411 const A: u32 = 42;
15412 const B: u32 = 42;
15413 const C: u32 = 42;
15414 ˇ
15415
15416 fn main() {
15417 println!("hello");
15418
15419 println!("world");
15420 }
15421 "#
15422 .unindent(),
15423 );
15424
15425 cx.set_head_text(&diff_base);
15426 executor.run_until_parked();
15427
15428 cx.update_editor(|editor, window, cx| {
15429 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15430 });
15431 executor.run_until_parked();
15432
15433 cx.assert_state_with_diff(
15434 r#"
15435 use some::mod1;
15436 use some::mod2;
15437
15438 const A: u32 = 42;
15439 + const B: u32 = 42;
15440 + const C: u32 = 42;
15441 + ˇ
15442
15443 fn main() {
15444 println!("hello");
15445
15446 println!("world");
15447 }
15448 "#
15449 .unindent(),
15450 );
15451
15452 cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
15453 executor.run_until_parked();
15454
15455 cx.assert_state_with_diff(
15456 r#"
15457 use some::mod1;
15458 use some::mod2;
15459
15460 const A: u32 = 42;
15461 + const B: u32 = 42;
15462 + const C: u32 = 42;
15463 + const D: u32 = 42;
15464 + ˇ
15465
15466 fn main() {
15467 println!("hello");
15468
15469 println!("world");
15470 }
15471 "#
15472 .unindent(),
15473 );
15474
15475 cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
15476 executor.run_until_parked();
15477
15478 cx.assert_state_with_diff(
15479 r#"
15480 use some::mod1;
15481 use some::mod2;
15482
15483 const A: u32 = 42;
15484 + const B: u32 = 42;
15485 + const C: u32 = 42;
15486 + const D: u32 = 42;
15487 + const E: u32 = 42;
15488 + ˇ
15489
15490 fn main() {
15491 println!("hello");
15492
15493 println!("world");
15494 }
15495 "#
15496 .unindent(),
15497 );
15498
15499 cx.update_editor(|editor, window, cx| {
15500 editor.delete_line(&DeleteLine, window, cx);
15501 });
15502 executor.run_until_parked();
15503
15504 cx.assert_state_with_diff(
15505 r#"
15506 use some::mod1;
15507 use some::mod2;
15508
15509 const A: u32 = 42;
15510 + const B: u32 = 42;
15511 + const C: u32 = 42;
15512 + const D: u32 = 42;
15513 + const E: u32 = 42;
15514 ˇ
15515 fn main() {
15516 println!("hello");
15517
15518 println!("world");
15519 }
15520 "#
15521 .unindent(),
15522 );
15523
15524 cx.update_editor(|editor, window, cx| {
15525 editor.move_up(&MoveUp, window, cx);
15526 editor.delete_line(&DeleteLine, window, cx);
15527 editor.move_up(&MoveUp, window, cx);
15528 editor.delete_line(&DeleteLine, window, cx);
15529 editor.move_up(&MoveUp, window, cx);
15530 editor.delete_line(&DeleteLine, window, cx);
15531 });
15532 executor.run_until_parked();
15533 cx.assert_state_with_diff(
15534 r#"
15535 use some::mod1;
15536 use some::mod2;
15537
15538 const A: u32 = 42;
15539 + const B: u32 = 42;
15540 ˇ
15541 fn main() {
15542 println!("hello");
15543
15544 println!("world");
15545 }
15546 "#
15547 .unindent(),
15548 );
15549
15550 cx.update_editor(|editor, window, cx| {
15551 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
15552 editor.delete_line(&DeleteLine, window, cx);
15553 });
15554 executor.run_until_parked();
15555 cx.assert_state_with_diff(
15556 r#"
15557 ˇ
15558 fn main() {
15559 println!("hello");
15560
15561 println!("world");
15562 }
15563 "#
15564 .unindent(),
15565 );
15566}
15567
15568#[gpui::test]
15569async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
15570 init_test(cx, |_| {});
15571
15572 let mut cx = EditorTestContext::new(cx).await;
15573 cx.set_head_text(indoc! { "
15574 one
15575 two
15576 three
15577 four
15578 five
15579 "
15580 });
15581 cx.set_state(indoc! { "
15582 one
15583 ˇthree
15584 five
15585 "});
15586 cx.run_until_parked();
15587 cx.update_editor(|editor, window, cx| {
15588 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
15589 });
15590 cx.assert_state_with_diff(
15591 indoc! { "
15592 one
15593 - two
15594 ˇthree
15595 - four
15596 five
15597 "}
15598 .to_string(),
15599 );
15600 cx.update_editor(|editor, window, cx| {
15601 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
15602 });
15603
15604 cx.assert_state_with_diff(
15605 indoc! { "
15606 one
15607 ˇthree
15608 five
15609 "}
15610 .to_string(),
15611 );
15612
15613 cx.set_state(indoc! { "
15614 one
15615 ˇTWO
15616 three
15617 four
15618 five
15619 "});
15620 cx.run_until_parked();
15621 cx.update_editor(|editor, window, cx| {
15622 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
15623 });
15624
15625 cx.assert_state_with_diff(
15626 indoc! { "
15627 one
15628 - two
15629 + ˇTWO
15630 three
15631 four
15632 five
15633 "}
15634 .to_string(),
15635 );
15636 cx.update_editor(|editor, window, cx| {
15637 editor.move_up(&Default::default(), window, cx);
15638 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
15639 });
15640 cx.assert_state_with_diff(
15641 indoc! { "
15642 one
15643 ˇTWO
15644 three
15645 four
15646 five
15647 "}
15648 .to_string(),
15649 );
15650}
15651
15652#[gpui::test]
15653async fn test_edits_around_expanded_deletion_hunks(
15654 executor: BackgroundExecutor,
15655 cx: &mut TestAppContext,
15656) {
15657 init_test(cx, |_| {});
15658
15659 let mut cx = EditorTestContext::new(cx).await;
15660
15661 let diff_base = r#"
15662 use some::mod1;
15663 use some::mod2;
15664
15665 const A: u32 = 42;
15666 const B: u32 = 42;
15667 const C: u32 = 42;
15668
15669
15670 fn main() {
15671 println!("hello");
15672
15673 println!("world");
15674 }
15675 "#
15676 .unindent();
15677 executor.run_until_parked();
15678 cx.set_state(
15679 &r#"
15680 use some::mod1;
15681 use some::mod2;
15682
15683 ˇconst B: u32 = 42;
15684 const C: u32 = 42;
15685
15686
15687 fn main() {
15688 println!("hello");
15689
15690 println!("world");
15691 }
15692 "#
15693 .unindent(),
15694 );
15695
15696 cx.set_head_text(&diff_base);
15697 executor.run_until_parked();
15698
15699 cx.update_editor(|editor, window, cx| {
15700 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15701 });
15702 executor.run_until_parked();
15703
15704 cx.assert_state_with_diff(
15705 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 );
15722
15723 cx.update_editor(|editor, window, cx| {
15724 editor.delete_line(&DeleteLine, window, cx);
15725 });
15726 executor.run_until_parked();
15727 cx.assert_state_with_diff(
15728 r#"
15729 use some::mod1;
15730 use some::mod2;
15731
15732 - const A: u32 = 42;
15733 - const B: u32 = 42;
15734 ˇconst C: u32 = 42;
15735
15736
15737 fn main() {
15738 println!("hello");
15739
15740 println!("world");
15741 }
15742 "#
15743 .unindent(),
15744 );
15745
15746 cx.update_editor(|editor, window, cx| {
15747 editor.delete_line(&DeleteLine, window, cx);
15748 });
15749 executor.run_until_parked();
15750 cx.assert_state_with_diff(
15751 r#"
15752 use some::mod1;
15753 use some::mod2;
15754
15755 - const A: u32 = 42;
15756 - const B: u32 = 42;
15757 - const C: u32 = 42;
15758 ˇ
15759
15760 fn main() {
15761 println!("hello");
15762
15763 println!("world");
15764 }
15765 "#
15766 .unindent(),
15767 );
15768
15769 cx.update_editor(|editor, window, cx| {
15770 editor.handle_input("replacement", window, cx);
15771 });
15772 executor.run_until_parked();
15773 cx.assert_state_with_diff(
15774 r#"
15775 use some::mod1;
15776 use some::mod2;
15777
15778 - const A: u32 = 42;
15779 - const B: u32 = 42;
15780 - const C: u32 = 42;
15781 -
15782 + replacementˇ
15783
15784 fn main() {
15785 println!("hello");
15786
15787 println!("world");
15788 }
15789 "#
15790 .unindent(),
15791 );
15792}
15793
15794#[gpui::test]
15795async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15796 init_test(cx, |_| {});
15797
15798 let mut cx = EditorTestContext::new(cx).await;
15799
15800 let base_text = r#"
15801 one
15802 two
15803 three
15804 four
15805 five
15806 "#
15807 .unindent();
15808 executor.run_until_parked();
15809 cx.set_state(
15810 &r#"
15811 one
15812 two
15813 fˇour
15814 five
15815 "#
15816 .unindent(),
15817 );
15818
15819 cx.set_head_text(&base_text);
15820 executor.run_until_parked();
15821
15822 cx.update_editor(|editor, window, cx| {
15823 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15824 });
15825 executor.run_until_parked();
15826
15827 cx.assert_state_with_diff(
15828 r#"
15829 one
15830 two
15831 - three
15832 fˇour
15833 five
15834 "#
15835 .unindent(),
15836 );
15837
15838 cx.update_editor(|editor, window, cx| {
15839 editor.backspace(&Backspace, window, cx);
15840 editor.backspace(&Backspace, window, cx);
15841 });
15842 executor.run_until_parked();
15843 cx.assert_state_with_diff(
15844 r#"
15845 one
15846 two
15847 - threeˇ
15848 - four
15849 + our
15850 five
15851 "#
15852 .unindent(),
15853 );
15854}
15855
15856#[gpui::test]
15857async fn test_edit_after_expanded_modification_hunk(
15858 executor: BackgroundExecutor,
15859 cx: &mut TestAppContext,
15860) {
15861 init_test(cx, |_| {});
15862
15863 let mut cx = EditorTestContext::new(cx).await;
15864
15865 let diff_base = r#"
15866 use some::mod1;
15867 use some::mod2;
15868
15869 const A: u32 = 42;
15870 const B: u32 = 42;
15871 const C: u32 = 42;
15872 const D: u32 = 42;
15873
15874
15875 fn main() {
15876 println!("hello");
15877
15878 println!("world");
15879 }"#
15880 .unindent();
15881
15882 cx.set_state(
15883 &r#"
15884 use some::mod1;
15885 use some::mod2;
15886
15887 const A: u32 = 42;
15888 const B: u32 = 42;
15889 const C: u32 = 43ˇ
15890 const D: u32 = 42;
15891
15892
15893 fn main() {
15894 println!("hello");
15895
15896 println!("world");
15897 }"#
15898 .unindent(),
15899 );
15900
15901 cx.set_head_text(&diff_base);
15902 executor.run_until_parked();
15903 cx.update_editor(|editor, window, cx| {
15904 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15905 });
15906 executor.run_until_parked();
15907
15908 cx.assert_state_with_diff(
15909 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 C: u32 = 43ˇ
15917 const D: u32 = 42;
15918
15919
15920 fn main() {
15921 println!("hello");
15922
15923 println!("world");
15924 }"#
15925 .unindent(),
15926 );
15927
15928 cx.update_editor(|editor, window, cx| {
15929 editor.handle_input("\nnew_line\n", window, cx);
15930 });
15931 executor.run_until_parked();
15932
15933 cx.assert_state_with_diff(
15934 r#"
15935 use some::mod1;
15936 use some::mod2;
15937
15938 const A: u32 = 42;
15939 const B: u32 = 42;
15940 - const C: u32 = 42;
15941 + const C: u32 = 43
15942 + new_line
15943 + ˇ
15944 const D: u32 = 42;
15945
15946
15947 fn main() {
15948 println!("hello");
15949
15950 println!("world");
15951 }"#
15952 .unindent(),
15953 );
15954}
15955
15956#[gpui::test]
15957async fn test_stage_and_unstage_added_file_hunk(
15958 executor: BackgroundExecutor,
15959 cx: &mut TestAppContext,
15960) {
15961 init_test(cx, |_| {});
15962
15963 let mut cx = EditorTestContext::new(cx).await;
15964 cx.update_editor(|editor, _, cx| {
15965 editor.set_expand_all_diff_hunks(cx);
15966 });
15967
15968 let working_copy = r#"
15969 ˇfn main() {
15970 println!("hello, world!");
15971 }
15972 "#
15973 .unindent();
15974
15975 cx.set_state(&working_copy);
15976 executor.run_until_parked();
15977
15978 cx.assert_state_with_diff(
15979 r#"
15980 + ˇfn main() {
15981 + println!("hello, world!");
15982 + }
15983 "#
15984 .unindent(),
15985 );
15986 cx.assert_index_text(None);
15987
15988 cx.update_editor(|editor, window, cx| {
15989 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
15990 });
15991 executor.run_until_parked();
15992 cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
15993 cx.assert_state_with_diff(
15994 r#"
15995 + ˇfn main() {
15996 + println!("hello, world!");
15997 + }
15998 "#
15999 .unindent(),
16000 );
16001
16002 cx.update_editor(|editor, window, cx| {
16003 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
16004 });
16005 executor.run_until_parked();
16006 cx.assert_index_text(None);
16007}
16008
16009async fn setup_indent_guides_editor(
16010 text: &str,
16011 cx: &mut TestAppContext,
16012) -> (BufferId, EditorTestContext) {
16013 init_test(cx, |_| {});
16014
16015 let mut cx = EditorTestContext::new(cx).await;
16016
16017 let buffer_id = cx.update_editor(|editor, window, cx| {
16018 editor.set_text(text, window, cx);
16019 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
16020
16021 buffer_ids[0]
16022 });
16023
16024 (buffer_id, cx)
16025}
16026
16027fn assert_indent_guides(
16028 range: Range<u32>,
16029 expected: Vec<IndentGuide>,
16030 active_indices: Option<Vec<usize>>,
16031 cx: &mut EditorTestContext,
16032) {
16033 let indent_guides = cx.update_editor(|editor, window, cx| {
16034 let snapshot = editor.snapshot(window, cx).display_snapshot;
16035 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
16036 editor,
16037 MultiBufferRow(range.start)..MultiBufferRow(range.end),
16038 true,
16039 &snapshot,
16040 cx,
16041 );
16042
16043 indent_guides.sort_by(|a, b| {
16044 a.depth.cmp(&b.depth).then(
16045 a.start_row
16046 .cmp(&b.start_row)
16047 .then(a.end_row.cmp(&b.end_row)),
16048 )
16049 });
16050 indent_guides
16051 });
16052
16053 if let Some(expected) = active_indices {
16054 let active_indices = cx.update_editor(|editor, window, cx| {
16055 let snapshot = editor.snapshot(window, cx).display_snapshot;
16056 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
16057 });
16058
16059 assert_eq!(
16060 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
16061 expected,
16062 "Active indent guide indices do not match"
16063 );
16064 }
16065
16066 assert_eq!(indent_guides, expected, "Indent guides do not match");
16067}
16068
16069fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
16070 IndentGuide {
16071 buffer_id,
16072 start_row: MultiBufferRow(start_row),
16073 end_row: MultiBufferRow(end_row),
16074 depth,
16075 tab_size: 4,
16076 settings: IndentGuideSettings {
16077 enabled: true,
16078 line_width: 1,
16079 active_line_width: 1,
16080 ..Default::default()
16081 },
16082 }
16083}
16084
16085#[gpui::test]
16086async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
16087 let (buffer_id, mut cx) = setup_indent_guides_editor(
16088 &"
16089 fn main() {
16090 let a = 1;
16091 }"
16092 .unindent(),
16093 cx,
16094 )
16095 .await;
16096
16097 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
16098}
16099
16100#[gpui::test]
16101async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
16102 let (buffer_id, mut cx) = setup_indent_guides_editor(
16103 &"
16104 fn main() {
16105 let a = 1;
16106 let b = 2;
16107 }"
16108 .unindent(),
16109 cx,
16110 )
16111 .await;
16112
16113 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
16114}
16115
16116#[gpui::test]
16117async fn test_indent_guide_nested(cx: &mut TestAppContext) {
16118 let (buffer_id, mut cx) = setup_indent_guides_editor(
16119 &"
16120 fn main() {
16121 let a = 1;
16122 if a == 3 {
16123 let b = 2;
16124 } else {
16125 let c = 3;
16126 }
16127 }"
16128 .unindent(),
16129 cx,
16130 )
16131 .await;
16132
16133 assert_indent_guides(
16134 0..8,
16135 vec![
16136 indent_guide(buffer_id, 1, 6, 0),
16137 indent_guide(buffer_id, 3, 3, 1),
16138 indent_guide(buffer_id, 5, 5, 1),
16139 ],
16140 None,
16141 &mut cx,
16142 );
16143}
16144
16145#[gpui::test]
16146async fn test_indent_guide_tab(cx: &mut TestAppContext) {
16147 let (buffer_id, mut cx) = setup_indent_guides_editor(
16148 &"
16149 fn main() {
16150 let a = 1;
16151 let b = 2;
16152 let c = 3;
16153 }"
16154 .unindent(),
16155 cx,
16156 )
16157 .await;
16158
16159 assert_indent_guides(
16160 0..5,
16161 vec![
16162 indent_guide(buffer_id, 1, 3, 0),
16163 indent_guide(buffer_id, 2, 2, 1),
16164 ],
16165 None,
16166 &mut cx,
16167 );
16168}
16169
16170#[gpui::test]
16171async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
16172 let (buffer_id, mut cx) = setup_indent_guides_editor(
16173 &"
16174 fn main() {
16175 let a = 1;
16176
16177 let c = 3;
16178 }"
16179 .unindent(),
16180 cx,
16181 )
16182 .await;
16183
16184 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
16185}
16186
16187#[gpui::test]
16188async fn test_indent_guide_complex(cx: &mut TestAppContext) {
16189 let (buffer_id, mut cx) = setup_indent_guides_editor(
16190 &"
16191 fn main() {
16192 let a = 1;
16193
16194 let c = 3;
16195
16196 if a == 3 {
16197 let b = 2;
16198 } else {
16199 let c = 3;
16200 }
16201 }"
16202 .unindent(),
16203 cx,
16204 )
16205 .await;
16206
16207 assert_indent_guides(
16208 0..11,
16209 vec![
16210 indent_guide(buffer_id, 1, 9, 0),
16211 indent_guide(buffer_id, 6, 6, 1),
16212 indent_guide(buffer_id, 8, 8, 1),
16213 ],
16214 None,
16215 &mut cx,
16216 );
16217}
16218
16219#[gpui::test]
16220async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
16221 let (buffer_id, mut cx) = setup_indent_guides_editor(
16222 &"
16223 fn main() {
16224 let a = 1;
16225
16226 let c = 3;
16227
16228 if a == 3 {
16229 let b = 2;
16230 } else {
16231 let c = 3;
16232 }
16233 }"
16234 .unindent(),
16235 cx,
16236 )
16237 .await;
16238
16239 assert_indent_guides(
16240 1..11,
16241 vec![
16242 indent_guide(buffer_id, 1, 9, 0),
16243 indent_guide(buffer_id, 6, 6, 1),
16244 indent_guide(buffer_id, 8, 8, 1),
16245 ],
16246 None,
16247 &mut cx,
16248 );
16249}
16250
16251#[gpui::test]
16252async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
16253 let (buffer_id, mut cx) = setup_indent_guides_editor(
16254 &"
16255 fn main() {
16256 let a = 1;
16257
16258 let c = 3;
16259
16260 if a == 3 {
16261 let b = 2;
16262 } else {
16263 let c = 3;
16264 }
16265 }"
16266 .unindent(),
16267 cx,
16268 )
16269 .await;
16270
16271 assert_indent_guides(
16272 1..10,
16273 vec![
16274 indent_guide(buffer_id, 1, 9, 0),
16275 indent_guide(buffer_id, 6, 6, 1),
16276 indent_guide(buffer_id, 8, 8, 1),
16277 ],
16278 None,
16279 &mut cx,
16280 );
16281}
16282
16283#[gpui::test]
16284async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
16285 let (buffer_id, mut cx) = setup_indent_guides_editor(
16286 &"
16287 block1
16288 block2
16289 block3
16290 block4
16291 block2
16292 block1
16293 block1"
16294 .unindent(),
16295 cx,
16296 )
16297 .await;
16298
16299 assert_indent_guides(
16300 1..10,
16301 vec![
16302 indent_guide(buffer_id, 1, 4, 0),
16303 indent_guide(buffer_id, 2, 3, 1),
16304 indent_guide(buffer_id, 3, 3, 2),
16305 ],
16306 None,
16307 &mut cx,
16308 );
16309}
16310
16311#[gpui::test]
16312async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
16313 let (buffer_id, mut cx) = setup_indent_guides_editor(
16314 &"
16315 block1
16316 block2
16317 block3
16318
16319 block1
16320 block1"
16321 .unindent(),
16322 cx,
16323 )
16324 .await;
16325
16326 assert_indent_guides(
16327 0..6,
16328 vec![
16329 indent_guide(buffer_id, 1, 2, 0),
16330 indent_guide(buffer_id, 2, 2, 1),
16331 ],
16332 None,
16333 &mut cx,
16334 );
16335}
16336
16337#[gpui::test]
16338async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
16339 let (buffer_id, mut cx) = setup_indent_guides_editor(
16340 &"
16341 block1
16342
16343
16344
16345 block2
16346 "
16347 .unindent(),
16348 cx,
16349 )
16350 .await;
16351
16352 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
16353}
16354
16355#[gpui::test]
16356async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
16357 let (buffer_id, mut cx) = setup_indent_guides_editor(
16358 &"
16359 def a:
16360 \tb = 3
16361 \tif True:
16362 \t\tc = 4
16363 \t\td = 5
16364 \tprint(b)
16365 "
16366 .unindent(),
16367 cx,
16368 )
16369 .await;
16370
16371 assert_indent_guides(
16372 0..6,
16373 vec![
16374 indent_guide(buffer_id, 1, 6, 0),
16375 indent_guide(buffer_id, 3, 4, 1),
16376 ],
16377 None,
16378 &mut cx,
16379 );
16380}
16381
16382#[gpui::test]
16383async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
16384 let (buffer_id, mut cx) = setup_indent_guides_editor(
16385 &"
16386 fn main() {
16387 let a = 1;
16388 }"
16389 .unindent(),
16390 cx,
16391 )
16392 .await;
16393
16394 cx.update_editor(|editor, window, cx| {
16395 editor.change_selections(None, window, cx, |s| {
16396 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
16397 });
16398 });
16399
16400 assert_indent_guides(
16401 0..3,
16402 vec![indent_guide(buffer_id, 1, 1, 0)],
16403 Some(vec![0]),
16404 &mut cx,
16405 );
16406}
16407
16408#[gpui::test]
16409async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
16410 let (buffer_id, mut cx) = setup_indent_guides_editor(
16411 &"
16412 fn main() {
16413 if 1 == 2 {
16414 let a = 1;
16415 }
16416 }"
16417 .unindent(),
16418 cx,
16419 )
16420 .await;
16421
16422 cx.update_editor(|editor, window, cx| {
16423 editor.change_selections(None, window, cx, |s| {
16424 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
16425 });
16426 });
16427
16428 assert_indent_guides(
16429 0..4,
16430 vec![
16431 indent_guide(buffer_id, 1, 3, 0),
16432 indent_guide(buffer_id, 2, 2, 1),
16433 ],
16434 Some(vec![1]),
16435 &mut cx,
16436 );
16437
16438 cx.update_editor(|editor, window, cx| {
16439 editor.change_selections(None, window, cx, |s| {
16440 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
16441 });
16442 });
16443
16444 assert_indent_guides(
16445 0..4,
16446 vec![
16447 indent_guide(buffer_id, 1, 3, 0),
16448 indent_guide(buffer_id, 2, 2, 1),
16449 ],
16450 Some(vec![1]),
16451 &mut cx,
16452 );
16453
16454 cx.update_editor(|editor, window, cx| {
16455 editor.change_selections(None, window, cx, |s| {
16456 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
16457 });
16458 });
16459
16460 assert_indent_guides(
16461 0..4,
16462 vec![
16463 indent_guide(buffer_id, 1, 3, 0),
16464 indent_guide(buffer_id, 2, 2, 1),
16465 ],
16466 Some(vec![0]),
16467 &mut cx,
16468 );
16469}
16470
16471#[gpui::test]
16472async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
16473 let (buffer_id, mut cx) = setup_indent_guides_editor(
16474 &"
16475 fn main() {
16476 let a = 1;
16477
16478 let b = 2;
16479 }"
16480 .unindent(),
16481 cx,
16482 )
16483 .await;
16484
16485 cx.update_editor(|editor, window, cx| {
16486 editor.change_selections(None, window, cx, |s| {
16487 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
16488 });
16489 });
16490
16491 assert_indent_guides(
16492 0..5,
16493 vec![indent_guide(buffer_id, 1, 3, 0)],
16494 Some(vec![0]),
16495 &mut cx,
16496 );
16497}
16498
16499#[gpui::test]
16500async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
16501 let (buffer_id, mut cx) = setup_indent_guides_editor(
16502 &"
16503 def m:
16504 a = 1
16505 pass"
16506 .unindent(),
16507 cx,
16508 )
16509 .await;
16510
16511 cx.update_editor(|editor, window, cx| {
16512 editor.change_selections(None, window, cx, |s| {
16513 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
16514 });
16515 });
16516
16517 assert_indent_guides(
16518 0..3,
16519 vec![indent_guide(buffer_id, 1, 2, 0)],
16520 Some(vec![0]),
16521 &mut cx,
16522 );
16523}
16524
16525#[gpui::test]
16526async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
16527 init_test(cx, |_| {});
16528 let mut cx = EditorTestContext::new(cx).await;
16529 let text = indoc! {
16530 "
16531 impl A {
16532 fn b() {
16533 0;
16534 3;
16535 5;
16536 6;
16537 7;
16538 }
16539 }
16540 "
16541 };
16542 let base_text = indoc! {
16543 "
16544 impl A {
16545 fn b() {
16546 0;
16547 1;
16548 2;
16549 3;
16550 4;
16551 }
16552 fn c() {
16553 5;
16554 6;
16555 7;
16556 }
16557 }
16558 "
16559 };
16560
16561 cx.update_editor(|editor, window, cx| {
16562 editor.set_text(text, window, cx);
16563
16564 editor.buffer().update(cx, |multibuffer, cx| {
16565 let buffer = multibuffer.as_singleton().unwrap();
16566 let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
16567
16568 multibuffer.set_all_diff_hunks_expanded(cx);
16569 multibuffer.add_diff(diff, cx);
16570
16571 buffer.read(cx).remote_id()
16572 })
16573 });
16574 cx.run_until_parked();
16575
16576 cx.assert_state_with_diff(
16577 indoc! { "
16578 impl A {
16579 fn b() {
16580 0;
16581 - 1;
16582 - 2;
16583 3;
16584 - 4;
16585 - }
16586 - fn c() {
16587 5;
16588 6;
16589 7;
16590 }
16591 }
16592 ˇ"
16593 }
16594 .to_string(),
16595 );
16596
16597 let mut actual_guides = cx.update_editor(|editor, window, cx| {
16598 editor
16599 .snapshot(window, cx)
16600 .buffer_snapshot
16601 .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
16602 .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
16603 .collect::<Vec<_>>()
16604 });
16605 actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
16606 assert_eq!(
16607 actual_guides,
16608 vec![
16609 (MultiBufferRow(1)..=MultiBufferRow(12), 0),
16610 (MultiBufferRow(2)..=MultiBufferRow(6), 1),
16611 (MultiBufferRow(9)..=MultiBufferRow(11), 1),
16612 ]
16613 );
16614}
16615
16616#[gpui::test]
16617async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16618 init_test(cx, |_| {});
16619 let mut cx = EditorTestContext::new(cx).await;
16620
16621 let diff_base = r#"
16622 a
16623 b
16624 c
16625 "#
16626 .unindent();
16627
16628 cx.set_state(
16629 &r#"
16630 ˇA
16631 b
16632 C
16633 "#
16634 .unindent(),
16635 );
16636 cx.set_head_text(&diff_base);
16637 cx.update_editor(|editor, window, cx| {
16638 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16639 });
16640 executor.run_until_parked();
16641
16642 let both_hunks_expanded = r#"
16643 - a
16644 + ˇA
16645 b
16646 - c
16647 + C
16648 "#
16649 .unindent();
16650
16651 cx.assert_state_with_diff(both_hunks_expanded.clone());
16652
16653 let hunk_ranges = cx.update_editor(|editor, window, cx| {
16654 let snapshot = editor.snapshot(window, cx);
16655 let hunks = editor
16656 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
16657 .collect::<Vec<_>>();
16658 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
16659 let buffer_id = hunks[0].buffer_id;
16660 hunks
16661 .into_iter()
16662 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
16663 .collect::<Vec<_>>()
16664 });
16665 assert_eq!(hunk_ranges.len(), 2);
16666
16667 cx.update_editor(|editor, _, cx| {
16668 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
16669 });
16670 executor.run_until_parked();
16671
16672 let second_hunk_expanded = r#"
16673 ˇA
16674 b
16675 - c
16676 + C
16677 "#
16678 .unindent();
16679
16680 cx.assert_state_with_diff(second_hunk_expanded);
16681
16682 cx.update_editor(|editor, _, cx| {
16683 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
16684 });
16685 executor.run_until_parked();
16686
16687 cx.assert_state_with_diff(both_hunks_expanded.clone());
16688
16689 cx.update_editor(|editor, _, cx| {
16690 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
16691 });
16692 executor.run_until_parked();
16693
16694 let first_hunk_expanded = r#"
16695 - a
16696 + ˇA
16697 b
16698 C
16699 "#
16700 .unindent();
16701
16702 cx.assert_state_with_diff(first_hunk_expanded);
16703
16704 cx.update_editor(|editor, _, cx| {
16705 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
16706 });
16707 executor.run_until_parked();
16708
16709 cx.assert_state_with_diff(both_hunks_expanded);
16710
16711 cx.set_state(
16712 &r#"
16713 ˇA
16714 b
16715 "#
16716 .unindent(),
16717 );
16718 cx.run_until_parked();
16719
16720 // TODO this cursor position seems bad
16721 cx.assert_state_with_diff(
16722 r#"
16723 - ˇa
16724 + A
16725 b
16726 "#
16727 .unindent(),
16728 );
16729
16730 cx.update_editor(|editor, window, cx| {
16731 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16732 });
16733
16734 cx.assert_state_with_diff(
16735 r#"
16736 - ˇa
16737 + A
16738 b
16739 - c
16740 "#
16741 .unindent(),
16742 );
16743
16744 let hunk_ranges = cx.update_editor(|editor, window, cx| {
16745 let snapshot = editor.snapshot(window, cx);
16746 let hunks = editor
16747 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
16748 .collect::<Vec<_>>();
16749 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
16750 let buffer_id = hunks[0].buffer_id;
16751 hunks
16752 .into_iter()
16753 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
16754 .collect::<Vec<_>>()
16755 });
16756 assert_eq!(hunk_ranges.len(), 2);
16757
16758 cx.update_editor(|editor, _, cx| {
16759 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
16760 });
16761 executor.run_until_parked();
16762
16763 cx.assert_state_with_diff(
16764 r#"
16765 - ˇa
16766 + A
16767 b
16768 "#
16769 .unindent(),
16770 );
16771}
16772
16773#[gpui::test]
16774async fn test_toggle_deletion_hunk_at_start_of_file(
16775 executor: BackgroundExecutor,
16776 cx: &mut TestAppContext,
16777) {
16778 init_test(cx, |_| {});
16779 let mut cx = EditorTestContext::new(cx).await;
16780
16781 let diff_base = r#"
16782 a
16783 b
16784 c
16785 "#
16786 .unindent();
16787
16788 cx.set_state(
16789 &r#"
16790 ˇb
16791 c
16792 "#
16793 .unindent(),
16794 );
16795 cx.set_head_text(&diff_base);
16796 cx.update_editor(|editor, window, cx| {
16797 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16798 });
16799 executor.run_until_parked();
16800
16801 let hunk_expanded = r#"
16802 - a
16803 ˇb
16804 c
16805 "#
16806 .unindent();
16807
16808 cx.assert_state_with_diff(hunk_expanded.clone());
16809
16810 let hunk_ranges = cx.update_editor(|editor, window, cx| {
16811 let snapshot = editor.snapshot(window, cx);
16812 let hunks = editor
16813 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
16814 .collect::<Vec<_>>();
16815 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
16816 let buffer_id = hunks[0].buffer_id;
16817 hunks
16818 .into_iter()
16819 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
16820 .collect::<Vec<_>>()
16821 });
16822 assert_eq!(hunk_ranges.len(), 1);
16823
16824 cx.update_editor(|editor, _, cx| {
16825 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
16826 });
16827 executor.run_until_parked();
16828
16829 let hunk_collapsed = r#"
16830 ˇb
16831 c
16832 "#
16833 .unindent();
16834
16835 cx.assert_state_with_diff(hunk_collapsed);
16836
16837 cx.update_editor(|editor, _, cx| {
16838 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
16839 });
16840 executor.run_until_parked();
16841
16842 cx.assert_state_with_diff(hunk_expanded.clone());
16843}
16844
16845#[gpui::test]
16846async fn test_display_diff_hunks(cx: &mut TestAppContext) {
16847 init_test(cx, |_| {});
16848
16849 let fs = FakeFs::new(cx.executor());
16850 fs.insert_tree(
16851 path!("/test"),
16852 json!({
16853 ".git": {},
16854 "file-1": "ONE\n",
16855 "file-2": "TWO\n",
16856 "file-3": "THREE\n",
16857 }),
16858 )
16859 .await;
16860
16861 fs.set_head_for_repo(
16862 path!("/test/.git").as_ref(),
16863 &[
16864 ("file-1".into(), "one\n".into()),
16865 ("file-2".into(), "two\n".into()),
16866 ("file-3".into(), "three\n".into()),
16867 ],
16868 );
16869
16870 let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
16871 let mut buffers = vec![];
16872 for i in 1..=3 {
16873 let buffer = project
16874 .update(cx, |project, cx| {
16875 let path = format!(path!("/test/file-{}"), i);
16876 project.open_local_buffer(path, cx)
16877 })
16878 .await
16879 .unwrap();
16880 buffers.push(buffer);
16881 }
16882
16883 let multibuffer = cx.new(|cx| {
16884 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
16885 multibuffer.set_all_diff_hunks_expanded(cx);
16886 for buffer in &buffers {
16887 let snapshot = buffer.read(cx).snapshot();
16888 multibuffer.set_excerpts_for_path(
16889 PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
16890 buffer.clone(),
16891 vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
16892 DEFAULT_MULTIBUFFER_CONTEXT,
16893 cx,
16894 );
16895 }
16896 multibuffer
16897 });
16898
16899 let editor = cx.add_window(|window, cx| {
16900 Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
16901 });
16902 cx.run_until_parked();
16903
16904 let snapshot = editor
16905 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
16906 .unwrap();
16907 let hunks = snapshot
16908 .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
16909 .map(|hunk| match hunk {
16910 DisplayDiffHunk::Unfolded {
16911 display_row_range, ..
16912 } => display_row_range,
16913 DisplayDiffHunk::Folded { .. } => unreachable!(),
16914 })
16915 .collect::<Vec<_>>();
16916 assert_eq!(
16917 hunks,
16918 [
16919 DisplayRow(2)..DisplayRow(4),
16920 DisplayRow(7)..DisplayRow(9),
16921 DisplayRow(12)..DisplayRow(14),
16922 ]
16923 );
16924}
16925
16926#[gpui::test]
16927async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
16928 init_test(cx, |_| {});
16929
16930 let mut cx = EditorTestContext::new(cx).await;
16931 cx.set_head_text(indoc! { "
16932 one
16933 two
16934 three
16935 four
16936 five
16937 "
16938 });
16939 cx.set_index_text(indoc! { "
16940 one
16941 two
16942 three
16943 four
16944 five
16945 "
16946 });
16947 cx.set_state(indoc! {"
16948 one
16949 TWO
16950 ˇTHREE
16951 FOUR
16952 five
16953 "});
16954 cx.run_until_parked();
16955 cx.update_editor(|editor, window, cx| {
16956 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
16957 });
16958 cx.run_until_parked();
16959 cx.assert_index_text(Some(indoc! {"
16960 one
16961 TWO
16962 THREE
16963 FOUR
16964 five
16965 "}));
16966 cx.set_state(indoc! { "
16967 one
16968 TWO
16969 ˇTHREE-HUNDRED
16970 FOUR
16971 five
16972 "});
16973 cx.run_until_parked();
16974 cx.update_editor(|editor, window, cx| {
16975 let snapshot = editor.snapshot(window, cx);
16976 let hunks = editor
16977 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
16978 .collect::<Vec<_>>();
16979 assert_eq!(hunks.len(), 1);
16980 assert_eq!(
16981 hunks[0].status(),
16982 DiffHunkStatus {
16983 kind: DiffHunkStatusKind::Modified,
16984 secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
16985 }
16986 );
16987
16988 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
16989 });
16990 cx.run_until_parked();
16991 cx.assert_index_text(Some(indoc! {"
16992 one
16993 TWO
16994 THREE-HUNDRED
16995 FOUR
16996 five
16997 "}));
16998}
16999
17000#[gpui::test]
17001fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
17002 init_test(cx, |_| {});
17003
17004 let editor = cx.add_window(|window, cx| {
17005 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
17006 build_editor(buffer, window, cx)
17007 });
17008
17009 let render_args = Arc::new(Mutex::new(None));
17010 let snapshot = editor
17011 .update(cx, |editor, window, cx| {
17012 let snapshot = editor.buffer().read(cx).snapshot(cx);
17013 let range =
17014 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
17015
17016 struct RenderArgs {
17017 row: MultiBufferRow,
17018 folded: bool,
17019 callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
17020 }
17021
17022 let crease = Crease::inline(
17023 range,
17024 FoldPlaceholder::test(),
17025 {
17026 let toggle_callback = render_args.clone();
17027 move |row, folded, callback, _window, _cx| {
17028 *toggle_callback.lock() = Some(RenderArgs {
17029 row,
17030 folded,
17031 callback,
17032 });
17033 div()
17034 }
17035 },
17036 |_row, _folded, _window, _cx| div(),
17037 );
17038
17039 editor.insert_creases(Some(crease), cx);
17040 let snapshot = editor.snapshot(window, cx);
17041 let _div = snapshot.render_crease_toggle(
17042 MultiBufferRow(1),
17043 false,
17044 cx.entity().clone(),
17045 window,
17046 cx,
17047 );
17048 snapshot
17049 })
17050 .unwrap();
17051
17052 let render_args = render_args.lock().take().unwrap();
17053 assert_eq!(render_args.row, MultiBufferRow(1));
17054 assert!(!render_args.folded);
17055 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
17056
17057 cx.update_window(*editor, |_, window, cx| {
17058 (render_args.callback)(true, window, cx)
17059 })
17060 .unwrap();
17061 let snapshot = editor
17062 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
17063 .unwrap();
17064 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
17065
17066 cx.update_window(*editor, |_, window, cx| {
17067 (render_args.callback)(false, window, cx)
17068 })
17069 .unwrap();
17070 let snapshot = editor
17071 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
17072 .unwrap();
17073 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
17074}
17075
17076#[gpui::test]
17077async fn test_input_text(cx: &mut TestAppContext) {
17078 init_test(cx, |_| {});
17079 let mut cx = EditorTestContext::new(cx).await;
17080
17081 cx.set_state(
17082 &r#"ˇone
17083 two
17084
17085 three
17086 fourˇ
17087 five
17088
17089 siˇx"#
17090 .unindent(),
17091 );
17092
17093 cx.dispatch_action(HandleInput(String::new()));
17094 cx.assert_editor_state(
17095 &r#"ˇone
17096 two
17097
17098 three
17099 fourˇ
17100 five
17101
17102 siˇx"#
17103 .unindent(),
17104 );
17105
17106 cx.dispatch_action(HandleInput("AAAA".to_string()));
17107 cx.assert_editor_state(
17108 &r#"AAAAˇone
17109 two
17110
17111 three
17112 fourAAAAˇ
17113 five
17114
17115 siAAAAˇx"#
17116 .unindent(),
17117 );
17118}
17119
17120#[gpui::test]
17121async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
17122 init_test(cx, |_| {});
17123
17124 let mut cx = EditorTestContext::new(cx).await;
17125 cx.set_state(
17126 r#"let foo = 1;
17127let foo = 2;
17128let foo = 3;
17129let fooˇ = 4;
17130let foo = 5;
17131let foo = 6;
17132let foo = 7;
17133let foo = 8;
17134let foo = 9;
17135let foo = 10;
17136let foo = 11;
17137let foo = 12;
17138let foo = 13;
17139let foo = 14;
17140let foo = 15;"#,
17141 );
17142
17143 cx.update_editor(|e, window, cx| {
17144 assert_eq!(
17145 e.next_scroll_position,
17146 NextScrollCursorCenterTopBottom::Center,
17147 "Default next scroll direction is center",
17148 );
17149
17150 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
17151 assert_eq!(
17152 e.next_scroll_position,
17153 NextScrollCursorCenterTopBottom::Top,
17154 "After center, next scroll direction should be top",
17155 );
17156
17157 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
17158 assert_eq!(
17159 e.next_scroll_position,
17160 NextScrollCursorCenterTopBottom::Bottom,
17161 "After top, next scroll direction should be bottom",
17162 );
17163
17164 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
17165 assert_eq!(
17166 e.next_scroll_position,
17167 NextScrollCursorCenterTopBottom::Center,
17168 "After bottom, scrolling should start over",
17169 );
17170
17171 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
17172 assert_eq!(
17173 e.next_scroll_position,
17174 NextScrollCursorCenterTopBottom::Top,
17175 "Scrolling continues if retriggered fast enough"
17176 );
17177 });
17178
17179 cx.executor()
17180 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
17181 cx.executor().run_until_parked();
17182 cx.update_editor(|e, _, _| {
17183 assert_eq!(
17184 e.next_scroll_position,
17185 NextScrollCursorCenterTopBottom::Center,
17186 "If scrolling is not triggered fast enough, it should reset"
17187 );
17188 });
17189}
17190
17191#[gpui::test]
17192async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
17193 init_test(cx, |_| {});
17194 let mut cx = EditorLspTestContext::new_rust(
17195 lsp::ServerCapabilities {
17196 definition_provider: Some(lsp::OneOf::Left(true)),
17197 references_provider: Some(lsp::OneOf::Left(true)),
17198 ..lsp::ServerCapabilities::default()
17199 },
17200 cx,
17201 )
17202 .await;
17203
17204 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
17205 let go_to_definition = cx
17206 .lsp
17207 .set_request_handler::<lsp::request::GotoDefinition, _, _>(
17208 move |params, _| async move {
17209 if empty_go_to_definition {
17210 Ok(None)
17211 } else {
17212 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
17213 uri: params.text_document_position_params.text_document.uri,
17214 range: lsp::Range::new(
17215 lsp::Position::new(4, 3),
17216 lsp::Position::new(4, 6),
17217 ),
17218 })))
17219 }
17220 },
17221 );
17222 let references = cx
17223 .lsp
17224 .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
17225 Ok(Some(vec![lsp::Location {
17226 uri: params.text_document_position.text_document.uri,
17227 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
17228 }]))
17229 });
17230 (go_to_definition, references)
17231 };
17232
17233 cx.set_state(
17234 &r#"fn one() {
17235 let mut a = ˇtwo();
17236 }
17237
17238 fn two() {}"#
17239 .unindent(),
17240 );
17241 set_up_lsp_handlers(false, &mut cx);
17242 let navigated = cx
17243 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
17244 .await
17245 .expect("Failed to navigate to definition");
17246 assert_eq!(
17247 navigated,
17248 Navigated::Yes,
17249 "Should have navigated to definition from the GetDefinition response"
17250 );
17251 cx.assert_editor_state(
17252 &r#"fn one() {
17253 let mut a = two();
17254 }
17255
17256 fn «twoˇ»() {}"#
17257 .unindent(),
17258 );
17259
17260 let editors = cx.update_workspace(|workspace, _, cx| {
17261 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
17262 });
17263 cx.update_editor(|_, _, test_editor_cx| {
17264 assert_eq!(
17265 editors.len(),
17266 1,
17267 "Initially, only one, test, editor should be open in the workspace"
17268 );
17269 assert_eq!(
17270 test_editor_cx.entity(),
17271 editors.last().expect("Asserted len is 1").clone()
17272 );
17273 });
17274
17275 set_up_lsp_handlers(true, &mut cx);
17276 let navigated = cx
17277 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
17278 .await
17279 .expect("Failed to navigate to lookup references");
17280 assert_eq!(
17281 navigated,
17282 Navigated::Yes,
17283 "Should have navigated to references as a fallback after empty GoToDefinition response"
17284 );
17285 // We should not change the selections in the existing file,
17286 // if opening another milti buffer with the references
17287 cx.assert_editor_state(
17288 &r#"fn one() {
17289 let mut a = two();
17290 }
17291
17292 fn «twoˇ»() {}"#
17293 .unindent(),
17294 );
17295 let editors = cx.update_workspace(|workspace, _, cx| {
17296 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
17297 });
17298 cx.update_editor(|_, _, test_editor_cx| {
17299 assert_eq!(
17300 editors.len(),
17301 2,
17302 "After falling back to references search, we open a new editor with the results"
17303 );
17304 let references_fallback_text = editors
17305 .into_iter()
17306 .find(|new_editor| *new_editor != test_editor_cx.entity())
17307 .expect("Should have one non-test editor now")
17308 .read(test_editor_cx)
17309 .text(test_editor_cx);
17310 assert_eq!(
17311 references_fallback_text, "fn one() {\n let mut a = two();\n}",
17312 "Should use the range from the references response and not the GoToDefinition one"
17313 );
17314 });
17315}
17316
17317#[gpui::test]
17318async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
17319 init_test(cx, |_| {});
17320 cx.update(|cx| {
17321 let mut editor_settings = EditorSettings::get_global(cx).clone();
17322 editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
17323 EditorSettings::override_global(editor_settings, cx);
17324 });
17325 let mut cx = EditorLspTestContext::new_rust(
17326 lsp::ServerCapabilities {
17327 definition_provider: Some(lsp::OneOf::Left(true)),
17328 references_provider: Some(lsp::OneOf::Left(true)),
17329 ..lsp::ServerCapabilities::default()
17330 },
17331 cx,
17332 )
17333 .await;
17334 let original_state = r#"fn one() {
17335 let mut a = ˇtwo();
17336 }
17337
17338 fn two() {}"#
17339 .unindent();
17340 cx.set_state(&original_state);
17341
17342 let mut go_to_definition = cx
17343 .lsp
17344 .set_request_handler::<lsp::request::GotoDefinition, _, _>(
17345 move |_, _| async move { Ok(None) },
17346 );
17347 let _references = cx
17348 .lsp
17349 .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
17350 panic!("Should not call for references with no go to definition fallback")
17351 });
17352
17353 let navigated = cx
17354 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
17355 .await
17356 .expect("Failed to navigate to lookup references");
17357 go_to_definition
17358 .next()
17359 .await
17360 .expect("Should have called the go_to_definition handler");
17361
17362 assert_eq!(
17363 navigated,
17364 Navigated::No,
17365 "Should have navigated to references as a fallback after empty GoToDefinition response"
17366 );
17367 cx.assert_editor_state(&original_state);
17368 let editors = cx.update_workspace(|workspace, _, cx| {
17369 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
17370 });
17371 cx.update_editor(|_, _, _| {
17372 assert_eq!(
17373 editors.len(),
17374 1,
17375 "After unsuccessful fallback, no other editor should have been opened"
17376 );
17377 });
17378}
17379
17380#[gpui::test]
17381async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
17382 init_test(cx, |_| {});
17383
17384 let language = Arc::new(Language::new(
17385 LanguageConfig::default(),
17386 Some(tree_sitter_rust::LANGUAGE.into()),
17387 ));
17388
17389 let text = r#"
17390 #[cfg(test)]
17391 mod tests() {
17392 #[test]
17393 fn runnable_1() {
17394 let a = 1;
17395 }
17396
17397 #[test]
17398 fn runnable_2() {
17399 let a = 1;
17400 let b = 2;
17401 }
17402 }
17403 "#
17404 .unindent();
17405
17406 let fs = FakeFs::new(cx.executor());
17407 fs.insert_file("/file.rs", Default::default()).await;
17408
17409 let project = Project::test(fs, ["/a".as_ref()], cx).await;
17410 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17411 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17412 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
17413 let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
17414
17415 let editor = cx.new_window_entity(|window, cx| {
17416 Editor::new(
17417 EditorMode::full(),
17418 multi_buffer,
17419 Some(project.clone()),
17420 window,
17421 cx,
17422 )
17423 });
17424
17425 editor.update_in(cx, |editor, window, cx| {
17426 let snapshot = editor.buffer().read(cx).snapshot(cx);
17427 editor.tasks.insert(
17428 (buffer.read(cx).remote_id(), 3),
17429 RunnableTasks {
17430 templates: vec![],
17431 offset: snapshot.anchor_before(43),
17432 column: 0,
17433 extra_variables: HashMap::default(),
17434 context_range: BufferOffset(43)..BufferOffset(85),
17435 },
17436 );
17437 editor.tasks.insert(
17438 (buffer.read(cx).remote_id(), 8),
17439 RunnableTasks {
17440 templates: vec![],
17441 offset: snapshot.anchor_before(86),
17442 column: 0,
17443 extra_variables: HashMap::default(),
17444 context_range: BufferOffset(86)..BufferOffset(191),
17445 },
17446 );
17447
17448 // Test finding task when cursor is inside function body
17449 editor.change_selections(None, window, cx, |s| {
17450 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
17451 });
17452 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
17453 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
17454
17455 // Test finding task when cursor is on function name
17456 editor.change_selections(None, window, cx, |s| {
17457 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
17458 });
17459 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
17460 assert_eq!(row, 8, "Should find task when cursor is on function name");
17461 });
17462}
17463
17464#[gpui::test]
17465async fn test_folding_buffers(cx: &mut TestAppContext) {
17466 init_test(cx, |_| {});
17467
17468 let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
17469 let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
17470 let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
17471
17472 let fs = FakeFs::new(cx.executor());
17473 fs.insert_tree(
17474 path!("/a"),
17475 json!({
17476 "first.rs": sample_text_1,
17477 "second.rs": sample_text_2,
17478 "third.rs": sample_text_3,
17479 }),
17480 )
17481 .await;
17482 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17483 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17484 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17485 let worktree = project.update(cx, |project, cx| {
17486 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
17487 assert_eq!(worktrees.len(), 1);
17488 worktrees.pop().unwrap()
17489 });
17490 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
17491
17492 let buffer_1 = project
17493 .update(cx, |project, cx| {
17494 project.open_buffer((worktree_id, "first.rs"), cx)
17495 })
17496 .await
17497 .unwrap();
17498 let buffer_2 = project
17499 .update(cx, |project, cx| {
17500 project.open_buffer((worktree_id, "second.rs"), cx)
17501 })
17502 .await
17503 .unwrap();
17504 let buffer_3 = project
17505 .update(cx, |project, cx| {
17506 project.open_buffer((worktree_id, "third.rs"), cx)
17507 })
17508 .await
17509 .unwrap();
17510
17511 let multi_buffer = cx.new(|cx| {
17512 let mut multi_buffer = MultiBuffer::new(ReadWrite);
17513 multi_buffer.push_excerpts(
17514 buffer_1.clone(),
17515 [
17516 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17517 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17518 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17519 ],
17520 cx,
17521 );
17522 multi_buffer.push_excerpts(
17523 buffer_2.clone(),
17524 [
17525 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17526 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17527 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17528 ],
17529 cx,
17530 );
17531 multi_buffer.push_excerpts(
17532 buffer_3.clone(),
17533 [
17534 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17535 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17536 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17537 ],
17538 cx,
17539 );
17540 multi_buffer
17541 });
17542 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
17543 Editor::new(
17544 EditorMode::full(),
17545 multi_buffer.clone(),
17546 Some(project.clone()),
17547 window,
17548 cx,
17549 )
17550 });
17551
17552 assert_eq!(
17553 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17554 "\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",
17555 );
17556
17557 multi_buffer_editor.update(cx, |editor, cx| {
17558 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
17559 });
17560 assert_eq!(
17561 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17562 "\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",
17563 "After folding the first buffer, its text should not be displayed"
17564 );
17565
17566 multi_buffer_editor.update(cx, |editor, cx| {
17567 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
17568 });
17569 assert_eq!(
17570 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17571 "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
17572 "After folding the second buffer, its text should not be displayed"
17573 );
17574
17575 multi_buffer_editor.update(cx, |editor, cx| {
17576 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
17577 });
17578 assert_eq!(
17579 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17580 "\n\n\n\n\n",
17581 "After folding the third buffer, its text should not be displayed"
17582 );
17583
17584 // Emulate selection inside the fold logic, that should work
17585 multi_buffer_editor.update_in(cx, |editor, window, cx| {
17586 editor
17587 .snapshot(window, cx)
17588 .next_line_boundary(Point::new(0, 4));
17589 });
17590
17591 multi_buffer_editor.update(cx, |editor, cx| {
17592 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
17593 });
17594 assert_eq!(
17595 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17596 "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
17597 "After unfolding the second buffer, its text should be displayed"
17598 );
17599
17600 // Typing inside of buffer 1 causes that buffer to be unfolded.
17601 multi_buffer_editor.update_in(cx, |editor, window, cx| {
17602 assert_eq!(
17603 multi_buffer
17604 .read(cx)
17605 .snapshot(cx)
17606 .text_for_range(Point::new(1, 0)..Point::new(1, 4))
17607 .collect::<String>(),
17608 "bbbb"
17609 );
17610 editor.change_selections(None, window, cx, |selections| {
17611 selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
17612 });
17613 editor.handle_input("B", window, cx);
17614 });
17615
17616 assert_eq!(
17617 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17618 "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
17619 "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
17620 );
17621
17622 multi_buffer_editor.update(cx, |editor, cx| {
17623 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
17624 });
17625 assert_eq!(
17626 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17627 "\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",
17628 "After unfolding the all buffers, all original text should be displayed"
17629 );
17630}
17631
17632#[gpui::test]
17633async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
17634 init_test(cx, |_| {});
17635
17636 let sample_text_1 = "1111\n2222\n3333".to_string();
17637 let sample_text_2 = "4444\n5555\n6666".to_string();
17638 let sample_text_3 = "7777\n8888\n9999".to_string();
17639
17640 let fs = FakeFs::new(cx.executor());
17641 fs.insert_tree(
17642 path!("/a"),
17643 json!({
17644 "first.rs": sample_text_1,
17645 "second.rs": sample_text_2,
17646 "third.rs": sample_text_3,
17647 }),
17648 )
17649 .await;
17650 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17651 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17652 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17653 let worktree = project.update(cx, |project, cx| {
17654 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
17655 assert_eq!(worktrees.len(), 1);
17656 worktrees.pop().unwrap()
17657 });
17658 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
17659
17660 let buffer_1 = project
17661 .update(cx, |project, cx| {
17662 project.open_buffer((worktree_id, "first.rs"), cx)
17663 })
17664 .await
17665 .unwrap();
17666 let buffer_2 = project
17667 .update(cx, |project, cx| {
17668 project.open_buffer((worktree_id, "second.rs"), cx)
17669 })
17670 .await
17671 .unwrap();
17672 let buffer_3 = project
17673 .update(cx, |project, cx| {
17674 project.open_buffer((worktree_id, "third.rs"), cx)
17675 })
17676 .await
17677 .unwrap();
17678
17679 let multi_buffer = cx.new(|cx| {
17680 let mut multi_buffer = MultiBuffer::new(ReadWrite);
17681 multi_buffer.push_excerpts(
17682 buffer_1.clone(),
17683 [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
17684 cx,
17685 );
17686 multi_buffer.push_excerpts(
17687 buffer_2.clone(),
17688 [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
17689 cx,
17690 );
17691 multi_buffer.push_excerpts(
17692 buffer_3.clone(),
17693 [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
17694 cx,
17695 );
17696 multi_buffer
17697 });
17698
17699 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
17700 Editor::new(
17701 EditorMode::full(),
17702 multi_buffer,
17703 Some(project.clone()),
17704 window,
17705 cx,
17706 )
17707 });
17708
17709 let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
17710 assert_eq!(
17711 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17712 full_text,
17713 );
17714
17715 multi_buffer_editor.update(cx, |editor, cx| {
17716 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
17717 });
17718 assert_eq!(
17719 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17720 "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
17721 "After folding the first buffer, its text should not be displayed"
17722 );
17723
17724 multi_buffer_editor.update(cx, |editor, cx| {
17725 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
17726 });
17727
17728 assert_eq!(
17729 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17730 "\n\n\n\n\n\n7777\n8888\n9999",
17731 "After folding the second buffer, its text should not be displayed"
17732 );
17733
17734 multi_buffer_editor.update(cx, |editor, cx| {
17735 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
17736 });
17737 assert_eq!(
17738 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17739 "\n\n\n\n\n",
17740 "After folding the third buffer, its text should not be displayed"
17741 );
17742
17743 multi_buffer_editor.update(cx, |editor, cx| {
17744 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
17745 });
17746 assert_eq!(
17747 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17748 "\n\n\n\n4444\n5555\n6666\n\n",
17749 "After unfolding the second buffer, its text should be displayed"
17750 );
17751
17752 multi_buffer_editor.update(cx, |editor, cx| {
17753 editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
17754 });
17755 assert_eq!(
17756 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17757 "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
17758 "After unfolding the first buffer, its text should be displayed"
17759 );
17760
17761 multi_buffer_editor.update(cx, |editor, cx| {
17762 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
17763 });
17764 assert_eq!(
17765 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17766 full_text,
17767 "After unfolding all buffers, all original text should be displayed"
17768 );
17769}
17770
17771#[gpui::test]
17772async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
17773 init_test(cx, |_| {});
17774
17775 let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
17776
17777 let fs = FakeFs::new(cx.executor());
17778 fs.insert_tree(
17779 path!("/a"),
17780 json!({
17781 "main.rs": sample_text,
17782 }),
17783 )
17784 .await;
17785 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17786 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17787 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17788 let worktree = project.update(cx, |project, cx| {
17789 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
17790 assert_eq!(worktrees.len(), 1);
17791 worktrees.pop().unwrap()
17792 });
17793 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
17794
17795 let buffer_1 = project
17796 .update(cx, |project, cx| {
17797 project.open_buffer((worktree_id, "main.rs"), cx)
17798 })
17799 .await
17800 .unwrap();
17801
17802 let multi_buffer = cx.new(|cx| {
17803 let mut multi_buffer = MultiBuffer::new(ReadWrite);
17804 multi_buffer.push_excerpts(
17805 buffer_1.clone(),
17806 [ExcerptRange::new(
17807 Point::new(0, 0)
17808 ..Point::new(
17809 sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
17810 0,
17811 ),
17812 )],
17813 cx,
17814 );
17815 multi_buffer
17816 });
17817 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
17818 Editor::new(
17819 EditorMode::full(),
17820 multi_buffer,
17821 Some(project.clone()),
17822 window,
17823 cx,
17824 )
17825 });
17826
17827 let selection_range = Point::new(1, 0)..Point::new(2, 0);
17828 multi_buffer_editor.update_in(cx, |editor, window, cx| {
17829 enum TestHighlight {}
17830 let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
17831 let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
17832 editor.highlight_text::<TestHighlight>(
17833 vec![highlight_range.clone()],
17834 HighlightStyle::color(Hsla::green()),
17835 cx,
17836 );
17837 editor.change_selections(None, window, cx, |s| s.select_ranges(Some(highlight_range)));
17838 });
17839
17840 let full_text = format!("\n\n{sample_text}");
17841 assert_eq!(
17842 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17843 full_text,
17844 );
17845}
17846
17847#[gpui::test]
17848async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
17849 init_test(cx, |_| {});
17850 cx.update(|cx| {
17851 let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
17852 "keymaps/default-linux.json",
17853 cx,
17854 )
17855 .unwrap();
17856 cx.bind_keys(default_key_bindings);
17857 });
17858
17859 let (editor, cx) = cx.add_window_view(|window, cx| {
17860 let multi_buffer = MultiBuffer::build_multi(
17861 [
17862 ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
17863 ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
17864 ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
17865 ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
17866 ],
17867 cx,
17868 );
17869 let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
17870
17871 let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
17872 // fold all but the second buffer, so that we test navigating between two
17873 // adjacent folded buffers, as well as folded buffers at the start and
17874 // end the multibuffer
17875 editor.fold_buffer(buffer_ids[0], cx);
17876 editor.fold_buffer(buffer_ids[2], cx);
17877 editor.fold_buffer(buffer_ids[3], cx);
17878
17879 editor
17880 });
17881 cx.simulate_resize(size(px(1000.), px(1000.)));
17882
17883 let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
17884 cx.assert_excerpts_with_selections(indoc! {"
17885 [EXCERPT]
17886 ˇ[FOLDED]
17887 [EXCERPT]
17888 a1
17889 b1
17890 [EXCERPT]
17891 [FOLDED]
17892 [EXCERPT]
17893 [FOLDED]
17894 "
17895 });
17896 cx.simulate_keystroke("down");
17897 cx.assert_excerpts_with_selections(indoc! {"
17898 [EXCERPT]
17899 [FOLDED]
17900 [EXCERPT]
17901 ˇa1
17902 b1
17903 [EXCERPT]
17904 [FOLDED]
17905 [EXCERPT]
17906 [FOLDED]
17907 "
17908 });
17909 cx.simulate_keystroke("down");
17910 cx.assert_excerpts_with_selections(indoc! {"
17911 [EXCERPT]
17912 [FOLDED]
17913 [EXCERPT]
17914 a1
17915 ˇb1
17916 [EXCERPT]
17917 [FOLDED]
17918 [EXCERPT]
17919 [FOLDED]
17920 "
17921 });
17922 cx.simulate_keystroke("down");
17923 cx.assert_excerpts_with_selections(indoc! {"
17924 [EXCERPT]
17925 [FOLDED]
17926 [EXCERPT]
17927 a1
17928 b1
17929 ˇ[EXCERPT]
17930 [FOLDED]
17931 [EXCERPT]
17932 [FOLDED]
17933 "
17934 });
17935 cx.simulate_keystroke("down");
17936 cx.assert_excerpts_with_selections(indoc! {"
17937 [EXCERPT]
17938 [FOLDED]
17939 [EXCERPT]
17940 a1
17941 b1
17942 [EXCERPT]
17943 ˇ[FOLDED]
17944 [EXCERPT]
17945 [FOLDED]
17946 "
17947 });
17948 for _ in 0..5 {
17949 cx.simulate_keystroke("down");
17950 cx.assert_excerpts_with_selections(indoc! {"
17951 [EXCERPT]
17952 [FOLDED]
17953 [EXCERPT]
17954 a1
17955 b1
17956 [EXCERPT]
17957 [FOLDED]
17958 [EXCERPT]
17959 ˇ[FOLDED]
17960 "
17961 });
17962 }
17963
17964 cx.simulate_keystroke("up");
17965 cx.assert_excerpts_with_selections(indoc! {"
17966 [EXCERPT]
17967 [FOLDED]
17968 [EXCERPT]
17969 a1
17970 b1
17971 [EXCERPT]
17972 ˇ[FOLDED]
17973 [EXCERPT]
17974 [FOLDED]
17975 "
17976 });
17977 cx.simulate_keystroke("up");
17978 cx.assert_excerpts_with_selections(indoc! {"
17979 [EXCERPT]
17980 [FOLDED]
17981 [EXCERPT]
17982 a1
17983 b1
17984 ˇ[EXCERPT]
17985 [FOLDED]
17986 [EXCERPT]
17987 [FOLDED]
17988 "
17989 });
17990 cx.simulate_keystroke("up");
17991 cx.assert_excerpts_with_selections(indoc! {"
17992 [EXCERPT]
17993 [FOLDED]
17994 [EXCERPT]
17995 a1
17996 ˇb1
17997 [EXCERPT]
17998 [FOLDED]
17999 [EXCERPT]
18000 [FOLDED]
18001 "
18002 });
18003 cx.simulate_keystroke("up");
18004 cx.assert_excerpts_with_selections(indoc! {"
18005 [EXCERPT]
18006 [FOLDED]
18007 [EXCERPT]
18008 ˇa1
18009 b1
18010 [EXCERPT]
18011 [FOLDED]
18012 [EXCERPT]
18013 [FOLDED]
18014 "
18015 });
18016 for _ in 0..5 {
18017 cx.simulate_keystroke("up");
18018 cx.assert_excerpts_with_selections(indoc! {"
18019 [EXCERPT]
18020 ˇ[FOLDED]
18021 [EXCERPT]
18022 a1
18023 b1
18024 [EXCERPT]
18025 [FOLDED]
18026 [EXCERPT]
18027 [FOLDED]
18028 "
18029 });
18030 }
18031}
18032
18033#[gpui::test]
18034async fn test_inline_completion_text(cx: &mut TestAppContext) {
18035 init_test(cx, |_| {});
18036
18037 // Simple insertion
18038 assert_highlighted_edits(
18039 "Hello, world!",
18040 vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
18041 true,
18042 cx,
18043 |highlighted_edits, cx| {
18044 assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
18045 assert_eq!(highlighted_edits.highlights.len(), 1);
18046 assert_eq!(highlighted_edits.highlights[0].0, 6..16);
18047 assert_eq!(
18048 highlighted_edits.highlights[0].1.background_color,
18049 Some(cx.theme().status().created_background)
18050 );
18051 },
18052 )
18053 .await;
18054
18055 // Replacement
18056 assert_highlighted_edits(
18057 "This is a test.",
18058 vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
18059 false,
18060 cx,
18061 |highlighted_edits, cx| {
18062 assert_eq!(highlighted_edits.text, "That is a test.");
18063 assert_eq!(highlighted_edits.highlights.len(), 1);
18064 assert_eq!(highlighted_edits.highlights[0].0, 0..4);
18065 assert_eq!(
18066 highlighted_edits.highlights[0].1.background_color,
18067 Some(cx.theme().status().created_background)
18068 );
18069 },
18070 )
18071 .await;
18072
18073 // Multiple edits
18074 assert_highlighted_edits(
18075 "Hello, world!",
18076 vec![
18077 (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
18078 (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
18079 ],
18080 false,
18081 cx,
18082 |highlighted_edits, cx| {
18083 assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
18084 assert_eq!(highlighted_edits.highlights.len(), 2);
18085 assert_eq!(highlighted_edits.highlights[0].0, 0..9);
18086 assert_eq!(highlighted_edits.highlights[1].0, 16..29);
18087 assert_eq!(
18088 highlighted_edits.highlights[0].1.background_color,
18089 Some(cx.theme().status().created_background)
18090 );
18091 assert_eq!(
18092 highlighted_edits.highlights[1].1.background_color,
18093 Some(cx.theme().status().created_background)
18094 );
18095 },
18096 )
18097 .await;
18098
18099 // Multiple lines with edits
18100 assert_highlighted_edits(
18101 "First line\nSecond line\nThird line\nFourth line",
18102 vec![
18103 (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
18104 (
18105 Point::new(2, 0)..Point::new(2, 10),
18106 "New third line".to_string(),
18107 ),
18108 (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
18109 ],
18110 false,
18111 cx,
18112 |highlighted_edits, cx| {
18113 assert_eq!(
18114 highlighted_edits.text,
18115 "Second modified\nNew third line\nFourth updated line"
18116 );
18117 assert_eq!(highlighted_edits.highlights.len(), 3);
18118 assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
18119 assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
18120 assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
18121 for highlight in &highlighted_edits.highlights {
18122 assert_eq!(
18123 highlight.1.background_color,
18124 Some(cx.theme().status().created_background)
18125 );
18126 }
18127 },
18128 )
18129 .await;
18130}
18131
18132#[gpui::test]
18133async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
18134 init_test(cx, |_| {});
18135
18136 // Deletion
18137 assert_highlighted_edits(
18138 "Hello, world!",
18139 vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
18140 true,
18141 cx,
18142 |highlighted_edits, cx| {
18143 assert_eq!(highlighted_edits.text, "Hello, world!");
18144 assert_eq!(highlighted_edits.highlights.len(), 1);
18145 assert_eq!(highlighted_edits.highlights[0].0, 5..11);
18146 assert_eq!(
18147 highlighted_edits.highlights[0].1.background_color,
18148 Some(cx.theme().status().deleted_background)
18149 );
18150 },
18151 )
18152 .await;
18153
18154 // Insertion
18155 assert_highlighted_edits(
18156 "Hello, world!",
18157 vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
18158 true,
18159 cx,
18160 |highlighted_edits, cx| {
18161 assert_eq!(highlighted_edits.highlights.len(), 1);
18162 assert_eq!(highlighted_edits.highlights[0].0, 6..14);
18163 assert_eq!(
18164 highlighted_edits.highlights[0].1.background_color,
18165 Some(cx.theme().status().created_background)
18166 );
18167 },
18168 )
18169 .await;
18170}
18171
18172async fn assert_highlighted_edits(
18173 text: &str,
18174 edits: Vec<(Range<Point>, String)>,
18175 include_deletions: bool,
18176 cx: &mut TestAppContext,
18177 assertion_fn: impl Fn(HighlightedText, &App),
18178) {
18179 let window = cx.add_window(|window, cx| {
18180 let buffer = MultiBuffer::build_simple(text, cx);
18181 Editor::new(EditorMode::full(), buffer, None, window, cx)
18182 });
18183 let cx = &mut VisualTestContext::from_window(*window, cx);
18184
18185 let (buffer, snapshot) = window
18186 .update(cx, |editor, _window, cx| {
18187 (
18188 editor.buffer().clone(),
18189 editor.buffer().read(cx).snapshot(cx),
18190 )
18191 })
18192 .unwrap();
18193
18194 let edits = edits
18195 .into_iter()
18196 .map(|(range, edit)| {
18197 (
18198 snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
18199 edit,
18200 )
18201 })
18202 .collect::<Vec<_>>();
18203
18204 let text_anchor_edits = edits
18205 .clone()
18206 .into_iter()
18207 .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
18208 .collect::<Vec<_>>();
18209
18210 let edit_preview = window
18211 .update(cx, |_, _window, cx| {
18212 buffer
18213 .read(cx)
18214 .as_singleton()
18215 .unwrap()
18216 .read(cx)
18217 .preview_edits(text_anchor_edits.into(), cx)
18218 })
18219 .unwrap()
18220 .await;
18221
18222 cx.update(|_window, cx| {
18223 let highlighted_edits = inline_completion_edit_text(
18224 &snapshot.as_singleton().unwrap().2,
18225 &edits,
18226 &edit_preview,
18227 include_deletions,
18228 cx,
18229 );
18230 assertion_fn(highlighted_edits, cx)
18231 });
18232}
18233
18234#[track_caller]
18235fn assert_breakpoint(
18236 breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
18237 path: &Arc<Path>,
18238 expected: Vec<(u32, Breakpoint)>,
18239) {
18240 if expected.len() == 0usize {
18241 assert!(!breakpoints.contains_key(path), "{}", path.display());
18242 } else {
18243 let mut breakpoint = breakpoints
18244 .get(path)
18245 .unwrap()
18246 .into_iter()
18247 .map(|breakpoint| {
18248 (
18249 breakpoint.row,
18250 Breakpoint {
18251 message: breakpoint.message.clone(),
18252 state: breakpoint.state,
18253 condition: breakpoint.condition.clone(),
18254 hit_condition: breakpoint.hit_condition.clone(),
18255 },
18256 )
18257 })
18258 .collect::<Vec<_>>();
18259
18260 breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
18261
18262 assert_eq!(expected, breakpoint);
18263 }
18264}
18265
18266fn add_log_breakpoint_at_cursor(
18267 editor: &mut Editor,
18268 log_message: &str,
18269 window: &mut Window,
18270 cx: &mut Context<Editor>,
18271) {
18272 let (anchor, bp) = editor
18273 .breakpoints_at_cursors(window, cx)
18274 .first()
18275 .and_then(|(anchor, bp)| {
18276 if let Some(bp) = bp {
18277 Some((*anchor, bp.clone()))
18278 } else {
18279 None
18280 }
18281 })
18282 .unwrap_or_else(|| {
18283 let cursor_position: Point = editor.selections.newest(cx).head();
18284
18285 let breakpoint_position = editor
18286 .snapshot(window, cx)
18287 .display_snapshot
18288 .buffer_snapshot
18289 .anchor_before(Point::new(cursor_position.row, 0));
18290
18291 (breakpoint_position, Breakpoint::new_log(&log_message))
18292 });
18293
18294 editor.edit_breakpoint_at_anchor(
18295 anchor,
18296 bp,
18297 BreakpointEditAction::EditLogMessage(log_message.into()),
18298 cx,
18299 );
18300}
18301
18302#[gpui::test]
18303async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
18304 init_test(cx, |_| {});
18305
18306 let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
18307 let fs = FakeFs::new(cx.executor());
18308 fs.insert_tree(
18309 path!("/a"),
18310 json!({
18311 "main.rs": sample_text,
18312 }),
18313 )
18314 .await;
18315 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18316 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18317 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18318
18319 let fs = FakeFs::new(cx.executor());
18320 fs.insert_tree(
18321 path!("/a"),
18322 json!({
18323 "main.rs": sample_text,
18324 }),
18325 )
18326 .await;
18327 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18328 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18329 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18330 let worktree_id = workspace
18331 .update(cx, |workspace, _window, cx| {
18332 workspace.project().update(cx, |project, cx| {
18333 project.worktrees(cx).next().unwrap().read(cx).id()
18334 })
18335 })
18336 .unwrap();
18337
18338 let buffer = project
18339 .update(cx, |project, cx| {
18340 project.open_buffer((worktree_id, "main.rs"), cx)
18341 })
18342 .await
18343 .unwrap();
18344
18345 let (editor, cx) = cx.add_window_view(|window, cx| {
18346 Editor::new(
18347 EditorMode::full(),
18348 MultiBuffer::build_from_buffer(buffer, cx),
18349 Some(project.clone()),
18350 window,
18351 cx,
18352 )
18353 });
18354
18355 let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
18356 let abs_path = project.read_with(cx, |project, cx| {
18357 project
18358 .absolute_path(&project_path, cx)
18359 .map(|path_buf| Arc::from(path_buf.to_owned()))
18360 .unwrap()
18361 });
18362
18363 // assert we can add breakpoint on the first line
18364 editor.update_in(cx, |editor, window, cx| {
18365 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18366 editor.move_to_end(&MoveToEnd, window, cx);
18367 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18368 });
18369
18370 let breakpoints = editor.update(cx, |editor, cx| {
18371 editor
18372 .breakpoint_store()
18373 .as_ref()
18374 .unwrap()
18375 .read(cx)
18376 .all_breakpoints(cx)
18377 .clone()
18378 });
18379
18380 assert_eq!(1, breakpoints.len());
18381 assert_breakpoint(
18382 &breakpoints,
18383 &abs_path,
18384 vec![
18385 (0, Breakpoint::new_standard()),
18386 (3, Breakpoint::new_standard()),
18387 ],
18388 );
18389
18390 editor.update_in(cx, |editor, window, cx| {
18391 editor.move_to_beginning(&MoveToBeginning, window, cx);
18392 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18393 });
18394
18395 let breakpoints = editor.update(cx, |editor, cx| {
18396 editor
18397 .breakpoint_store()
18398 .as_ref()
18399 .unwrap()
18400 .read(cx)
18401 .all_breakpoints(cx)
18402 .clone()
18403 });
18404
18405 assert_eq!(1, breakpoints.len());
18406 assert_breakpoint(
18407 &breakpoints,
18408 &abs_path,
18409 vec![(3, Breakpoint::new_standard())],
18410 );
18411
18412 editor.update_in(cx, |editor, window, cx| {
18413 editor.move_to_end(&MoveToEnd, window, cx);
18414 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18415 });
18416
18417 let breakpoints = editor.update(cx, |editor, cx| {
18418 editor
18419 .breakpoint_store()
18420 .as_ref()
18421 .unwrap()
18422 .read(cx)
18423 .all_breakpoints(cx)
18424 .clone()
18425 });
18426
18427 assert_eq!(0, breakpoints.len());
18428 assert_breakpoint(&breakpoints, &abs_path, vec![]);
18429}
18430
18431#[gpui::test]
18432async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
18433 init_test(cx, |_| {});
18434
18435 let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
18436
18437 let fs = FakeFs::new(cx.executor());
18438 fs.insert_tree(
18439 path!("/a"),
18440 json!({
18441 "main.rs": sample_text,
18442 }),
18443 )
18444 .await;
18445 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18446 let (workspace, cx) =
18447 cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
18448
18449 let worktree_id = workspace.update(cx, |workspace, cx| {
18450 workspace.project().update(cx, |project, cx| {
18451 project.worktrees(cx).next().unwrap().read(cx).id()
18452 })
18453 });
18454
18455 let buffer = project
18456 .update(cx, |project, cx| {
18457 project.open_buffer((worktree_id, "main.rs"), cx)
18458 })
18459 .await
18460 .unwrap();
18461
18462 let (editor, cx) = cx.add_window_view(|window, cx| {
18463 Editor::new(
18464 EditorMode::full(),
18465 MultiBuffer::build_from_buffer(buffer, cx),
18466 Some(project.clone()),
18467 window,
18468 cx,
18469 )
18470 });
18471
18472 let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
18473 let abs_path = project.read_with(cx, |project, cx| {
18474 project
18475 .absolute_path(&project_path, cx)
18476 .map(|path_buf| Arc::from(path_buf.to_owned()))
18477 .unwrap()
18478 });
18479
18480 editor.update_in(cx, |editor, window, cx| {
18481 add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
18482 });
18483
18484 let breakpoints = editor.update(cx, |editor, cx| {
18485 editor
18486 .breakpoint_store()
18487 .as_ref()
18488 .unwrap()
18489 .read(cx)
18490 .all_breakpoints(cx)
18491 .clone()
18492 });
18493
18494 assert_breakpoint(
18495 &breakpoints,
18496 &abs_path,
18497 vec![(0, Breakpoint::new_log("hello world"))],
18498 );
18499
18500 // Removing a log message from a log breakpoint should remove it
18501 editor.update_in(cx, |editor, window, cx| {
18502 add_log_breakpoint_at_cursor(editor, "", window, cx);
18503 });
18504
18505 let breakpoints = editor.update(cx, |editor, cx| {
18506 editor
18507 .breakpoint_store()
18508 .as_ref()
18509 .unwrap()
18510 .read(cx)
18511 .all_breakpoints(cx)
18512 .clone()
18513 });
18514
18515 assert_breakpoint(&breakpoints, &abs_path, vec![]);
18516
18517 editor.update_in(cx, |editor, window, cx| {
18518 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18519 editor.move_to_end(&MoveToEnd, window, cx);
18520 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18521 // Not adding a log message to a standard breakpoint shouldn't remove it
18522 add_log_breakpoint_at_cursor(editor, "", window, cx);
18523 });
18524
18525 let breakpoints = editor.update(cx, |editor, cx| {
18526 editor
18527 .breakpoint_store()
18528 .as_ref()
18529 .unwrap()
18530 .read(cx)
18531 .all_breakpoints(cx)
18532 .clone()
18533 });
18534
18535 assert_breakpoint(
18536 &breakpoints,
18537 &abs_path,
18538 vec![
18539 (0, Breakpoint::new_standard()),
18540 (3, Breakpoint::new_standard()),
18541 ],
18542 );
18543
18544 editor.update_in(cx, |editor, window, cx| {
18545 add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
18546 });
18547
18548 let breakpoints = editor.update(cx, |editor, cx| {
18549 editor
18550 .breakpoint_store()
18551 .as_ref()
18552 .unwrap()
18553 .read(cx)
18554 .all_breakpoints(cx)
18555 .clone()
18556 });
18557
18558 assert_breakpoint(
18559 &breakpoints,
18560 &abs_path,
18561 vec![
18562 (0, Breakpoint::new_standard()),
18563 (3, Breakpoint::new_log("hello world")),
18564 ],
18565 );
18566
18567 editor.update_in(cx, |editor, window, cx| {
18568 add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
18569 });
18570
18571 let breakpoints = editor.update(cx, |editor, cx| {
18572 editor
18573 .breakpoint_store()
18574 .as_ref()
18575 .unwrap()
18576 .read(cx)
18577 .all_breakpoints(cx)
18578 .clone()
18579 });
18580
18581 assert_breakpoint(
18582 &breakpoints,
18583 &abs_path,
18584 vec![
18585 (0, Breakpoint::new_standard()),
18586 (3, Breakpoint::new_log("hello Earth!!")),
18587 ],
18588 );
18589}
18590
18591/// This also tests that Editor::breakpoint_at_cursor_head is working properly
18592/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
18593/// or when breakpoints were placed out of order. This tests for a regression too
18594#[gpui::test]
18595async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
18596 init_test(cx, |_| {});
18597
18598 let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
18599 let fs = FakeFs::new(cx.executor());
18600 fs.insert_tree(
18601 path!("/a"),
18602 json!({
18603 "main.rs": sample_text,
18604 }),
18605 )
18606 .await;
18607 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18608 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18609 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18610
18611 let fs = FakeFs::new(cx.executor());
18612 fs.insert_tree(
18613 path!("/a"),
18614 json!({
18615 "main.rs": sample_text,
18616 }),
18617 )
18618 .await;
18619 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18620 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18621 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18622 let worktree_id = workspace
18623 .update(cx, |workspace, _window, cx| {
18624 workspace.project().update(cx, |project, cx| {
18625 project.worktrees(cx).next().unwrap().read(cx).id()
18626 })
18627 })
18628 .unwrap();
18629
18630 let buffer = project
18631 .update(cx, |project, cx| {
18632 project.open_buffer((worktree_id, "main.rs"), cx)
18633 })
18634 .await
18635 .unwrap();
18636
18637 let (editor, cx) = cx.add_window_view(|window, cx| {
18638 Editor::new(
18639 EditorMode::full(),
18640 MultiBuffer::build_from_buffer(buffer, cx),
18641 Some(project.clone()),
18642 window,
18643 cx,
18644 )
18645 });
18646
18647 let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
18648 let abs_path = project.read_with(cx, |project, cx| {
18649 project
18650 .absolute_path(&project_path, cx)
18651 .map(|path_buf| Arc::from(path_buf.to_owned()))
18652 .unwrap()
18653 });
18654
18655 // assert we can add breakpoint on the first line
18656 editor.update_in(cx, |editor, window, cx| {
18657 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18658 editor.move_to_end(&MoveToEnd, window, cx);
18659 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18660 editor.move_up(&MoveUp, window, cx);
18661 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18662 });
18663
18664 let breakpoints = editor.update(cx, |editor, cx| {
18665 editor
18666 .breakpoint_store()
18667 .as_ref()
18668 .unwrap()
18669 .read(cx)
18670 .all_breakpoints(cx)
18671 .clone()
18672 });
18673
18674 assert_eq!(1, breakpoints.len());
18675 assert_breakpoint(
18676 &breakpoints,
18677 &abs_path,
18678 vec![
18679 (0, Breakpoint::new_standard()),
18680 (2, Breakpoint::new_standard()),
18681 (3, Breakpoint::new_standard()),
18682 ],
18683 );
18684
18685 editor.update_in(cx, |editor, window, cx| {
18686 editor.move_to_beginning(&MoveToBeginning, window, cx);
18687 editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
18688 editor.move_to_end(&MoveToEnd, window, cx);
18689 editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
18690 // Disabling a breakpoint that doesn't exist should do nothing
18691 editor.move_up(&MoveUp, window, cx);
18692 editor.move_up(&MoveUp, window, cx);
18693 editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
18694 });
18695
18696 let breakpoints = editor.update(cx, |editor, cx| {
18697 editor
18698 .breakpoint_store()
18699 .as_ref()
18700 .unwrap()
18701 .read(cx)
18702 .all_breakpoints(cx)
18703 .clone()
18704 });
18705
18706 let disable_breakpoint = {
18707 let mut bp = Breakpoint::new_standard();
18708 bp.state = BreakpointState::Disabled;
18709 bp
18710 };
18711
18712 assert_eq!(1, breakpoints.len());
18713 assert_breakpoint(
18714 &breakpoints,
18715 &abs_path,
18716 vec![
18717 (0, disable_breakpoint.clone()),
18718 (2, Breakpoint::new_standard()),
18719 (3, disable_breakpoint.clone()),
18720 ],
18721 );
18722
18723 editor.update_in(cx, |editor, window, cx| {
18724 editor.move_to_beginning(&MoveToBeginning, window, cx);
18725 editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
18726 editor.move_to_end(&MoveToEnd, window, cx);
18727 editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
18728 editor.move_up(&MoveUp, window, cx);
18729 editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
18730 });
18731
18732 let breakpoints = editor.update(cx, |editor, cx| {
18733 editor
18734 .breakpoint_store()
18735 .as_ref()
18736 .unwrap()
18737 .read(cx)
18738 .all_breakpoints(cx)
18739 .clone()
18740 });
18741
18742 assert_eq!(1, breakpoints.len());
18743 assert_breakpoint(
18744 &breakpoints,
18745 &abs_path,
18746 vec![
18747 (0, Breakpoint::new_standard()),
18748 (2, disable_breakpoint),
18749 (3, Breakpoint::new_standard()),
18750 ],
18751 );
18752}
18753
18754#[gpui::test]
18755async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
18756 init_test(cx, |_| {});
18757 let capabilities = lsp::ServerCapabilities {
18758 rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
18759 prepare_provider: Some(true),
18760 work_done_progress_options: Default::default(),
18761 })),
18762 ..Default::default()
18763 };
18764 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
18765
18766 cx.set_state(indoc! {"
18767 struct Fˇoo {}
18768 "});
18769
18770 cx.update_editor(|editor, _, cx| {
18771 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
18772 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
18773 editor.highlight_background::<DocumentHighlightRead>(
18774 &[highlight_range],
18775 |c| c.editor_document_highlight_read_background,
18776 cx,
18777 );
18778 });
18779
18780 let mut prepare_rename_handler = cx
18781 .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
18782 move |_, _, _| async move {
18783 Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
18784 start: lsp::Position {
18785 line: 0,
18786 character: 7,
18787 },
18788 end: lsp::Position {
18789 line: 0,
18790 character: 10,
18791 },
18792 })))
18793 },
18794 );
18795 let prepare_rename_task = cx
18796 .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
18797 .expect("Prepare rename was not started");
18798 prepare_rename_handler.next().await.unwrap();
18799 prepare_rename_task.await.expect("Prepare rename failed");
18800
18801 let mut rename_handler =
18802 cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
18803 let edit = lsp::TextEdit {
18804 range: lsp::Range {
18805 start: lsp::Position {
18806 line: 0,
18807 character: 7,
18808 },
18809 end: lsp::Position {
18810 line: 0,
18811 character: 10,
18812 },
18813 },
18814 new_text: "FooRenamed".to_string(),
18815 };
18816 Ok(Some(lsp::WorkspaceEdit::new(
18817 // Specify the same edit twice
18818 std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
18819 )))
18820 });
18821 let rename_task = cx
18822 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
18823 .expect("Confirm rename was not started");
18824 rename_handler.next().await.unwrap();
18825 rename_task.await.expect("Confirm rename failed");
18826 cx.run_until_parked();
18827
18828 // Despite two edits, only one is actually applied as those are identical
18829 cx.assert_editor_state(indoc! {"
18830 struct FooRenamedˇ {}
18831 "});
18832}
18833
18834#[gpui::test]
18835async fn test_rename_without_prepare(cx: &mut TestAppContext) {
18836 init_test(cx, |_| {});
18837 // These capabilities indicate that the server does not support prepare rename.
18838 let capabilities = lsp::ServerCapabilities {
18839 rename_provider: Some(lsp::OneOf::Left(true)),
18840 ..Default::default()
18841 };
18842 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
18843
18844 cx.set_state(indoc! {"
18845 struct Fˇoo {}
18846 "});
18847
18848 cx.update_editor(|editor, _window, cx| {
18849 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
18850 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
18851 editor.highlight_background::<DocumentHighlightRead>(
18852 &[highlight_range],
18853 |c| c.editor_document_highlight_read_background,
18854 cx,
18855 );
18856 });
18857
18858 cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
18859 .expect("Prepare rename was not started")
18860 .await
18861 .expect("Prepare rename failed");
18862
18863 let mut rename_handler =
18864 cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
18865 let edit = lsp::TextEdit {
18866 range: lsp::Range {
18867 start: lsp::Position {
18868 line: 0,
18869 character: 7,
18870 },
18871 end: lsp::Position {
18872 line: 0,
18873 character: 10,
18874 },
18875 },
18876 new_text: "FooRenamed".to_string(),
18877 };
18878 Ok(Some(lsp::WorkspaceEdit::new(
18879 std::collections::HashMap::from_iter(Some((url, vec![edit]))),
18880 )))
18881 });
18882 let rename_task = cx
18883 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
18884 .expect("Confirm rename was not started");
18885 rename_handler.next().await.unwrap();
18886 rename_task.await.expect("Confirm rename failed");
18887 cx.run_until_parked();
18888
18889 // Correct range is renamed, as `surrounding_word` is used to find it.
18890 cx.assert_editor_state(indoc! {"
18891 struct FooRenamedˇ {}
18892 "});
18893}
18894
18895#[gpui::test]
18896async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
18897 init_test(cx, |_| {});
18898 let mut cx = EditorTestContext::new(cx).await;
18899
18900 let language = Arc::new(
18901 Language::new(
18902 LanguageConfig::default(),
18903 Some(tree_sitter_html::LANGUAGE.into()),
18904 )
18905 .with_brackets_query(
18906 r#"
18907 ("<" @open "/>" @close)
18908 ("</" @open ">" @close)
18909 ("<" @open ">" @close)
18910 ("\"" @open "\"" @close)
18911 ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
18912 "#,
18913 )
18914 .unwrap(),
18915 );
18916 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
18917
18918 cx.set_state(indoc! {"
18919 <span>ˇ</span>
18920 "});
18921 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
18922 cx.assert_editor_state(indoc! {"
18923 <span>
18924 ˇ
18925 </span>
18926 "});
18927
18928 cx.set_state(indoc! {"
18929 <span><span></span>ˇ</span>
18930 "});
18931 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
18932 cx.assert_editor_state(indoc! {"
18933 <span><span></span>
18934 ˇ</span>
18935 "});
18936
18937 cx.set_state(indoc! {"
18938 <span>ˇ
18939 </span>
18940 "});
18941 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
18942 cx.assert_editor_state(indoc! {"
18943 <span>
18944 ˇ
18945 </span>
18946 "});
18947}
18948
18949#[gpui::test(iterations = 10)]
18950async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
18951 init_test(cx, |_| {});
18952
18953 let fs = FakeFs::new(cx.executor());
18954 fs.insert_tree(
18955 path!("/dir"),
18956 json!({
18957 "a.ts": "a",
18958 }),
18959 )
18960 .await;
18961
18962 let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
18963 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18964 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18965
18966 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18967 language_registry.add(Arc::new(Language::new(
18968 LanguageConfig {
18969 name: "TypeScript".into(),
18970 matcher: LanguageMatcher {
18971 path_suffixes: vec!["ts".to_string()],
18972 ..Default::default()
18973 },
18974 ..Default::default()
18975 },
18976 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
18977 )));
18978 let mut fake_language_servers = language_registry.register_fake_lsp(
18979 "TypeScript",
18980 FakeLspAdapter {
18981 capabilities: lsp::ServerCapabilities {
18982 code_lens_provider: Some(lsp::CodeLensOptions {
18983 resolve_provider: Some(true),
18984 }),
18985 execute_command_provider: Some(lsp::ExecuteCommandOptions {
18986 commands: vec!["_the/command".to_string()],
18987 ..lsp::ExecuteCommandOptions::default()
18988 }),
18989 ..lsp::ServerCapabilities::default()
18990 },
18991 ..FakeLspAdapter::default()
18992 },
18993 );
18994
18995 let (buffer, _handle) = project
18996 .update(cx, |p, cx| {
18997 p.open_local_buffer_with_lsp(path!("/dir/a.ts"), cx)
18998 })
18999 .await
19000 .unwrap();
19001 cx.executor().run_until_parked();
19002
19003 let fake_server = fake_language_servers.next().await.unwrap();
19004
19005 let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
19006 let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
19007 drop(buffer_snapshot);
19008 let actions = cx
19009 .update_window(*workspace, |_, window, cx| {
19010 project.code_actions(&buffer, anchor..anchor, window, cx)
19011 })
19012 .unwrap();
19013
19014 fake_server
19015 .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
19016 Ok(Some(vec![
19017 lsp::CodeLens {
19018 range: lsp::Range::default(),
19019 command: Some(lsp::Command {
19020 title: "Code lens command".to_owned(),
19021 command: "_the/command".to_owned(),
19022 arguments: None,
19023 }),
19024 data: None,
19025 },
19026 lsp::CodeLens {
19027 range: lsp::Range::default(),
19028 command: Some(lsp::Command {
19029 title: "Command not in capabilities".to_owned(),
19030 command: "not in capabilities".to_owned(),
19031 arguments: None,
19032 }),
19033 data: None,
19034 },
19035 lsp::CodeLens {
19036 range: lsp::Range {
19037 start: lsp::Position {
19038 line: 1,
19039 character: 1,
19040 },
19041 end: lsp::Position {
19042 line: 1,
19043 character: 1,
19044 },
19045 },
19046 command: Some(lsp::Command {
19047 title: "Command not in range".to_owned(),
19048 command: "_the/command".to_owned(),
19049 arguments: None,
19050 }),
19051 data: None,
19052 },
19053 ]))
19054 })
19055 .next()
19056 .await;
19057
19058 let actions = actions.await.unwrap();
19059 assert_eq!(
19060 actions.len(),
19061 1,
19062 "Should have only one valid action for the 0..0 range"
19063 );
19064 let action = actions[0].clone();
19065 let apply = project.update(cx, |project, cx| {
19066 project.apply_code_action(buffer.clone(), action, true, cx)
19067 });
19068
19069 // Resolving the code action does not populate its edits. In absence of
19070 // edits, we must execute the given command.
19071 fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
19072 |mut lens, _| async move {
19073 let lens_command = lens.command.as_mut().expect("should have a command");
19074 assert_eq!(lens_command.title, "Code lens command");
19075 lens_command.arguments = Some(vec![json!("the-argument")]);
19076 Ok(lens)
19077 },
19078 );
19079
19080 // While executing the command, the language server sends the editor
19081 // a `workspaceEdit` request.
19082 fake_server
19083 .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
19084 let fake = fake_server.clone();
19085 move |params, _| {
19086 assert_eq!(params.command, "_the/command");
19087 let fake = fake.clone();
19088 async move {
19089 fake.server
19090 .request::<lsp::request::ApplyWorkspaceEdit>(
19091 lsp::ApplyWorkspaceEditParams {
19092 label: None,
19093 edit: lsp::WorkspaceEdit {
19094 changes: Some(
19095 [(
19096 lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
19097 vec![lsp::TextEdit {
19098 range: lsp::Range::new(
19099 lsp::Position::new(0, 0),
19100 lsp::Position::new(0, 0),
19101 ),
19102 new_text: "X".into(),
19103 }],
19104 )]
19105 .into_iter()
19106 .collect(),
19107 ),
19108 ..Default::default()
19109 },
19110 },
19111 )
19112 .await
19113 .unwrap();
19114 Ok(Some(json!(null)))
19115 }
19116 }
19117 })
19118 .next()
19119 .await;
19120
19121 // Applying the code lens command returns a project transaction containing the edits
19122 // sent by the language server in its `workspaceEdit` request.
19123 let transaction = apply.await.unwrap();
19124 assert!(transaction.0.contains_key(&buffer));
19125 buffer.update(cx, |buffer, cx| {
19126 assert_eq!(buffer.text(), "Xa");
19127 buffer.undo(cx);
19128 assert_eq!(buffer.text(), "a");
19129 });
19130}
19131
19132#[gpui::test]
19133async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
19134 init_test(cx, |_| {});
19135
19136 let fs = FakeFs::new(cx.executor());
19137 let main_text = r#"fn main() {
19138println!("1");
19139println!("2");
19140println!("3");
19141println!("4");
19142println!("5");
19143}"#;
19144 let lib_text = "mod foo {}";
19145 fs.insert_tree(
19146 path!("/a"),
19147 json!({
19148 "lib.rs": lib_text,
19149 "main.rs": main_text,
19150 }),
19151 )
19152 .await;
19153
19154 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19155 let (workspace, cx) =
19156 cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
19157 let worktree_id = workspace.update(cx, |workspace, cx| {
19158 workspace.project().update(cx, |project, cx| {
19159 project.worktrees(cx).next().unwrap().read(cx).id()
19160 })
19161 });
19162
19163 let expected_ranges = vec![
19164 Point::new(0, 0)..Point::new(0, 0),
19165 Point::new(1, 0)..Point::new(1, 1),
19166 Point::new(2, 0)..Point::new(2, 2),
19167 Point::new(3, 0)..Point::new(3, 3),
19168 ];
19169
19170 let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
19171 let editor_1 = workspace
19172 .update_in(cx, |workspace, window, cx| {
19173 workspace.open_path(
19174 (worktree_id, "main.rs"),
19175 Some(pane_1.downgrade()),
19176 true,
19177 window,
19178 cx,
19179 )
19180 })
19181 .unwrap()
19182 .await
19183 .downcast::<Editor>()
19184 .unwrap();
19185 pane_1.update(cx, |pane, cx| {
19186 let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19187 open_editor.update(cx, |editor, cx| {
19188 assert_eq!(
19189 editor.display_text(cx),
19190 main_text,
19191 "Original main.rs text on initial open",
19192 );
19193 assert_eq!(
19194 editor
19195 .selections
19196 .all::<Point>(cx)
19197 .into_iter()
19198 .map(|s| s.range())
19199 .collect::<Vec<_>>(),
19200 vec![Point::zero()..Point::zero()],
19201 "Default selections on initial open",
19202 );
19203 })
19204 });
19205 editor_1.update_in(cx, |editor, window, cx| {
19206 editor.change_selections(None, window, cx, |s| {
19207 s.select_ranges(expected_ranges.clone());
19208 });
19209 });
19210
19211 let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
19212 workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
19213 });
19214 let editor_2 = workspace
19215 .update_in(cx, |workspace, window, cx| {
19216 workspace.open_path(
19217 (worktree_id, "main.rs"),
19218 Some(pane_2.downgrade()),
19219 true,
19220 window,
19221 cx,
19222 )
19223 })
19224 .unwrap()
19225 .await
19226 .downcast::<Editor>()
19227 .unwrap();
19228 pane_2.update(cx, |pane, cx| {
19229 let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19230 open_editor.update(cx, |editor, cx| {
19231 assert_eq!(
19232 editor.display_text(cx),
19233 main_text,
19234 "Original main.rs text on initial open in another panel",
19235 );
19236 assert_eq!(
19237 editor
19238 .selections
19239 .all::<Point>(cx)
19240 .into_iter()
19241 .map(|s| s.range())
19242 .collect::<Vec<_>>(),
19243 vec![Point::zero()..Point::zero()],
19244 "Default selections on initial open in another panel",
19245 );
19246 })
19247 });
19248
19249 editor_2.update_in(cx, |editor, window, cx| {
19250 editor.fold_ranges(expected_ranges.clone(), false, window, cx);
19251 });
19252
19253 let _other_editor_1 = workspace
19254 .update_in(cx, |workspace, window, cx| {
19255 workspace.open_path(
19256 (worktree_id, "lib.rs"),
19257 Some(pane_1.downgrade()),
19258 true,
19259 window,
19260 cx,
19261 )
19262 })
19263 .unwrap()
19264 .await
19265 .downcast::<Editor>()
19266 .unwrap();
19267 pane_1
19268 .update_in(cx, |pane, window, cx| {
19269 pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
19270 .unwrap()
19271 })
19272 .await
19273 .unwrap();
19274 drop(editor_1);
19275 pane_1.update(cx, |pane, cx| {
19276 pane.active_item()
19277 .unwrap()
19278 .downcast::<Editor>()
19279 .unwrap()
19280 .update(cx, |editor, cx| {
19281 assert_eq!(
19282 editor.display_text(cx),
19283 lib_text,
19284 "Other file should be open and active",
19285 );
19286 });
19287 assert_eq!(pane.items().count(), 1, "No other editors should be open");
19288 });
19289
19290 let _other_editor_2 = workspace
19291 .update_in(cx, |workspace, window, cx| {
19292 workspace.open_path(
19293 (worktree_id, "lib.rs"),
19294 Some(pane_2.downgrade()),
19295 true,
19296 window,
19297 cx,
19298 )
19299 })
19300 .unwrap()
19301 .await
19302 .downcast::<Editor>()
19303 .unwrap();
19304 pane_2
19305 .update_in(cx, |pane, window, cx| {
19306 pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
19307 .unwrap()
19308 })
19309 .await
19310 .unwrap();
19311 drop(editor_2);
19312 pane_2.update(cx, |pane, cx| {
19313 let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19314 open_editor.update(cx, |editor, cx| {
19315 assert_eq!(
19316 editor.display_text(cx),
19317 lib_text,
19318 "Other file should be open and active in another panel too",
19319 );
19320 });
19321 assert_eq!(
19322 pane.items().count(),
19323 1,
19324 "No other editors should be open in another pane",
19325 );
19326 });
19327
19328 let _editor_1_reopened = workspace
19329 .update_in(cx, |workspace, window, cx| {
19330 workspace.open_path(
19331 (worktree_id, "main.rs"),
19332 Some(pane_1.downgrade()),
19333 true,
19334 window,
19335 cx,
19336 )
19337 })
19338 .unwrap()
19339 .await
19340 .downcast::<Editor>()
19341 .unwrap();
19342 let _editor_2_reopened = workspace
19343 .update_in(cx, |workspace, window, cx| {
19344 workspace.open_path(
19345 (worktree_id, "main.rs"),
19346 Some(pane_2.downgrade()),
19347 true,
19348 window,
19349 cx,
19350 )
19351 })
19352 .unwrap()
19353 .await
19354 .downcast::<Editor>()
19355 .unwrap();
19356 pane_1.update(cx, |pane, cx| {
19357 let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19358 open_editor.update(cx, |editor, cx| {
19359 assert_eq!(
19360 editor.display_text(cx),
19361 main_text,
19362 "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
19363 );
19364 assert_eq!(
19365 editor
19366 .selections
19367 .all::<Point>(cx)
19368 .into_iter()
19369 .map(|s| s.range())
19370 .collect::<Vec<_>>(),
19371 expected_ranges,
19372 "Previous editor in the 1st panel had selections and should get them restored on reopen",
19373 );
19374 })
19375 });
19376 pane_2.update(cx, |pane, cx| {
19377 let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19378 open_editor.update(cx, |editor, cx| {
19379 assert_eq!(
19380 editor.display_text(cx),
19381 r#"fn main() {
19382⋯rintln!("1");
19383⋯intln!("2");
19384⋯ntln!("3");
19385println!("4");
19386println!("5");
19387}"#,
19388 "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
19389 );
19390 assert_eq!(
19391 editor
19392 .selections
19393 .all::<Point>(cx)
19394 .into_iter()
19395 .map(|s| s.range())
19396 .collect::<Vec<_>>(),
19397 vec![Point::zero()..Point::zero()],
19398 "Previous editor in the 2nd pane had no selections changed hence should restore none",
19399 );
19400 })
19401 });
19402}
19403
19404#[gpui::test]
19405async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
19406 init_test(cx, |_| {});
19407
19408 let fs = FakeFs::new(cx.executor());
19409 let main_text = r#"fn main() {
19410println!("1");
19411println!("2");
19412println!("3");
19413println!("4");
19414println!("5");
19415}"#;
19416 let lib_text = "mod foo {}";
19417 fs.insert_tree(
19418 path!("/a"),
19419 json!({
19420 "lib.rs": lib_text,
19421 "main.rs": main_text,
19422 }),
19423 )
19424 .await;
19425
19426 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19427 let (workspace, cx) =
19428 cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
19429 let worktree_id = workspace.update(cx, |workspace, cx| {
19430 workspace.project().update(cx, |project, cx| {
19431 project.worktrees(cx).next().unwrap().read(cx).id()
19432 })
19433 });
19434
19435 let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
19436 let editor = workspace
19437 .update_in(cx, |workspace, window, cx| {
19438 workspace.open_path(
19439 (worktree_id, "main.rs"),
19440 Some(pane.downgrade()),
19441 true,
19442 window,
19443 cx,
19444 )
19445 })
19446 .unwrap()
19447 .await
19448 .downcast::<Editor>()
19449 .unwrap();
19450 pane.update(cx, |pane, cx| {
19451 let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19452 open_editor.update(cx, |editor, cx| {
19453 assert_eq!(
19454 editor.display_text(cx),
19455 main_text,
19456 "Original main.rs text on initial open",
19457 );
19458 })
19459 });
19460 editor.update_in(cx, |editor, window, cx| {
19461 editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
19462 });
19463
19464 cx.update_global(|store: &mut SettingsStore, cx| {
19465 store.update_user_settings::<WorkspaceSettings>(cx, |s| {
19466 s.restore_on_file_reopen = Some(false);
19467 });
19468 });
19469 editor.update_in(cx, |editor, window, cx| {
19470 editor.fold_ranges(
19471 vec![
19472 Point::new(1, 0)..Point::new(1, 1),
19473 Point::new(2, 0)..Point::new(2, 2),
19474 Point::new(3, 0)..Point::new(3, 3),
19475 ],
19476 false,
19477 window,
19478 cx,
19479 );
19480 });
19481 pane.update_in(cx, |pane, window, cx| {
19482 pane.close_all_items(&CloseAllItems::default(), window, cx)
19483 .unwrap()
19484 })
19485 .await
19486 .unwrap();
19487 pane.update(cx, |pane, _| {
19488 assert!(pane.active_item().is_none());
19489 });
19490 cx.update_global(|store: &mut SettingsStore, cx| {
19491 store.update_user_settings::<WorkspaceSettings>(cx, |s| {
19492 s.restore_on_file_reopen = Some(true);
19493 });
19494 });
19495
19496 let _editor_reopened = workspace
19497 .update_in(cx, |workspace, window, cx| {
19498 workspace.open_path(
19499 (worktree_id, "main.rs"),
19500 Some(pane.downgrade()),
19501 true,
19502 window,
19503 cx,
19504 )
19505 })
19506 .unwrap()
19507 .await
19508 .downcast::<Editor>()
19509 .unwrap();
19510 pane.update(cx, |pane, cx| {
19511 let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19512 open_editor.update(cx, |editor, cx| {
19513 assert_eq!(
19514 editor.display_text(cx),
19515 main_text,
19516 "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
19517 );
19518 })
19519 });
19520}
19521
19522fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
19523 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
19524 point..point
19525}
19526
19527fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
19528 let (text, ranges) = marked_text_ranges(marked_text, true);
19529 assert_eq!(editor.text(cx), text);
19530 assert_eq!(
19531 editor.selections.ranges(cx),
19532 ranges,
19533 "Assert selections are {}",
19534 marked_text
19535 );
19536}
19537
19538pub fn handle_signature_help_request(
19539 cx: &mut EditorLspTestContext,
19540 mocked_response: lsp::SignatureHelp,
19541) -> impl Future<Output = ()> + use<> {
19542 let mut request =
19543 cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
19544 let mocked_response = mocked_response.clone();
19545 async move { Ok(Some(mocked_response)) }
19546 });
19547
19548 async move {
19549 request.next().await;
19550 }
19551}
19552
19553/// Handle completion request passing a marked string specifying where the completion
19554/// should be triggered from using '|' character, what range should be replaced, and what completions
19555/// should be returned using '<' and '>' to delimit the range.
19556///
19557/// Also see `handle_completion_request_with_insert_and_replace`.
19558#[track_caller]
19559pub fn handle_completion_request(
19560 cx: &mut EditorLspTestContext,
19561 marked_string: &str,
19562 completions: Vec<&'static str>,
19563 counter: Arc<AtomicUsize>,
19564) -> impl Future<Output = ()> {
19565 let complete_from_marker: TextRangeMarker = '|'.into();
19566 let replace_range_marker: TextRangeMarker = ('<', '>').into();
19567 let (_, mut marked_ranges) = marked_text_ranges_by(
19568 marked_string,
19569 vec![complete_from_marker.clone(), replace_range_marker.clone()],
19570 );
19571
19572 let complete_from_position =
19573 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
19574 let replace_range =
19575 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
19576
19577 let mut request =
19578 cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
19579 let completions = completions.clone();
19580 counter.fetch_add(1, atomic::Ordering::Release);
19581 async move {
19582 assert_eq!(params.text_document_position.text_document.uri, url.clone());
19583 assert_eq!(
19584 params.text_document_position.position,
19585 complete_from_position
19586 );
19587 Ok(Some(lsp::CompletionResponse::Array(
19588 completions
19589 .iter()
19590 .map(|completion_text| lsp::CompletionItem {
19591 label: completion_text.to_string(),
19592 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
19593 range: replace_range,
19594 new_text: completion_text.to_string(),
19595 })),
19596 ..Default::default()
19597 })
19598 .collect(),
19599 )))
19600 }
19601 });
19602
19603 async move {
19604 request.next().await;
19605 }
19606}
19607
19608/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
19609/// given instead, which also contains an `insert` range.
19610///
19611/// This function uses the cursor position to mimic what Rust-Analyzer provides as the `insert` range,
19612/// that is, `replace_range.start..cursor_pos`.
19613pub fn handle_completion_request_with_insert_and_replace(
19614 cx: &mut EditorLspTestContext,
19615 marked_string: &str,
19616 completions: Vec<&'static str>,
19617 counter: Arc<AtomicUsize>,
19618) -> impl Future<Output = ()> {
19619 let complete_from_marker: TextRangeMarker = '|'.into();
19620 let replace_range_marker: TextRangeMarker = ('<', '>').into();
19621 let (_, mut marked_ranges) = marked_text_ranges_by(
19622 marked_string,
19623 vec![complete_from_marker.clone(), replace_range_marker.clone()],
19624 );
19625
19626 let complete_from_position =
19627 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
19628 let replace_range =
19629 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
19630
19631 let mut request =
19632 cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
19633 let completions = completions.clone();
19634 counter.fetch_add(1, atomic::Ordering::Release);
19635 async move {
19636 assert_eq!(params.text_document_position.text_document.uri, url.clone());
19637 assert_eq!(
19638 params.text_document_position.position, complete_from_position,
19639 "marker `|` position doesn't match",
19640 );
19641 Ok(Some(lsp::CompletionResponse::Array(
19642 completions
19643 .iter()
19644 .map(|completion_text| lsp::CompletionItem {
19645 label: completion_text.to_string(),
19646 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
19647 lsp::InsertReplaceEdit {
19648 insert: lsp::Range {
19649 start: replace_range.start,
19650 end: complete_from_position,
19651 },
19652 replace: replace_range,
19653 new_text: completion_text.to_string(),
19654 },
19655 )),
19656 ..Default::default()
19657 })
19658 .collect(),
19659 )))
19660 }
19661 });
19662
19663 async move {
19664 request.next().await;
19665 }
19666}
19667
19668fn handle_resolve_completion_request(
19669 cx: &mut EditorLspTestContext,
19670 edits: Option<Vec<(&'static str, &'static str)>>,
19671) -> impl Future<Output = ()> {
19672 let edits = edits.map(|edits| {
19673 edits
19674 .iter()
19675 .map(|(marked_string, new_text)| {
19676 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
19677 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
19678 lsp::TextEdit::new(replace_range, new_text.to_string())
19679 })
19680 .collect::<Vec<_>>()
19681 });
19682
19683 let mut request =
19684 cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
19685 let edits = edits.clone();
19686 async move {
19687 Ok(lsp::CompletionItem {
19688 additional_text_edits: edits,
19689 ..Default::default()
19690 })
19691 }
19692 });
19693
19694 async move {
19695 request.next().await;
19696 }
19697}
19698
19699pub(crate) fn update_test_language_settings(
19700 cx: &mut TestAppContext,
19701 f: impl Fn(&mut AllLanguageSettingsContent),
19702) {
19703 cx.update(|cx| {
19704 SettingsStore::update_global(cx, |store, cx| {
19705 store.update_user_settings::<AllLanguageSettings>(cx, f);
19706 });
19707 });
19708}
19709
19710pub(crate) fn update_test_project_settings(
19711 cx: &mut TestAppContext,
19712 f: impl Fn(&mut ProjectSettings),
19713) {
19714 cx.update(|cx| {
19715 SettingsStore::update_global(cx, |store, cx| {
19716 store.update_user_settings::<ProjectSettings>(cx, f);
19717 });
19718 });
19719}
19720
19721pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
19722 cx.update(|cx| {
19723 assets::Assets.load_test_fonts(cx);
19724 let store = SettingsStore::test(cx);
19725 cx.set_global(store);
19726 theme::init(theme::LoadThemes::JustBase, cx);
19727 release_channel::init(SemanticVersion::default(), cx);
19728 client::init_settings(cx);
19729 language::init(cx);
19730 Project::init_settings(cx);
19731 workspace::init_settings(cx);
19732 crate::init(cx);
19733 });
19734
19735 update_test_language_settings(cx, f);
19736}
19737
19738#[track_caller]
19739fn assert_hunk_revert(
19740 not_reverted_text_with_selections: &str,
19741 expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
19742 expected_reverted_text_with_selections: &str,
19743 base_text: &str,
19744 cx: &mut EditorLspTestContext,
19745) {
19746 cx.set_state(not_reverted_text_with_selections);
19747 cx.set_head_text(base_text);
19748 cx.executor().run_until_parked();
19749
19750 let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
19751 let snapshot = editor.snapshot(window, cx);
19752 let reverted_hunk_statuses = snapshot
19753 .buffer_snapshot
19754 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
19755 .map(|hunk| hunk.status().kind)
19756 .collect::<Vec<_>>();
19757
19758 editor.git_restore(&Default::default(), window, cx);
19759 reverted_hunk_statuses
19760 });
19761 cx.executor().run_until_parked();
19762 cx.assert_editor_state(expected_reverted_text_with_selections);
19763 assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
19764}