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// TODO: Re-enable this test
1320#[cfg(target_os = "macos")]
1321#[gpui::test]
1322fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
1323 init_test(cx, |_| {});
1324
1325 let editor = cx.add_window(|window, cx| {
1326 let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
1327 build_editor(buffer.clone(), window, cx)
1328 });
1329
1330 assert_eq!('🟥'.len_utf8(), 4);
1331 assert_eq!('α'.len_utf8(), 2);
1332
1333 _ = editor.update(cx, |editor, window, cx| {
1334 editor.fold_creases(
1335 vec![
1336 Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
1337 Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
1338 Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
1339 ],
1340 true,
1341 window,
1342 cx,
1343 );
1344 assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
1345
1346 editor.move_right(&MoveRight, window, cx);
1347 assert_eq!(
1348 editor.selections.display_ranges(cx),
1349 &[empty_range(0, "🟥".len())]
1350 );
1351 editor.move_right(&MoveRight, window, cx);
1352 assert_eq!(
1353 editor.selections.display_ranges(cx),
1354 &[empty_range(0, "🟥🟧".len())]
1355 );
1356 editor.move_right(&MoveRight, window, cx);
1357 assert_eq!(
1358 editor.selections.display_ranges(cx),
1359 &[empty_range(0, "🟥🟧⋯".len())]
1360 );
1361
1362 editor.move_down(&MoveDown, window, cx);
1363 assert_eq!(
1364 editor.selections.display_ranges(cx),
1365 &[empty_range(1, "ab⋯e".len())]
1366 );
1367 editor.move_left(&MoveLeft, window, cx);
1368 assert_eq!(
1369 editor.selections.display_ranges(cx),
1370 &[empty_range(1, "ab⋯".len())]
1371 );
1372 editor.move_left(&MoveLeft, window, cx);
1373 assert_eq!(
1374 editor.selections.display_ranges(cx),
1375 &[empty_range(1, "ab".len())]
1376 );
1377 editor.move_left(&MoveLeft, window, cx);
1378 assert_eq!(
1379 editor.selections.display_ranges(cx),
1380 &[empty_range(1, "a".len())]
1381 );
1382
1383 editor.move_down(&MoveDown, window, cx);
1384 assert_eq!(
1385 editor.selections.display_ranges(cx),
1386 &[empty_range(2, "α".len())]
1387 );
1388 editor.move_right(&MoveRight, window, cx);
1389 assert_eq!(
1390 editor.selections.display_ranges(cx),
1391 &[empty_range(2, "αβ".len())]
1392 );
1393 editor.move_right(&MoveRight, window, cx);
1394 assert_eq!(
1395 editor.selections.display_ranges(cx),
1396 &[empty_range(2, "αβ⋯".len())]
1397 );
1398 editor.move_right(&MoveRight, window, cx);
1399 assert_eq!(
1400 editor.selections.display_ranges(cx),
1401 &[empty_range(2, "αβ⋯ε".len())]
1402 );
1403
1404 editor.move_up(&MoveUp, window, cx);
1405 assert_eq!(
1406 editor.selections.display_ranges(cx),
1407 &[empty_range(1, "ab⋯e".len())]
1408 );
1409 editor.move_down(&MoveDown, window, cx);
1410 assert_eq!(
1411 editor.selections.display_ranges(cx),
1412 &[empty_range(2, "αβ⋯ε".len())]
1413 );
1414 editor.move_up(&MoveUp, window, cx);
1415 assert_eq!(
1416 editor.selections.display_ranges(cx),
1417 &[empty_range(1, "ab⋯e".len())]
1418 );
1419
1420 editor.move_up(&MoveUp, window, cx);
1421 assert_eq!(
1422 editor.selections.display_ranges(cx),
1423 &[empty_range(0, "🟥🟧".len())]
1424 );
1425 editor.move_left(&MoveLeft, window, cx);
1426 assert_eq!(
1427 editor.selections.display_ranges(cx),
1428 &[empty_range(0, "🟥".len())]
1429 );
1430 editor.move_left(&MoveLeft, window, cx);
1431 assert_eq!(
1432 editor.selections.display_ranges(cx),
1433 &[empty_range(0, "".len())]
1434 );
1435 });
1436}
1437
1438#[gpui::test]
1439fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
1440 init_test(cx, |_| {});
1441
1442 let editor = cx.add_window(|window, cx| {
1443 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
1444 build_editor(buffer.clone(), window, cx)
1445 });
1446 _ = editor.update(cx, |editor, window, cx| {
1447 editor.change_selections(None, window, cx, |s| {
1448 s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
1449 });
1450
1451 // moving above start of document should move selection to start of document,
1452 // but the next move down should still be at the original goal_x
1453 editor.move_up(&MoveUp, window, cx);
1454 assert_eq!(
1455 editor.selections.display_ranges(cx),
1456 &[empty_range(0, "".len())]
1457 );
1458
1459 editor.move_down(&MoveDown, window, cx);
1460 assert_eq!(
1461 editor.selections.display_ranges(cx),
1462 &[empty_range(1, "abcd".len())]
1463 );
1464
1465 editor.move_down(&MoveDown, window, cx);
1466 assert_eq!(
1467 editor.selections.display_ranges(cx),
1468 &[empty_range(2, "αβγ".len())]
1469 );
1470
1471 editor.move_down(&MoveDown, window, cx);
1472 assert_eq!(
1473 editor.selections.display_ranges(cx),
1474 &[empty_range(3, "abcd".len())]
1475 );
1476
1477 editor.move_down(&MoveDown, window, cx);
1478 assert_eq!(
1479 editor.selections.display_ranges(cx),
1480 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1481 );
1482
1483 // moving past end of document should not change goal_x
1484 editor.move_down(&MoveDown, window, cx);
1485 assert_eq!(
1486 editor.selections.display_ranges(cx),
1487 &[empty_range(5, "".len())]
1488 );
1489
1490 editor.move_down(&MoveDown, window, cx);
1491 assert_eq!(
1492 editor.selections.display_ranges(cx),
1493 &[empty_range(5, "".len())]
1494 );
1495
1496 editor.move_up(&MoveUp, window, cx);
1497 assert_eq!(
1498 editor.selections.display_ranges(cx),
1499 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1500 );
1501
1502 editor.move_up(&MoveUp, window, cx);
1503 assert_eq!(
1504 editor.selections.display_ranges(cx),
1505 &[empty_range(3, "abcd".len())]
1506 );
1507
1508 editor.move_up(&MoveUp, window, cx);
1509 assert_eq!(
1510 editor.selections.display_ranges(cx),
1511 &[empty_range(2, "αβγ".len())]
1512 );
1513 });
1514}
1515
1516#[gpui::test]
1517fn test_beginning_end_of_line(cx: &mut TestAppContext) {
1518 init_test(cx, |_| {});
1519 let move_to_beg = MoveToBeginningOfLine {
1520 stop_at_soft_wraps: true,
1521 stop_at_indent: true,
1522 };
1523
1524 let delete_to_beg = DeleteToBeginningOfLine {
1525 stop_at_indent: false,
1526 };
1527
1528 let move_to_end = MoveToEndOfLine {
1529 stop_at_soft_wraps: true,
1530 };
1531
1532 let editor = cx.add_window(|window, cx| {
1533 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1534 build_editor(buffer, window, cx)
1535 });
1536 _ = editor.update(cx, |editor, window, cx| {
1537 editor.change_selections(None, window, cx, |s| {
1538 s.select_display_ranges([
1539 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1540 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1541 ]);
1542 });
1543 });
1544
1545 _ = editor.update(cx, |editor, window, cx| {
1546 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1547 assert_eq!(
1548 editor.selections.display_ranges(cx),
1549 &[
1550 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1551 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1552 ]
1553 );
1554 });
1555
1556 _ = editor.update(cx, |editor, window, cx| {
1557 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1558 assert_eq!(
1559 editor.selections.display_ranges(cx),
1560 &[
1561 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1562 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1563 ]
1564 );
1565 });
1566
1567 _ = editor.update(cx, |editor, window, cx| {
1568 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1569 assert_eq!(
1570 editor.selections.display_ranges(cx),
1571 &[
1572 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1573 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1574 ]
1575 );
1576 });
1577
1578 _ = editor.update(cx, |editor, window, cx| {
1579 editor.move_to_end_of_line(&move_to_end, window, cx);
1580 assert_eq!(
1581 editor.selections.display_ranges(cx),
1582 &[
1583 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1584 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1585 ]
1586 );
1587 });
1588
1589 // Moving to the end of line again is a no-op.
1590 _ = editor.update(cx, |editor, window, cx| {
1591 editor.move_to_end_of_line(&move_to_end, window, cx);
1592 assert_eq!(
1593 editor.selections.display_ranges(cx),
1594 &[
1595 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1596 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1597 ]
1598 );
1599 });
1600
1601 _ = editor.update(cx, |editor, window, cx| {
1602 editor.move_left(&MoveLeft, window, cx);
1603 editor.select_to_beginning_of_line(
1604 &SelectToBeginningOfLine {
1605 stop_at_soft_wraps: true,
1606 stop_at_indent: true,
1607 },
1608 window,
1609 cx,
1610 );
1611 assert_eq!(
1612 editor.selections.display_ranges(cx),
1613 &[
1614 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1615 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1616 ]
1617 );
1618 });
1619
1620 _ = editor.update(cx, |editor, window, cx| {
1621 editor.select_to_beginning_of_line(
1622 &SelectToBeginningOfLine {
1623 stop_at_soft_wraps: true,
1624 stop_at_indent: true,
1625 },
1626 window,
1627 cx,
1628 );
1629 assert_eq!(
1630 editor.selections.display_ranges(cx),
1631 &[
1632 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1633 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1634 ]
1635 );
1636 });
1637
1638 _ = editor.update(cx, |editor, window, cx| {
1639 editor.select_to_beginning_of_line(
1640 &SelectToBeginningOfLine {
1641 stop_at_soft_wraps: true,
1642 stop_at_indent: true,
1643 },
1644 window,
1645 cx,
1646 );
1647 assert_eq!(
1648 editor.selections.display_ranges(cx),
1649 &[
1650 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1651 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1652 ]
1653 );
1654 });
1655
1656 _ = editor.update(cx, |editor, window, cx| {
1657 editor.select_to_end_of_line(
1658 &SelectToEndOfLine {
1659 stop_at_soft_wraps: true,
1660 },
1661 window,
1662 cx,
1663 );
1664 assert_eq!(
1665 editor.selections.display_ranges(cx),
1666 &[
1667 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
1668 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
1669 ]
1670 );
1671 });
1672
1673 _ = editor.update(cx, |editor, window, cx| {
1674 editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
1675 assert_eq!(editor.display_text(cx), "ab\n de");
1676 assert_eq!(
1677 editor.selections.display_ranges(cx),
1678 &[
1679 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
1680 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1681 ]
1682 );
1683 });
1684
1685 _ = editor.update(cx, |editor, window, cx| {
1686 editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
1687 assert_eq!(editor.display_text(cx), "\n");
1688 assert_eq!(
1689 editor.selections.display_ranges(cx),
1690 &[
1691 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1692 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1693 ]
1694 );
1695 });
1696}
1697
1698#[gpui::test]
1699fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
1700 init_test(cx, |_| {});
1701 let move_to_beg = MoveToBeginningOfLine {
1702 stop_at_soft_wraps: false,
1703 stop_at_indent: false,
1704 };
1705
1706 let move_to_end = MoveToEndOfLine {
1707 stop_at_soft_wraps: false,
1708 };
1709
1710 let editor = cx.add_window(|window, cx| {
1711 let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
1712 build_editor(buffer, window, cx)
1713 });
1714
1715 _ = editor.update(cx, |editor, window, cx| {
1716 editor.set_wrap_width(Some(140.0.into()), cx);
1717
1718 // We expect the following lines after wrapping
1719 // ```
1720 // thequickbrownfox
1721 // jumpedoverthelazydo
1722 // gs
1723 // ```
1724 // The final `gs` was soft-wrapped onto a new line.
1725 assert_eq!(
1726 "thequickbrownfox\njumpedoverthelaz\nydogs",
1727 editor.display_text(cx),
1728 );
1729
1730 // First, let's assert behavior on the first line, that was not soft-wrapped.
1731 // Start the cursor at the `k` on the first line
1732 editor.change_selections(None, window, cx, |s| {
1733 s.select_display_ranges([
1734 DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
1735 ]);
1736 });
1737
1738 // Moving to the beginning of the line should put us at the beginning of the line.
1739 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1740 assert_eq!(
1741 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
1742 editor.selections.display_ranges(cx)
1743 );
1744
1745 // Moving to the end of the line should put us at the end of the line.
1746 editor.move_to_end_of_line(&move_to_end, window, cx);
1747 assert_eq!(
1748 vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
1749 editor.selections.display_ranges(cx)
1750 );
1751
1752 // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
1753 // Start the cursor at the last line (`y` that was wrapped to a new line)
1754 editor.change_selections(None, window, cx, |s| {
1755 s.select_display_ranges([
1756 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
1757 ]);
1758 });
1759
1760 // Moving to the beginning of the line should put us at the start of the second line of
1761 // display text, i.e., the `j`.
1762 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1763 assert_eq!(
1764 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1765 editor.selections.display_ranges(cx)
1766 );
1767
1768 // Moving to the beginning of the line again should be a no-op.
1769 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1770 assert_eq!(
1771 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1772 editor.selections.display_ranges(cx)
1773 );
1774
1775 // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
1776 // next display line.
1777 editor.move_to_end_of_line(&move_to_end, window, cx);
1778 assert_eq!(
1779 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1780 editor.selections.display_ranges(cx)
1781 );
1782
1783 // Moving to the end of the line again should be a no-op.
1784 editor.move_to_end_of_line(&move_to_end, window, cx);
1785 assert_eq!(
1786 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1787 editor.selections.display_ranges(cx)
1788 );
1789 });
1790}
1791
1792#[gpui::test]
1793fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
1794 init_test(cx, |_| {});
1795
1796 let move_to_beg = MoveToBeginningOfLine {
1797 stop_at_soft_wraps: true,
1798 stop_at_indent: true,
1799 };
1800
1801 let select_to_beg = SelectToBeginningOfLine {
1802 stop_at_soft_wraps: true,
1803 stop_at_indent: true,
1804 };
1805
1806 let delete_to_beg = DeleteToBeginningOfLine {
1807 stop_at_indent: true,
1808 };
1809
1810 let move_to_end = MoveToEndOfLine {
1811 stop_at_soft_wraps: false,
1812 };
1813
1814 let editor = cx.add_window(|window, cx| {
1815 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1816 build_editor(buffer, window, cx)
1817 });
1818
1819 _ = editor.update(cx, |editor, window, cx| {
1820 editor.change_selections(None, window, cx, |s| {
1821 s.select_display_ranges([
1822 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1823 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1824 ]);
1825 });
1826
1827 // Moving to the beginning of the line should put the first cursor at the beginning of the line,
1828 // and the second cursor at the first non-whitespace character in the line.
1829 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1830 assert_eq!(
1831 editor.selections.display_ranges(cx),
1832 &[
1833 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1834 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1835 ]
1836 );
1837
1838 // Moving to the beginning of the line again should be a no-op for the first cursor,
1839 // and should move the second cursor to the beginning of the line.
1840 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1841 assert_eq!(
1842 editor.selections.display_ranges(cx),
1843 &[
1844 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1845 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1846 ]
1847 );
1848
1849 // Moving to the beginning of the line again should still be a no-op for the first cursor,
1850 // and should move the second cursor back to the first non-whitespace character in the line.
1851 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1852 assert_eq!(
1853 editor.selections.display_ranges(cx),
1854 &[
1855 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1856 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1857 ]
1858 );
1859
1860 // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
1861 // and to the first non-whitespace character in the line for the second cursor.
1862 editor.move_to_end_of_line(&move_to_end, window, cx);
1863 editor.move_left(&MoveLeft, window, cx);
1864 editor.select_to_beginning_of_line(&select_to_beg, window, cx);
1865 assert_eq!(
1866 editor.selections.display_ranges(cx),
1867 &[
1868 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1869 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1870 ]
1871 );
1872
1873 // Selecting to the beginning of the line again should be a no-op for the first cursor,
1874 // and should select to the beginning of the line for the second cursor.
1875 editor.select_to_beginning_of_line(&select_to_beg, window, cx);
1876 assert_eq!(
1877 editor.selections.display_ranges(cx),
1878 &[
1879 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1880 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1881 ]
1882 );
1883
1884 // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
1885 // and should delete to the first non-whitespace character in the line for the second cursor.
1886 editor.move_to_end_of_line(&move_to_end, window, cx);
1887 editor.move_left(&MoveLeft, window, cx);
1888 editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
1889 assert_eq!(editor.text(cx), "c\n f");
1890 });
1891}
1892
1893#[gpui::test]
1894fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1895 init_test(cx, |_| {});
1896
1897 let editor = cx.add_window(|window, cx| {
1898 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1899 build_editor(buffer, window, cx)
1900 });
1901 _ = editor.update(cx, |editor, window, cx| {
1902 editor.change_selections(None, window, cx, |s| {
1903 s.select_display_ranges([
1904 DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
1905 DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
1906 ])
1907 });
1908
1909 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1910 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", editor, cx);
1911
1912 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1913 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", editor, cx);
1914
1915 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1916 assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", editor, cx);
1917
1918 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1919 assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", editor, cx);
1920
1921 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1922 assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", editor, cx);
1923
1924 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1925 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", editor, cx);
1926
1927 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1928 assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", editor, cx);
1929
1930 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1931 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", editor, cx);
1932
1933 editor.move_right(&MoveRight, window, cx);
1934 editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
1935 assert_selection_ranges(
1936 "use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}",
1937 editor,
1938 cx,
1939 );
1940
1941 editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
1942 assert_selection_ranges(
1943 "use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}",
1944 editor,
1945 cx,
1946 );
1947
1948 editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
1949 assert_selection_ranges(
1950 "use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}",
1951 editor,
1952 cx,
1953 );
1954 });
1955}
1956
1957#[gpui::test]
1958fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1959 init_test(cx, |_| {});
1960
1961 let editor = cx.add_window(|window, cx| {
1962 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1963 build_editor(buffer, window, cx)
1964 });
1965
1966 _ = editor.update(cx, |editor, window, cx| {
1967 editor.set_wrap_width(Some(140.0.into()), cx);
1968 assert_eq!(
1969 editor.display_text(cx),
1970 "use one::{\n two::three::\n four::five\n};"
1971 );
1972
1973 editor.change_selections(None, window, cx, |s| {
1974 s.select_display_ranges([
1975 DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
1976 ]);
1977 });
1978
1979 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1980 assert_eq!(
1981 editor.selections.display_ranges(cx),
1982 &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
1983 );
1984
1985 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1986 assert_eq!(
1987 editor.selections.display_ranges(cx),
1988 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1989 );
1990
1991 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1992 assert_eq!(
1993 editor.selections.display_ranges(cx),
1994 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1995 );
1996
1997 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1998 assert_eq!(
1999 editor.selections.display_ranges(cx),
2000 &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
2001 );
2002
2003 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
2004 assert_eq!(
2005 editor.selections.display_ranges(cx),
2006 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
2007 );
2008
2009 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
2010 assert_eq!(
2011 editor.selections.display_ranges(cx),
2012 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
2013 );
2014 });
2015}
2016
2017#[gpui::test]
2018async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
2019 init_test(cx, |_| {});
2020 let mut cx = EditorTestContext::new(cx).await;
2021
2022 let line_height = cx.editor(|editor, window, _| {
2023 editor
2024 .style()
2025 .unwrap()
2026 .text
2027 .line_height_in_pixels(window.rem_size())
2028 });
2029 cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
2030
2031 cx.set_state(
2032 &r#"ˇone
2033 two
2034
2035 three
2036 fourˇ
2037 five
2038
2039 six"#
2040 .unindent(),
2041 );
2042
2043 cx.update_editor(|editor, window, cx| {
2044 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
2045 });
2046 cx.assert_editor_state(
2047 &r#"one
2048 two
2049 ˇ
2050 three
2051 four
2052 five
2053 ˇ
2054 six"#
2055 .unindent(),
2056 );
2057
2058 cx.update_editor(|editor, window, cx| {
2059 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
2060 });
2061 cx.assert_editor_state(
2062 &r#"one
2063 two
2064
2065 three
2066 four
2067 five
2068 ˇ
2069 sixˇ"#
2070 .unindent(),
2071 );
2072
2073 cx.update_editor(|editor, window, cx| {
2074 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
2075 });
2076 cx.assert_editor_state(
2077 &r#"one
2078 two
2079
2080 three
2081 four
2082 five
2083
2084 sixˇ"#
2085 .unindent(),
2086 );
2087
2088 cx.update_editor(|editor, window, cx| {
2089 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
2090 });
2091 cx.assert_editor_state(
2092 &r#"one
2093 two
2094
2095 three
2096 four
2097 five
2098 ˇ
2099 six"#
2100 .unindent(),
2101 );
2102
2103 cx.update_editor(|editor, window, cx| {
2104 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
2105 });
2106 cx.assert_editor_state(
2107 &r#"one
2108 two
2109 ˇ
2110 three
2111 four
2112 five
2113
2114 six"#
2115 .unindent(),
2116 );
2117
2118 cx.update_editor(|editor, window, cx| {
2119 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
2120 });
2121 cx.assert_editor_state(
2122 &r#"ˇone
2123 two
2124
2125 three
2126 four
2127 five
2128
2129 six"#
2130 .unindent(),
2131 );
2132}
2133
2134#[gpui::test]
2135async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
2136 init_test(cx, |_| {});
2137 let mut cx = EditorTestContext::new(cx).await;
2138 let line_height = cx.editor(|editor, window, _| {
2139 editor
2140 .style()
2141 .unwrap()
2142 .text
2143 .line_height_in_pixels(window.rem_size())
2144 });
2145 let window = cx.window;
2146 cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
2147
2148 cx.set_state(
2149 r#"ˇone
2150 two
2151 three
2152 four
2153 five
2154 six
2155 seven
2156 eight
2157 nine
2158 ten
2159 "#,
2160 );
2161
2162 cx.update_editor(|editor, window, cx| {
2163 assert_eq!(
2164 editor.snapshot(window, cx).scroll_position(),
2165 gpui::Point::new(0., 0.)
2166 );
2167 editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
2168 assert_eq!(
2169 editor.snapshot(window, cx).scroll_position(),
2170 gpui::Point::new(0., 3.)
2171 );
2172 editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
2173 assert_eq!(
2174 editor.snapshot(window, cx).scroll_position(),
2175 gpui::Point::new(0., 6.)
2176 );
2177 editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
2178 assert_eq!(
2179 editor.snapshot(window, cx).scroll_position(),
2180 gpui::Point::new(0., 3.)
2181 );
2182
2183 editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
2184 assert_eq!(
2185 editor.snapshot(window, cx).scroll_position(),
2186 gpui::Point::new(0., 1.)
2187 );
2188 editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
2189 assert_eq!(
2190 editor.snapshot(window, cx).scroll_position(),
2191 gpui::Point::new(0., 3.)
2192 );
2193 });
2194}
2195
2196#[gpui::test]
2197async fn test_autoscroll(cx: &mut TestAppContext) {
2198 init_test(cx, |_| {});
2199 let mut cx = EditorTestContext::new(cx).await;
2200
2201 let line_height = cx.update_editor(|editor, window, cx| {
2202 editor.set_vertical_scroll_margin(2, cx);
2203 editor
2204 .style()
2205 .unwrap()
2206 .text
2207 .line_height_in_pixels(window.rem_size())
2208 });
2209 let window = cx.window;
2210 cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
2211
2212 cx.set_state(
2213 r#"ˇone
2214 two
2215 three
2216 four
2217 five
2218 six
2219 seven
2220 eight
2221 nine
2222 ten
2223 "#,
2224 );
2225 cx.update_editor(|editor, window, cx| {
2226 assert_eq!(
2227 editor.snapshot(window, cx).scroll_position(),
2228 gpui::Point::new(0., 0.0)
2229 );
2230 });
2231
2232 // Add a cursor below the visible area. Since both cursors cannot fit
2233 // on screen, the editor autoscrolls to reveal the newest cursor, and
2234 // allows the vertical scroll margin below that cursor.
2235 cx.update_editor(|editor, window, cx| {
2236 editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
2237 selections.select_ranges([
2238 Point::new(0, 0)..Point::new(0, 0),
2239 Point::new(6, 0)..Point::new(6, 0),
2240 ]);
2241 })
2242 });
2243 cx.update_editor(|editor, window, cx| {
2244 assert_eq!(
2245 editor.snapshot(window, cx).scroll_position(),
2246 gpui::Point::new(0., 3.0)
2247 );
2248 });
2249
2250 // Move down. The editor cursor scrolls down to track the newest cursor.
2251 cx.update_editor(|editor, window, cx| {
2252 editor.move_down(&Default::default(), window, cx);
2253 });
2254 cx.update_editor(|editor, window, cx| {
2255 assert_eq!(
2256 editor.snapshot(window, cx).scroll_position(),
2257 gpui::Point::new(0., 4.0)
2258 );
2259 });
2260
2261 // Add a cursor above the visible area. Since both cursors fit on screen,
2262 // the editor scrolls to show both.
2263 cx.update_editor(|editor, window, cx| {
2264 editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
2265 selections.select_ranges([
2266 Point::new(1, 0)..Point::new(1, 0),
2267 Point::new(6, 0)..Point::new(6, 0),
2268 ]);
2269 })
2270 });
2271 cx.update_editor(|editor, window, cx| {
2272 assert_eq!(
2273 editor.snapshot(window, cx).scroll_position(),
2274 gpui::Point::new(0., 1.0)
2275 );
2276 });
2277}
2278
2279#[gpui::test]
2280async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
2281 init_test(cx, |_| {});
2282 let mut cx = EditorTestContext::new(cx).await;
2283
2284 let line_height = cx.editor(|editor, window, _cx| {
2285 editor
2286 .style()
2287 .unwrap()
2288 .text
2289 .line_height_in_pixels(window.rem_size())
2290 });
2291 let window = cx.window;
2292 cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
2293 cx.set_state(
2294 &r#"
2295 ˇone
2296 two
2297 threeˇ
2298 four
2299 five
2300 six
2301 seven
2302 eight
2303 nine
2304 ten
2305 "#
2306 .unindent(),
2307 );
2308
2309 cx.update_editor(|editor, window, cx| {
2310 editor.move_page_down(&MovePageDown::default(), window, cx)
2311 });
2312 cx.assert_editor_state(
2313 &r#"
2314 one
2315 two
2316 three
2317 ˇfour
2318 five
2319 sixˇ
2320 seven
2321 eight
2322 nine
2323 ten
2324 "#
2325 .unindent(),
2326 );
2327
2328 cx.update_editor(|editor, window, cx| {
2329 editor.move_page_down(&MovePageDown::default(), window, cx)
2330 });
2331 cx.assert_editor_state(
2332 &r#"
2333 one
2334 two
2335 three
2336 four
2337 five
2338 six
2339 ˇseven
2340 eight
2341 nineˇ
2342 ten
2343 "#
2344 .unindent(),
2345 );
2346
2347 cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
2348 cx.assert_editor_state(
2349 &r#"
2350 one
2351 two
2352 three
2353 ˇfour
2354 five
2355 sixˇ
2356 seven
2357 eight
2358 nine
2359 ten
2360 "#
2361 .unindent(),
2362 );
2363
2364 cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
2365 cx.assert_editor_state(
2366 &r#"
2367 ˇone
2368 two
2369 threeˇ
2370 four
2371 five
2372 six
2373 seven
2374 eight
2375 nine
2376 ten
2377 "#
2378 .unindent(),
2379 );
2380
2381 // Test select collapsing
2382 cx.update_editor(|editor, window, cx| {
2383 editor.move_page_down(&MovePageDown::default(), window, cx);
2384 editor.move_page_down(&MovePageDown::default(), window, cx);
2385 editor.move_page_down(&MovePageDown::default(), window, cx);
2386 });
2387 cx.assert_editor_state(
2388 &r#"
2389 one
2390 two
2391 three
2392 four
2393 five
2394 six
2395 seven
2396 eight
2397 nine
2398 ˇten
2399 ˇ"#
2400 .unindent(),
2401 );
2402}
2403
2404#[gpui::test]
2405async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
2406 init_test(cx, |_| {});
2407 let mut cx = EditorTestContext::new(cx).await;
2408 cx.set_state("one «two threeˇ» four");
2409 cx.update_editor(|editor, window, cx| {
2410 editor.delete_to_beginning_of_line(
2411 &DeleteToBeginningOfLine {
2412 stop_at_indent: false,
2413 },
2414 window,
2415 cx,
2416 );
2417 assert_eq!(editor.text(cx), " four");
2418 });
2419}
2420
2421#[gpui::test]
2422fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
2423 init_test(cx, |_| {});
2424
2425 let editor = cx.add_window(|window, cx| {
2426 let buffer = MultiBuffer::build_simple("one two three four", cx);
2427 build_editor(buffer.clone(), window, cx)
2428 });
2429
2430 _ = editor.update(cx, |editor, window, cx| {
2431 editor.change_selections(None, window, cx, |s| {
2432 s.select_display_ranges([
2433 // an empty selection - the preceding word fragment is deleted
2434 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2435 // characters selected - they are deleted
2436 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
2437 ])
2438 });
2439 editor.delete_to_previous_word_start(
2440 &DeleteToPreviousWordStart {
2441 ignore_newlines: false,
2442 },
2443 window,
2444 cx,
2445 );
2446 assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
2447 });
2448
2449 _ = editor.update(cx, |editor, window, cx| {
2450 editor.change_selections(None, window, cx, |s| {
2451 s.select_display_ranges([
2452 // an empty selection - the following word fragment is deleted
2453 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
2454 // characters selected - they are deleted
2455 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
2456 ])
2457 });
2458 editor.delete_to_next_word_end(
2459 &DeleteToNextWordEnd {
2460 ignore_newlines: false,
2461 },
2462 window,
2463 cx,
2464 );
2465 assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
2466 });
2467}
2468
2469#[gpui::test]
2470fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
2471 init_test(cx, |_| {});
2472
2473 let editor = cx.add_window(|window, cx| {
2474 let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
2475 build_editor(buffer.clone(), window, cx)
2476 });
2477 let del_to_prev_word_start = DeleteToPreviousWordStart {
2478 ignore_newlines: false,
2479 };
2480 let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
2481 ignore_newlines: true,
2482 };
2483
2484 _ = editor.update(cx, |editor, window, cx| {
2485 editor.change_selections(None, window, cx, |s| {
2486 s.select_display_ranges([
2487 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
2488 ])
2489 });
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\n");
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\nthree");
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\n");
2496 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2497 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
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(), "one\n");
2500 editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
2501 assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
2502 });
2503}
2504
2505#[gpui::test]
2506fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
2507 init_test(cx, |_| {});
2508
2509 let editor = cx.add_window(|window, cx| {
2510 let buffer = MultiBuffer::build_simple("\none\n two\nthree\n four", cx);
2511 build_editor(buffer.clone(), window, cx)
2512 });
2513 let del_to_next_word_end = DeleteToNextWordEnd {
2514 ignore_newlines: false,
2515 };
2516 let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
2517 ignore_newlines: true,
2518 };
2519
2520 _ = editor.update(cx, |editor, window, cx| {
2521 editor.change_selections(None, window, cx, |s| {
2522 s.select_display_ranges([
2523 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
2524 ])
2525 });
2526 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2527 assert_eq!(
2528 editor.buffer.read(cx).read(cx).text(),
2529 "one\n two\nthree\n four"
2530 );
2531 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2532 assert_eq!(
2533 editor.buffer.read(cx).read(cx).text(),
2534 "\n two\nthree\n four"
2535 );
2536 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2537 assert_eq!(
2538 editor.buffer.read(cx).read(cx).text(),
2539 "two\nthree\n four"
2540 );
2541 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2542 assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\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(), "\n four");
2545 editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
2546 assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
2547 });
2548}
2549
2550#[gpui::test]
2551fn test_newline(cx: &mut TestAppContext) {
2552 init_test(cx, |_| {});
2553
2554 let editor = cx.add_window(|window, cx| {
2555 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
2556 build_editor(buffer.clone(), window, cx)
2557 });
2558
2559 _ = editor.update(cx, |editor, window, cx| {
2560 editor.change_selections(None, window, cx, |s| {
2561 s.select_display_ranges([
2562 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2563 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
2564 DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
2565 ])
2566 });
2567
2568 editor.newline(&Newline, window, cx);
2569 assert_eq!(editor.text(cx), "aa\naa\n \n bb\n bb\n");
2570 });
2571}
2572
2573#[gpui::test]
2574fn test_newline_with_old_selections(cx: &mut TestAppContext) {
2575 init_test(cx, |_| {});
2576
2577 let editor = cx.add_window(|window, cx| {
2578 let buffer = MultiBuffer::build_simple(
2579 "
2580 a
2581 b(
2582 X
2583 )
2584 c(
2585 X
2586 )
2587 "
2588 .unindent()
2589 .as_str(),
2590 cx,
2591 );
2592 let mut editor = build_editor(buffer.clone(), window, cx);
2593 editor.change_selections(None, window, cx, |s| {
2594 s.select_ranges([
2595 Point::new(2, 4)..Point::new(2, 5),
2596 Point::new(5, 4)..Point::new(5, 5),
2597 ])
2598 });
2599 editor
2600 });
2601
2602 _ = editor.update(cx, |editor, window, cx| {
2603 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2604 editor.buffer.update(cx, |buffer, cx| {
2605 buffer.edit(
2606 [
2607 (Point::new(1, 2)..Point::new(3, 0), ""),
2608 (Point::new(4, 2)..Point::new(6, 0), ""),
2609 ],
2610 None,
2611 cx,
2612 );
2613 assert_eq!(
2614 buffer.read(cx).text(),
2615 "
2616 a
2617 b()
2618 c()
2619 "
2620 .unindent()
2621 );
2622 });
2623 assert_eq!(
2624 editor.selections.ranges(cx),
2625 &[
2626 Point::new(1, 2)..Point::new(1, 2),
2627 Point::new(2, 2)..Point::new(2, 2),
2628 ],
2629 );
2630
2631 editor.newline(&Newline, window, cx);
2632 assert_eq!(
2633 editor.text(cx),
2634 "
2635 a
2636 b(
2637 )
2638 c(
2639 )
2640 "
2641 .unindent()
2642 );
2643
2644 // The selections are moved after the inserted newlines
2645 assert_eq!(
2646 editor.selections.ranges(cx),
2647 &[
2648 Point::new(2, 0)..Point::new(2, 0),
2649 Point::new(4, 0)..Point::new(4, 0),
2650 ],
2651 );
2652 });
2653}
2654
2655#[gpui::test]
2656async fn test_newline_above(cx: &mut TestAppContext) {
2657 init_test(cx, |settings| {
2658 settings.defaults.tab_size = NonZeroU32::new(4)
2659 });
2660
2661 let language = Arc::new(
2662 Language::new(
2663 LanguageConfig::default(),
2664 Some(tree_sitter_rust::LANGUAGE.into()),
2665 )
2666 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2667 .unwrap(),
2668 );
2669
2670 let mut cx = EditorTestContext::new(cx).await;
2671 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2672 cx.set_state(indoc! {"
2673 const a: ˇA = (
2674 (ˇ
2675 «const_functionˇ»(ˇ),
2676 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2677 )ˇ
2678 ˇ);ˇ
2679 "});
2680
2681 cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
2682 cx.assert_editor_state(indoc! {"
2683 ˇ
2684 const a: A = (
2685 ˇ
2686 (
2687 ˇ
2688 ˇ
2689 const_function(),
2690 ˇ
2691 ˇ
2692 ˇ
2693 ˇ
2694 something_else,
2695 ˇ
2696 )
2697 ˇ
2698 ˇ
2699 );
2700 "});
2701}
2702
2703#[gpui::test]
2704async fn test_newline_below(cx: &mut TestAppContext) {
2705 init_test(cx, |settings| {
2706 settings.defaults.tab_size = NonZeroU32::new(4)
2707 });
2708
2709 let language = Arc::new(
2710 Language::new(
2711 LanguageConfig::default(),
2712 Some(tree_sitter_rust::LANGUAGE.into()),
2713 )
2714 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2715 .unwrap(),
2716 );
2717
2718 let mut cx = EditorTestContext::new(cx).await;
2719 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2720 cx.set_state(indoc! {"
2721 const a: ˇA = (
2722 (ˇ
2723 «const_functionˇ»(ˇ),
2724 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2725 )ˇ
2726 ˇ);ˇ
2727 "});
2728
2729 cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
2730 cx.assert_editor_state(indoc! {"
2731 const a: A = (
2732 ˇ
2733 (
2734 ˇ
2735 const_function(),
2736 ˇ
2737 ˇ
2738 something_else,
2739 ˇ
2740 ˇ
2741 ˇ
2742 ˇ
2743 )
2744 ˇ
2745 );
2746 ˇ
2747 ˇ
2748 "});
2749}
2750
2751#[gpui::test]
2752async fn test_newline_comments(cx: &mut TestAppContext) {
2753 init_test(cx, |settings| {
2754 settings.defaults.tab_size = NonZeroU32::new(4)
2755 });
2756
2757 let language = Arc::new(Language::new(
2758 LanguageConfig {
2759 line_comments: vec!["//".into()],
2760 ..LanguageConfig::default()
2761 },
2762 None,
2763 ));
2764 {
2765 let mut cx = EditorTestContext::new(cx).await;
2766 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2767 cx.set_state(indoc! {"
2768 // Fooˇ
2769 "});
2770
2771 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2772 cx.assert_editor_state(indoc! {"
2773 // Foo
2774 //ˇ
2775 "});
2776 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
2777 cx.set_state(indoc! {"
2778 ˇ// Foo
2779 "});
2780 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2781 cx.assert_editor_state(indoc! {"
2782
2783 ˇ// Foo
2784 "});
2785 }
2786 // Ensure that comment continuations can be disabled.
2787 update_test_language_settings(cx, |settings| {
2788 settings.defaults.extend_comment_on_newline = Some(false);
2789 });
2790 let mut cx = EditorTestContext::new(cx).await;
2791 cx.set_state(indoc! {"
2792 // Fooˇ
2793 "});
2794 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2795 cx.assert_editor_state(indoc! {"
2796 // Foo
2797 ˇ
2798 "});
2799}
2800
2801#[gpui::test]
2802fn test_insert_with_old_selections(cx: &mut TestAppContext) {
2803 init_test(cx, |_| {});
2804
2805 let editor = cx.add_window(|window, cx| {
2806 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
2807 let mut editor = build_editor(buffer.clone(), window, cx);
2808 editor.change_selections(None, window, cx, |s| {
2809 s.select_ranges([3..4, 11..12, 19..20])
2810 });
2811 editor
2812 });
2813
2814 _ = editor.update(cx, |editor, window, cx| {
2815 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2816 editor.buffer.update(cx, |buffer, cx| {
2817 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
2818 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
2819 });
2820 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
2821
2822 editor.insert("Z", window, cx);
2823 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
2824
2825 // The selections are moved after the inserted characters
2826 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
2827 });
2828}
2829
2830#[gpui::test]
2831async fn test_tab(cx: &mut TestAppContext) {
2832 init_test(cx, |settings| {
2833 settings.defaults.tab_size = NonZeroU32::new(3)
2834 });
2835
2836 let mut cx = EditorTestContext::new(cx).await;
2837 cx.set_state(indoc! {"
2838 ˇabˇc
2839 ˇ🏀ˇ🏀ˇefg
2840 dˇ
2841 "});
2842 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2843 cx.assert_editor_state(indoc! {"
2844 ˇab ˇc
2845 ˇ🏀 ˇ🏀 ˇefg
2846 d ˇ
2847 "});
2848
2849 cx.set_state(indoc! {"
2850 a
2851 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2852 "});
2853 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2854 cx.assert_editor_state(indoc! {"
2855 a
2856 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2857 "});
2858}
2859
2860#[gpui::test]
2861async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
2862 init_test(cx, |_| {});
2863
2864 let mut cx = EditorTestContext::new(cx).await;
2865 let language = Arc::new(
2866 Language::new(
2867 LanguageConfig::default(),
2868 Some(tree_sitter_rust::LANGUAGE.into()),
2869 )
2870 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2871 .unwrap(),
2872 );
2873 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2874
2875 // cursors that are already at the suggested indent level insert
2876 // a soft tab. cursors that are to the left of the suggested indent
2877 // auto-indent their line.
2878 cx.set_state(indoc! {"
2879 ˇ
2880 const a: B = (
2881 c(
2882 d(
2883 ˇ
2884 )
2885 ˇ
2886 ˇ )
2887 );
2888 "});
2889 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2890 cx.assert_editor_state(indoc! {"
2891 ˇ
2892 const a: B = (
2893 c(
2894 d(
2895 ˇ
2896 )
2897 ˇ
2898 ˇ)
2899 );
2900 "});
2901
2902 // handle auto-indent when there are multiple cursors on the same line
2903 cx.set_state(indoc! {"
2904 const a: B = (
2905 c(
2906 ˇ ˇ
2907 ˇ )
2908 );
2909 "});
2910 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2911 cx.assert_editor_state(indoc! {"
2912 const a: B = (
2913 c(
2914 ˇ
2915 ˇ)
2916 );
2917 "});
2918}
2919
2920#[gpui::test]
2921async fn test_tab_with_mixed_whitespace(cx: &mut TestAppContext) {
2922 init_test(cx, |settings| {
2923 settings.defaults.tab_size = NonZeroU32::new(4)
2924 });
2925
2926 let language = Arc::new(
2927 Language::new(
2928 LanguageConfig::default(),
2929 Some(tree_sitter_rust::LANGUAGE.into()),
2930 )
2931 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2932 .unwrap(),
2933 );
2934
2935 let mut cx = EditorTestContext::new(cx).await;
2936 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2937 cx.set_state(indoc! {"
2938 fn a() {
2939 if b {
2940 \t ˇc
2941 }
2942 }
2943 "});
2944
2945 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2946 cx.assert_editor_state(indoc! {"
2947 fn a() {
2948 if b {
2949 ˇc
2950 }
2951 }
2952 "});
2953}
2954
2955#[gpui::test]
2956async fn test_indent_outdent(cx: &mut TestAppContext) {
2957 init_test(cx, |settings| {
2958 settings.defaults.tab_size = NonZeroU32::new(4);
2959 });
2960
2961 let mut cx = EditorTestContext::new(cx).await;
2962
2963 cx.set_state(indoc! {"
2964 «oneˇ» «twoˇ»
2965 three
2966 four
2967 "});
2968 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2969 cx.assert_editor_state(indoc! {"
2970 «oneˇ» «twoˇ»
2971 three
2972 four
2973 "});
2974
2975 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
2976 cx.assert_editor_state(indoc! {"
2977 «oneˇ» «twoˇ»
2978 three
2979 four
2980 "});
2981
2982 // select across line ending
2983 cx.set_state(indoc! {"
2984 one two
2985 t«hree
2986 ˇ» four
2987 "});
2988 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2989 cx.assert_editor_state(indoc! {"
2990 one two
2991 t«hree
2992 ˇ» four
2993 "});
2994
2995 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
2996 cx.assert_editor_state(indoc! {"
2997 one two
2998 t«hree
2999 ˇ» four
3000 "});
3001
3002 // Ensure that indenting/outdenting works when the cursor is at column 0.
3003 cx.set_state(indoc! {"
3004 one two
3005 ˇthree
3006 four
3007 "});
3008 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3009 cx.assert_editor_state(indoc! {"
3010 one two
3011 ˇthree
3012 four
3013 "});
3014
3015 cx.set_state(indoc! {"
3016 one two
3017 ˇ three
3018 four
3019 "});
3020 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3021 cx.assert_editor_state(indoc! {"
3022 one two
3023 ˇthree
3024 four
3025 "});
3026}
3027
3028#[gpui::test]
3029async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
3030 init_test(cx, |settings| {
3031 settings.defaults.hard_tabs = Some(true);
3032 });
3033
3034 let mut cx = EditorTestContext::new(cx).await;
3035
3036 // select two ranges on one line
3037 cx.set_state(indoc! {"
3038 «oneˇ» «twoˇ»
3039 three
3040 four
3041 "});
3042 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3043 cx.assert_editor_state(indoc! {"
3044 \t«oneˇ» «twoˇ»
3045 three
3046 four
3047 "});
3048 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3049 cx.assert_editor_state(indoc! {"
3050 \t\t«oneˇ» «twoˇ»
3051 three
3052 four
3053 "});
3054 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3055 cx.assert_editor_state(indoc! {"
3056 \t«oneˇ» «twoˇ»
3057 three
3058 four
3059 "});
3060 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3061 cx.assert_editor_state(indoc! {"
3062 «oneˇ» «twoˇ»
3063 three
3064 four
3065 "});
3066
3067 // select across a line ending
3068 cx.set_state(indoc! {"
3069 one two
3070 t«hree
3071 ˇ»four
3072 "});
3073 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3074 cx.assert_editor_state(indoc! {"
3075 one two
3076 \tt«hree
3077 ˇ»four
3078 "});
3079 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3080 cx.assert_editor_state(indoc! {"
3081 one two
3082 \t\tt«hree
3083 ˇ»four
3084 "});
3085 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3086 cx.assert_editor_state(indoc! {"
3087 one two
3088 \tt«hree
3089 ˇ»four
3090 "});
3091 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3092 cx.assert_editor_state(indoc! {"
3093 one two
3094 t«hree
3095 ˇ»four
3096 "});
3097
3098 // Ensure that indenting/outdenting works when the cursor is at column 0.
3099 cx.set_state(indoc! {"
3100 one two
3101 ˇthree
3102 four
3103 "});
3104 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3105 cx.assert_editor_state(indoc! {"
3106 one two
3107 ˇthree
3108 four
3109 "});
3110 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3111 cx.assert_editor_state(indoc! {"
3112 one two
3113 \tˇthree
3114 four
3115 "});
3116 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3117 cx.assert_editor_state(indoc! {"
3118 one two
3119 ˇthree
3120 four
3121 "});
3122}
3123
3124#[gpui::test]
3125fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
3126 init_test(cx, |settings| {
3127 settings.languages.extend([
3128 (
3129 "TOML".into(),
3130 LanguageSettingsContent {
3131 tab_size: NonZeroU32::new(2),
3132 ..Default::default()
3133 },
3134 ),
3135 (
3136 "Rust".into(),
3137 LanguageSettingsContent {
3138 tab_size: NonZeroU32::new(4),
3139 ..Default::default()
3140 },
3141 ),
3142 ]);
3143 });
3144
3145 let toml_language = Arc::new(Language::new(
3146 LanguageConfig {
3147 name: "TOML".into(),
3148 ..Default::default()
3149 },
3150 None,
3151 ));
3152 let rust_language = Arc::new(Language::new(
3153 LanguageConfig {
3154 name: "Rust".into(),
3155 ..Default::default()
3156 },
3157 None,
3158 ));
3159
3160 let toml_buffer =
3161 cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
3162 let rust_buffer =
3163 cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
3164 let multibuffer = cx.new(|cx| {
3165 let mut multibuffer = MultiBuffer::new(ReadWrite);
3166 multibuffer.push_excerpts(
3167 toml_buffer.clone(),
3168 [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
3169 cx,
3170 );
3171 multibuffer.push_excerpts(
3172 rust_buffer.clone(),
3173 [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
3174 cx,
3175 );
3176 multibuffer
3177 });
3178
3179 cx.add_window(|window, cx| {
3180 let mut editor = build_editor(multibuffer, window, cx);
3181
3182 assert_eq!(
3183 editor.text(cx),
3184 indoc! {"
3185 a = 1
3186 b = 2
3187
3188 const c: usize = 3;
3189 "}
3190 );
3191
3192 select_ranges(
3193 &mut editor,
3194 indoc! {"
3195 «aˇ» = 1
3196 b = 2
3197
3198 «const c:ˇ» usize = 3;
3199 "},
3200 window,
3201 cx,
3202 );
3203
3204 editor.tab(&Tab, window, cx);
3205 assert_text_with_selections(
3206 &mut editor,
3207 indoc! {"
3208 «aˇ» = 1
3209 b = 2
3210
3211 «const c:ˇ» usize = 3;
3212 "},
3213 cx,
3214 );
3215 editor.backtab(&Backtab, window, cx);
3216 assert_text_with_selections(
3217 &mut editor,
3218 indoc! {"
3219 «aˇ» = 1
3220 b = 2
3221
3222 «const c:ˇ» usize = 3;
3223 "},
3224 cx,
3225 );
3226
3227 editor
3228 });
3229}
3230
3231#[gpui::test]
3232async fn test_backspace(cx: &mut TestAppContext) {
3233 init_test(cx, |_| {});
3234
3235 let mut cx = EditorTestContext::new(cx).await;
3236
3237 // Basic backspace
3238 cx.set_state(indoc! {"
3239 onˇe two three
3240 fou«rˇ» five six
3241 seven «ˇeight nine
3242 »ten
3243 "});
3244 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3245 cx.assert_editor_state(indoc! {"
3246 oˇe two three
3247 fouˇ five six
3248 seven ˇten
3249 "});
3250
3251 // Test backspace inside and around indents
3252 cx.set_state(indoc! {"
3253 zero
3254 ˇone
3255 ˇtwo
3256 ˇ ˇ ˇ three
3257 ˇ ˇ four
3258 "});
3259 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3260 cx.assert_editor_state(indoc! {"
3261 zero
3262 ˇone
3263 ˇtwo
3264 ˇ threeˇ four
3265 "});
3266}
3267
3268#[gpui::test]
3269async fn test_delete(cx: &mut TestAppContext) {
3270 init_test(cx, |_| {});
3271
3272 let mut cx = EditorTestContext::new(cx).await;
3273 cx.set_state(indoc! {"
3274 onˇe two three
3275 fou«rˇ» five six
3276 seven «ˇeight nine
3277 »ten
3278 "});
3279 cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
3280 cx.assert_editor_state(indoc! {"
3281 onˇ two three
3282 fouˇ five six
3283 seven ˇten
3284 "});
3285}
3286
3287#[gpui::test]
3288fn test_delete_line(cx: &mut TestAppContext) {
3289 init_test(cx, |_| {});
3290
3291 let editor = cx.add_window(|window, cx| {
3292 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3293 build_editor(buffer, window, cx)
3294 });
3295 _ = editor.update(cx, |editor, window, cx| {
3296 editor.change_selections(None, window, cx, |s| {
3297 s.select_display_ranges([
3298 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3299 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3300 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3301 ])
3302 });
3303 editor.delete_line(&DeleteLine, window, cx);
3304 assert_eq!(editor.display_text(cx), "ghi");
3305 assert_eq!(
3306 editor.selections.display_ranges(cx),
3307 vec![
3308 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
3309 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
3310 ]
3311 );
3312 });
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(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
3322 ])
3323 });
3324 editor.delete_line(&DeleteLine, window, cx);
3325 assert_eq!(editor.display_text(cx), "ghi\n");
3326 assert_eq!(
3327 editor.selections.display_ranges(cx),
3328 vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
3329 );
3330 });
3331}
3332
3333#[gpui::test]
3334fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
3335 init_test(cx, |_| {});
3336
3337 cx.add_window(|window, cx| {
3338 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3339 let mut editor = build_editor(buffer.clone(), window, cx);
3340 let buffer = buffer.read(cx).as_singleton().unwrap();
3341
3342 assert_eq!(
3343 editor.selections.ranges::<Point>(cx),
3344 &[Point::new(0, 0)..Point::new(0, 0)]
3345 );
3346
3347 // When on single line, replace newline at end by space
3348 editor.join_lines(&JoinLines, window, cx);
3349 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3350 assert_eq!(
3351 editor.selections.ranges::<Point>(cx),
3352 &[Point::new(0, 3)..Point::new(0, 3)]
3353 );
3354
3355 // When multiple lines are selected, remove newlines that are spanned by the selection
3356 editor.change_selections(None, window, cx, |s| {
3357 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
3358 });
3359 editor.join_lines(&JoinLines, window, cx);
3360 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
3361 assert_eq!(
3362 editor.selections.ranges::<Point>(cx),
3363 &[Point::new(0, 11)..Point::new(0, 11)]
3364 );
3365
3366 // Undo should be transactional
3367 editor.undo(&Undo, window, cx);
3368 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3369 assert_eq!(
3370 editor.selections.ranges::<Point>(cx),
3371 &[Point::new(0, 5)..Point::new(2, 2)]
3372 );
3373
3374 // When joining an empty line don't insert a space
3375 editor.change_selections(None, window, cx, |s| {
3376 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
3377 });
3378 editor.join_lines(&JoinLines, window, cx);
3379 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
3380 assert_eq!(
3381 editor.selections.ranges::<Point>(cx),
3382 [Point::new(2, 3)..Point::new(2, 3)]
3383 );
3384
3385 // We can remove trailing newlines
3386 editor.join_lines(&JoinLines, window, cx);
3387 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3388 assert_eq!(
3389 editor.selections.ranges::<Point>(cx),
3390 [Point::new(2, 3)..Point::new(2, 3)]
3391 );
3392
3393 // We don't blow up on the last line
3394 editor.join_lines(&JoinLines, window, cx);
3395 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3396 assert_eq!(
3397 editor.selections.ranges::<Point>(cx),
3398 [Point::new(2, 3)..Point::new(2, 3)]
3399 );
3400
3401 // reset to test indentation
3402 editor.buffer.update(cx, |buffer, cx| {
3403 buffer.edit(
3404 [
3405 (Point::new(1, 0)..Point::new(1, 2), " "),
3406 (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
3407 ],
3408 None,
3409 cx,
3410 )
3411 });
3412
3413 // We remove any leading spaces
3414 assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
3415 editor.change_selections(None, window, cx, |s| {
3416 s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
3417 });
3418 editor.join_lines(&JoinLines, window, cx);
3419 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
3420
3421 // We don't insert a space for a line containing only spaces
3422 editor.join_lines(&JoinLines, window, cx);
3423 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
3424
3425 // We ignore any leading tabs
3426 editor.join_lines(&JoinLines, window, cx);
3427 assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
3428
3429 editor
3430 });
3431}
3432
3433#[gpui::test]
3434fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
3435 init_test(cx, |_| {});
3436
3437 cx.add_window(|window, cx| {
3438 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3439 let mut editor = build_editor(buffer.clone(), window, cx);
3440 let buffer = buffer.read(cx).as_singleton().unwrap();
3441
3442 editor.change_selections(None, window, cx, |s| {
3443 s.select_ranges([
3444 Point::new(0, 2)..Point::new(1, 1),
3445 Point::new(1, 2)..Point::new(1, 2),
3446 Point::new(3, 1)..Point::new(3, 2),
3447 ])
3448 });
3449
3450 editor.join_lines(&JoinLines, window, cx);
3451 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
3452
3453 assert_eq!(
3454 editor.selections.ranges::<Point>(cx),
3455 [
3456 Point::new(0, 7)..Point::new(0, 7),
3457 Point::new(1, 3)..Point::new(1, 3)
3458 ]
3459 );
3460 editor
3461 });
3462}
3463
3464#[gpui::test]
3465async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
3466 init_test(cx, |_| {});
3467
3468 let mut cx = EditorTestContext::new(cx).await;
3469
3470 let diff_base = r#"
3471 Line 0
3472 Line 1
3473 Line 2
3474 Line 3
3475 "#
3476 .unindent();
3477
3478 cx.set_state(
3479 &r#"
3480 ˇLine 0
3481 Line 1
3482 Line 2
3483 Line 3
3484 "#
3485 .unindent(),
3486 );
3487
3488 cx.set_head_text(&diff_base);
3489 executor.run_until_parked();
3490
3491 // Join lines
3492 cx.update_editor(|editor, window, cx| {
3493 editor.join_lines(&JoinLines, window, cx);
3494 });
3495 executor.run_until_parked();
3496
3497 cx.assert_editor_state(
3498 &r#"
3499 Line 0ˇ Line 1
3500 Line 2
3501 Line 3
3502 "#
3503 .unindent(),
3504 );
3505 // Join again
3506 cx.update_editor(|editor, window, cx| {
3507 editor.join_lines(&JoinLines, window, cx);
3508 });
3509 executor.run_until_parked();
3510
3511 cx.assert_editor_state(
3512 &r#"
3513 Line 0 Line 1ˇ Line 2
3514 Line 3
3515 "#
3516 .unindent(),
3517 );
3518}
3519
3520#[gpui::test]
3521async fn test_custom_newlines_cause_no_false_positive_diffs(
3522 executor: BackgroundExecutor,
3523 cx: &mut TestAppContext,
3524) {
3525 init_test(cx, |_| {});
3526 let mut cx = EditorTestContext::new(cx).await;
3527 cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
3528 cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
3529 executor.run_until_parked();
3530
3531 cx.update_editor(|editor, window, cx| {
3532 let snapshot = editor.snapshot(window, cx);
3533 assert_eq!(
3534 snapshot
3535 .buffer_snapshot
3536 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
3537 .collect::<Vec<_>>(),
3538 Vec::new(),
3539 "Should not have any diffs for files with custom newlines"
3540 );
3541 });
3542}
3543
3544#[gpui::test]
3545async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
3546 init_test(cx, |_| {});
3547
3548 let mut cx = EditorTestContext::new(cx).await;
3549
3550 // Test sort_lines_case_insensitive()
3551 cx.set_state(indoc! {"
3552 «z
3553 y
3554 x
3555 Z
3556 Y
3557 Xˇ»
3558 "});
3559 cx.update_editor(|e, window, cx| {
3560 e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
3561 });
3562 cx.assert_editor_state(indoc! {"
3563 «x
3564 X
3565 y
3566 Y
3567 z
3568 Zˇ»
3569 "});
3570
3571 // Test reverse_lines()
3572 cx.set_state(indoc! {"
3573 «5
3574 4
3575 3
3576 2
3577 1ˇ»
3578 "});
3579 cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
3580 cx.assert_editor_state(indoc! {"
3581 «1
3582 2
3583 3
3584 4
3585 5ˇ»
3586 "});
3587
3588 // Skip testing shuffle_line()
3589
3590 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
3591 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
3592
3593 // Don't manipulate when cursor is on single line, but expand the selection
3594 cx.set_state(indoc! {"
3595 ddˇdd
3596 ccc
3597 bb
3598 a
3599 "});
3600 cx.update_editor(|e, window, cx| {
3601 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3602 });
3603 cx.assert_editor_state(indoc! {"
3604 «ddddˇ»
3605 ccc
3606 bb
3607 a
3608 "});
3609
3610 // Basic manipulate case
3611 // Start selection moves to column 0
3612 // End of selection shrinks to fit shorter line
3613 cx.set_state(indoc! {"
3614 dd«d
3615 ccc
3616 bb
3617 aaaaaˇ»
3618 "});
3619 cx.update_editor(|e, window, cx| {
3620 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3621 });
3622 cx.assert_editor_state(indoc! {"
3623 «aaaaa
3624 bb
3625 ccc
3626 dddˇ»
3627 "});
3628
3629 // Manipulate case with newlines
3630 cx.set_state(indoc! {"
3631 dd«d
3632 ccc
3633
3634 bb
3635 aaaaa
3636
3637 ˇ»
3638 "});
3639 cx.update_editor(|e, window, cx| {
3640 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3641 });
3642 cx.assert_editor_state(indoc! {"
3643 «
3644
3645 aaaaa
3646 bb
3647 ccc
3648 dddˇ»
3649
3650 "});
3651
3652 // Adding new line
3653 cx.set_state(indoc! {"
3654 aa«a
3655 bbˇ»b
3656 "});
3657 cx.update_editor(|e, window, cx| {
3658 e.manipulate_lines(window, cx, |lines| lines.push("added_line"))
3659 });
3660 cx.assert_editor_state(indoc! {"
3661 «aaa
3662 bbb
3663 added_lineˇ»
3664 "});
3665
3666 // Removing line
3667 cx.set_state(indoc! {"
3668 aa«a
3669 bbbˇ»
3670 "});
3671 cx.update_editor(|e, window, cx| {
3672 e.manipulate_lines(window, cx, |lines| {
3673 lines.pop();
3674 })
3675 });
3676 cx.assert_editor_state(indoc! {"
3677 «aaaˇ»
3678 "});
3679
3680 // Removing all lines
3681 cx.set_state(indoc! {"
3682 aa«a
3683 bbbˇ»
3684 "});
3685 cx.update_editor(|e, window, cx| {
3686 e.manipulate_lines(window, cx, |lines| {
3687 lines.drain(..);
3688 })
3689 });
3690 cx.assert_editor_state(indoc! {"
3691 ˇ
3692 "});
3693}
3694
3695#[gpui::test]
3696async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
3697 init_test(cx, |_| {});
3698
3699 let mut cx = EditorTestContext::new(cx).await;
3700
3701 // Consider continuous selection as single selection
3702 cx.set_state(indoc! {"
3703 Aaa«aa
3704 cˇ»c«c
3705 bb
3706 aaaˇ»aa
3707 "});
3708 cx.update_editor(|e, window, cx| {
3709 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3710 });
3711 cx.assert_editor_state(indoc! {"
3712 «Aaaaa
3713 ccc
3714 bb
3715 aaaaaˇ»
3716 "});
3717
3718 cx.set_state(indoc! {"
3719 Aaa«aa
3720 cˇ»c«c
3721 bb
3722 aaaˇ»aa
3723 "});
3724 cx.update_editor(|e, window, cx| {
3725 e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
3726 });
3727 cx.assert_editor_state(indoc! {"
3728 «Aaaaa
3729 ccc
3730 bbˇ»
3731 "});
3732
3733 // Consider non continuous selection as distinct dedup operations
3734 cx.set_state(indoc! {"
3735 «aaaaa
3736 bb
3737 aaaaa
3738 aaaaaˇ»
3739
3740 aaa«aaˇ»
3741 "});
3742 cx.update_editor(|e, window, cx| {
3743 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3744 });
3745 cx.assert_editor_state(indoc! {"
3746 «aaaaa
3747 bbˇ»
3748
3749 «aaaaaˇ»
3750 "});
3751}
3752
3753#[gpui::test]
3754async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
3755 init_test(cx, |_| {});
3756
3757 let mut cx = EditorTestContext::new(cx).await;
3758
3759 cx.set_state(indoc! {"
3760 «Aaa
3761 aAa
3762 Aaaˇ»
3763 "});
3764 cx.update_editor(|e, window, cx| {
3765 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3766 });
3767 cx.assert_editor_state(indoc! {"
3768 «Aaa
3769 aAaˇ»
3770 "});
3771
3772 cx.set_state(indoc! {"
3773 «Aaa
3774 aAa
3775 aaAˇ»
3776 "});
3777 cx.update_editor(|e, window, cx| {
3778 e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
3779 });
3780 cx.assert_editor_state(indoc! {"
3781 «Aaaˇ»
3782 "});
3783}
3784
3785#[gpui::test]
3786async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
3787 init_test(cx, |_| {});
3788
3789 let mut cx = EditorTestContext::new(cx).await;
3790
3791 // Manipulate with multiple selections on a single line
3792 cx.set_state(indoc! {"
3793 dd«dd
3794 cˇ»c«c
3795 bb
3796 aaaˇ»aa
3797 "});
3798 cx.update_editor(|e, window, cx| {
3799 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3800 });
3801 cx.assert_editor_state(indoc! {"
3802 «aaaaa
3803 bb
3804 ccc
3805 ddddˇ»
3806 "});
3807
3808 // Manipulate with multiple disjoin selections
3809 cx.set_state(indoc! {"
3810 5«
3811 4
3812 3
3813 2
3814 1ˇ»
3815
3816 dd«dd
3817 ccc
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 «1
3826 2
3827 3
3828 4
3829 5ˇ»
3830
3831 «aaaaa
3832 bb
3833 ccc
3834 ddddˇ»
3835 "});
3836
3837 // Adding lines on each selection
3838 cx.set_state(indoc! {"
3839 2«
3840 1ˇ»
3841
3842 bb«bb
3843 aaaˇ»aa
3844 "});
3845 cx.update_editor(|e, window, cx| {
3846 e.manipulate_lines(window, cx, |lines| lines.push("added line"))
3847 });
3848 cx.assert_editor_state(indoc! {"
3849 «2
3850 1
3851 added lineˇ»
3852
3853 «bbbb
3854 aaaaa
3855 added lineˇ»
3856 "});
3857
3858 // Removing lines on each selection
3859 cx.set_state(indoc! {"
3860 2«
3861 1ˇ»
3862
3863 bb«bb
3864 aaaˇ»aa
3865 "});
3866 cx.update_editor(|e, window, cx| {
3867 e.manipulate_lines(window, cx, |lines| {
3868 lines.pop();
3869 })
3870 });
3871 cx.assert_editor_state(indoc! {"
3872 «2ˇ»
3873
3874 «bbbbˇ»
3875 "});
3876}
3877
3878#[gpui::test]
3879async fn test_manipulate_text(cx: &mut TestAppContext) {
3880 init_test(cx, |_| {});
3881
3882 let mut cx = EditorTestContext::new(cx).await;
3883
3884 // Test convert_to_upper_case()
3885 cx.set_state(indoc! {"
3886 «hello worldˇ»
3887 "});
3888 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3889 cx.assert_editor_state(indoc! {"
3890 «HELLO WORLDˇ»
3891 "});
3892
3893 // Test convert_to_lower_case()
3894 cx.set_state(indoc! {"
3895 «HELLO WORLDˇ»
3896 "});
3897 cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
3898 cx.assert_editor_state(indoc! {"
3899 «hello worldˇ»
3900 "});
3901
3902 // Test multiple line, single selection case
3903 cx.set_state(indoc! {"
3904 «The quick brown
3905 fox jumps over
3906 the lazy dogˇ»
3907 "});
3908 cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
3909 cx.assert_editor_state(indoc! {"
3910 «The Quick Brown
3911 Fox Jumps Over
3912 The Lazy Dogˇ»
3913 "});
3914
3915 // Test multiple line, single selection case
3916 cx.set_state(indoc! {"
3917 «The quick brown
3918 fox jumps over
3919 the lazy dogˇ»
3920 "});
3921 cx.update_editor(|e, window, cx| {
3922 e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
3923 });
3924 cx.assert_editor_state(indoc! {"
3925 «TheQuickBrown
3926 FoxJumpsOver
3927 TheLazyDogˇ»
3928 "});
3929
3930 // From here on out, test more complex cases of manipulate_text()
3931
3932 // Test no selection case - should affect words cursors are in
3933 // Cursor at beginning, middle, and end of word
3934 cx.set_state(indoc! {"
3935 ˇhello big beauˇtiful worldˇ
3936 "});
3937 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3938 cx.assert_editor_state(indoc! {"
3939 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3940 "});
3941
3942 // Test multiple selections on a single line and across multiple lines
3943 cx.set_state(indoc! {"
3944 «Theˇ» quick «brown
3945 foxˇ» jumps «overˇ»
3946 the «lazyˇ» dog
3947 "});
3948 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3949 cx.assert_editor_state(indoc! {"
3950 «THEˇ» quick «BROWN
3951 FOXˇ» jumps «OVERˇ»
3952 the «LAZYˇ» dog
3953 "});
3954
3955 // Test case where text length grows
3956 cx.set_state(indoc! {"
3957 «tschüߡ»
3958 "});
3959 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3960 cx.assert_editor_state(indoc! {"
3961 «TSCHÜSSˇ»
3962 "});
3963
3964 // Test to make sure we don't crash when text shrinks
3965 cx.set_state(indoc! {"
3966 aaa_bbbˇ
3967 "});
3968 cx.update_editor(|e, window, cx| {
3969 e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
3970 });
3971 cx.assert_editor_state(indoc! {"
3972 «aaaBbbˇ»
3973 "});
3974
3975 // Test to make sure we all aware of the fact that each word can grow and shrink
3976 // Final selections should be aware of this fact
3977 cx.set_state(indoc! {"
3978 aaa_bˇbb bbˇb_ccc ˇccc_ddd
3979 "});
3980 cx.update_editor(|e, window, cx| {
3981 e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
3982 });
3983 cx.assert_editor_state(indoc! {"
3984 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
3985 "});
3986
3987 cx.set_state(indoc! {"
3988 «hElLo, WoRld!ˇ»
3989 "});
3990 cx.update_editor(|e, window, cx| {
3991 e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
3992 });
3993 cx.assert_editor_state(indoc! {"
3994 «HeLlO, wOrLD!ˇ»
3995 "});
3996}
3997
3998#[gpui::test]
3999fn test_duplicate_line(cx: &mut TestAppContext) {
4000 init_test(cx, |_| {});
4001
4002 let editor = cx.add_window(|window, cx| {
4003 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4004 build_editor(buffer, window, cx)
4005 });
4006 _ = editor.update(cx, |editor, window, cx| {
4007 editor.change_selections(None, window, cx, |s| {
4008 s.select_display_ranges([
4009 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4010 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4011 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4012 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
4013 ])
4014 });
4015 editor.duplicate_line_down(&DuplicateLineDown, window, cx);
4016 assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
4017 assert_eq!(
4018 editor.selections.display_ranges(cx),
4019 vec![
4020 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
4021 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
4022 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
4023 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
4024 ]
4025 );
4026 });
4027
4028 let editor = cx.add_window(|window, cx| {
4029 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4030 build_editor(buffer, window, cx)
4031 });
4032 _ = editor.update(cx, |editor, window, cx| {
4033 editor.change_selections(None, window, cx, |s| {
4034 s.select_display_ranges([
4035 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4036 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4037 ])
4038 });
4039 editor.duplicate_line_down(&DuplicateLineDown, window, cx);
4040 assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
4041 assert_eq!(
4042 editor.selections.display_ranges(cx),
4043 vec![
4044 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
4045 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
4046 ]
4047 );
4048 });
4049
4050 // With `move_upwards` the selections stay in place, except for
4051 // the lines inserted above them
4052 let editor = cx.add_window(|window, cx| {
4053 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4054 build_editor(buffer, window, cx)
4055 });
4056 _ = editor.update(cx, |editor, window, cx| {
4057 editor.change_selections(None, window, cx, |s| {
4058 s.select_display_ranges([
4059 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4060 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4061 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4062 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
4063 ])
4064 });
4065 editor.duplicate_line_up(&DuplicateLineUp, window, cx);
4066 assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
4067 assert_eq!(
4068 editor.selections.display_ranges(cx),
4069 vec![
4070 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4071 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4072 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
4073 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
4074 ]
4075 );
4076 });
4077
4078 let editor = cx.add_window(|window, cx| {
4079 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4080 build_editor(buffer, window, cx)
4081 });
4082 _ = editor.update(cx, |editor, window, cx| {
4083 editor.change_selections(None, window, cx, |s| {
4084 s.select_display_ranges([
4085 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4086 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4087 ])
4088 });
4089 editor.duplicate_line_up(&DuplicateLineUp, window, cx);
4090 assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
4091 assert_eq!(
4092 editor.selections.display_ranges(cx),
4093 vec![
4094 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4095 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4096 ]
4097 );
4098 });
4099
4100 let editor = cx.add_window(|window, cx| {
4101 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4102 build_editor(buffer, window, cx)
4103 });
4104 _ = editor.update(cx, |editor, window, cx| {
4105 editor.change_selections(None, window, cx, |s| {
4106 s.select_display_ranges([
4107 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4108 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4109 ])
4110 });
4111 editor.duplicate_selection(&DuplicateSelection, window, cx);
4112 assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
4113 assert_eq!(
4114 editor.selections.display_ranges(cx),
4115 vec![
4116 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4117 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
4118 ]
4119 );
4120 });
4121}
4122
4123#[gpui::test]
4124fn test_move_line_up_down(cx: &mut TestAppContext) {
4125 init_test(cx, |_| {});
4126
4127 let editor = cx.add_window(|window, cx| {
4128 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4129 build_editor(buffer, window, cx)
4130 });
4131 _ = editor.update(cx, |editor, window, cx| {
4132 editor.fold_creases(
4133 vec![
4134 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4135 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4136 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4137 ],
4138 true,
4139 window,
4140 cx,
4141 );
4142 editor.change_selections(None, window, cx, |s| {
4143 s.select_display_ranges([
4144 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4145 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4146 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4147 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
4148 ])
4149 });
4150 assert_eq!(
4151 editor.display_text(cx),
4152 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
4153 );
4154
4155 editor.move_line_up(&MoveLineUp, window, cx);
4156 assert_eq!(
4157 editor.display_text(cx),
4158 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
4159 );
4160 assert_eq!(
4161 editor.selections.display_ranges(cx),
4162 vec![
4163 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4164 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4165 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
4166 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
4167 ]
4168 );
4169 });
4170
4171 _ = editor.update(cx, |editor, window, cx| {
4172 editor.move_line_down(&MoveLineDown, window, cx);
4173 assert_eq!(
4174 editor.display_text(cx),
4175 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
4176 );
4177 assert_eq!(
4178 editor.selections.display_ranges(cx),
4179 vec![
4180 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
4181 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4182 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4183 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
4184 ]
4185 );
4186 });
4187
4188 _ = editor.update(cx, |editor, window, cx| {
4189 editor.move_line_down(&MoveLineDown, window, cx);
4190 assert_eq!(
4191 editor.display_text(cx),
4192 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
4193 );
4194 assert_eq!(
4195 editor.selections.display_ranges(cx),
4196 vec![
4197 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4198 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4199 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4200 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
4201 ]
4202 );
4203 });
4204
4205 _ = editor.update(cx, |editor, window, cx| {
4206 editor.move_line_up(&MoveLineUp, window, cx);
4207 assert_eq!(
4208 editor.display_text(cx),
4209 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
4210 );
4211 assert_eq!(
4212 editor.selections.display_ranges(cx),
4213 vec![
4214 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
4215 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4216 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
4217 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
4218 ]
4219 );
4220 });
4221}
4222
4223#[gpui::test]
4224fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
4225 init_test(cx, |_| {});
4226
4227 let editor = cx.add_window(|window, cx| {
4228 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4229 build_editor(buffer, window, cx)
4230 });
4231 _ = editor.update(cx, |editor, window, cx| {
4232 let snapshot = editor.buffer.read(cx).snapshot(cx);
4233 editor.insert_blocks(
4234 [BlockProperties {
4235 style: BlockStyle::Fixed,
4236 placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
4237 height: 1,
4238 render: Arc::new(|_| div().into_any()),
4239 priority: 0,
4240 }],
4241 Some(Autoscroll::fit()),
4242 cx,
4243 );
4244 editor.change_selections(None, window, cx, |s| {
4245 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
4246 });
4247 editor.move_line_down(&MoveLineDown, window, cx);
4248 });
4249}
4250
4251#[gpui::test]
4252async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
4253 init_test(cx, |_| {});
4254
4255 let mut cx = EditorTestContext::new(cx).await;
4256 cx.set_state(
4257 &"
4258 ˇzero
4259 one
4260 two
4261 three
4262 four
4263 five
4264 "
4265 .unindent(),
4266 );
4267
4268 // Create a four-line block that replaces three lines of text.
4269 cx.update_editor(|editor, window, cx| {
4270 let snapshot = editor.snapshot(window, cx);
4271 let snapshot = &snapshot.buffer_snapshot;
4272 let placement = BlockPlacement::Replace(
4273 snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
4274 );
4275 editor.insert_blocks(
4276 [BlockProperties {
4277 placement,
4278 height: 4,
4279 style: BlockStyle::Sticky,
4280 render: Arc::new(|_| gpui::div().into_any_element()),
4281 priority: 0,
4282 }],
4283 None,
4284 cx,
4285 );
4286 });
4287
4288 // Move down so that the cursor touches the block.
4289 cx.update_editor(|editor, window, cx| {
4290 editor.move_down(&Default::default(), window, cx);
4291 });
4292 cx.assert_editor_state(
4293 &"
4294 zero
4295 «one
4296 two
4297 threeˇ»
4298 four
4299 five
4300 "
4301 .unindent(),
4302 );
4303
4304 // Move down past the block.
4305 cx.update_editor(|editor, window, cx| {
4306 editor.move_down(&Default::default(), window, cx);
4307 });
4308 cx.assert_editor_state(
4309 &"
4310 zero
4311 one
4312 two
4313 three
4314 ˇfour
4315 five
4316 "
4317 .unindent(),
4318 );
4319}
4320
4321#[gpui::test]
4322fn test_transpose(cx: &mut TestAppContext) {
4323 init_test(cx, |_| {});
4324
4325 _ = cx.add_window(|window, cx| {
4326 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
4327 editor.set_style(EditorStyle::default(), window, cx);
4328 editor.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
4329 editor.transpose(&Default::default(), window, cx);
4330 assert_eq!(editor.text(cx), "bac");
4331 assert_eq!(editor.selections.ranges(cx), [2..2]);
4332
4333 editor.transpose(&Default::default(), window, cx);
4334 assert_eq!(editor.text(cx), "bca");
4335 assert_eq!(editor.selections.ranges(cx), [3..3]);
4336
4337 editor.transpose(&Default::default(), window, cx);
4338 assert_eq!(editor.text(cx), "bac");
4339 assert_eq!(editor.selections.ranges(cx), [3..3]);
4340
4341 editor
4342 });
4343
4344 _ = cx.add_window(|window, cx| {
4345 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
4346 editor.set_style(EditorStyle::default(), window, cx);
4347 editor.change_selections(None, window, cx, |s| s.select_ranges([3..3]));
4348 editor.transpose(&Default::default(), window, cx);
4349 assert_eq!(editor.text(cx), "acb\nde");
4350 assert_eq!(editor.selections.ranges(cx), [3..3]);
4351
4352 editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
4353 editor.transpose(&Default::default(), window, cx);
4354 assert_eq!(editor.text(cx), "acbd\ne");
4355 assert_eq!(editor.selections.ranges(cx), [5..5]);
4356
4357 editor.transpose(&Default::default(), window, cx);
4358 assert_eq!(editor.text(cx), "acbde\n");
4359 assert_eq!(editor.selections.ranges(cx), [6..6]);
4360
4361 editor.transpose(&Default::default(), window, cx);
4362 assert_eq!(editor.text(cx), "acbd\ne");
4363 assert_eq!(editor.selections.ranges(cx), [6..6]);
4364
4365 editor
4366 });
4367
4368 _ = cx.add_window(|window, cx| {
4369 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
4370 editor.set_style(EditorStyle::default(), window, cx);
4371 editor.change_selections(None, window, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
4372 editor.transpose(&Default::default(), window, cx);
4373 assert_eq!(editor.text(cx), "bacd\ne");
4374 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
4375
4376 editor.transpose(&Default::default(), window, cx);
4377 assert_eq!(editor.text(cx), "bcade\n");
4378 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
4379
4380 editor.transpose(&Default::default(), window, cx);
4381 assert_eq!(editor.text(cx), "bcda\ne");
4382 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4383
4384 editor.transpose(&Default::default(), window, cx);
4385 assert_eq!(editor.text(cx), "bcade\n");
4386 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4387
4388 editor.transpose(&Default::default(), window, cx);
4389 assert_eq!(editor.text(cx), "bcaed\n");
4390 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
4391
4392 editor
4393 });
4394
4395 _ = cx.add_window(|window, cx| {
4396 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
4397 editor.set_style(EditorStyle::default(), window, cx);
4398 editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
4399 editor.transpose(&Default::default(), window, cx);
4400 assert_eq!(editor.text(cx), "🏀🍐✋");
4401 assert_eq!(editor.selections.ranges(cx), [8..8]);
4402
4403 editor.transpose(&Default::default(), window, cx);
4404 assert_eq!(editor.text(cx), "🏀✋🍐");
4405 assert_eq!(editor.selections.ranges(cx), [11..11]);
4406
4407 editor.transpose(&Default::default(), window, cx);
4408 assert_eq!(editor.text(cx), "🏀🍐✋");
4409 assert_eq!(editor.selections.ranges(cx), [11..11]);
4410
4411 editor
4412 });
4413}
4414
4415#[gpui::test]
4416async fn test_rewrap(cx: &mut TestAppContext) {
4417 init_test(cx, |settings| {
4418 settings.languages.extend([
4419 (
4420 "Markdown".into(),
4421 LanguageSettingsContent {
4422 allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
4423 ..Default::default()
4424 },
4425 ),
4426 (
4427 "Plain Text".into(),
4428 LanguageSettingsContent {
4429 allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
4430 ..Default::default()
4431 },
4432 ),
4433 ])
4434 });
4435
4436 let mut cx = EditorTestContext::new(cx).await;
4437
4438 let language_with_c_comments = Arc::new(Language::new(
4439 LanguageConfig {
4440 line_comments: vec!["// ".into()],
4441 ..LanguageConfig::default()
4442 },
4443 None,
4444 ));
4445 let language_with_pound_comments = Arc::new(Language::new(
4446 LanguageConfig {
4447 line_comments: vec!["# ".into()],
4448 ..LanguageConfig::default()
4449 },
4450 None,
4451 ));
4452 let markdown_language = Arc::new(Language::new(
4453 LanguageConfig {
4454 name: "Markdown".into(),
4455 ..LanguageConfig::default()
4456 },
4457 None,
4458 ));
4459 let language_with_doc_comments = Arc::new(Language::new(
4460 LanguageConfig {
4461 line_comments: vec!["// ".into(), "/// ".into()],
4462 ..LanguageConfig::default()
4463 },
4464 Some(tree_sitter_rust::LANGUAGE.into()),
4465 ));
4466
4467 let plaintext_language = Arc::new(Language::new(
4468 LanguageConfig {
4469 name: "Plain Text".into(),
4470 ..LanguageConfig::default()
4471 },
4472 None,
4473 ));
4474
4475 assert_rewrap(
4476 indoc! {"
4477 // ˇ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.
4478 "},
4479 indoc! {"
4480 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4481 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4482 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4483 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4484 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4485 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4486 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4487 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4488 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4489 // porttitor id. Aliquam id accumsan eros.
4490 "},
4491 language_with_c_comments.clone(),
4492 &mut cx,
4493 );
4494
4495 // Test that rewrapping works inside of a selection
4496 assert_rewrap(
4497 indoc! {"
4498 «// 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.ˇ»
4499 "},
4500 indoc! {"
4501 «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4502 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4503 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4504 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4505 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4506 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4507 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4508 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4509 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4510 // porttitor id. Aliquam id accumsan eros.ˇ»
4511 "},
4512 language_with_c_comments.clone(),
4513 &mut cx,
4514 );
4515
4516 // Test that cursors that expand to the same region are collapsed.
4517 assert_rewrap(
4518 indoc! {"
4519 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4520 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4521 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4522 // ˇ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.
4523 "},
4524 indoc! {"
4525 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4526 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4527 // auctor, eu lacinia sapien scelerisque. ˇVivamus sit amet neque et quam
4528 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4529 // Pellentesque odio lectus, iaculis ac volutpat et, ˇblandit quis urna. Sed
4530 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4531 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4532 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4533 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4534 // porttitor id. Aliquam id accumsan eros.
4535 "},
4536 language_with_c_comments.clone(),
4537 &mut cx,
4538 );
4539
4540 // Test that non-contiguous selections are treated separately.
4541 assert_rewrap(
4542 indoc! {"
4543 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4544 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4545 //
4546 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4547 // ˇ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.
4548 "},
4549 indoc! {"
4550 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4551 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4552 // auctor, eu lacinia sapien scelerisque.
4553 //
4554 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
4555 // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4556 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
4557 // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
4558 // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
4559 // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
4560 // vulputate turpis porttitor id. Aliquam id accumsan eros.
4561 "},
4562 language_with_c_comments.clone(),
4563 &mut cx,
4564 );
4565
4566 // Test that different comment prefixes are supported.
4567 assert_rewrap(
4568 indoc! {"
4569 # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4570 "},
4571 indoc! {"
4572 # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4573 # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4574 # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4575 # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4576 # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
4577 # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
4578 # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
4579 # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
4580 # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
4581 # accumsan eros.
4582 "},
4583 language_with_pound_comments.clone(),
4584 &mut cx,
4585 );
4586
4587 // Test that rewrapping is ignored outside of comments in most languages.
4588 assert_rewrap(
4589 indoc! {"
4590 /// Adds two numbers.
4591 /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4592 fn add(a: u32, b: u32) -> u32 {
4593 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ˇ
4594 }
4595 "},
4596 indoc! {"
4597 /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
4598 /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4599 fn add(a: u32, b: u32) -> u32 {
4600 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ˇ
4601 }
4602 "},
4603 language_with_doc_comments.clone(),
4604 &mut cx,
4605 );
4606
4607 // Test that rewrapping works in Markdown and Plain Text languages.
4608 assert_rewrap(
4609 indoc! {"
4610 # Hello
4611
4612 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.
4613 "},
4614 indoc! {"
4615 # Hello
4616
4617 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4618 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4619 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4620 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4621 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4622 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4623 Integer sit amet scelerisque nisi.
4624 "},
4625 markdown_language,
4626 &mut cx,
4627 );
4628
4629 assert_rewrap(
4630 indoc! {"
4631 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.
4632 "},
4633 indoc! {"
4634 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4635 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4636 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4637 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4638 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4639 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4640 Integer sit amet scelerisque nisi.
4641 "},
4642 plaintext_language,
4643 &mut cx,
4644 );
4645
4646 // Test rewrapping unaligned comments in a selection.
4647 assert_rewrap(
4648 indoc! {"
4649 fn foo() {
4650 if true {
4651 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4652 // Praesent semper egestas tellus id dignissim.ˇ»
4653 do_something();
4654 } else {
4655 //
4656 }
4657 }
4658 "},
4659 indoc! {"
4660 fn foo() {
4661 if true {
4662 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4663 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4664 // egestas tellus id dignissim.ˇ»
4665 do_something();
4666 } else {
4667 //
4668 }
4669 }
4670 "},
4671 language_with_doc_comments.clone(),
4672 &mut cx,
4673 );
4674
4675 assert_rewrap(
4676 indoc! {"
4677 fn foo() {
4678 if true {
4679 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4680 // Praesent semper egestas tellus id dignissim.»
4681 do_something();
4682 } else {
4683 //
4684 }
4685
4686 }
4687 "},
4688 indoc! {"
4689 fn foo() {
4690 if true {
4691 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4692 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4693 // egestas tellus id dignissim.»
4694 do_something();
4695 } else {
4696 //
4697 }
4698
4699 }
4700 "},
4701 language_with_doc_comments.clone(),
4702 &mut cx,
4703 );
4704
4705 #[track_caller]
4706 fn assert_rewrap(
4707 unwrapped_text: &str,
4708 wrapped_text: &str,
4709 language: Arc<Language>,
4710 cx: &mut EditorTestContext,
4711 ) {
4712 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4713 cx.set_state(unwrapped_text);
4714 cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
4715 cx.assert_editor_state(wrapped_text);
4716 }
4717}
4718
4719#[gpui::test]
4720async fn test_hard_wrap(cx: &mut TestAppContext) {
4721 init_test(cx, |_| {});
4722 let mut cx = EditorTestContext::new(cx).await;
4723
4724 cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
4725 cx.update_editor(|editor, _, cx| {
4726 editor.set_hard_wrap(Some(14), cx);
4727 });
4728
4729 cx.set_state(indoc!(
4730 "
4731 one two three ˇ
4732 "
4733 ));
4734 cx.simulate_input("four");
4735 cx.run_until_parked();
4736
4737 cx.assert_editor_state(indoc!(
4738 "
4739 one two three
4740 fourˇ
4741 "
4742 ));
4743
4744 cx.update_editor(|editor, window, cx| {
4745 editor.newline(&Default::default(), window, cx);
4746 });
4747 cx.run_until_parked();
4748 cx.assert_editor_state(indoc!(
4749 "
4750 one two three
4751 four
4752 ˇ
4753 "
4754 ));
4755
4756 cx.simulate_input("five");
4757 cx.run_until_parked();
4758 cx.assert_editor_state(indoc!(
4759 "
4760 one two three
4761 four
4762 fiveˇ
4763 "
4764 ));
4765
4766 cx.update_editor(|editor, window, cx| {
4767 editor.newline(&Default::default(), window, cx);
4768 });
4769 cx.run_until_parked();
4770 cx.simulate_input("# ");
4771 cx.run_until_parked();
4772 cx.assert_editor_state(indoc!(
4773 "
4774 one two three
4775 four
4776 five
4777 # ˇ
4778 "
4779 ));
4780
4781 cx.update_editor(|editor, window, cx| {
4782 editor.newline(&Default::default(), window, cx);
4783 });
4784 cx.run_until_parked();
4785 cx.assert_editor_state(indoc!(
4786 "
4787 one two three
4788 four
4789 five
4790 #\x20
4791 #ˇ
4792 "
4793 ));
4794
4795 cx.simulate_input(" 6");
4796 cx.run_until_parked();
4797 cx.assert_editor_state(indoc!(
4798 "
4799 one two three
4800 four
4801 five
4802 #
4803 # 6ˇ
4804 "
4805 ));
4806}
4807
4808#[gpui::test]
4809async fn test_clipboard(cx: &mut TestAppContext) {
4810 init_test(cx, |_| {});
4811
4812 let mut cx = EditorTestContext::new(cx).await;
4813
4814 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
4815 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4816 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
4817
4818 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
4819 cx.set_state("two ˇfour ˇsix ˇ");
4820 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4821 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
4822
4823 // Paste again but with only two cursors. Since the number of cursors doesn't
4824 // match the number of slices in the clipboard, the entire clipboard text
4825 // is pasted at each cursor.
4826 cx.set_state("ˇtwo one✅ four three six five ˇ");
4827 cx.update_editor(|e, window, cx| {
4828 e.handle_input("( ", window, cx);
4829 e.paste(&Paste, window, cx);
4830 e.handle_input(") ", window, cx);
4831 });
4832 cx.assert_editor_state(
4833 &([
4834 "( one✅ ",
4835 "three ",
4836 "five ) ˇtwo one✅ four three six five ( one✅ ",
4837 "three ",
4838 "five ) ˇ",
4839 ]
4840 .join("\n")),
4841 );
4842
4843 // Cut with three selections, one of which is full-line.
4844 cx.set_state(indoc! {"
4845 1«2ˇ»3
4846 4ˇ567
4847 «8ˇ»9"});
4848 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4849 cx.assert_editor_state(indoc! {"
4850 1ˇ3
4851 ˇ9"});
4852
4853 // Paste with three selections, noticing how the copied selection that was full-line
4854 // gets inserted before the second cursor.
4855 cx.set_state(indoc! {"
4856 1ˇ3
4857 9ˇ
4858 «oˇ»ne"});
4859 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4860 cx.assert_editor_state(indoc! {"
4861 12ˇ3
4862 4567
4863 9ˇ
4864 8ˇne"});
4865
4866 // Copy with a single cursor only, which writes the whole line into the clipboard.
4867 cx.set_state(indoc! {"
4868 The quick brown
4869 fox juˇmps over
4870 the lazy dog"});
4871 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
4872 assert_eq!(
4873 cx.read_from_clipboard()
4874 .and_then(|item| item.text().as_deref().map(str::to_string)),
4875 Some("fox jumps over\n".to_string())
4876 );
4877
4878 // Paste with three selections, noticing how the copied full-line selection is inserted
4879 // before the empty selections but replaces the selection that is non-empty.
4880 cx.set_state(indoc! {"
4881 Tˇhe quick brown
4882 «foˇ»x jumps over
4883 tˇhe lazy dog"});
4884 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4885 cx.assert_editor_state(indoc! {"
4886 fox jumps over
4887 Tˇhe quick brown
4888 fox jumps over
4889 ˇx jumps over
4890 fox jumps over
4891 tˇhe lazy dog"});
4892}
4893
4894#[gpui::test]
4895async fn test_copy_trim(cx: &mut TestAppContext) {
4896 init_test(cx, |_| {});
4897
4898 let mut cx = EditorTestContext::new(cx).await;
4899 cx.set_state(
4900 r#" «for selection in selections.iter() {
4901 let mut start = selection.start;
4902 let mut end = selection.end;
4903 let is_entire_line = selection.is_empty();
4904 if is_entire_line {
4905 start = Point::new(start.row, 0);ˇ»
4906 end = cmp::min(max_point, Point::new(end.row + 1, 0));
4907 }
4908 "#,
4909 );
4910 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
4911 assert_eq!(
4912 cx.read_from_clipboard()
4913 .and_then(|item| item.text().as_deref().map(str::to_string)),
4914 Some(
4915 "for selection in selections.iter() {
4916 let mut start = selection.start;
4917 let mut end = selection.end;
4918 let is_entire_line = selection.is_empty();
4919 if is_entire_line {
4920 start = Point::new(start.row, 0);"
4921 .to_string()
4922 ),
4923 "Regular copying preserves all indentation selected",
4924 );
4925 cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
4926 assert_eq!(
4927 cx.read_from_clipboard()
4928 .and_then(|item| item.text().as_deref().map(str::to_string)),
4929 Some(
4930 "for selection in selections.iter() {
4931let mut start = selection.start;
4932let mut end = selection.end;
4933let is_entire_line = selection.is_empty();
4934if is_entire_line {
4935 start = Point::new(start.row, 0);"
4936 .to_string()
4937 ),
4938 "Copying with stripping should strip all leading whitespaces"
4939 );
4940
4941 cx.set_state(
4942 r#" « for selection in selections.iter() {
4943 let mut start = selection.start;
4944 let mut end = selection.end;
4945 let is_entire_line = selection.is_empty();
4946 if is_entire_line {
4947 start = Point::new(start.row, 0);ˇ»
4948 end = cmp::min(max_point, Point::new(end.row + 1, 0));
4949 }
4950 "#,
4951 );
4952 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
4953 assert_eq!(
4954 cx.read_from_clipboard()
4955 .and_then(|item| item.text().as_deref().map(str::to_string)),
4956 Some(
4957 " for selection in selections.iter() {
4958 let mut start = selection.start;
4959 let mut end = selection.end;
4960 let is_entire_line = selection.is_empty();
4961 if is_entire_line {
4962 start = Point::new(start.row, 0);"
4963 .to_string()
4964 ),
4965 "Regular copying preserves all indentation selected",
4966 );
4967 cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
4968 assert_eq!(
4969 cx.read_from_clipboard()
4970 .and_then(|item| item.text().as_deref().map(str::to_string)),
4971 Some(
4972 "for selection in selections.iter() {
4973let mut start = selection.start;
4974let mut end = selection.end;
4975let is_entire_line = selection.is_empty();
4976if is_entire_line {
4977 start = Point::new(start.row, 0);"
4978 .to_string()
4979 ),
4980 "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
4981 );
4982
4983 cx.set_state(
4984 r#" «ˇ for selection in selections.iter() {
4985 let mut start = selection.start;
4986 let mut end = selection.end;
4987 let is_entire_line = selection.is_empty();
4988 if is_entire_line {
4989 start = Point::new(start.row, 0);»
4990 end = cmp::min(max_point, Point::new(end.row + 1, 0));
4991 }
4992 "#,
4993 );
4994 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
4995 assert_eq!(
4996 cx.read_from_clipboard()
4997 .and_then(|item| item.text().as_deref().map(str::to_string)),
4998 Some(
4999 " for selection in selections.iter() {
5000 let mut start = selection.start;
5001 let mut end = selection.end;
5002 let is_entire_line = selection.is_empty();
5003 if is_entire_line {
5004 start = Point::new(start.row, 0);"
5005 .to_string()
5006 ),
5007 "Regular copying for reverse selection works the same",
5008 );
5009 cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
5010 assert_eq!(
5011 cx.read_from_clipboard()
5012 .and_then(|item| item.text().as_deref().map(str::to_string)),
5013 Some(
5014 "for selection in selections.iter() {
5015let mut start = selection.start;
5016let mut end = selection.end;
5017let is_entire_line = selection.is_empty();
5018if is_entire_line {
5019 start = Point::new(start.row, 0);"
5020 .to_string()
5021 ),
5022 "Copying with stripping for reverse selection works the same"
5023 );
5024
5025 cx.set_state(
5026 r#" for selection «in selections.iter() {
5027 let mut start = selection.start;
5028 let mut end = selection.end;
5029 let is_entire_line = selection.is_empty();
5030 if is_entire_line {
5031 start = Point::new(start.row, 0);ˇ»
5032 end = cmp::min(max_point, Point::new(end.row + 1, 0));
5033 }
5034 "#,
5035 );
5036 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
5037 assert_eq!(
5038 cx.read_from_clipboard()
5039 .and_then(|item| item.text().as_deref().map(str::to_string)),
5040 Some(
5041 "in selections.iter() {
5042 let mut start = selection.start;
5043 let mut end = selection.end;
5044 let is_entire_line = selection.is_empty();
5045 if is_entire_line {
5046 start = Point::new(start.row, 0);"
5047 .to_string()
5048 ),
5049 "When selecting past the indent, the copying works as usual",
5050 );
5051 cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
5052 assert_eq!(
5053 cx.read_from_clipboard()
5054 .and_then(|item| item.text().as_deref().map(str::to_string)),
5055 Some(
5056 "in selections.iter() {
5057 let mut start = selection.start;
5058 let mut end = selection.end;
5059 let is_entire_line = selection.is_empty();
5060 if is_entire_line {
5061 start = Point::new(start.row, 0);"
5062 .to_string()
5063 ),
5064 "When selecting past the indent, nothing is trimmed"
5065 );
5066}
5067
5068#[gpui::test]
5069async fn test_paste_multiline(cx: &mut TestAppContext) {
5070 init_test(cx, |_| {});
5071
5072 let mut cx = EditorTestContext::new(cx).await;
5073 cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
5074
5075 // Cut an indented block, without the leading whitespace.
5076 cx.set_state(indoc! {"
5077 const a: B = (
5078 c(),
5079 «d(
5080 e,
5081 f
5082 )ˇ»
5083 );
5084 "});
5085 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
5086 cx.assert_editor_state(indoc! {"
5087 const a: B = (
5088 c(),
5089 ˇ
5090 );
5091 "});
5092
5093 // Paste it at the same position.
5094 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
5095 cx.assert_editor_state(indoc! {"
5096 const a: B = (
5097 c(),
5098 d(
5099 e,
5100 f
5101 )ˇ
5102 );
5103 "});
5104
5105 // Paste it at a line with a lower indent level.
5106 cx.set_state(indoc! {"
5107 ˇ
5108 const a: B = (
5109 c(),
5110 );
5111 "});
5112 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
5113 cx.assert_editor_state(indoc! {"
5114 d(
5115 e,
5116 f
5117 )ˇ
5118 const a: B = (
5119 c(),
5120 );
5121 "});
5122
5123 // Cut an indented block, with the leading whitespace.
5124 cx.set_state(indoc! {"
5125 const a: B = (
5126 c(),
5127 « d(
5128 e,
5129 f
5130 )
5131 ˇ»);
5132 "});
5133 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
5134 cx.assert_editor_state(indoc! {"
5135 const a: B = (
5136 c(),
5137 ˇ);
5138 "});
5139
5140 // Paste it at the same position.
5141 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
5142 cx.assert_editor_state(indoc! {"
5143 const a: B = (
5144 c(),
5145 d(
5146 e,
5147 f
5148 )
5149 ˇ);
5150 "});
5151
5152 // Paste it at a line with a higher indent level.
5153 cx.set_state(indoc! {"
5154 const a: B = (
5155 c(),
5156 d(
5157 e,
5158 fˇ
5159 )
5160 );
5161 "});
5162 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
5163 cx.assert_editor_state(indoc! {"
5164 const a: B = (
5165 c(),
5166 d(
5167 e,
5168 f d(
5169 e,
5170 f
5171 )
5172 ˇ
5173 )
5174 );
5175 "});
5176
5177 // Copy an indented block, starting mid-line
5178 cx.set_state(indoc! {"
5179 const a: B = (
5180 c(),
5181 somethin«g(
5182 e,
5183 f
5184 )ˇ»
5185 );
5186 "});
5187 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
5188
5189 // Paste it on a line with a lower indent level
5190 cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
5191 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
5192 cx.assert_editor_state(indoc! {"
5193 const a: B = (
5194 c(),
5195 something(
5196 e,
5197 f
5198 )
5199 );
5200 g(
5201 e,
5202 f
5203 )ˇ"});
5204}
5205
5206#[gpui::test]
5207async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
5208 init_test(cx, |_| {});
5209
5210 cx.write_to_clipboard(ClipboardItem::new_string(
5211 " d(\n e\n );\n".into(),
5212 ));
5213
5214 let mut cx = EditorTestContext::new(cx).await;
5215 cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
5216
5217 cx.set_state(indoc! {"
5218 fn a() {
5219 b();
5220 if c() {
5221 ˇ
5222 }
5223 }
5224 "});
5225
5226 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
5227 cx.assert_editor_state(indoc! {"
5228 fn a() {
5229 b();
5230 if c() {
5231 d(
5232 e
5233 );
5234 ˇ
5235 }
5236 }
5237 "});
5238
5239 cx.set_state(indoc! {"
5240 fn a() {
5241 b();
5242 ˇ
5243 }
5244 "});
5245
5246 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
5247 cx.assert_editor_state(indoc! {"
5248 fn a() {
5249 b();
5250 d(
5251 e
5252 );
5253 ˇ
5254 }
5255 "});
5256}
5257
5258#[gpui::test]
5259fn test_select_all(cx: &mut TestAppContext) {
5260 init_test(cx, |_| {});
5261
5262 let editor = cx.add_window(|window, cx| {
5263 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
5264 build_editor(buffer, window, cx)
5265 });
5266 _ = editor.update(cx, |editor, window, cx| {
5267 editor.select_all(&SelectAll, window, cx);
5268 assert_eq!(
5269 editor.selections.display_ranges(cx),
5270 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
5271 );
5272 });
5273}
5274
5275#[gpui::test]
5276fn test_select_line(cx: &mut TestAppContext) {
5277 init_test(cx, |_| {});
5278
5279 let editor = cx.add_window(|window, cx| {
5280 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
5281 build_editor(buffer, window, cx)
5282 });
5283 _ = editor.update(cx, |editor, window, cx| {
5284 editor.change_selections(None, window, cx, |s| {
5285 s.select_display_ranges([
5286 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5287 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
5288 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
5289 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
5290 ])
5291 });
5292 editor.select_line(&SelectLine, window, cx);
5293 assert_eq!(
5294 editor.selections.display_ranges(cx),
5295 vec![
5296 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
5297 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
5298 ]
5299 );
5300 });
5301
5302 _ = editor.update(cx, |editor, window, cx| {
5303 editor.select_line(&SelectLine, window, cx);
5304 assert_eq!(
5305 editor.selections.display_ranges(cx),
5306 vec![
5307 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
5308 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
5309 ]
5310 );
5311 });
5312
5313 _ = editor.update(cx, |editor, window, cx| {
5314 editor.select_line(&SelectLine, window, cx);
5315 assert_eq!(
5316 editor.selections.display_ranges(cx),
5317 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
5318 );
5319 });
5320}
5321
5322#[gpui::test]
5323async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
5324 init_test(cx, |_| {});
5325 let mut cx = EditorTestContext::new(cx).await;
5326
5327 #[track_caller]
5328 fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
5329 cx.set_state(initial_state);
5330 cx.update_editor(|e, window, cx| {
5331 e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
5332 });
5333 cx.assert_editor_state(expected_state);
5334 }
5335
5336 // Selection starts and ends at the middle of lines, left-to-right
5337 test(
5338 &mut cx,
5339 "aa\nb«ˇb\ncc\ndd\ne»e\nff",
5340 "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
5341 );
5342 // Same thing, right-to-left
5343 test(
5344 &mut cx,
5345 "aa\nb«b\ncc\ndd\neˇ»e\nff",
5346 "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
5347 );
5348
5349 // Whole buffer, left-to-right, last line *doesn't* end with newline
5350 test(
5351 &mut cx,
5352 "«ˇaa\nbb\ncc\ndd\nee\nff»",
5353 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
5354 );
5355 // Same thing, right-to-left
5356 test(
5357 &mut cx,
5358 "«aa\nbb\ncc\ndd\nee\nffˇ»",
5359 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
5360 );
5361
5362 // Whole buffer, left-to-right, last line ends with newline
5363 test(
5364 &mut cx,
5365 "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
5366 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
5367 );
5368 // Same thing, right-to-left
5369 test(
5370 &mut cx,
5371 "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
5372 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
5373 );
5374
5375 // Starts at the end of a line, ends at the start of another
5376 test(
5377 &mut cx,
5378 "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
5379 "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
5380 );
5381}
5382
5383#[gpui::test]
5384async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
5385 init_test(cx, |_| {});
5386
5387 let editor = cx.add_window(|window, cx| {
5388 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
5389 build_editor(buffer, window, cx)
5390 });
5391
5392 // setup
5393 _ = editor.update(cx, |editor, window, cx| {
5394 editor.fold_creases(
5395 vec![
5396 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
5397 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
5398 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
5399 ],
5400 true,
5401 window,
5402 cx,
5403 );
5404 assert_eq!(
5405 editor.display_text(cx),
5406 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
5407 );
5408 });
5409
5410 _ = editor.update(cx, |editor, window, cx| {
5411 editor.change_selections(None, window, cx, |s| {
5412 s.select_display_ranges([
5413 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5414 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
5415 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
5416 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
5417 ])
5418 });
5419 editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
5420 assert_eq!(
5421 editor.display_text(cx),
5422 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
5423 );
5424 });
5425 EditorTestContext::for_editor(editor, cx)
5426 .await
5427 .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
5428
5429 _ = editor.update(cx, |editor, window, cx| {
5430 editor.change_selections(None, window, cx, |s| {
5431 s.select_display_ranges([
5432 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
5433 ])
5434 });
5435 editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
5436 assert_eq!(
5437 editor.display_text(cx),
5438 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
5439 );
5440 assert_eq!(
5441 editor.selections.display_ranges(cx),
5442 [
5443 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
5444 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
5445 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
5446 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
5447 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
5448 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
5449 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
5450 ]
5451 );
5452 });
5453 EditorTestContext::for_editor(editor, cx)
5454 .await
5455 .assert_editor_state(
5456 "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
5457 );
5458}
5459
5460#[gpui::test]
5461async fn test_add_selection_above_below(cx: &mut TestAppContext) {
5462 init_test(cx, |_| {});
5463
5464 let mut cx = EditorTestContext::new(cx).await;
5465
5466 cx.set_state(indoc!(
5467 r#"abc
5468 defˇghi
5469
5470 jk
5471 nlmo
5472 "#
5473 ));
5474
5475 cx.update_editor(|editor, window, cx| {
5476 editor.add_selection_above(&Default::default(), window, cx);
5477 });
5478
5479 cx.assert_editor_state(indoc!(
5480 r#"abcˇ
5481 defˇghi
5482
5483 jk
5484 nlmo
5485 "#
5486 ));
5487
5488 cx.update_editor(|editor, window, cx| {
5489 editor.add_selection_above(&Default::default(), window, cx);
5490 });
5491
5492 cx.assert_editor_state(indoc!(
5493 r#"abcˇ
5494 defˇghi
5495
5496 jk
5497 nlmo
5498 "#
5499 ));
5500
5501 cx.update_editor(|editor, window, cx| {
5502 editor.add_selection_below(&Default::default(), window, cx);
5503 });
5504
5505 cx.assert_editor_state(indoc!(
5506 r#"abc
5507 defˇghi
5508
5509 jk
5510 nlmo
5511 "#
5512 ));
5513
5514 cx.update_editor(|editor, window, cx| {
5515 editor.undo_selection(&Default::default(), window, cx);
5516 });
5517
5518 cx.assert_editor_state(indoc!(
5519 r#"abcˇ
5520 defˇghi
5521
5522 jk
5523 nlmo
5524 "#
5525 ));
5526
5527 cx.update_editor(|editor, window, cx| {
5528 editor.redo_selection(&Default::default(), window, cx);
5529 });
5530
5531 cx.assert_editor_state(indoc!(
5532 r#"abc
5533 defˇghi
5534
5535 jk
5536 nlmo
5537 "#
5538 ));
5539
5540 cx.update_editor(|editor, window, cx| {
5541 editor.add_selection_below(&Default::default(), window, cx);
5542 });
5543
5544 cx.assert_editor_state(indoc!(
5545 r#"abc
5546 defˇghi
5547
5548 jk
5549 nlmˇo
5550 "#
5551 ));
5552
5553 cx.update_editor(|editor, window, cx| {
5554 editor.add_selection_below(&Default::default(), window, cx);
5555 });
5556
5557 cx.assert_editor_state(indoc!(
5558 r#"abc
5559 defˇghi
5560
5561 jk
5562 nlmˇo
5563 "#
5564 ));
5565
5566 // change selections
5567 cx.set_state(indoc!(
5568 r#"abc
5569 def«ˇg»hi
5570
5571 jk
5572 nlmo
5573 "#
5574 ));
5575
5576 cx.update_editor(|editor, window, cx| {
5577 editor.add_selection_below(&Default::default(), window, cx);
5578 });
5579
5580 cx.assert_editor_state(indoc!(
5581 r#"abc
5582 def«ˇg»hi
5583
5584 jk
5585 nlm«ˇo»
5586 "#
5587 ));
5588
5589 cx.update_editor(|editor, window, cx| {
5590 editor.add_selection_below(&Default::default(), window, cx);
5591 });
5592
5593 cx.assert_editor_state(indoc!(
5594 r#"abc
5595 def«ˇg»hi
5596
5597 jk
5598 nlm«ˇo»
5599 "#
5600 ));
5601
5602 cx.update_editor(|editor, window, cx| {
5603 editor.add_selection_above(&Default::default(), window, cx);
5604 });
5605
5606 cx.assert_editor_state(indoc!(
5607 r#"abc
5608 def«ˇg»hi
5609
5610 jk
5611 nlmo
5612 "#
5613 ));
5614
5615 cx.update_editor(|editor, window, cx| {
5616 editor.add_selection_above(&Default::default(), window, cx);
5617 });
5618
5619 cx.assert_editor_state(indoc!(
5620 r#"abc
5621 def«ˇg»hi
5622
5623 jk
5624 nlmo
5625 "#
5626 ));
5627
5628 // Change selections again
5629 cx.set_state(indoc!(
5630 r#"a«bc
5631 defgˇ»hi
5632
5633 jk
5634 nlmo
5635 "#
5636 ));
5637
5638 cx.update_editor(|editor, window, cx| {
5639 editor.add_selection_below(&Default::default(), window, cx);
5640 });
5641
5642 cx.assert_editor_state(indoc!(
5643 r#"a«bcˇ»
5644 d«efgˇ»hi
5645
5646 j«kˇ»
5647 nlmo
5648 "#
5649 ));
5650
5651 cx.update_editor(|editor, window, cx| {
5652 editor.add_selection_below(&Default::default(), window, cx);
5653 });
5654 cx.assert_editor_state(indoc!(
5655 r#"a«bcˇ»
5656 d«efgˇ»hi
5657
5658 j«kˇ»
5659 n«lmoˇ»
5660 "#
5661 ));
5662 cx.update_editor(|editor, window, cx| {
5663 editor.add_selection_above(&Default::default(), window, cx);
5664 });
5665
5666 cx.assert_editor_state(indoc!(
5667 r#"a«bcˇ»
5668 d«efgˇ»hi
5669
5670 j«kˇ»
5671 nlmo
5672 "#
5673 ));
5674
5675 // Change selections again
5676 cx.set_state(indoc!(
5677 r#"abc
5678 d«ˇefghi
5679
5680 jk
5681 nlm»o
5682 "#
5683 ));
5684
5685 cx.update_editor(|editor, window, cx| {
5686 editor.add_selection_above(&Default::default(), window, cx);
5687 });
5688
5689 cx.assert_editor_state(indoc!(
5690 r#"a«ˇbc»
5691 d«ˇef»ghi
5692
5693 j«ˇk»
5694 n«ˇlm»o
5695 "#
5696 ));
5697
5698 cx.update_editor(|editor, window, cx| {
5699 editor.add_selection_below(&Default::default(), window, cx);
5700 });
5701
5702 cx.assert_editor_state(indoc!(
5703 r#"abc
5704 d«ˇef»ghi
5705
5706 j«ˇk»
5707 n«ˇlm»o
5708 "#
5709 ));
5710}
5711
5712#[gpui::test]
5713async fn test_select_next(cx: &mut TestAppContext) {
5714 init_test(cx, |_| {});
5715
5716 let mut cx = EditorTestContext::new(cx).await;
5717 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5718
5719 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5720 .unwrap();
5721 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5722
5723 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5724 .unwrap();
5725 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5726
5727 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5728 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5729
5730 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5731 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5732
5733 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5734 .unwrap();
5735 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5736
5737 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5738 .unwrap();
5739 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5740}
5741
5742#[gpui::test]
5743async fn test_select_all_matches(cx: &mut TestAppContext) {
5744 init_test(cx, |_| {});
5745
5746 let mut cx = EditorTestContext::new(cx).await;
5747
5748 // Test caret-only selections
5749 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5750 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5751 .unwrap();
5752 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5753
5754 // Test left-to-right selections
5755 cx.set_state("abc\n«abcˇ»\nabc");
5756 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5757 .unwrap();
5758 cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
5759
5760 // Test right-to-left selections
5761 cx.set_state("abc\n«ˇabc»\nabc");
5762 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5763 .unwrap();
5764 cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
5765
5766 // Test selecting whitespace with caret selection
5767 cx.set_state("abc\nˇ abc\nabc");
5768 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5769 .unwrap();
5770 cx.assert_editor_state("abc\n« ˇ»abc\nabc");
5771
5772 // Test selecting whitespace with left-to-right selection
5773 cx.set_state("abc\n«ˇ »abc\nabc");
5774 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5775 .unwrap();
5776 cx.assert_editor_state("abc\n«ˇ »abc\nabc");
5777
5778 // Test no matches with right-to-left selection
5779 cx.set_state("abc\n« ˇ»abc\nabc");
5780 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5781 .unwrap();
5782 cx.assert_editor_state("abc\n« ˇ»abc\nabc");
5783}
5784
5785#[gpui::test]
5786async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
5787 init_test(cx, |_| {});
5788
5789 let mut cx = EditorTestContext::new(cx).await;
5790 cx.set_state(
5791 r#"let foo = 2;
5792lˇet foo = 2;
5793let fooˇ = 2;
5794let foo = 2;
5795let foo = ˇ2;"#,
5796 );
5797
5798 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5799 .unwrap();
5800 cx.assert_editor_state(
5801 r#"let foo = 2;
5802«letˇ» foo = 2;
5803let «fooˇ» = 2;
5804let foo = 2;
5805let foo = «2ˇ»;"#,
5806 );
5807
5808 // noop for multiple selections with different contents
5809 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5810 .unwrap();
5811 cx.assert_editor_state(
5812 r#"let foo = 2;
5813«letˇ» foo = 2;
5814let «fooˇ» = 2;
5815let foo = 2;
5816let foo = «2ˇ»;"#,
5817 );
5818}
5819
5820#[gpui::test]
5821async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
5822 init_test(cx, |_| {});
5823
5824 let mut cx =
5825 EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
5826
5827 cx.assert_editor_state(indoc! {"
5828 ˇbbb
5829 ccc
5830
5831 bbb
5832 ccc
5833 "});
5834 cx.dispatch_action(SelectPrevious::default());
5835 cx.assert_editor_state(indoc! {"
5836 «bbbˇ»
5837 ccc
5838
5839 bbb
5840 ccc
5841 "});
5842 cx.dispatch_action(SelectPrevious::default());
5843 cx.assert_editor_state(indoc! {"
5844 «bbbˇ»
5845 ccc
5846
5847 «bbbˇ»
5848 ccc
5849 "});
5850}
5851
5852#[gpui::test]
5853async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
5854 init_test(cx, |_| {});
5855
5856 let mut cx = EditorTestContext::new(cx).await;
5857 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5858
5859 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5860 .unwrap();
5861 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5862
5863 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5864 .unwrap();
5865 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5866
5867 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5868 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5869
5870 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5871 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5872
5873 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5874 .unwrap();
5875 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
5876
5877 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5878 .unwrap();
5879 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
5880
5881 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5882 .unwrap();
5883 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5884}
5885
5886#[gpui::test]
5887async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
5888 init_test(cx, |_| {});
5889
5890 let mut cx = EditorTestContext::new(cx).await;
5891 cx.set_state("aˇ");
5892
5893 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5894 .unwrap();
5895 cx.assert_editor_state("«aˇ»");
5896 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5897 .unwrap();
5898 cx.assert_editor_state("«aˇ»");
5899}
5900
5901#[gpui::test]
5902async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
5903 init_test(cx, |_| {});
5904
5905 let mut cx = EditorTestContext::new(cx).await;
5906 cx.set_state(
5907 r#"let foo = 2;
5908lˇet foo = 2;
5909let fooˇ = 2;
5910let foo = 2;
5911let foo = ˇ2;"#,
5912 );
5913
5914 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5915 .unwrap();
5916 cx.assert_editor_state(
5917 r#"let foo = 2;
5918«letˇ» foo = 2;
5919let «fooˇ» = 2;
5920let foo = 2;
5921let foo = «2ˇ»;"#,
5922 );
5923
5924 // noop for multiple selections with different contents
5925 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5926 .unwrap();
5927 cx.assert_editor_state(
5928 r#"let foo = 2;
5929«letˇ» foo = 2;
5930let «fooˇ» = 2;
5931let foo = 2;
5932let foo = «2ˇ»;"#,
5933 );
5934}
5935
5936#[gpui::test]
5937async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
5938 init_test(cx, |_| {});
5939
5940 let mut cx = EditorTestContext::new(cx).await;
5941 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
5942
5943 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5944 .unwrap();
5945 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5946
5947 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5948 .unwrap();
5949 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5950
5951 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5952 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5953
5954 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5955 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5956
5957 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5958 .unwrap();
5959 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
5960
5961 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5962 .unwrap();
5963 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5964}
5965
5966#[gpui::test]
5967async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
5968 init_test(cx, |_| {});
5969
5970 let language = Arc::new(Language::new(
5971 LanguageConfig::default(),
5972 Some(tree_sitter_rust::LANGUAGE.into()),
5973 ));
5974
5975 let text = r#"
5976 use mod1::mod2::{mod3, mod4};
5977
5978 fn fn_1(param1: bool, param2: &str) {
5979 let var1 = "text";
5980 }
5981 "#
5982 .unindent();
5983
5984 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
5985 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
5986 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
5987
5988 editor
5989 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5990 .await;
5991
5992 editor.update_in(cx, |editor, window, cx| {
5993 editor.change_selections(None, window, cx, |s| {
5994 s.select_display_ranges([
5995 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
5996 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
5997 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
5998 ]);
5999 });
6000 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6001 });
6002 editor.update(cx, |editor, cx| {
6003 assert_text_with_selections(
6004 editor,
6005 indoc! {r#"
6006 use mod1::mod2::{mod3, «mod4ˇ»};
6007
6008 fn fn_1«ˇ(param1: bool, param2: &str)» {
6009 let var1 = "«ˇtext»";
6010 }
6011 "#},
6012 cx,
6013 );
6014 });
6015
6016 editor.update_in(cx, |editor, window, cx| {
6017 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6018 });
6019 editor.update(cx, |editor, cx| {
6020 assert_text_with_selections(
6021 editor,
6022 indoc! {r#"
6023 use mod1::mod2::«{mod3, mod4}ˇ»;
6024
6025 «ˇfn fn_1(param1: bool, param2: &str) {
6026 let var1 = "text";
6027 }»
6028 "#},
6029 cx,
6030 );
6031 });
6032
6033 editor.update_in(cx, |editor, window, cx| {
6034 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6035 });
6036 assert_eq!(
6037 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
6038 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
6039 );
6040
6041 // Trying to expand the selected syntax node one more time has no effect.
6042 editor.update_in(cx, |editor, window, cx| {
6043 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6044 });
6045 assert_eq!(
6046 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
6047 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
6048 );
6049
6050 editor.update_in(cx, |editor, window, cx| {
6051 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
6052 });
6053 editor.update(cx, |editor, cx| {
6054 assert_text_with_selections(
6055 editor,
6056 indoc! {r#"
6057 use mod1::mod2::«{mod3, mod4}ˇ»;
6058
6059 «ˇfn fn_1(param1: bool, param2: &str) {
6060 let var1 = "text";
6061 }»
6062 "#},
6063 cx,
6064 );
6065 });
6066
6067 editor.update_in(cx, |editor, window, cx| {
6068 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
6069 });
6070 editor.update(cx, |editor, cx| {
6071 assert_text_with_selections(
6072 editor,
6073 indoc! {r#"
6074 use mod1::mod2::{mod3, «mod4ˇ»};
6075
6076 fn fn_1«ˇ(param1: bool, param2: &str)» {
6077 let var1 = "«ˇtext»";
6078 }
6079 "#},
6080 cx,
6081 );
6082 });
6083
6084 editor.update_in(cx, |editor, window, cx| {
6085 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
6086 });
6087 editor.update(cx, |editor, cx| {
6088 assert_text_with_selections(
6089 editor,
6090 indoc! {r#"
6091 use mod1::mod2::{mod3, mo«ˇ»d4};
6092
6093 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
6094 let var1 = "te«ˇ»xt";
6095 }
6096 "#},
6097 cx,
6098 );
6099 });
6100
6101 // Trying to shrink the selected syntax node one more time has no effect.
6102 editor.update_in(cx, |editor, window, cx| {
6103 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
6104 });
6105 editor.update_in(cx, |editor, _, cx| {
6106 assert_text_with_selections(
6107 editor,
6108 indoc! {r#"
6109 use mod1::mod2::{mod3, mo«ˇ»d4};
6110
6111 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
6112 let var1 = "te«ˇ»xt";
6113 }
6114 "#},
6115 cx,
6116 );
6117 });
6118
6119 // Ensure that we keep expanding the selection if the larger selection starts or ends within
6120 // a fold.
6121 editor.update_in(cx, |editor, window, cx| {
6122 editor.fold_creases(
6123 vec![
6124 Crease::simple(
6125 Point::new(0, 21)..Point::new(0, 24),
6126 FoldPlaceholder::test(),
6127 ),
6128 Crease::simple(
6129 Point::new(3, 20)..Point::new(3, 22),
6130 FoldPlaceholder::test(),
6131 ),
6132 ],
6133 true,
6134 window,
6135 cx,
6136 );
6137 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6138 });
6139 editor.update(cx, |editor, cx| {
6140 assert_text_with_selections(
6141 editor,
6142 indoc! {r#"
6143 use mod1::mod2::«{mod3, mod4}ˇ»;
6144
6145 fn fn_1«ˇ(param1: bool, param2: &str)» {
6146 «ˇlet var1 = "text";»
6147 }
6148 "#},
6149 cx,
6150 );
6151 });
6152}
6153
6154#[gpui::test]
6155async fn test_fold_function_bodies(cx: &mut TestAppContext) {
6156 init_test(cx, |_| {});
6157
6158 let base_text = r#"
6159 impl A {
6160 // this is an uncommitted comment
6161
6162 fn b() {
6163 c();
6164 }
6165
6166 // this is another uncommitted comment
6167
6168 fn d() {
6169 // e
6170 // f
6171 }
6172 }
6173
6174 fn g() {
6175 // h
6176 }
6177 "#
6178 .unindent();
6179
6180 let text = r#"
6181 ˇimpl A {
6182
6183 fn b() {
6184 c();
6185 }
6186
6187 fn d() {
6188 // e
6189 // f
6190 }
6191 }
6192
6193 fn g() {
6194 // h
6195 }
6196 "#
6197 .unindent();
6198
6199 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
6200 cx.set_state(&text);
6201 cx.set_head_text(&base_text);
6202 cx.update_editor(|editor, window, cx| {
6203 editor.expand_all_diff_hunks(&Default::default(), window, cx);
6204 });
6205
6206 cx.assert_state_with_diff(
6207 "
6208 ˇimpl A {
6209 - // this is an uncommitted comment
6210
6211 fn b() {
6212 c();
6213 }
6214
6215 - // this is another uncommitted comment
6216 -
6217 fn d() {
6218 // e
6219 // f
6220 }
6221 }
6222
6223 fn g() {
6224 // h
6225 }
6226 "
6227 .unindent(),
6228 );
6229
6230 let expected_display_text = "
6231 impl A {
6232 // this is an uncommitted comment
6233
6234 fn b() {
6235 ⋯
6236 }
6237
6238 // this is another uncommitted comment
6239
6240 fn d() {
6241 ⋯
6242 }
6243 }
6244
6245 fn g() {
6246 ⋯
6247 }
6248 "
6249 .unindent();
6250
6251 cx.update_editor(|editor, window, cx| {
6252 editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
6253 assert_eq!(editor.display_text(cx), expected_display_text);
6254 });
6255}
6256
6257#[gpui::test]
6258async fn test_autoindent(cx: &mut TestAppContext) {
6259 init_test(cx, |_| {});
6260
6261 let language = Arc::new(
6262 Language::new(
6263 LanguageConfig {
6264 brackets: BracketPairConfig {
6265 pairs: vec![
6266 BracketPair {
6267 start: "{".to_string(),
6268 end: "}".to_string(),
6269 close: false,
6270 surround: false,
6271 newline: true,
6272 },
6273 BracketPair {
6274 start: "(".to_string(),
6275 end: ")".to_string(),
6276 close: false,
6277 surround: false,
6278 newline: true,
6279 },
6280 ],
6281 ..Default::default()
6282 },
6283 ..Default::default()
6284 },
6285 Some(tree_sitter_rust::LANGUAGE.into()),
6286 )
6287 .with_indents_query(
6288 r#"
6289 (_ "(" ")" @end) @indent
6290 (_ "{" "}" @end) @indent
6291 "#,
6292 )
6293 .unwrap(),
6294 );
6295
6296 let text = "fn a() {}";
6297
6298 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6299 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6300 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6301 editor
6302 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6303 .await;
6304
6305 editor.update_in(cx, |editor, window, cx| {
6306 editor.change_selections(None, window, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
6307 editor.newline(&Newline, window, cx);
6308 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
6309 assert_eq!(
6310 editor.selections.ranges(cx),
6311 &[
6312 Point::new(1, 4)..Point::new(1, 4),
6313 Point::new(3, 4)..Point::new(3, 4),
6314 Point::new(5, 0)..Point::new(5, 0)
6315 ]
6316 );
6317 });
6318}
6319
6320#[gpui::test]
6321async fn test_autoindent_selections(cx: &mut TestAppContext) {
6322 init_test(cx, |_| {});
6323
6324 {
6325 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
6326 cx.set_state(indoc! {"
6327 impl A {
6328
6329 fn b() {}
6330
6331 «fn c() {
6332
6333 }ˇ»
6334 }
6335 "});
6336
6337 cx.update_editor(|editor, window, cx| {
6338 editor.autoindent(&Default::default(), window, cx);
6339 });
6340
6341 cx.assert_editor_state(indoc! {"
6342 impl A {
6343
6344 fn b() {}
6345
6346 «fn c() {
6347
6348 }ˇ»
6349 }
6350 "});
6351 }
6352
6353 {
6354 let mut cx = EditorTestContext::new_multibuffer(
6355 cx,
6356 [indoc! { "
6357 impl A {
6358 «
6359 // a
6360 fn b(){}
6361 »
6362 «
6363 }
6364 fn c(){}
6365 »
6366 "}],
6367 );
6368
6369 let buffer = cx.update_editor(|editor, _, cx| {
6370 let buffer = editor.buffer().update(cx, |buffer, _| {
6371 buffer.all_buffers().iter().next().unwrap().clone()
6372 });
6373 buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
6374 buffer
6375 });
6376
6377 cx.run_until_parked();
6378 cx.update_editor(|editor, window, cx| {
6379 editor.select_all(&Default::default(), window, cx);
6380 editor.autoindent(&Default::default(), window, cx)
6381 });
6382 cx.run_until_parked();
6383
6384 cx.update(|_, cx| {
6385 assert_eq!(
6386 buffer.read(cx).text(),
6387 indoc! { "
6388 impl A {
6389
6390 // a
6391 fn b(){}
6392
6393
6394 }
6395 fn c(){}
6396
6397 " }
6398 )
6399 });
6400 }
6401}
6402
6403#[gpui::test]
6404async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
6405 init_test(cx, |_| {});
6406
6407 let mut cx = EditorTestContext::new(cx).await;
6408
6409 let language = Arc::new(Language::new(
6410 LanguageConfig {
6411 brackets: BracketPairConfig {
6412 pairs: vec![
6413 BracketPair {
6414 start: "{".to_string(),
6415 end: "}".to_string(),
6416 close: true,
6417 surround: true,
6418 newline: true,
6419 },
6420 BracketPair {
6421 start: "(".to_string(),
6422 end: ")".to_string(),
6423 close: true,
6424 surround: true,
6425 newline: true,
6426 },
6427 BracketPair {
6428 start: "/*".to_string(),
6429 end: " */".to_string(),
6430 close: true,
6431 surround: true,
6432 newline: true,
6433 },
6434 BracketPair {
6435 start: "[".to_string(),
6436 end: "]".to_string(),
6437 close: false,
6438 surround: false,
6439 newline: true,
6440 },
6441 BracketPair {
6442 start: "\"".to_string(),
6443 end: "\"".to_string(),
6444 close: true,
6445 surround: true,
6446 newline: false,
6447 },
6448 BracketPair {
6449 start: "<".to_string(),
6450 end: ">".to_string(),
6451 close: false,
6452 surround: true,
6453 newline: true,
6454 },
6455 ],
6456 ..Default::default()
6457 },
6458 autoclose_before: "})]".to_string(),
6459 ..Default::default()
6460 },
6461 Some(tree_sitter_rust::LANGUAGE.into()),
6462 ));
6463
6464 cx.language_registry().add(language.clone());
6465 cx.update_buffer(|buffer, cx| {
6466 buffer.set_language(Some(language), cx);
6467 });
6468
6469 cx.set_state(
6470 &r#"
6471 🏀ˇ
6472 εˇ
6473 ❤️ˇ
6474 "#
6475 .unindent(),
6476 );
6477
6478 // autoclose multiple nested brackets at multiple cursors
6479 cx.update_editor(|editor, window, cx| {
6480 editor.handle_input("{", window, cx);
6481 editor.handle_input("{", window, cx);
6482 editor.handle_input("{", window, cx);
6483 });
6484 cx.assert_editor_state(
6485 &"
6486 🏀{{{ˇ}}}
6487 ε{{{ˇ}}}
6488 ❤️{{{ˇ}}}
6489 "
6490 .unindent(),
6491 );
6492
6493 // insert a different closing bracket
6494 cx.update_editor(|editor, window, cx| {
6495 editor.handle_input(")", window, cx);
6496 });
6497 cx.assert_editor_state(
6498 &"
6499 🏀{{{)ˇ}}}
6500 ε{{{)ˇ}}}
6501 ❤️{{{)ˇ}}}
6502 "
6503 .unindent(),
6504 );
6505
6506 // skip over the auto-closed brackets when typing a closing bracket
6507 cx.update_editor(|editor, window, cx| {
6508 editor.move_right(&MoveRight, window, cx);
6509 editor.handle_input("}", window, cx);
6510 editor.handle_input("}", window, cx);
6511 editor.handle_input("}", window, cx);
6512 });
6513 cx.assert_editor_state(
6514 &"
6515 🏀{{{)}}}}ˇ
6516 ε{{{)}}}}ˇ
6517 ❤️{{{)}}}}ˇ
6518 "
6519 .unindent(),
6520 );
6521
6522 // autoclose multi-character pairs
6523 cx.set_state(
6524 &"
6525 ˇ
6526 ˇ
6527 "
6528 .unindent(),
6529 );
6530 cx.update_editor(|editor, window, cx| {
6531 editor.handle_input("/", window, cx);
6532 editor.handle_input("*", window, cx);
6533 });
6534 cx.assert_editor_state(
6535 &"
6536 /*ˇ */
6537 /*ˇ */
6538 "
6539 .unindent(),
6540 );
6541
6542 // one cursor autocloses a multi-character pair, one cursor
6543 // does not autoclose.
6544 cx.set_state(
6545 &"
6546 /ˇ
6547 ˇ
6548 "
6549 .unindent(),
6550 );
6551 cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
6552 cx.assert_editor_state(
6553 &"
6554 /*ˇ */
6555 *ˇ
6556 "
6557 .unindent(),
6558 );
6559
6560 // Don't autoclose if the next character isn't whitespace and isn't
6561 // listed in the language's "autoclose_before" section.
6562 cx.set_state("ˇa b");
6563 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
6564 cx.assert_editor_state("{ˇa b");
6565
6566 // Don't autoclose if `close` is false for the bracket pair
6567 cx.set_state("ˇ");
6568 cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
6569 cx.assert_editor_state("[ˇ");
6570
6571 // Surround with brackets if text is selected
6572 cx.set_state("«aˇ» b");
6573 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
6574 cx.assert_editor_state("{«aˇ»} b");
6575
6576 // Autoclose when not immediately after a word character
6577 cx.set_state("a ˇ");
6578 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6579 cx.assert_editor_state("a \"ˇ\"");
6580
6581 // Autoclose pair where the start and end characters are the same
6582 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6583 cx.assert_editor_state("a \"\"ˇ");
6584
6585 // Don't autoclose when immediately after a word character
6586 cx.set_state("aˇ");
6587 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6588 cx.assert_editor_state("a\"ˇ");
6589
6590 // Do autoclose when after a non-word character
6591 cx.set_state("{ˇ");
6592 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6593 cx.assert_editor_state("{\"ˇ\"");
6594
6595 // Non identical pairs autoclose regardless of preceding character
6596 cx.set_state("aˇ");
6597 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
6598 cx.assert_editor_state("a{ˇ}");
6599
6600 // Don't autoclose pair if autoclose is disabled
6601 cx.set_state("ˇ");
6602 cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
6603 cx.assert_editor_state("<ˇ");
6604
6605 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
6606 cx.set_state("«aˇ» b");
6607 cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
6608 cx.assert_editor_state("<«aˇ»> b");
6609}
6610
6611#[gpui::test]
6612async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
6613 init_test(cx, |settings| {
6614 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6615 });
6616
6617 let mut cx = EditorTestContext::new(cx).await;
6618
6619 let language = Arc::new(Language::new(
6620 LanguageConfig {
6621 brackets: BracketPairConfig {
6622 pairs: vec![
6623 BracketPair {
6624 start: "{".to_string(),
6625 end: "}".to_string(),
6626 close: true,
6627 surround: true,
6628 newline: true,
6629 },
6630 BracketPair {
6631 start: "(".to_string(),
6632 end: ")".to_string(),
6633 close: true,
6634 surround: true,
6635 newline: true,
6636 },
6637 BracketPair {
6638 start: "[".to_string(),
6639 end: "]".to_string(),
6640 close: false,
6641 surround: false,
6642 newline: true,
6643 },
6644 ],
6645 ..Default::default()
6646 },
6647 autoclose_before: "})]".to_string(),
6648 ..Default::default()
6649 },
6650 Some(tree_sitter_rust::LANGUAGE.into()),
6651 ));
6652
6653 cx.language_registry().add(language.clone());
6654 cx.update_buffer(|buffer, cx| {
6655 buffer.set_language(Some(language), cx);
6656 });
6657
6658 cx.set_state(
6659 &"
6660 ˇ
6661 ˇ
6662 ˇ
6663 "
6664 .unindent(),
6665 );
6666
6667 // ensure only matching closing brackets are skipped over
6668 cx.update_editor(|editor, window, cx| {
6669 editor.handle_input("}", window, cx);
6670 editor.move_left(&MoveLeft, window, cx);
6671 editor.handle_input(")", window, cx);
6672 editor.move_left(&MoveLeft, window, cx);
6673 });
6674 cx.assert_editor_state(
6675 &"
6676 ˇ)}
6677 ˇ)}
6678 ˇ)}
6679 "
6680 .unindent(),
6681 );
6682
6683 // skip-over closing brackets at multiple cursors
6684 cx.update_editor(|editor, window, cx| {
6685 editor.handle_input(")", window, cx);
6686 editor.handle_input("}", window, cx);
6687 });
6688 cx.assert_editor_state(
6689 &"
6690 )}ˇ
6691 )}ˇ
6692 )}ˇ
6693 "
6694 .unindent(),
6695 );
6696
6697 // ignore non-close brackets
6698 cx.update_editor(|editor, window, cx| {
6699 editor.handle_input("]", window, cx);
6700 editor.move_left(&MoveLeft, window, cx);
6701 editor.handle_input("]", window, cx);
6702 });
6703 cx.assert_editor_state(
6704 &"
6705 )}]ˇ]
6706 )}]ˇ]
6707 )}]ˇ]
6708 "
6709 .unindent(),
6710 );
6711}
6712
6713#[gpui::test]
6714async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
6715 init_test(cx, |_| {});
6716
6717 let mut cx = EditorTestContext::new(cx).await;
6718
6719 let html_language = Arc::new(
6720 Language::new(
6721 LanguageConfig {
6722 name: "HTML".into(),
6723 brackets: BracketPairConfig {
6724 pairs: vec![
6725 BracketPair {
6726 start: "<".into(),
6727 end: ">".into(),
6728 close: true,
6729 ..Default::default()
6730 },
6731 BracketPair {
6732 start: "{".into(),
6733 end: "}".into(),
6734 close: true,
6735 ..Default::default()
6736 },
6737 BracketPair {
6738 start: "(".into(),
6739 end: ")".into(),
6740 close: true,
6741 ..Default::default()
6742 },
6743 ],
6744 ..Default::default()
6745 },
6746 autoclose_before: "})]>".into(),
6747 ..Default::default()
6748 },
6749 Some(tree_sitter_html::LANGUAGE.into()),
6750 )
6751 .with_injection_query(
6752 r#"
6753 (script_element
6754 (raw_text) @injection.content
6755 (#set! injection.language "javascript"))
6756 "#,
6757 )
6758 .unwrap(),
6759 );
6760
6761 let javascript_language = Arc::new(Language::new(
6762 LanguageConfig {
6763 name: "JavaScript".into(),
6764 brackets: BracketPairConfig {
6765 pairs: vec![
6766 BracketPair {
6767 start: "/*".into(),
6768 end: " */".into(),
6769 close: true,
6770 ..Default::default()
6771 },
6772 BracketPair {
6773 start: "{".into(),
6774 end: "}".into(),
6775 close: true,
6776 ..Default::default()
6777 },
6778 BracketPair {
6779 start: "(".into(),
6780 end: ")".into(),
6781 close: true,
6782 ..Default::default()
6783 },
6784 ],
6785 ..Default::default()
6786 },
6787 autoclose_before: "})]>".into(),
6788 ..Default::default()
6789 },
6790 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
6791 ));
6792
6793 cx.language_registry().add(html_language.clone());
6794 cx.language_registry().add(javascript_language.clone());
6795
6796 cx.update_buffer(|buffer, cx| {
6797 buffer.set_language(Some(html_language), cx);
6798 });
6799
6800 cx.set_state(
6801 &r#"
6802 <body>ˇ
6803 <script>
6804 var x = 1;ˇ
6805 </script>
6806 </body>ˇ
6807 "#
6808 .unindent(),
6809 );
6810
6811 // Precondition: different languages are active at different locations.
6812 cx.update_editor(|editor, window, cx| {
6813 let snapshot = editor.snapshot(window, cx);
6814 let cursors = editor.selections.ranges::<usize>(cx);
6815 let languages = cursors
6816 .iter()
6817 .map(|c| snapshot.language_at(c.start).unwrap().name())
6818 .collect::<Vec<_>>();
6819 assert_eq!(
6820 languages,
6821 &["HTML".into(), "JavaScript".into(), "HTML".into()]
6822 );
6823 });
6824
6825 // Angle brackets autoclose in HTML, but not JavaScript.
6826 cx.update_editor(|editor, window, cx| {
6827 editor.handle_input("<", window, cx);
6828 editor.handle_input("a", window, cx);
6829 });
6830 cx.assert_editor_state(
6831 &r#"
6832 <body><aˇ>
6833 <script>
6834 var x = 1;<aˇ
6835 </script>
6836 </body><aˇ>
6837 "#
6838 .unindent(),
6839 );
6840
6841 // Curly braces and parens autoclose in both HTML and JavaScript.
6842 cx.update_editor(|editor, window, cx| {
6843 editor.handle_input(" b=", window, cx);
6844 editor.handle_input("{", window, cx);
6845 editor.handle_input("c", window, cx);
6846 editor.handle_input("(", window, cx);
6847 });
6848 cx.assert_editor_state(
6849 &r#"
6850 <body><a b={c(ˇ)}>
6851 <script>
6852 var x = 1;<a b={c(ˇ)}
6853 </script>
6854 </body><a b={c(ˇ)}>
6855 "#
6856 .unindent(),
6857 );
6858
6859 // Brackets that were already autoclosed are skipped.
6860 cx.update_editor(|editor, window, cx| {
6861 editor.handle_input(")", window, cx);
6862 editor.handle_input("d", window, cx);
6863 editor.handle_input("}", window, cx);
6864 });
6865 cx.assert_editor_state(
6866 &r#"
6867 <body><a b={c()d}ˇ>
6868 <script>
6869 var x = 1;<a b={c()d}ˇ
6870 </script>
6871 </body><a b={c()d}ˇ>
6872 "#
6873 .unindent(),
6874 );
6875 cx.update_editor(|editor, window, cx| {
6876 editor.handle_input(">", window, cx);
6877 });
6878 cx.assert_editor_state(
6879 &r#"
6880 <body><a b={c()d}>ˇ
6881 <script>
6882 var x = 1;<a b={c()d}>ˇ
6883 </script>
6884 </body><a b={c()d}>ˇ
6885 "#
6886 .unindent(),
6887 );
6888
6889 // Reset
6890 cx.set_state(
6891 &r#"
6892 <body>ˇ
6893 <script>
6894 var x = 1;ˇ
6895 </script>
6896 </body>ˇ
6897 "#
6898 .unindent(),
6899 );
6900
6901 cx.update_editor(|editor, window, cx| {
6902 editor.handle_input("<", window, cx);
6903 });
6904 cx.assert_editor_state(
6905 &r#"
6906 <body><ˇ>
6907 <script>
6908 var x = 1;<ˇ
6909 </script>
6910 </body><ˇ>
6911 "#
6912 .unindent(),
6913 );
6914
6915 // When backspacing, the closing angle brackets are removed.
6916 cx.update_editor(|editor, window, cx| {
6917 editor.backspace(&Backspace, window, cx);
6918 });
6919 cx.assert_editor_state(
6920 &r#"
6921 <body>ˇ
6922 <script>
6923 var x = 1;ˇ
6924 </script>
6925 </body>ˇ
6926 "#
6927 .unindent(),
6928 );
6929
6930 // Block comments autoclose in JavaScript, but not HTML.
6931 cx.update_editor(|editor, window, cx| {
6932 editor.handle_input("/", window, cx);
6933 editor.handle_input("*", window, cx);
6934 });
6935 cx.assert_editor_state(
6936 &r#"
6937 <body>/*ˇ
6938 <script>
6939 var x = 1;/*ˇ */
6940 </script>
6941 </body>/*ˇ
6942 "#
6943 .unindent(),
6944 );
6945}
6946
6947#[gpui::test]
6948async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
6949 init_test(cx, |_| {});
6950
6951 let mut cx = EditorTestContext::new(cx).await;
6952
6953 let rust_language = Arc::new(
6954 Language::new(
6955 LanguageConfig {
6956 name: "Rust".into(),
6957 brackets: serde_json::from_value(json!([
6958 { "start": "{", "end": "}", "close": true, "newline": true },
6959 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
6960 ]))
6961 .unwrap(),
6962 autoclose_before: "})]>".into(),
6963 ..Default::default()
6964 },
6965 Some(tree_sitter_rust::LANGUAGE.into()),
6966 )
6967 .with_override_query("(string_literal) @string")
6968 .unwrap(),
6969 );
6970
6971 cx.language_registry().add(rust_language.clone());
6972 cx.update_buffer(|buffer, cx| {
6973 buffer.set_language(Some(rust_language), cx);
6974 });
6975
6976 cx.set_state(
6977 &r#"
6978 let x = ˇ
6979 "#
6980 .unindent(),
6981 );
6982
6983 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
6984 cx.update_editor(|editor, window, cx| {
6985 editor.handle_input("\"", window, cx);
6986 });
6987 cx.assert_editor_state(
6988 &r#"
6989 let x = "ˇ"
6990 "#
6991 .unindent(),
6992 );
6993
6994 // Inserting another quotation mark. The cursor moves across the existing
6995 // automatically-inserted quotation mark.
6996 cx.update_editor(|editor, window, cx| {
6997 editor.handle_input("\"", window, cx);
6998 });
6999 cx.assert_editor_state(
7000 &r#"
7001 let x = ""ˇ
7002 "#
7003 .unindent(),
7004 );
7005
7006 // Reset
7007 cx.set_state(
7008 &r#"
7009 let x = ˇ
7010 "#
7011 .unindent(),
7012 );
7013
7014 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
7015 cx.update_editor(|editor, window, cx| {
7016 editor.handle_input("\"", window, cx);
7017 editor.handle_input(" ", window, cx);
7018 editor.move_left(&Default::default(), window, cx);
7019 editor.handle_input("\\", window, cx);
7020 editor.handle_input("\"", window, cx);
7021 });
7022 cx.assert_editor_state(
7023 &r#"
7024 let x = "\"ˇ "
7025 "#
7026 .unindent(),
7027 );
7028
7029 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
7030 // mark. Nothing is inserted.
7031 cx.update_editor(|editor, window, cx| {
7032 editor.move_right(&Default::default(), window, cx);
7033 editor.handle_input("\"", window, cx);
7034 });
7035 cx.assert_editor_state(
7036 &r#"
7037 let x = "\" "ˇ
7038 "#
7039 .unindent(),
7040 );
7041}
7042
7043#[gpui::test]
7044async fn test_surround_with_pair(cx: &mut TestAppContext) {
7045 init_test(cx, |_| {});
7046
7047 let language = Arc::new(Language::new(
7048 LanguageConfig {
7049 brackets: BracketPairConfig {
7050 pairs: vec![
7051 BracketPair {
7052 start: "{".to_string(),
7053 end: "}".to_string(),
7054 close: true,
7055 surround: true,
7056 newline: true,
7057 },
7058 BracketPair {
7059 start: "/* ".to_string(),
7060 end: "*/".to_string(),
7061 close: true,
7062 surround: true,
7063 ..Default::default()
7064 },
7065 ],
7066 ..Default::default()
7067 },
7068 ..Default::default()
7069 },
7070 Some(tree_sitter_rust::LANGUAGE.into()),
7071 ));
7072
7073 let text = r#"
7074 a
7075 b
7076 c
7077 "#
7078 .unindent();
7079
7080 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
7081 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7082 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7083 editor
7084 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
7085 .await;
7086
7087 editor.update_in(cx, |editor, window, cx| {
7088 editor.change_selections(None, window, cx, |s| {
7089 s.select_display_ranges([
7090 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
7091 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
7092 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
7093 ])
7094 });
7095
7096 editor.handle_input("{", window, cx);
7097 editor.handle_input("{", window, cx);
7098 editor.handle_input("{", window, cx);
7099 assert_eq!(
7100 editor.text(cx),
7101 "
7102 {{{a}}}
7103 {{{b}}}
7104 {{{c}}}
7105 "
7106 .unindent()
7107 );
7108 assert_eq!(
7109 editor.selections.display_ranges(cx),
7110 [
7111 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
7112 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
7113 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
7114 ]
7115 );
7116
7117 editor.undo(&Undo, window, cx);
7118 editor.undo(&Undo, window, cx);
7119 editor.undo(&Undo, window, cx);
7120 assert_eq!(
7121 editor.text(cx),
7122 "
7123 a
7124 b
7125 c
7126 "
7127 .unindent()
7128 );
7129 assert_eq!(
7130 editor.selections.display_ranges(cx),
7131 [
7132 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
7133 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
7134 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
7135 ]
7136 );
7137
7138 // Ensure inserting the first character of a multi-byte bracket pair
7139 // doesn't surround the selections with the bracket.
7140 editor.handle_input("/", window, cx);
7141 assert_eq!(
7142 editor.text(cx),
7143 "
7144 /
7145 /
7146 /
7147 "
7148 .unindent()
7149 );
7150 assert_eq!(
7151 editor.selections.display_ranges(cx),
7152 [
7153 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
7154 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
7155 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
7156 ]
7157 );
7158
7159 editor.undo(&Undo, window, cx);
7160 assert_eq!(
7161 editor.text(cx),
7162 "
7163 a
7164 b
7165 c
7166 "
7167 .unindent()
7168 );
7169 assert_eq!(
7170 editor.selections.display_ranges(cx),
7171 [
7172 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
7173 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
7174 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
7175 ]
7176 );
7177
7178 // Ensure inserting the last character of a multi-byte bracket pair
7179 // doesn't surround the selections with the bracket.
7180 editor.handle_input("*", window, cx);
7181 assert_eq!(
7182 editor.text(cx),
7183 "
7184 *
7185 *
7186 *
7187 "
7188 .unindent()
7189 );
7190 assert_eq!(
7191 editor.selections.display_ranges(cx),
7192 [
7193 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
7194 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
7195 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
7196 ]
7197 );
7198 });
7199}
7200
7201#[gpui::test]
7202async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
7203 init_test(cx, |_| {});
7204
7205 let language = Arc::new(Language::new(
7206 LanguageConfig {
7207 brackets: BracketPairConfig {
7208 pairs: vec![BracketPair {
7209 start: "{".to_string(),
7210 end: "}".to_string(),
7211 close: true,
7212 surround: true,
7213 newline: true,
7214 }],
7215 ..Default::default()
7216 },
7217 autoclose_before: "}".to_string(),
7218 ..Default::default()
7219 },
7220 Some(tree_sitter_rust::LANGUAGE.into()),
7221 ));
7222
7223 let text = r#"
7224 a
7225 b
7226 c
7227 "#
7228 .unindent();
7229
7230 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
7231 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7232 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7233 editor
7234 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
7235 .await;
7236
7237 editor.update_in(cx, |editor, window, cx| {
7238 editor.change_selections(None, window, cx, |s| {
7239 s.select_ranges([
7240 Point::new(0, 1)..Point::new(0, 1),
7241 Point::new(1, 1)..Point::new(1, 1),
7242 Point::new(2, 1)..Point::new(2, 1),
7243 ])
7244 });
7245
7246 editor.handle_input("{", window, cx);
7247 editor.handle_input("{", window, cx);
7248 editor.handle_input("_", window, cx);
7249 assert_eq!(
7250 editor.text(cx),
7251 "
7252 a{{_}}
7253 b{{_}}
7254 c{{_}}
7255 "
7256 .unindent()
7257 );
7258 assert_eq!(
7259 editor.selections.ranges::<Point>(cx),
7260 [
7261 Point::new(0, 4)..Point::new(0, 4),
7262 Point::new(1, 4)..Point::new(1, 4),
7263 Point::new(2, 4)..Point::new(2, 4)
7264 ]
7265 );
7266
7267 editor.backspace(&Default::default(), window, cx);
7268 editor.backspace(&Default::default(), window, cx);
7269 assert_eq!(
7270 editor.text(cx),
7271 "
7272 a{}
7273 b{}
7274 c{}
7275 "
7276 .unindent()
7277 );
7278 assert_eq!(
7279 editor.selections.ranges::<Point>(cx),
7280 [
7281 Point::new(0, 2)..Point::new(0, 2),
7282 Point::new(1, 2)..Point::new(1, 2),
7283 Point::new(2, 2)..Point::new(2, 2)
7284 ]
7285 );
7286
7287 editor.delete_to_previous_word_start(&Default::default(), window, cx);
7288 assert_eq!(
7289 editor.text(cx),
7290 "
7291 a
7292 b
7293 c
7294 "
7295 .unindent()
7296 );
7297 assert_eq!(
7298 editor.selections.ranges::<Point>(cx),
7299 [
7300 Point::new(0, 1)..Point::new(0, 1),
7301 Point::new(1, 1)..Point::new(1, 1),
7302 Point::new(2, 1)..Point::new(2, 1)
7303 ]
7304 );
7305 });
7306}
7307
7308#[gpui::test]
7309async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
7310 init_test(cx, |settings| {
7311 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
7312 });
7313
7314 let mut cx = EditorTestContext::new(cx).await;
7315
7316 let language = Arc::new(Language::new(
7317 LanguageConfig {
7318 brackets: BracketPairConfig {
7319 pairs: vec![
7320 BracketPair {
7321 start: "{".to_string(),
7322 end: "}".to_string(),
7323 close: true,
7324 surround: true,
7325 newline: true,
7326 },
7327 BracketPair {
7328 start: "(".to_string(),
7329 end: ")".to_string(),
7330 close: true,
7331 surround: true,
7332 newline: true,
7333 },
7334 BracketPair {
7335 start: "[".to_string(),
7336 end: "]".to_string(),
7337 close: false,
7338 surround: true,
7339 newline: true,
7340 },
7341 ],
7342 ..Default::default()
7343 },
7344 autoclose_before: "})]".to_string(),
7345 ..Default::default()
7346 },
7347 Some(tree_sitter_rust::LANGUAGE.into()),
7348 ));
7349
7350 cx.language_registry().add(language.clone());
7351 cx.update_buffer(|buffer, cx| {
7352 buffer.set_language(Some(language), cx);
7353 });
7354
7355 cx.set_state(
7356 &"
7357 {(ˇ)}
7358 [[ˇ]]
7359 {(ˇ)}
7360 "
7361 .unindent(),
7362 );
7363
7364 cx.update_editor(|editor, window, cx| {
7365 editor.backspace(&Default::default(), window, cx);
7366 editor.backspace(&Default::default(), window, cx);
7367 });
7368
7369 cx.assert_editor_state(
7370 &"
7371 ˇ
7372 ˇ]]
7373 ˇ
7374 "
7375 .unindent(),
7376 );
7377
7378 cx.update_editor(|editor, window, cx| {
7379 editor.handle_input("{", window, cx);
7380 editor.handle_input("{", window, cx);
7381 editor.move_right(&MoveRight, window, cx);
7382 editor.move_right(&MoveRight, window, cx);
7383 editor.move_left(&MoveLeft, window, cx);
7384 editor.move_left(&MoveLeft, window, cx);
7385 editor.backspace(&Default::default(), window, cx);
7386 });
7387
7388 cx.assert_editor_state(
7389 &"
7390 {ˇ}
7391 {ˇ}]]
7392 {ˇ}
7393 "
7394 .unindent(),
7395 );
7396
7397 cx.update_editor(|editor, window, cx| {
7398 editor.backspace(&Default::default(), window, cx);
7399 });
7400
7401 cx.assert_editor_state(
7402 &"
7403 ˇ
7404 ˇ]]
7405 ˇ
7406 "
7407 .unindent(),
7408 );
7409}
7410
7411#[gpui::test]
7412async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
7413 init_test(cx, |_| {});
7414
7415 let language = Arc::new(Language::new(
7416 LanguageConfig::default(),
7417 Some(tree_sitter_rust::LANGUAGE.into()),
7418 ));
7419
7420 let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
7421 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7422 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7423 editor
7424 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
7425 .await;
7426
7427 editor.update_in(cx, |editor, window, cx| {
7428 editor.set_auto_replace_emoji_shortcode(true);
7429
7430 editor.handle_input("Hello ", window, cx);
7431 editor.handle_input(":wave", window, cx);
7432 assert_eq!(editor.text(cx), "Hello :wave".unindent());
7433
7434 editor.handle_input(":", window, cx);
7435 assert_eq!(editor.text(cx), "Hello 👋".unindent());
7436
7437 editor.handle_input(" :smile", window, cx);
7438 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
7439
7440 editor.handle_input(":", window, cx);
7441 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
7442
7443 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
7444 editor.handle_input(":wave", window, cx);
7445 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
7446
7447 editor.handle_input(":", window, cx);
7448 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
7449
7450 editor.handle_input(":1", window, cx);
7451 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
7452
7453 editor.handle_input(":", window, cx);
7454 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
7455
7456 // Ensure shortcode does not get replaced when it is part of a word
7457 editor.handle_input(" Test:wave", window, cx);
7458 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
7459
7460 editor.handle_input(":", window, cx);
7461 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
7462
7463 editor.set_auto_replace_emoji_shortcode(false);
7464
7465 // Ensure shortcode does not get replaced when auto replace is off
7466 editor.handle_input(" :wave", window, cx);
7467 assert_eq!(
7468 editor.text(cx),
7469 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
7470 );
7471
7472 editor.handle_input(":", window, cx);
7473 assert_eq!(
7474 editor.text(cx),
7475 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
7476 );
7477 });
7478}
7479
7480#[gpui::test]
7481async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
7482 init_test(cx, |_| {});
7483
7484 let (text, insertion_ranges) = marked_text_ranges(
7485 indoc! {"
7486 ˇ
7487 "},
7488 false,
7489 );
7490
7491 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
7492 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7493
7494 _ = editor.update_in(cx, |editor, window, cx| {
7495 let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
7496
7497 editor
7498 .insert_snippet(&insertion_ranges, snippet, window, cx)
7499 .unwrap();
7500
7501 fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
7502 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
7503 assert_eq!(editor.text(cx), expected_text);
7504 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
7505 }
7506
7507 assert(
7508 editor,
7509 cx,
7510 indoc! {"
7511 type «» =•
7512 "},
7513 );
7514
7515 assert!(editor.context_menu_visible(), "There should be a matches");
7516 });
7517}
7518
7519#[gpui::test]
7520async fn test_snippets(cx: &mut TestAppContext) {
7521 init_test(cx, |_| {});
7522
7523 let (text, insertion_ranges) = marked_text_ranges(
7524 indoc! {"
7525 a.ˇ b
7526 a.ˇ b
7527 a.ˇ b
7528 "},
7529 false,
7530 );
7531
7532 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
7533 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7534
7535 editor.update_in(cx, |editor, window, cx| {
7536 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
7537
7538 editor
7539 .insert_snippet(&insertion_ranges, snippet, window, cx)
7540 .unwrap();
7541
7542 fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
7543 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
7544 assert_eq!(editor.text(cx), expected_text);
7545 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
7546 }
7547
7548 assert(
7549 editor,
7550 cx,
7551 indoc! {"
7552 a.f(«one», two, «three») b
7553 a.f(«one», two, «three») b
7554 a.f(«one», two, «three») b
7555 "},
7556 );
7557
7558 // Can't move earlier than the first tab stop
7559 assert!(!editor.move_to_prev_snippet_tabstop(window, cx));
7560 assert(
7561 editor,
7562 cx,
7563 indoc! {"
7564 a.f(«one», two, «three») b
7565 a.f(«one», two, «three») b
7566 a.f(«one», two, «three») b
7567 "},
7568 );
7569
7570 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7571 assert(
7572 editor,
7573 cx,
7574 indoc! {"
7575 a.f(one, «two», three) b
7576 a.f(one, «two», three) b
7577 a.f(one, «two», three) b
7578 "},
7579 );
7580
7581 editor.move_to_prev_snippet_tabstop(window, cx);
7582 assert(
7583 editor,
7584 cx,
7585 indoc! {"
7586 a.f(«one», two, «three») b
7587 a.f(«one», two, «three») b
7588 a.f(«one», two, «three») b
7589 "},
7590 );
7591
7592 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7593 assert(
7594 editor,
7595 cx,
7596 indoc! {"
7597 a.f(one, «two», three) b
7598 a.f(one, «two», three) b
7599 a.f(one, «two», three) b
7600 "},
7601 );
7602 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7603 assert(
7604 editor,
7605 cx,
7606 indoc! {"
7607 a.f(one, two, three)ˇ b
7608 a.f(one, two, three)ˇ b
7609 a.f(one, two, three)ˇ b
7610 "},
7611 );
7612
7613 // As soon as the last tab stop is reached, snippet state is gone
7614 editor.move_to_prev_snippet_tabstop(window, cx);
7615 assert(
7616 editor,
7617 cx,
7618 indoc! {"
7619 a.f(one, two, three)ˇ b
7620 a.f(one, two, three)ˇ b
7621 a.f(one, two, three)ˇ b
7622 "},
7623 );
7624 });
7625}
7626
7627#[gpui::test]
7628async fn test_document_format_during_save(cx: &mut TestAppContext) {
7629 init_test(cx, |_| {});
7630
7631 let fs = FakeFs::new(cx.executor());
7632 fs.insert_file(path!("/file.rs"), Default::default()).await;
7633
7634 let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
7635
7636 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7637 language_registry.add(rust_lang());
7638 let mut fake_servers = language_registry.register_fake_lsp(
7639 "Rust",
7640 FakeLspAdapter {
7641 capabilities: lsp::ServerCapabilities {
7642 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7643 ..Default::default()
7644 },
7645 ..Default::default()
7646 },
7647 );
7648
7649 let buffer = project
7650 .update(cx, |project, cx| {
7651 project.open_local_buffer(path!("/file.rs"), cx)
7652 })
7653 .await
7654 .unwrap();
7655
7656 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7657 let (editor, cx) = cx.add_window_view(|window, cx| {
7658 build_editor_with_project(project.clone(), buffer, window, cx)
7659 });
7660 editor.update_in(cx, |editor, window, cx| {
7661 editor.set_text("one\ntwo\nthree\n", window, cx)
7662 });
7663 assert!(cx.read(|cx| editor.is_dirty(cx)));
7664
7665 cx.executor().start_waiting();
7666 let fake_server = fake_servers.next().await.unwrap();
7667
7668 let save = editor
7669 .update_in(cx, |editor, window, cx| {
7670 editor.save(true, project.clone(), window, cx)
7671 })
7672 .unwrap();
7673 fake_server
7674 .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
7675 assert_eq!(
7676 params.text_document.uri,
7677 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7678 );
7679 assert_eq!(params.options.tab_size, 4);
7680 Ok(Some(vec![lsp::TextEdit::new(
7681 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7682 ", ".to_string(),
7683 )]))
7684 })
7685 .next()
7686 .await;
7687 cx.executor().start_waiting();
7688 save.await;
7689
7690 assert_eq!(
7691 editor.update(cx, |editor, cx| editor.text(cx)),
7692 "one, two\nthree\n"
7693 );
7694 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7695
7696 editor.update_in(cx, |editor, window, cx| {
7697 editor.set_text("one\ntwo\nthree\n", window, cx)
7698 });
7699 assert!(cx.read(|cx| editor.is_dirty(cx)));
7700
7701 // Ensure we can still save even if formatting hangs.
7702 fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
7703 move |params, _| async move {
7704 assert_eq!(
7705 params.text_document.uri,
7706 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7707 );
7708 futures::future::pending::<()>().await;
7709 unreachable!()
7710 },
7711 );
7712 let save = editor
7713 .update_in(cx, |editor, window, cx| {
7714 editor.save(true, project.clone(), window, cx)
7715 })
7716 .unwrap();
7717 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7718 cx.executor().start_waiting();
7719 save.await;
7720 assert_eq!(
7721 editor.update(cx, |editor, cx| editor.text(cx)),
7722 "one\ntwo\nthree\n"
7723 );
7724 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7725
7726 // For non-dirty buffer, no formatting request should be sent
7727 let save = editor
7728 .update_in(cx, |editor, window, cx| {
7729 editor.save(true, project.clone(), window, cx)
7730 })
7731 .unwrap();
7732 let _pending_format_request = fake_server
7733 .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7734 panic!("Should not be invoked on non-dirty buffer");
7735 })
7736 .next();
7737 cx.executor().start_waiting();
7738 save.await;
7739
7740 // Set rust language override and assert overridden tabsize is sent to language server
7741 update_test_language_settings(cx, |settings| {
7742 settings.languages.insert(
7743 "Rust".into(),
7744 LanguageSettingsContent {
7745 tab_size: NonZeroU32::new(8),
7746 ..Default::default()
7747 },
7748 );
7749 });
7750
7751 editor.update_in(cx, |editor, window, cx| {
7752 editor.set_text("somehting_new\n", window, cx)
7753 });
7754 assert!(cx.read(|cx| editor.is_dirty(cx)));
7755 let save = editor
7756 .update_in(cx, |editor, window, cx| {
7757 editor.save(true, project.clone(), window, cx)
7758 })
7759 .unwrap();
7760 fake_server
7761 .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
7762 assert_eq!(
7763 params.text_document.uri,
7764 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7765 );
7766 assert_eq!(params.options.tab_size, 8);
7767 Ok(Some(vec![]))
7768 })
7769 .next()
7770 .await;
7771 cx.executor().start_waiting();
7772 save.await;
7773}
7774
7775#[gpui::test]
7776async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
7777 init_test(cx, |_| {});
7778
7779 let cols = 4;
7780 let rows = 10;
7781 let sample_text_1 = sample_text(rows, cols, 'a');
7782 assert_eq!(
7783 sample_text_1,
7784 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
7785 );
7786 let sample_text_2 = sample_text(rows, cols, 'l');
7787 assert_eq!(
7788 sample_text_2,
7789 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
7790 );
7791 let sample_text_3 = sample_text(rows, cols, 'v');
7792 assert_eq!(
7793 sample_text_3,
7794 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
7795 );
7796
7797 let fs = FakeFs::new(cx.executor());
7798 fs.insert_tree(
7799 path!("/a"),
7800 json!({
7801 "main.rs": sample_text_1,
7802 "other.rs": sample_text_2,
7803 "lib.rs": sample_text_3,
7804 }),
7805 )
7806 .await;
7807
7808 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
7809 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
7810 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
7811
7812 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7813 language_registry.add(rust_lang());
7814 let mut fake_servers = language_registry.register_fake_lsp(
7815 "Rust",
7816 FakeLspAdapter {
7817 capabilities: lsp::ServerCapabilities {
7818 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7819 ..Default::default()
7820 },
7821 ..Default::default()
7822 },
7823 );
7824
7825 let worktree = project.update(cx, |project, cx| {
7826 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
7827 assert_eq!(worktrees.len(), 1);
7828 worktrees.pop().unwrap()
7829 });
7830 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
7831
7832 let buffer_1 = project
7833 .update(cx, |project, cx| {
7834 project.open_buffer((worktree_id, "main.rs"), cx)
7835 })
7836 .await
7837 .unwrap();
7838 let buffer_2 = project
7839 .update(cx, |project, cx| {
7840 project.open_buffer((worktree_id, "other.rs"), cx)
7841 })
7842 .await
7843 .unwrap();
7844 let buffer_3 = project
7845 .update(cx, |project, cx| {
7846 project.open_buffer((worktree_id, "lib.rs"), cx)
7847 })
7848 .await
7849 .unwrap();
7850
7851 let multi_buffer = cx.new(|cx| {
7852 let mut multi_buffer = MultiBuffer::new(ReadWrite);
7853 multi_buffer.push_excerpts(
7854 buffer_1.clone(),
7855 [
7856 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
7857 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
7858 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
7859 ],
7860 cx,
7861 );
7862 multi_buffer.push_excerpts(
7863 buffer_2.clone(),
7864 [
7865 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
7866 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
7867 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
7868 ],
7869 cx,
7870 );
7871 multi_buffer.push_excerpts(
7872 buffer_3.clone(),
7873 [
7874 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
7875 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
7876 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
7877 ],
7878 cx,
7879 );
7880 multi_buffer
7881 });
7882 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
7883 Editor::new(
7884 EditorMode::Full,
7885 multi_buffer,
7886 Some(project.clone()),
7887 window,
7888 cx,
7889 )
7890 });
7891
7892 multi_buffer_editor.update_in(cx, |editor, window, cx| {
7893 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
7894 s.select_ranges(Some(1..2))
7895 });
7896 editor.insert("|one|two|three|", window, cx);
7897 });
7898 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7899 multi_buffer_editor.update_in(cx, |editor, window, cx| {
7900 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
7901 s.select_ranges(Some(60..70))
7902 });
7903 editor.insert("|four|five|six|", window, cx);
7904 });
7905 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7906
7907 // First two buffers should be edited, but not the third one.
7908 assert_eq!(
7909 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7910 "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}",
7911 );
7912 buffer_1.update(cx, |buffer, _| {
7913 assert!(buffer.is_dirty());
7914 assert_eq!(
7915 buffer.text(),
7916 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
7917 )
7918 });
7919 buffer_2.update(cx, |buffer, _| {
7920 assert!(buffer.is_dirty());
7921 assert_eq!(
7922 buffer.text(),
7923 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
7924 )
7925 });
7926 buffer_3.update(cx, |buffer, _| {
7927 assert!(!buffer.is_dirty());
7928 assert_eq!(buffer.text(), sample_text_3,)
7929 });
7930 cx.executor().run_until_parked();
7931
7932 cx.executor().start_waiting();
7933 let save = multi_buffer_editor
7934 .update_in(cx, |editor, window, cx| {
7935 editor.save(true, project.clone(), window, cx)
7936 })
7937 .unwrap();
7938
7939 let fake_server = fake_servers.next().await.unwrap();
7940 fake_server
7941 .server
7942 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7943 Ok(Some(vec![lsp::TextEdit::new(
7944 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7945 format!("[{} formatted]", params.text_document.uri),
7946 )]))
7947 })
7948 .detach();
7949 save.await;
7950
7951 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
7952 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
7953 assert_eq!(
7954 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7955 uri!(
7956 "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}"
7957 ),
7958 );
7959 buffer_1.update(cx, |buffer, _| {
7960 assert!(!buffer.is_dirty());
7961 assert_eq!(
7962 buffer.text(),
7963 uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
7964 )
7965 });
7966 buffer_2.update(cx, |buffer, _| {
7967 assert!(!buffer.is_dirty());
7968 assert_eq!(
7969 buffer.text(),
7970 uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
7971 )
7972 });
7973 buffer_3.update(cx, |buffer, _| {
7974 assert!(!buffer.is_dirty());
7975 assert_eq!(buffer.text(), sample_text_3,)
7976 });
7977}
7978
7979#[gpui::test]
7980async fn test_range_format_during_save(cx: &mut TestAppContext) {
7981 init_test(cx, |_| {});
7982
7983 let fs = FakeFs::new(cx.executor());
7984 fs.insert_file(path!("/file.rs"), Default::default()).await;
7985
7986 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
7987
7988 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7989 language_registry.add(rust_lang());
7990 let mut fake_servers = language_registry.register_fake_lsp(
7991 "Rust",
7992 FakeLspAdapter {
7993 capabilities: lsp::ServerCapabilities {
7994 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
7995 ..Default::default()
7996 },
7997 ..Default::default()
7998 },
7999 );
8000
8001 let buffer = project
8002 .update(cx, |project, cx| {
8003 project.open_local_buffer(path!("/file.rs"), cx)
8004 })
8005 .await
8006 .unwrap();
8007
8008 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
8009 let (editor, cx) = cx.add_window_view(|window, cx| {
8010 build_editor_with_project(project.clone(), buffer, window, cx)
8011 });
8012 editor.update_in(cx, |editor, window, cx| {
8013 editor.set_text("one\ntwo\nthree\n", window, cx)
8014 });
8015 assert!(cx.read(|cx| editor.is_dirty(cx)));
8016
8017 cx.executor().start_waiting();
8018 let fake_server = fake_servers.next().await.unwrap();
8019
8020 let save = editor
8021 .update_in(cx, |editor, window, cx| {
8022 editor.save(true, project.clone(), window, cx)
8023 })
8024 .unwrap();
8025 fake_server
8026 .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
8027 assert_eq!(
8028 params.text_document.uri,
8029 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
8030 );
8031 assert_eq!(params.options.tab_size, 4);
8032 Ok(Some(vec![lsp::TextEdit::new(
8033 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
8034 ", ".to_string(),
8035 )]))
8036 })
8037 .next()
8038 .await;
8039 cx.executor().start_waiting();
8040 save.await;
8041 assert_eq!(
8042 editor.update(cx, |editor, cx| editor.text(cx)),
8043 "one, two\nthree\n"
8044 );
8045 assert!(!cx.read(|cx| editor.is_dirty(cx)));
8046
8047 editor.update_in(cx, |editor, window, cx| {
8048 editor.set_text("one\ntwo\nthree\n", window, cx)
8049 });
8050 assert!(cx.read(|cx| editor.is_dirty(cx)));
8051
8052 // Ensure we can still save even if formatting hangs.
8053 fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
8054 move |params, _| async move {
8055 assert_eq!(
8056 params.text_document.uri,
8057 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
8058 );
8059 futures::future::pending::<()>().await;
8060 unreachable!()
8061 },
8062 );
8063 let save = editor
8064 .update_in(cx, |editor, window, cx| {
8065 editor.save(true, project.clone(), window, cx)
8066 })
8067 .unwrap();
8068 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
8069 cx.executor().start_waiting();
8070 save.await;
8071 assert_eq!(
8072 editor.update(cx, |editor, cx| editor.text(cx)),
8073 "one\ntwo\nthree\n"
8074 );
8075 assert!(!cx.read(|cx| editor.is_dirty(cx)));
8076
8077 // For non-dirty buffer, no formatting request should be sent
8078 let save = editor
8079 .update_in(cx, |editor, window, cx| {
8080 editor.save(true, project.clone(), window, cx)
8081 })
8082 .unwrap();
8083 let _pending_format_request = fake_server
8084 .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
8085 panic!("Should not be invoked on non-dirty buffer");
8086 })
8087 .next();
8088 cx.executor().start_waiting();
8089 save.await;
8090
8091 // Set Rust language override and assert overridden tabsize is sent to language server
8092 update_test_language_settings(cx, |settings| {
8093 settings.languages.insert(
8094 "Rust".into(),
8095 LanguageSettingsContent {
8096 tab_size: NonZeroU32::new(8),
8097 ..Default::default()
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 save = editor
8107 .update_in(cx, |editor, window, cx| {
8108 editor.save(true, project.clone(), window, cx)
8109 })
8110 .unwrap();
8111 fake_server
8112 .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
8113 assert_eq!(
8114 params.text_document.uri,
8115 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
8116 );
8117 assert_eq!(params.options.tab_size, 8);
8118 Ok(Some(vec![]))
8119 })
8120 .next()
8121 .await;
8122 cx.executor().start_waiting();
8123 save.await;
8124}
8125
8126#[gpui::test]
8127async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
8128 init_test(cx, |settings| {
8129 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
8130 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
8131 ))
8132 });
8133
8134 let fs = FakeFs::new(cx.executor());
8135 fs.insert_file(path!("/file.rs"), Default::default()).await;
8136
8137 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
8138
8139 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8140 language_registry.add(Arc::new(Language::new(
8141 LanguageConfig {
8142 name: "Rust".into(),
8143 matcher: LanguageMatcher {
8144 path_suffixes: vec!["rs".to_string()],
8145 ..Default::default()
8146 },
8147 ..LanguageConfig::default()
8148 },
8149 Some(tree_sitter_rust::LANGUAGE.into()),
8150 )));
8151 update_test_language_settings(cx, |settings| {
8152 // Enable Prettier formatting for the same buffer, and ensure
8153 // LSP is called instead of Prettier.
8154 settings.defaults.prettier = Some(PrettierSettings {
8155 allowed: true,
8156 ..PrettierSettings::default()
8157 });
8158 });
8159 let mut fake_servers = language_registry.register_fake_lsp(
8160 "Rust",
8161 FakeLspAdapter {
8162 capabilities: lsp::ServerCapabilities {
8163 document_formatting_provider: Some(lsp::OneOf::Left(true)),
8164 ..Default::default()
8165 },
8166 ..Default::default()
8167 },
8168 );
8169
8170 let buffer = project
8171 .update(cx, |project, cx| {
8172 project.open_local_buffer(path!("/file.rs"), cx)
8173 })
8174 .await
8175 .unwrap();
8176
8177 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
8178 let (editor, cx) = cx.add_window_view(|window, cx| {
8179 build_editor_with_project(project.clone(), buffer, window, cx)
8180 });
8181 editor.update_in(cx, |editor, window, cx| {
8182 editor.set_text("one\ntwo\nthree\n", window, cx)
8183 });
8184
8185 cx.executor().start_waiting();
8186 let fake_server = fake_servers.next().await.unwrap();
8187
8188 let format = editor
8189 .update_in(cx, |editor, window, cx| {
8190 editor.perform_format(
8191 project.clone(),
8192 FormatTrigger::Manual,
8193 FormatTarget::Buffers,
8194 window,
8195 cx,
8196 )
8197 })
8198 .unwrap();
8199 fake_server
8200 .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
8201 assert_eq!(
8202 params.text_document.uri,
8203 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
8204 );
8205 assert_eq!(params.options.tab_size, 4);
8206 Ok(Some(vec![lsp::TextEdit::new(
8207 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
8208 ", ".to_string(),
8209 )]))
8210 })
8211 .next()
8212 .await;
8213 cx.executor().start_waiting();
8214 format.await;
8215 assert_eq!(
8216 editor.update(cx, |editor, cx| editor.text(cx)),
8217 "one, two\nthree\n"
8218 );
8219
8220 editor.update_in(cx, |editor, window, cx| {
8221 editor.set_text("one\ntwo\nthree\n", window, cx)
8222 });
8223 // Ensure we don't lock if formatting hangs.
8224 fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
8225 move |params, _| async move {
8226 assert_eq!(
8227 params.text_document.uri,
8228 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
8229 );
8230 futures::future::pending::<()>().await;
8231 unreachable!()
8232 },
8233 );
8234 let format = editor
8235 .update_in(cx, |editor, window, cx| {
8236 editor.perform_format(
8237 project,
8238 FormatTrigger::Manual,
8239 FormatTarget::Buffers,
8240 window,
8241 cx,
8242 )
8243 })
8244 .unwrap();
8245 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
8246 cx.executor().start_waiting();
8247 format.await;
8248 assert_eq!(
8249 editor.update(cx, |editor, cx| editor.text(cx)),
8250 "one\ntwo\nthree\n"
8251 );
8252}
8253
8254#[gpui::test]
8255async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
8256 init_test(cx, |settings| {
8257 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
8258 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
8259 ))
8260 });
8261
8262 let fs = FakeFs::new(cx.executor());
8263 fs.insert_file(path!("/file.ts"), Default::default()).await;
8264
8265 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
8266
8267 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8268 language_registry.add(Arc::new(Language::new(
8269 LanguageConfig {
8270 name: "TypeScript".into(),
8271 matcher: LanguageMatcher {
8272 path_suffixes: vec!["ts".to_string()],
8273 ..Default::default()
8274 },
8275 ..LanguageConfig::default()
8276 },
8277 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
8278 )));
8279 update_test_language_settings(cx, |settings| {
8280 settings.defaults.prettier = Some(PrettierSettings {
8281 allowed: true,
8282 ..PrettierSettings::default()
8283 });
8284 });
8285 let mut fake_servers = language_registry.register_fake_lsp(
8286 "TypeScript",
8287 FakeLspAdapter {
8288 capabilities: lsp::ServerCapabilities {
8289 code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
8290 ..Default::default()
8291 },
8292 ..Default::default()
8293 },
8294 );
8295
8296 let buffer = project
8297 .update(cx, |project, cx| {
8298 project.open_local_buffer(path!("/file.ts"), cx)
8299 })
8300 .await
8301 .unwrap();
8302
8303 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
8304 let (editor, cx) = cx.add_window_view(|window, cx| {
8305 build_editor_with_project(project.clone(), buffer, window, cx)
8306 });
8307 editor.update_in(cx, |editor, window, cx| {
8308 editor.set_text(
8309 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
8310 window,
8311 cx,
8312 )
8313 });
8314
8315 cx.executor().start_waiting();
8316 let fake_server = fake_servers.next().await.unwrap();
8317
8318 let format = editor
8319 .update_in(cx, |editor, window, cx| {
8320 editor.perform_code_action_kind(
8321 project.clone(),
8322 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
8323 window,
8324 cx,
8325 )
8326 })
8327 .unwrap();
8328 fake_server
8329 .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
8330 assert_eq!(
8331 params.text_document.uri,
8332 lsp::Url::from_file_path(path!("/file.ts")).unwrap()
8333 );
8334 Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
8335 lsp::CodeAction {
8336 title: "Organize Imports".to_string(),
8337 kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
8338 edit: Some(lsp::WorkspaceEdit {
8339 changes: Some(
8340 [(
8341 params.text_document.uri.clone(),
8342 vec![lsp::TextEdit::new(
8343 lsp::Range::new(
8344 lsp::Position::new(1, 0),
8345 lsp::Position::new(2, 0),
8346 ),
8347 "".to_string(),
8348 )],
8349 )]
8350 .into_iter()
8351 .collect(),
8352 ),
8353 ..Default::default()
8354 }),
8355 ..Default::default()
8356 },
8357 )]))
8358 })
8359 .next()
8360 .await;
8361 cx.executor().start_waiting();
8362 format.await;
8363 assert_eq!(
8364 editor.update(cx, |editor, cx| editor.text(cx)),
8365 "import { a } from 'module';\n\nconst x = a;\n"
8366 );
8367
8368 editor.update_in(cx, |editor, window, cx| {
8369 editor.set_text(
8370 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
8371 window,
8372 cx,
8373 )
8374 });
8375 // Ensure we don't lock if code action hangs.
8376 fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
8377 move |params, _| async move {
8378 assert_eq!(
8379 params.text_document.uri,
8380 lsp::Url::from_file_path(path!("/file.ts")).unwrap()
8381 );
8382 futures::future::pending::<()>().await;
8383 unreachable!()
8384 },
8385 );
8386 let format = editor
8387 .update_in(cx, |editor, window, cx| {
8388 editor.perform_code_action_kind(
8389 project,
8390 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
8391 window,
8392 cx,
8393 )
8394 })
8395 .unwrap();
8396 cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
8397 cx.executor().start_waiting();
8398 format.await;
8399 assert_eq!(
8400 editor.update(cx, |editor, cx| editor.text(cx)),
8401 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
8402 );
8403}
8404
8405#[gpui::test]
8406async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
8407 init_test(cx, |_| {});
8408
8409 let mut cx = EditorLspTestContext::new_rust(
8410 lsp::ServerCapabilities {
8411 document_formatting_provider: Some(lsp::OneOf::Left(true)),
8412 ..Default::default()
8413 },
8414 cx,
8415 )
8416 .await;
8417
8418 cx.set_state(indoc! {"
8419 one.twoˇ
8420 "});
8421
8422 // The format request takes a long time. When it completes, it inserts
8423 // a newline and an indent before the `.`
8424 cx.lsp
8425 .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
8426 let executor = cx.background_executor().clone();
8427 async move {
8428 executor.timer(Duration::from_millis(100)).await;
8429 Ok(Some(vec![lsp::TextEdit {
8430 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
8431 new_text: "\n ".into(),
8432 }]))
8433 }
8434 });
8435
8436 // Submit a format request.
8437 let format_1 = cx
8438 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
8439 .unwrap();
8440 cx.executor().run_until_parked();
8441
8442 // Submit a second format request.
8443 let format_2 = cx
8444 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
8445 .unwrap();
8446 cx.executor().run_until_parked();
8447
8448 // Wait for both format requests to complete
8449 cx.executor().advance_clock(Duration::from_millis(200));
8450 cx.executor().start_waiting();
8451 format_1.await.unwrap();
8452 cx.executor().start_waiting();
8453 format_2.await.unwrap();
8454
8455 // The formatting edits only happens once.
8456 cx.assert_editor_state(indoc! {"
8457 one
8458 .twoˇ
8459 "});
8460}
8461
8462#[gpui::test]
8463async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
8464 init_test(cx, |settings| {
8465 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
8466 });
8467
8468 let mut cx = EditorLspTestContext::new_rust(
8469 lsp::ServerCapabilities {
8470 document_formatting_provider: Some(lsp::OneOf::Left(true)),
8471 ..Default::default()
8472 },
8473 cx,
8474 )
8475 .await;
8476
8477 // Set up a buffer white some trailing whitespace and no trailing newline.
8478 cx.set_state(
8479 &[
8480 "one ", //
8481 "twoˇ", //
8482 "three ", //
8483 "four", //
8484 ]
8485 .join("\n"),
8486 );
8487
8488 // Submit a format request.
8489 let format = cx
8490 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
8491 .unwrap();
8492
8493 // Record which buffer changes have been sent to the language server
8494 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
8495 cx.lsp
8496 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
8497 let buffer_changes = buffer_changes.clone();
8498 move |params, _| {
8499 buffer_changes.lock().extend(
8500 params
8501 .content_changes
8502 .into_iter()
8503 .map(|e| (e.range.unwrap(), e.text)),
8504 );
8505 }
8506 });
8507
8508 // Handle formatting requests to the language server.
8509 cx.lsp
8510 .set_request_handler::<lsp::request::Formatting, _, _>({
8511 let buffer_changes = buffer_changes.clone();
8512 move |_, _| {
8513 // When formatting is requested, trailing whitespace has already been stripped,
8514 // and the trailing newline has already been added.
8515 assert_eq!(
8516 &buffer_changes.lock()[1..],
8517 &[
8518 (
8519 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
8520 "".into()
8521 ),
8522 (
8523 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
8524 "".into()
8525 ),
8526 (
8527 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
8528 "\n".into()
8529 ),
8530 ]
8531 );
8532
8533 // Insert blank lines between each line of the buffer.
8534 async move {
8535 Ok(Some(vec![
8536 lsp::TextEdit {
8537 range: lsp::Range::new(
8538 lsp::Position::new(1, 0),
8539 lsp::Position::new(1, 0),
8540 ),
8541 new_text: "\n".into(),
8542 },
8543 lsp::TextEdit {
8544 range: lsp::Range::new(
8545 lsp::Position::new(2, 0),
8546 lsp::Position::new(2, 0),
8547 ),
8548 new_text: "\n".into(),
8549 },
8550 ]))
8551 }
8552 }
8553 });
8554
8555 // After formatting the buffer, the trailing whitespace is stripped,
8556 // a newline is appended, and the edits provided by the language server
8557 // have been applied.
8558 format.await.unwrap();
8559 cx.assert_editor_state(
8560 &[
8561 "one", //
8562 "", //
8563 "twoˇ", //
8564 "", //
8565 "three", //
8566 "four", //
8567 "", //
8568 ]
8569 .join("\n"),
8570 );
8571
8572 // Undoing the formatting undoes the trailing whitespace removal, the
8573 // trailing newline, and the LSP edits.
8574 cx.update_buffer(|buffer, cx| buffer.undo(cx));
8575 cx.assert_editor_state(
8576 &[
8577 "one ", //
8578 "twoˇ", //
8579 "three ", //
8580 "four", //
8581 ]
8582 .join("\n"),
8583 );
8584}
8585
8586#[gpui::test]
8587async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
8588 cx: &mut TestAppContext,
8589) {
8590 init_test(cx, |_| {});
8591
8592 cx.update(|cx| {
8593 cx.update_global::<SettingsStore, _>(|settings, cx| {
8594 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8595 settings.auto_signature_help = Some(true);
8596 });
8597 });
8598 });
8599
8600 let mut cx = EditorLspTestContext::new_rust(
8601 lsp::ServerCapabilities {
8602 signature_help_provider: Some(lsp::SignatureHelpOptions {
8603 ..Default::default()
8604 }),
8605 ..Default::default()
8606 },
8607 cx,
8608 )
8609 .await;
8610
8611 let language = Language::new(
8612 LanguageConfig {
8613 name: "Rust".into(),
8614 brackets: BracketPairConfig {
8615 pairs: vec![
8616 BracketPair {
8617 start: "{".to_string(),
8618 end: "}".to_string(),
8619 close: true,
8620 surround: true,
8621 newline: true,
8622 },
8623 BracketPair {
8624 start: "(".to_string(),
8625 end: ")".to_string(),
8626 close: true,
8627 surround: true,
8628 newline: true,
8629 },
8630 BracketPair {
8631 start: "/*".to_string(),
8632 end: " */".to_string(),
8633 close: true,
8634 surround: true,
8635 newline: true,
8636 },
8637 BracketPair {
8638 start: "[".to_string(),
8639 end: "]".to_string(),
8640 close: false,
8641 surround: false,
8642 newline: true,
8643 },
8644 BracketPair {
8645 start: "\"".to_string(),
8646 end: "\"".to_string(),
8647 close: true,
8648 surround: true,
8649 newline: false,
8650 },
8651 BracketPair {
8652 start: "<".to_string(),
8653 end: ">".to_string(),
8654 close: false,
8655 surround: true,
8656 newline: true,
8657 },
8658 ],
8659 ..Default::default()
8660 },
8661 autoclose_before: "})]".to_string(),
8662 ..Default::default()
8663 },
8664 Some(tree_sitter_rust::LANGUAGE.into()),
8665 );
8666 let language = Arc::new(language);
8667
8668 cx.language_registry().add(language.clone());
8669 cx.update_buffer(|buffer, cx| {
8670 buffer.set_language(Some(language), cx);
8671 });
8672
8673 cx.set_state(
8674 &r#"
8675 fn main() {
8676 sampleˇ
8677 }
8678 "#
8679 .unindent(),
8680 );
8681
8682 cx.update_editor(|editor, window, cx| {
8683 editor.handle_input("(", window, cx);
8684 });
8685 cx.assert_editor_state(
8686 &"
8687 fn main() {
8688 sample(ˇ)
8689 }
8690 "
8691 .unindent(),
8692 );
8693
8694 let mocked_response = lsp::SignatureHelp {
8695 signatures: vec![lsp::SignatureInformation {
8696 label: "fn sample(param1: u8, param2: u8)".to_string(),
8697 documentation: None,
8698 parameters: Some(vec![
8699 lsp::ParameterInformation {
8700 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8701 documentation: None,
8702 },
8703 lsp::ParameterInformation {
8704 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8705 documentation: None,
8706 },
8707 ]),
8708 active_parameter: None,
8709 }],
8710 active_signature: Some(0),
8711 active_parameter: Some(0),
8712 };
8713 handle_signature_help_request(&mut cx, mocked_response).await;
8714
8715 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8716 .await;
8717
8718 cx.editor(|editor, _, _| {
8719 let signature_help_state = editor.signature_help_state.popover().cloned();
8720 assert_eq!(
8721 signature_help_state.unwrap().label,
8722 "param1: u8, param2: u8"
8723 );
8724 });
8725}
8726
8727#[gpui::test]
8728async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
8729 init_test(cx, |_| {});
8730
8731 cx.update(|cx| {
8732 cx.update_global::<SettingsStore, _>(|settings, cx| {
8733 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8734 settings.auto_signature_help = Some(false);
8735 settings.show_signature_help_after_edits = Some(false);
8736 });
8737 });
8738 });
8739
8740 let mut cx = EditorLspTestContext::new_rust(
8741 lsp::ServerCapabilities {
8742 signature_help_provider: Some(lsp::SignatureHelpOptions {
8743 ..Default::default()
8744 }),
8745 ..Default::default()
8746 },
8747 cx,
8748 )
8749 .await;
8750
8751 let language = Language::new(
8752 LanguageConfig {
8753 name: "Rust".into(),
8754 brackets: BracketPairConfig {
8755 pairs: vec![
8756 BracketPair {
8757 start: "{".to_string(),
8758 end: "}".to_string(),
8759 close: true,
8760 surround: true,
8761 newline: true,
8762 },
8763 BracketPair {
8764 start: "(".to_string(),
8765 end: ")".to_string(),
8766 close: true,
8767 surround: true,
8768 newline: true,
8769 },
8770 BracketPair {
8771 start: "/*".to_string(),
8772 end: " */".to_string(),
8773 close: true,
8774 surround: true,
8775 newline: true,
8776 },
8777 BracketPair {
8778 start: "[".to_string(),
8779 end: "]".to_string(),
8780 close: false,
8781 surround: false,
8782 newline: true,
8783 },
8784 BracketPair {
8785 start: "\"".to_string(),
8786 end: "\"".to_string(),
8787 close: true,
8788 surround: true,
8789 newline: false,
8790 },
8791 BracketPair {
8792 start: "<".to_string(),
8793 end: ">".to_string(),
8794 close: false,
8795 surround: true,
8796 newline: true,
8797 },
8798 ],
8799 ..Default::default()
8800 },
8801 autoclose_before: "})]".to_string(),
8802 ..Default::default()
8803 },
8804 Some(tree_sitter_rust::LANGUAGE.into()),
8805 );
8806 let language = Arc::new(language);
8807
8808 cx.language_registry().add(language.clone());
8809 cx.update_buffer(|buffer, cx| {
8810 buffer.set_language(Some(language), cx);
8811 });
8812
8813 // Ensure that signature_help is not called when no signature help is enabled.
8814 cx.set_state(
8815 &r#"
8816 fn main() {
8817 sampleˇ
8818 }
8819 "#
8820 .unindent(),
8821 );
8822 cx.update_editor(|editor, window, cx| {
8823 editor.handle_input("(", window, cx);
8824 });
8825 cx.assert_editor_state(
8826 &"
8827 fn main() {
8828 sample(ˇ)
8829 }
8830 "
8831 .unindent(),
8832 );
8833 cx.editor(|editor, _, _| {
8834 assert!(editor.signature_help_state.task().is_none());
8835 });
8836
8837 let mocked_response = lsp::SignatureHelp {
8838 signatures: vec![lsp::SignatureInformation {
8839 label: "fn sample(param1: u8, param2: u8)".to_string(),
8840 documentation: None,
8841 parameters: Some(vec![
8842 lsp::ParameterInformation {
8843 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8844 documentation: None,
8845 },
8846 lsp::ParameterInformation {
8847 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8848 documentation: None,
8849 },
8850 ]),
8851 active_parameter: None,
8852 }],
8853 active_signature: Some(0),
8854 active_parameter: Some(0),
8855 };
8856
8857 // Ensure that signature_help is called when enabled afte edits
8858 cx.update(|_, cx| {
8859 cx.update_global::<SettingsStore, _>(|settings, cx| {
8860 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8861 settings.auto_signature_help = Some(false);
8862 settings.show_signature_help_after_edits = Some(true);
8863 });
8864 });
8865 });
8866 cx.set_state(
8867 &r#"
8868 fn main() {
8869 sampleˇ
8870 }
8871 "#
8872 .unindent(),
8873 );
8874 cx.update_editor(|editor, window, cx| {
8875 editor.handle_input("(", window, cx);
8876 });
8877 cx.assert_editor_state(
8878 &"
8879 fn main() {
8880 sample(ˇ)
8881 }
8882 "
8883 .unindent(),
8884 );
8885 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8886 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8887 .await;
8888 cx.update_editor(|editor, _, _| {
8889 let signature_help_state = editor.signature_help_state.popover().cloned();
8890 assert!(signature_help_state.is_some());
8891 assert_eq!(
8892 signature_help_state.unwrap().label,
8893 "param1: u8, param2: u8"
8894 );
8895 editor.signature_help_state = SignatureHelpState::default();
8896 });
8897
8898 // Ensure that signature_help is called when auto signature help override is enabled
8899 cx.update(|_, cx| {
8900 cx.update_global::<SettingsStore, _>(|settings, cx| {
8901 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8902 settings.auto_signature_help = Some(true);
8903 settings.show_signature_help_after_edits = Some(false);
8904 });
8905 });
8906 });
8907 cx.set_state(
8908 &r#"
8909 fn main() {
8910 sampleˇ
8911 }
8912 "#
8913 .unindent(),
8914 );
8915 cx.update_editor(|editor, window, cx| {
8916 editor.handle_input("(", window, cx);
8917 });
8918 cx.assert_editor_state(
8919 &"
8920 fn main() {
8921 sample(ˇ)
8922 }
8923 "
8924 .unindent(),
8925 );
8926 handle_signature_help_request(&mut cx, mocked_response).await;
8927 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8928 .await;
8929 cx.editor(|editor, _, _| {
8930 let signature_help_state = editor.signature_help_state.popover().cloned();
8931 assert!(signature_help_state.is_some());
8932 assert_eq!(
8933 signature_help_state.unwrap().label,
8934 "param1: u8, param2: u8"
8935 );
8936 });
8937}
8938
8939#[gpui::test]
8940async fn test_signature_help(cx: &mut TestAppContext) {
8941 init_test(cx, |_| {});
8942 cx.update(|cx| {
8943 cx.update_global::<SettingsStore, _>(|settings, cx| {
8944 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8945 settings.auto_signature_help = Some(true);
8946 });
8947 });
8948 });
8949
8950 let mut cx = EditorLspTestContext::new_rust(
8951 lsp::ServerCapabilities {
8952 signature_help_provider: Some(lsp::SignatureHelpOptions {
8953 ..Default::default()
8954 }),
8955 ..Default::default()
8956 },
8957 cx,
8958 )
8959 .await;
8960
8961 // A test that directly calls `show_signature_help`
8962 cx.update_editor(|editor, window, cx| {
8963 editor.show_signature_help(&ShowSignatureHelp, window, cx);
8964 });
8965
8966 let mocked_response = lsp::SignatureHelp {
8967 signatures: vec![lsp::SignatureInformation {
8968 label: "fn sample(param1: u8, param2: u8)".to_string(),
8969 documentation: None,
8970 parameters: Some(vec![
8971 lsp::ParameterInformation {
8972 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8973 documentation: None,
8974 },
8975 lsp::ParameterInformation {
8976 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8977 documentation: None,
8978 },
8979 ]),
8980 active_parameter: None,
8981 }],
8982 active_signature: Some(0),
8983 active_parameter: Some(0),
8984 };
8985 handle_signature_help_request(&mut cx, mocked_response).await;
8986
8987 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8988 .await;
8989
8990 cx.editor(|editor, _, _| {
8991 let signature_help_state = editor.signature_help_state.popover().cloned();
8992 assert!(signature_help_state.is_some());
8993 assert_eq!(
8994 signature_help_state.unwrap().label,
8995 "param1: u8, param2: u8"
8996 );
8997 });
8998
8999 // When exiting outside from inside the brackets, `signature_help` is closed.
9000 cx.set_state(indoc! {"
9001 fn main() {
9002 sample(ˇ);
9003 }
9004
9005 fn sample(param1: u8, param2: u8) {}
9006 "});
9007
9008 cx.update_editor(|editor, window, cx| {
9009 editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
9010 });
9011
9012 let mocked_response = lsp::SignatureHelp {
9013 signatures: Vec::new(),
9014 active_signature: None,
9015 active_parameter: None,
9016 };
9017 handle_signature_help_request(&mut cx, mocked_response).await;
9018
9019 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
9020 .await;
9021
9022 cx.editor(|editor, _, _| {
9023 assert!(!editor.signature_help_state.is_shown());
9024 });
9025
9026 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
9027 cx.set_state(indoc! {"
9028 fn main() {
9029 sample(ˇ);
9030 }
9031
9032 fn sample(param1: u8, param2: u8) {}
9033 "});
9034
9035 let mocked_response = lsp::SignatureHelp {
9036 signatures: vec![lsp::SignatureInformation {
9037 label: "fn sample(param1: u8, param2: u8)".to_string(),
9038 documentation: None,
9039 parameters: Some(vec![
9040 lsp::ParameterInformation {
9041 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
9042 documentation: None,
9043 },
9044 lsp::ParameterInformation {
9045 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
9046 documentation: None,
9047 },
9048 ]),
9049 active_parameter: None,
9050 }],
9051 active_signature: Some(0),
9052 active_parameter: Some(0),
9053 };
9054 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
9055 cx.condition(|editor, _| editor.signature_help_state.is_shown())
9056 .await;
9057 cx.editor(|editor, _, _| {
9058 assert!(editor.signature_help_state.is_shown());
9059 });
9060
9061 // Restore the popover with more parameter input
9062 cx.set_state(indoc! {"
9063 fn main() {
9064 sample(param1, param2ˇ);
9065 }
9066
9067 fn sample(param1: u8, param2: u8) {}
9068 "});
9069
9070 let mocked_response = lsp::SignatureHelp {
9071 signatures: vec![lsp::SignatureInformation {
9072 label: "fn sample(param1: u8, param2: u8)".to_string(),
9073 documentation: None,
9074 parameters: Some(vec![
9075 lsp::ParameterInformation {
9076 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
9077 documentation: None,
9078 },
9079 lsp::ParameterInformation {
9080 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
9081 documentation: None,
9082 },
9083 ]),
9084 active_parameter: None,
9085 }],
9086 active_signature: Some(0),
9087 active_parameter: Some(1),
9088 };
9089 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
9090 cx.condition(|editor, _| editor.signature_help_state.is_shown())
9091 .await;
9092
9093 // When selecting a range, the popover is gone.
9094 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
9095 cx.update_editor(|editor, window, cx| {
9096 editor.change_selections(None, window, cx, |s| {
9097 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
9098 })
9099 });
9100 cx.assert_editor_state(indoc! {"
9101 fn main() {
9102 sample(param1, «ˇparam2»);
9103 }
9104
9105 fn sample(param1: u8, param2: u8) {}
9106 "});
9107 cx.editor(|editor, _, _| {
9108 assert!(!editor.signature_help_state.is_shown());
9109 });
9110
9111 // When unselecting again, the popover is back if within the brackets.
9112 cx.update_editor(|editor, window, cx| {
9113 editor.change_selections(None, window, cx, |s| {
9114 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
9115 })
9116 });
9117 cx.assert_editor_state(indoc! {"
9118 fn main() {
9119 sample(param1, ˇparam2);
9120 }
9121
9122 fn sample(param1: u8, param2: u8) {}
9123 "});
9124 handle_signature_help_request(&mut cx, mocked_response).await;
9125 cx.condition(|editor, _| editor.signature_help_state.is_shown())
9126 .await;
9127 cx.editor(|editor, _, _| {
9128 assert!(editor.signature_help_state.is_shown());
9129 });
9130
9131 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
9132 cx.update_editor(|editor, window, cx| {
9133 editor.change_selections(None, window, cx, |s| {
9134 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
9135 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
9136 })
9137 });
9138 cx.assert_editor_state(indoc! {"
9139 fn main() {
9140 sample(param1, ˇparam2);
9141 }
9142
9143 fn sample(param1: u8, param2: u8) {}
9144 "});
9145
9146 let mocked_response = lsp::SignatureHelp {
9147 signatures: vec![lsp::SignatureInformation {
9148 label: "fn sample(param1: u8, param2: u8)".to_string(),
9149 documentation: None,
9150 parameters: Some(vec![
9151 lsp::ParameterInformation {
9152 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
9153 documentation: None,
9154 },
9155 lsp::ParameterInformation {
9156 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
9157 documentation: None,
9158 },
9159 ]),
9160 active_parameter: None,
9161 }],
9162 active_signature: Some(0),
9163 active_parameter: Some(1),
9164 };
9165 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
9166 cx.condition(|editor, _| editor.signature_help_state.is_shown())
9167 .await;
9168 cx.update_editor(|editor, _, cx| {
9169 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
9170 });
9171 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
9172 .await;
9173 cx.update_editor(|editor, window, cx| {
9174 editor.change_selections(None, window, cx, |s| {
9175 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
9176 })
9177 });
9178 cx.assert_editor_state(indoc! {"
9179 fn main() {
9180 sample(param1, «ˇparam2»);
9181 }
9182
9183 fn sample(param1: u8, param2: u8) {}
9184 "});
9185 cx.update_editor(|editor, window, cx| {
9186 editor.change_selections(None, window, cx, |s| {
9187 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
9188 })
9189 });
9190 cx.assert_editor_state(indoc! {"
9191 fn main() {
9192 sample(param1, ˇparam2);
9193 }
9194
9195 fn sample(param1: u8, param2: u8) {}
9196 "});
9197 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
9198 .await;
9199}
9200
9201#[gpui::test]
9202async fn test_completion_mode(cx: &mut TestAppContext) {
9203 init_test(cx, |_| {});
9204 let mut cx = EditorLspTestContext::new_rust(
9205 lsp::ServerCapabilities {
9206 completion_provider: Some(lsp::CompletionOptions {
9207 resolve_provider: Some(true),
9208 ..Default::default()
9209 }),
9210 ..Default::default()
9211 },
9212 cx,
9213 )
9214 .await;
9215
9216 struct Run {
9217 run_description: &'static str,
9218 initial_state: String,
9219 buffer_marked_text: String,
9220 completion_text: &'static str,
9221 expected_with_insertion_mode: String,
9222 expected_with_replace_mode: String,
9223 expected_with_replace_subsequence_mode: String,
9224 expected_with_replace_suffix_mode: String,
9225 }
9226
9227 let runs = [
9228 Run {
9229 run_description: "Start of word matches completion text",
9230 initial_state: "before ediˇ after".into(),
9231 buffer_marked_text: "before <edi|> after".into(),
9232 completion_text: "editor",
9233 expected_with_insertion_mode: "before editorˇ after".into(),
9234 expected_with_replace_mode: "before editorˇ after".into(),
9235 expected_with_replace_subsequence_mode: "before editorˇ after".into(),
9236 expected_with_replace_suffix_mode: "before editorˇ after".into(),
9237 },
9238 Run {
9239 run_description: "Accept same text at the middle of the word",
9240 initial_state: "before ediˇtor after".into(),
9241 buffer_marked_text: "before <edi|tor> after".into(),
9242 completion_text: "editor",
9243 expected_with_insertion_mode: "before editorˇtor after".into(),
9244 expected_with_replace_mode: "before ediˇtor after".into(),
9245 expected_with_replace_subsequence_mode: "before ediˇtor after".into(),
9246 expected_with_replace_suffix_mode: "before ediˇtor after".into(),
9247 },
9248 Run {
9249 run_description: "End of word matches completion text -- cursor at end",
9250 initial_state: "before torˇ after".into(),
9251 buffer_marked_text: "before <tor|> after".into(),
9252 completion_text: "editor",
9253 expected_with_insertion_mode: "before editorˇ after".into(),
9254 expected_with_replace_mode: "before editorˇ after".into(),
9255 expected_with_replace_subsequence_mode: "before editorˇ after".into(),
9256 expected_with_replace_suffix_mode: "before editorˇ after".into(),
9257 },
9258 Run {
9259 run_description: "End of word matches completion text -- cursor at start",
9260 initial_state: "before ˇtor after".into(),
9261 buffer_marked_text: "before <|tor> after".into(),
9262 completion_text: "editor",
9263 expected_with_insertion_mode: "before editorˇtor after".into(),
9264 expected_with_replace_mode: "before editorˇ after".into(),
9265 expected_with_replace_subsequence_mode: "before editorˇ after".into(),
9266 expected_with_replace_suffix_mode: "before editorˇ after".into(),
9267 },
9268 Run {
9269 run_description: "Prepend text containing whitespace",
9270 initial_state: "pˇfield: bool".into(),
9271 buffer_marked_text: "<p|field>: bool".into(),
9272 completion_text: "pub ",
9273 expected_with_insertion_mode: "pub ˇfield: bool".into(),
9274 expected_with_replace_mode: "pub ˇ: bool".into(),
9275 expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
9276 expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
9277 },
9278 Run {
9279 run_description: "Add element to start of list",
9280 initial_state: "[element_ˇelement_2]".into(),
9281 buffer_marked_text: "[<element_|element_2>]".into(),
9282 completion_text: "element_1",
9283 expected_with_insertion_mode: "[element_1ˇelement_2]".into(),
9284 expected_with_replace_mode: "[element_1ˇ]".into(),
9285 expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
9286 expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
9287 },
9288 Run {
9289 run_description: "Add element to start of list -- first and second elements are equal",
9290 initial_state: "[elˇelement]".into(),
9291 buffer_marked_text: "[<el|element>]".into(),
9292 completion_text: "element",
9293 expected_with_insertion_mode: "[elementˇelement]".into(),
9294 expected_with_replace_mode: "[elˇement]".into(),
9295 expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
9296 expected_with_replace_suffix_mode: "[elˇement]".into(),
9297 },
9298 Run {
9299 run_description: "Ends with matching suffix",
9300 initial_state: "SubˇError".into(),
9301 buffer_marked_text: "<Sub|Error>".into(),
9302 completion_text: "SubscriptionError",
9303 expected_with_insertion_mode: "SubscriptionErrorˇError".into(),
9304 expected_with_replace_mode: "SubscriptionErrorˇ".into(),
9305 expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
9306 expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
9307 },
9308 Run {
9309 run_description: "Suffix is a subsequence -- contiguous",
9310 initial_state: "SubˇErr".into(),
9311 buffer_marked_text: "<Sub|Err>".into(),
9312 completion_text: "SubscriptionError",
9313 expected_with_insertion_mode: "SubscriptionErrorˇErr".into(),
9314 expected_with_replace_mode: "SubscriptionErrorˇ".into(),
9315 expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
9316 expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
9317 },
9318 Run {
9319 run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
9320 initial_state: "Suˇscrirr".into(),
9321 buffer_marked_text: "<Su|scrirr>".into(),
9322 completion_text: "SubscriptionError",
9323 expected_with_insertion_mode: "SubscriptionErrorˇscrirr".into(),
9324 expected_with_replace_mode: "SubscriptionErrorˇ".into(),
9325 expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
9326 expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
9327 },
9328 Run {
9329 run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
9330 initial_state: "foo(indˇix)".into(),
9331 buffer_marked_text: "foo(<ind|ix>)".into(),
9332 completion_text: "node_index",
9333 expected_with_insertion_mode: "foo(node_indexˇix)".into(),
9334 expected_with_replace_mode: "foo(node_indexˇ)".into(),
9335 expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
9336 expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
9337 },
9338 ];
9339
9340 for run in runs {
9341 let run_variations = [
9342 (LspInsertMode::Insert, run.expected_with_insertion_mode),
9343 (LspInsertMode::Replace, run.expected_with_replace_mode),
9344 (
9345 LspInsertMode::ReplaceSubsequence,
9346 run.expected_with_replace_subsequence_mode,
9347 ),
9348 (
9349 LspInsertMode::ReplaceSuffix,
9350 run.expected_with_replace_suffix_mode,
9351 ),
9352 ];
9353
9354 for (lsp_insert_mode, expected_text) in run_variations {
9355 eprintln!(
9356 "run = {:?}, mode = {lsp_insert_mode:.?}",
9357 run.run_description,
9358 );
9359
9360 update_test_language_settings(&mut cx, |settings| {
9361 settings.defaults.completions = Some(CompletionSettings {
9362 lsp_insert_mode,
9363 words: WordsCompletionMode::Disabled,
9364 lsp: true,
9365 lsp_fetch_timeout_ms: 0,
9366 });
9367 });
9368
9369 cx.set_state(&run.initial_state);
9370 cx.update_editor(|editor, window, cx| {
9371 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
9372 });
9373
9374 let counter = Arc::new(AtomicUsize::new(0));
9375 handle_completion_request_with_insert_and_replace(
9376 &mut cx,
9377 &run.buffer_marked_text,
9378 vec![run.completion_text],
9379 counter.clone(),
9380 )
9381 .await;
9382 cx.condition(|editor, _| editor.context_menu_visible())
9383 .await;
9384 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
9385
9386 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
9387 editor
9388 .confirm_completion(&ConfirmCompletion::default(), window, cx)
9389 .unwrap()
9390 });
9391 cx.assert_editor_state(&expected_text);
9392 handle_resolve_completion_request(&mut cx, None).await;
9393 apply_additional_edits.await.unwrap();
9394 }
9395 }
9396}
9397
9398#[gpui::test]
9399async fn test_completion(cx: &mut TestAppContext) {
9400 init_test(cx, |_| {});
9401
9402 let mut cx = EditorLspTestContext::new_rust(
9403 lsp::ServerCapabilities {
9404 completion_provider: Some(lsp::CompletionOptions {
9405 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
9406 resolve_provider: Some(true),
9407 ..Default::default()
9408 }),
9409 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
9410 ..Default::default()
9411 },
9412 cx,
9413 )
9414 .await;
9415 let counter = Arc::new(AtomicUsize::new(0));
9416
9417 cx.set_state(indoc! {"
9418 oneˇ
9419 two
9420 three
9421 "});
9422 cx.simulate_keystroke(".");
9423 handle_completion_request(
9424 &mut cx,
9425 indoc! {"
9426 one.|<>
9427 two
9428 three
9429 "},
9430 vec!["first_completion", "second_completion"],
9431 counter.clone(),
9432 )
9433 .await;
9434 cx.condition(|editor, _| editor.context_menu_visible())
9435 .await;
9436 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
9437
9438 let _handler = handle_signature_help_request(
9439 &mut cx,
9440 lsp::SignatureHelp {
9441 signatures: vec![lsp::SignatureInformation {
9442 label: "test signature".to_string(),
9443 documentation: None,
9444 parameters: Some(vec![lsp::ParameterInformation {
9445 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
9446 documentation: None,
9447 }]),
9448 active_parameter: None,
9449 }],
9450 active_signature: None,
9451 active_parameter: None,
9452 },
9453 );
9454 cx.update_editor(|editor, window, cx| {
9455 assert!(
9456 !editor.signature_help_state.is_shown(),
9457 "No signature help was called for"
9458 );
9459 editor.show_signature_help(&ShowSignatureHelp, window, cx);
9460 });
9461 cx.run_until_parked();
9462 cx.update_editor(|editor, _, _| {
9463 assert!(
9464 !editor.signature_help_state.is_shown(),
9465 "No signature help should be shown when completions menu is open"
9466 );
9467 });
9468
9469 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
9470 editor.context_menu_next(&Default::default(), window, cx);
9471 editor
9472 .confirm_completion(&ConfirmCompletion::default(), window, cx)
9473 .unwrap()
9474 });
9475 cx.assert_editor_state(indoc! {"
9476 one.second_completionˇ
9477 two
9478 three
9479 "});
9480
9481 handle_resolve_completion_request(
9482 &mut cx,
9483 Some(vec![
9484 (
9485 //This overlaps with the primary completion edit which is
9486 //misbehavior from the LSP spec, test that we filter it out
9487 indoc! {"
9488 one.second_ˇcompletion
9489 two
9490 threeˇ
9491 "},
9492 "overlapping additional edit",
9493 ),
9494 (
9495 indoc! {"
9496 one.second_completion
9497 two
9498 threeˇ
9499 "},
9500 "\nadditional edit",
9501 ),
9502 ]),
9503 )
9504 .await;
9505 apply_additional_edits.await.unwrap();
9506 cx.assert_editor_state(indoc! {"
9507 one.second_completionˇ
9508 two
9509 three
9510 additional edit
9511 "});
9512
9513 cx.set_state(indoc! {"
9514 one.second_completion
9515 twoˇ
9516 threeˇ
9517 additional edit
9518 "});
9519 cx.simulate_keystroke(" ");
9520 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9521 cx.simulate_keystroke("s");
9522 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9523
9524 cx.assert_editor_state(indoc! {"
9525 one.second_completion
9526 two sˇ
9527 three sˇ
9528 additional edit
9529 "});
9530 handle_completion_request(
9531 &mut cx,
9532 indoc! {"
9533 one.second_completion
9534 two s
9535 three <s|>
9536 additional edit
9537 "},
9538 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
9539 counter.clone(),
9540 )
9541 .await;
9542 cx.condition(|editor, _| editor.context_menu_visible())
9543 .await;
9544 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
9545
9546 cx.simulate_keystroke("i");
9547
9548 handle_completion_request(
9549 &mut cx,
9550 indoc! {"
9551 one.second_completion
9552 two si
9553 three <si|>
9554 additional edit
9555 "},
9556 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
9557 counter.clone(),
9558 )
9559 .await;
9560 cx.condition(|editor, _| editor.context_menu_visible())
9561 .await;
9562 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
9563
9564 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
9565 editor
9566 .confirm_completion(&ConfirmCompletion::default(), window, cx)
9567 .unwrap()
9568 });
9569 cx.assert_editor_state(indoc! {"
9570 one.second_completion
9571 two sixth_completionˇ
9572 three sixth_completionˇ
9573 additional edit
9574 "});
9575
9576 apply_additional_edits.await.unwrap();
9577
9578 update_test_language_settings(&mut cx, |settings| {
9579 settings.defaults.show_completions_on_input = Some(false);
9580 });
9581 cx.set_state("editorˇ");
9582 cx.simulate_keystroke(".");
9583 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9584 cx.simulate_keystrokes("c l o");
9585 cx.assert_editor_state("editor.cloˇ");
9586 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9587 cx.update_editor(|editor, window, cx| {
9588 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
9589 });
9590 handle_completion_request(
9591 &mut cx,
9592 "editor.<clo|>",
9593 vec!["close", "clobber"],
9594 counter.clone(),
9595 )
9596 .await;
9597 cx.condition(|editor, _| editor.context_menu_visible())
9598 .await;
9599 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
9600
9601 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
9602 editor
9603 .confirm_completion(&ConfirmCompletion::default(), window, cx)
9604 .unwrap()
9605 });
9606 cx.assert_editor_state("editor.closeˇ");
9607 handle_resolve_completion_request(&mut cx, None).await;
9608 apply_additional_edits.await.unwrap();
9609}
9610
9611#[gpui::test]
9612async fn test_word_completion(cx: &mut TestAppContext) {
9613 let lsp_fetch_timeout_ms = 10;
9614 init_test(cx, |language_settings| {
9615 language_settings.defaults.completions = Some(CompletionSettings {
9616 words: WordsCompletionMode::Fallback,
9617 lsp: true,
9618 lsp_fetch_timeout_ms: 10,
9619 lsp_insert_mode: LspInsertMode::Insert,
9620 });
9621 });
9622
9623 let mut cx = EditorLspTestContext::new_rust(
9624 lsp::ServerCapabilities {
9625 completion_provider: Some(lsp::CompletionOptions {
9626 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
9627 ..lsp::CompletionOptions::default()
9628 }),
9629 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
9630 ..lsp::ServerCapabilities::default()
9631 },
9632 cx,
9633 )
9634 .await;
9635
9636 let throttle_completions = Arc::new(AtomicBool::new(false));
9637
9638 let lsp_throttle_completions = throttle_completions.clone();
9639 let _completion_requests_handler =
9640 cx.lsp
9641 .server
9642 .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
9643 let lsp_throttle_completions = lsp_throttle_completions.clone();
9644 let cx = cx.clone();
9645 async move {
9646 if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
9647 cx.background_executor()
9648 .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
9649 .await;
9650 }
9651 Ok(Some(lsp::CompletionResponse::Array(vec![
9652 lsp::CompletionItem {
9653 label: "first".into(),
9654 ..lsp::CompletionItem::default()
9655 },
9656 lsp::CompletionItem {
9657 label: "last".into(),
9658 ..lsp::CompletionItem::default()
9659 },
9660 ])))
9661 }
9662 });
9663
9664 cx.set_state(indoc! {"
9665 oneˇ
9666 two
9667 three
9668 "});
9669 cx.simulate_keystroke(".");
9670 cx.executor().run_until_parked();
9671 cx.condition(|editor, _| editor.context_menu_visible())
9672 .await;
9673 cx.update_editor(|editor, window, cx| {
9674 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9675 {
9676 assert_eq!(
9677 completion_menu_entries(&menu),
9678 &["first", "last"],
9679 "When LSP server is fast to reply, no fallback word completions are used"
9680 );
9681 } else {
9682 panic!("expected completion menu to be open");
9683 }
9684 editor.cancel(&Cancel, window, cx);
9685 });
9686 cx.executor().run_until_parked();
9687 cx.condition(|editor, _| !editor.context_menu_visible())
9688 .await;
9689
9690 throttle_completions.store(true, atomic::Ordering::Release);
9691 cx.simulate_keystroke(".");
9692 cx.executor()
9693 .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
9694 cx.executor().run_until_parked();
9695 cx.condition(|editor, _| editor.context_menu_visible())
9696 .await;
9697 cx.update_editor(|editor, _, _| {
9698 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9699 {
9700 assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
9701 "When LSP server is slow, document words can be shown instead, if configured accordingly");
9702 } else {
9703 panic!("expected completion menu to be open");
9704 }
9705 });
9706}
9707
9708#[gpui::test]
9709async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
9710 init_test(cx, |language_settings| {
9711 language_settings.defaults.completions = Some(CompletionSettings {
9712 words: WordsCompletionMode::Enabled,
9713 lsp: true,
9714 lsp_fetch_timeout_ms: 0,
9715 lsp_insert_mode: LspInsertMode::Insert,
9716 });
9717 });
9718
9719 let mut cx = EditorLspTestContext::new_rust(
9720 lsp::ServerCapabilities {
9721 completion_provider: Some(lsp::CompletionOptions {
9722 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
9723 ..lsp::CompletionOptions::default()
9724 }),
9725 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
9726 ..lsp::ServerCapabilities::default()
9727 },
9728 cx,
9729 )
9730 .await;
9731
9732 let _completion_requests_handler =
9733 cx.lsp
9734 .server
9735 .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9736 Ok(Some(lsp::CompletionResponse::Array(vec![
9737 lsp::CompletionItem {
9738 label: "first".into(),
9739 ..lsp::CompletionItem::default()
9740 },
9741 lsp::CompletionItem {
9742 label: "last".into(),
9743 ..lsp::CompletionItem::default()
9744 },
9745 ])))
9746 });
9747
9748 cx.set_state(indoc! {"ˇ
9749 first
9750 last
9751 second
9752 "});
9753 cx.simulate_keystroke(".");
9754 cx.executor().run_until_parked();
9755 cx.condition(|editor, _| editor.context_menu_visible())
9756 .await;
9757 cx.update_editor(|editor, _, _| {
9758 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9759 {
9760 assert_eq!(
9761 completion_menu_entries(&menu),
9762 &["first", "last", "second"],
9763 "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
9764 );
9765 } else {
9766 panic!("expected completion menu to be open");
9767 }
9768 });
9769}
9770
9771#[gpui::test]
9772async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
9773 init_test(cx, |language_settings| {
9774 language_settings.defaults.completions = Some(CompletionSettings {
9775 words: WordsCompletionMode::Disabled,
9776 lsp: true,
9777 lsp_fetch_timeout_ms: 0,
9778 lsp_insert_mode: LspInsertMode::Insert,
9779 });
9780 });
9781
9782 let mut cx = EditorLspTestContext::new_rust(
9783 lsp::ServerCapabilities {
9784 completion_provider: Some(lsp::CompletionOptions {
9785 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
9786 ..lsp::CompletionOptions::default()
9787 }),
9788 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
9789 ..lsp::ServerCapabilities::default()
9790 },
9791 cx,
9792 )
9793 .await;
9794
9795 let _completion_requests_handler =
9796 cx.lsp
9797 .server
9798 .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9799 panic!("LSP completions should not be queried when dealing with word completions")
9800 });
9801
9802 cx.set_state(indoc! {"ˇ
9803 first
9804 last
9805 second
9806 "});
9807 cx.update_editor(|editor, window, cx| {
9808 editor.show_word_completions(&ShowWordCompletions, window, cx);
9809 });
9810 cx.executor().run_until_parked();
9811 cx.condition(|editor, _| editor.context_menu_visible())
9812 .await;
9813 cx.update_editor(|editor, _, _| {
9814 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9815 {
9816 assert_eq!(
9817 completion_menu_entries(&menu),
9818 &["first", "last", "second"],
9819 "`ShowWordCompletions` action should show word completions"
9820 );
9821 } else {
9822 panic!("expected completion menu to be open");
9823 }
9824 });
9825
9826 cx.simulate_keystroke("l");
9827 cx.executor().run_until_parked();
9828 cx.condition(|editor, _| editor.context_menu_visible())
9829 .await;
9830 cx.update_editor(|editor, _, _| {
9831 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9832 {
9833 assert_eq!(
9834 completion_menu_entries(&menu),
9835 &["last"],
9836 "After showing word completions, further editing should filter them and not query the LSP"
9837 );
9838 } else {
9839 panic!("expected completion menu to be open");
9840 }
9841 });
9842}
9843
9844#[gpui::test]
9845async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
9846 init_test(cx, |language_settings| {
9847 language_settings.defaults.completions = Some(CompletionSettings {
9848 words: WordsCompletionMode::Fallback,
9849 lsp: false,
9850 lsp_fetch_timeout_ms: 0,
9851 lsp_insert_mode: LspInsertMode::Insert,
9852 });
9853 });
9854
9855 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
9856
9857 cx.set_state(indoc! {"ˇ
9858 0_usize
9859 let
9860 33
9861 4.5f32
9862 "});
9863 cx.update_editor(|editor, window, cx| {
9864 editor.show_completions(&ShowCompletions::default(), window, cx);
9865 });
9866 cx.executor().run_until_parked();
9867 cx.condition(|editor, _| editor.context_menu_visible())
9868 .await;
9869 cx.update_editor(|editor, window, cx| {
9870 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9871 {
9872 assert_eq!(
9873 completion_menu_entries(&menu),
9874 &["let"],
9875 "With no digits in the completion query, no digits should be in the word completions"
9876 );
9877 } else {
9878 panic!("expected completion menu to be open");
9879 }
9880 editor.cancel(&Cancel, window, cx);
9881 });
9882
9883 cx.set_state(indoc! {"3ˇ
9884 0_usize
9885 let
9886 3
9887 33.35f32
9888 "});
9889 cx.update_editor(|editor, window, cx| {
9890 editor.show_completions(&ShowCompletions::default(), window, cx);
9891 });
9892 cx.executor().run_until_parked();
9893 cx.condition(|editor, _| editor.context_menu_visible())
9894 .await;
9895 cx.update_editor(|editor, _, _| {
9896 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9897 {
9898 assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
9899 return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
9900 } else {
9901 panic!("expected completion menu to be open");
9902 }
9903 });
9904}
9905
9906fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
9907 let position = || lsp::Position {
9908 line: params.text_document_position.position.line,
9909 character: params.text_document_position.position.character,
9910 };
9911 Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9912 range: lsp::Range {
9913 start: position(),
9914 end: position(),
9915 },
9916 new_text: text.to_string(),
9917 }))
9918}
9919
9920#[gpui::test]
9921async fn test_multiline_completion(cx: &mut TestAppContext) {
9922 init_test(cx, |_| {});
9923
9924 let fs = FakeFs::new(cx.executor());
9925 fs.insert_tree(
9926 path!("/a"),
9927 json!({
9928 "main.ts": "a",
9929 }),
9930 )
9931 .await;
9932
9933 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
9934 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9935 let typescript_language = Arc::new(Language::new(
9936 LanguageConfig {
9937 name: "TypeScript".into(),
9938 matcher: LanguageMatcher {
9939 path_suffixes: vec!["ts".to_string()],
9940 ..LanguageMatcher::default()
9941 },
9942 line_comments: vec!["// ".into()],
9943 ..LanguageConfig::default()
9944 },
9945 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
9946 ));
9947 language_registry.add(typescript_language.clone());
9948 let mut fake_servers = language_registry.register_fake_lsp(
9949 "TypeScript",
9950 FakeLspAdapter {
9951 capabilities: lsp::ServerCapabilities {
9952 completion_provider: Some(lsp::CompletionOptions {
9953 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
9954 ..lsp::CompletionOptions::default()
9955 }),
9956 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
9957 ..lsp::ServerCapabilities::default()
9958 },
9959 // Emulate vtsls label generation
9960 label_for_completion: Some(Box::new(|item, _| {
9961 let text = if let Some(description) = item
9962 .label_details
9963 .as_ref()
9964 .and_then(|label_details| label_details.description.as_ref())
9965 {
9966 format!("{} {}", item.label, description)
9967 } else if let Some(detail) = &item.detail {
9968 format!("{} {}", item.label, detail)
9969 } else {
9970 item.label.clone()
9971 };
9972 let len = text.len();
9973 Some(language::CodeLabel {
9974 text,
9975 runs: Vec::new(),
9976 filter_range: 0..len,
9977 })
9978 })),
9979 ..FakeLspAdapter::default()
9980 },
9981 );
9982 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
9983 let cx = &mut VisualTestContext::from_window(*workspace, cx);
9984 let worktree_id = workspace
9985 .update(cx, |workspace, _window, cx| {
9986 workspace.project().update(cx, |project, cx| {
9987 project.worktrees(cx).next().unwrap().read(cx).id()
9988 })
9989 })
9990 .unwrap();
9991 let _buffer = project
9992 .update(cx, |project, cx| {
9993 project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
9994 })
9995 .await
9996 .unwrap();
9997 let editor = workspace
9998 .update(cx, |workspace, window, cx| {
9999 workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
10000 })
10001 .unwrap()
10002 .await
10003 .unwrap()
10004 .downcast::<Editor>()
10005 .unwrap();
10006 let fake_server = fake_servers.next().await.unwrap();
10007
10008 let multiline_label = "StickyHeaderExcerpt {\n excerpt,\n next_excerpt_controls_present,\n next_buffer_row,\n }: StickyHeaderExcerpt<'_>,";
10009 let multiline_label_2 = "a\nb\nc\n";
10010 let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
10011 let multiline_description = "d\ne\nf\n";
10012 let multiline_detail_2 = "g\nh\ni\n";
10013
10014 let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
10015 move |params, _| async move {
10016 Ok(Some(lsp::CompletionResponse::Array(vec![
10017 lsp::CompletionItem {
10018 label: multiline_label.to_string(),
10019 text_edit: gen_text_edit(¶ms, "new_text_1"),
10020 ..lsp::CompletionItem::default()
10021 },
10022 lsp::CompletionItem {
10023 label: "single line label 1".to_string(),
10024 detail: Some(multiline_detail.to_string()),
10025 text_edit: gen_text_edit(¶ms, "new_text_2"),
10026 ..lsp::CompletionItem::default()
10027 },
10028 lsp::CompletionItem {
10029 label: "single line label 2".to_string(),
10030 label_details: Some(lsp::CompletionItemLabelDetails {
10031 description: Some(multiline_description.to_string()),
10032 detail: None,
10033 }),
10034 text_edit: gen_text_edit(¶ms, "new_text_2"),
10035 ..lsp::CompletionItem::default()
10036 },
10037 lsp::CompletionItem {
10038 label: multiline_label_2.to_string(),
10039 detail: Some(multiline_detail_2.to_string()),
10040 text_edit: gen_text_edit(¶ms, "new_text_3"),
10041 ..lsp::CompletionItem::default()
10042 },
10043 lsp::CompletionItem {
10044 label: "Label with many spaces and \t but without newlines".to_string(),
10045 detail: Some(
10046 "Details with many spaces and \t but without newlines".to_string(),
10047 ),
10048 text_edit: gen_text_edit(¶ms, "new_text_4"),
10049 ..lsp::CompletionItem::default()
10050 },
10051 ])))
10052 },
10053 );
10054
10055 editor.update_in(cx, |editor, window, cx| {
10056 cx.focus_self(window);
10057 editor.move_to_end(&MoveToEnd, window, cx);
10058 editor.handle_input(".", window, cx);
10059 });
10060 cx.run_until_parked();
10061 completion_handle.next().await.unwrap();
10062
10063 editor.update(cx, |editor, _| {
10064 assert!(editor.context_menu_visible());
10065 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10066 {
10067 let completion_labels = menu
10068 .completions
10069 .borrow()
10070 .iter()
10071 .map(|c| c.label.text.clone())
10072 .collect::<Vec<_>>();
10073 assert_eq!(
10074 completion_labels,
10075 &[
10076 "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
10077 "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
10078 "single line label 2 d e f ",
10079 "a b c g h i ",
10080 "Label with many spaces and \t but without newlines Details with many spaces and \t but without newlines",
10081 ],
10082 "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
10083 );
10084
10085 for completion in menu
10086 .completions
10087 .borrow()
10088 .iter() {
10089 assert_eq!(
10090 completion.label.filter_range,
10091 0..completion.label.text.len(),
10092 "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
10093 );
10094 }
10095 } else {
10096 panic!("expected completion menu to be open");
10097 }
10098 });
10099}
10100
10101#[gpui::test]
10102async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
10103 init_test(cx, |_| {});
10104 let mut cx = EditorLspTestContext::new_rust(
10105 lsp::ServerCapabilities {
10106 completion_provider: Some(lsp::CompletionOptions {
10107 trigger_characters: Some(vec![".".to_string()]),
10108 ..Default::default()
10109 }),
10110 ..Default::default()
10111 },
10112 cx,
10113 )
10114 .await;
10115 cx.lsp
10116 .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
10117 Ok(Some(lsp::CompletionResponse::Array(vec![
10118 lsp::CompletionItem {
10119 label: "first".into(),
10120 ..Default::default()
10121 },
10122 lsp::CompletionItem {
10123 label: "last".into(),
10124 ..Default::default()
10125 },
10126 ])))
10127 });
10128 cx.set_state("variableˇ");
10129 cx.simulate_keystroke(".");
10130 cx.executor().run_until_parked();
10131
10132 cx.update_editor(|editor, _, _| {
10133 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10134 {
10135 assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
10136 } else {
10137 panic!("expected completion menu to be open");
10138 }
10139 });
10140
10141 cx.update_editor(|editor, window, cx| {
10142 editor.move_page_down(&MovePageDown::default(), window, cx);
10143 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10144 {
10145 assert!(
10146 menu.selected_item == 1,
10147 "expected PageDown to select the last item from the context menu"
10148 );
10149 } else {
10150 panic!("expected completion menu to stay open after PageDown");
10151 }
10152 });
10153
10154 cx.update_editor(|editor, window, cx| {
10155 editor.move_page_up(&MovePageUp::default(), window, cx);
10156 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10157 {
10158 assert!(
10159 menu.selected_item == 0,
10160 "expected PageUp to select the first item from the context menu"
10161 );
10162 } else {
10163 panic!("expected completion menu to stay open after PageUp");
10164 }
10165 });
10166}
10167
10168#[gpui::test]
10169async fn test_completion_sort(cx: &mut TestAppContext) {
10170 init_test(cx, |_| {});
10171 let mut cx = EditorLspTestContext::new_rust(
10172 lsp::ServerCapabilities {
10173 completion_provider: Some(lsp::CompletionOptions {
10174 trigger_characters: Some(vec![".".to_string()]),
10175 ..Default::default()
10176 }),
10177 ..Default::default()
10178 },
10179 cx,
10180 )
10181 .await;
10182 cx.lsp
10183 .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
10184 Ok(Some(lsp::CompletionResponse::Array(vec![
10185 lsp::CompletionItem {
10186 label: "Range".into(),
10187 sort_text: Some("a".into()),
10188 ..Default::default()
10189 },
10190 lsp::CompletionItem {
10191 label: "r".into(),
10192 sort_text: Some("b".into()),
10193 ..Default::default()
10194 },
10195 lsp::CompletionItem {
10196 label: "ret".into(),
10197 sort_text: Some("c".into()),
10198 ..Default::default()
10199 },
10200 lsp::CompletionItem {
10201 label: "return".into(),
10202 sort_text: Some("d".into()),
10203 ..Default::default()
10204 },
10205 lsp::CompletionItem {
10206 label: "slice".into(),
10207 sort_text: Some("d".into()),
10208 ..Default::default()
10209 },
10210 ])))
10211 });
10212 cx.set_state("rˇ");
10213 cx.executor().run_until_parked();
10214 cx.update_editor(|editor, window, cx| {
10215 editor.show_completions(
10216 &ShowCompletions {
10217 trigger: Some("r".into()),
10218 },
10219 window,
10220 cx,
10221 );
10222 });
10223 cx.executor().run_until_parked();
10224
10225 cx.update_editor(|editor, _, _| {
10226 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10227 {
10228 assert_eq!(
10229 completion_menu_entries(&menu),
10230 &["r", "ret", "Range", "return"]
10231 );
10232 } else {
10233 panic!("expected completion menu to be open");
10234 }
10235 });
10236}
10237
10238#[gpui::test]
10239async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
10240 init_test(cx, |_| {});
10241
10242 let mut cx = EditorLspTestContext::new_rust(
10243 lsp::ServerCapabilities {
10244 completion_provider: Some(lsp::CompletionOptions {
10245 trigger_characters: Some(vec![".".to_string()]),
10246 resolve_provider: Some(true),
10247 ..Default::default()
10248 }),
10249 ..Default::default()
10250 },
10251 cx,
10252 )
10253 .await;
10254
10255 cx.set_state("fn main() { let a = 2ˇ; }");
10256 cx.simulate_keystroke(".");
10257 let completion_item = lsp::CompletionItem {
10258 label: "Some".into(),
10259 kind: Some(lsp::CompletionItemKind::SNIPPET),
10260 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
10261 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
10262 kind: lsp::MarkupKind::Markdown,
10263 value: "```rust\nSome(2)\n```".to_string(),
10264 })),
10265 deprecated: Some(false),
10266 sort_text: Some("Some".to_string()),
10267 filter_text: Some("Some".to_string()),
10268 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
10269 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10270 range: lsp::Range {
10271 start: lsp::Position {
10272 line: 0,
10273 character: 22,
10274 },
10275 end: lsp::Position {
10276 line: 0,
10277 character: 22,
10278 },
10279 },
10280 new_text: "Some(2)".to_string(),
10281 })),
10282 additional_text_edits: Some(vec![lsp::TextEdit {
10283 range: lsp::Range {
10284 start: lsp::Position {
10285 line: 0,
10286 character: 20,
10287 },
10288 end: lsp::Position {
10289 line: 0,
10290 character: 22,
10291 },
10292 },
10293 new_text: "".to_string(),
10294 }]),
10295 ..Default::default()
10296 };
10297
10298 let closure_completion_item = completion_item.clone();
10299 let counter = Arc::new(AtomicUsize::new(0));
10300 let counter_clone = counter.clone();
10301 let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
10302 let task_completion_item = closure_completion_item.clone();
10303 counter_clone.fetch_add(1, atomic::Ordering::Release);
10304 async move {
10305 Ok(Some(lsp::CompletionResponse::Array(vec![
10306 task_completion_item,
10307 ])))
10308 }
10309 });
10310
10311 cx.condition(|editor, _| editor.context_menu_visible())
10312 .await;
10313 cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
10314 assert!(request.next().await.is_some());
10315 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
10316
10317 cx.simulate_keystrokes("S o m");
10318 cx.condition(|editor, _| editor.context_menu_visible())
10319 .await;
10320 cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
10321 assert!(request.next().await.is_some());
10322 assert!(request.next().await.is_some());
10323 assert!(request.next().await.is_some());
10324 request.close();
10325 assert!(request.next().await.is_none());
10326 assert_eq!(
10327 counter.load(atomic::Ordering::Acquire),
10328 4,
10329 "With the completions menu open, only one LSP request should happen per input"
10330 );
10331}
10332
10333#[gpui::test]
10334async fn test_toggle_comment(cx: &mut TestAppContext) {
10335 init_test(cx, |_| {});
10336 let mut cx = EditorTestContext::new(cx).await;
10337 let language = Arc::new(Language::new(
10338 LanguageConfig {
10339 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
10340 ..Default::default()
10341 },
10342 Some(tree_sitter_rust::LANGUAGE.into()),
10343 ));
10344 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
10345
10346 // If multiple selections intersect a line, the line is only toggled once.
10347 cx.set_state(indoc! {"
10348 fn a() {
10349 «//b();
10350 ˇ»// «c();
10351 //ˇ» d();
10352 }
10353 "});
10354
10355 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
10356
10357 cx.assert_editor_state(indoc! {"
10358 fn a() {
10359 «b();
10360 c();
10361 ˇ» d();
10362 }
10363 "});
10364
10365 // The comment prefix is inserted at the same column for every line in a
10366 // selection.
10367 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
10368
10369 cx.assert_editor_state(indoc! {"
10370 fn a() {
10371 // «b();
10372 // c();
10373 ˇ»// d();
10374 }
10375 "});
10376
10377 // If a selection ends at the beginning of a line, that line is not toggled.
10378 cx.set_selections_state(indoc! {"
10379 fn a() {
10380 // b();
10381 «// c();
10382 ˇ» // d();
10383 }
10384 "});
10385
10386 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
10387
10388 cx.assert_editor_state(indoc! {"
10389 fn a() {
10390 // b();
10391 «c();
10392 ˇ» // d();
10393 }
10394 "});
10395
10396 // If a selection span a single line and is empty, the line is toggled.
10397 cx.set_state(indoc! {"
10398 fn a() {
10399 a();
10400 b();
10401 ˇ
10402 }
10403 "});
10404
10405 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
10406
10407 cx.assert_editor_state(indoc! {"
10408 fn a() {
10409 a();
10410 b();
10411 //•ˇ
10412 }
10413 "});
10414
10415 // If a selection span multiple lines, empty lines are not toggled.
10416 cx.set_state(indoc! {"
10417 fn a() {
10418 «a();
10419
10420 c();ˇ»
10421 }
10422 "});
10423
10424 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
10425
10426 cx.assert_editor_state(indoc! {"
10427 fn a() {
10428 // «a();
10429
10430 // c();ˇ»
10431 }
10432 "});
10433
10434 // If a selection includes multiple comment prefixes, all lines are uncommented.
10435 cx.set_state(indoc! {"
10436 fn a() {
10437 «// a();
10438 /// b();
10439 //! c();ˇ»
10440 }
10441 "});
10442
10443 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
10444
10445 cx.assert_editor_state(indoc! {"
10446 fn a() {
10447 «a();
10448 b();
10449 c();ˇ»
10450 }
10451 "});
10452}
10453
10454#[gpui::test]
10455async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
10456 init_test(cx, |_| {});
10457 let mut cx = EditorTestContext::new(cx).await;
10458 let language = Arc::new(Language::new(
10459 LanguageConfig {
10460 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
10461 ..Default::default()
10462 },
10463 Some(tree_sitter_rust::LANGUAGE.into()),
10464 ));
10465 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
10466
10467 let toggle_comments = &ToggleComments {
10468 advance_downwards: false,
10469 ignore_indent: true,
10470 };
10471
10472 // If multiple selections intersect a line, the line is only toggled once.
10473 cx.set_state(indoc! {"
10474 fn a() {
10475 // «b();
10476 // c();
10477 // ˇ» d();
10478 }
10479 "});
10480
10481 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10482
10483 cx.assert_editor_state(indoc! {"
10484 fn a() {
10485 «b();
10486 c();
10487 ˇ» d();
10488 }
10489 "});
10490
10491 // The comment prefix is inserted at the beginning of each line
10492 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10493
10494 cx.assert_editor_state(indoc! {"
10495 fn a() {
10496 // «b();
10497 // c();
10498 // ˇ» d();
10499 }
10500 "});
10501
10502 // If a selection ends at the beginning of a line, that line is not toggled.
10503 cx.set_selections_state(indoc! {"
10504 fn a() {
10505 // b();
10506 // «c();
10507 ˇ»// d();
10508 }
10509 "});
10510
10511 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10512
10513 cx.assert_editor_state(indoc! {"
10514 fn a() {
10515 // b();
10516 «c();
10517 ˇ»// d();
10518 }
10519 "});
10520
10521 // If a selection span a single line and is empty, the line is toggled.
10522 cx.set_state(indoc! {"
10523 fn a() {
10524 a();
10525 b();
10526 ˇ
10527 }
10528 "});
10529
10530 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10531
10532 cx.assert_editor_state(indoc! {"
10533 fn a() {
10534 a();
10535 b();
10536 //ˇ
10537 }
10538 "});
10539
10540 // If a selection span multiple lines, empty lines are not toggled.
10541 cx.set_state(indoc! {"
10542 fn a() {
10543 «a();
10544
10545 c();ˇ»
10546 }
10547 "});
10548
10549 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10550
10551 cx.assert_editor_state(indoc! {"
10552 fn a() {
10553 // «a();
10554
10555 // c();ˇ»
10556 }
10557 "});
10558
10559 // If a selection includes multiple comment prefixes, all lines are uncommented.
10560 cx.set_state(indoc! {"
10561 fn a() {
10562 // «a();
10563 /// b();
10564 //! c();ˇ»
10565 }
10566 "});
10567
10568 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10569
10570 cx.assert_editor_state(indoc! {"
10571 fn a() {
10572 «a();
10573 b();
10574 c();ˇ»
10575 }
10576 "});
10577}
10578
10579#[gpui::test]
10580async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
10581 init_test(cx, |_| {});
10582
10583 let language = Arc::new(Language::new(
10584 LanguageConfig {
10585 line_comments: vec!["// ".into()],
10586 ..Default::default()
10587 },
10588 Some(tree_sitter_rust::LANGUAGE.into()),
10589 ));
10590
10591 let mut cx = EditorTestContext::new(cx).await;
10592
10593 cx.language_registry().add(language.clone());
10594 cx.update_buffer(|buffer, cx| {
10595 buffer.set_language(Some(language), cx);
10596 });
10597
10598 let toggle_comments = &ToggleComments {
10599 advance_downwards: true,
10600 ignore_indent: false,
10601 };
10602
10603 // Single cursor on one line -> advance
10604 // Cursor moves horizontally 3 characters as well on non-blank line
10605 cx.set_state(indoc!(
10606 "fn a() {
10607 ˇdog();
10608 cat();
10609 }"
10610 ));
10611 cx.update_editor(|editor, window, cx| {
10612 editor.toggle_comments(toggle_comments, window, cx);
10613 });
10614 cx.assert_editor_state(indoc!(
10615 "fn a() {
10616 // dog();
10617 catˇ();
10618 }"
10619 ));
10620
10621 // Single selection on one line -> don't advance
10622 cx.set_state(indoc!(
10623 "fn a() {
10624 «dog()ˇ»;
10625 cat();
10626 }"
10627 ));
10628 cx.update_editor(|editor, window, cx| {
10629 editor.toggle_comments(toggle_comments, window, cx);
10630 });
10631 cx.assert_editor_state(indoc!(
10632 "fn a() {
10633 // «dog()ˇ»;
10634 cat();
10635 }"
10636 ));
10637
10638 // Multiple cursors on one line -> advance
10639 cx.set_state(indoc!(
10640 "fn a() {
10641 ˇdˇog();
10642 cat();
10643 }"
10644 ));
10645 cx.update_editor(|editor, window, cx| {
10646 editor.toggle_comments(toggle_comments, window, cx);
10647 });
10648 cx.assert_editor_state(indoc!(
10649 "fn a() {
10650 // dog();
10651 catˇ(ˇ);
10652 }"
10653 ));
10654
10655 // Multiple cursors on one line, with selection -> don't advance
10656 cx.set_state(indoc!(
10657 "fn a() {
10658 ˇdˇog«()ˇ»;
10659 cat();
10660 }"
10661 ));
10662 cx.update_editor(|editor, window, cx| {
10663 editor.toggle_comments(toggle_comments, window, cx);
10664 });
10665 cx.assert_editor_state(indoc!(
10666 "fn a() {
10667 // ˇdˇog«()ˇ»;
10668 cat();
10669 }"
10670 ));
10671
10672 // Single cursor on one line -> advance
10673 // Cursor moves to column 0 on blank line
10674 cx.set_state(indoc!(
10675 "fn a() {
10676 ˇdog();
10677
10678 cat();
10679 }"
10680 ));
10681 cx.update_editor(|editor, window, cx| {
10682 editor.toggle_comments(toggle_comments, window, cx);
10683 });
10684 cx.assert_editor_state(indoc!(
10685 "fn a() {
10686 // dog();
10687 ˇ
10688 cat();
10689 }"
10690 ));
10691
10692 // Single cursor on one line -> advance
10693 // Cursor starts and ends at column 0
10694 cx.set_state(indoc!(
10695 "fn a() {
10696 ˇ dog();
10697 cat();
10698 }"
10699 ));
10700 cx.update_editor(|editor, window, cx| {
10701 editor.toggle_comments(toggle_comments, window, cx);
10702 });
10703 cx.assert_editor_state(indoc!(
10704 "fn a() {
10705 // dog();
10706 ˇ cat();
10707 }"
10708 ));
10709}
10710
10711#[gpui::test]
10712async fn test_toggle_block_comment(cx: &mut TestAppContext) {
10713 init_test(cx, |_| {});
10714
10715 let mut cx = EditorTestContext::new(cx).await;
10716
10717 let html_language = Arc::new(
10718 Language::new(
10719 LanguageConfig {
10720 name: "HTML".into(),
10721 block_comment: Some(("<!-- ".into(), " -->".into())),
10722 ..Default::default()
10723 },
10724 Some(tree_sitter_html::LANGUAGE.into()),
10725 )
10726 .with_injection_query(
10727 r#"
10728 (script_element
10729 (raw_text) @injection.content
10730 (#set! injection.language "javascript"))
10731 "#,
10732 )
10733 .unwrap(),
10734 );
10735
10736 let javascript_language = Arc::new(Language::new(
10737 LanguageConfig {
10738 name: "JavaScript".into(),
10739 line_comments: vec!["// ".into()],
10740 ..Default::default()
10741 },
10742 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10743 ));
10744
10745 cx.language_registry().add(html_language.clone());
10746 cx.language_registry().add(javascript_language.clone());
10747 cx.update_buffer(|buffer, cx| {
10748 buffer.set_language(Some(html_language), cx);
10749 });
10750
10751 // Toggle comments for empty selections
10752 cx.set_state(
10753 &r#"
10754 <p>A</p>ˇ
10755 <p>B</p>ˇ
10756 <p>C</p>ˇ
10757 "#
10758 .unindent(),
10759 );
10760 cx.update_editor(|editor, window, cx| {
10761 editor.toggle_comments(&ToggleComments::default(), window, cx)
10762 });
10763 cx.assert_editor_state(
10764 &r#"
10765 <!-- <p>A</p>ˇ -->
10766 <!-- <p>B</p>ˇ -->
10767 <!-- <p>C</p>ˇ -->
10768 "#
10769 .unindent(),
10770 );
10771 cx.update_editor(|editor, window, cx| {
10772 editor.toggle_comments(&ToggleComments::default(), window, cx)
10773 });
10774 cx.assert_editor_state(
10775 &r#"
10776 <p>A</p>ˇ
10777 <p>B</p>ˇ
10778 <p>C</p>ˇ
10779 "#
10780 .unindent(),
10781 );
10782
10783 // Toggle comments for mixture of empty and non-empty selections, where
10784 // multiple selections occupy a given line.
10785 cx.set_state(
10786 &r#"
10787 <p>A«</p>
10788 <p>ˇ»B</p>ˇ
10789 <p>C«</p>
10790 <p>ˇ»D</p>ˇ
10791 "#
10792 .unindent(),
10793 );
10794
10795 cx.update_editor(|editor, window, cx| {
10796 editor.toggle_comments(&ToggleComments::default(), window, cx)
10797 });
10798 cx.assert_editor_state(
10799 &r#"
10800 <!-- <p>A«</p>
10801 <p>ˇ»B</p>ˇ -->
10802 <!-- <p>C«</p>
10803 <p>ˇ»D</p>ˇ -->
10804 "#
10805 .unindent(),
10806 );
10807 cx.update_editor(|editor, window, cx| {
10808 editor.toggle_comments(&ToggleComments::default(), window, cx)
10809 });
10810 cx.assert_editor_state(
10811 &r#"
10812 <p>A«</p>
10813 <p>ˇ»B</p>ˇ
10814 <p>C«</p>
10815 <p>ˇ»D</p>ˇ
10816 "#
10817 .unindent(),
10818 );
10819
10820 // Toggle comments when different languages are active for different
10821 // selections.
10822 cx.set_state(
10823 &r#"
10824 ˇ<script>
10825 ˇvar x = new Y();
10826 ˇ</script>
10827 "#
10828 .unindent(),
10829 );
10830 cx.executor().run_until_parked();
10831 cx.update_editor(|editor, window, cx| {
10832 editor.toggle_comments(&ToggleComments::default(), window, cx)
10833 });
10834 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
10835 // Uncommenting and commenting from this position brings in even more wrong artifacts.
10836 cx.assert_editor_state(
10837 &r#"
10838 <!-- ˇ<script> -->
10839 // ˇvar x = new Y();
10840 <!-- ˇ</script> -->
10841 "#
10842 .unindent(),
10843 );
10844}
10845
10846#[gpui::test]
10847fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
10848 init_test(cx, |_| {});
10849
10850 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10851 let multibuffer = cx.new(|cx| {
10852 let mut multibuffer = MultiBuffer::new(ReadWrite);
10853 multibuffer.push_excerpts(
10854 buffer.clone(),
10855 [
10856 ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
10857 ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
10858 ],
10859 cx,
10860 );
10861 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
10862 multibuffer
10863 });
10864
10865 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
10866 editor.update_in(cx, |editor, window, cx| {
10867 assert_eq!(editor.text(cx), "aaaa\nbbbb");
10868 editor.change_selections(None, window, cx, |s| {
10869 s.select_ranges([
10870 Point::new(0, 0)..Point::new(0, 0),
10871 Point::new(1, 0)..Point::new(1, 0),
10872 ])
10873 });
10874
10875 editor.handle_input("X", window, cx);
10876 assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
10877 assert_eq!(
10878 editor.selections.ranges(cx),
10879 [
10880 Point::new(0, 1)..Point::new(0, 1),
10881 Point::new(1, 1)..Point::new(1, 1),
10882 ]
10883 );
10884
10885 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
10886 editor.change_selections(None, window, cx, |s| {
10887 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
10888 });
10889 editor.backspace(&Default::default(), window, cx);
10890 assert_eq!(editor.text(cx), "Xa\nbbb");
10891 assert_eq!(
10892 editor.selections.ranges(cx),
10893 [Point::new(1, 0)..Point::new(1, 0)]
10894 );
10895
10896 editor.change_selections(None, window, cx, |s| {
10897 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
10898 });
10899 editor.backspace(&Default::default(), window, cx);
10900 assert_eq!(editor.text(cx), "X\nbb");
10901 assert_eq!(
10902 editor.selections.ranges(cx),
10903 [Point::new(0, 1)..Point::new(0, 1)]
10904 );
10905 });
10906}
10907
10908#[gpui::test]
10909fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
10910 init_test(cx, |_| {});
10911
10912 let markers = vec![('[', ']').into(), ('(', ')').into()];
10913 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
10914 indoc! {"
10915 [aaaa
10916 (bbbb]
10917 cccc)",
10918 },
10919 markers.clone(),
10920 );
10921 let excerpt_ranges = markers.into_iter().map(|marker| {
10922 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
10923 ExcerptRange::new(context.clone())
10924 });
10925 let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
10926 let multibuffer = cx.new(|cx| {
10927 let mut multibuffer = MultiBuffer::new(ReadWrite);
10928 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
10929 multibuffer
10930 });
10931
10932 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
10933 editor.update_in(cx, |editor, window, cx| {
10934 let (expected_text, selection_ranges) = marked_text_ranges(
10935 indoc! {"
10936 aaaa
10937 bˇbbb
10938 bˇbbˇb
10939 cccc"
10940 },
10941 true,
10942 );
10943 assert_eq!(editor.text(cx), expected_text);
10944 editor.change_selections(None, window, cx, |s| s.select_ranges(selection_ranges));
10945
10946 editor.handle_input("X", window, cx);
10947
10948 let (expected_text, expected_selections) = marked_text_ranges(
10949 indoc! {"
10950 aaaa
10951 bXˇbbXb
10952 bXˇbbXˇb
10953 cccc"
10954 },
10955 false,
10956 );
10957 assert_eq!(editor.text(cx), expected_text);
10958 assert_eq!(editor.selections.ranges(cx), expected_selections);
10959
10960 editor.newline(&Newline, window, cx);
10961 let (expected_text, expected_selections) = marked_text_ranges(
10962 indoc! {"
10963 aaaa
10964 bX
10965 ˇbbX
10966 b
10967 bX
10968 ˇbbX
10969 ˇb
10970 cccc"
10971 },
10972 false,
10973 );
10974 assert_eq!(editor.text(cx), expected_text);
10975 assert_eq!(editor.selections.ranges(cx), expected_selections);
10976 });
10977}
10978
10979#[gpui::test]
10980fn test_refresh_selections(cx: &mut TestAppContext) {
10981 init_test(cx, |_| {});
10982
10983 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10984 let mut excerpt1_id = None;
10985 let multibuffer = cx.new(|cx| {
10986 let mut multibuffer = MultiBuffer::new(ReadWrite);
10987 excerpt1_id = multibuffer
10988 .push_excerpts(
10989 buffer.clone(),
10990 [
10991 ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
10992 ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
10993 ],
10994 cx,
10995 )
10996 .into_iter()
10997 .next();
10998 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
10999 multibuffer
11000 });
11001
11002 let editor = cx.add_window(|window, cx| {
11003 let mut editor = build_editor(multibuffer.clone(), window, cx);
11004 let snapshot = editor.snapshot(window, cx);
11005 editor.change_selections(None, window, cx, |s| {
11006 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
11007 });
11008 editor.begin_selection(
11009 Point::new(2, 1).to_display_point(&snapshot),
11010 true,
11011 1,
11012 window,
11013 cx,
11014 );
11015 assert_eq!(
11016 editor.selections.ranges(cx),
11017 [
11018 Point::new(1, 3)..Point::new(1, 3),
11019 Point::new(2, 1)..Point::new(2, 1),
11020 ]
11021 );
11022 editor
11023 });
11024
11025 // Refreshing selections is a no-op when excerpts haven't changed.
11026 _ = editor.update(cx, |editor, window, cx| {
11027 editor.change_selections(None, window, cx, |s| s.refresh());
11028 assert_eq!(
11029 editor.selections.ranges(cx),
11030 [
11031 Point::new(1, 3)..Point::new(1, 3),
11032 Point::new(2, 1)..Point::new(2, 1),
11033 ]
11034 );
11035 });
11036
11037 multibuffer.update(cx, |multibuffer, cx| {
11038 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
11039 });
11040 _ = editor.update(cx, |editor, window, cx| {
11041 // Removing an excerpt causes the first selection to become degenerate.
11042 assert_eq!(
11043 editor.selections.ranges(cx),
11044 [
11045 Point::new(0, 0)..Point::new(0, 0),
11046 Point::new(0, 1)..Point::new(0, 1)
11047 ]
11048 );
11049
11050 // Refreshing selections will relocate the first selection to the original buffer
11051 // location.
11052 editor.change_selections(None, window, cx, |s| s.refresh());
11053 assert_eq!(
11054 editor.selections.ranges(cx),
11055 [
11056 Point::new(0, 1)..Point::new(0, 1),
11057 Point::new(0, 3)..Point::new(0, 3)
11058 ]
11059 );
11060 assert!(editor.selections.pending_anchor().is_some());
11061 });
11062}
11063
11064#[gpui::test]
11065fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
11066 init_test(cx, |_| {});
11067
11068 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
11069 let mut excerpt1_id = None;
11070 let multibuffer = cx.new(|cx| {
11071 let mut multibuffer = MultiBuffer::new(ReadWrite);
11072 excerpt1_id = multibuffer
11073 .push_excerpts(
11074 buffer.clone(),
11075 [
11076 ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
11077 ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
11078 ],
11079 cx,
11080 )
11081 .into_iter()
11082 .next();
11083 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
11084 multibuffer
11085 });
11086
11087 let editor = cx.add_window(|window, cx| {
11088 let mut editor = build_editor(multibuffer.clone(), window, cx);
11089 let snapshot = editor.snapshot(window, cx);
11090 editor.begin_selection(
11091 Point::new(1, 3).to_display_point(&snapshot),
11092 false,
11093 1,
11094 window,
11095 cx,
11096 );
11097 assert_eq!(
11098 editor.selections.ranges(cx),
11099 [Point::new(1, 3)..Point::new(1, 3)]
11100 );
11101 editor
11102 });
11103
11104 multibuffer.update(cx, |multibuffer, cx| {
11105 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
11106 });
11107 _ = editor.update(cx, |editor, window, cx| {
11108 assert_eq!(
11109 editor.selections.ranges(cx),
11110 [Point::new(0, 0)..Point::new(0, 0)]
11111 );
11112
11113 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
11114 editor.change_selections(None, window, cx, |s| s.refresh());
11115 assert_eq!(
11116 editor.selections.ranges(cx),
11117 [Point::new(0, 3)..Point::new(0, 3)]
11118 );
11119 assert!(editor.selections.pending_anchor().is_some());
11120 });
11121}
11122
11123#[gpui::test]
11124async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
11125 init_test(cx, |_| {});
11126
11127 let language = Arc::new(
11128 Language::new(
11129 LanguageConfig {
11130 brackets: BracketPairConfig {
11131 pairs: vec![
11132 BracketPair {
11133 start: "{".to_string(),
11134 end: "}".to_string(),
11135 close: true,
11136 surround: true,
11137 newline: true,
11138 },
11139 BracketPair {
11140 start: "/* ".to_string(),
11141 end: " */".to_string(),
11142 close: true,
11143 surround: true,
11144 newline: true,
11145 },
11146 ],
11147 ..Default::default()
11148 },
11149 ..Default::default()
11150 },
11151 Some(tree_sitter_rust::LANGUAGE.into()),
11152 )
11153 .with_indents_query("")
11154 .unwrap(),
11155 );
11156
11157 let text = concat!(
11158 "{ }\n", //
11159 " x\n", //
11160 " /* */\n", //
11161 "x\n", //
11162 "{{} }\n", //
11163 );
11164
11165 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
11166 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11167 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11168 editor
11169 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11170 .await;
11171
11172 editor.update_in(cx, |editor, window, cx| {
11173 editor.change_selections(None, window, cx, |s| {
11174 s.select_display_ranges([
11175 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
11176 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
11177 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
11178 ])
11179 });
11180 editor.newline(&Newline, window, cx);
11181
11182 assert_eq!(
11183 editor.buffer().read(cx).read(cx).text(),
11184 concat!(
11185 "{ \n", // Suppress rustfmt
11186 "\n", //
11187 "}\n", //
11188 " x\n", //
11189 " /* \n", //
11190 " \n", //
11191 " */\n", //
11192 "x\n", //
11193 "{{} \n", //
11194 "}\n", //
11195 )
11196 );
11197 });
11198}
11199
11200#[gpui::test]
11201fn test_highlighted_ranges(cx: &mut TestAppContext) {
11202 init_test(cx, |_| {});
11203
11204 let editor = cx.add_window(|window, cx| {
11205 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
11206 build_editor(buffer.clone(), window, cx)
11207 });
11208
11209 _ = editor.update(cx, |editor, window, cx| {
11210 struct Type1;
11211 struct Type2;
11212
11213 let buffer = editor.buffer.read(cx).snapshot(cx);
11214
11215 let anchor_range =
11216 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
11217
11218 editor.highlight_background::<Type1>(
11219 &[
11220 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
11221 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
11222 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
11223 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
11224 ],
11225 |_| Hsla::red(),
11226 cx,
11227 );
11228 editor.highlight_background::<Type2>(
11229 &[
11230 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
11231 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
11232 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
11233 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
11234 ],
11235 |_| Hsla::green(),
11236 cx,
11237 );
11238
11239 let snapshot = editor.snapshot(window, cx);
11240 let mut highlighted_ranges = editor.background_highlights_in_range(
11241 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
11242 &snapshot,
11243 cx.theme().colors(),
11244 );
11245 // Enforce a consistent ordering based on color without relying on the ordering of the
11246 // highlight's `TypeId` which is non-executor.
11247 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
11248 assert_eq!(
11249 highlighted_ranges,
11250 &[
11251 (
11252 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
11253 Hsla::red(),
11254 ),
11255 (
11256 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
11257 Hsla::red(),
11258 ),
11259 (
11260 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
11261 Hsla::green(),
11262 ),
11263 (
11264 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
11265 Hsla::green(),
11266 ),
11267 ]
11268 );
11269 assert_eq!(
11270 editor.background_highlights_in_range(
11271 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
11272 &snapshot,
11273 cx.theme().colors(),
11274 ),
11275 &[(
11276 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
11277 Hsla::red(),
11278 )]
11279 );
11280 });
11281}
11282
11283#[gpui::test]
11284async fn test_following(cx: &mut TestAppContext) {
11285 init_test(cx, |_| {});
11286
11287 let fs = FakeFs::new(cx.executor());
11288 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
11289
11290 let buffer = project.update(cx, |project, cx| {
11291 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
11292 cx.new(|cx| MultiBuffer::singleton(buffer, cx))
11293 });
11294 let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
11295 let follower = cx.update(|cx| {
11296 cx.open_window(
11297 WindowOptions {
11298 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
11299 gpui::Point::new(px(0.), px(0.)),
11300 gpui::Point::new(px(10.), px(80.)),
11301 ))),
11302 ..Default::default()
11303 },
11304 |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
11305 )
11306 .unwrap()
11307 });
11308
11309 let is_still_following = Rc::new(RefCell::new(true));
11310 let follower_edit_event_count = Rc::new(RefCell::new(0));
11311 let pending_update = Rc::new(RefCell::new(None));
11312 let leader_entity = leader.root(cx).unwrap();
11313 let follower_entity = follower.root(cx).unwrap();
11314 _ = follower.update(cx, {
11315 let update = pending_update.clone();
11316 let is_still_following = is_still_following.clone();
11317 let follower_edit_event_count = follower_edit_event_count.clone();
11318 |_, window, cx| {
11319 cx.subscribe_in(
11320 &leader_entity,
11321 window,
11322 move |_, leader, event, window, cx| {
11323 leader.read(cx).add_event_to_update_proto(
11324 event,
11325 &mut update.borrow_mut(),
11326 window,
11327 cx,
11328 );
11329 },
11330 )
11331 .detach();
11332
11333 cx.subscribe_in(
11334 &follower_entity,
11335 window,
11336 move |_, _, event: &EditorEvent, _window, _cx| {
11337 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
11338 *is_still_following.borrow_mut() = false;
11339 }
11340
11341 if let EditorEvent::BufferEdited = event {
11342 *follower_edit_event_count.borrow_mut() += 1;
11343 }
11344 },
11345 )
11346 .detach();
11347 }
11348 });
11349
11350 // Update the selections only
11351 _ = leader.update(cx, |leader, window, cx| {
11352 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
11353 });
11354 follower
11355 .update(cx, |follower, window, cx| {
11356 follower.apply_update_proto(
11357 &project,
11358 pending_update.borrow_mut().take().unwrap(),
11359 window,
11360 cx,
11361 )
11362 })
11363 .unwrap()
11364 .await
11365 .unwrap();
11366 _ = follower.update(cx, |follower, _, cx| {
11367 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
11368 });
11369 assert!(*is_still_following.borrow());
11370 assert_eq!(*follower_edit_event_count.borrow(), 0);
11371
11372 // Update the scroll position only
11373 _ = leader.update(cx, |leader, window, cx| {
11374 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
11375 });
11376 follower
11377 .update(cx, |follower, window, cx| {
11378 follower.apply_update_proto(
11379 &project,
11380 pending_update.borrow_mut().take().unwrap(),
11381 window,
11382 cx,
11383 )
11384 })
11385 .unwrap()
11386 .await
11387 .unwrap();
11388 assert_eq!(
11389 follower
11390 .update(cx, |follower, _, cx| follower.scroll_position(cx))
11391 .unwrap(),
11392 gpui::Point::new(1.5, 3.5)
11393 );
11394 assert!(*is_still_following.borrow());
11395 assert_eq!(*follower_edit_event_count.borrow(), 0);
11396
11397 // Update the selections and scroll position. The follower's scroll position is updated
11398 // via autoscroll, not via the leader's exact scroll position.
11399 _ = leader.update(cx, |leader, window, cx| {
11400 leader.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
11401 leader.request_autoscroll(Autoscroll::newest(), cx);
11402 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
11403 });
11404 follower
11405 .update(cx, |follower, window, cx| {
11406 follower.apply_update_proto(
11407 &project,
11408 pending_update.borrow_mut().take().unwrap(),
11409 window,
11410 cx,
11411 )
11412 })
11413 .unwrap()
11414 .await
11415 .unwrap();
11416 _ = follower.update(cx, |follower, _, cx| {
11417 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
11418 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
11419 });
11420 assert!(*is_still_following.borrow());
11421
11422 // Creating a pending selection that precedes another selection
11423 _ = leader.update(cx, |leader, window, cx| {
11424 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
11425 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
11426 });
11427 follower
11428 .update(cx, |follower, window, cx| {
11429 follower.apply_update_proto(
11430 &project,
11431 pending_update.borrow_mut().take().unwrap(),
11432 window,
11433 cx,
11434 )
11435 })
11436 .unwrap()
11437 .await
11438 .unwrap();
11439 _ = follower.update(cx, |follower, _, cx| {
11440 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
11441 });
11442 assert!(*is_still_following.borrow());
11443
11444 // Extend the pending selection so that it surrounds another selection
11445 _ = leader.update(cx, |leader, window, cx| {
11446 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
11447 });
11448 follower
11449 .update(cx, |follower, window, cx| {
11450 follower.apply_update_proto(
11451 &project,
11452 pending_update.borrow_mut().take().unwrap(),
11453 window,
11454 cx,
11455 )
11456 })
11457 .unwrap()
11458 .await
11459 .unwrap();
11460 _ = follower.update(cx, |follower, _, cx| {
11461 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
11462 });
11463
11464 // Scrolling locally breaks the follow
11465 _ = follower.update(cx, |follower, window, cx| {
11466 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
11467 follower.set_scroll_anchor(
11468 ScrollAnchor {
11469 anchor: top_anchor,
11470 offset: gpui::Point::new(0.0, 0.5),
11471 },
11472 window,
11473 cx,
11474 );
11475 });
11476 assert!(!(*is_still_following.borrow()));
11477}
11478
11479#[gpui::test]
11480async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
11481 init_test(cx, |_| {});
11482
11483 let fs = FakeFs::new(cx.executor());
11484 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
11485 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11486 let pane = workspace
11487 .update(cx, |workspace, _, _| workspace.active_pane().clone())
11488 .unwrap();
11489
11490 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11491
11492 let leader = pane.update_in(cx, |_, window, cx| {
11493 let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
11494 cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
11495 });
11496
11497 // Start following the editor when it has no excerpts.
11498 let mut state_message =
11499 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
11500 let workspace_entity = workspace.root(cx).unwrap();
11501 let follower_1 = cx
11502 .update_window(*workspace.deref(), |_, window, cx| {
11503 Editor::from_state_proto(
11504 workspace_entity,
11505 ViewId {
11506 creator: Default::default(),
11507 id: 0,
11508 },
11509 &mut state_message,
11510 window,
11511 cx,
11512 )
11513 })
11514 .unwrap()
11515 .unwrap()
11516 .await
11517 .unwrap();
11518
11519 let update_message = Rc::new(RefCell::new(None));
11520 follower_1.update_in(cx, {
11521 let update = update_message.clone();
11522 |_, window, cx| {
11523 cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
11524 leader.read(cx).add_event_to_update_proto(
11525 event,
11526 &mut update.borrow_mut(),
11527 window,
11528 cx,
11529 );
11530 })
11531 .detach();
11532 }
11533 });
11534
11535 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
11536 (
11537 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
11538 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
11539 )
11540 });
11541
11542 // Insert some excerpts.
11543 leader.update(cx, |leader, cx| {
11544 leader.buffer.update(cx, |multibuffer, cx| {
11545 let excerpt_ids = multibuffer.push_excerpts(
11546 buffer_1.clone(),
11547 [
11548 ExcerptRange::new(1..6),
11549 ExcerptRange::new(12..15),
11550 ExcerptRange::new(0..3),
11551 ],
11552 cx,
11553 );
11554 multibuffer.insert_excerpts_after(
11555 excerpt_ids[0],
11556 buffer_2.clone(),
11557 [ExcerptRange::new(8..12), ExcerptRange::new(0..6)],
11558 cx,
11559 );
11560 });
11561 });
11562
11563 // Apply the update of adding the excerpts.
11564 follower_1
11565 .update_in(cx, |follower, window, cx| {
11566 follower.apply_update_proto(
11567 &project,
11568 update_message.borrow().clone().unwrap(),
11569 window,
11570 cx,
11571 )
11572 })
11573 .await
11574 .unwrap();
11575 assert_eq!(
11576 follower_1.update(cx, |editor, cx| editor.text(cx)),
11577 leader.update(cx, |editor, cx| editor.text(cx))
11578 );
11579 update_message.borrow_mut().take();
11580
11581 // Start following separately after it already has excerpts.
11582 let mut state_message =
11583 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
11584 let workspace_entity = workspace.root(cx).unwrap();
11585 let follower_2 = cx
11586 .update_window(*workspace.deref(), |_, window, cx| {
11587 Editor::from_state_proto(
11588 workspace_entity,
11589 ViewId {
11590 creator: Default::default(),
11591 id: 0,
11592 },
11593 &mut state_message,
11594 window,
11595 cx,
11596 )
11597 })
11598 .unwrap()
11599 .unwrap()
11600 .await
11601 .unwrap();
11602 assert_eq!(
11603 follower_2.update(cx, |editor, cx| editor.text(cx)),
11604 leader.update(cx, |editor, cx| editor.text(cx))
11605 );
11606
11607 // Remove some excerpts.
11608 leader.update(cx, |leader, cx| {
11609 leader.buffer.update(cx, |multibuffer, cx| {
11610 let excerpt_ids = multibuffer.excerpt_ids();
11611 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
11612 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
11613 });
11614 });
11615
11616 // Apply the update of removing the excerpts.
11617 follower_1
11618 .update_in(cx, |follower, window, cx| {
11619 follower.apply_update_proto(
11620 &project,
11621 update_message.borrow().clone().unwrap(),
11622 window,
11623 cx,
11624 )
11625 })
11626 .await
11627 .unwrap();
11628 follower_2
11629 .update_in(cx, |follower, window, cx| {
11630 follower.apply_update_proto(
11631 &project,
11632 update_message.borrow().clone().unwrap(),
11633 window,
11634 cx,
11635 )
11636 })
11637 .await
11638 .unwrap();
11639 update_message.borrow_mut().take();
11640 assert_eq!(
11641 follower_1.update(cx, |editor, cx| editor.text(cx)),
11642 leader.update(cx, |editor, cx| editor.text(cx))
11643 );
11644}
11645
11646#[gpui::test]
11647async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
11648 init_test(cx, |_| {});
11649
11650 let mut cx = EditorTestContext::new(cx).await;
11651 let lsp_store =
11652 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11653
11654 cx.set_state(indoc! {"
11655 ˇfn func(abc def: i32) -> u32 {
11656 }
11657 "});
11658
11659 cx.update(|_, cx| {
11660 lsp_store.update(cx, |lsp_store, cx| {
11661 lsp_store
11662 .update_diagnostics(
11663 LanguageServerId(0),
11664 lsp::PublishDiagnosticsParams {
11665 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11666 version: None,
11667 diagnostics: vec![
11668 lsp::Diagnostic {
11669 range: lsp::Range::new(
11670 lsp::Position::new(0, 11),
11671 lsp::Position::new(0, 12),
11672 ),
11673 severity: Some(lsp::DiagnosticSeverity::ERROR),
11674 ..Default::default()
11675 },
11676 lsp::Diagnostic {
11677 range: lsp::Range::new(
11678 lsp::Position::new(0, 12),
11679 lsp::Position::new(0, 15),
11680 ),
11681 severity: Some(lsp::DiagnosticSeverity::ERROR),
11682 ..Default::default()
11683 },
11684 lsp::Diagnostic {
11685 range: lsp::Range::new(
11686 lsp::Position::new(0, 25),
11687 lsp::Position::new(0, 28),
11688 ),
11689 severity: Some(lsp::DiagnosticSeverity::ERROR),
11690 ..Default::default()
11691 },
11692 ],
11693 },
11694 &[],
11695 cx,
11696 )
11697 .unwrap()
11698 });
11699 });
11700
11701 executor.run_until_parked();
11702
11703 cx.update_editor(|editor, window, cx| {
11704 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11705 });
11706
11707 cx.assert_editor_state(indoc! {"
11708 fn func(abc def: i32) -> ˇu32 {
11709 }
11710 "});
11711
11712 cx.update_editor(|editor, window, cx| {
11713 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11714 });
11715
11716 cx.assert_editor_state(indoc! {"
11717 fn func(abc ˇdef: i32) -> u32 {
11718 }
11719 "});
11720
11721 cx.update_editor(|editor, window, cx| {
11722 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11723 });
11724
11725 cx.assert_editor_state(indoc! {"
11726 fn func(abcˇ def: i32) -> u32 {
11727 }
11728 "});
11729
11730 cx.update_editor(|editor, window, cx| {
11731 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11732 });
11733
11734 cx.assert_editor_state(indoc! {"
11735 fn func(abc def: i32) -> ˇu32 {
11736 }
11737 "});
11738}
11739
11740#[gpui::test]
11741async fn cycle_through_same_place_diagnostics(
11742 executor: BackgroundExecutor,
11743 cx: &mut TestAppContext,
11744) {
11745 init_test(cx, |_| {});
11746
11747 let mut cx = EditorTestContext::new(cx).await;
11748 let lsp_store =
11749 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11750
11751 cx.set_state(indoc! {"
11752 ˇfn func(abc def: i32) -> u32 {
11753 }
11754 "});
11755
11756 cx.update(|_, cx| {
11757 lsp_store.update(cx, |lsp_store, cx| {
11758 lsp_store
11759 .update_diagnostics(
11760 LanguageServerId(0),
11761 lsp::PublishDiagnosticsParams {
11762 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11763 version: None,
11764 diagnostics: vec![
11765 lsp::Diagnostic {
11766 range: lsp::Range::new(
11767 lsp::Position::new(0, 11),
11768 lsp::Position::new(0, 12),
11769 ),
11770 severity: Some(lsp::DiagnosticSeverity::ERROR),
11771 ..Default::default()
11772 },
11773 lsp::Diagnostic {
11774 range: lsp::Range::new(
11775 lsp::Position::new(0, 12),
11776 lsp::Position::new(0, 15),
11777 ),
11778 severity: Some(lsp::DiagnosticSeverity::ERROR),
11779 ..Default::default()
11780 },
11781 lsp::Diagnostic {
11782 range: lsp::Range::new(
11783 lsp::Position::new(0, 12),
11784 lsp::Position::new(0, 15),
11785 ),
11786 severity: Some(lsp::DiagnosticSeverity::ERROR),
11787 ..Default::default()
11788 },
11789 lsp::Diagnostic {
11790 range: lsp::Range::new(
11791 lsp::Position::new(0, 25),
11792 lsp::Position::new(0, 28),
11793 ),
11794 severity: Some(lsp::DiagnosticSeverity::ERROR),
11795 ..Default::default()
11796 },
11797 ],
11798 },
11799 &[],
11800 cx,
11801 )
11802 .unwrap()
11803 });
11804 });
11805 executor.run_until_parked();
11806
11807 //// Backward
11808
11809 // Fourth diagnostic
11810 cx.update_editor(|editor, window, cx| {
11811 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11812 });
11813 cx.assert_editor_state(indoc! {"
11814 fn func(abc def: i32) -> ˇu32 {
11815 }
11816 "});
11817
11818 // Third diagnostic
11819 cx.update_editor(|editor, window, cx| {
11820 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11821 });
11822 cx.assert_editor_state(indoc! {"
11823 fn func(abc ˇdef: i32) -> u32 {
11824 }
11825 "});
11826
11827 // Second diagnostic, same place
11828 cx.update_editor(|editor, window, cx| {
11829 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11830 });
11831 cx.assert_editor_state(indoc! {"
11832 fn func(abc ˇdef: i32) -> u32 {
11833 }
11834 "});
11835
11836 // First diagnostic
11837 cx.update_editor(|editor, window, cx| {
11838 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11839 });
11840 cx.assert_editor_state(indoc! {"
11841 fn func(abcˇ def: i32) -> u32 {
11842 }
11843 "});
11844
11845 // Wrapped over, fourth diagnostic
11846 cx.update_editor(|editor, window, cx| {
11847 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11848 });
11849 cx.assert_editor_state(indoc! {"
11850 fn func(abc def: i32) -> ˇu32 {
11851 }
11852 "});
11853
11854 cx.update_editor(|editor, window, cx| {
11855 editor.move_to_beginning(&MoveToBeginning, window, cx);
11856 });
11857 cx.assert_editor_state(indoc! {"
11858 ˇfn func(abc def: i32) -> u32 {
11859 }
11860 "});
11861
11862 //// Forward
11863
11864 // First diagnostic
11865 cx.update_editor(|editor, window, cx| {
11866 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11867 });
11868 cx.assert_editor_state(indoc! {"
11869 fn func(abcˇ def: i32) -> u32 {
11870 }
11871 "});
11872
11873 // Second diagnostic
11874 cx.update_editor(|editor, window, cx| {
11875 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11876 });
11877 cx.assert_editor_state(indoc! {"
11878 fn func(abc ˇdef: i32) -> u32 {
11879 }
11880 "});
11881
11882 // Third diagnostic, same place
11883 cx.update_editor(|editor, window, cx| {
11884 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11885 });
11886 cx.assert_editor_state(indoc! {"
11887 fn func(abc ˇdef: i32) -> u32 {
11888 }
11889 "});
11890
11891 // Fourth diagnostic
11892 cx.update_editor(|editor, window, cx| {
11893 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11894 });
11895 cx.assert_editor_state(indoc! {"
11896 fn func(abc def: i32) -> ˇu32 {
11897 }
11898 "});
11899
11900 // Wrapped around, first diagnostic
11901 cx.update_editor(|editor, window, cx| {
11902 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11903 });
11904 cx.assert_editor_state(indoc! {"
11905 fn func(abcˇ def: i32) -> u32 {
11906 }
11907 "});
11908}
11909
11910#[gpui::test]
11911async fn active_diagnostics_dismiss_after_invalidation(
11912 executor: BackgroundExecutor,
11913 cx: &mut TestAppContext,
11914) {
11915 init_test(cx, |_| {});
11916
11917 let mut cx = EditorTestContext::new(cx).await;
11918 let lsp_store =
11919 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11920
11921 cx.set_state(indoc! {"
11922 ˇfn func(abc def: i32) -> u32 {
11923 }
11924 "});
11925
11926 let message = "Something's wrong!";
11927 cx.update(|_, cx| {
11928 lsp_store.update(cx, |lsp_store, cx| {
11929 lsp_store
11930 .update_diagnostics(
11931 LanguageServerId(0),
11932 lsp::PublishDiagnosticsParams {
11933 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11934 version: None,
11935 diagnostics: vec![lsp::Diagnostic {
11936 range: lsp::Range::new(
11937 lsp::Position::new(0, 11),
11938 lsp::Position::new(0, 12),
11939 ),
11940 severity: Some(lsp::DiagnosticSeverity::ERROR),
11941 message: message.to_string(),
11942 ..Default::default()
11943 }],
11944 },
11945 &[],
11946 cx,
11947 )
11948 .unwrap()
11949 });
11950 });
11951 executor.run_until_parked();
11952
11953 cx.update_editor(|editor, window, cx| {
11954 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11955 assert_eq!(
11956 editor
11957 .active_diagnostics
11958 .as_ref()
11959 .map(|diagnostics_group| diagnostics_group.primary_message.as_str()),
11960 Some(message),
11961 "Should have a diagnostics group activated"
11962 );
11963 });
11964 cx.assert_editor_state(indoc! {"
11965 fn func(abcˇ def: i32) -> u32 {
11966 }
11967 "});
11968
11969 cx.update(|_, cx| {
11970 lsp_store.update(cx, |lsp_store, cx| {
11971 lsp_store
11972 .update_diagnostics(
11973 LanguageServerId(0),
11974 lsp::PublishDiagnosticsParams {
11975 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11976 version: None,
11977 diagnostics: Vec::new(),
11978 },
11979 &[],
11980 cx,
11981 )
11982 .unwrap()
11983 });
11984 });
11985 executor.run_until_parked();
11986 cx.update_editor(|editor, _, _| {
11987 assert_eq!(
11988 editor.active_diagnostics, None,
11989 "After no diagnostics set to the editor, no diagnostics should be active"
11990 );
11991 });
11992 cx.assert_editor_state(indoc! {"
11993 fn func(abcˇ def: i32) -> u32 {
11994 }
11995 "});
11996
11997 cx.update_editor(|editor, window, cx| {
11998 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11999 assert_eq!(
12000 editor.active_diagnostics, None,
12001 "Should be no diagnostics to go to and activate"
12002 );
12003 });
12004 cx.assert_editor_state(indoc! {"
12005 fn func(abcˇ def: i32) -> u32 {
12006 }
12007 "});
12008}
12009
12010#[gpui::test]
12011async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
12012 init_test(cx, |_| {});
12013
12014 let mut cx = EditorTestContext::new(cx).await;
12015
12016 cx.set_state(indoc! {"
12017 fn func(abˇc def: i32) -> u32 {
12018 }
12019 "});
12020 let lsp_store =
12021 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
12022
12023 cx.update(|_, cx| {
12024 lsp_store.update(cx, |lsp_store, cx| {
12025 lsp_store.update_diagnostics(
12026 LanguageServerId(0),
12027 lsp::PublishDiagnosticsParams {
12028 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
12029 version: None,
12030 diagnostics: vec![lsp::Diagnostic {
12031 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
12032 severity: Some(lsp::DiagnosticSeverity::ERROR),
12033 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
12034 ..Default::default()
12035 }],
12036 },
12037 &[],
12038 cx,
12039 )
12040 })
12041 }).unwrap();
12042 cx.run_until_parked();
12043 cx.update_editor(|editor, window, cx| {
12044 hover_popover::hover(editor, &Default::default(), window, cx)
12045 });
12046 cx.run_until_parked();
12047 cx.update_editor(|editor, _, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
12048}
12049
12050#[gpui::test]
12051async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
12052 init_test(cx, |_| {});
12053
12054 let mut cx = EditorTestContext::new(cx).await;
12055
12056 let diff_base = r#"
12057 use some::mod;
12058
12059 const A: u32 = 42;
12060
12061 fn main() {
12062 println!("hello");
12063
12064 println!("world");
12065 }
12066 "#
12067 .unindent();
12068
12069 // Edits are modified, removed, modified, added
12070 cx.set_state(
12071 &r#"
12072 use some::modified;
12073
12074 ˇ
12075 fn main() {
12076 println!("hello there");
12077
12078 println!("around the");
12079 println!("world");
12080 }
12081 "#
12082 .unindent(),
12083 );
12084
12085 cx.set_head_text(&diff_base);
12086 executor.run_until_parked();
12087
12088 cx.update_editor(|editor, window, cx| {
12089 //Wrap around the bottom of the buffer
12090 for _ in 0..3 {
12091 editor.go_to_next_hunk(&GoToHunk, window, cx);
12092 }
12093 });
12094
12095 cx.assert_editor_state(
12096 &r#"
12097 ˇuse some::modified;
12098
12099
12100 fn main() {
12101 println!("hello there");
12102
12103 println!("around the");
12104 println!("world");
12105 }
12106 "#
12107 .unindent(),
12108 );
12109
12110 cx.update_editor(|editor, window, cx| {
12111 //Wrap around the top of the buffer
12112 for _ in 0..2 {
12113 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
12114 }
12115 });
12116
12117 cx.assert_editor_state(
12118 &r#"
12119 use some::modified;
12120
12121
12122 fn main() {
12123 ˇ println!("hello there");
12124
12125 println!("around the");
12126 println!("world");
12127 }
12128 "#
12129 .unindent(),
12130 );
12131
12132 cx.update_editor(|editor, window, cx| {
12133 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
12134 });
12135
12136 cx.assert_editor_state(
12137 &r#"
12138 use some::modified;
12139
12140 ˇ
12141 fn main() {
12142 println!("hello there");
12143
12144 println!("around the");
12145 println!("world");
12146 }
12147 "#
12148 .unindent(),
12149 );
12150
12151 cx.update_editor(|editor, window, cx| {
12152 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
12153 });
12154
12155 cx.assert_editor_state(
12156 &r#"
12157 ˇuse some::modified;
12158
12159
12160 fn main() {
12161 println!("hello there");
12162
12163 println!("around the");
12164 println!("world");
12165 }
12166 "#
12167 .unindent(),
12168 );
12169
12170 cx.update_editor(|editor, window, cx| {
12171 for _ in 0..2 {
12172 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
12173 }
12174 });
12175
12176 cx.assert_editor_state(
12177 &r#"
12178 use some::modified;
12179
12180
12181 fn main() {
12182 ˇ println!("hello there");
12183
12184 println!("around the");
12185 println!("world");
12186 }
12187 "#
12188 .unindent(),
12189 );
12190
12191 cx.update_editor(|editor, window, cx| {
12192 editor.fold(&Fold, window, cx);
12193 });
12194
12195 cx.update_editor(|editor, window, cx| {
12196 editor.go_to_next_hunk(&GoToHunk, window, cx);
12197 });
12198
12199 cx.assert_editor_state(
12200 &r#"
12201 ˇuse some::modified;
12202
12203
12204 fn main() {
12205 println!("hello there");
12206
12207 println!("around the");
12208 println!("world");
12209 }
12210 "#
12211 .unindent(),
12212 );
12213}
12214
12215#[test]
12216fn test_split_words() {
12217 fn split(text: &str) -> Vec<&str> {
12218 split_words(text).collect()
12219 }
12220
12221 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
12222 assert_eq!(split("hello_world"), &["hello_", "world"]);
12223 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
12224 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
12225 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
12226 assert_eq!(split("helloworld"), &["helloworld"]);
12227
12228 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
12229}
12230
12231#[gpui::test]
12232async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
12233 init_test(cx, |_| {});
12234
12235 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
12236 let mut assert = |before, after| {
12237 let _state_context = cx.set_state(before);
12238 cx.run_until_parked();
12239 cx.update_editor(|editor, window, cx| {
12240 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
12241 });
12242 cx.run_until_parked();
12243 cx.assert_editor_state(after);
12244 };
12245
12246 // Outside bracket jumps to outside of matching bracket
12247 assert("console.logˇ(var);", "console.log(var)ˇ;");
12248 assert("console.log(var)ˇ;", "console.logˇ(var);");
12249
12250 // Inside bracket jumps to inside of matching bracket
12251 assert("console.log(ˇvar);", "console.log(varˇ);");
12252 assert("console.log(varˇ);", "console.log(ˇvar);");
12253
12254 // When outside a bracket and inside, favor jumping to the inside bracket
12255 assert(
12256 "console.log('foo', [1, 2, 3]ˇ);",
12257 "console.log(ˇ'foo', [1, 2, 3]);",
12258 );
12259 assert(
12260 "console.log(ˇ'foo', [1, 2, 3]);",
12261 "console.log('foo', [1, 2, 3]ˇ);",
12262 );
12263
12264 // Bias forward if two options are equally likely
12265 assert(
12266 "let result = curried_fun()ˇ();",
12267 "let result = curried_fun()()ˇ;",
12268 );
12269
12270 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
12271 assert(
12272 indoc! {"
12273 function test() {
12274 console.log('test')ˇ
12275 }"},
12276 indoc! {"
12277 function test() {
12278 console.logˇ('test')
12279 }"},
12280 );
12281}
12282
12283#[gpui::test]
12284async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
12285 init_test(cx, |_| {});
12286
12287 let fs = FakeFs::new(cx.executor());
12288 fs.insert_tree(
12289 path!("/a"),
12290 json!({
12291 "main.rs": "fn main() { let a = 5; }",
12292 "other.rs": "// Test file",
12293 }),
12294 )
12295 .await;
12296 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12297
12298 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12299 language_registry.add(Arc::new(Language::new(
12300 LanguageConfig {
12301 name: "Rust".into(),
12302 matcher: LanguageMatcher {
12303 path_suffixes: vec!["rs".to_string()],
12304 ..Default::default()
12305 },
12306 brackets: BracketPairConfig {
12307 pairs: vec![BracketPair {
12308 start: "{".to_string(),
12309 end: "}".to_string(),
12310 close: true,
12311 surround: true,
12312 newline: true,
12313 }],
12314 disabled_scopes_by_bracket_ix: Vec::new(),
12315 },
12316 ..Default::default()
12317 },
12318 Some(tree_sitter_rust::LANGUAGE.into()),
12319 )));
12320 let mut fake_servers = language_registry.register_fake_lsp(
12321 "Rust",
12322 FakeLspAdapter {
12323 capabilities: lsp::ServerCapabilities {
12324 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
12325 first_trigger_character: "{".to_string(),
12326 more_trigger_character: None,
12327 }),
12328 ..Default::default()
12329 },
12330 ..Default::default()
12331 },
12332 );
12333
12334 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12335
12336 let cx = &mut VisualTestContext::from_window(*workspace, cx);
12337
12338 let worktree_id = workspace
12339 .update(cx, |workspace, _, cx| {
12340 workspace.project().update(cx, |project, cx| {
12341 project.worktrees(cx).next().unwrap().read(cx).id()
12342 })
12343 })
12344 .unwrap();
12345
12346 let buffer = project
12347 .update(cx, |project, cx| {
12348 project.open_local_buffer(path!("/a/main.rs"), cx)
12349 })
12350 .await
12351 .unwrap();
12352 let editor_handle = workspace
12353 .update(cx, |workspace, window, cx| {
12354 workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
12355 })
12356 .unwrap()
12357 .await
12358 .unwrap()
12359 .downcast::<Editor>()
12360 .unwrap();
12361
12362 cx.executor().start_waiting();
12363 let fake_server = fake_servers.next().await.unwrap();
12364
12365 fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
12366 |params, _| async move {
12367 assert_eq!(
12368 params.text_document_position.text_document.uri,
12369 lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
12370 );
12371 assert_eq!(
12372 params.text_document_position.position,
12373 lsp::Position::new(0, 21),
12374 );
12375
12376 Ok(Some(vec![lsp::TextEdit {
12377 new_text: "]".to_string(),
12378 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12379 }]))
12380 },
12381 );
12382
12383 editor_handle.update_in(cx, |editor, window, cx| {
12384 window.focus(&editor.focus_handle(cx));
12385 editor.change_selections(None, window, cx, |s| {
12386 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
12387 });
12388 editor.handle_input("{", window, cx);
12389 });
12390
12391 cx.executor().run_until_parked();
12392
12393 buffer.update(cx, |buffer, _| {
12394 assert_eq!(
12395 buffer.text(),
12396 "fn main() { let a = {5}; }",
12397 "No extra braces from on type formatting should appear in the buffer"
12398 )
12399 });
12400}
12401
12402#[gpui::test]
12403async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
12404 init_test(cx, |_| {});
12405
12406 let fs = FakeFs::new(cx.executor());
12407 fs.insert_tree(
12408 path!("/a"),
12409 json!({
12410 "main.rs": "fn main() { let a = 5; }",
12411 "other.rs": "// Test file",
12412 }),
12413 )
12414 .await;
12415
12416 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12417
12418 let server_restarts = Arc::new(AtomicUsize::new(0));
12419 let closure_restarts = Arc::clone(&server_restarts);
12420 let language_server_name = "test language server";
12421 let language_name: LanguageName = "Rust".into();
12422
12423 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12424 language_registry.add(Arc::new(Language::new(
12425 LanguageConfig {
12426 name: language_name.clone(),
12427 matcher: LanguageMatcher {
12428 path_suffixes: vec!["rs".to_string()],
12429 ..Default::default()
12430 },
12431 ..Default::default()
12432 },
12433 Some(tree_sitter_rust::LANGUAGE.into()),
12434 )));
12435 let mut fake_servers = language_registry.register_fake_lsp(
12436 "Rust",
12437 FakeLspAdapter {
12438 name: language_server_name,
12439 initialization_options: Some(json!({
12440 "testOptionValue": true
12441 })),
12442 initializer: Some(Box::new(move |fake_server| {
12443 let task_restarts = Arc::clone(&closure_restarts);
12444 fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
12445 task_restarts.fetch_add(1, atomic::Ordering::Release);
12446 futures::future::ready(Ok(()))
12447 });
12448 })),
12449 ..Default::default()
12450 },
12451 );
12452
12453 let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12454 let _buffer = project
12455 .update(cx, |project, cx| {
12456 project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
12457 })
12458 .await
12459 .unwrap();
12460 let _fake_server = fake_servers.next().await.unwrap();
12461 update_test_language_settings(cx, |language_settings| {
12462 language_settings.languages.insert(
12463 language_name.clone(),
12464 LanguageSettingsContent {
12465 tab_size: NonZeroU32::new(8),
12466 ..Default::default()
12467 },
12468 );
12469 });
12470 cx.executor().run_until_parked();
12471 assert_eq!(
12472 server_restarts.load(atomic::Ordering::Acquire),
12473 0,
12474 "Should not restart LSP server on an unrelated change"
12475 );
12476
12477 update_test_project_settings(cx, |project_settings| {
12478 project_settings.lsp.insert(
12479 "Some other server name".into(),
12480 LspSettings {
12481 binary: None,
12482 settings: None,
12483 initialization_options: Some(json!({
12484 "some other init value": false
12485 })),
12486 },
12487 );
12488 });
12489 cx.executor().run_until_parked();
12490 assert_eq!(
12491 server_restarts.load(atomic::Ordering::Acquire),
12492 0,
12493 "Should not restart LSP server on an unrelated LSP settings change"
12494 );
12495
12496 update_test_project_settings(cx, |project_settings| {
12497 project_settings.lsp.insert(
12498 language_server_name.into(),
12499 LspSettings {
12500 binary: None,
12501 settings: None,
12502 initialization_options: Some(json!({
12503 "anotherInitValue": false
12504 })),
12505 },
12506 );
12507 });
12508 cx.executor().run_until_parked();
12509 assert_eq!(
12510 server_restarts.load(atomic::Ordering::Acquire),
12511 1,
12512 "Should restart LSP server on a related LSP settings change"
12513 );
12514
12515 update_test_project_settings(cx, |project_settings| {
12516 project_settings.lsp.insert(
12517 language_server_name.into(),
12518 LspSettings {
12519 binary: None,
12520 settings: None,
12521 initialization_options: Some(json!({
12522 "anotherInitValue": false
12523 })),
12524 },
12525 );
12526 });
12527 cx.executor().run_until_parked();
12528 assert_eq!(
12529 server_restarts.load(atomic::Ordering::Acquire),
12530 1,
12531 "Should not restart LSP server on a related LSP settings change that is the same"
12532 );
12533
12534 update_test_project_settings(cx, |project_settings| {
12535 project_settings.lsp.insert(
12536 language_server_name.into(),
12537 LspSettings {
12538 binary: None,
12539 settings: None,
12540 initialization_options: None,
12541 },
12542 );
12543 });
12544 cx.executor().run_until_parked();
12545 assert_eq!(
12546 server_restarts.load(atomic::Ordering::Acquire),
12547 2,
12548 "Should restart LSP server on another related LSP settings change"
12549 );
12550}
12551
12552#[gpui::test]
12553async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
12554 init_test(cx, |_| {});
12555
12556 let mut cx = EditorLspTestContext::new_rust(
12557 lsp::ServerCapabilities {
12558 completion_provider: Some(lsp::CompletionOptions {
12559 trigger_characters: Some(vec![".".to_string()]),
12560 resolve_provider: Some(true),
12561 ..Default::default()
12562 }),
12563 ..Default::default()
12564 },
12565 cx,
12566 )
12567 .await;
12568
12569 cx.set_state("fn main() { let a = 2ˇ; }");
12570 cx.simulate_keystroke(".");
12571 let completion_item = lsp::CompletionItem {
12572 label: "some".into(),
12573 kind: Some(lsp::CompletionItemKind::SNIPPET),
12574 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
12575 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
12576 kind: lsp::MarkupKind::Markdown,
12577 value: "```rust\nSome(2)\n```".to_string(),
12578 })),
12579 deprecated: Some(false),
12580 sort_text: Some("fffffff2".to_string()),
12581 filter_text: Some("some".to_string()),
12582 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
12583 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12584 range: lsp::Range {
12585 start: lsp::Position {
12586 line: 0,
12587 character: 22,
12588 },
12589 end: lsp::Position {
12590 line: 0,
12591 character: 22,
12592 },
12593 },
12594 new_text: "Some(2)".to_string(),
12595 })),
12596 additional_text_edits: Some(vec![lsp::TextEdit {
12597 range: lsp::Range {
12598 start: lsp::Position {
12599 line: 0,
12600 character: 20,
12601 },
12602 end: lsp::Position {
12603 line: 0,
12604 character: 22,
12605 },
12606 },
12607 new_text: "".to_string(),
12608 }]),
12609 ..Default::default()
12610 };
12611
12612 let closure_completion_item = completion_item.clone();
12613 let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
12614 let task_completion_item = closure_completion_item.clone();
12615 async move {
12616 Ok(Some(lsp::CompletionResponse::Array(vec![
12617 task_completion_item,
12618 ])))
12619 }
12620 });
12621
12622 request.next().await;
12623
12624 cx.condition(|editor, _| editor.context_menu_visible())
12625 .await;
12626 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12627 editor
12628 .confirm_completion(&ConfirmCompletion::default(), window, cx)
12629 .unwrap()
12630 });
12631 cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
12632
12633 cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
12634 let task_completion_item = completion_item.clone();
12635 async move { Ok(task_completion_item) }
12636 })
12637 .next()
12638 .await
12639 .unwrap();
12640 apply_additional_edits.await.unwrap();
12641 cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
12642}
12643
12644#[gpui::test]
12645async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
12646 init_test(cx, |_| {});
12647
12648 let mut cx = EditorLspTestContext::new_rust(
12649 lsp::ServerCapabilities {
12650 completion_provider: Some(lsp::CompletionOptions {
12651 trigger_characters: Some(vec![".".to_string()]),
12652 resolve_provider: Some(true),
12653 ..Default::default()
12654 }),
12655 ..Default::default()
12656 },
12657 cx,
12658 )
12659 .await;
12660
12661 cx.set_state("fn main() { let a = 2ˇ; }");
12662 cx.simulate_keystroke(".");
12663
12664 let item1 = lsp::CompletionItem {
12665 label: "method id()".to_string(),
12666 filter_text: Some("id".to_string()),
12667 detail: None,
12668 documentation: None,
12669 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12670 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12671 new_text: ".id".to_string(),
12672 })),
12673 ..lsp::CompletionItem::default()
12674 };
12675
12676 let item2 = lsp::CompletionItem {
12677 label: "other".to_string(),
12678 filter_text: Some("other".to_string()),
12679 detail: None,
12680 documentation: None,
12681 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12682 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12683 new_text: ".other".to_string(),
12684 })),
12685 ..lsp::CompletionItem::default()
12686 };
12687
12688 let item1 = item1.clone();
12689 cx.set_request_handler::<lsp::request::Completion, _, _>({
12690 let item1 = item1.clone();
12691 move |_, _, _| {
12692 let item1 = item1.clone();
12693 let item2 = item2.clone();
12694 async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
12695 }
12696 })
12697 .next()
12698 .await;
12699
12700 cx.condition(|editor, _| editor.context_menu_visible())
12701 .await;
12702 cx.update_editor(|editor, _, _| {
12703 let context_menu = editor.context_menu.borrow_mut();
12704 let context_menu = context_menu
12705 .as_ref()
12706 .expect("Should have the context menu deployed");
12707 match context_menu {
12708 CodeContextMenu::Completions(completions_menu) => {
12709 let completions = completions_menu.completions.borrow_mut();
12710 assert_eq!(
12711 completions
12712 .iter()
12713 .map(|completion| &completion.label.text)
12714 .collect::<Vec<_>>(),
12715 vec!["method id()", "other"]
12716 )
12717 }
12718 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
12719 }
12720 });
12721
12722 cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
12723 let item1 = item1.clone();
12724 move |_, item_to_resolve, _| {
12725 let item1 = item1.clone();
12726 async move {
12727 if item1 == item_to_resolve {
12728 Ok(lsp::CompletionItem {
12729 label: "method id()".to_string(),
12730 filter_text: Some("id".to_string()),
12731 detail: Some("Now resolved!".to_string()),
12732 documentation: Some(lsp::Documentation::String("Docs".to_string())),
12733 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12734 range: lsp::Range::new(
12735 lsp::Position::new(0, 22),
12736 lsp::Position::new(0, 22),
12737 ),
12738 new_text: ".id".to_string(),
12739 })),
12740 ..lsp::CompletionItem::default()
12741 })
12742 } else {
12743 Ok(item_to_resolve)
12744 }
12745 }
12746 }
12747 })
12748 .next()
12749 .await
12750 .unwrap();
12751 cx.run_until_parked();
12752
12753 cx.update_editor(|editor, window, cx| {
12754 editor.context_menu_next(&Default::default(), window, cx);
12755 });
12756
12757 cx.update_editor(|editor, _, _| {
12758 let context_menu = editor.context_menu.borrow_mut();
12759 let context_menu = context_menu
12760 .as_ref()
12761 .expect("Should have the context menu deployed");
12762 match context_menu {
12763 CodeContextMenu::Completions(completions_menu) => {
12764 let completions = completions_menu.completions.borrow_mut();
12765 assert_eq!(
12766 completions
12767 .iter()
12768 .map(|completion| &completion.label.text)
12769 .collect::<Vec<_>>(),
12770 vec!["method id() Now resolved!", "other"],
12771 "Should update first completion label, but not second as the filter text did not match."
12772 );
12773 }
12774 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
12775 }
12776 });
12777}
12778
12779#[gpui::test]
12780async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
12781 init_test(cx, |_| {});
12782
12783 let mut cx = EditorLspTestContext::new_rust(
12784 lsp::ServerCapabilities {
12785 completion_provider: Some(lsp::CompletionOptions {
12786 trigger_characters: Some(vec![".".to_string()]),
12787 resolve_provider: Some(true),
12788 ..Default::default()
12789 }),
12790 ..Default::default()
12791 },
12792 cx,
12793 )
12794 .await;
12795
12796 cx.set_state("fn main() { let a = 2ˇ; }");
12797 cx.simulate_keystroke(".");
12798
12799 let unresolved_item_1 = lsp::CompletionItem {
12800 label: "id".to_string(),
12801 filter_text: Some("id".to_string()),
12802 detail: None,
12803 documentation: None,
12804 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12805 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12806 new_text: ".id".to_string(),
12807 })),
12808 ..lsp::CompletionItem::default()
12809 };
12810 let resolved_item_1 = lsp::CompletionItem {
12811 additional_text_edits: Some(vec![lsp::TextEdit {
12812 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
12813 new_text: "!!".to_string(),
12814 }]),
12815 ..unresolved_item_1.clone()
12816 };
12817 let unresolved_item_2 = lsp::CompletionItem {
12818 label: "other".to_string(),
12819 filter_text: Some("other".to_string()),
12820 detail: None,
12821 documentation: None,
12822 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12823 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12824 new_text: ".other".to_string(),
12825 })),
12826 ..lsp::CompletionItem::default()
12827 };
12828 let resolved_item_2 = lsp::CompletionItem {
12829 additional_text_edits: Some(vec![lsp::TextEdit {
12830 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
12831 new_text: "??".to_string(),
12832 }]),
12833 ..unresolved_item_2.clone()
12834 };
12835
12836 let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
12837 let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
12838 cx.lsp
12839 .server
12840 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
12841 let unresolved_item_1 = unresolved_item_1.clone();
12842 let resolved_item_1 = resolved_item_1.clone();
12843 let unresolved_item_2 = unresolved_item_2.clone();
12844 let resolved_item_2 = resolved_item_2.clone();
12845 let resolve_requests_1 = resolve_requests_1.clone();
12846 let resolve_requests_2 = resolve_requests_2.clone();
12847 move |unresolved_request, _| {
12848 let unresolved_item_1 = unresolved_item_1.clone();
12849 let resolved_item_1 = resolved_item_1.clone();
12850 let unresolved_item_2 = unresolved_item_2.clone();
12851 let resolved_item_2 = resolved_item_2.clone();
12852 let resolve_requests_1 = resolve_requests_1.clone();
12853 let resolve_requests_2 = resolve_requests_2.clone();
12854 async move {
12855 if unresolved_request == unresolved_item_1 {
12856 resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
12857 Ok(resolved_item_1.clone())
12858 } else if unresolved_request == unresolved_item_2 {
12859 resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
12860 Ok(resolved_item_2.clone())
12861 } else {
12862 panic!("Unexpected completion item {unresolved_request:?}")
12863 }
12864 }
12865 }
12866 })
12867 .detach();
12868
12869 cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
12870 let unresolved_item_1 = unresolved_item_1.clone();
12871 let unresolved_item_2 = unresolved_item_2.clone();
12872 async move {
12873 Ok(Some(lsp::CompletionResponse::Array(vec![
12874 unresolved_item_1,
12875 unresolved_item_2,
12876 ])))
12877 }
12878 })
12879 .next()
12880 .await;
12881
12882 cx.condition(|editor, _| editor.context_menu_visible())
12883 .await;
12884 cx.update_editor(|editor, _, _| {
12885 let context_menu = editor.context_menu.borrow_mut();
12886 let context_menu = context_menu
12887 .as_ref()
12888 .expect("Should have the context menu deployed");
12889 match context_menu {
12890 CodeContextMenu::Completions(completions_menu) => {
12891 let completions = completions_menu.completions.borrow_mut();
12892 assert_eq!(
12893 completions
12894 .iter()
12895 .map(|completion| &completion.label.text)
12896 .collect::<Vec<_>>(),
12897 vec!["id", "other"]
12898 )
12899 }
12900 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
12901 }
12902 });
12903 cx.run_until_parked();
12904
12905 cx.update_editor(|editor, window, cx| {
12906 editor.context_menu_next(&ContextMenuNext, window, cx);
12907 });
12908 cx.run_until_parked();
12909 cx.update_editor(|editor, window, cx| {
12910 editor.context_menu_prev(&ContextMenuPrevious, window, cx);
12911 });
12912 cx.run_until_parked();
12913 cx.update_editor(|editor, window, cx| {
12914 editor.context_menu_next(&ContextMenuNext, window, cx);
12915 });
12916 cx.run_until_parked();
12917 cx.update_editor(|editor, window, cx| {
12918 editor
12919 .compose_completion(&ComposeCompletion::default(), window, cx)
12920 .expect("No task returned")
12921 })
12922 .await
12923 .expect("Completion failed");
12924 cx.run_until_parked();
12925
12926 cx.update_editor(|editor, _, cx| {
12927 assert_eq!(
12928 resolve_requests_1.load(atomic::Ordering::Acquire),
12929 1,
12930 "Should always resolve once despite multiple selections"
12931 );
12932 assert_eq!(
12933 resolve_requests_2.load(atomic::Ordering::Acquire),
12934 1,
12935 "Should always resolve once after multiple selections and applying the completion"
12936 );
12937 assert_eq!(
12938 editor.text(cx),
12939 "fn main() { let a = ??.other; }",
12940 "Should use resolved data when applying the completion"
12941 );
12942 });
12943}
12944
12945#[gpui::test]
12946async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
12947 init_test(cx, |_| {});
12948
12949 let item_0 = lsp::CompletionItem {
12950 label: "abs".into(),
12951 insert_text: Some("abs".into()),
12952 data: Some(json!({ "very": "special"})),
12953 insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
12954 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
12955 lsp::InsertReplaceEdit {
12956 new_text: "abs".to_string(),
12957 insert: lsp::Range::default(),
12958 replace: lsp::Range::default(),
12959 },
12960 )),
12961 ..lsp::CompletionItem::default()
12962 };
12963 let items = iter::once(item_0.clone())
12964 .chain((11..51).map(|i| lsp::CompletionItem {
12965 label: format!("item_{}", i),
12966 insert_text: Some(format!("item_{}", i)),
12967 insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
12968 ..lsp::CompletionItem::default()
12969 }))
12970 .collect::<Vec<_>>();
12971
12972 let default_commit_characters = vec!["?".to_string()];
12973 let default_data = json!({ "default": "data"});
12974 let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
12975 let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
12976 let default_edit_range = lsp::Range {
12977 start: lsp::Position {
12978 line: 0,
12979 character: 5,
12980 },
12981 end: lsp::Position {
12982 line: 0,
12983 character: 5,
12984 },
12985 };
12986
12987 let mut cx = EditorLspTestContext::new_rust(
12988 lsp::ServerCapabilities {
12989 completion_provider: Some(lsp::CompletionOptions {
12990 trigger_characters: Some(vec![".".to_string()]),
12991 resolve_provider: Some(true),
12992 ..Default::default()
12993 }),
12994 ..Default::default()
12995 },
12996 cx,
12997 )
12998 .await;
12999
13000 cx.set_state("fn main() { let a = 2ˇ; }");
13001 cx.simulate_keystroke(".");
13002
13003 let completion_data = default_data.clone();
13004 let completion_characters = default_commit_characters.clone();
13005 let completion_items = items.clone();
13006 cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
13007 let default_data = completion_data.clone();
13008 let default_commit_characters = completion_characters.clone();
13009 let items = completion_items.clone();
13010 async move {
13011 Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13012 items,
13013 item_defaults: Some(lsp::CompletionListItemDefaults {
13014 data: Some(default_data.clone()),
13015 commit_characters: Some(default_commit_characters.clone()),
13016 edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
13017 default_edit_range,
13018 )),
13019 insert_text_format: Some(default_insert_text_format),
13020 insert_text_mode: Some(default_insert_text_mode),
13021 }),
13022 ..lsp::CompletionList::default()
13023 })))
13024 }
13025 })
13026 .next()
13027 .await;
13028
13029 let resolved_items = Arc::new(Mutex::new(Vec::new()));
13030 cx.lsp
13031 .server
13032 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
13033 let closure_resolved_items = resolved_items.clone();
13034 move |item_to_resolve, _| {
13035 let closure_resolved_items = closure_resolved_items.clone();
13036 async move {
13037 closure_resolved_items.lock().push(item_to_resolve.clone());
13038 Ok(item_to_resolve)
13039 }
13040 }
13041 })
13042 .detach();
13043
13044 cx.condition(|editor, _| editor.context_menu_visible())
13045 .await;
13046 cx.run_until_parked();
13047 cx.update_editor(|editor, _, _| {
13048 let menu = editor.context_menu.borrow_mut();
13049 match menu.as_ref().expect("should have the completions menu") {
13050 CodeContextMenu::Completions(completions_menu) => {
13051 assert_eq!(
13052 completions_menu
13053 .entries
13054 .borrow()
13055 .iter()
13056 .map(|mat| mat.string.clone())
13057 .collect::<Vec<String>>(),
13058 items
13059 .iter()
13060 .map(|completion| completion.label.clone())
13061 .collect::<Vec<String>>()
13062 );
13063 }
13064 CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
13065 }
13066 });
13067 // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
13068 // with 4 from the end.
13069 assert_eq!(
13070 *resolved_items.lock(),
13071 [&items[0..16], &items[items.len() - 4..items.len()]]
13072 .concat()
13073 .iter()
13074 .cloned()
13075 .map(|mut item| {
13076 if item.data.is_none() {
13077 item.data = Some(default_data.clone());
13078 }
13079 item
13080 })
13081 .collect::<Vec<lsp::CompletionItem>>(),
13082 "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
13083 );
13084 resolved_items.lock().clear();
13085
13086 cx.update_editor(|editor, window, cx| {
13087 editor.context_menu_prev(&ContextMenuPrevious, window, cx);
13088 });
13089 cx.run_until_parked();
13090 // Completions that have already been resolved are skipped.
13091 assert_eq!(
13092 *resolved_items.lock(),
13093 items[items.len() - 16..items.len() - 4]
13094 .iter()
13095 .cloned()
13096 .map(|mut item| {
13097 if item.data.is_none() {
13098 item.data = Some(default_data.clone());
13099 }
13100 item
13101 })
13102 .collect::<Vec<lsp::CompletionItem>>()
13103 );
13104 resolved_items.lock().clear();
13105}
13106
13107#[gpui::test]
13108async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
13109 init_test(cx, |_| {});
13110
13111 let mut cx = EditorLspTestContext::new(
13112 Language::new(
13113 LanguageConfig {
13114 matcher: LanguageMatcher {
13115 path_suffixes: vec!["jsx".into()],
13116 ..Default::default()
13117 },
13118 overrides: [(
13119 "element".into(),
13120 LanguageConfigOverride {
13121 completion_query_characters: Override::Set(['-'].into_iter().collect()),
13122 ..Default::default()
13123 },
13124 )]
13125 .into_iter()
13126 .collect(),
13127 ..Default::default()
13128 },
13129 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
13130 )
13131 .with_override_query("(jsx_self_closing_element) @element")
13132 .unwrap(),
13133 lsp::ServerCapabilities {
13134 completion_provider: Some(lsp::CompletionOptions {
13135 trigger_characters: Some(vec![":".to_string()]),
13136 ..Default::default()
13137 }),
13138 ..Default::default()
13139 },
13140 cx,
13141 )
13142 .await;
13143
13144 cx.lsp
13145 .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13146 Ok(Some(lsp::CompletionResponse::Array(vec![
13147 lsp::CompletionItem {
13148 label: "bg-blue".into(),
13149 ..Default::default()
13150 },
13151 lsp::CompletionItem {
13152 label: "bg-red".into(),
13153 ..Default::default()
13154 },
13155 lsp::CompletionItem {
13156 label: "bg-yellow".into(),
13157 ..Default::default()
13158 },
13159 ])))
13160 });
13161
13162 cx.set_state(r#"<p class="bgˇ" />"#);
13163
13164 // Trigger completion when typing a dash, because the dash is an extra
13165 // word character in the 'element' scope, which contains the cursor.
13166 cx.simulate_keystroke("-");
13167 cx.executor().run_until_parked();
13168 cx.update_editor(|editor, _, _| {
13169 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13170 {
13171 assert_eq!(
13172 completion_menu_entries(&menu),
13173 &["bg-red", "bg-blue", "bg-yellow"]
13174 );
13175 } else {
13176 panic!("expected completion menu to be open");
13177 }
13178 });
13179
13180 cx.simulate_keystroke("l");
13181 cx.executor().run_until_parked();
13182 cx.update_editor(|editor, _, _| {
13183 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13184 {
13185 assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
13186 } else {
13187 panic!("expected completion menu to be open");
13188 }
13189 });
13190
13191 // When filtering completions, consider the character after the '-' to
13192 // be the start of a subword.
13193 cx.set_state(r#"<p class="yelˇ" />"#);
13194 cx.simulate_keystroke("l");
13195 cx.executor().run_until_parked();
13196 cx.update_editor(|editor, _, _| {
13197 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13198 {
13199 assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
13200 } else {
13201 panic!("expected completion menu to be open");
13202 }
13203 });
13204}
13205
13206fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
13207 let entries = menu.entries.borrow();
13208 entries.iter().map(|mat| mat.string.clone()).collect()
13209}
13210
13211#[gpui::test]
13212async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
13213 init_test(cx, |settings| {
13214 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
13215 FormatterList(vec![Formatter::Prettier].into()),
13216 ))
13217 });
13218
13219 let fs = FakeFs::new(cx.executor());
13220 fs.insert_file(path!("/file.ts"), Default::default()).await;
13221
13222 let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
13223 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13224
13225 language_registry.add(Arc::new(Language::new(
13226 LanguageConfig {
13227 name: "TypeScript".into(),
13228 matcher: LanguageMatcher {
13229 path_suffixes: vec!["ts".to_string()],
13230 ..Default::default()
13231 },
13232 ..Default::default()
13233 },
13234 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
13235 )));
13236 update_test_language_settings(cx, |settings| {
13237 settings.defaults.prettier = Some(PrettierSettings {
13238 allowed: true,
13239 ..PrettierSettings::default()
13240 });
13241 });
13242
13243 let test_plugin = "test_plugin";
13244 let _ = language_registry.register_fake_lsp(
13245 "TypeScript",
13246 FakeLspAdapter {
13247 prettier_plugins: vec![test_plugin],
13248 ..Default::default()
13249 },
13250 );
13251
13252 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
13253 let buffer = project
13254 .update(cx, |project, cx| {
13255 project.open_local_buffer(path!("/file.ts"), cx)
13256 })
13257 .await
13258 .unwrap();
13259
13260 let buffer_text = "one\ntwo\nthree\n";
13261 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
13262 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
13263 editor.update_in(cx, |editor, window, cx| {
13264 editor.set_text(buffer_text, window, cx)
13265 });
13266
13267 editor
13268 .update_in(cx, |editor, window, cx| {
13269 editor.perform_format(
13270 project.clone(),
13271 FormatTrigger::Manual,
13272 FormatTarget::Buffers,
13273 window,
13274 cx,
13275 )
13276 })
13277 .unwrap()
13278 .await;
13279 assert_eq!(
13280 editor.update(cx, |editor, cx| editor.text(cx)),
13281 buffer_text.to_string() + prettier_format_suffix,
13282 "Test prettier formatting was not applied to the original buffer text",
13283 );
13284
13285 update_test_language_settings(cx, |settings| {
13286 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
13287 });
13288 let format = editor.update_in(cx, |editor, window, cx| {
13289 editor.perform_format(
13290 project.clone(),
13291 FormatTrigger::Manual,
13292 FormatTarget::Buffers,
13293 window,
13294 cx,
13295 )
13296 });
13297 format.await.unwrap();
13298 assert_eq!(
13299 editor.update(cx, |editor, cx| editor.text(cx)),
13300 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
13301 "Autoformatting (via test prettier) was not applied to the original buffer text",
13302 );
13303}
13304
13305#[gpui::test]
13306async fn test_addition_reverts(cx: &mut TestAppContext) {
13307 init_test(cx, |_| {});
13308 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13309 let base_text = indoc! {r#"
13310 struct Row;
13311 struct Row1;
13312 struct Row2;
13313
13314 struct Row4;
13315 struct Row5;
13316 struct Row6;
13317
13318 struct Row8;
13319 struct Row9;
13320 struct Row10;"#};
13321
13322 // When addition hunks are not adjacent to carets, no hunk revert is performed
13323 assert_hunk_revert(
13324 indoc! {r#"struct Row;
13325 struct Row1;
13326 struct Row1.1;
13327 struct Row1.2;
13328 struct Row2;ˇ
13329
13330 struct Row4;
13331 struct Row5;
13332 struct Row6;
13333
13334 struct Row8;
13335 ˇstruct Row9;
13336 struct Row9.1;
13337 struct Row9.2;
13338 struct Row9.3;
13339 struct Row10;"#},
13340 vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
13341 indoc! {r#"struct Row;
13342 struct Row1;
13343 struct Row1.1;
13344 struct Row1.2;
13345 struct Row2;ˇ
13346
13347 struct Row4;
13348 struct Row5;
13349 struct Row6;
13350
13351 struct Row8;
13352 ˇstruct Row9;
13353 struct Row9.1;
13354 struct Row9.2;
13355 struct Row9.3;
13356 struct Row10;"#},
13357 base_text,
13358 &mut cx,
13359 );
13360 // Same for selections
13361 assert_hunk_revert(
13362 indoc! {r#"struct Row;
13363 struct Row1;
13364 struct Row2;
13365 struct Row2.1;
13366 struct Row2.2;
13367 «ˇ
13368 struct Row4;
13369 struct» Row5;
13370 «struct Row6;
13371 ˇ»
13372 struct Row9.1;
13373 struct Row9.2;
13374 struct Row9.3;
13375 struct Row8;
13376 struct Row9;
13377 struct Row10;"#},
13378 vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
13379 indoc! {r#"struct Row;
13380 struct Row1;
13381 struct Row2;
13382 struct Row2.1;
13383 struct Row2.2;
13384 «ˇ
13385 struct Row4;
13386 struct» Row5;
13387 «struct Row6;
13388 ˇ»
13389 struct Row9.1;
13390 struct Row9.2;
13391 struct Row9.3;
13392 struct Row8;
13393 struct Row9;
13394 struct Row10;"#},
13395 base_text,
13396 &mut cx,
13397 );
13398
13399 // When carets and selections intersect the addition hunks, those are reverted.
13400 // Adjacent carets got merged.
13401 assert_hunk_revert(
13402 indoc! {r#"struct Row;
13403 ˇ// something on the top
13404 struct Row1;
13405 struct Row2;
13406 struct Roˇw3.1;
13407 struct Row2.2;
13408 struct Row2.3;ˇ
13409
13410 struct Row4;
13411 struct ˇRow5.1;
13412 struct Row5.2;
13413 struct «Rowˇ»5.3;
13414 struct Row5;
13415 struct Row6;
13416 ˇ
13417 struct Row9.1;
13418 struct «Rowˇ»9.2;
13419 struct «ˇRow»9.3;
13420 struct Row8;
13421 struct Row9;
13422 «ˇ// something on bottom»
13423 struct Row10;"#},
13424 vec![
13425 DiffHunkStatusKind::Added,
13426 DiffHunkStatusKind::Added,
13427 DiffHunkStatusKind::Added,
13428 DiffHunkStatusKind::Added,
13429 DiffHunkStatusKind::Added,
13430 ],
13431 indoc! {r#"struct Row;
13432 ˇstruct Row1;
13433 struct Row2;
13434 ˇ
13435 struct Row4;
13436 ˇstruct Row5;
13437 struct Row6;
13438 ˇ
13439 ˇstruct Row8;
13440 struct Row9;
13441 ˇstruct Row10;"#},
13442 base_text,
13443 &mut cx,
13444 );
13445}
13446
13447#[gpui::test]
13448async fn test_modification_reverts(cx: &mut TestAppContext) {
13449 init_test(cx, |_| {});
13450 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13451 let base_text = indoc! {r#"
13452 struct Row;
13453 struct Row1;
13454 struct Row2;
13455
13456 struct Row4;
13457 struct Row5;
13458 struct Row6;
13459
13460 struct Row8;
13461 struct Row9;
13462 struct Row10;"#};
13463
13464 // Modification hunks behave the same as the addition ones.
13465 assert_hunk_revert(
13466 indoc! {r#"struct Row;
13467 struct Row1;
13468 struct Row33;
13469 ˇ
13470 struct Row4;
13471 struct Row5;
13472 struct Row6;
13473 ˇ
13474 struct Row99;
13475 struct Row9;
13476 struct Row10;"#},
13477 vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
13478 indoc! {r#"struct Row;
13479 struct Row1;
13480 struct Row33;
13481 ˇ
13482 struct Row4;
13483 struct Row5;
13484 struct Row6;
13485 ˇ
13486 struct Row99;
13487 struct Row9;
13488 struct Row10;"#},
13489 base_text,
13490 &mut cx,
13491 );
13492 assert_hunk_revert(
13493 indoc! {r#"struct Row;
13494 struct Row1;
13495 struct Row33;
13496 «ˇ
13497 struct Row4;
13498 struct» Row5;
13499 «struct Row6;
13500 ˇ»
13501 struct Row99;
13502 struct Row9;
13503 struct Row10;"#},
13504 vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
13505 indoc! {r#"struct Row;
13506 struct Row1;
13507 struct Row33;
13508 «ˇ
13509 struct Row4;
13510 struct» Row5;
13511 «struct Row6;
13512 ˇ»
13513 struct Row99;
13514 struct Row9;
13515 struct Row10;"#},
13516 base_text,
13517 &mut cx,
13518 );
13519
13520 assert_hunk_revert(
13521 indoc! {r#"ˇstruct Row1.1;
13522 struct Row1;
13523 «ˇstr»uct Row22;
13524
13525 struct ˇRow44;
13526 struct Row5;
13527 struct «Rˇ»ow66;ˇ
13528
13529 «struˇ»ct Row88;
13530 struct Row9;
13531 struct Row1011;ˇ"#},
13532 vec![
13533 DiffHunkStatusKind::Modified,
13534 DiffHunkStatusKind::Modified,
13535 DiffHunkStatusKind::Modified,
13536 DiffHunkStatusKind::Modified,
13537 DiffHunkStatusKind::Modified,
13538 DiffHunkStatusKind::Modified,
13539 ],
13540 indoc! {r#"struct Row;
13541 ˇstruct Row1;
13542 struct Row2;
13543 ˇ
13544 struct Row4;
13545 ˇstruct Row5;
13546 struct Row6;
13547 ˇ
13548 struct Row8;
13549 ˇstruct Row9;
13550 struct Row10;ˇ"#},
13551 base_text,
13552 &mut cx,
13553 );
13554}
13555
13556#[gpui::test]
13557async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
13558 init_test(cx, |_| {});
13559 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13560 let base_text = indoc! {r#"
13561 one
13562
13563 two
13564 three
13565 "#};
13566
13567 cx.set_head_text(base_text);
13568 cx.set_state("\nˇ\n");
13569 cx.executor().run_until_parked();
13570 cx.update_editor(|editor, _window, cx| {
13571 editor.expand_selected_diff_hunks(cx);
13572 });
13573 cx.executor().run_until_parked();
13574 cx.update_editor(|editor, window, cx| {
13575 editor.backspace(&Default::default(), window, cx);
13576 });
13577 cx.run_until_parked();
13578 cx.assert_state_with_diff(
13579 indoc! {r#"
13580
13581 - two
13582 - threeˇ
13583 +
13584 "#}
13585 .to_string(),
13586 );
13587}
13588
13589#[gpui::test]
13590async fn test_deletion_reverts(cx: &mut TestAppContext) {
13591 init_test(cx, |_| {});
13592 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13593 let base_text = indoc! {r#"struct Row;
13594struct Row1;
13595struct Row2;
13596
13597struct Row4;
13598struct Row5;
13599struct Row6;
13600
13601struct Row8;
13602struct Row9;
13603struct Row10;"#};
13604
13605 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
13606 assert_hunk_revert(
13607 indoc! {r#"struct Row;
13608 struct Row2;
13609
13610 ˇstruct Row4;
13611 struct Row5;
13612 struct Row6;
13613 ˇ
13614 struct Row8;
13615 struct Row10;"#},
13616 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
13617 indoc! {r#"struct Row;
13618 struct Row2;
13619
13620 ˇstruct Row4;
13621 struct Row5;
13622 struct Row6;
13623 ˇ
13624 struct Row8;
13625 struct Row10;"#},
13626 base_text,
13627 &mut cx,
13628 );
13629 assert_hunk_revert(
13630 indoc! {r#"struct Row;
13631 struct Row2;
13632
13633 «ˇstruct Row4;
13634 struct» Row5;
13635 «struct Row6;
13636 ˇ»
13637 struct Row8;
13638 struct Row10;"#},
13639 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
13640 indoc! {r#"struct Row;
13641 struct Row2;
13642
13643 «ˇstruct Row4;
13644 struct» Row5;
13645 «struct Row6;
13646 ˇ»
13647 struct Row8;
13648 struct Row10;"#},
13649 base_text,
13650 &mut cx,
13651 );
13652
13653 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
13654 assert_hunk_revert(
13655 indoc! {r#"struct Row;
13656 ˇstruct Row2;
13657
13658 struct Row4;
13659 struct Row5;
13660 struct Row6;
13661
13662 struct Row8;ˇ
13663 struct Row10;"#},
13664 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
13665 indoc! {r#"struct Row;
13666 struct Row1;
13667 ˇstruct Row2;
13668
13669 struct Row4;
13670 struct Row5;
13671 struct Row6;
13672
13673 struct Row8;ˇ
13674 struct Row9;
13675 struct Row10;"#},
13676 base_text,
13677 &mut cx,
13678 );
13679 assert_hunk_revert(
13680 indoc! {r#"struct Row;
13681 struct Row2«ˇ;
13682 struct Row4;
13683 struct» Row5;
13684 «struct Row6;
13685
13686 struct Row8;ˇ»
13687 struct Row10;"#},
13688 vec![
13689 DiffHunkStatusKind::Deleted,
13690 DiffHunkStatusKind::Deleted,
13691 DiffHunkStatusKind::Deleted,
13692 ],
13693 indoc! {r#"struct Row;
13694 struct Row1;
13695 struct Row2«ˇ;
13696
13697 struct Row4;
13698 struct» Row5;
13699 «struct Row6;
13700
13701 struct Row8;ˇ»
13702 struct Row9;
13703 struct Row10;"#},
13704 base_text,
13705 &mut cx,
13706 );
13707}
13708
13709#[gpui::test]
13710async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
13711 init_test(cx, |_| {});
13712
13713 let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
13714 let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
13715 let base_text_3 =
13716 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
13717
13718 let text_1 = edit_first_char_of_every_line(base_text_1);
13719 let text_2 = edit_first_char_of_every_line(base_text_2);
13720 let text_3 = edit_first_char_of_every_line(base_text_3);
13721
13722 let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
13723 let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
13724 let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
13725
13726 let multibuffer = cx.new(|cx| {
13727 let mut multibuffer = MultiBuffer::new(ReadWrite);
13728 multibuffer.push_excerpts(
13729 buffer_1.clone(),
13730 [
13731 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
13732 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
13733 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
13734 ],
13735 cx,
13736 );
13737 multibuffer.push_excerpts(
13738 buffer_2.clone(),
13739 [
13740 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
13741 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
13742 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
13743 ],
13744 cx,
13745 );
13746 multibuffer.push_excerpts(
13747 buffer_3.clone(),
13748 [
13749 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
13750 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
13751 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
13752 ],
13753 cx,
13754 );
13755 multibuffer
13756 });
13757
13758 let fs = FakeFs::new(cx.executor());
13759 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
13760 let (editor, cx) = cx
13761 .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
13762 editor.update_in(cx, |editor, _window, cx| {
13763 for (buffer, diff_base) in [
13764 (buffer_1.clone(), base_text_1),
13765 (buffer_2.clone(), base_text_2),
13766 (buffer_3.clone(), base_text_3),
13767 ] {
13768 let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
13769 editor
13770 .buffer
13771 .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
13772 }
13773 });
13774 cx.executor().run_until_parked();
13775
13776 editor.update_in(cx, |editor, window, cx| {
13777 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}");
13778 editor.select_all(&SelectAll, window, cx);
13779 editor.git_restore(&Default::default(), window, cx);
13780 });
13781 cx.executor().run_until_parked();
13782
13783 // When all ranges are selected, all buffer hunks are reverted.
13784 editor.update(cx, |editor, cx| {
13785 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");
13786 });
13787 buffer_1.update(cx, |buffer, _| {
13788 assert_eq!(buffer.text(), base_text_1);
13789 });
13790 buffer_2.update(cx, |buffer, _| {
13791 assert_eq!(buffer.text(), base_text_2);
13792 });
13793 buffer_3.update(cx, |buffer, _| {
13794 assert_eq!(buffer.text(), base_text_3);
13795 });
13796
13797 editor.update_in(cx, |editor, window, cx| {
13798 editor.undo(&Default::default(), window, cx);
13799 });
13800
13801 editor.update_in(cx, |editor, window, cx| {
13802 editor.change_selections(None, window, cx, |s| {
13803 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
13804 });
13805 editor.git_restore(&Default::default(), window, cx);
13806 });
13807
13808 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
13809 // but not affect buffer_2 and its related excerpts.
13810 editor.update(cx, |editor, cx| {
13811 assert_eq!(
13812 editor.text(cx),
13813 "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}"
13814 );
13815 });
13816 buffer_1.update(cx, |buffer, _| {
13817 assert_eq!(buffer.text(), base_text_1);
13818 });
13819 buffer_2.update(cx, |buffer, _| {
13820 assert_eq!(
13821 buffer.text(),
13822 "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
13823 );
13824 });
13825 buffer_3.update(cx, |buffer, _| {
13826 assert_eq!(
13827 buffer.text(),
13828 "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
13829 );
13830 });
13831
13832 fn edit_first_char_of_every_line(text: &str) -> String {
13833 text.split('\n')
13834 .map(|line| format!("X{}", &line[1..]))
13835 .collect::<Vec<_>>()
13836 .join("\n")
13837 }
13838}
13839
13840#[gpui::test]
13841async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
13842 init_test(cx, |_| {});
13843
13844 let cols = 4;
13845 let rows = 10;
13846 let sample_text_1 = sample_text(rows, cols, 'a');
13847 assert_eq!(
13848 sample_text_1,
13849 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
13850 );
13851 let sample_text_2 = sample_text(rows, cols, 'l');
13852 assert_eq!(
13853 sample_text_2,
13854 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
13855 );
13856 let sample_text_3 = sample_text(rows, cols, 'v');
13857 assert_eq!(
13858 sample_text_3,
13859 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
13860 );
13861
13862 let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
13863 let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
13864 let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
13865
13866 let multi_buffer = cx.new(|cx| {
13867 let mut multibuffer = MultiBuffer::new(ReadWrite);
13868 multibuffer.push_excerpts(
13869 buffer_1.clone(),
13870 [
13871 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
13872 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
13873 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
13874 ],
13875 cx,
13876 );
13877 multibuffer.push_excerpts(
13878 buffer_2.clone(),
13879 [
13880 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
13881 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
13882 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
13883 ],
13884 cx,
13885 );
13886 multibuffer.push_excerpts(
13887 buffer_3.clone(),
13888 [
13889 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
13890 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
13891 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
13892 ],
13893 cx,
13894 );
13895 multibuffer
13896 });
13897
13898 let fs = FakeFs::new(cx.executor());
13899 fs.insert_tree(
13900 "/a",
13901 json!({
13902 "main.rs": sample_text_1,
13903 "other.rs": sample_text_2,
13904 "lib.rs": sample_text_3,
13905 }),
13906 )
13907 .await;
13908 let project = Project::test(fs, ["/a".as_ref()], cx).await;
13909 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13910 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
13911 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
13912 Editor::new(
13913 EditorMode::Full,
13914 multi_buffer,
13915 Some(project.clone()),
13916 window,
13917 cx,
13918 )
13919 });
13920 let multibuffer_item_id = workspace
13921 .update(cx, |workspace, window, cx| {
13922 assert!(
13923 workspace.active_item(cx).is_none(),
13924 "active item should be None before the first item is added"
13925 );
13926 workspace.add_item_to_active_pane(
13927 Box::new(multi_buffer_editor.clone()),
13928 None,
13929 true,
13930 window,
13931 cx,
13932 );
13933 let active_item = workspace
13934 .active_item(cx)
13935 .expect("should have an active item after adding the multi buffer");
13936 assert!(
13937 !active_item.is_singleton(cx),
13938 "A multi buffer was expected to active after adding"
13939 );
13940 active_item.item_id()
13941 })
13942 .unwrap();
13943 cx.executor().run_until_parked();
13944
13945 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13946 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13947 s.select_ranges(Some(1..2))
13948 });
13949 editor.open_excerpts(&OpenExcerpts, window, cx);
13950 });
13951 cx.executor().run_until_parked();
13952 let first_item_id = workspace
13953 .update(cx, |workspace, window, cx| {
13954 let active_item = workspace
13955 .active_item(cx)
13956 .expect("should have an active item after navigating into the 1st buffer");
13957 let first_item_id = active_item.item_id();
13958 assert_ne!(
13959 first_item_id, multibuffer_item_id,
13960 "Should navigate into the 1st buffer and activate it"
13961 );
13962 assert!(
13963 active_item.is_singleton(cx),
13964 "New active item should be a singleton buffer"
13965 );
13966 assert_eq!(
13967 active_item
13968 .act_as::<Editor>(cx)
13969 .expect("should have navigated into an editor for the 1st buffer")
13970 .read(cx)
13971 .text(cx),
13972 sample_text_1
13973 );
13974
13975 workspace
13976 .go_back(workspace.active_pane().downgrade(), window, cx)
13977 .detach_and_log_err(cx);
13978
13979 first_item_id
13980 })
13981 .unwrap();
13982 cx.executor().run_until_parked();
13983 workspace
13984 .update(cx, |workspace, _, cx| {
13985 let active_item = workspace
13986 .active_item(cx)
13987 .expect("should have an active item after navigating back");
13988 assert_eq!(
13989 active_item.item_id(),
13990 multibuffer_item_id,
13991 "Should navigate back to the multi buffer"
13992 );
13993 assert!(!active_item.is_singleton(cx));
13994 })
13995 .unwrap();
13996
13997 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13998 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13999 s.select_ranges(Some(39..40))
14000 });
14001 editor.open_excerpts(&OpenExcerpts, window, cx);
14002 });
14003 cx.executor().run_until_parked();
14004 let second_item_id = workspace
14005 .update(cx, |workspace, window, cx| {
14006 let active_item = workspace
14007 .active_item(cx)
14008 .expect("should have an active item after navigating into the 2nd buffer");
14009 let second_item_id = active_item.item_id();
14010 assert_ne!(
14011 second_item_id, multibuffer_item_id,
14012 "Should navigate away from the multibuffer"
14013 );
14014 assert_ne!(
14015 second_item_id, first_item_id,
14016 "Should navigate into the 2nd buffer and activate it"
14017 );
14018 assert!(
14019 active_item.is_singleton(cx),
14020 "New active item should be a singleton buffer"
14021 );
14022 assert_eq!(
14023 active_item
14024 .act_as::<Editor>(cx)
14025 .expect("should have navigated into an editor")
14026 .read(cx)
14027 .text(cx),
14028 sample_text_2
14029 );
14030
14031 workspace
14032 .go_back(workspace.active_pane().downgrade(), window, cx)
14033 .detach_and_log_err(cx);
14034
14035 second_item_id
14036 })
14037 .unwrap();
14038 cx.executor().run_until_parked();
14039 workspace
14040 .update(cx, |workspace, _, cx| {
14041 let active_item = workspace
14042 .active_item(cx)
14043 .expect("should have an active item after navigating back from the 2nd buffer");
14044 assert_eq!(
14045 active_item.item_id(),
14046 multibuffer_item_id,
14047 "Should navigate back from the 2nd buffer to the multi buffer"
14048 );
14049 assert!(!active_item.is_singleton(cx));
14050 })
14051 .unwrap();
14052
14053 multi_buffer_editor.update_in(cx, |editor, window, cx| {
14054 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
14055 s.select_ranges(Some(70..70))
14056 });
14057 editor.open_excerpts(&OpenExcerpts, window, cx);
14058 });
14059 cx.executor().run_until_parked();
14060 workspace
14061 .update(cx, |workspace, window, cx| {
14062 let active_item = workspace
14063 .active_item(cx)
14064 .expect("should have an active item after navigating into the 3rd buffer");
14065 let third_item_id = active_item.item_id();
14066 assert_ne!(
14067 third_item_id, multibuffer_item_id,
14068 "Should navigate into the 3rd buffer and activate it"
14069 );
14070 assert_ne!(third_item_id, first_item_id);
14071 assert_ne!(third_item_id, second_item_id);
14072 assert!(
14073 active_item.is_singleton(cx),
14074 "New active item should be a singleton buffer"
14075 );
14076 assert_eq!(
14077 active_item
14078 .act_as::<Editor>(cx)
14079 .expect("should have navigated into an editor")
14080 .read(cx)
14081 .text(cx),
14082 sample_text_3
14083 );
14084
14085 workspace
14086 .go_back(workspace.active_pane().downgrade(), window, cx)
14087 .detach_and_log_err(cx);
14088 })
14089 .unwrap();
14090 cx.executor().run_until_parked();
14091 workspace
14092 .update(cx, |workspace, _, cx| {
14093 let active_item = workspace
14094 .active_item(cx)
14095 .expect("should have an active item after navigating back from the 3rd buffer");
14096 assert_eq!(
14097 active_item.item_id(),
14098 multibuffer_item_id,
14099 "Should navigate back from the 3rd buffer to the multi buffer"
14100 );
14101 assert!(!active_item.is_singleton(cx));
14102 })
14103 .unwrap();
14104}
14105
14106#[gpui::test]
14107async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14108 init_test(cx, |_| {});
14109
14110 let mut cx = EditorTestContext::new(cx).await;
14111
14112 let diff_base = r#"
14113 use some::mod;
14114
14115 const A: u32 = 42;
14116
14117 fn main() {
14118 println!("hello");
14119
14120 println!("world");
14121 }
14122 "#
14123 .unindent();
14124
14125 cx.set_state(
14126 &r#"
14127 use some::modified;
14128
14129 ˇ
14130 fn main() {
14131 println!("hello there");
14132
14133 println!("around the");
14134 println!("world");
14135 }
14136 "#
14137 .unindent(),
14138 );
14139
14140 cx.set_head_text(&diff_base);
14141 executor.run_until_parked();
14142
14143 cx.update_editor(|editor, window, cx| {
14144 editor.go_to_next_hunk(&GoToHunk, window, cx);
14145 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
14146 });
14147 executor.run_until_parked();
14148 cx.assert_state_with_diff(
14149 r#"
14150 use some::modified;
14151
14152
14153 fn main() {
14154 - println!("hello");
14155 + ˇ println!("hello there");
14156
14157 println!("around the");
14158 println!("world");
14159 }
14160 "#
14161 .unindent(),
14162 );
14163
14164 cx.update_editor(|editor, window, cx| {
14165 for _ in 0..2 {
14166 editor.go_to_next_hunk(&GoToHunk, window, cx);
14167 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
14168 }
14169 });
14170 executor.run_until_parked();
14171 cx.assert_state_with_diff(
14172 r#"
14173 - use some::mod;
14174 + ˇuse some::modified;
14175
14176
14177 fn main() {
14178 - println!("hello");
14179 + println!("hello there");
14180
14181 + println!("around the");
14182 println!("world");
14183 }
14184 "#
14185 .unindent(),
14186 );
14187
14188 cx.update_editor(|editor, window, cx| {
14189 editor.go_to_next_hunk(&GoToHunk, window, cx);
14190 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
14191 });
14192 executor.run_until_parked();
14193 cx.assert_state_with_diff(
14194 r#"
14195 - use some::mod;
14196 + use some::modified;
14197
14198 - const A: u32 = 42;
14199 ˇ
14200 fn main() {
14201 - println!("hello");
14202 + println!("hello there");
14203
14204 + println!("around the");
14205 println!("world");
14206 }
14207 "#
14208 .unindent(),
14209 );
14210
14211 cx.update_editor(|editor, window, cx| {
14212 editor.cancel(&Cancel, window, cx);
14213 });
14214
14215 cx.assert_state_with_diff(
14216 r#"
14217 use some::modified;
14218
14219 ˇ
14220 fn main() {
14221 println!("hello there");
14222
14223 println!("around the");
14224 println!("world");
14225 }
14226 "#
14227 .unindent(),
14228 );
14229}
14230
14231#[gpui::test]
14232async fn test_diff_base_change_with_expanded_diff_hunks(
14233 executor: BackgroundExecutor,
14234 cx: &mut TestAppContext,
14235) {
14236 init_test(cx, |_| {});
14237
14238 let mut cx = EditorTestContext::new(cx).await;
14239
14240 let diff_base = r#"
14241 use some::mod1;
14242 use some::mod2;
14243
14244 const A: u32 = 42;
14245 const B: u32 = 42;
14246 const C: u32 = 42;
14247
14248 fn main() {
14249 println!("hello");
14250
14251 println!("world");
14252 }
14253 "#
14254 .unindent();
14255
14256 cx.set_state(
14257 &r#"
14258 use some::mod2;
14259
14260 const A: u32 = 42;
14261 const C: u32 = 42;
14262
14263 fn main(ˇ) {
14264 //println!("hello");
14265
14266 println!("world");
14267 //
14268 //
14269 }
14270 "#
14271 .unindent(),
14272 );
14273
14274 cx.set_head_text(&diff_base);
14275 executor.run_until_parked();
14276
14277 cx.update_editor(|editor, window, cx| {
14278 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14279 });
14280 executor.run_until_parked();
14281 cx.assert_state_with_diff(
14282 r#"
14283 - use some::mod1;
14284 use some::mod2;
14285
14286 const A: u32 = 42;
14287 - const B: u32 = 42;
14288 const C: u32 = 42;
14289
14290 fn main(ˇ) {
14291 - println!("hello");
14292 + //println!("hello");
14293
14294 println!("world");
14295 + //
14296 + //
14297 }
14298 "#
14299 .unindent(),
14300 );
14301
14302 cx.set_head_text("new diff base!");
14303 executor.run_until_parked();
14304 cx.assert_state_with_diff(
14305 r#"
14306 - new diff base!
14307 + use some::mod2;
14308 +
14309 + const A: u32 = 42;
14310 + const C: u32 = 42;
14311 +
14312 + fn main(ˇ) {
14313 + //println!("hello");
14314 +
14315 + println!("world");
14316 + //
14317 + //
14318 + }
14319 "#
14320 .unindent(),
14321 );
14322}
14323
14324#[gpui::test]
14325async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
14326 init_test(cx, |_| {});
14327
14328 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
14329 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
14330 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
14331 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
14332 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
14333 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
14334
14335 let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
14336 let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
14337 let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
14338
14339 let multi_buffer = cx.new(|cx| {
14340 let mut multibuffer = MultiBuffer::new(ReadWrite);
14341 multibuffer.push_excerpts(
14342 buffer_1.clone(),
14343 [
14344 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14345 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14346 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
14347 ],
14348 cx,
14349 );
14350 multibuffer.push_excerpts(
14351 buffer_2.clone(),
14352 [
14353 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14354 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14355 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
14356 ],
14357 cx,
14358 );
14359 multibuffer.push_excerpts(
14360 buffer_3.clone(),
14361 [
14362 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14363 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14364 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
14365 ],
14366 cx,
14367 );
14368 multibuffer
14369 });
14370
14371 let editor =
14372 cx.add_window(|window, cx| Editor::new(EditorMode::Full, multi_buffer, None, window, cx));
14373 editor
14374 .update(cx, |editor, _window, cx| {
14375 for (buffer, diff_base) in [
14376 (buffer_1.clone(), file_1_old),
14377 (buffer_2.clone(), file_2_old),
14378 (buffer_3.clone(), file_3_old),
14379 ] {
14380 let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
14381 editor
14382 .buffer
14383 .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
14384 }
14385 })
14386 .unwrap();
14387
14388 let mut cx = EditorTestContext::for_editor(editor, cx).await;
14389 cx.run_until_parked();
14390
14391 cx.assert_editor_state(
14392 &"
14393 ˇaaa
14394 ccc
14395 ddd
14396
14397 ggg
14398 hhh
14399
14400
14401 lll
14402 mmm
14403 NNN
14404
14405 qqq
14406 rrr
14407
14408 uuu
14409 111
14410 222
14411 333
14412
14413 666
14414 777
14415
14416 000
14417 !!!"
14418 .unindent(),
14419 );
14420
14421 cx.update_editor(|editor, window, cx| {
14422 editor.select_all(&SelectAll, window, cx);
14423 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
14424 });
14425 cx.executor().run_until_parked();
14426
14427 cx.assert_state_with_diff(
14428 "
14429 «aaa
14430 - bbb
14431 ccc
14432 ddd
14433
14434 ggg
14435 hhh
14436
14437
14438 lll
14439 mmm
14440 - nnn
14441 + NNN
14442
14443 qqq
14444 rrr
14445
14446 uuu
14447 111
14448 222
14449 333
14450
14451 + 666
14452 777
14453
14454 000
14455 !!!ˇ»"
14456 .unindent(),
14457 );
14458}
14459
14460#[gpui::test]
14461async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
14462 init_test(cx, |_| {});
14463
14464 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
14465 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
14466
14467 let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
14468 let multi_buffer = cx.new(|cx| {
14469 let mut multibuffer = MultiBuffer::new(ReadWrite);
14470 multibuffer.push_excerpts(
14471 buffer.clone(),
14472 [
14473 ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
14474 ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
14475 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
14476 ],
14477 cx,
14478 );
14479 multibuffer
14480 });
14481
14482 let editor =
14483 cx.add_window(|window, cx| Editor::new(EditorMode::Full, multi_buffer, None, window, cx));
14484 editor
14485 .update(cx, |editor, _window, cx| {
14486 let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
14487 editor
14488 .buffer
14489 .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
14490 })
14491 .unwrap();
14492
14493 let mut cx = EditorTestContext::for_editor(editor, cx).await;
14494 cx.run_until_parked();
14495
14496 cx.update_editor(|editor, window, cx| {
14497 editor.expand_all_diff_hunks(&Default::default(), window, cx)
14498 });
14499 cx.executor().run_until_parked();
14500
14501 // When the start of a hunk coincides with the start of its excerpt,
14502 // the hunk is expanded. When the start of a a hunk is earlier than
14503 // the start of its excerpt, the hunk is not expanded.
14504 cx.assert_state_with_diff(
14505 "
14506 ˇaaa
14507 - bbb
14508 + BBB
14509
14510 - ddd
14511 - eee
14512 + DDD
14513 + EEE
14514 fff
14515
14516 iii
14517 "
14518 .unindent(),
14519 );
14520}
14521
14522#[gpui::test]
14523async fn test_edits_around_expanded_insertion_hunks(
14524 executor: BackgroundExecutor,
14525 cx: &mut TestAppContext,
14526) {
14527 init_test(cx, |_| {});
14528
14529 let mut cx = EditorTestContext::new(cx).await;
14530
14531 let diff_base = r#"
14532 use some::mod1;
14533 use some::mod2;
14534
14535 const A: u32 = 42;
14536
14537 fn main() {
14538 println!("hello");
14539
14540 println!("world");
14541 }
14542 "#
14543 .unindent();
14544 executor.run_until_parked();
14545 cx.set_state(
14546 &r#"
14547 use some::mod1;
14548 use some::mod2;
14549
14550 const A: u32 = 42;
14551 const B: u32 = 42;
14552 const C: u32 = 42;
14553 ˇ
14554
14555 fn main() {
14556 println!("hello");
14557
14558 println!("world");
14559 }
14560 "#
14561 .unindent(),
14562 );
14563
14564 cx.set_head_text(&diff_base);
14565 executor.run_until_parked();
14566
14567 cx.update_editor(|editor, window, cx| {
14568 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14569 });
14570 executor.run_until_parked();
14571
14572 cx.assert_state_with_diff(
14573 r#"
14574 use some::mod1;
14575 use some::mod2;
14576
14577 const A: u32 = 42;
14578 + const B: u32 = 42;
14579 + const C: u32 = 42;
14580 + ˇ
14581
14582 fn main() {
14583 println!("hello");
14584
14585 println!("world");
14586 }
14587 "#
14588 .unindent(),
14589 );
14590
14591 cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
14592 executor.run_until_parked();
14593
14594 cx.assert_state_with_diff(
14595 r#"
14596 use some::mod1;
14597 use some::mod2;
14598
14599 const A: u32 = 42;
14600 + const B: u32 = 42;
14601 + const C: u32 = 42;
14602 + const D: u32 = 42;
14603 + ˇ
14604
14605 fn main() {
14606 println!("hello");
14607
14608 println!("world");
14609 }
14610 "#
14611 .unindent(),
14612 );
14613
14614 cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
14615 executor.run_until_parked();
14616
14617 cx.assert_state_with_diff(
14618 r#"
14619 use some::mod1;
14620 use some::mod2;
14621
14622 const A: u32 = 42;
14623 + const B: u32 = 42;
14624 + const C: u32 = 42;
14625 + const D: u32 = 42;
14626 + const E: u32 = 42;
14627 + ˇ
14628
14629 fn main() {
14630 println!("hello");
14631
14632 println!("world");
14633 }
14634 "#
14635 .unindent(),
14636 );
14637
14638 cx.update_editor(|editor, window, cx| {
14639 editor.delete_line(&DeleteLine, window, cx);
14640 });
14641 executor.run_until_parked();
14642
14643 cx.assert_state_with_diff(
14644 r#"
14645 use some::mod1;
14646 use some::mod2;
14647
14648 const A: u32 = 42;
14649 + const B: u32 = 42;
14650 + const C: u32 = 42;
14651 + const D: u32 = 42;
14652 + const E: u32 = 42;
14653 ˇ
14654 fn main() {
14655 println!("hello");
14656
14657 println!("world");
14658 }
14659 "#
14660 .unindent(),
14661 );
14662
14663 cx.update_editor(|editor, window, cx| {
14664 editor.move_up(&MoveUp, window, cx);
14665 editor.delete_line(&DeleteLine, window, cx);
14666 editor.move_up(&MoveUp, window, cx);
14667 editor.delete_line(&DeleteLine, window, cx);
14668 editor.move_up(&MoveUp, window, cx);
14669 editor.delete_line(&DeleteLine, window, cx);
14670 });
14671 executor.run_until_parked();
14672 cx.assert_state_with_diff(
14673 r#"
14674 use some::mod1;
14675 use some::mod2;
14676
14677 const A: u32 = 42;
14678 + const B: u32 = 42;
14679 ˇ
14680 fn main() {
14681 println!("hello");
14682
14683 println!("world");
14684 }
14685 "#
14686 .unindent(),
14687 );
14688
14689 cx.update_editor(|editor, window, cx| {
14690 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
14691 editor.delete_line(&DeleteLine, window, cx);
14692 });
14693 executor.run_until_parked();
14694 cx.assert_state_with_diff(
14695 r#"
14696 ˇ
14697 fn main() {
14698 println!("hello");
14699
14700 println!("world");
14701 }
14702 "#
14703 .unindent(),
14704 );
14705}
14706
14707#[gpui::test]
14708async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
14709 init_test(cx, |_| {});
14710
14711 let mut cx = EditorTestContext::new(cx).await;
14712 cx.set_head_text(indoc! { "
14713 one
14714 two
14715 three
14716 four
14717 five
14718 "
14719 });
14720 cx.set_state(indoc! { "
14721 one
14722 ˇthree
14723 five
14724 "});
14725 cx.run_until_parked();
14726 cx.update_editor(|editor, window, cx| {
14727 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14728 });
14729 cx.assert_state_with_diff(
14730 indoc! { "
14731 one
14732 - two
14733 ˇthree
14734 - four
14735 five
14736 "}
14737 .to_string(),
14738 );
14739 cx.update_editor(|editor, window, cx| {
14740 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14741 });
14742
14743 cx.assert_state_with_diff(
14744 indoc! { "
14745 one
14746 ˇthree
14747 five
14748 "}
14749 .to_string(),
14750 );
14751
14752 cx.set_state(indoc! { "
14753 one
14754 ˇTWO
14755 three
14756 four
14757 five
14758 "});
14759 cx.run_until_parked();
14760 cx.update_editor(|editor, window, cx| {
14761 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14762 });
14763
14764 cx.assert_state_with_diff(
14765 indoc! { "
14766 one
14767 - two
14768 + ˇTWO
14769 three
14770 four
14771 five
14772 "}
14773 .to_string(),
14774 );
14775 cx.update_editor(|editor, window, cx| {
14776 editor.move_up(&Default::default(), window, cx);
14777 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14778 });
14779 cx.assert_state_with_diff(
14780 indoc! { "
14781 one
14782 ˇTWO
14783 three
14784 four
14785 five
14786 "}
14787 .to_string(),
14788 );
14789}
14790
14791#[gpui::test]
14792async fn test_edits_around_expanded_deletion_hunks(
14793 executor: BackgroundExecutor,
14794 cx: &mut TestAppContext,
14795) {
14796 init_test(cx, |_| {});
14797
14798 let mut cx = EditorTestContext::new(cx).await;
14799
14800 let diff_base = r#"
14801 use some::mod1;
14802 use some::mod2;
14803
14804 const A: u32 = 42;
14805 const B: u32 = 42;
14806 const C: u32 = 42;
14807
14808
14809 fn main() {
14810 println!("hello");
14811
14812 println!("world");
14813 }
14814 "#
14815 .unindent();
14816 executor.run_until_parked();
14817 cx.set_state(
14818 &r#"
14819 use some::mod1;
14820 use some::mod2;
14821
14822 ˇconst B: u32 = 42;
14823 const C: u32 = 42;
14824
14825
14826 fn main() {
14827 println!("hello");
14828
14829 println!("world");
14830 }
14831 "#
14832 .unindent(),
14833 );
14834
14835 cx.set_head_text(&diff_base);
14836 executor.run_until_parked();
14837
14838 cx.update_editor(|editor, window, cx| {
14839 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14840 });
14841 executor.run_until_parked();
14842
14843 cx.assert_state_with_diff(
14844 r#"
14845 use some::mod1;
14846 use some::mod2;
14847
14848 - const A: u32 = 42;
14849 ˇconst B: u32 = 42;
14850 const C: u32 = 42;
14851
14852
14853 fn main() {
14854 println!("hello");
14855
14856 println!("world");
14857 }
14858 "#
14859 .unindent(),
14860 );
14861
14862 cx.update_editor(|editor, window, cx| {
14863 editor.delete_line(&DeleteLine, window, cx);
14864 });
14865 executor.run_until_parked();
14866 cx.assert_state_with_diff(
14867 r#"
14868 use some::mod1;
14869 use some::mod2;
14870
14871 - const A: u32 = 42;
14872 - const B: u32 = 42;
14873 ˇconst C: u32 = 42;
14874
14875
14876 fn main() {
14877 println!("hello");
14878
14879 println!("world");
14880 }
14881 "#
14882 .unindent(),
14883 );
14884
14885 cx.update_editor(|editor, window, cx| {
14886 editor.delete_line(&DeleteLine, window, cx);
14887 });
14888 executor.run_until_parked();
14889 cx.assert_state_with_diff(
14890 r#"
14891 use some::mod1;
14892 use some::mod2;
14893
14894 - const A: u32 = 42;
14895 - const B: u32 = 42;
14896 - const C: u32 = 42;
14897 ˇ
14898
14899 fn main() {
14900 println!("hello");
14901
14902 println!("world");
14903 }
14904 "#
14905 .unindent(),
14906 );
14907
14908 cx.update_editor(|editor, window, cx| {
14909 editor.handle_input("replacement", window, cx);
14910 });
14911 executor.run_until_parked();
14912 cx.assert_state_with_diff(
14913 r#"
14914 use some::mod1;
14915 use some::mod2;
14916
14917 - const A: u32 = 42;
14918 - const B: u32 = 42;
14919 - const C: u32 = 42;
14920 -
14921 + replacementˇ
14922
14923 fn main() {
14924 println!("hello");
14925
14926 println!("world");
14927 }
14928 "#
14929 .unindent(),
14930 );
14931}
14932
14933#[gpui::test]
14934async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14935 init_test(cx, |_| {});
14936
14937 let mut cx = EditorTestContext::new(cx).await;
14938
14939 let base_text = r#"
14940 one
14941 two
14942 three
14943 four
14944 five
14945 "#
14946 .unindent();
14947 executor.run_until_parked();
14948 cx.set_state(
14949 &r#"
14950 one
14951 two
14952 fˇour
14953 five
14954 "#
14955 .unindent(),
14956 );
14957
14958 cx.set_head_text(&base_text);
14959 executor.run_until_parked();
14960
14961 cx.update_editor(|editor, window, cx| {
14962 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14963 });
14964 executor.run_until_parked();
14965
14966 cx.assert_state_with_diff(
14967 r#"
14968 one
14969 two
14970 - three
14971 fˇour
14972 five
14973 "#
14974 .unindent(),
14975 );
14976
14977 cx.update_editor(|editor, window, cx| {
14978 editor.backspace(&Backspace, window, cx);
14979 editor.backspace(&Backspace, window, cx);
14980 });
14981 executor.run_until_parked();
14982 cx.assert_state_with_diff(
14983 r#"
14984 one
14985 two
14986 - threeˇ
14987 - four
14988 + our
14989 five
14990 "#
14991 .unindent(),
14992 );
14993}
14994
14995#[gpui::test]
14996async fn test_edit_after_expanded_modification_hunk(
14997 executor: BackgroundExecutor,
14998 cx: &mut TestAppContext,
14999) {
15000 init_test(cx, |_| {});
15001
15002 let mut cx = EditorTestContext::new(cx).await;
15003
15004 let diff_base = r#"
15005 use some::mod1;
15006 use some::mod2;
15007
15008 const A: u32 = 42;
15009 const B: u32 = 42;
15010 const C: u32 = 42;
15011 const D: u32 = 42;
15012
15013
15014 fn main() {
15015 println!("hello");
15016
15017 println!("world");
15018 }"#
15019 .unindent();
15020
15021 cx.set_state(
15022 &r#"
15023 use some::mod1;
15024 use some::mod2;
15025
15026 const A: u32 = 42;
15027 const B: u32 = 42;
15028 const C: u32 = 43ˇ
15029 const D: u32 = 42;
15030
15031
15032 fn main() {
15033 println!("hello");
15034
15035 println!("world");
15036 }"#
15037 .unindent(),
15038 );
15039
15040 cx.set_head_text(&diff_base);
15041 executor.run_until_parked();
15042 cx.update_editor(|editor, window, cx| {
15043 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15044 });
15045 executor.run_until_parked();
15046
15047 cx.assert_state_with_diff(
15048 r#"
15049 use some::mod1;
15050 use some::mod2;
15051
15052 const A: u32 = 42;
15053 const B: u32 = 42;
15054 - const C: u32 = 42;
15055 + const C: u32 = 43ˇ
15056 const D: u32 = 42;
15057
15058
15059 fn main() {
15060 println!("hello");
15061
15062 println!("world");
15063 }"#
15064 .unindent(),
15065 );
15066
15067 cx.update_editor(|editor, window, cx| {
15068 editor.handle_input("\nnew_line\n", window, cx);
15069 });
15070 executor.run_until_parked();
15071
15072 cx.assert_state_with_diff(
15073 r#"
15074 use some::mod1;
15075 use some::mod2;
15076
15077 const A: u32 = 42;
15078 const B: u32 = 42;
15079 - const C: u32 = 42;
15080 + const C: u32 = 43
15081 + new_line
15082 + ˇ
15083 const D: u32 = 42;
15084
15085
15086 fn main() {
15087 println!("hello");
15088
15089 println!("world");
15090 }"#
15091 .unindent(),
15092 );
15093}
15094
15095#[gpui::test]
15096async fn test_stage_and_unstage_added_file_hunk(
15097 executor: BackgroundExecutor,
15098 cx: &mut TestAppContext,
15099) {
15100 init_test(cx, |_| {});
15101
15102 let mut cx = EditorTestContext::new(cx).await;
15103 cx.update_editor(|editor, _, cx| {
15104 editor.set_expand_all_diff_hunks(cx);
15105 });
15106
15107 let working_copy = r#"
15108 ˇfn main() {
15109 println!("hello, world!");
15110 }
15111 "#
15112 .unindent();
15113
15114 cx.set_state(&working_copy);
15115 executor.run_until_parked();
15116
15117 cx.assert_state_with_diff(
15118 r#"
15119 + ˇfn main() {
15120 + println!("hello, world!");
15121 + }
15122 "#
15123 .unindent(),
15124 );
15125 cx.assert_index_text(None);
15126
15127 cx.update_editor(|editor, window, cx| {
15128 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
15129 });
15130 executor.run_until_parked();
15131 cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
15132 cx.assert_state_with_diff(
15133 r#"
15134 + ˇfn main() {
15135 + println!("hello, world!");
15136 + }
15137 "#
15138 .unindent(),
15139 );
15140
15141 cx.update_editor(|editor, window, cx| {
15142 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
15143 });
15144 executor.run_until_parked();
15145 cx.assert_index_text(None);
15146}
15147
15148async fn setup_indent_guides_editor(
15149 text: &str,
15150 cx: &mut TestAppContext,
15151) -> (BufferId, EditorTestContext) {
15152 init_test(cx, |_| {});
15153
15154 let mut cx = EditorTestContext::new(cx).await;
15155
15156 let buffer_id = cx.update_editor(|editor, window, cx| {
15157 editor.set_text(text, window, cx);
15158 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
15159
15160 buffer_ids[0]
15161 });
15162
15163 (buffer_id, cx)
15164}
15165
15166fn assert_indent_guides(
15167 range: Range<u32>,
15168 expected: Vec<IndentGuide>,
15169 active_indices: Option<Vec<usize>>,
15170 cx: &mut EditorTestContext,
15171) {
15172 let indent_guides = cx.update_editor(|editor, window, cx| {
15173 let snapshot = editor.snapshot(window, cx).display_snapshot;
15174 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
15175 editor,
15176 MultiBufferRow(range.start)..MultiBufferRow(range.end),
15177 true,
15178 &snapshot,
15179 cx,
15180 );
15181
15182 indent_guides.sort_by(|a, b| {
15183 a.depth.cmp(&b.depth).then(
15184 a.start_row
15185 .cmp(&b.start_row)
15186 .then(a.end_row.cmp(&b.end_row)),
15187 )
15188 });
15189 indent_guides
15190 });
15191
15192 if let Some(expected) = active_indices {
15193 let active_indices = cx.update_editor(|editor, window, cx| {
15194 let snapshot = editor.snapshot(window, cx).display_snapshot;
15195 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
15196 });
15197
15198 assert_eq!(
15199 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
15200 expected,
15201 "Active indent guide indices do not match"
15202 );
15203 }
15204
15205 assert_eq!(indent_guides, expected, "Indent guides do not match");
15206}
15207
15208fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
15209 IndentGuide {
15210 buffer_id,
15211 start_row: MultiBufferRow(start_row),
15212 end_row: MultiBufferRow(end_row),
15213 depth,
15214 tab_size: 4,
15215 settings: IndentGuideSettings {
15216 enabled: true,
15217 line_width: 1,
15218 active_line_width: 1,
15219 ..Default::default()
15220 },
15221 }
15222}
15223
15224#[gpui::test]
15225async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
15226 let (buffer_id, mut cx) = setup_indent_guides_editor(
15227 &"
15228 fn main() {
15229 let a = 1;
15230 }"
15231 .unindent(),
15232 cx,
15233 )
15234 .await;
15235
15236 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
15237}
15238
15239#[gpui::test]
15240async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
15241 let (buffer_id, mut cx) = setup_indent_guides_editor(
15242 &"
15243 fn main() {
15244 let a = 1;
15245 let b = 2;
15246 }"
15247 .unindent(),
15248 cx,
15249 )
15250 .await;
15251
15252 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
15253}
15254
15255#[gpui::test]
15256async fn test_indent_guide_nested(cx: &mut TestAppContext) {
15257 let (buffer_id, mut cx) = setup_indent_guides_editor(
15258 &"
15259 fn main() {
15260 let a = 1;
15261 if a == 3 {
15262 let b = 2;
15263 } else {
15264 let c = 3;
15265 }
15266 }"
15267 .unindent(),
15268 cx,
15269 )
15270 .await;
15271
15272 assert_indent_guides(
15273 0..8,
15274 vec![
15275 indent_guide(buffer_id, 1, 6, 0),
15276 indent_guide(buffer_id, 3, 3, 1),
15277 indent_guide(buffer_id, 5, 5, 1),
15278 ],
15279 None,
15280 &mut cx,
15281 );
15282}
15283
15284#[gpui::test]
15285async fn test_indent_guide_tab(cx: &mut TestAppContext) {
15286 let (buffer_id, mut cx) = setup_indent_guides_editor(
15287 &"
15288 fn main() {
15289 let a = 1;
15290 let b = 2;
15291 let c = 3;
15292 }"
15293 .unindent(),
15294 cx,
15295 )
15296 .await;
15297
15298 assert_indent_guides(
15299 0..5,
15300 vec![
15301 indent_guide(buffer_id, 1, 3, 0),
15302 indent_guide(buffer_id, 2, 2, 1),
15303 ],
15304 None,
15305 &mut cx,
15306 );
15307}
15308
15309#[gpui::test]
15310async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
15311 let (buffer_id, mut cx) = setup_indent_guides_editor(
15312 &"
15313 fn main() {
15314 let a = 1;
15315
15316 let c = 3;
15317 }"
15318 .unindent(),
15319 cx,
15320 )
15321 .await;
15322
15323 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
15324}
15325
15326#[gpui::test]
15327async fn test_indent_guide_complex(cx: &mut TestAppContext) {
15328 let (buffer_id, mut cx) = setup_indent_guides_editor(
15329 &"
15330 fn main() {
15331 let a = 1;
15332
15333 let c = 3;
15334
15335 if a == 3 {
15336 let b = 2;
15337 } else {
15338 let c = 3;
15339 }
15340 }"
15341 .unindent(),
15342 cx,
15343 )
15344 .await;
15345
15346 assert_indent_guides(
15347 0..11,
15348 vec![
15349 indent_guide(buffer_id, 1, 9, 0),
15350 indent_guide(buffer_id, 6, 6, 1),
15351 indent_guide(buffer_id, 8, 8, 1),
15352 ],
15353 None,
15354 &mut cx,
15355 );
15356}
15357
15358#[gpui::test]
15359async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
15360 let (buffer_id, mut cx) = setup_indent_guides_editor(
15361 &"
15362 fn main() {
15363 let a = 1;
15364
15365 let c = 3;
15366
15367 if a == 3 {
15368 let b = 2;
15369 } else {
15370 let c = 3;
15371 }
15372 }"
15373 .unindent(),
15374 cx,
15375 )
15376 .await;
15377
15378 assert_indent_guides(
15379 1..11,
15380 vec![
15381 indent_guide(buffer_id, 1, 9, 0),
15382 indent_guide(buffer_id, 6, 6, 1),
15383 indent_guide(buffer_id, 8, 8, 1),
15384 ],
15385 None,
15386 &mut cx,
15387 );
15388}
15389
15390#[gpui::test]
15391async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
15392 let (buffer_id, mut cx) = setup_indent_guides_editor(
15393 &"
15394 fn main() {
15395 let a = 1;
15396
15397 let c = 3;
15398
15399 if a == 3 {
15400 let b = 2;
15401 } else {
15402 let c = 3;
15403 }
15404 }"
15405 .unindent(),
15406 cx,
15407 )
15408 .await;
15409
15410 assert_indent_guides(
15411 1..10,
15412 vec![
15413 indent_guide(buffer_id, 1, 9, 0),
15414 indent_guide(buffer_id, 6, 6, 1),
15415 indent_guide(buffer_id, 8, 8, 1),
15416 ],
15417 None,
15418 &mut cx,
15419 );
15420}
15421
15422#[gpui::test]
15423async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
15424 let (buffer_id, mut cx) = setup_indent_guides_editor(
15425 &"
15426 block1
15427 block2
15428 block3
15429 block4
15430 block2
15431 block1
15432 block1"
15433 .unindent(),
15434 cx,
15435 )
15436 .await;
15437
15438 assert_indent_guides(
15439 1..10,
15440 vec![
15441 indent_guide(buffer_id, 1, 4, 0),
15442 indent_guide(buffer_id, 2, 3, 1),
15443 indent_guide(buffer_id, 3, 3, 2),
15444 ],
15445 None,
15446 &mut cx,
15447 );
15448}
15449
15450#[gpui::test]
15451async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
15452 let (buffer_id, mut cx) = setup_indent_guides_editor(
15453 &"
15454 block1
15455 block2
15456 block3
15457
15458 block1
15459 block1"
15460 .unindent(),
15461 cx,
15462 )
15463 .await;
15464
15465 assert_indent_guides(
15466 0..6,
15467 vec![
15468 indent_guide(buffer_id, 1, 2, 0),
15469 indent_guide(buffer_id, 2, 2, 1),
15470 ],
15471 None,
15472 &mut cx,
15473 );
15474}
15475
15476#[gpui::test]
15477async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
15478 let (buffer_id, mut cx) = setup_indent_guides_editor(
15479 &"
15480 block1
15481
15482
15483
15484 block2
15485 "
15486 .unindent(),
15487 cx,
15488 )
15489 .await;
15490
15491 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
15492}
15493
15494#[gpui::test]
15495async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
15496 let (buffer_id, mut cx) = setup_indent_guides_editor(
15497 &"
15498 def a:
15499 \tb = 3
15500 \tif True:
15501 \t\tc = 4
15502 \t\td = 5
15503 \tprint(b)
15504 "
15505 .unindent(),
15506 cx,
15507 )
15508 .await;
15509
15510 assert_indent_guides(
15511 0..6,
15512 vec![
15513 indent_guide(buffer_id, 1, 6, 0),
15514 indent_guide(buffer_id, 3, 4, 1),
15515 ],
15516 None,
15517 &mut cx,
15518 );
15519}
15520
15521#[gpui::test]
15522async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
15523 let (buffer_id, mut cx) = setup_indent_guides_editor(
15524 &"
15525 fn main() {
15526 let a = 1;
15527 }"
15528 .unindent(),
15529 cx,
15530 )
15531 .await;
15532
15533 cx.update_editor(|editor, window, cx| {
15534 editor.change_selections(None, window, cx, |s| {
15535 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
15536 });
15537 });
15538
15539 assert_indent_guides(
15540 0..3,
15541 vec![indent_guide(buffer_id, 1, 1, 0)],
15542 Some(vec![0]),
15543 &mut cx,
15544 );
15545}
15546
15547#[gpui::test]
15548async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
15549 let (buffer_id, mut cx) = setup_indent_guides_editor(
15550 &"
15551 fn main() {
15552 if 1 == 2 {
15553 let a = 1;
15554 }
15555 }"
15556 .unindent(),
15557 cx,
15558 )
15559 .await;
15560
15561 cx.update_editor(|editor, window, cx| {
15562 editor.change_selections(None, window, cx, |s| {
15563 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
15564 });
15565 });
15566
15567 assert_indent_guides(
15568 0..4,
15569 vec![
15570 indent_guide(buffer_id, 1, 3, 0),
15571 indent_guide(buffer_id, 2, 2, 1),
15572 ],
15573 Some(vec![1]),
15574 &mut cx,
15575 );
15576
15577 cx.update_editor(|editor, window, cx| {
15578 editor.change_selections(None, window, cx, |s| {
15579 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
15580 });
15581 });
15582
15583 assert_indent_guides(
15584 0..4,
15585 vec![
15586 indent_guide(buffer_id, 1, 3, 0),
15587 indent_guide(buffer_id, 2, 2, 1),
15588 ],
15589 Some(vec![1]),
15590 &mut cx,
15591 );
15592
15593 cx.update_editor(|editor, window, cx| {
15594 editor.change_selections(None, window, cx, |s| {
15595 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
15596 });
15597 });
15598
15599 assert_indent_guides(
15600 0..4,
15601 vec![
15602 indent_guide(buffer_id, 1, 3, 0),
15603 indent_guide(buffer_id, 2, 2, 1),
15604 ],
15605 Some(vec![0]),
15606 &mut cx,
15607 );
15608}
15609
15610#[gpui::test]
15611async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
15612 let (buffer_id, mut cx) = setup_indent_guides_editor(
15613 &"
15614 fn main() {
15615 let a = 1;
15616
15617 let b = 2;
15618 }"
15619 .unindent(),
15620 cx,
15621 )
15622 .await;
15623
15624 cx.update_editor(|editor, window, cx| {
15625 editor.change_selections(None, window, cx, |s| {
15626 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
15627 });
15628 });
15629
15630 assert_indent_guides(
15631 0..5,
15632 vec![indent_guide(buffer_id, 1, 3, 0)],
15633 Some(vec![0]),
15634 &mut cx,
15635 );
15636}
15637
15638#[gpui::test]
15639async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
15640 let (buffer_id, mut cx) = setup_indent_guides_editor(
15641 &"
15642 def m:
15643 a = 1
15644 pass"
15645 .unindent(),
15646 cx,
15647 )
15648 .await;
15649
15650 cx.update_editor(|editor, window, cx| {
15651 editor.change_selections(None, window, cx, |s| {
15652 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
15653 });
15654 });
15655
15656 assert_indent_guides(
15657 0..3,
15658 vec![indent_guide(buffer_id, 1, 2, 0)],
15659 Some(vec![0]),
15660 &mut cx,
15661 );
15662}
15663
15664#[gpui::test]
15665async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
15666 init_test(cx, |_| {});
15667 let mut cx = EditorTestContext::new(cx).await;
15668 let text = indoc! {
15669 "
15670 impl A {
15671 fn b() {
15672 0;
15673 3;
15674 5;
15675 6;
15676 7;
15677 }
15678 }
15679 "
15680 };
15681 let base_text = indoc! {
15682 "
15683 impl A {
15684 fn b() {
15685 0;
15686 1;
15687 2;
15688 3;
15689 4;
15690 }
15691 fn c() {
15692 5;
15693 6;
15694 7;
15695 }
15696 }
15697 "
15698 };
15699
15700 cx.update_editor(|editor, window, cx| {
15701 editor.set_text(text, window, cx);
15702
15703 editor.buffer().update(cx, |multibuffer, cx| {
15704 let buffer = multibuffer.as_singleton().unwrap();
15705 let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
15706
15707 multibuffer.set_all_diff_hunks_expanded(cx);
15708 multibuffer.add_diff(diff, cx);
15709
15710 buffer.read(cx).remote_id()
15711 })
15712 });
15713 cx.run_until_parked();
15714
15715 cx.assert_state_with_diff(
15716 indoc! { "
15717 impl A {
15718 fn b() {
15719 0;
15720 - 1;
15721 - 2;
15722 3;
15723 - 4;
15724 - }
15725 - fn c() {
15726 5;
15727 6;
15728 7;
15729 }
15730 }
15731 ˇ"
15732 }
15733 .to_string(),
15734 );
15735
15736 let mut actual_guides = cx.update_editor(|editor, window, cx| {
15737 editor
15738 .snapshot(window, cx)
15739 .buffer_snapshot
15740 .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
15741 .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
15742 .collect::<Vec<_>>()
15743 });
15744 actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
15745 assert_eq!(
15746 actual_guides,
15747 vec![
15748 (MultiBufferRow(1)..=MultiBufferRow(12), 0),
15749 (MultiBufferRow(2)..=MultiBufferRow(6), 1),
15750 (MultiBufferRow(9)..=MultiBufferRow(11), 1),
15751 ]
15752 );
15753}
15754
15755#[gpui::test]
15756async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15757 init_test(cx, |_| {});
15758 let mut cx = EditorTestContext::new(cx).await;
15759
15760 let diff_base = r#"
15761 a
15762 b
15763 c
15764 "#
15765 .unindent();
15766
15767 cx.set_state(
15768 &r#"
15769 ˇA
15770 b
15771 C
15772 "#
15773 .unindent(),
15774 );
15775 cx.set_head_text(&diff_base);
15776 cx.update_editor(|editor, window, cx| {
15777 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15778 });
15779 executor.run_until_parked();
15780
15781 let both_hunks_expanded = r#"
15782 - a
15783 + ˇA
15784 b
15785 - c
15786 + C
15787 "#
15788 .unindent();
15789
15790 cx.assert_state_with_diff(both_hunks_expanded.clone());
15791
15792 let hunk_ranges = cx.update_editor(|editor, window, cx| {
15793 let snapshot = editor.snapshot(window, cx);
15794 let hunks = editor
15795 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15796 .collect::<Vec<_>>();
15797 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15798 let buffer_id = hunks[0].buffer_id;
15799 hunks
15800 .into_iter()
15801 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15802 .collect::<Vec<_>>()
15803 });
15804 assert_eq!(hunk_ranges.len(), 2);
15805
15806 cx.update_editor(|editor, _, cx| {
15807 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15808 });
15809 executor.run_until_parked();
15810
15811 let second_hunk_expanded = r#"
15812 ˇA
15813 b
15814 - c
15815 + C
15816 "#
15817 .unindent();
15818
15819 cx.assert_state_with_diff(second_hunk_expanded);
15820
15821 cx.update_editor(|editor, _, cx| {
15822 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15823 });
15824 executor.run_until_parked();
15825
15826 cx.assert_state_with_diff(both_hunks_expanded.clone());
15827
15828 cx.update_editor(|editor, _, cx| {
15829 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15830 });
15831 executor.run_until_parked();
15832
15833 let first_hunk_expanded = r#"
15834 - a
15835 + ˇA
15836 b
15837 C
15838 "#
15839 .unindent();
15840
15841 cx.assert_state_with_diff(first_hunk_expanded);
15842
15843 cx.update_editor(|editor, _, cx| {
15844 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15845 });
15846 executor.run_until_parked();
15847
15848 cx.assert_state_with_diff(both_hunks_expanded);
15849
15850 cx.set_state(
15851 &r#"
15852 ˇA
15853 b
15854 "#
15855 .unindent(),
15856 );
15857 cx.run_until_parked();
15858
15859 // TODO this cursor position seems bad
15860 cx.assert_state_with_diff(
15861 r#"
15862 - ˇa
15863 + A
15864 b
15865 "#
15866 .unindent(),
15867 );
15868
15869 cx.update_editor(|editor, window, cx| {
15870 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15871 });
15872
15873 cx.assert_state_with_diff(
15874 r#"
15875 - ˇa
15876 + A
15877 b
15878 - c
15879 "#
15880 .unindent(),
15881 );
15882
15883 let hunk_ranges = cx.update_editor(|editor, window, cx| {
15884 let snapshot = editor.snapshot(window, cx);
15885 let hunks = editor
15886 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15887 .collect::<Vec<_>>();
15888 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15889 let buffer_id = hunks[0].buffer_id;
15890 hunks
15891 .into_iter()
15892 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15893 .collect::<Vec<_>>()
15894 });
15895 assert_eq!(hunk_ranges.len(), 2);
15896
15897 cx.update_editor(|editor, _, cx| {
15898 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15899 });
15900 executor.run_until_parked();
15901
15902 cx.assert_state_with_diff(
15903 r#"
15904 - ˇa
15905 + A
15906 b
15907 "#
15908 .unindent(),
15909 );
15910}
15911
15912#[gpui::test]
15913async fn test_toggle_deletion_hunk_at_start_of_file(
15914 executor: BackgroundExecutor,
15915 cx: &mut TestAppContext,
15916) {
15917 init_test(cx, |_| {});
15918 let mut cx = EditorTestContext::new(cx).await;
15919
15920 let diff_base = r#"
15921 a
15922 b
15923 c
15924 "#
15925 .unindent();
15926
15927 cx.set_state(
15928 &r#"
15929 ˇb
15930 c
15931 "#
15932 .unindent(),
15933 );
15934 cx.set_head_text(&diff_base);
15935 cx.update_editor(|editor, window, cx| {
15936 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15937 });
15938 executor.run_until_parked();
15939
15940 let hunk_expanded = r#"
15941 - a
15942 ˇb
15943 c
15944 "#
15945 .unindent();
15946
15947 cx.assert_state_with_diff(hunk_expanded.clone());
15948
15949 let hunk_ranges = cx.update_editor(|editor, window, cx| {
15950 let snapshot = editor.snapshot(window, cx);
15951 let hunks = editor
15952 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15953 .collect::<Vec<_>>();
15954 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15955 let buffer_id = hunks[0].buffer_id;
15956 hunks
15957 .into_iter()
15958 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15959 .collect::<Vec<_>>()
15960 });
15961 assert_eq!(hunk_ranges.len(), 1);
15962
15963 cx.update_editor(|editor, _, cx| {
15964 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15965 });
15966 executor.run_until_parked();
15967
15968 let hunk_collapsed = r#"
15969 ˇb
15970 c
15971 "#
15972 .unindent();
15973
15974 cx.assert_state_with_diff(hunk_collapsed);
15975
15976 cx.update_editor(|editor, _, cx| {
15977 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15978 });
15979 executor.run_until_parked();
15980
15981 cx.assert_state_with_diff(hunk_expanded.clone());
15982}
15983
15984#[gpui::test]
15985async fn test_display_diff_hunks(cx: &mut TestAppContext) {
15986 init_test(cx, |_| {});
15987
15988 let fs = FakeFs::new(cx.executor());
15989 fs.insert_tree(
15990 path!("/test"),
15991 json!({
15992 ".git": {},
15993 "file-1": "ONE\n",
15994 "file-2": "TWO\n",
15995 "file-3": "THREE\n",
15996 }),
15997 )
15998 .await;
15999
16000 fs.set_head_for_repo(
16001 path!("/test/.git").as_ref(),
16002 &[
16003 ("file-1".into(), "one\n".into()),
16004 ("file-2".into(), "two\n".into()),
16005 ("file-3".into(), "three\n".into()),
16006 ],
16007 );
16008
16009 let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
16010 let mut buffers = vec![];
16011 for i in 1..=3 {
16012 let buffer = project
16013 .update(cx, |project, cx| {
16014 let path = format!(path!("/test/file-{}"), i);
16015 project.open_local_buffer(path, cx)
16016 })
16017 .await
16018 .unwrap();
16019 buffers.push(buffer);
16020 }
16021
16022 let multibuffer = cx.new(|cx| {
16023 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
16024 multibuffer.set_all_diff_hunks_expanded(cx);
16025 for buffer in &buffers {
16026 let snapshot = buffer.read(cx).snapshot();
16027 multibuffer.set_excerpts_for_path(
16028 PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
16029 buffer.clone(),
16030 vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
16031 DEFAULT_MULTIBUFFER_CONTEXT,
16032 cx,
16033 );
16034 }
16035 multibuffer
16036 });
16037
16038 let editor = cx.add_window(|window, cx| {
16039 Editor::new(EditorMode::Full, multibuffer, Some(project), window, cx)
16040 });
16041 cx.run_until_parked();
16042
16043 let snapshot = editor
16044 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
16045 .unwrap();
16046 let hunks = snapshot
16047 .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
16048 .map(|hunk| match hunk {
16049 DisplayDiffHunk::Unfolded {
16050 display_row_range, ..
16051 } => display_row_range,
16052 DisplayDiffHunk::Folded { .. } => unreachable!(),
16053 })
16054 .collect::<Vec<_>>();
16055 assert_eq!(
16056 hunks,
16057 [
16058 DisplayRow(2)..DisplayRow(4),
16059 DisplayRow(7)..DisplayRow(9),
16060 DisplayRow(12)..DisplayRow(14),
16061 ]
16062 );
16063}
16064
16065#[gpui::test]
16066async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
16067 init_test(cx, |_| {});
16068
16069 let mut cx = EditorTestContext::new(cx).await;
16070 cx.set_head_text(indoc! { "
16071 one
16072 two
16073 three
16074 four
16075 five
16076 "
16077 });
16078 cx.set_index_text(indoc! { "
16079 one
16080 two
16081 three
16082 four
16083 five
16084 "
16085 });
16086 cx.set_state(indoc! {"
16087 one
16088 TWO
16089 ˇTHREE
16090 FOUR
16091 five
16092 "});
16093 cx.run_until_parked();
16094 cx.update_editor(|editor, window, cx| {
16095 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
16096 });
16097 cx.run_until_parked();
16098 cx.assert_index_text(Some(indoc! {"
16099 one
16100 TWO
16101 THREE
16102 FOUR
16103 five
16104 "}));
16105 cx.set_state(indoc! { "
16106 one
16107 TWO
16108 ˇTHREE-HUNDRED
16109 FOUR
16110 five
16111 "});
16112 cx.run_until_parked();
16113 cx.update_editor(|editor, window, cx| {
16114 let snapshot = editor.snapshot(window, cx);
16115 let hunks = editor
16116 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
16117 .collect::<Vec<_>>();
16118 assert_eq!(hunks.len(), 1);
16119 assert_eq!(
16120 hunks[0].status(),
16121 DiffHunkStatus {
16122 kind: DiffHunkStatusKind::Modified,
16123 secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
16124 }
16125 );
16126
16127 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
16128 });
16129 cx.run_until_parked();
16130 cx.assert_index_text(Some(indoc! {"
16131 one
16132 TWO
16133 THREE-HUNDRED
16134 FOUR
16135 five
16136 "}));
16137}
16138
16139#[gpui::test]
16140fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
16141 init_test(cx, |_| {});
16142
16143 let editor = cx.add_window(|window, cx| {
16144 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
16145 build_editor(buffer, window, cx)
16146 });
16147
16148 let render_args = Arc::new(Mutex::new(None));
16149 let snapshot = editor
16150 .update(cx, |editor, window, cx| {
16151 let snapshot = editor.buffer().read(cx).snapshot(cx);
16152 let range =
16153 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
16154
16155 struct RenderArgs {
16156 row: MultiBufferRow,
16157 folded: bool,
16158 callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
16159 }
16160
16161 let crease = Crease::inline(
16162 range,
16163 FoldPlaceholder::test(),
16164 {
16165 let toggle_callback = render_args.clone();
16166 move |row, folded, callback, _window, _cx| {
16167 *toggle_callback.lock() = Some(RenderArgs {
16168 row,
16169 folded,
16170 callback,
16171 });
16172 div()
16173 }
16174 },
16175 |_row, _folded, _window, _cx| div(),
16176 );
16177
16178 editor.insert_creases(Some(crease), cx);
16179 let snapshot = editor.snapshot(window, cx);
16180 let _div = snapshot.render_crease_toggle(
16181 MultiBufferRow(1),
16182 false,
16183 cx.entity().clone(),
16184 window,
16185 cx,
16186 );
16187 snapshot
16188 })
16189 .unwrap();
16190
16191 let render_args = render_args.lock().take().unwrap();
16192 assert_eq!(render_args.row, MultiBufferRow(1));
16193 assert!(!render_args.folded);
16194 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
16195
16196 cx.update_window(*editor, |_, window, cx| {
16197 (render_args.callback)(true, window, cx)
16198 })
16199 .unwrap();
16200 let snapshot = editor
16201 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
16202 .unwrap();
16203 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
16204
16205 cx.update_window(*editor, |_, window, cx| {
16206 (render_args.callback)(false, window, cx)
16207 })
16208 .unwrap();
16209 let snapshot = editor
16210 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
16211 .unwrap();
16212 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
16213}
16214
16215#[gpui::test]
16216async fn test_input_text(cx: &mut TestAppContext) {
16217 init_test(cx, |_| {});
16218 let mut cx = EditorTestContext::new(cx).await;
16219
16220 cx.set_state(
16221 &r#"ˇone
16222 two
16223
16224 three
16225 fourˇ
16226 five
16227
16228 siˇx"#
16229 .unindent(),
16230 );
16231
16232 cx.dispatch_action(HandleInput(String::new()));
16233 cx.assert_editor_state(
16234 &r#"ˇone
16235 two
16236
16237 three
16238 fourˇ
16239 five
16240
16241 siˇx"#
16242 .unindent(),
16243 );
16244
16245 cx.dispatch_action(HandleInput("AAAA".to_string()));
16246 cx.assert_editor_state(
16247 &r#"AAAAˇone
16248 two
16249
16250 three
16251 fourAAAAˇ
16252 five
16253
16254 siAAAAˇx"#
16255 .unindent(),
16256 );
16257}
16258
16259#[gpui::test]
16260async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
16261 init_test(cx, |_| {});
16262
16263 let mut cx = EditorTestContext::new(cx).await;
16264 cx.set_state(
16265 r#"let foo = 1;
16266let foo = 2;
16267let foo = 3;
16268let fooˇ = 4;
16269let foo = 5;
16270let foo = 6;
16271let foo = 7;
16272let foo = 8;
16273let foo = 9;
16274let foo = 10;
16275let foo = 11;
16276let foo = 12;
16277let foo = 13;
16278let foo = 14;
16279let foo = 15;"#,
16280 );
16281
16282 cx.update_editor(|e, window, cx| {
16283 assert_eq!(
16284 e.next_scroll_position,
16285 NextScrollCursorCenterTopBottom::Center,
16286 "Default next scroll direction is center",
16287 );
16288
16289 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
16290 assert_eq!(
16291 e.next_scroll_position,
16292 NextScrollCursorCenterTopBottom::Top,
16293 "After center, next scroll direction should be top",
16294 );
16295
16296 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
16297 assert_eq!(
16298 e.next_scroll_position,
16299 NextScrollCursorCenterTopBottom::Bottom,
16300 "After top, next scroll direction should be bottom",
16301 );
16302
16303 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
16304 assert_eq!(
16305 e.next_scroll_position,
16306 NextScrollCursorCenterTopBottom::Center,
16307 "After bottom, scrolling should start over",
16308 );
16309
16310 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
16311 assert_eq!(
16312 e.next_scroll_position,
16313 NextScrollCursorCenterTopBottom::Top,
16314 "Scrolling continues if retriggered fast enough"
16315 );
16316 });
16317
16318 cx.executor()
16319 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
16320 cx.executor().run_until_parked();
16321 cx.update_editor(|e, _, _| {
16322 assert_eq!(
16323 e.next_scroll_position,
16324 NextScrollCursorCenterTopBottom::Center,
16325 "If scrolling is not triggered fast enough, it should reset"
16326 );
16327 });
16328}
16329
16330#[gpui::test]
16331async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
16332 init_test(cx, |_| {});
16333 let mut cx = EditorLspTestContext::new_rust(
16334 lsp::ServerCapabilities {
16335 definition_provider: Some(lsp::OneOf::Left(true)),
16336 references_provider: Some(lsp::OneOf::Left(true)),
16337 ..lsp::ServerCapabilities::default()
16338 },
16339 cx,
16340 )
16341 .await;
16342
16343 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
16344 let go_to_definition = cx
16345 .lsp
16346 .set_request_handler::<lsp::request::GotoDefinition, _, _>(
16347 move |params, _| async move {
16348 if empty_go_to_definition {
16349 Ok(None)
16350 } else {
16351 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
16352 uri: params.text_document_position_params.text_document.uri,
16353 range: lsp::Range::new(
16354 lsp::Position::new(4, 3),
16355 lsp::Position::new(4, 6),
16356 ),
16357 })))
16358 }
16359 },
16360 );
16361 let references = cx
16362 .lsp
16363 .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
16364 Ok(Some(vec![lsp::Location {
16365 uri: params.text_document_position.text_document.uri,
16366 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
16367 }]))
16368 });
16369 (go_to_definition, references)
16370 };
16371
16372 cx.set_state(
16373 &r#"fn one() {
16374 let mut a = ˇtwo();
16375 }
16376
16377 fn two() {}"#
16378 .unindent(),
16379 );
16380 set_up_lsp_handlers(false, &mut cx);
16381 let navigated = cx
16382 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
16383 .await
16384 .expect("Failed to navigate to definition");
16385 assert_eq!(
16386 navigated,
16387 Navigated::Yes,
16388 "Should have navigated to definition from the GetDefinition response"
16389 );
16390 cx.assert_editor_state(
16391 &r#"fn one() {
16392 let mut a = two();
16393 }
16394
16395 fn «twoˇ»() {}"#
16396 .unindent(),
16397 );
16398
16399 let editors = cx.update_workspace(|workspace, _, cx| {
16400 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
16401 });
16402 cx.update_editor(|_, _, test_editor_cx| {
16403 assert_eq!(
16404 editors.len(),
16405 1,
16406 "Initially, only one, test, editor should be open in the workspace"
16407 );
16408 assert_eq!(
16409 test_editor_cx.entity(),
16410 editors.last().expect("Asserted len is 1").clone()
16411 );
16412 });
16413
16414 set_up_lsp_handlers(true, &mut cx);
16415 let navigated = cx
16416 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
16417 .await
16418 .expect("Failed to navigate to lookup references");
16419 assert_eq!(
16420 navigated,
16421 Navigated::Yes,
16422 "Should have navigated to references as a fallback after empty GoToDefinition response"
16423 );
16424 // We should not change the selections in the existing file,
16425 // if opening another milti buffer with the references
16426 cx.assert_editor_state(
16427 &r#"fn one() {
16428 let mut a = two();
16429 }
16430
16431 fn «twoˇ»() {}"#
16432 .unindent(),
16433 );
16434 let editors = cx.update_workspace(|workspace, _, cx| {
16435 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
16436 });
16437 cx.update_editor(|_, _, test_editor_cx| {
16438 assert_eq!(
16439 editors.len(),
16440 2,
16441 "After falling back to references search, we open a new editor with the results"
16442 );
16443 let references_fallback_text = editors
16444 .into_iter()
16445 .find(|new_editor| *new_editor != test_editor_cx.entity())
16446 .expect("Should have one non-test editor now")
16447 .read(test_editor_cx)
16448 .text(test_editor_cx);
16449 assert_eq!(
16450 references_fallback_text, "fn one() {\n let mut a = two();\n}",
16451 "Should use the range from the references response and not the GoToDefinition one"
16452 );
16453 });
16454}
16455
16456#[gpui::test]
16457async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
16458 init_test(cx, |_| {});
16459 cx.update(|cx| {
16460 let mut editor_settings = EditorSettings::get_global(cx).clone();
16461 editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
16462 EditorSettings::override_global(editor_settings, cx);
16463 });
16464 let mut cx = EditorLspTestContext::new_rust(
16465 lsp::ServerCapabilities {
16466 definition_provider: Some(lsp::OneOf::Left(true)),
16467 references_provider: Some(lsp::OneOf::Left(true)),
16468 ..lsp::ServerCapabilities::default()
16469 },
16470 cx,
16471 )
16472 .await;
16473 let original_state = r#"fn one() {
16474 let mut a = ˇtwo();
16475 }
16476
16477 fn two() {}"#
16478 .unindent();
16479 cx.set_state(&original_state);
16480
16481 let mut go_to_definition = cx
16482 .lsp
16483 .set_request_handler::<lsp::request::GotoDefinition, _, _>(
16484 move |_, _| async move { Ok(None) },
16485 );
16486 let _references = cx
16487 .lsp
16488 .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
16489 panic!("Should not call for references with no go to definition fallback")
16490 });
16491
16492 let navigated = cx
16493 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
16494 .await
16495 .expect("Failed to navigate to lookup references");
16496 go_to_definition
16497 .next()
16498 .await
16499 .expect("Should have called the go_to_definition handler");
16500
16501 assert_eq!(
16502 navigated,
16503 Navigated::No,
16504 "Should have navigated to references as a fallback after empty GoToDefinition response"
16505 );
16506 cx.assert_editor_state(&original_state);
16507 let editors = cx.update_workspace(|workspace, _, cx| {
16508 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
16509 });
16510 cx.update_editor(|_, _, _| {
16511 assert_eq!(
16512 editors.len(),
16513 1,
16514 "After unsuccessful fallback, no other editor should have been opened"
16515 );
16516 });
16517}
16518
16519#[gpui::test]
16520async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
16521 init_test(cx, |_| {});
16522
16523 let language = Arc::new(Language::new(
16524 LanguageConfig::default(),
16525 Some(tree_sitter_rust::LANGUAGE.into()),
16526 ));
16527
16528 let text = r#"
16529 #[cfg(test)]
16530 mod tests() {
16531 #[test]
16532 fn runnable_1() {
16533 let a = 1;
16534 }
16535
16536 #[test]
16537 fn runnable_2() {
16538 let a = 1;
16539 let b = 2;
16540 }
16541 }
16542 "#
16543 .unindent();
16544
16545 let fs = FakeFs::new(cx.executor());
16546 fs.insert_file("/file.rs", Default::default()).await;
16547
16548 let project = Project::test(fs, ["/a".as_ref()], cx).await;
16549 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16550 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16551 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16552 let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
16553
16554 let editor = cx.new_window_entity(|window, cx| {
16555 Editor::new(
16556 EditorMode::Full,
16557 multi_buffer,
16558 Some(project.clone()),
16559 window,
16560 cx,
16561 )
16562 });
16563
16564 editor.update_in(cx, |editor, window, cx| {
16565 let snapshot = editor.buffer().read(cx).snapshot(cx);
16566 editor.tasks.insert(
16567 (buffer.read(cx).remote_id(), 3),
16568 RunnableTasks {
16569 templates: vec![],
16570 offset: snapshot.anchor_before(43),
16571 column: 0,
16572 extra_variables: HashMap::default(),
16573 context_range: BufferOffset(43)..BufferOffset(85),
16574 },
16575 );
16576 editor.tasks.insert(
16577 (buffer.read(cx).remote_id(), 8),
16578 RunnableTasks {
16579 templates: vec![],
16580 offset: snapshot.anchor_before(86),
16581 column: 0,
16582 extra_variables: HashMap::default(),
16583 context_range: BufferOffset(86)..BufferOffset(191),
16584 },
16585 );
16586
16587 // Test finding task when cursor is inside function body
16588 editor.change_selections(None, window, cx, |s| {
16589 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
16590 });
16591 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
16592 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
16593
16594 // Test finding task when cursor is on function name
16595 editor.change_selections(None, window, cx, |s| {
16596 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
16597 });
16598 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
16599 assert_eq!(row, 8, "Should find task when cursor is on function name");
16600 });
16601}
16602
16603#[gpui::test]
16604async fn test_folding_buffers(cx: &mut TestAppContext) {
16605 init_test(cx, |_| {});
16606
16607 let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
16608 let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
16609 let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
16610
16611 let fs = FakeFs::new(cx.executor());
16612 fs.insert_tree(
16613 path!("/a"),
16614 json!({
16615 "first.rs": sample_text_1,
16616 "second.rs": sample_text_2,
16617 "third.rs": sample_text_3,
16618 }),
16619 )
16620 .await;
16621 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16622 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16623 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16624 let worktree = project.update(cx, |project, cx| {
16625 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
16626 assert_eq!(worktrees.len(), 1);
16627 worktrees.pop().unwrap()
16628 });
16629 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
16630
16631 let buffer_1 = project
16632 .update(cx, |project, cx| {
16633 project.open_buffer((worktree_id, "first.rs"), cx)
16634 })
16635 .await
16636 .unwrap();
16637 let buffer_2 = project
16638 .update(cx, |project, cx| {
16639 project.open_buffer((worktree_id, "second.rs"), cx)
16640 })
16641 .await
16642 .unwrap();
16643 let buffer_3 = project
16644 .update(cx, |project, cx| {
16645 project.open_buffer((worktree_id, "third.rs"), cx)
16646 })
16647 .await
16648 .unwrap();
16649
16650 let multi_buffer = cx.new(|cx| {
16651 let mut multi_buffer = MultiBuffer::new(ReadWrite);
16652 multi_buffer.push_excerpts(
16653 buffer_1.clone(),
16654 [
16655 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16656 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16657 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16658 ],
16659 cx,
16660 );
16661 multi_buffer.push_excerpts(
16662 buffer_2.clone(),
16663 [
16664 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16665 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16666 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16667 ],
16668 cx,
16669 );
16670 multi_buffer.push_excerpts(
16671 buffer_3.clone(),
16672 [
16673 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16674 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16675 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16676 ],
16677 cx,
16678 );
16679 multi_buffer
16680 });
16681 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16682 Editor::new(
16683 EditorMode::Full,
16684 multi_buffer.clone(),
16685 Some(project.clone()),
16686 window,
16687 cx,
16688 )
16689 });
16690
16691 assert_eq!(
16692 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16693 "\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",
16694 );
16695
16696 multi_buffer_editor.update(cx, |editor, cx| {
16697 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
16698 });
16699 assert_eq!(
16700 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16701 "\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",
16702 "After folding the first buffer, its text should not be displayed"
16703 );
16704
16705 multi_buffer_editor.update(cx, |editor, cx| {
16706 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
16707 });
16708 assert_eq!(
16709 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16710 "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
16711 "After folding the second buffer, its text should not be displayed"
16712 );
16713
16714 multi_buffer_editor.update(cx, |editor, cx| {
16715 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
16716 });
16717 assert_eq!(
16718 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16719 "\n\n\n\n\n",
16720 "After folding the third buffer, its text should not be displayed"
16721 );
16722
16723 // Emulate selection inside the fold logic, that should work
16724 multi_buffer_editor.update_in(cx, |editor, window, cx| {
16725 editor
16726 .snapshot(window, cx)
16727 .next_line_boundary(Point::new(0, 4));
16728 });
16729
16730 multi_buffer_editor.update(cx, |editor, cx| {
16731 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
16732 });
16733 assert_eq!(
16734 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16735 "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
16736 "After unfolding the second buffer, its text should be displayed"
16737 );
16738
16739 // Typing inside of buffer 1 causes that buffer to be unfolded.
16740 multi_buffer_editor.update_in(cx, |editor, window, cx| {
16741 assert_eq!(
16742 multi_buffer
16743 .read(cx)
16744 .snapshot(cx)
16745 .text_for_range(Point::new(1, 0)..Point::new(1, 4))
16746 .collect::<String>(),
16747 "bbbb"
16748 );
16749 editor.change_selections(None, window, cx, |selections| {
16750 selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
16751 });
16752 editor.handle_input("B", window, cx);
16753 });
16754
16755 assert_eq!(
16756 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16757 "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
16758 "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
16759 );
16760
16761 multi_buffer_editor.update(cx, |editor, cx| {
16762 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
16763 });
16764 assert_eq!(
16765 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16766 "\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",
16767 "After unfolding the all buffers, all original text should be displayed"
16768 );
16769}
16770
16771#[gpui::test]
16772async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
16773 init_test(cx, |_| {});
16774
16775 let sample_text_1 = "1111\n2222\n3333".to_string();
16776 let sample_text_2 = "4444\n5555\n6666".to_string();
16777 let sample_text_3 = "7777\n8888\n9999".to_string();
16778
16779 let fs = FakeFs::new(cx.executor());
16780 fs.insert_tree(
16781 path!("/a"),
16782 json!({
16783 "first.rs": sample_text_1,
16784 "second.rs": sample_text_2,
16785 "third.rs": sample_text_3,
16786 }),
16787 )
16788 .await;
16789 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16790 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16791 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16792 let worktree = project.update(cx, |project, cx| {
16793 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
16794 assert_eq!(worktrees.len(), 1);
16795 worktrees.pop().unwrap()
16796 });
16797 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
16798
16799 let buffer_1 = project
16800 .update(cx, |project, cx| {
16801 project.open_buffer((worktree_id, "first.rs"), cx)
16802 })
16803 .await
16804 .unwrap();
16805 let buffer_2 = project
16806 .update(cx, |project, cx| {
16807 project.open_buffer((worktree_id, "second.rs"), cx)
16808 })
16809 .await
16810 .unwrap();
16811 let buffer_3 = project
16812 .update(cx, |project, cx| {
16813 project.open_buffer((worktree_id, "third.rs"), cx)
16814 })
16815 .await
16816 .unwrap();
16817
16818 let multi_buffer = cx.new(|cx| {
16819 let mut multi_buffer = MultiBuffer::new(ReadWrite);
16820 multi_buffer.push_excerpts(
16821 buffer_1.clone(),
16822 [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
16823 cx,
16824 );
16825 multi_buffer.push_excerpts(
16826 buffer_2.clone(),
16827 [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
16828 cx,
16829 );
16830 multi_buffer.push_excerpts(
16831 buffer_3.clone(),
16832 [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
16833 cx,
16834 );
16835 multi_buffer
16836 });
16837
16838 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16839 Editor::new(
16840 EditorMode::Full,
16841 multi_buffer,
16842 Some(project.clone()),
16843 window,
16844 cx,
16845 )
16846 });
16847
16848 let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
16849 assert_eq!(
16850 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16851 full_text,
16852 );
16853
16854 multi_buffer_editor.update(cx, |editor, cx| {
16855 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
16856 });
16857 assert_eq!(
16858 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16859 "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
16860 "After folding the first buffer, its text should not be displayed"
16861 );
16862
16863 multi_buffer_editor.update(cx, |editor, cx| {
16864 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
16865 });
16866
16867 assert_eq!(
16868 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16869 "\n\n\n\n\n\n7777\n8888\n9999",
16870 "After folding the second buffer, its text should not be displayed"
16871 );
16872
16873 multi_buffer_editor.update(cx, |editor, cx| {
16874 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
16875 });
16876 assert_eq!(
16877 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16878 "\n\n\n\n\n",
16879 "After folding the third buffer, its text should not be displayed"
16880 );
16881
16882 multi_buffer_editor.update(cx, |editor, cx| {
16883 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
16884 });
16885 assert_eq!(
16886 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16887 "\n\n\n\n4444\n5555\n6666\n\n",
16888 "After unfolding the second buffer, its text should be displayed"
16889 );
16890
16891 multi_buffer_editor.update(cx, |editor, cx| {
16892 editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
16893 });
16894 assert_eq!(
16895 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16896 "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
16897 "After unfolding the first buffer, its text should be displayed"
16898 );
16899
16900 multi_buffer_editor.update(cx, |editor, cx| {
16901 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
16902 });
16903 assert_eq!(
16904 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16905 full_text,
16906 "After unfolding all buffers, all original text should be displayed"
16907 );
16908}
16909
16910#[gpui::test]
16911async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
16912 init_test(cx, |_| {});
16913
16914 let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
16915
16916 let fs = FakeFs::new(cx.executor());
16917 fs.insert_tree(
16918 path!("/a"),
16919 json!({
16920 "main.rs": sample_text,
16921 }),
16922 )
16923 .await;
16924 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16925 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16926 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16927 let worktree = project.update(cx, |project, cx| {
16928 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
16929 assert_eq!(worktrees.len(), 1);
16930 worktrees.pop().unwrap()
16931 });
16932 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
16933
16934 let buffer_1 = project
16935 .update(cx, |project, cx| {
16936 project.open_buffer((worktree_id, "main.rs"), cx)
16937 })
16938 .await
16939 .unwrap();
16940
16941 let multi_buffer = cx.new(|cx| {
16942 let mut multi_buffer = MultiBuffer::new(ReadWrite);
16943 multi_buffer.push_excerpts(
16944 buffer_1.clone(),
16945 [ExcerptRange::new(
16946 Point::new(0, 0)
16947 ..Point::new(
16948 sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
16949 0,
16950 ),
16951 )],
16952 cx,
16953 );
16954 multi_buffer
16955 });
16956 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16957 Editor::new(
16958 EditorMode::Full,
16959 multi_buffer,
16960 Some(project.clone()),
16961 window,
16962 cx,
16963 )
16964 });
16965
16966 let selection_range = Point::new(1, 0)..Point::new(2, 0);
16967 multi_buffer_editor.update_in(cx, |editor, window, cx| {
16968 enum TestHighlight {}
16969 let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
16970 let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
16971 editor.highlight_text::<TestHighlight>(
16972 vec![highlight_range.clone()],
16973 HighlightStyle::color(Hsla::green()),
16974 cx,
16975 );
16976 editor.change_selections(None, window, cx, |s| s.select_ranges(Some(highlight_range)));
16977 });
16978
16979 let full_text = format!("\n\n{sample_text}");
16980 assert_eq!(
16981 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16982 full_text,
16983 );
16984}
16985
16986#[gpui::test]
16987async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
16988 init_test(cx, |_| {});
16989 cx.update(|cx| {
16990 let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
16991 "keymaps/default-linux.json",
16992 cx,
16993 )
16994 .unwrap();
16995 cx.bind_keys(default_key_bindings);
16996 });
16997
16998 let (editor, cx) = cx.add_window_view(|window, cx| {
16999 let multi_buffer = MultiBuffer::build_multi(
17000 [
17001 ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
17002 ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
17003 ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
17004 ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
17005 ],
17006 cx,
17007 );
17008 let mut editor = Editor::new(EditorMode::Full, multi_buffer.clone(), None, window, cx);
17009
17010 let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
17011 // fold all but the second buffer, so that we test navigating between two
17012 // adjacent folded buffers, as well as folded buffers at the start and
17013 // end the multibuffer
17014 editor.fold_buffer(buffer_ids[0], cx);
17015 editor.fold_buffer(buffer_ids[2], cx);
17016 editor.fold_buffer(buffer_ids[3], cx);
17017
17018 editor
17019 });
17020 cx.simulate_resize(size(px(1000.), px(1000.)));
17021
17022 let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
17023 cx.assert_excerpts_with_selections(indoc! {"
17024 [EXCERPT]
17025 ˇ[FOLDED]
17026 [EXCERPT]
17027 a1
17028 b1
17029 [EXCERPT]
17030 [FOLDED]
17031 [EXCERPT]
17032 [FOLDED]
17033 "
17034 });
17035 cx.simulate_keystroke("down");
17036 cx.assert_excerpts_with_selections(indoc! {"
17037 [EXCERPT]
17038 [FOLDED]
17039 [EXCERPT]
17040 ˇa1
17041 b1
17042 [EXCERPT]
17043 [FOLDED]
17044 [EXCERPT]
17045 [FOLDED]
17046 "
17047 });
17048 cx.simulate_keystroke("down");
17049 cx.assert_excerpts_with_selections(indoc! {"
17050 [EXCERPT]
17051 [FOLDED]
17052 [EXCERPT]
17053 a1
17054 ˇb1
17055 [EXCERPT]
17056 [FOLDED]
17057 [EXCERPT]
17058 [FOLDED]
17059 "
17060 });
17061 cx.simulate_keystroke("down");
17062 cx.assert_excerpts_with_selections(indoc! {"
17063 [EXCERPT]
17064 [FOLDED]
17065 [EXCERPT]
17066 a1
17067 b1
17068 ˇ[EXCERPT]
17069 [FOLDED]
17070 [EXCERPT]
17071 [FOLDED]
17072 "
17073 });
17074 cx.simulate_keystroke("down");
17075 cx.assert_excerpts_with_selections(indoc! {"
17076 [EXCERPT]
17077 [FOLDED]
17078 [EXCERPT]
17079 a1
17080 b1
17081 [EXCERPT]
17082 ˇ[FOLDED]
17083 [EXCERPT]
17084 [FOLDED]
17085 "
17086 });
17087 for _ in 0..5 {
17088 cx.simulate_keystroke("down");
17089 cx.assert_excerpts_with_selections(indoc! {"
17090 [EXCERPT]
17091 [FOLDED]
17092 [EXCERPT]
17093 a1
17094 b1
17095 [EXCERPT]
17096 [FOLDED]
17097 [EXCERPT]
17098 ˇ[FOLDED]
17099 "
17100 });
17101 }
17102
17103 cx.simulate_keystroke("up");
17104 cx.assert_excerpts_with_selections(indoc! {"
17105 [EXCERPT]
17106 [FOLDED]
17107 [EXCERPT]
17108 a1
17109 b1
17110 [EXCERPT]
17111 ˇ[FOLDED]
17112 [EXCERPT]
17113 [FOLDED]
17114 "
17115 });
17116 cx.simulate_keystroke("up");
17117 cx.assert_excerpts_with_selections(indoc! {"
17118 [EXCERPT]
17119 [FOLDED]
17120 [EXCERPT]
17121 a1
17122 b1
17123 ˇ[EXCERPT]
17124 [FOLDED]
17125 [EXCERPT]
17126 [FOLDED]
17127 "
17128 });
17129 cx.simulate_keystroke("up");
17130 cx.assert_excerpts_with_selections(indoc! {"
17131 [EXCERPT]
17132 [FOLDED]
17133 [EXCERPT]
17134 a1
17135 ˇb1
17136 [EXCERPT]
17137 [FOLDED]
17138 [EXCERPT]
17139 [FOLDED]
17140 "
17141 });
17142 cx.simulate_keystroke("up");
17143 cx.assert_excerpts_with_selections(indoc! {"
17144 [EXCERPT]
17145 [FOLDED]
17146 [EXCERPT]
17147 ˇa1
17148 b1
17149 [EXCERPT]
17150 [FOLDED]
17151 [EXCERPT]
17152 [FOLDED]
17153 "
17154 });
17155 for _ in 0..5 {
17156 cx.simulate_keystroke("up");
17157 cx.assert_excerpts_with_selections(indoc! {"
17158 [EXCERPT]
17159 ˇ[FOLDED]
17160 [EXCERPT]
17161 a1
17162 b1
17163 [EXCERPT]
17164 [FOLDED]
17165 [EXCERPT]
17166 [FOLDED]
17167 "
17168 });
17169 }
17170}
17171
17172#[gpui::test]
17173async fn test_inline_completion_text(cx: &mut TestAppContext) {
17174 init_test(cx, |_| {});
17175
17176 // Simple insertion
17177 assert_highlighted_edits(
17178 "Hello, world!",
17179 vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
17180 true,
17181 cx,
17182 |highlighted_edits, cx| {
17183 assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
17184 assert_eq!(highlighted_edits.highlights.len(), 1);
17185 assert_eq!(highlighted_edits.highlights[0].0, 6..16);
17186 assert_eq!(
17187 highlighted_edits.highlights[0].1.background_color,
17188 Some(cx.theme().status().created_background)
17189 );
17190 },
17191 )
17192 .await;
17193
17194 // Replacement
17195 assert_highlighted_edits(
17196 "This is a test.",
17197 vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
17198 false,
17199 cx,
17200 |highlighted_edits, cx| {
17201 assert_eq!(highlighted_edits.text, "That is a test.");
17202 assert_eq!(highlighted_edits.highlights.len(), 1);
17203 assert_eq!(highlighted_edits.highlights[0].0, 0..4);
17204 assert_eq!(
17205 highlighted_edits.highlights[0].1.background_color,
17206 Some(cx.theme().status().created_background)
17207 );
17208 },
17209 )
17210 .await;
17211
17212 // Multiple edits
17213 assert_highlighted_edits(
17214 "Hello, world!",
17215 vec![
17216 (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
17217 (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
17218 ],
17219 false,
17220 cx,
17221 |highlighted_edits, cx| {
17222 assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
17223 assert_eq!(highlighted_edits.highlights.len(), 2);
17224 assert_eq!(highlighted_edits.highlights[0].0, 0..9);
17225 assert_eq!(highlighted_edits.highlights[1].0, 16..29);
17226 assert_eq!(
17227 highlighted_edits.highlights[0].1.background_color,
17228 Some(cx.theme().status().created_background)
17229 );
17230 assert_eq!(
17231 highlighted_edits.highlights[1].1.background_color,
17232 Some(cx.theme().status().created_background)
17233 );
17234 },
17235 )
17236 .await;
17237
17238 // Multiple lines with edits
17239 assert_highlighted_edits(
17240 "First line\nSecond line\nThird line\nFourth line",
17241 vec![
17242 (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
17243 (
17244 Point::new(2, 0)..Point::new(2, 10),
17245 "New third line".to_string(),
17246 ),
17247 (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
17248 ],
17249 false,
17250 cx,
17251 |highlighted_edits, cx| {
17252 assert_eq!(
17253 highlighted_edits.text,
17254 "Second modified\nNew third line\nFourth updated line"
17255 );
17256 assert_eq!(highlighted_edits.highlights.len(), 3);
17257 assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
17258 assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
17259 assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
17260 for highlight in &highlighted_edits.highlights {
17261 assert_eq!(
17262 highlight.1.background_color,
17263 Some(cx.theme().status().created_background)
17264 );
17265 }
17266 },
17267 )
17268 .await;
17269}
17270
17271#[gpui::test]
17272async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
17273 init_test(cx, |_| {});
17274
17275 // Deletion
17276 assert_highlighted_edits(
17277 "Hello, world!",
17278 vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
17279 true,
17280 cx,
17281 |highlighted_edits, cx| {
17282 assert_eq!(highlighted_edits.text, "Hello, world!");
17283 assert_eq!(highlighted_edits.highlights.len(), 1);
17284 assert_eq!(highlighted_edits.highlights[0].0, 5..11);
17285 assert_eq!(
17286 highlighted_edits.highlights[0].1.background_color,
17287 Some(cx.theme().status().deleted_background)
17288 );
17289 },
17290 )
17291 .await;
17292
17293 // Insertion
17294 assert_highlighted_edits(
17295 "Hello, world!",
17296 vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
17297 true,
17298 cx,
17299 |highlighted_edits, cx| {
17300 assert_eq!(highlighted_edits.highlights.len(), 1);
17301 assert_eq!(highlighted_edits.highlights[0].0, 6..14);
17302 assert_eq!(
17303 highlighted_edits.highlights[0].1.background_color,
17304 Some(cx.theme().status().created_background)
17305 );
17306 },
17307 )
17308 .await;
17309}
17310
17311async fn assert_highlighted_edits(
17312 text: &str,
17313 edits: Vec<(Range<Point>, String)>,
17314 include_deletions: bool,
17315 cx: &mut TestAppContext,
17316 assertion_fn: impl Fn(HighlightedText, &App),
17317) {
17318 let window = cx.add_window(|window, cx| {
17319 let buffer = MultiBuffer::build_simple(text, cx);
17320 Editor::new(EditorMode::Full, buffer, None, window, cx)
17321 });
17322 let cx = &mut VisualTestContext::from_window(*window, cx);
17323
17324 let (buffer, snapshot) = window
17325 .update(cx, |editor, _window, cx| {
17326 (
17327 editor.buffer().clone(),
17328 editor.buffer().read(cx).snapshot(cx),
17329 )
17330 })
17331 .unwrap();
17332
17333 let edits = edits
17334 .into_iter()
17335 .map(|(range, edit)| {
17336 (
17337 snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
17338 edit,
17339 )
17340 })
17341 .collect::<Vec<_>>();
17342
17343 let text_anchor_edits = edits
17344 .clone()
17345 .into_iter()
17346 .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
17347 .collect::<Vec<_>>();
17348
17349 let edit_preview = window
17350 .update(cx, |_, _window, cx| {
17351 buffer
17352 .read(cx)
17353 .as_singleton()
17354 .unwrap()
17355 .read(cx)
17356 .preview_edits(text_anchor_edits.into(), cx)
17357 })
17358 .unwrap()
17359 .await;
17360
17361 cx.update(|_window, cx| {
17362 let highlighted_edits = inline_completion_edit_text(
17363 &snapshot.as_singleton().unwrap().2,
17364 &edits,
17365 &edit_preview,
17366 include_deletions,
17367 cx,
17368 );
17369 assertion_fn(highlighted_edits, cx)
17370 });
17371}
17372
17373#[track_caller]
17374fn assert_breakpoint(
17375 breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
17376 path: &Arc<Path>,
17377 expected: Vec<(u32, Breakpoint)>,
17378) {
17379 if expected.len() == 0usize {
17380 assert!(!breakpoints.contains_key(path), "{}", path.display());
17381 } else {
17382 let mut breakpoint = breakpoints
17383 .get(path)
17384 .unwrap()
17385 .into_iter()
17386 .map(|breakpoint| {
17387 (
17388 breakpoint.row,
17389 Breakpoint {
17390 message: breakpoint.message.clone(),
17391 state: breakpoint.state,
17392 condition: breakpoint.condition.clone(),
17393 hit_condition: breakpoint.hit_condition.clone(),
17394 },
17395 )
17396 })
17397 .collect::<Vec<_>>();
17398
17399 breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
17400
17401 assert_eq!(expected, breakpoint);
17402 }
17403}
17404
17405fn add_log_breakpoint_at_cursor(
17406 editor: &mut Editor,
17407 log_message: &str,
17408 window: &mut Window,
17409 cx: &mut Context<Editor>,
17410) {
17411 let (anchor, bp) = editor
17412 .breakpoint_at_cursor_head(window, cx)
17413 .unwrap_or_else(|| {
17414 let cursor_position: Point = editor.selections.newest(cx).head();
17415
17416 let breakpoint_position = editor
17417 .snapshot(window, cx)
17418 .display_snapshot
17419 .buffer_snapshot
17420 .anchor_before(Point::new(cursor_position.row, 0));
17421
17422 (breakpoint_position, Breakpoint::new_log(&log_message))
17423 });
17424
17425 editor.edit_breakpoint_at_anchor(
17426 anchor,
17427 bp,
17428 BreakpointEditAction::EditLogMessage(log_message.into()),
17429 cx,
17430 );
17431}
17432
17433#[gpui::test]
17434async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
17435 init_test(cx, |_| {});
17436
17437 let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
17438 let fs = FakeFs::new(cx.executor());
17439 fs.insert_tree(
17440 path!("/a"),
17441 json!({
17442 "main.rs": sample_text,
17443 }),
17444 )
17445 .await;
17446 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17447 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17448 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17449
17450 let fs = FakeFs::new(cx.executor());
17451 fs.insert_tree(
17452 path!("/a"),
17453 json!({
17454 "main.rs": sample_text,
17455 }),
17456 )
17457 .await;
17458 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17459 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17460 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17461 let worktree_id = workspace
17462 .update(cx, |workspace, _window, cx| {
17463 workspace.project().update(cx, |project, cx| {
17464 project.worktrees(cx).next().unwrap().read(cx).id()
17465 })
17466 })
17467 .unwrap();
17468
17469 let buffer = project
17470 .update(cx, |project, cx| {
17471 project.open_buffer((worktree_id, "main.rs"), cx)
17472 })
17473 .await
17474 .unwrap();
17475
17476 let (editor, cx) = cx.add_window_view(|window, cx| {
17477 Editor::new(
17478 EditorMode::Full,
17479 MultiBuffer::build_from_buffer(buffer, cx),
17480 Some(project.clone()),
17481 window,
17482 cx,
17483 )
17484 });
17485
17486 let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
17487 let abs_path = project.read_with(cx, |project, cx| {
17488 project
17489 .absolute_path(&project_path, cx)
17490 .map(|path_buf| Arc::from(path_buf.to_owned()))
17491 .unwrap()
17492 });
17493
17494 // assert we can add breakpoint on the first line
17495 editor.update_in(cx, |editor, window, cx| {
17496 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
17497 editor.move_to_end(&MoveToEnd, window, cx);
17498 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
17499 });
17500
17501 let breakpoints = editor.update(cx, |editor, cx| {
17502 editor
17503 .breakpoint_store()
17504 .as_ref()
17505 .unwrap()
17506 .read(cx)
17507 .all_breakpoints(cx)
17508 .clone()
17509 });
17510
17511 assert_eq!(1, breakpoints.len());
17512 assert_breakpoint(
17513 &breakpoints,
17514 &abs_path,
17515 vec![
17516 (0, Breakpoint::new_standard()),
17517 (3, Breakpoint::new_standard()),
17518 ],
17519 );
17520
17521 editor.update_in(cx, |editor, window, cx| {
17522 editor.move_to_beginning(&MoveToBeginning, window, cx);
17523 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
17524 });
17525
17526 let breakpoints = editor.update(cx, |editor, cx| {
17527 editor
17528 .breakpoint_store()
17529 .as_ref()
17530 .unwrap()
17531 .read(cx)
17532 .all_breakpoints(cx)
17533 .clone()
17534 });
17535
17536 assert_eq!(1, breakpoints.len());
17537 assert_breakpoint(
17538 &breakpoints,
17539 &abs_path,
17540 vec![(3, Breakpoint::new_standard())],
17541 );
17542
17543 editor.update_in(cx, |editor, window, cx| {
17544 editor.move_to_end(&MoveToEnd, window, cx);
17545 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
17546 });
17547
17548 let breakpoints = editor.update(cx, |editor, cx| {
17549 editor
17550 .breakpoint_store()
17551 .as_ref()
17552 .unwrap()
17553 .read(cx)
17554 .all_breakpoints(cx)
17555 .clone()
17556 });
17557
17558 assert_eq!(0, breakpoints.len());
17559 assert_breakpoint(&breakpoints, &abs_path, vec![]);
17560}
17561
17562#[gpui::test]
17563async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
17564 init_test(cx, |_| {});
17565
17566 let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
17567
17568 let fs = FakeFs::new(cx.executor());
17569 fs.insert_tree(
17570 path!("/a"),
17571 json!({
17572 "main.rs": sample_text,
17573 }),
17574 )
17575 .await;
17576 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17577 let (workspace, cx) =
17578 cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
17579
17580 let worktree_id = workspace.update(cx, |workspace, cx| {
17581 workspace.project().update(cx, |project, cx| {
17582 project.worktrees(cx).next().unwrap().read(cx).id()
17583 })
17584 });
17585
17586 let buffer = project
17587 .update(cx, |project, cx| {
17588 project.open_buffer((worktree_id, "main.rs"), cx)
17589 })
17590 .await
17591 .unwrap();
17592
17593 let (editor, cx) = cx.add_window_view(|window, cx| {
17594 Editor::new(
17595 EditorMode::Full,
17596 MultiBuffer::build_from_buffer(buffer, cx),
17597 Some(project.clone()),
17598 window,
17599 cx,
17600 )
17601 });
17602
17603 let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
17604 let abs_path = project.read_with(cx, |project, cx| {
17605 project
17606 .absolute_path(&project_path, cx)
17607 .map(|path_buf| Arc::from(path_buf.to_owned()))
17608 .unwrap()
17609 });
17610
17611 editor.update_in(cx, |editor, window, cx| {
17612 add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
17613 });
17614
17615 let breakpoints = editor.update(cx, |editor, cx| {
17616 editor
17617 .breakpoint_store()
17618 .as_ref()
17619 .unwrap()
17620 .read(cx)
17621 .all_breakpoints(cx)
17622 .clone()
17623 });
17624
17625 assert_breakpoint(
17626 &breakpoints,
17627 &abs_path,
17628 vec![(0, Breakpoint::new_log("hello world"))],
17629 );
17630
17631 // Removing a log message from a log breakpoint should remove it
17632 editor.update_in(cx, |editor, window, cx| {
17633 add_log_breakpoint_at_cursor(editor, "", window, cx);
17634 });
17635
17636 let breakpoints = editor.update(cx, |editor, cx| {
17637 editor
17638 .breakpoint_store()
17639 .as_ref()
17640 .unwrap()
17641 .read(cx)
17642 .all_breakpoints(cx)
17643 .clone()
17644 });
17645
17646 assert_breakpoint(&breakpoints, &abs_path, vec![]);
17647
17648 editor.update_in(cx, |editor, window, cx| {
17649 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
17650 editor.move_to_end(&MoveToEnd, window, cx);
17651 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
17652 // Not adding a log message to a standard breakpoint shouldn't remove it
17653 add_log_breakpoint_at_cursor(editor, "", window, cx);
17654 });
17655
17656 let breakpoints = editor.update(cx, |editor, cx| {
17657 editor
17658 .breakpoint_store()
17659 .as_ref()
17660 .unwrap()
17661 .read(cx)
17662 .all_breakpoints(cx)
17663 .clone()
17664 });
17665
17666 assert_breakpoint(
17667 &breakpoints,
17668 &abs_path,
17669 vec![
17670 (0, Breakpoint::new_standard()),
17671 (3, Breakpoint::new_standard()),
17672 ],
17673 );
17674
17675 editor.update_in(cx, |editor, window, cx| {
17676 add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
17677 });
17678
17679 let breakpoints = editor.update(cx, |editor, cx| {
17680 editor
17681 .breakpoint_store()
17682 .as_ref()
17683 .unwrap()
17684 .read(cx)
17685 .all_breakpoints(cx)
17686 .clone()
17687 });
17688
17689 assert_breakpoint(
17690 &breakpoints,
17691 &abs_path,
17692 vec![
17693 (0, Breakpoint::new_standard()),
17694 (3, Breakpoint::new_log("hello world")),
17695 ],
17696 );
17697
17698 editor.update_in(cx, |editor, window, cx| {
17699 add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
17700 });
17701
17702 let breakpoints = editor.update(cx, |editor, cx| {
17703 editor
17704 .breakpoint_store()
17705 .as_ref()
17706 .unwrap()
17707 .read(cx)
17708 .all_breakpoints(cx)
17709 .clone()
17710 });
17711
17712 assert_breakpoint(
17713 &breakpoints,
17714 &abs_path,
17715 vec![
17716 (0, Breakpoint::new_standard()),
17717 (3, Breakpoint::new_log("hello Earth!!")),
17718 ],
17719 );
17720}
17721
17722/// This also tests that Editor::breakpoint_at_cursor_head is working properly
17723/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
17724/// or when breakpoints were placed out of order. This tests for a regression too
17725#[gpui::test]
17726async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
17727 init_test(cx, |_| {});
17728
17729 let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
17730 let fs = FakeFs::new(cx.executor());
17731 fs.insert_tree(
17732 path!("/a"),
17733 json!({
17734 "main.rs": sample_text,
17735 }),
17736 )
17737 .await;
17738 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17739 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17740 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17741
17742 let fs = FakeFs::new(cx.executor());
17743 fs.insert_tree(
17744 path!("/a"),
17745 json!({
17746 "main.rs": sample_text,
17747 }),
17748 )
17749 .await;
17750 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17751 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17752 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17753 let worktree_id = workspace
17754 .update(cx, |workspace, _window, cx| {
17755 workspace.project().update(cx, |project, cx| {
17756 project.worktrees(cx).next().unwrap().read(cx).id()
17757 })
17758 })
17759 .unwrap();
17760
17761 let buffer = project
17762 .update(cx, |project, cx| {
17763 project.open_buffer((worktree_id, "main.rs"), cx)
17764 })
17765 .await
17766 .unwrap();
17767
17768 let (editor, cx) = cx.add_window_view(|window, cx| {
17769 Editor::new(
17770 EditorMode::Full,
17771 MultiBuffer::build_from_buffer(buffer, cx),
17772 Some(project.clone()),
17773 window,
17774 cx,
17775 )
17776 });
17777
17778 let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
17779 let abs_path = project.read_with(cx, |project, cx| {
17780 project
17781 .absolute_path(&project_path, cx)
17782 .map(|path_buf| Arc::from(path_buf.to_owned()))
17783 .unwrap()
17784 });
17785
17786 // assert we can add breakpoint on the first line
17787 editor.update_in(cx, |editor, window, cx| {
17788 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
17789 editor.move_to_end(&MoveToEnd, window, cx);
17790 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
17791 editor.move_up(&MoveUp, window, cx);
17792 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
17793 });
17794
17795 let breakpoints = editor.update(cx, |editor, cx| {
17796 editor
17797 .breakpoint_store()
17798 .as_ref()
17799 .unwrap()
17800 .read(cx)
17801 .all_breakpoints(cx)
17802 .clone()
17803 });
17804
17805 assert_eq!(1, breakpoints.len());
17806 assert_breakpoint(
17807 &breakpoints,
17808 &abs_path,
17809 vec![
17810 (0, Breakpoint::new_standard()),
17811 (2, Breakpoint::new_standard()),
17812 (3, Breakpoint::new_standard()),
17813 ],
17814 );
17815
17816 editor.update_in(cx, |editor, window, cx| {
17817 editor.move_to_beginning(&MoveToBeginning, window, cx);
17818 editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
17819 editor.move_to_end(&MoveToEnd, window, cx);
17820 editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
17821 // Disabling a breakpoint that doesn't exist should do nothing
17822 editor.move_up(&MoveUp, window, cx);
17823 editor.move_up(&MoveUp, window, cx);
17824 editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
17825 });
17826
17827 let breakpoints = editor.update(cx, |editor, cx| {
17828 editor
17829 .breakpoint_store()
17830 .as_ref()
17831 .unwrap()
17832 .read(cx)
17833 .all_breakpoints(cx)
17834 .clone()
17835 });
17836
17837 let disable_breakpoint = {
17838 let mut bp = Breakpoint::new_standard();
17839 bp.state = BreakpointState::Disabled;
17840 bp
17841 };
17842
17843 assert_eq!(1, breakpoints.len());
17844 assert_breakpoint(
17845 &breakpoints,
17846 &abs_path,
17847 vec![
17848 (0, disable_breakpoint.clone()),
17849 (2, Breakpoint::new_standard()),
17850 (3, disable_breakpoint.clone()),
17851 ],
17852 );
17853
17854 editor.update_in(cx, |editor, window, cx| {
17855 editor.move_to_beginning(&MoveToBeginning, window, cx);
17856 editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
17857 editor.move_to_end(&MoveToEnd, window, cx);
17858 editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
17859 editor.move_up(&MoveUp, window, cx);
17860 editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
17861 });
17862
17863 let breakpoints = editor.update(cx, |editor, cx| {
17864 editor
17865 .breakpoint_store()
17866 .as_ref()
17867 .unwrap()
17868 .read(cx)
17869 .all_breakpoints(cx)
17870 .clone()
17871 });
17872
17873 assert_eq!(1, breakpoints.len());
17874 assert_breakpoint(
17875 &breakpoints,
17876 &abs_path,
17877 vec![
17878 (0, Breakpoint::new_standard()),
17879 (2, disable_breakpoint),
17880 (3, Breakpoint::new_standard()),
17881 ],
17882 );
17883}
17884
17885#[gpui::test]
17886async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
17887 init_test(cx, |_| {});
17888 let capabilities = lsp::ServerCapabilities {
17889 rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
17890 prepare_provider: Some(true),
17891 work_done_progress_options: Default::default(),
17892 })),
17893 ..Default::default()
17894 };
17895 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
17896
17897 cx.set_state(indoc! {"
17898 struct Fˇoo {}
17899 "});
17900
17901 cx.update_editor(|editor, _, cx| {
17902 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
17903 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
17904 editor.highlight_background::<DocumentHighlightRead>(
17905 &[highlight_range],
17906 |c| c.editor_document_highlight_read_background,
17907 cx,
17908 );
17909 });
17910
17911 let mut prepare_rename_handler = cx
17912 .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
17913 move |_, _, _| async move {
17914 Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
17915 start: lsp::Position {
17916 line: 0,
17917 character: 7,
17918 },
17919 end: lsp::Position {
17920 line: 0,
17921 character: 10,
17922 },
17923 })))
17924 },
17925 );
17926 let prepare_rename_task = cx
17927 .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
17928 .expect("Prepare rename was not started");
17929 prepare_rename_handler.next().await.unwrap();
17930 prepare_rename_task.await.expect("Prepare rename failed");
17931
17932 let mut rename_handler =
17933 cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
17934 let edit = lsp::TextEdit {
17935 range: lsp::Range {
17936 start: lsp::Position {
17937 line: 0,
17938 character: 7,
17939 },
17940 end: lsp::Position {
17941 line: 0,
17942 character: 10,
17943 },
17944 },
17945 new_text: "FooRenamed".to_string(),
17946 };
17947 Ok(Some(lsp::WorkspaceEdit::new(
17948 // Specify the same edit twice
17949 std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
17950 )))
17951 });
17952 let rename_task = cx
17953 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
17954 .expect("Confirm rename was not started");
17955 rename_handler.next().await.unwrap();
17956 rename_task.await.expect("Confirm rename failed");
17957 cx.run_until_parked();
17958
17959 // Despite two edits, only one is actually applied as those are identical
17960 cx.assert_editor_state(indoc! {"
17961 struct FooRenamedˇ {}
17962 "});
17963}
17964
17965#[gpui::test]
17966async fn test_rename_without_prepare(cx: &mut TestAppContext) {
17967 init_test(cx, |_| {});
17968 // These capabilities indicate that the server does not support prepare rename.
17969 let capabilities = lsp::ServerCapabilities {
17970 rename_provider: Some(lsp::OneOf::Left(true)),
17971 ..Default::default()
17972 };
17973 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
17974
17975 cx.set_state(indoc! {"
17976 struct Fˇoo {}
17977 "});
17978
17979 cx.update_editor(|editor, _window, cx| {
17980 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
17981 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
17982 editor.highlight_background::<DocumentHighlightRead>(
17983 &[highlight_range],
17984 |c| c.editor_document_highlight_read_background,
17985 cx,
17986 );
17987 });
17988
17989 cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
17990 .expect("Prepare rename was not started")
17991 .await
17992 .expect("Prepare rename failed");
17993
17994 let mut rename_handler =
17995 cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
17996 let edit = lsp::TextEdit {
17997 range: lsp::Range {
17998 start: lsp::Position {
17999 line: 0,
18000 character: 7,
18001 },
18002 end: lsp::Position {
18003 line: 0,
18004 character: 10,
18005 },
18006 },
18007 new_text: "FooRenamed".to_string(),
18008 };
18009 Ok(Some(lsp::WorkspaceEdit::new(
18010 std::collections::HashMap::from_iter(Some((url, vec![edit]))),
18011 )))
18012 });
18013 let rename_task = cx
18014 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
18015 .expect("Confirm rename was not started");
18016 rename_handler.next().await.unwrap();
18017 rename_task.await.expect("Confirm rename failed");
18018 cx.run_until_parked();
18019
18020 // Correct range is renamed, as `surrounding_word` is used to find it.
18021 cx.assert_editor_state(indoc! {"
18022 struct FooRenamedˇ {}
18023 "});
18024}
18025
18026#[gpui::test]
18027async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
18028 init_test(cx, |_| {});
18029 let mut cx = EditorTestContext::new(cx).await;
18030
18031 let language = Arc::new(
18032 Language::new(
18033 LanguageConfig::default(),
18034 Some(tree_sitter_html::LANGUAGE.into()),
18035 )
18036 .with_brackets_query(
18037 r#"
18038 ("<" @open "/>" @close)
18039 ("</" @open ">" @close)
18040 ("<" @open ">" @close)
18041 ("\"" @open "\"" @close)
18042 ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
18043 "#,
18044 )
18045 .unwrap(),
18046 );
18047 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
18048
18049 cx.set_state(indoc! {"
18050 <span>ˇ</span>
18051 "});
18052 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
18053 cx.assert_editor_state(indoc! {"
18054 <span>
18055 ˇ
18056 </span>
18057 "});
18058
18059 cx.set_state(indoc! {"
18060 <span><span></span>ˇ</span>
18061 "});
18062 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
18063 cx.assert_editor_state(indoc! {"
18064 <span><span></span>
18065 ˇ</span>
18066 "});
18067
18068 cx.set_state(indoc! {"
18069 <span>ˇ
18070 </span>
18071 "});
18072 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
18073 cx.assert_editor_state(indoc! {"
18074 <span>
18075 ˇ
18076 </span>
18077 "});
18078}
18079
18080#[gpui::test(iterations = 10)]
18081async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
18082 init_test(cx, |_| {});
18083
18084 let fs = FakeFs::new(cx.executor());
18085 fs.insert_tree(
18086 path!("/dir"),
18087 json!({
18088 "a.ts": "a",
18089 }),
18090 )
18091 .await;
18092
18093 let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
18094 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18095 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18096
18097 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18098 language_registry.add(Arc::new(Language::new(
18099 LanguageConfig {
18100 name: "TypeScript".into(),
18101 matcher: LanguageMatcher {
18102 path_suffixes: vec!["ts".to_string()],
18103 ..Default::default()
18104 },
18105 ..Default::default()
18106 },
18107 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
18108 )));
18109 let mut fake_language_servers = language_registry.register_fake_lsp(
18110 "TypeScript",
18111 FakeLspAdapter {
18112 capabilities: lsp::ServerCapabilities {
18113 code_lens_provider: Some(lsp::CodeLensOptions {
18114 resolve_provider: Some(true),
18115 }),
18116 execute_command_provider: Some(lsp::ExecuteCommandOptions {
18117 commands: vec!["_the/command".to_string()],
18118 ..lsp::ExecuteCommandOptions::default()
18119 }),
18120 ..lsp::ServerCapabilities::default()
18121 },
18122 ..FakeLspAdapter::default()
18123 },
18124 );
18125
18126 let (buffer, _handle) = project
18127 .update(cx, |p, cx| {
18128 p.open_local_buffer_with_lsp(path!("/dir/a.ts"), cx)
18129 })
18130 .await
18131 .unwrap();
18132 cx.executor().run_until_parked();
18133
18134 let fake_server = fake_language_servers.next().await.unwrap();
18135
18136 let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
18137 let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
18138 drop(buffer_snapshot);
18139 let actions = cx
18140 .update_window(*workspace, |_, window, cx| {
18141 project.code_actions(&buffer, anchor..anchor, window, cx)
18142 })
18143 .unwrap();
18144
18145 fake_server
18146 .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
18147 Ok(Some(vec![
18148 lsp::CodeLens {
18149 range: lsp::Range::default(),
18150 command: Some(lsp::Command {
18151 title: "Code lens command".to_owned(),
18152 command: "_the/command".to_owned(),
18153 arguments: None,
18154 }),
18155 data: None,
18156 },
18157 lsp::CodeLens {
18158 range: lsp::Range::default(),
18159 command: Some(lsp::Command {
18160 title: "Command not in capabilities".to_owned(),
18161 command: "not in capabilities".to_owned(),
18162 arguments: None,
18163 }),
18164 data: None,
18165 },
18166 lsp::CodeLens {
18167 range: lsp::Range {
18168 start: lsp::Position {
18169 line: 1,
18170 character: 1,
18171 },
18172 end: lsp::Position {
18173 line: 1,
18174 character: 1,
18175 },
18176 },
18177 command: Some(lsp::Command {
18178 title: "Command not in range".to_owned(),
18179 command: "_the/command".to_owned(),
18180 arguments: None,
18181 }),
18182 data: None,
18183 },
18184 ]))
18185 })
18186 .next()
18187 .await;
18188
18189 let actions = actions.await.unwrap();
18190 assert_eq!(
18191 actions.len(),
18192 1,
18193 "Should have only one valid action for the 0..0 range"
18194 );
18195 let action = actions[0].clone();
18196 let apply = project.update(cx, |project, cx| {
18197 project.apply_code_action(buffer.clone(), action, true, cx)
18198 });
18199
18200 // Resolving the code action does not populate its edits. In absence of
18201 // edits, we must execute the given command.
18202 fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
18203 |mut lens, _| async move {
18204 let lens_command = lens.command.as_mut().expect("should have a command");
18205 assert_eq!(lens_command.title, "Code lens command");
18206 lens_command.arguments = Some(vec![json!("the-argument")]);
18207 Ok(lens)
18208 },
18209 );
18210
18211 // While executing the command, the language server sends the editor
18212 // a `workspaceEdit` request.
18213 fake_server
18214 .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
18215 let fake = fake_server.clone();
18216 move |params, _| {
18217 assert_eq!(params.command, "_the/command");
18218 let fake = fake.clone();
18219 async move {
18220 fake.server
18221 .request::<lsp::request::ApplyWorkspaceEdit>(
18222 lsp::ApplyWorkspaceEditParams {
18223 label: None,
18224 edit: lsp::WorkspaceEdit {
18225 changes: Some(
18226 [(
18227 lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
18228 vec![lsp::TextEdit {
18229 range: lsp::Range::new(
18230 lsp::Position::new(0, 0),
18231 lsp::Position::new(0, 0),
18232 ),
18233 new_text: "X".into(),
18234 }],
18235 )]
18236 .into_iter()
18237 .collect(),
18238 ),
18239 ..Default::default()
18240 },
18241 },
18242 )
18243 .await
18244 .unwrap();
18245 Ok(Some(json!(null)))
18246 }
18247 }
18248 })
18249 .next()
18250 .await;
18251
18252 // Applying the code lens command returns a project transaction containing the edits
18253 // sent by the language server in its `workspaceEdit` request.
18254 let transaction = apply.await.unwrap();
18255 assert!(transaction.0.contains_key(&buffer));
18256 buffer.update(cx, |buffer, cx| {
18257 assert_eq!(buffer.text(), "Xa");
18258 buffer.undo(cx);
18259 assert_eq!(buffer.text(), "a");
18260 });
18261}
18262
18263#[gpui::test]
18264async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
18265 init_test(cx, |_| {});
18266
18267 let fs = FakeFs::new(cx.executor());
18268 let main_text = r#"fn main() {
18269println!("1");
18270println!("2");
18271println!("3");
18272println!("4");
18273println!("5");
18274}"#;
18275 let lib_text = "mod foo {}";
18276 fs.insert_tree(
18277 path!("/a"),
18278 json!({
18279 "lib.rs": lib_text,
18280 "main.rs": main_text,
18281 }),
18282 )
18283 .await;
18284
18285 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18286 let (workspace, cx) =
18287 cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
18288 let worktree_id = workspace.update(cx, |workspace, cx| {
18289 workspace.project().update(cx, |project, cx| {
18290 project.worktrees(cx).next().unwrap().read(cx).id()
18291 })
18292 });
18293
18294 let expected_ranges = vec![
18295 Point::new(0, 0)..Point::new(0, 0),
18296 Point::new(1, 0)..Point::new(1, 1),
18297 Point::new(2, 0)..Point::new(2, 2),
18298 Point::new(3, 0)..Point::new(3, 3),
18299 ];
18300
18301 let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
18302 let editor_1 = workspace
18303 .update_in(cx, |workspace, window, cx| {
18304 workspace.open_path(
18305 (worktree_id, "main.rs"),
18306 Some(pane_1.downgrade()),
18307 true,
18308 window,
18309 cx,
18310 )
18311 })
18312 .unwrap()
18313 .await
18314 .downcast::<Editor>()
18315 .unwrap();
18316 pane_1.update(cx, |pane, cx| {
18317 let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
18318 open_editor.update(cx, |editor, cx| {
18319 assert_eq!(
18320 editor.display_text(cx),
18321 main_text,
18322 "Original main.rs text on initial open",
18323 );
18324 assert_eq!(
18325 editor
18326 .selections
18327 .all::<Point>(cx)
18328 .into_iter()
18329 .map(|s| s.range())
18330 .collect::<Vec<_>>(),
18331 vec![Point::zero()..Point::zero()],
18332 "Default selections on initial open",
18333 );
18334 })
18335 });
18336 editor_1.update_in(cx, |editor, window, cx| {
18337 editor.change_selections(None, window, cx, |s| {
18338 s.select_ranges(expected_ranges.clone());
18339 });
18340 });
18341
18342 let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
18343 workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
18344 });
18345 let editor_2 = workspace
18346 .update_in(cx, |workspace, window, cx| {
18347 workspace.open_path(
18348 (worktree_id, "main.rs"),
18349 Some(pane_2.downgrade()),
18350 true,
18351 window,
18352 cx,
18353 )
18354 })
18355 .unwrap()
18356 .await
18357 .downcast::<Editor>()
18358 .unwrap();
18359 pane_2.update(cx, |pane, cx| {
18360 let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
18361 open_editor.update(cx, |editor, cx| {
18362 assert_eq!(
18363 editor.display_text(cx),
18364 main_text,
18365 "Original main.rs text on initial open in another panel",
18366 );
18367 assert_eq!(
18368 editor
18369 .selections
18370 .all::<Point>(cx)
18371 .into_iter()
18372 .map(|s| s.range())
18373 .collect::<Vec<_>>(),
18374 vec![Point::zero()..Point::zero()],
18375 "Default selections on initial open in another panel",
18376 );
18377 })
18378 });
18379
18380 editor_2.update_in(cx, |editor, window, cx| {
18381 editor.fold_ranges(expected_ranges.clone(), false, window, cx);
18382 });
18383
18384 let _other_editor_1 = workspace
18385 .update_in(cx, |workspace, window, cx| {
18386 workspace.open_path(
18387 (worktree_id, "lib.rs"),
18388 Some(pane_1.downgrade()),
18389 true,
18390 window,
18391 cx,
18392 )
18393 })
18394 .unwrap()
18395 .await
18396 .downcast::<Editor>()
18397 .unwrap();
18398 pane_1
18399 .update_in(cx, |pane, window, cx| {
18400 pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
18401 .unwrap()
18402 })
18403 .await
18404 .unwrap();
18405 drop(editor_1);
18406 pane_1.update(cx, |pane, cx| {
18407 pane.active_item()
18408 .unwrap()
18409 .downcast::<Editor>()
18410 .unwrap()
18411 .update(cx, |editor, cx| {
18412 assert_eq!(
18413 editor.display_text(cx),
18414 lib_text,
18415 "Other file should be open and active",
18416 );
18417 });
18418 assert_eq!(pane.items().count(), 1, "No other editors should be open");
18419 });
18420
18421 let _other_editor_2 = workspace
18422 .update_in(cx, |workspace, window, cx| {
18423 workspace.open_path(
18424 (worktree_id, "lib.rs"),
18425 Some(pane_2.downgrade()),
18426 true,
18427 window,
18428 cx,
18429 )
18430 })
18431 .unwrap()
18432 .await
18433 .downcast::<Editor>()
18434 .unwrap();
18435 pane_2
18436 .update_in(cx, |pane, window, cx| {
18437 pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
18438 .unwrap()
18439 })
18440 .await
18441 .unwrap();
18442 drop(editor_2);
18443 pane_2.update(cx, |pane, cx| {
18444 let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
18445 open_editor.update(cx, |editor, cx| {
18446 assert_eq!(
18447 editor.display_text(cx),
18448 lib_text,
18449 "Other file should be open and active in another panel too",
18450 );
18451 });
18452 assert_eq!(
18453 pane.items().count(),
18454 1,
18455 "No other editors should be open in another pane",
18456 );
18457 });
18458
18459 let _editor_1_reopened = workspace
18460 .update_in(cx, |workspace, window, cx| {
18461 workspace.open_path(
18462 (worktree_id, "main.rs"),
18463 Some(pane_1.downgrade()),
18464 true,
18465 window,
18466 cx,
18467 )
18468 })
18469 .unwrap()
18470 .await
18471 .downcast::<Editor>()
18472 .unwrap();
18473 let _editor_2_reopened = workspace
18474 .update_in(cx, |workspace, window, cx| {
18475 workspace.open_path(
18476 (worktree_id, "main.rs"),
18477 Some(pane_2.downgrade()),
18478 true,
18479 window,
18480 cx,
18481 )
18482 })
18483 .unwrap()
18484 .await
18485 .downcast::<Editor>()
18486 .unwrap();
18487 pane_1.update(cx, |pane, cx| {
18488 let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
18489 open_editor.update(cx, |editor, cx| {
18490 assert_eq!(
18491 editor.display_text(cx),
18492 main_text,
18493 "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
18494 );
18495 assert_eq!(
18496 editor
18497 .selections
18498 .all::<Point>(cx)
18499 .into_iter()
18500 .map(|s| s.range())
18501 .collect::<Vec<_>>(),
18502 expected_ranges,
18503 "Previous editor in the 1st panel had selections and should get them restored on reopen",
18504 );
18505 })
18506 });
18507 pane_2.update(cx, |pane, cx| {
18508 let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
18509 open_editor.update(cx, |editor, cx| {
18510 assert_eq!(
18511 editor.display_text(cx),
18512 r#"fn main() {
18513⋯rintln!("1");
18514⋯intln!("2");
18515⋯ntln!("3");
18516println!("4");
18517println!("5");
18518}"#,
18519 "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
18520 );
18521 assert_eq!(
18522 editor
18523 .selections
18524 .all::<Point>(cx)
18525 .into_iter()
18526 .map(|s| s.range())
18527 .collect::<Vec<_>>(),
18528 vec![Point::zero()..Point::zero()],
18529 "Previous editor in the 2nd pane had no selections changed hence should restore none",
18530 );
18531 })
18532 });
18533}
18534
18535#[gpui::test]
18536async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
18537 init_test(cx, |_| {});
18538
18539 let fs = FakeFs::new(cx.executor());
18540 let main_text = r#"fn main() {
18541println!("1");
18542println!("2");
18543println!("3");
18544println!("4");
18545println!("5");
18546}"#;
18547 let lib_text = "mod foo {}";
18548 fs.insert_tree(
18549 path!("/a"),
18550 json!({
18551 "lib.rs": lib_text,
18552 "main.rs": main_text,
18553 }),
18554 )
18555 .await;
18556
18557 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18558 let (workspace, cx) =
18559 cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
18560 let worktree_id = workspace.update(cx, |workspace, cx| {
18561 workspace.project().update(cx, |project, cx| {
18562 project.worktrees(cx).next().unwrap().read(cx).id()
18563 })
18564 });
18565
18566 let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
18567 let editor = workspace
18568 .update_in(cx, |workspace, window, cx| {
18569 workspace.open_path(
18570 (worktree_id, "main.rs"),
18571 Some(pane.downgrade()),
18572 true,
18573 window,
18574 cx,
18575 )
18576 })
18577 .unwrap()
18578 .await
18579 .downcast::<Editor>()
18580 .unwrap();
18581 pane.update(cx, |pane, cx| {
18582 let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
18583 open_editor.update(cx, |editor, cx| {
18584 assert_eq!(
18585 editor.display_text(cx),
18586 main_text,
18587 "Original main.rs text on initial open",
18588 );
18589 })
18590 });
18591 editor.update_in(cx, |editor, window, cx| {
18592 editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
18593 });
18594
18595 cx.update_global(|store: &mut SettingsStore, cx| {
18596 store.update_user_settings::<WorkspaceSettings>(cx, |s| {
18597 s.restore_on_file_reopen = Some(false);
18598 });
18599 });
18600 editor.update_in(cx, |editor, window, cx| {
18601 editor.fold_ranges(
18602 vec![
18603 Point::new(1, 0)..Point::new(1, 1),
18604 Point::new(2, 0)..Point::new(2, 2),
18605 Point::new(3, 0)..Point::new(3, 3),
18606 ],
18607 false,
18608 window,
18609 cx,
18610 );
18611 });
18612 pane.update_in(cx, |pane, window, cx| {
18613 pane.close_all_items(&CloseAllItems::default(), window, cx)
18614 .unwrap()
18615 })
18616 .await
18617 .unwrap();
18618 pane.update(cx, |pane, _| {
18619 assert!(pane.active_item().is_none());
18620 });
18621 cx.update_global(|store: &mut SettingsStore, cx| {
18622 store.update_user_settings::<WorkspaceSettings>(cx, |s| {
18623 s.restore_on_file_reopen = Some(true);
18624 });
18625 });
18626
18627 let _editor_reopened = workspace
18628 .update_in(cx, |workspace, window, cx| {
18629 workspace.open_path(
18630 (worktree_id, "main.rs"),
18631 Some(pane.downgrade()),
18632 true,
18633 window,
18634 cx,
18635 )
18636 })
18637 .unwrap()
18638 .await
18639 .downcast::<Editor>()
18640 .unwrap();
18641 pane.update(cx, |pane, cx| {
18642 let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
18643 open_editor.update(cx, |editor, cx| {
18644 assert_eq!(
18645 editor.display_text(cx),
18646 main_text,
18647 "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
18648 );
18649 })
18650 });
18651}
18652
18653fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
18654 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
18655 point..point
18656}
18657
18658fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
18659 let (text, ranges) = marked_text_ranges(marked_text, true);
18660 assert_eq!(editor.text(cx), text);
18661 assert_eq!(
18662 editor.selections.ranges(cx),
18663 ranges,
18664 "Assert selections are {}",
18665 marked_text
18666 );
18667}
18668
18669pub fn handle_signature_help_request(
18670 cx: &mut EditorLspTestContext,
18671 mocked_response: lsp::SignatureHelp,
18672) -> impl Future<Output = ()> + use<> {
18673 let mut request =
18674 cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
18675 let mocked_response = mocked_response.clone();
18676 async move { Ok(Some(mocked_response)) }
18677 });
18678
18679 async move {
18680 request.next().await;
18681 }
18682}
18683
18684/// Handle completion request passing a marked string specifying where the completion
18685/// should be triggered from using '|' character, what range should be replaced, and what completions
18686/// should be returned using '<' and '>' to delimit the range.
18687///
18688/// Also see `handle_completion_request_with_insert_and_replace`.
18689#[track_caller]
18690pub fn handle_completion_request(
18691 cx: &mut EditorLspTestContext,
18692 marked_string: &str,
18693 completions: Vec<&'static str>,
18694 counter: Arc<AtomicUsize>,
18695) -> impl Future<Output = ()> {
18696 let complete_from_marker: TextRangeMarker = '|'.into();
18697 let replace_range_marker: TextRangeMarker = ('<', '>').into();
18698 let (_, mut marked_ranges) = marked_text_ranges_by(
18699 marked_string,
18700 vec![complete_from_marker.clone(), replace_range_marker.clone()],
18701 );
18702
18703 let complete_from_position =
18704 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
18705 let replace_range =
18706 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
18707
18708 let mut request =
18709 cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
18710 let completions = completions.clone();
18711 counter.fetch_add(1, atomic::Ordering::Release);
18712 async move {
18713 assert_eq!(params.text_document_position.text_document.uri, url.clone());
18714 assert_eq!(
18715 params.text_document_position.position,
18716 complete_from_position
18717 );
18718 Ok(Some(lsp::CompletionResponse::Array(
18719 completions
18720 .iter()
18721 .map(|completion_text| lsp::CompletionItem {
18722 label: completion_text.to_string(),
18723 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18724 range: replace_range,
18725 new_text: completion_text.to_string(),
18726 })),
18727 ..Default::default()
18728 })
18729 .collect(),
18730 )))
18731 }
18732 });
18733
18734 async move {
18735 request.next().await;
18736 }
18737}
18738
18739/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
18740/// given instead, which also contains an `insert` range.
18741///
18742/// This function uses the cursor position to mimic what Rust-Analyzer provides as the `insert` range,
18743/// that is, `replace_range.start..cursor_pos`.
18744pub fn handle_completion_request_with_insert_and_replace(
18745 cx: &mut EditorLspTestContext,
18746 marked_string: &str,
18747 completions: Vec<&'static str>,
18748 counter: Arc<AtomicUsize>,
18749) -> impl Future<Output = ()> {
18750 let complete_from_marker: TextRangeMarker = '|'.into();
18751 let replace_range_marker: TextRangeMarker = ('<', '>').into();
18752 let (_, mut marked_ranges) = marked_text_ranges_by(
18753 marked_string,
18754 vec![complete_from_marker.clone(), replace_range_marker.clone()],
18755 );
18756
18757 let complete_from_position =
18758 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
18759 let replace_range =
18760 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
18761
18762 let mut request =
18763 cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
18764 let completions = completions.clone();
18765 counter.fetch_add(1, atomic::Ordering::Release);
18766 async move {
18767 assert_eq!(params.text_document_position.text_document.uri, url.clone());
18768 assert_eq!(
18769 params.text_document_position.position, complete_from_position,
18770 "marker `|` position doesn't match",
18771 );
18772 Ok(Some(lsp::CompletionResponse::Array(
18773 completions
18774 .iter()
18775 .map(|completion_text| lsp::CompletionItem {
18776 label: completion_text.to_string(),
18777 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
18778 lsp::InsertReplaceEdit {
18779 insert: lsp::Range {
18780 start: replace_range.start,
18781 end: complete_from_position,
18782 },
18783 replace: replace_range,
18784 new_text: completion_text.to_string(),
18785 },
18786 )),
18787 ..Default::default()
18788 })
18789 .collect(),
18790 )))
18791 }
18792 });
18793
18794 async move {
18795 request.next().await;
18796 }
18797}
18798
18799fn handle_resolve_completion_request(
18800 cx: &mut EditorLspTestContext,
18801 edits: Option<Vec<(&'static str, &'static str)>>,
18802) -> impl Future<Output = ()> {
18803 let edits = edits.map(|edits| {
18804 edits
18805 .iter()
18806 .map(|(marked_string, new_text)| {
18807 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
18808 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
18809 lsp::TextEdit::new(replace_range, new_text.to_string())
18810 })
18811 .collect::<Vec<_>>()
18812 });
18813
18814 let mut request =
18815 cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
18816 let edits = edits.clone();
18817 async move {
18818 Ok(lsp::CompletionItem {
18819 additional_text_edits: edits,
18820 ..Default::default()
18821 })
18822 }
18823 });
18824
18825 async move {
18826 request.next().await;
18827 }
18828}
18829
18830pub(crate) fn update_test_language_settings(
18831 cx: &mut TestAppContext,
18832 f: impl Fn(&mut AllLanguageSettingsContent),
18833) {
18834 cx.update(|cx| {
18835 SettingsStore::update_global(cx, |store, cx| {
18836 store.update_user_settings::<AllLanguageSettings>(cx, f);
18837 });
18838 });
18839}
18840
18841pub(crate) fn update_test_project_settings(
18842 cx: &mut TestAppContext,
18843 f: impl Fn(&mut ProjectSettings),
18844) {
18845 cx.update(|cx| {
18846 SettingsStore::update_global(cx, |store, cx| {
18847 store.update_user_settings::<ProjectSettings>(cx, f);
18848 });
18849 });
18850}
18851
18852pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
18853 cx.update(|cx| {
18854 assets::Assets.load_test_fonts(cx);
18855 let store = SettingsStore::test(cx);
18856 cx.set_global(store);
18857 theme::init(theme::LoadThemes::JustBase, cx);
18858 release_channel::init(SemanticVersion::default(), cx);
18859 client::init_settings(cx);
18860 language::init(cx);
18861 Project::init_settings(cx);
18862 workspace::init_settings(cx);
18863 crate::init(cx);
18864 });
18865
18866 update_test_language_settings(cx, f);
18867}
18868
18869#[track_caller]
18870fn assert_hunk_revert(
18871 not_reverted_text_with_selections: &str,
18872 expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
18873 expected_reverted_text_with_selections: &str,
18874 base_text: &str,
18875 cx: &mut EditorLspTestContext,
18876) {
18877 cx.set_state(not_reverted_text_with_selections);
18878 cx.set_head_text(base_text);
18879 cx.executor().run_until_parked();
18880
18881 let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
18882 let snapshot = editor.snapshot(window, cx);
18883 let reverted_hunk_statuses = snapshot
18884 .buffer_snapshot
18885 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
18886 .map(|hunk| hunk.status().kind)
18887 .collect::<Vec<_>>();
18888
18889 editor.git_restore(&Default::default(), window, cx);
18890 reverted_hunk_statuses
18891 });
18892 cx.executor().run_until_parked();
18893 cx.assert_editor_state(expected_reverted_text_with_selections);
18894 assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
18895}