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, DismissEvent, SemanticVersion, TestAppContext, UpdateGlobal,
16 VisualTestContext, WindowBounds, WindowOptions, div,
17};
18use indoc::indoc;
19use language::{
20 BracketPairConfig,
21 Capability::ReadWrite,
22 FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher, LanguageName,
23 Override, Point,
24 language_settings::{
25 AllLanguageSettings, AllLanguageSettingsContent, CompletionSettings,
26 LanguageSettingsContent, LspInsertMode, PrettierSettings,
27 },
28};
29use language_settings::{Formatter, FormatterList, IndentGuideSettings};
30use lsp::CompletionParams;
31use multi_buffer::{IndentGuide, PathKey};
32use parking_lot::Mutex;
33use pretty_assertions::{assert_eq, assert_ne};
34use project::{
35 FakeFs,
36 debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
37 project_settings::{LspSettings, ProjectSettings},
38};
39use serde_json::{self, json};
40use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
41use std::{
42 iter,
43 sync::atomic::{self, AtomicUsize},
44};
45use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
46use text::ToPoint as _;
47use unindent::Unindent;
48use util::{
49 assert_set_eq, path,
50 test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
51 uri,
52};
53use workspace::{
54 CloseAllItems, CloseInactiveItems, NavigationEntry, ViewId,
55 item::{FollowEvent, FollowableItem, Item, ItemHandle},
56};
57
58#[gpui::test]
59fn test_edit_events(cx: &mut TestAppContext) {
60 init_test(cx, |_| {});
61
62 let buffer = cx.new(|cx| {
63 let mut buffer = language::Buffer::local("123456", cx);
64 buffer.set_group_interval(Duration::from_secs(1));
65 buffer
66 });
67
68 let events = Rc::new(RefCell::new(Vec::new()));
69 let editor1 = cx.add_window({
70 let events = events.clone();
71 |window, cx| {
72 let entity = cx.entity().clone();
73 cx.subscribe_in(
74 &entity,
75 window,
76 move |_, _, event: &EditorEvent, _, _| match event {
77 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
78 EditorEvent::BufferEdited => {
79 events.borrow_mut().push(("editor1", "buffer edited"))
80 }
81 _ => {}
82 },
83 )
84 .detach();
85 Editor::for_buffer(buffer.clone(), None, window, cx)
86 }
87 });
88
89 let editor2 = cx.add_window({
90 let events = events.clone();
91 |window, cx| {
92 cx.subscribe_in(
93 &cx.entity().clone(),
94 window,
95 move |_, _, event: &EditorEvent, _, _| match event {
96 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
97 EditorEvent::BufferEdited => {
98 events.borrow_mut().push(("editor2", "buffer edited"))
99 }
100 _ => {}
101 },
102 )
103 .detach();
104 Editor::for_buffer(buffer.clone(), None, window, cx)
105 }
106 });
107
108 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
109
110 // Mutating editor 1 will emit an `Edited` event only for that editor.
111 _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
112 assert_eq!(
113 mem::take(&mut *events.borrow_mut()),
114 [
115 ("editor1", "edited"),
116 ("editor1", "buffer edited"),
117 ("editor2", "buffer edited"),
118 ]
119 );
120
121 // Mutating editor 2 will emit an `Edited` event only for that editor.
122 _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
123 assert_eq!(
124 mem::take(&mut *events.borrow_mut()),
125 [
126 ("editor2", "edited"),
127 ("editor1", "buffer edited"),
128 ("editor2", "buffer edited"),
129 ]
130 );
131
132 // Undoing on editor 1 will emit an `Edited` event only for that editor.
133 _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
134 assert_eq!(
135 mem::take(&mut *events.borrow_mut()),
136 [
137 ("editor1", "edited"),
138 ("editor1", "buffer edited"),
139 ("editor2", "buffer edited"),
140 ]
141 );
142
143 // Redoing on editor 1 will emit an `Edited` event only for that editor.
144 _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
145 assert_eq!(
146 mem::take(&mut *events.borrow_mut()),
147 [
148 ("editor1", "edited"),
149 ("editor1", "buffer edited"),
150 ("editor2", "buffer edited"),
151 ]
152 );
153
154 // Undoing on editor 2 will emit an `Edited` event only for that editor.
155 _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
156 assert_eq!(
157 mem::take(&mut *events.borrow_mut()),
158 [
159 ("editor2", "edited"),
160 ("editor1", "buffer edited"),
161 ("editor2", "buffer edited"),
162 ]
163 );
164
165 // Redoing on editor 2 will emit an `Edited` event only for that editor.
166 _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
167 assert_eq!(
168 mem::take(&mut *events.borrow_mut()),
169 [
170 ("editor2", "edited"),
171 ("editor1", "buffer edited"),
172 ("editor2", "buffer edited"),
173 ]
174 );
175
176 // No event is emitted when the mutation is a no-op.
177 _ = editor2.update(cx, |editor, window, cx| {
178 editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
179
180 editor.backspace(&Backspace, window, cx);
181 });
182 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
183}
184
185#[gpui::test]
186fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
187 init_test(cx, |_| {});
188
189 let mut now = Instant::now();
190 let group_interval = Duration::from_millis(1);
191 let buffer = cx.new(|cx| {
192 let mut buf = language::Buffer::local("123456", cx);
193 buf.set_group_interval(group_interval);
194 buf
195 });
196 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
197 let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
198
199 _ = editor.update(cx, |editor, window, cx| {
200 editor.start_transaction_at(now, window, cx);
201 editor.change_selections(None, window, cx, |s| s.select_ranges([2..4]));
202
203 editor.insert("cd", window, cx);
204 editor.end_transaction_at(now, cx);
205 assert_eq!(editor.text(cx), "12cd56");
206 assert_eq!(editor.selections.ranges(cx), vec![4..4]);
207
208 editor.start_transaction_at(now, window, cx);
209 editor.change_selections(None, window, cx, |s| s.select_ranges([4..5]));
210 editor.insert("e", window, cx);
211 editor.end_transaction_at(now, cx);
212 assert_eq!(editor.text(cx), "12cde6");
213 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
214
215 now += group_interval + Duration::from_millis(1);
216 editor.change_selections(None, window, cx, |s| s.select_ranges([2..2]));
217
218 // Simulate an edit in another editor
219 buffer.update(cx, |buffer, cx| {
220 buffer.start_transaction_at(now, cx);
221 buffer.edit([(0..1, "a")], None, cx);
222 buffer.edit([(1..1, "b")], None, cx);
223 buffer.end_transaction_at(now, cx);
224 });
225
226 assert_eq!(editor.text(cx), "ab2cde6");
227 assert_eq!(editor.selections.ranges(cx), vec![3..3]);
228
229 // Last transaction happened past the group interval in a different editor.
230 // Undo it individually and don't restore selections.
231 editor.undo(&Undo, window, cx);
232 assert_eq!(editor.text(cx), "12cde6");
233 assert_eq!(editor.selections.ranges(cx), vec![2..2]);
234
235 // First two transactions happened within the group interval in this editor.
236 // Undo them together and restore selections.
237 editor.undo(&Undo, window, cx);
238 editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
239 assert_eq!(editor.text(cx), "123456");
240 assert_eq!(editor.selections.ranges(cx), vec![0..0]);
241
242 // Redo the first two transactions together.
243 editor.redo(&Redo, window, cx);
244 assert_eq!(editor.text(cx), "12cde6");
245 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
246
247 // Redo the last transaction on its own.
248 editor.redo(&Redo, window, cx);
249 assert_eq!(editor.text(cx), "ab2cde6");
250 assert_eq!(editor.selections.ranges(cx), vec![6..6]);
251
252 // Test empty transactions.
253 editor.start_transaction_at(now, window, cx);
254 editor.end_transaction_at(now, cx);
255 editor.undo(&Undo, window, cx);
256 assert_eq!(editor.text(cx), "12cde6");
257 });
258}
259
260#[gpui::test]
261fn test_ime_composition(cx: &mut TestAppContext) {
262 init_test(cx, |_| {});
263
264 let buffer = cx.new(|cx| {
265 let mut buffer = language::Buffer::local("abcde", cx);
266 // Ensure automatic grouping doesn't occur.
267 buffer.set_group_interval(Duration::ZERO);
268 buffer
269 });
270
271 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
272 cx.add_window(|window, cx| {
273 let mut editor = build_editor(buffer.clone(), window, cx);
274
275 // Start a new IME composition.
276 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
277 editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
278 editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
279 assert_eq!(editor.text(cx), "äbcde");
280 assert_eq!(
281 editor.marked_text_ranges(cx),
282 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
283 );
284
285 // Finalize IME composition.
286 editor.replace_text_in_range(None, "ā", window, cx);
287 assert_eq!(editor.text(cx), "ābcde");
288 assert_eq!(editor.marked_text_ranges(cx), None);
289
290 // IME composition edits are grouped and are undone/redone at once.
291 editor.undo(&Default::default(), window, cx);
292 assert_eq!(editor.text(cx), "abcde");
293 assert_eq!(editor.marked_text_ranges(cx), None);
294 editor.redo(&Default::default(), window, cx);
295 assert_eq!(editor.text(cx), "ābcde");
296 assert_eq!(editor.marked_text_ranges(cx), None);
297
298 // Start a new IME composition.
299 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
300 assert_eq!(
301 editor.marked_text_ranges(cx),
302 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
303 );
304
305 // Undoing during an IME composition cancels it.
306 editor.undo(&Default::default(), window, cx);
307 assert_eq!(editor.text(cx), "ābcde");
308 assert_eq!(editor.marked_text_ranges(cx), None);
309
310 // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
311 editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
312 assert_eq!(editor.text(cx), "ābcdè");
313 assert_eq!(
314 editor.marked_text_ranges(cx),
315 Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
316 );
317
318 // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
319 editor.replace_text_in_range(Some(4..999), "ę", window, cx);
320 assert_eq!(editor.text(cx), "ābcdę");
321 assert_eq!(editor.marked_text_ranges(cx), None);
322
323 // Start a new IME composition with multiple cursors.
324 editor.change_selections(None, window, cx, |s| {
325 s.select_ranges([
326 OffsetUtf16(1)..OffsetUtf16(1),
327 OffsetUtf16(3)..OffsetUtf16(3),
328 OffsetUtf16(5)..OffsetUtf16(5),
329 ])
330 });
331 editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
332 assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
333 assert_eq!(
334 editor.marked_text_ranges(cx),
335 Some(vec![
336 OffsetUtf16(0)..OffsetUtf16(3),
337 OffsetUtf16(4)..OffsetUtf16(7),
338 OffsetUtf16(8)..OffsetUtf16(11)
339 ])
340 );
341
342 // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
343 editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
344 assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
345 assert_eq!(
346 editor.marked_text_ranges(cx),
347 Some(vec![
348 OffsetUtf16(1)..OffsetUtf16(2),
349 OffsetUtf16(5)..OffsetUtf16(6),
350 OffsetUtf16(9)..OffsetUtf16(10)
351 ])
352 );
353
354 // Finalize IME composition with multiple cursors.
355 editor.replace_text_in_range(Some(9..10), "2", window, cx);
356 assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
357 assert_eq!(editor.marked_text_ranges(cx), None);
358
359 editor
360 });
361}
362
363#[gpui::test]
364fn test_selection_with_mouse(cx: &mut TestAppContext) {
365 init_test(cx, |_| {});
366
367 let editor = cx.add_window(|window, cx| {
368 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
369 build_editor(buffer, window, cx)
370 });
371
372 _ = editor.update(cx, |editor, window, cx| {
373 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
374 });
375 assert_eq!(
376 editor
377 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
378 .unwrap(),
379 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
380 );
381
382 _ = editor.update(cx, |editor, window, cx| {
383 editor.update_selection(
384 DisplayPoint::new(DisplayRow(3), 3),
385 0,
386 gpui::Point::<f32>::default(),
387 window,
388 cx,
389 );
390 });
391
392 assert_eq!(
393 editor
394 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
395 .unwrap(),
396 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
397 );
398
399 _ = editor.update(cx, |editor, window, cx| {
400 editor.update_selection(
401 DisplayPoint::new(DisplayRow(1), 1),
402 0,
403 gpui::Point::<f32>::default(),
404 window,
405 cx,
406 );
407 });
408
409 assert_eq!(
410 editor
411 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
412 .unwrap(),
413 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
414 );
415
416 _ = editor.update(cx, |editor, window, cx| {
417 editor.end_selection(window, cx);
418 editor.update_selection(
419 DisplayPoint::new(DisplayRow(3), 3),
420 0,
421 gpui::Point::<f32>::default(),
422 window,
423 cx,
424 );
425 });
426
427 assert_eq!(
428 editor
429 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
430 .unwrap(),
431 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
432 );
433
434 _ = editor.update(cx, |editor, window, cx| {
435 editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
436 editor.update_selection(
437 DisplayPoint::new(DisplayRow(0), 0),
438 0,
439 gpui::Point::<f32>::default(),
440 window,
441 cx,
442 );
443 });
444
445 assert_eq!(
446 editor
447 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
448 .unwrap(),
449 [
450 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
451 DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
452 ]
453 );
454
455 _ = editor.update(cx, |editor, window, cx| {
456 editor.end_selection(window, cx);
457 });
458
459 assert_eq!(
460 editor
461 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
462 .unwrap(),
463 [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
464 );
465}
466
467#[gpui::test]
468fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
469 init_test(cx, |_| {});
470
471 let editor = cx.add_window(|window, cx| {
472 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
473 build_editor(buffer, window, cx)
474 });
475
476 _ = editor.update(cx, |editor, window, cx| {
477 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
478 });
479
480 _ = editor.update(cx, |editor, window, cx| {
481 editor.end_selection(window, cx);
482 });
483
484 _ = editor.update(cx, |editor, window, cx| {
485 editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
486 });
487
488 _ = editor.update(cx, |editor, window, cx| {
489 editor.end_selection(window, cx);
490 });
491
492 assert_eq!(
493 editor
494 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
495 .unwrap(),
496 [
497 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
498 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
499 ]
500 );
501
502 _ = editor.update(cx, |editor, window, cx| {
503 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
504 });
505
506 _ = editor.update(cx, |editor, window, cx| {
507 editor.end_selection(window, cx);
508 });
509
510 assert_eq!(
511 editor
512 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
513 .unwrap(),
514 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
515 );
516}
517
518#[gpui::test]
519fn test_canceling_pending_selection(cx: &mut TestAppContext) {
520 init_test(cx, |_| {});
521
522 let editor = cx.add_window(|window, cx| {
523 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
524 build_editor(buffer, window, cx)
525 });
526
527 _ = editor.update(cx, |editor, window, cx| {
528 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
529 assert_eq!(
530 editor.selections.display_ranges(cx),
531 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
532 );
533 });
534
535 _ = editor.update(cx, |editor, window, cx| {
536 editor.update_selection(
537 DisplayPoint::new(DisplayRow(3), 3),
538 0,
539 gpui::Point::<f32>::default(),
540 window,
541 cx,
542 );
543 assert_eq!(
544 editor.selections.display_ranges(cx),
545 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
546 );
547 });
548
549 _ = editor.update(cx, |editor, window, cx| {
550 editor.cancel(&Cancel, window, cx);
551 editor.update_selection(
552 DisplayPoint::new(DisplayRow(1), 1),
553 0,
554 gpui::Point::<f32>::default(),
555 window,
556 cx,
557 );
558 assert_eq!(
559 editor.selections.display_ranges(cx),
560 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
561 );
562 });
563}
564
565#[gpui::test]
566fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
567 init_test(cx, |_| {});
568
569 let editor = cx.add_window(|window, cx| {
570 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
571 build_editor(buffer, window, cx)
572 });
573
574 _ = editor.update(cx, |editor, window, cx| {
575 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
576 assert_eq!(
577 editor.selections.display_ranges(cx),
578 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
579 );
580
581 editor.move_down(&Default::default(), window, cx);
582 assert_eq!(
583 editor.selections.display_ranges(cx),
584 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
585 );
586
587 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
588 assert_eq!(
589 editor.selections.display_ranges(cx),
590 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
591 );
592
593 editor.move_up(&Default::default(), window, cx);
594 assert_eq!(
595 editor.selections.display_ranges(cx),
596 [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
597 );
598 });
599}
600
601#[gpui::test]
602fn test_clone(cx: &mut TestAppContext) {
603 init_test(cx, |_| {});
604
605 let (text, selection_ranges) = marked_text_ranges(
606 indoc! {"
607 one
608 two
609 threeˇ
610 four
611 fiveˇ
612 "},
613 true,
614 );
615
616 let editor = cx.add_window(|window, cx| {
617 let buffer = MultiBuffer::build_simple(&text, cx);
618 build_editor(buffer, window, cx)
619 });
620
621 _ = editor.update(cx, |editor, window, cx| {
622 editor.change_selections(None, window, cx, |s| {
623 s.select_ranges(selection_ranges.clone())
624 });
625 editor.fold_creases(
626 vec![
627 Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
628 Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
629 ],
630 true,
631 window,
632 cx,
633 );
634 });
635
636 let cloned_editor = editor
637 .update(cx, |editor, _, cx| {
638 cx.open_window(Default::default(), |window, cx| {
639 cx.new(|cx| editor.clone(window, cx))
640 })
641 })
642 .unwrap()
643 .unwrap();
644
645 let snapshot = editor
646 .update(cx, |e, window, cx| e.snapshot(window, cx))
647 .unwrap();
648 let cloned_snapshot = cloned_editor
649 .update(cx, |e, window, cx| e.snapshot(window, cx))
650 .unwrap();
651
652 assert_eq!(
653 cloned_editor
654 .update(cx, |e, _, cx| e.display_text(cx))
655 .unwrap(),
656 editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
657 );
658 assert_eq!(
659 cloned_snapshot
660 .folds_in_range(0..text.len())
661 .collect::<Vec<_>>(),
662 snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
663 );
664 assert_set_eq!(
665 cloned_editor
666 .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
667 .unwrap(),
668 editor
669 .update(cx, |editor, _, cx| editor.selections.ranges(cx))
670 .unwrap()
671 );
672 assert_set_eq!(
673 cloned_editor
674 .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
675 .unwrap(),
676 editor
677 .update(cx, |e, _, cx| e.selections.display_ranges(cx))
678 .unwrap()
679 );
680}
681
682#[gpui::test]
683async fn test_navigation_history(cx: &mut TestAppContext) {
684 init_test(cx, |_| {});
685
686 use workspace::item::Item;
687
688 let fs = FakeFs::new(cx.executor());
689 let project = Project::test(fs, [], cx).await;
690 let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
691 let pane = workspace
692 .update(cx, |workspace, _, _| workspace.active_pane().clone())
693 .unwrap();
694
695 _ = workspace.update(cx, |_v, window, cx| {
696 cx.new(|cx| {
697 let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
698 let mut editor = build_editor(buffer.clone(), window, cx);
699 let handle = cx.entity();
700 editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
701
702 fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
703 editor.nav_history.as_mut().unwrap().pop_backward(cx)
704 }
705
706 // Move the cursor a small distance.
707 // Nothing is added to the navigation history.
708 editor.change_selections(None, window, cx, |s| {
709 s.select_display_ranges([
710 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
711 ])
712 });
713 editor.change_selections(None, window, cx, |s| {
714 s.select_display_ranges([
715 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
716 ])
717 });
718 assert!(pop_history(&mut editor, cx).is_none());
719
720 // Move the cursor a large distance.
721 // The history can jump back to the previous position.
722 editor.change_selections(None, window, cx, |s| {
723 s.select_display_ranges([
724 DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
725 ])
726 });
727 let nav_entry = pop_history(&mut editor, cx).unwrap();
728 editor.navigate(nav_entry.data.unwrap(), window, cx);
729 assert_eq!(nav_entry.item.id(), cx.entity_id());
730 assert_eq!(
731 editor.selections.display_ranges(cx),
732 &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
733 );
734 assert!(pop_history(&mut editor, cx).is_none());
735
736 // Move the cursor a small distance via the mouse.
737 // Nothing is added to the navigation history.
738 editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
739 editor.end_selection(window, cx);
740 assert_eq!(
741 editor.selections.display_ranges(cx),
742 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
743 );
744 assert!(pop_history(&mut editor, cx).is_none());
745
746 // Move the cursor a large distance via the mouse.
747 // The history can jump back to the previous position.
748 editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
749 editor.end_selection(window, cx);
750 assert_eq!(
751 editor.selections.display_ranges(cx),
752 &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
753 );
754 let nav_entry = pop_history(&mut editor, cx).unwrap();
755 editor.navigate(nav_entry.data.unwrap(), window, cx);
756 assert_eq!(nav_entry.item.id(), cx.entity_id());
757 assert_eq!(
758 editor.selections.display_ranges(cx),
759 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
760 );
761 assert!(pop_history(&mut editor, cx).is_none());
762
763 // Set scroll position to check later
764 editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), window, cx);
765 let original_scroll_position = editor.scroll_manager.anchor();
766
767 // Jump to the end of the document and adjust scroll
768 editor.move_to_end(&MoveToEnd, window, cx);
769 editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), window, cx);
770 assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
771
772 let nav_entry = pop_history(&mut editor, cx).unwrap();
773 editor.navigate(nav_entry.data.unwrap(), window, cx);
774 assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
775
776 // Ensure we don't panic when navigation data contains invalid anchors *and* points.
777 let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
778 invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
779 let invalid_point = Point::new(9999, 0);
780 editor.navigate(
781 Box::new(NavigationData {
782 cursor_anchor: invalid_anchor,
783 cursor_position: invalid_point,
784 scroll_anchor: ScrollAnchor {
785 anchor: invalid_anchor,
786 offset: Default::default(),
787 },
788 scroll_top_row: invalid_point.row,
789 }),
790 window,
791 cx,
792 );
793 assert_eq!(
794 editor.selections.display_ranges(cx),
795 &[editor.max_point(cx)..editor.max_point(cx)]
796 );
797 assert_eq!(
798 editor.scroll_position(cx),
799 gpui::Point::new(0., editor.max_point(cx).row().as_f32())
800 );
801
802 editor
803 })
804 });
805}
806
807#[gpui::test]
808fn test_cancel(cx: &mut TestAppContext) {
809 init_test(cx, |_| {});
810
811 let editor = cx.add_window(|window, cx| {
812 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
813 build_editor(buffer, window, cx)
814 });
815
816 _ = editor.update(cx, |editor, window, cx| {
817 editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
818 editor.update_selection(
819 DisplayPoint::new(DisplayRow(1), 1),
820 0,
821 gpui::Point::<f32>::default(),
822 window,
823 cx,
824 );
825 editor.end_selection(window, cx);
826
827 editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
828 editor.update_selection(
829 DisplayPoint::new(DisplayRow(0), 3),
830 0,
831 gpui::Point::<f32>::default(),
832 window,
833 cx,
834 );
835 editor.end_selection(window, cx);
836 assert_eq!(
837 editor.selections.display_ranges(cx),
838 [
839 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
840 DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
841 ]
842 );
843 });
844
845 _ = editor.update(cx, |editor, window, cx| {
846 editor.cancel(&Cancel, window, cx);
847 assert_eq!(
848 editor.selections.display_ranges(cx),
849 [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
850 );
851 });
852
853 _ = editor.update(cx, |editor, window, cx| {
854 editor.cancel(&Cancel, window, cx);
855 assert_eq!(
856 editor.selections.display_ranges(cx),
857 [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
858 );
859 });
860}
861
862#[gpui::test]
863fn test_fold_action(cx: &mut TestAppContext) {
864 init_test(cx, |_| {});
865
866 let editor = cx.add_window(|window, cx| {
867 let buffer = MultiBuffer::build_simple(
868 &"
869 impl Foo {
870 // Hello!
871
872 fn a() {
873 1
874 }
875
876 fn b() {
877 2
878 }
879
880 fn c() {
881 3
882 }
883 }
884 "
885 .unindent(),
886 cx,
887 );
888 build_editor(buffer.clone(), window, cx)
889 });
890
891 _ = editor.update(cx, |editor, window, cx| {
892 editor.change_selections(None, window, cx, |s| {
893 s.select_display_ranges([
894 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
895 ]);
896 });
897 editor.fold(&Fold, window, cx);
898 assert_eq!(
899 editor.display_text(cx),
900 "
901 impl Foo {
902 // Hello!
903
904 fn a() {
905 1
906 }
907
908 fn b() {⋯
909 }
910
911 fn c() {⋯
912 }
913 }
914 "
915 .unindent(),
916 );
917
918 editor.fold(&Fold, window, cx);
919 assert_eq!(
920 editor.display_text(cx),
921 "
922 impl Foo {⋯
923 }
924 "
925 .unindent(),
926 );
927
928 editor.unfold_lines(&UnfoldLines, window, cx);
929 assert_eq!(
930 editor.display_text(cx),
931 "
932 impl Foo {
933 // Hello!
934
935 fn a() {
936 1
937 }
938
939 fn b() {⋯
940 }
941
942 fn c() {⋯
943 }
944 }
945 "
946 .unindent(),
947 );
948
949 editor.unfold_lines(&UnfoldLines, window, cx);
950 assert_eq!(
951 editor.display_text(cx),
952 editor.buffer.read(cx).read(cx).text()
953 );
954 });
955}
956
957#[gpui::test]
958fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
959 init_test(cx, |_| {});
960
961 let editor = cx.add_window(|window, cx| {
962 let buffer = MultiBuffer::build_simple(
963 &"
964 class Foo:
965 # Hello!
966
967 def a():
968 print(1)
969
970 def b():
971 print(2)
972
973 def c():
974 print(3)
975 "
976 .unindent(),
977 cx,
978 );
979 build_editor(buffer.clone(), window, cx)
980 });
981
982 _ = editor.update(cx, |editor, window, cx| {
983 editor.change_selections(None, window, cx, |s| {
984 s.select_display_ranges([
985 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
986 ]);
987 });
988 editor.fold(&Fold, window, cx);
989 assert_eq!(
990 editor.display_text(cx),
991 "
992 class Foo:
993 # Hello!
994
995 def a():
996 print(1)
997
998 def b():⋯
999
1000 def c():⋯
1001 "
1002 .unindent(),
1003 );
1004
1005 editor.fold(&Fold, window, cx);
1006 assert_eq!(
1007 editor.display_text(cx),
1008 "
1009 class Foo:⋯
1010 "
1011 .unindent(),
1012 );
1013
1014 editor.unfold_lines(&UnfoldLines, window, cx);
1015 assert_eq!(
1016 editor.display_text(cx),
1017 "
1018 class Foo:
1019 # Hello!
1020
1021 def a():
1022 print(1)
1023
1024 def b():⋯
1025
1026 def c():⋯
1027 "
1028 .unindent(),
1029 );
1030
1031 editor.unfold_lines(&UnfoldLines, window, cx);
1032 assert_eq!(
1033 editor.display_text(cx),
1034 editor.buffer.read(cx).read(cx).text()
1035 );
1036 });
1037}
1038
1039#[gpui::test]
1040fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
1041 init_test(cx, |_| {});
1042
1043 let editor = cx.add_window(|window, cx| {
1044 let buffer = MultiBuffer::build_simple(
1045 &"
1046 class Foo:
1047 # Hello!
1048
1049 def a():
1050 print(1)
1051
1052 def b():
1053 print(2)
1054
1055
1056 def c():
1057 print(3)
1058
1059
1060 "
1061 .unindent(),
1062 cx,
1063 );
1064 build_editor(buffer.clone(), window, cx)
1065 });
1066
1067 _ = editor.update(cx, |editor, window, cx| {
1068 editor.change_selections(None, window, cx, |s| {
1069 s.select_display_ranges([
1070 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
1071 ]);
1072 });
1073 editor.fold(&Fold, window, cx);
1074 assert_eq!(
1075 editor.display_text(cx),
1076 "
1077 class Foo:
1078 # Hello!
1079
1080 def a():
1081 print(1)
1082
1083 def b():⋯
1084
1085
1086 def c():⋯
1087
1088
1089 "
1090 .unindent(),
1091 );
1092
1093 editor.fold(&Fold, window, cx);
1094 assert_eq!(
1095 editor.display_text(cx),
1096 "
1097 class Foo:⋯
1098
1099
1100 "
1101 .unindent(),
1102 );
1103
1104 editor.unfold_lines(&UnfoldLines, window, cx);
1105 assert_eq!(
1106 editor.display_text(cx),
1107 "
1108 class Foo:
1109 # Hello!
1110
1111 def a():
1112 print(1)
1113
1114 def b():⋯
1115
1116
1117 def c():⋯
1118
1119
1120 "
1121 .unindent(),
1122 );
1123
1124 editor.unfold_lines(&UnfoldLines, window, cx);
1125 assert_eq!(
1126 editor.display_text(cx),
1127 editor.buffer.read(cx).read(cx).text()
1128 );
1129 });
1130}
1131
1132#[gpui::test]
1133fn test_fold_at_level(cx: &mut TestAppContext) {
1134 init_test(cx, |_| {});
1135
1136 let editor = cx.add_window(|window, cx| {
1137 let buffer = MultiBuffer::build_simple(
1138 &"
1139 class Foo:
1140 # Hello!
1141
1142 def a():
1143 print(1)
1144
1145 def b():
1146 print(2)
1147
1148
1149 class Bar:
1150 # World!
1151
1152 def a():
1153 print(1)
1154
1155 def b():
1156 print(2)
1157
1158
1159 "
1160 .unindent(),
1161 cx,
1162 );
1163 build_editor(buffer.clone(), window, cx)
1164 });
1165
1166 _ = editor.update(cx, |editor, window, cx| {
1167 editor.fold_at_level(&FoldAtLevel(2), window, cx);
1168 assert_eq!(
1169 editor.display_text(cx),
1170 "
1171 class Foo:
1172 # Hello!
1173
1174 def a():⋯
1175
1176 def b():⋯
1177
1178
1179 class Bar:
1180 # World!
1181
1182 def a():⋯
1183
1184 def b():⋯
1185
1186
1187 "
1188 .unindent(),
1189 );
1190
1191 editor.fold_at_level(&FoldAtLevel(1), window, cx);
1192 assert_eq!(
1193 editor.display_text(cx),
1194 "
1195 class Foo:⋯
1196
1197
1198 class Bar:⋯
1199
1200
1201 "
1202 .unindent(),
1203 );
1204
1205 editor.unfold_all(&UnfoldAll, window, cx);
1206 editor.fold_at_level(&FoldAtLevel(0), window, cx);
1207 assert_eq!(
1208 editor.display_text(cx),
1209 "
1210 class Foo:
1211 # Hello!
1212
1213 def a():
1214 print(1)
1215
1216 def b():
1217 print(2)
1218
1219
1220 class Bar:
1221 # World!
1222
1223 def a():
1224 print(1)
1225
1226 def b():
1227 print(2)
1228
1229
1230 "
1231 .unindent(),
1232 );
1233
1234 assert_eq!(
1235 editor.display_text(cx),
1236 editor.buffer.read(cx).read(cx).text()
1237 );
1238 });
1239}
1240
1241#[gpui::test]
1242fn test_move_cursor(cx: &mut TestAppContext) {
1243 init_test(cx, |_| {});
1244
1245 let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
1246 let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
1247
1248 buffer.update(cx, |buffer, cx| {
1249 buffer.edit(
1250 vec![
1251 (Point::new(1, 0)..Point::new(1, 0), "\t"),
1252 (Point::new(1, 1)..Point::new(1, 1), "\t"),
1253 ],
1254 None,
1255 cx,
1256 );
1257 });
1258 _ = editor.update(cx, |editor, window, cx| {
1259 assert_eq!(
1260 editor.selections.display_ranges(cx),
1261 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1262 );
1263
1264 editor.move_down(&MoveDown, window, cx);
1265 assert_eq!(
1266 editor.selections.display_ranges(cx),
1267 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1268 );
1269
1270 editor.move_right(&MoveRight, window, cx);
1271 assert_eq!(
1272 editor.selections.display_ranges(cx),
1273 &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
1274 );
1275
1276 editor.move_left(&MoveLeft, window, cx);
1277 assert_eq!(
1278 editor.selections.display_ranges(cx),
1279 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1280 );
1281
1282 editor.move_up(&MoveUp, window, cx);
1283 assert_eq!(
1284 editor.selections.display_ranges(cx),
1285 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1286 );
1287
1288 editor.move_to_end(&MoveToEnd, window, cx);
1289 assert_eq!(
1290 editor.selections.display_ranges(cx),
1291 &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
1292 );
1293
1294 editor.move_to_beginning(&MoveToBeginning, window, cx);
1295 assert_eq!(
1296 editor.selections.display_ranges(cx),
1297 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1298 );
1299
1300 editor.change_selections(None, window, cx, |s| {
1301 s.select_display_ranges([
1302 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
1303 ]);
1304 });
1305 editor.select_to_beginning(&SelectToBeginning, window, cx);
1306 assert_eq!(
1307 editor.selections.display_ranges(cx),
1308 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
1309 );
1310
1311 editor.select_to_end(&SelectToEnd, window, cx);
1312 assert_eq!(
1313 editor.selections.display_ranges(cx),
1314 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
1315 );
1316 });
1317}
1318
1319#[gpui::test]
1320fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
1321 init_test(cx, |_| {});
1322
1323 let editor = cx.add_window(|window, cx| {
1324 let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
1325 build_editor(buffer.clone(), window, cx)
1326 });
1327
1328 assert_eq!('🟥'.len_utf8(), 4);
1329 assert_eq!('α'.len_utf8(), 2);
1330
1331 _ = editor.update(cx, |editor, window, cx| {
1332 editor.fold_creases(
1333 vec![
1334 Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
1335 Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
1336 Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
1337 ],
1338 true,
1339 window,
1340 cx,
1341 );
1342 assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
1343
1344 editor.move_right(&MoveRight, window, cx);
1345 assert_eq!(
1346 editor.selections.display_ranges(cx),
1347 &[empty_range(0, "🟥".len())]
1348 );
1349 editor.move_right(&MoveRight, window, cx);
1350 assert_eq!(
1351 editor.selections.display_ranges(cx),
1352 &[empty_range(0, "🟥🟧".len())]
1353 );
1354 editor.move_right(&MoveRight, window, cx);
1355 assert_eq!(
1356 editor.selections.display_ranges(cx),
1357 &[empty_range(0, "🟥🟧⋯".len())]
1358 );
1359
1360 editor.move_down(&MoveDown, window, cx);
1361 assert_eq!(
1362 editor.selections.display_ranges(cx),
1363 &[empty_range(1, "ab⋯e".len())]
1364 );
1365 editor.move_left(&MoveLeft, window, cx);
1366 assert_eq!(
1367 editor.selections.display_ranges(cx),
1368 &[empty_range(1, "ab⋯".len())]
1369 );
1370 editor.move_left(&MoveLeft, window, cx);
1371 assert_eq!(
1372 editor.selections.display_ranges(cx),
1373 &[empty_range(1, "ab".len())]
1374 );
1375 editor.move_left(&MoveLeft, window, cx);
1376 assert_eq!(
1377 editor.selections.display_ranges(cx),
1378 &[empty_range(1, "a".len())]
1379 );
1380
1381 editor.move_down(&MoveDown, window, cx);
1382 assert_eq!(
1383 editor.selections.display_ranges(cx),
1384 &[empty_range(2, "α".len())]
1385 );
1386 editor.move_right(&MoveRight, window, cx);
1387 assert_eq!(
1388 editor.selections.display_ranges(cx),
1389 &[empty_range(2, "αβ".len())]
1390 );
1391 editor.move_right(&MoveRight, window, cx);
1392 assert_eq!(
1393 editor.selections.display_ranges(cx),
1394 &[empty_range(2, "αβ⋯".len())]
1395 );
1396 editor.move_right(&MoveRight, window, cx);
1397 assert_eq!(
1398 editor.selections.display_ranges(cx),
1399 &[empty_range(2, "αβ⋯ε".len())]
1400 );
1401
1402 editor.move_up(&MoveUp, window, cx);
1403 assert_eq!(
1404 editor.selections.display_ranges(cx),
1405 &[empty_range(1, "ab⋯e".len())]
1406 );
1407 editor.move_down(&MoveDown, window, cx);
1408 assert_eq!(
1409 editor.selections.display_ranges(cx),
1410 &[empty_range(2, "αβ⋯ε".len())]
1411 );
1412 editor.move_up(&MoveUp, window, cx);
1413 assert_eq!(
1414 editor.selections.display_ranges(cx),
1415 &[empty_range(1, "ab⋯e".len())]
1416 );
1417
1418 editor.move_up(&MoveUp, window, cx);
1419 assert_eq!(
1420 editor.selections.display_ranges(cx),
1421 &[empty_range(0, "🟥🟧".len())]
1422 );
1423 editor.move_left(&MoveLeft, window, cx);
1424 assert_eq!(
1425 editor.selections.display_ranges(cx),
1426 &[empty_range(0, "🟥".len())]
1427 );
1428 editor.move_left(&MoveLeft, window, cx);
1429 assert_eq!(
1430 editor.selections.display_ranges(cx),
1431 &[empty_range(0, "".len())]
1432 );
1433 });
1434}
1435
1436#[gpui::test]
1437fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
1438 init_test(cx, |_| {});
1439
1440 let editor = cx.add_window(|window, cx| {
1441 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
1442 build_editor(buffer.clone(), window, cx)
1443 });
1444 _ = editor.update(cx, |editor, window, cx| {
1445 editor.change_selections(None, window, cx, |s| {
1446 s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
1447 });
1448
1449 // moving above start of document should move selection to start of document,
1450 // but the next move down should still be at the original goal_x
1451 editor.move_up(&MoveUp, window, cx);
1452 assert_eq!(
1453 editor.selections.display_ranges(cx),
1454 &[empty_range(0, "".len())]
1455 );
1456
1457 editor.move_down(&MoveDown, window, cx);
1458 assert_eq!(
1459 editor.selections.display_ranges(cx),
1460 &[empty_range(1, "abcd".len())]
1461 );
1462
1463 editor.move_down(&MoveDown, window, cx);
1464 assert_eq!(
1465 editor.selections.display_ranges(cx),
1466 &[empty_range(2, "αβγ".len())]
1467 );
1468
1469 editor.move_down(&MoveDown, window, cx);
1470 assert_eq!(
1471 editor.selections.display_ranges(cx),
1472 &[empty_range(3, "abcd".len())]
1473 );
1474
1475 editor.move_down(&MoveDown, window, cx);
1476 assert_eq!(
1477 editor.selections.display_ranges(cx),
1478 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1479 );
1480
1481 // moving past end of document should not change goal_x
1482 editor.move_down(&MoveDown, window, cx);
1483 assert_eq!(
1484 editor.selections.display_ranges(cx),
1485 &[empty_range(5, "".len())]
1486 );
1487
1488 editor.move_down(&MoveDown, window, cx);
1489 assert_eq!(
1490 editor.selections.display_ranges(cx),
1491 &[empty_range(5, "".len())]
1492 );
1493
1494 editor.move_up(&MoveUp, window, cx);
1495 assert_eq!(
1496 editor.selections.display_ranges(cx),
1497 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1498 );
1499
1500 editor.move_up(&MoveUp, window, cx);
1501 assert_eq!(
1502 editor.selections.display_ranges(cx),
1503 &[empty_range(3, "abcd".len())]
1504 );
1505
1506 editor.move_up(&MoveUp, window, cx);
1507 assert_eq!(
1508 editor.selections.display_ranges(cx),
1509 &[empty_range(2, "αβγ".len())]
1510 );
1511 });
1512}
1513
1514#[gpui::test]
1515fn test_beginning_end_of_line(cx: &mut TestAppContext) {
1516 init_test(cx, |_| {});
1517 let move_to_beg = MoveToBeginningOfLine {
1518 stop_at_soft_wraps: true,
1519 stop_at_indent: true,
1520 };
1521
1522 let delete_to_beg = DeleteToBeginningOfLine {
1523 stop_at_indent: false,
1524 };
1525
1526 let move_to_end = MoveToEndOfLine {
1527 stop_at_soft_wraps: true,
1528 };
1529
1530 let editor = cx.add_window(|window, cx| {
1531 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1532 build_editor(buffer, window, cx)
1533 });
1534 _ = editor.update(cx, |editor, window, cx| {
1535 editor.change_selections(None, window, cx, |s| {
1536 s.select_display_ranges([
1537 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1538 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1539 ]);
1540 });
1541 });
1542
1543 _ = editor.update(cx, |editor, window, cx| {
1544 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1545 assert_eq!(
1546 editor.selections.display_ranges(cx),
1547 &[
1548 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1549 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1550 ]
1551 );
1552 });
1553
1554 _ = editor.update(cx, |editor, window, cx| {
1555 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1556 assert_eq!(
1557 editor.selections.display_ranges(cx),
1558 &[
1559 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1560 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1561 ]
1562 );
1563 });
1564
1565 _ = editor.update(cx, |editor, window, cx| {
1566 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1567 assert_eq!(
1568 editor.selections.display_ranges(cx),
1569 &[
1570 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1571 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1572 ]
1573 );
1574 });
1575
1576 _ = editor.update(cx, |editor, window, cx| {
1577 editor.move_to_end_of_line(&move_to_end, window, cx);
1578 assert_eq!(
1579 editor.selections.display_ranges(cx),
1580 &[
1581 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1582 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1583 ]
1584 );
1585 });
1586
1587 // Moving to the end of line again is a no-op.
1588 _ = editor.update(cx, |editor, window, cx| {
1589 editor.move_to_end_of_line(&move_to_end, window, cx);
1590 assert_eq!(
1591 editor.selections.display_ranges(cx),
1592 &[
1593 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1594 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1595 ]
1596 );
1597 });
1598
1599 _ = editor.update(cx, |editor, window, cx| {
1600 editor.move_left(&MoveLeft, window, cx);
1601 editor.select_to_beginning_of_line(
1602 &SelectToBeginningOfLine {
1603 stop_at_soft_wraps: true,
1604 stop_at_indent: true,
1605 },
1606 window,
1607 cx,
1608 );
1609 assert_eq!(
1610 editor.selections.display_ranges(cx),
1611 &[
1612 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1613 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1614 ]
1615 );
1616 });
1617
1618 _ = editor.update(cx, |editor, window, cx| {
1619 editor.select_to_beginning_of_line(
1620 &SelectToBeginningOfLine {
1621 stop_at_soft_wraps: true,
1622 stop_at_indent: true,
1623 },
1624 window,
1625 cx,
1626 );
1627 assert_eq!(
1628 editor.selections.display_ranges(cx),
1629 &[
1630 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1631 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1632 ]
1633 );
1634 });
1635
1636 _ = editor.update(cx, |editor, window, cx| {
1637 editor.select_to_beginning_of_line(
1638 &SelectToBeginningOfLine {
1639 stop_at_soft_wraps: true,
1640 stop_at_indent: true,
1641 },
1642 window,
1643 cx,
1644 );
1645 assert_eq!(
1646 editor.selections.display_ranges(cx),
1647 &[
1648 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1649 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1650 ]
1651 );
1652 });
1653
1654 _ = editor.update(cx, |editor, window, cx| {
1655 editor.select_to_end_of_line(
1656 &SelectToEndOfLine {
1657 stop_at_soft_wraps: true,
1658 },
1659 window,
1660 cx,
1661 );
1662 assert_eq!(
1663 editor.selections.display_ranges(cx),
1664 &[
1665 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
1666 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
1667 ]
1668 );
1669 });
1670
1671 _ = editor.update(cx, |editor, window, cx| {
1672 editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
1673 assert_eq!(editor.display_text(cx), "ab\n de");
1674 assert_eq!(
1675 editor.selections.display_ranges(cx),
1676 &[
1677 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
1678 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1679 ]
1680 );
1681 });
1682
1683 _ = editor.update(cx, |editor, window, cx| {
1684 editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
1685 assert_eq!(editor.display_text(cx), "\n");
1686 assert_eq!(
1687 editor.selections.display_ranges(cx),
1688 &[
1689 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1690 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1691 ]
1692 );
1693 });
1694}
1695
1696#[gpui::test]
1697fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
1698 init_test(cx, |_| {});
1699 let move_to_beg = MoveToBeginningOfLine {
1700 stop_at_soft_wraps: false,
1701 stop_at_indent: false,
1702 };
1703
1704 let move_to_end = MoveToEndOfLine {
1705 stop_at_soft_wraps: false,
1706 };
1707
1708 let editor = cx.add_window(|window, cx| {
1709 let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
1710 build_editor(buffer, window, cx)
1711 });
1712
1713 _ = editor.update(cx, |editor, window, cx| {
1714 editor.set_wrap_width(Some(140.0.into()), cx);
1715
1716 // We expect the following lines after wrapping
1717 // ```
1718 // thequickbrownfox
1719 // jumpedoverthelazydo
1720 // gs
1721 // ```
1722 // The final `gs` was soft-wrapped onto a new line.
1723 assert_eq!(
1724 "thequickbrownfox\njumpedoverthelaz\nydogs",
1725 editor.display_text(cx),
1726 );
1727
1728 // First, let's assert behavior on the first line, that was not soft-wrapped.
1729 // Start the cursor at the `k` on the first line
1730 editor.change_selections(None, window, cx, |s| {
1731 s.select_display_ranges([
1732 DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
1733 ]);
1734 });
1735
1736 // Moving to the beginning of the line should put us at the beginning of the line.
1737 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1738 assert_eq!(
1739 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
1740 editor.selections.display_ranges(cx)
1741 );
1742
1743 // Moving to the end of the line should put us at the end of the line.
1744 editor.move_to_end_of_line(&move_to_end, window, cx);
1745 assert_eq!(
1746 vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
1747 editor.selections.display_ranges(cx)
1748 );
1749
1750 // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
1751 // Start the cursor at the last line (`y` that was wrapped to a new line)
1752 editor.change_selections(None, window, cx, |s| {
1753 s.select_display_ranges([
1754 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
1755 ]);
1756 });
1757
1758 // Moving to the beginning of the line should put us at the start of the second line of
1759 // display text, i.e., the `j`.
1760 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1761 assert_eq!(
1762 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1763 editor.selections.display_ranges(cx)
1764 );
1765
1766 // Moving to the beginning of the line again should be a no-op.
1767 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1768 assert_eq!(
1769 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1770 editor.selections.display_ranges(cx)
1771 );
1772
1773 // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
1774 // next display line.
1775 editor.move_to_end_of_line(&move_to_end, window, cx);
1776 assert_eq!(
1777 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1778 editor.selections.display_ranges(cx)
1779 );
1780
1781 // Moving to the end of the line again should be a no-op.
1782 editor.move_to_end_of_line(&move_to_end, window, cx);
1783 assert_eq!(
1784 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1785 editor.selections.display_ranges(cx)
1786 );
1787 });
1788}
1789
1790#[gpui::test]
1791fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
1792 init_test(cx, |_| {});
1793
1794 let move_to_beg = MoveToBeginningOfLine {
1795 stop_at_soft_wraps: true,
1796 stop_at_indent: true,
1797 };
1798
1799 let select_to_beg = SelectToBeginningOfLine {
1800 stop_at_soft_wraps: true,
1801 stop_at_indent: true,
1802 };
1803
1804 let delete_to_beg = DeleteToBeginningOfLine {
1805 stop_at_indent: true,
1806 };
1807
1808 let move_to_end = MoveToEndOfLine {
1809 stop_at_soft_wraps: false,
1810 };
1811
1812 let editor = cx.add_window(|window, cx| {
1813 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1814 build_editor(buffer, window, cx)
1815 });
1816
1817 _ = editor.update(cx, |editor, window, cx| {
1818 editor.change_selections(None, window, cx, |s| {
1819 s.select_display_ranges([
1820 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1821 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1822 ]);
1823 });
1824
1825 // Moving to the beginning of the line should put the first cursor at the beginning of the line,
1826 // and the second cursor at the first non-whitespace character in the line.
1827 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1828 assert_eq!(
1829 editor.selections.display_ranges(cx),
1830 &[
1831 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1832 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1833 ]
1834 );
1835
1836 // Moving to the beginning of the line again should be a no-op for the first cursor,
1837 // and should move the second cursor to the beginning of the line.
1838 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1839 assert_eq!(
1840 editor.selections.display_ranges(cx),
1841 &[
1842 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1843 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1844 ]
1845 );
1846
1847 // Moving to the beginning of the line again should still be a no-op for the first cursor,
1848 // and should move the second cursor back to the first non-whitespace character in the line.
1849 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1850 assert_eq!(
1851 editor.selections.display_ranges(cx),
1852 &[
1853 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1854 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1855 ]
1856 );
1857
1858 // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
1859 // and to the first non-whitespace character in the line for the second cursor.
1860 editor.move_to_end_of_line(&move_to_end, window, cx);
1861 editor.move_left(&MoveLeft, window, cx);
1862 editor.select_to_beginning_of_line(&select_to_beg, window, cx);
1863 assert_eq!(
1864 editor.selections.display_ranges(cx),
1865 &[
1866 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1867 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1868 ]
1869 );
1870
1871 // Selecting to the beginning of the line again should be a no-op for the first cursor,
1872 // and should select to the beginning of the line for the second cursor.
1873 editor.select_to_beginning_of_line(&select_to_beg, window, cx);
1874 assert_eq!(
1875 editor.selections.display_ranges(cx),
1876 &[
1877 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1878 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1879 ]
1880 );
1881
1882 // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
1883 // and should delete to the first non-whitespace character in the line for the second cursor.
1884 editor.move_to_end_of_line(&move_to_end, window, cx);
1885 editor.move_left(&MoveLeft, window, cx);
1886 editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
1887 assert_eq!(editor.text(cx), "c\n f");
1888 });
1889}
1890
1891#[gpui::test]
1892fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1893 init_test(cx, |_| {});
1894
1895 let editor = cx.add_window(|window, cx| {
1896 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1897 build_editor(buffer, window, cx)
1898 });
1899 _ = editor.update(cx, |editor, window, cx| {
1900 editor.change_selections(None, window, cx, |s| {
1901 s.select_display_ranges([
1902 DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
1903 DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
1904 ])
1905 });
1906
1907 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1908 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", editor, cx);
1909
1910 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1911 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", editor, cx);
1912
1913 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1914 assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", editor, cx);
1915
1916 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1917 assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", editor, cx);
1918
1919 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1920 assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", editor, cx);
1921
1922 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1923 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", editor, cx);
1924
1925 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1926 assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", editor, cx);
1927
1928 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1929 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", editor, cx);
1930
1931 editor.move_right(&MoveRight, window, cx);
1932 editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
1933 assert_selection_ranges(
1934 "use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}",
1935 editor,
1936 cx,
1937 );
1938
1939 editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
1940 assert_selection_ranges(
1941 "use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}",
1942 editor,
1943 cx,
1944 );
1945
1946 editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
1947 assert_selection_ranges(
1948 "use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}",
1949 editor,
1950 cx,
1951 );
1952 });
1953}
1954
1955#[gpui::test]
1956fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1957 init_test(cx, |_| {});
1958
1959 let editor = cx.add_window(|window, cx| {
1960 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1961 build_editor(buffer, window, cx)
1962 });
1963
1964 _ = editor.update(cx, |editor, window, cx| {
1965 editor.set_wrap_width(Some(140.0.into()), cx);
1966 assert_eq!(
1967 editor.display_text(cx),
1968 "use one::{\n two::three::\n four::five\n};"
1969 );
1970
1971 editor.change_selections(None, window, cx, |s| {
1972 s.select_display_ranges([
1973 DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
1974 ]);
1975 });
1976
1977 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1978 assert_eq!(
1979 editor.selections.display_ranges(cx),
1980 &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
1981 );
1982
1983 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1984 assert_eq!(
1985 editor.selections.display_ranges(cx),
1986 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1987 );
1988
1989 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1990 assert_eq!(
1991 editor.selections.display_ranges(cx),
1992 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1993 );
1994
1995 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1996 assert_eq!(
1997 editor.selections.display_ranges(cx),
1998 &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
1999 );
2000
2001 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
2002 assert_eq!(
2003 editor.selections.display_ranges(cx),
2004 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
2005 );
2006
2007 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
2008 assert_eq!(
2009 editor.selections.display_ranges(cx),
2010 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
2011 );
2012 });
2013}
2014
2015#[gpui::test]
2016async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
2017 init_test(cx, |_| {});
2018 let mut cx = EditorTestContext::new(cx).await;
2019
2020 let line_height = cx.editor(|editor, window, _| {
2021 editor
2022 .style()
2023 .unwrap()
2024 .text
2025 .line_height_in_pixels(window.rem_size())
2026 });
2027 cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
2028
2029 cx.set_state(
2030 &r#"ˇone
2031 two
2032
2033 three
2034 fourˇ
2035 five
2036
2037 six"#
2038 .unindent(),
2039 );
2040
2041 cx.update_editor(|editor, window, cx| {
2042 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
2043 });
2044 cx.assert_editor_state(
2045 &r#"one
2046 two
2047 ˇ
2048 three
2049 four
2050 five
2051 ˇ
2052 six"#
2053 .unindent(),
2054 );
2055
2056 cx.update_editor(|editor, window, cx| {
2057 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
2058 });
2059 cx.assert_editor_state(
2060 &r#"one
2061 two
2062
2063 three
2064 four
2065 five
2066 ˇ
2067 sixˇ"#
2068 .unindent(),
2069 );
2070
2071 cx.update_editor(|editor, window, cx| {
2072 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
2073 });
2074 cx.assert_editor_state(
2075 &r#"one
2076 two
2077
2078 three
2079 four
2080 five
2081
2082 sixˇ"#
2083 .unindent(),
2084 );
2085
2086 cx.update_editor(|editor, window, cx| {
2087 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
2088 });
2089 cx.assert_editor_state(
2090 &r#"one
2091 two
2092
2093 three
2094 four
2095 five
2096 ˇ
2097 six"#
2098 .unindent(),
2099 );
2100
2101 cx.update_editor(|editor, window, cx| {
2102 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
2103 });
2104 cx.assert_editor_state(
2105 &r#"one
2106 two
2107 ˇ
2108 three
2109 four
2110 five
2111
2112 six"#
2113 .unindent(),
2114 );
2115
2116 cx.update_editor(|editor, window, cx| {
2117 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
2118 });
2119 cx.assert_editor_state(
2120 &r#"ˇone
2121 two
2122
2123 three
2124 four
2125 five
2126
2127 six"#
2128 .unindent(),
2129 );
2130}
2131
2132#[gpui::test]
2133async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
2134 init_test(cx, |_| {});
2135 let mut cx = EditorTestContext::new(cx).await;
2136 let line_height = cx.editor(|editor, window, _| {
2137 editor
2138 .style()
2139 .unwrap()
2140 .text
2141 .line_height_in_pixels(window.rem_size())
2142 });
2143 let window = cx.window;
2144 cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
2145
2146 cx.set_state(
2147 r#"ˇone
2148 two
2149 three
2150 four
2151 five
2152 six
2153 seven
2154 eight
2155 nine
2156 ten
2157 "#,
2158 );
2159
2160 cx.update_editor(|editor, window, cx| {
2161 assert_eq!(
2162 editor.snapshot(window, cx).scroll_position(),
2163 gpui::Point::new(0., 0.)
2164 );
2165 editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
2166 assert_eq!(
2167 editor.snapshot(window, cx).scroll_position(),
2168 gpui::Point::new(0., 3.)
2169 );
2170 editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
2171 assert_eq!(
2172 editor.snapshot(window, cx).scroll_position(),
2173 gpui::Point::new(0., 6.)
2174 );
2175 editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
2176 assert_eq!(
2177 editor.snapshot(window, cx).scroll_position(),
2178 gpui::Point::new(0., 3.)
2179 );
2180
2181 editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
2182 assert_eq!(
2183 editor.snapshot(window, cx).scroll_position(),
2184 gpui::Point::new(0., 1.)
2185 );
2186 editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
2187 assert_eq!(
2188 editor.snapshot(window, cx).scroll_position(),
2189 gpui::Point::new(0., 3.)
2190 );
2191 });
2192}
2193
2194#[gpui::test]
2195async fn test_autoscroll(cx: &mut TestAppContext) {
2196 init_test(cx, |_| {});
2197 let mut cx = EditorTestContext::new(cx).await;
2198
2199 let line_height = cx.update_editor(|editor, window, cx| {
2200 editor.set_vertical_scroll_margin(2, cx);
2201 editor
2202 .style()
2203 .unwrap()
2204 .text
2205 .line_height_in_pixels(window.rem_size())
2206 });
2207 let window = cx.window;
2208 cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
2209
2210 cx.set_state(
2211 r#"ˇone
2212 two
2213 three
2214 four
2215 five
2216 six
2217 seven
2218 eight
2219 nine
2220 ten
2221 "#,
2222 );
2223 cx.update_editor(|editor, window, cx| {
2224 assert_eq!(
2225 editor.snapshot(window, cx).scroll_position(),
2226 gpui::Point::new(0., 0.0)
2227 );
2228 });
2229
2230 // Add a cursor below the visible area. Since both cursors cannot fit
2231 // on screen, the editor autoscrolls to reveal the newest cursor, and
2232 // allows the vertical scroll margin below that cursor.
2233 cx.update_editor(|editor, window, cx| {
2234 editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
2235 selections.select_ranges([
2236 Point::new(0, 0)..Point::new(0, 0),
2237 Point::new(6, 0)..Point::new(6, 0),
2238 ]);
2239 })
2240 });
2241 cx.update_editor(|editor, window, cx| {
2242 assert_eq!(
2243 editor.snapshot(window, cx).scroll_position(),
2244 gpui::Point::new(0., 3.0)
2245 );
2246 });
2247
2248 // Move down. The editor cursor scrolls down to track the newest cursor.
2249 cx.update_editor(|editor, window, cx| {
2250 editor.move_down(&Default::default(), window, cx);
2251 });
2252 cx.update_editor(|editor, window, cx| {
2253 assert_eq!(
2254 editor.snapshot(window, cx).scroll_position(),
2255 gpui::Point::new(0., 4.0)
2256 );
2257 });
2258
2259 // Add a cursor above the visible area. Since both cursors fit on screen,
2260 // the editor scrolls to show both.
2261 cx.update_editor(|editor, window, cx| {
2262 editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
2263 selections.select_ranges([
2264 Point::new(1, 0)..Point::new(1, 0),
2265 Point::new(6, 0)..Point::new(6, 0),
2266 ]);
2267 })
2268 });
2269 cx.update_editor(|editor, window, cx| {
2270 assert_eq!(
2271 editor.snapshot(window, cx).scroll_position(),
2272 gpui::Point::new(0., 1.0)
2273 );
2274 });
2275}
2276
2277#[gpui::test]
2278async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
2279 init_test(cx, |_| {});
2280 let mut cx = EditorTestContext::new(cx).await;
2281
2282 let line_height = cx.editor(|editor, window, _cx| {
2283 editor
2284 .style()
2285 .unwrap()
2286 .text
2287 .line_height_in_pixels(window.rem_size())
2288 });
2289 let window = cx.window;
2290 cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
2291 cx.set_state(
2292 &r#"
2293 ˇone
2294 two
2295 threeˇ
2296 four
2297 five
2298 six
2299 seven
2300 eight
2301 nine
2302 ten
2303 "#
2304 .unindent(),
2305 );
2306
2307 cx.update_editor(|editor, window, cx| {
2308 editor.move_page_down(&MovePageDown::default(), window, cx)
2309 });
2310 cx.assert_editor_state(
2311 &r#"
2312 one
2313 two
2314 three
2315 ˇfour
2316 five
2317 sixˇ
2318 seven
2319 eight
2320 nine
2321 ten
2322 "#
2323 .unindent(),
2324 );
2325
2326 cx.update_editor(|editor, window, cx| {
2327 editor.move_page_down(&MovePageDown::default(), window, cx)
2328 });
2329 cx.assert_editor_state(
2330 &r#"
2331 one
2332 two
2333 three
2334 four
2335 five
2336 six
2337 ˇseven
2338 eight
2339 nineˇ
2340 ten
2341 "#
2342 .unindent(),
2343 );
2344
2345 cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
2346 cx.assert_editor_state(
2347 &r#"
2348 one
2349 two
2350 three
2351 ˇfour
2352 five
2353 sixˇ
2354 seven
2355 eight
2356 nine
2357 ten
2358 "#
2359 .unindent(),
2360 );
2361
2362 cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
2363 cx.assert_editor_state(
2364 &r#"
2365 ˇone
2366 two
2367 threeˇ
2368 four
2369 five
2370 six
2371 seven
2372 eight
2373 nine
2374 ten
2375 "#
2376 .unindent(),
2377 );
2378
2379 // Test select collapsing
2380 cx.update_editor(|editor, window, cx| {
2381 editor.move_page_down(&MovePageDown::default(), window, cx);
2382 editor.move_page_down(&MovePageDown::default(), window, cx);
2383 editor.move_page_down(&MovePageDown::default(), window, cx);
2384 });
2385 cx.assert_editor_state(
2386 &r#"
2387 one
2388 two
2389 three
2390 four
2391 five
2392 six
2393 seven
2394 eight
2395 nine
2396 ˇten
2397 ˇ"#
2398 .unindent(),
2399 );
2400}
2401
2402#[gpui::test]
2403async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
2404 init_test(cx, |_| {});
2405 let mut cx = EditorTestContext::new(cx).await;
2406 cx.set_state("one «two threeˇ» four");
2407 cx.update_editor(|editor, window, cx| {
2408 editor.delete_to_beginning_of_line(
2409 &DeleteToBeginningOfLine {
2410 stop_at_indent: false,
2411 },
2412 window,
2413 cx,
2414 );
2415 assert_eq!(editor.text(cx), " four");
2416 });
2417}
2418
2419#[gpui::test]
2420fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
2421 init_test(cx, |_| {});
2422
2423 let editor = cx.add_window(|window, cx| {
2424 let buffer = MultiBuffer::build_simple("one two three four", cx);
2425 build_editor(buffer.clone(), window, cx)
2426 });
2427
2428 _ = editor.update(cx, |editor, window, cx| {
2429 editor.change_selections(None, window, cx, |s| {
2430 s.select_display_ranges([
2431 // an empty selection - the preceding word fragment is deleted
2432 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2433 // characters selected - they are deleted
2434 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
2435 ])
2436 });
2437 editor.delete_to_previous_word_start(
2438 &DeleteToPreviousWordStart {
2439 ignore_newlines: false,
2440 },
2441 window,
2442 cx,
2443 );
2444 assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
2445 });
2446
2447 _ = editor.update(cx, |editor, window, cx| {
2448 editor.change_selections(None, window, cx, |s| {
2449 s.select_display_ranges([
2450 // an empty selection - the following word fragment is deleted
2451 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
2452 // characters selected - they are deleted
2453 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
2454 ])
2455 });
2456 editor.delete_to_next_word_end(
2457 &DeleteToNextWordEnd {
2458 ignore_newlines: false,
2459 },
2460 window,
2461 cx,
2462 );
2463 assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
2464 });
2465}
2466
2467#[gpui::test]
2468fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
2469 init_test(cx, |_| {});
2470
2471 let editor = cx.add_window(|window, cx| {
2472 let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
2473 build_editor(buffer.clone(), window, cx)
2474 });
2475 let del_to_prev_word_start = DeleteToPreviousWordStart {
2476 ignore_newlines: false,
2477 };
2478 let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
2479 ignore_newlines: true,
2480 };
2481
2482 _ = editor.update(cx, |editor, window, cx| {
2483 editor.change_selections(None, window, cx, |s| {
2484 s.select_display_ranges([
2485 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
2486 ])
2487 });
2488 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2489 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
2490 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2491 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
2492 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2493 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
2494 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2495 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
2496 editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
2497 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
2498 editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
2499 assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
2500 });
2501}
2502
2503#[gpui::test]
2504fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
2505 init_test(cx, |_| {});
2506
2507 let editor = cx.add_window(|window, cx| {
2508 let buffer = MultiBuffer::build_simple("\none\n two\nthree\n four", cx);
2509 build_editor(buffer.clone(), window, cx)
2510 });
2511 let del_to_next_word_end = DeleteToNextWordEnd {
2512 ignore_newlines: false,
2513 };
2514 let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
2515 ignore_newlines: true,
2516 };
2517
2518 _ = editor.update(cx, |editor, window, cx| {
2519 editor.change_selections(None, window, cx, |s| {
2520 s.select_display_ranges([
2521 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
2522 ])
2523 });
2524 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2525 assert_eq!(
2526 editor.buffer.read(cx).read(cx).text(),
2527 "one\n two\nthree\n four"
2528 );
2529 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2530 assert_eq!(
2531 editor.buffer.read(cx).read(cx).text(),
2532 "\n two\nthree\n four"
2533 );
2534 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2535 assert_eq!(
2536 editor.buffer.read(cx).read(cx).text(),
2537 "two\nthree\n four"
2538 );
2539 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2540 assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n four");
2541 editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
2542 assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n four");
2543 editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
2544 assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
2545 });
2546}
2547
2548#[gpui::test]
2549fn test_newline(cx: &mut TestAppContext) {
2550 init_test(cx, |_| {});
2551
2552 let editor = cx.add_window(|window, cx| {
2553 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
2554 build_editor(buffer.clone(), window, cx)
2555 });
2556
2557 _ = editor.update(cx, |editor, window, cx| {
2558 editor.change_selections(None, window, cx, |s| {
2559 s.select_display_ranges([
2560 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2561 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
2562 DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
2563 ])
2564 });
2565
2566 editor.newline(&Newline, window, cx);
2567 assert_eq!(editor.text(cx), "aa\naa\n \n bb\n bb\n");
2568 });
2569}
2570
2571#[gpui::test]
2572fn test_newline_with_old_selections(cx: &mut TestAppContext) {
2573 init_test(cx, |_| {});
2574
2575 let editor = cx.add_window(|window, cx| {
2576 let buffer = MultiBuffer::build_simple(
2577 "
2578 a
2579 b(
2580 X
2581 )
2582 c(
2583 X
2584 )
2585 "
2586 .unindent()
2587 .as_str(),
2588 cx,
2589 );
2590 let mut editor = build_editor(buffer.clone(), window, cx);
2591 editor.change_selections(None, window, cx, |s| {
2592 s.select_ranges([
2593 Point::new(2, 4)..Point::new(2, 5),
2594 Point::new(5, 4)..Point::new(5, 5),
2595 ])
2596 });
2597 editor
2598 });
2599
2600 _ = editor.update(cx, |editor, window, cx| {
2601 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2602 editor.buffer.update(cx, |buffer, cx| {
2603 buffer.edit(
2604 [
2605 (Point::new(1, 2)..Point::new(3, 0), ""),
2606 (Point::new(4, 2)..Point::new(6, 0), ""),
2607 ],
2608 None,
2609 cx,
2610 );
2611 assert_eq!(
2612 buffer.read(cx).text(),
2613 "
2614 a
2615 b()
2616 c()
2617 "
2618 .unindent()
2619 );
2620 });
2621 assert_eq!(
2622 editor.selections.ranges(cx),
2623 &[
2624 Point::new(1, 2)..Point::new(1, 2),
2625 Point::new(2, 2)..Point::new(2, 2),
2626 ],
2627 );
2628
2629 editor.newline(&Newline, window, cx);
2630 assert_eq!(
2631 editor.text(cx),
2632 "
2633 a
2634 b(
2635 )
2636 c(
2637 )
2638 "
2639 .unindent()
2640 );
2641
2642 // The selections are moved after the inserted newlines
2643 assert_eq!(
2644 editor.selections.ranges(cx),
2645 &[
2646 Point::new(2, 0)..Point::new(2, 0),
2647 Point::new(4, 0)..Point::new(4, 0),
2648 ],
2649 );
2650 });
2651}
2652
2653#[gpui::test]
2654async fn test_newline_above(cx: &mut TestAppContext) {
2655 init_test(cx, |settings| {
2656 settings.defaults.tab_size = NonZeroU32::new(4)
2657 });
2658
2659 let language = Arc::new(
2660 Language::new(
2661 LanguageConfig::default(),
2662 Some(tree_sitter_rust::LANGUAGE.into()),
2663 )
2664 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2665 .unwrap(),
2666 );
2667
2668 let mut cx = EditorTestContext::new(cx).await;
2669 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2670 cx.set_state(indoc! {"
2671 const a: ˇA = (
2672 (ˇ
2673 «const_functionˇ»(ˇ),
2674 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2675 )ˇ
2676 ˇ);ˇ
2677 "});
2678
2679 cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
2680 cx.assert_editor_state(indoc! {"
2681 ˇ
2682 const a: A = (
2683 ˇ
2684 (
2685 ˇ
2686 ˇ
2687 const_function(),
2688 ˇ
2689 ˇ
2690 ˇ
2691 ˇ
2692 something_else,
2693 ˇ
2694 )
2695 ˇ
2696 ˇ
2697 );
2698 "});
2699}
2700
2701#[gpui::test]
2702async fn test_newline_below(cx: &mut TestAppContext) {
2703 init_test(cx, |settings| {
2704 settings.defaults.tab_size = NonZeroU32::new(4)
2705 });
2706
2707 let language = Arc::new(
2708 Language::new(
2709 LanguageConfig::default(),
2710 Some(tree_sitter_rust::LANGUAGE.into()),
2711 )
2712 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2713 .unwrap(),
2714 );
2715
2716 let mut cx = EditorTestContext::new(cx).await;
2717 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2718 cx.set_state(indoc! {"
2719 const a: ˇA = (
2720 (ˇ
2721 «const_functionˇ»(ˇ),
2722 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2723 )ˇ
2724 ˇ);ˇ
2725 "});
2726
2727 cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
2728 cx.assert_editor_state(indoc! {"
2729 const a: A = (
2730 ˇ
2731 (
2732 ˇ
2733 const_function(),
2734 ˇ
2735 ˇ
2736 something_else,
2737 ˇ
2738 ˇ
2739 ˇ
2740 ˇ
2741 )
2742 ˇ
2743 );
2744 ˇ
2745 ˇ
2746 "});
2747}
2748
2749#[gpui::test]
2750async fn test_newline_comments(cx: &mut TestAppContext) {
2751 init_test(cx, |settings| {
2752 settings.defaults.tab_size = NonZeroU32::new(4)
2753 });
2754
2755 let language = Arc::new(Language::new(
2756 LanguageConfig {
2757 line_comments: vec!["//".into()],
2758 ..LanguageConfig::default()
2759 },
2760 None,
2761 ));
2762 {
2763 let mut cx = EditorTestContext::new(cx).await;
2764 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2765 cx.set_state(indoc! {"
2766 // Fooˇ
2767 "});
2768
2769 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2770 cx.assert_editor_state(indoc! {"
2771 // Foo
2772 //ˇ
2773 "});
2774 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
2775 cx.set_state(indoc! {"
2776 ˇ// Foo
2777 "});
2778 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2779 cx.assert_editor_state(indoc! {"
2780
2781 ˇ// Foo
2782 "});
2783 }
2784 // Ensure that comment continuations can be disabled.
2785 update_test_language_settings(cx, |settings| {
2786 settings.defaults.extend_comment_on_newline = Some(false);
2787 });
2788 let mut cx = EditorTestContext::new(cx).await;
2789 cx.set_state(indoc! {"
2790 // Fooˇ
2791 "});
2792 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2793 cx.assert_editor_state(indoc! {"
2794 // Foo
2795 ˇ
2796 "});
2797}
2798
2799#[gpui::test]
2800fn test_insert_with_old_selections(cx: &mut TestAppContext) {
2801 init_test(cx, |_| {});
2802
2803 let editor = cx.add_window(|window, cx| {
2804 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
2805 let mut editor = build_editor(buffer.clone(), window, cx);
2806 editor.change_selections(None, window, cx, |s| {
2807 s.select_ranges([3..4, 11..12, 19..20])
2808 });
2809 editor
2810 });
2811
2812 _ = editor.update(cx, |editor, window, cx| {
2813 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2814 editor.buffer.update(cx, |buffer, cx| {
2815 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
2816 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
2817 });
2818 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
2819
2820 editor.insert("Z", window, cx);
2821 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
2822
2823 // The selections are moved after the inserted characters
2824 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
2825 });
2826}
2827
2828#[gpui::test]
2829async fn test_tab(cx: &mut TestAppContext) {
2830 init_test(cx, |settings| {
2831 settings.defaults.tab_size = NonZeroU32::new(3)
2832 });
2833
2834 let mut cx = EditorTestContext::new(cx).await;
2835 cx.set_state(indoc! {"
2836 ˇabˇc
2837 ˇ🏀ˇ🏀ˇefg
2838 dˇ
2839 "});
2840 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2841 cx.assert_editor_state(indoc! {"
2842 ˇab ˇc
2843 ˇ🏀 ˇ🏀 ˇefg
2844 d ˇ
2845 "});
2846
2847 cx.set_state(indoc! {"
2848 a
2849 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2850 "});
2851 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2852 cx.assert_editor_state(indoc! {"
2853 a
2854 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2855 "});
2856}
2857
2858#[gpui::test]
2859async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
2860 init_test(cx, |_| {});
2861
2862 let mut cx = EditorTestContext::new(cx).await;
2863 let language = Arc::new(
2864 Language::new(
2865 LanguageConfig::default(),
2866 Some(tree_sitter_rust::LANGUAGE.into()),
2867 )
2868 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2869 .unwrap(),
2870 );
2871 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2872
2873 // cursors that are already at the suggested indent level insert
2874 // a soft tab. cursors that are to the left of the suggested indent
2875 // auto-indent their line.
2876 cx.set_state(indoc! {"
2877 ˇ
2878 const a: B = (
2879 c(
2880 d(
2881 ˇ
2882 )
2883 ˇ
2884 ˇ )
2885 );
2886 "});
2887 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2888 cx.assert_editor_state(indoc! {"
2889 ˇ
2890 const a: B = (
2891 c(
2892 d(
2893 ˇ
2894 )
2895 ˇ
2896 ˇ)
2897 );
2898 "});
2899
2900 // handle auto-indent when there are multiple cursors on the same line
2901 cx.set_state(indoc! {"
2902 const a: B = (
2903 c(
2904 ˇ ˇ
2905 ˇ )
2906 );
2907 "});
2908 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2909 cx.assert_editor_state(indoc! {"
2910 const a: B = (
2911 c(
2912 ˇ
2913 ˇ)
2914 );
2915 "});
2916}
2917
2918#[gpui::test]
2919async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
2920 init_test(cx, |settings| {
2921 settings.defaults.tab_size = NonZeroU32::new(3)
2922 });
2923
2924 let mut cx = EditorTestContext::new(cx).await;
2925 cx.set_state(indoc! {"
2926 ˇ
2927 \t ˇ
2928 \t ˇ
2929 \t ˇ
2930 \t \t\t \t \t\t \t\t \t \t ˇ
2931 "});
2932
2933 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2934 cx.assert_editor_state(indoc! {"
2935 ˇ
2936 \t ˇ
2937 \t ˇ
2938 \t ˇ
2939 \t \t\t \t \t\t \t\t \t \t ˇ
2940 "});
2941}
2942
2943#[gpui::test]
2944async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
2945 init_test(cx, |settings| {
2946 settings.defaults.tab_size = NonZeroU32::new(4)
2947 });
2948
2949 let language = Arc::new(
2950 Language::new(
2951 LanguageConfig::default(),
2952 Some(tree_sitter_rust::LANGUAGE.into()),
2953 )
2954 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2955 .unwrap(),
2956 );
2957
2958 let mut cx = EditorTestContext::new(cx).await;
2959 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2960 cx.set_state(indoc! {"
2961 fn a() {
2962 if b {
2963 \t ˇc
2964 }
2965 }
2966 "});
2967
2968 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2969 cx.assert_editor_state(indoc! {"
2970 fn a() {
2971 if b {
2972 ˇc
2973 }
2974 }
2975 "});
2976}
2977
2978#[gpui::test]
2979async fn test_indent_outdent(cx: &mut TestAppContext) {
2980 init_test(cx, |settings| {
2981 settings.defaults.tab_size = NonZeroU32::new(4);
2982 });
2983
2984 let mut cx = EditorTestContext::new(cx).await;
2985
2986 cx.set_state(indoc! {"
2987 «oneˇ» «twoˇ»
2988 three
2989 four
2990 "});
2991 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2992 cx.assert_editor_state(indoc! {"
2993 «oneˇ» «twoˇ»
2994 three
2995 four
2996 "});
2997
2998 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
2999 cx.assert_editor_state(indoc! {"
3000 «oneˇ» «twoˇ»
3001 three
3002 four
3003 "});
3004
3005 // select across line ending
3006 cx.set_state(indoc! {"
3007 one two
3008 t«hree
3009 ˇ» four
3010 "});
3011 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3012 cx.assert_editor_state(indoc! {"
3013 one two
3014 t«hree
3015 ˇ» four
3016 "});
3017
3018 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3019 cx.assert_editor_state(indoc! {"
3020 one two
3021 t«hree
3022 ˇ» four
3023 "});
3024
3025 // Ensure that indenting/outdenting works when the cursor is at column 0.
3026 cx.set_state(indoc! {"
3027 one two
3028 ˇthree
3029 four
3030 "});
3031 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3032 cx.assert_editor_state(indoc! {"
3033 one two
3034 ˇthree
3035 four
3036 "});
3037
3038 cx.set_state(indoc! {"
3039 one two
3040 ˇ three
3041 four
3042 "});
3043 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3044 cx.assert_editor_state(indoc! {"
3045 one two
3046 ˇthree
3047 four
3048 "});
3049}
3050
3051#[gpui::test]
3052async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
3053 init_test(cx, |settings| {
3054 settings.defaults.hard_tabs = Some(true);
3055 });
3056
3057 let mut cx = EditorTestContext::new(cx).await;
3058
3059 // select two ranges on one line
3060 cx.set_state(indoc! {"
3061 «oneˇ» «twoˇ»
3062 three
3063 four
3064 "});
3065 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3066 cx.assert_editor_state(indoc! {"
3067 \t«oneˇ» «twoˇ»
3068 three
3069 four
3070 "});
3071 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3072 cx.assert_editor_state(indoc! {"
3073 \t\t«oneˇ» «twoˇ»
3074 three
3075 four
3076 "});
3077 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3078 cx.assert_editor_state(indoc! {"
3079 \t«oneˇ» «twoˇ»
3080 three
3081 four
3082 "});
3083 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3084 cx.assert_editor_state(indoc! {"
3085 «oneˇ» «twoˇ»
3086 three
3087 four
3088 "});
3089
3090 // select across a line ending
3091 cx.set_state(indoc! {"
3092 one two
3093 t«hree
3094 ˇ»four
3095 "});
3096 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3097 cx.assert_editor_state(indoc! {"
3098 one two
3099 \tt«hree
3100 ˇ»four
3101 "});
3102 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3103 cx.assert_editor_state(indoc! {"
3104 one two
3105 \t\tt«hree
3106 ˇ»four
3107 "});
3108 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3109 cx.assert_editor_state(indoc! {"
3110 one two
3111 \tt«hree
3112 ˇ»four
3113 "});
3114 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3115 cx.assert_editor_state(indoc! {"
3116 one two
3117 t«hree
3118 ˇ»four
3119 "});
3120
3121 // Ensure that indenting/outdenting works when the cursor is at column 0.
3122 cx.set_state(indoc! {"
3123 one two
3124 ˇthree
3125 four
3126 "});
3127 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3128 cx.assert_editor_state(indoc! {"
3129 one two
3130 ˇthree
3131 four
3132 "});
3133 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3134 cx.assert_editor_state(indoc! {"
3135 one two
3136 \tˇthree
3137 four
3138 "});
3139 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3140 cx.assert_editor_state(indoc! {"
3141 one two
3142 ˇthree
3143 four
3144 "});
3145}
3146
3147#[gpui::test]
3148fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
3149 init_test(cx, |settings| {
3150 settings.languages.extend([
3151 (
3152 "TOML".into(),
3153 LanguageSettingsContent {
3154 tab_size: NonZeroU32::new(2),
3155 ..Default::default()
3156 },
3157 ),
3158 (
3159 "Rust".into(),
3160 LanguageSettingsContent {
3161 tab_size: NonZeroU32::new(4),
3162 ..Default::default()
3163 },
3164 ),
3165 ]);
3166 });
3167
3168 let toml_language = Arc::new(Language::new(
3169 LanguageConfig {
3170 name: "TOML".into(),
3171 ..Default::default()
3172 },
3173 None,
3174 ));
3175 let rust_language = Arc::new(Language::new(
3176 LanguageConfig {
3177 name: "Rust".into(),
3178 ..Default::default()
3179 },
3180 None,
3181 ));
3182
3183 let toml_buffer =
3184 cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
3185 let rust_buffer =
3186 cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
3187 let multibuffer = cx.new(|cx| {
3188 let mut multibuffer = MultiBuffer::new(ReadWrite);
3189 multibuffer.push_excerpts(
3190 toml_buffer.clone(),
3191 [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
3192 cx,
3193 );
3194 multibuffer.push_excerpts(
3195 rust_buffer.clone(),
3196 [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
3197 cx,
3198 );
3199 multibuffer
3200 });
3201
3202 cx.add_window(|window, cx| {
3203 let mut editor = build_editor(multibuffer, window, cx);
3204
3205 assert_eq!(
3206 editor.text(cx),
3207 indoc! {"
3208 a = 1
3209 b = 2
3210
3211 const c: usize = 3;
3212 "}
3213 );
3214
3215 select_ranges(
3216 &mut editor,
3217 indoc! {"
3218 «aˇ» = 1
3219 b = 2
3220
3221 «const c:ˇ» usize = 3;
3222 "},
3223 window,
3224 cx,
3225 );
3226
3227 editor.tab(&Tab, window, cx);
3228 assert_text_with_selections(
3229 &mut editor,
3230 indoc! {"
3231 «aˇ» = 1
3232 b = 2
3233
3234 «const c:ˇ» usize = 3;
3235 "},
3236 cx,
3237 );
3238 editor.backtab(&Backtab, window, cx);
3239 assert_text_with_selections(
3240 &mut editor,
3241 indoc! {"
3242 «aˇ» = 1
3243 b = 2
3244
3245 «const c:ˇ» usize = 3;
3246 "},
3247 cx,
3248 );
3249
3250 editor
3251 });
3252}
3253
3254#[gpui::test]
3255async fn test_backspace(cx: &mut TestAppContext) {
3256 init_test(cx, |_| {});
3257
3258 let mut cx = EditorTestContext::new(cx).await;
3259
3260 // Basic backspace
3261 cx.set_state(indoc! {"
3262 onˇe two three
3263 fou«rˇ» five six
3264 seven «ˇeight nine
3265 »ten
3266 "});
3267 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3268 cx.assert_editor_state(indoc! {"
3269 oˇe two three
3270 fouˇ five six
3271 seven ˇten
3272 "});
3273
3274 // Test backspace inside and around indents
3275 cx.set_state(indoc! {"
3276 zero
3277 ˇone
3278 ˇtwo
3279 ˇ ˇ ˇ three
3280 ˇ ˇ four
3281 "});
3282 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3283 cx.assert_editor_state(indoc! {"
3284 zero
3285 ˇone
3286 ˇtwo
3287 ˇ threeˇ four
3288 "});
3289}
3290
3291#[gpui::test]
3292async fn test_delete(cx: &mut TestAppContext) {
3293 init_test(cx, |_| {});
3294
3295 let mut cx = EditorTestContext::new(cx).await;
3296 cx.set_state(indoc! {"
3297 onˇe two three
3298 fou«rˇ» five six
3299 seven «ˇeight nine
3300 »ten
3301 "});
3302 cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
3303 cx.assert_editor_state(indoc! {"
3304 onˇ two three
3305 fouˇ five six
3306 seven ˇten
3307 "});
3308}
3309
3310#[gpui::test]
3311fn test_delete_line(cx: &mut TestAppContext) {
3312 init_test(cx, |_| {});
3313
3314 let editor = cx.add_window(|window, cx| {
3315 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3316 build_editor(buffer, window, cx)
3317 });
3318 _ = editor.update(cx, |editor, window, cx| {
3319 editor.change_selections(None, window, cx, |s| {
3320 s.select_display_ranges([
3321 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3322 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3323 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3324 ])
3325 });
3326 editor.delete_line(&DeleteLine, window, cx);
3327 assert_eq!(editor.display_text(cx), "ghi");
3328 assert_eq!(
3329 editor.selections.display_ranges(cx),
3330 vec![
3331 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
3332 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
3333 ]
3334 );
3335 });
3336
3337 let editor = cx.add_window(|window, cx| {
3338 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3339 build_editor(buffer, window, cx)
3340 });
3341 _ = editor.update(cx, |editor, window, cx| {
3342 editor.change_selections(None, window, cx, |s| {
3343 s.select_display_ranges([
3344 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
3345 ])
3346 });
3347 editor.delete_line(&DeleteLine, window, cx);
3348 assert_eq!(editor.display_text(cx), "ghi\n");
3349 assert_eq!(
3350 editor.selections.display_ranges(cx),
3351 vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
3352 );
3353 });
3354}
3355
3356#[gpui::test]
3357fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
3358 init_test(cx, |_| {});
3359
3360 cx.add_window(|window, cx| {
3361 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3362 let mut editor = build_editor(buffer.clone(), window, cx);
3363 let buffer = buffer.read(cx).as_singleton().unwrap();
3364
3365 assert_eq!(
3366 editor.selections.ranges::<Point>(cx),
3367 &[Point::new(0, 0)..Point::new(0, 0)]
3368 );
3369
3370 // When on single line, replace newline at end by space
3371 editor.join_lines(&JoinLines, window, cx);
3372 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3373 assert_eq!(
3374 editor.selections.ranges::<Point>(cx),
3375 &[Point::new(0, 3)..Point::new(0, 3)]
3376 );
3377
3378 // When multiple lines are selected, remove newlines that are spanned by the selection
3379 editor.change_selections(None, window, cx, |s| {
3380 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
3381 });
3382 editor.join_lines(&JoinLines, window, cx);
3383 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
3384 assert_eq!(
3385 editor.selections.ranges::<Point>(cx),
3386 &[Point::new(0, 11)..Point::new(0, 11)]
3387 );
3388
3389 // Undo should be transactional
3390 editor.undo(&Undo, window, cx);
3391 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3392 assert_eq!(
3393 editor.selections.ranges::<Point>(cx),
3394 &[Point::new(0, 5)..Point::new(2, 2)]
3395 );
3396
3397 // When joining an empty line don't insert a space
3398 editor.change_selections(None, window, cx, |s| {
3399 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
3400 });
3401 editor.join_lines(&JoinLines, window, cx);
3402 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
3403 assert_eq!(
3404 editor.selections.ranges::<Point>(cx),
3405 [Point::new(2, 3)..Point::new(2, 3)]
3406 );
3407
3408 // We can remove trailing newlines
3409 editor.join_lines(&JoinLines, window, cx);
3410 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3411 assert_eq!(
3412 editor.selections.ranges::<Point>(cx),
3413 [Point::new(2, 3)..Point::new(2, 3)]
3414 );
3415
3416 // We don't blow up on the last line
3417 editor.join_lines(&JoinLines, window, cx);
3418 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3419 assert_eq!(
3420 editor.selections.ranges::<Point>(cx),
3421 [Point::new(2, 3)..Point::new(2, 3)]
3422 );
3423
3424 // reset to test indentation
3425 editor.buffer.update(cx, |buffer, cx| {
3426 buffer.edit(
3427 [
3428 (Point::new(1, 0)..Point::new(1, 2), " "),
3429 (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
3430 ],
3431 None,
3432 cx,
3433 )
3434 });
3435
3436 // We remove any leading spaces
3437 assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
3438 editor.change_selections(None, window, cx, |s| {
3439 s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
3440 });
3441 editor.join_lines(&JoinLines, window, cx);
3442 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
3443
3444 // We don't insert a space for a line containing only spaces
3445 editor.join_lines(&JoinLines, window, cx);
3446 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
3447
3448 // We ignore any leading tabs
3449 editor.join_lines(&JoinLines, window, cx);
3450 assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
3451
3452 editor
3453 });
3454}
3455
3456#[gpui::test]
3457fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
3458 init_test(cx, |_| {});
3459
3460 cx.add_window(|window, cx| {
3461 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3462 let mut editor = build_editor(buffer.clone(), window, cx);
3463 let buffer = buffer.read(cx).as_singleton().unwrap();
3464
3465 editor.change_selections(None, window, cx, |s| {
3466 s.select_ranges([
3467 Point::new(0, 2)..Point::new(1, 1),
3468 Point::new(1, 2)..Point::new(1, 2),
3469 Point::new(3, 1)..Point::new(3, 2),
3470 ])
3471 });
3472
3473 editor.join_lines(&JoinLines, window, cx);
3474 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
3475
3476 assert_eq!(
3477 editor.selections.ranges::<Point>(cx),
3478 [
3479 Point::new(0, 7)..Point::new(0, 7),
3480 Point::new(1, 3)..Point::new(1, 3)
3481 ]
3482 );
3483 editor
3484 });
3485}
3486
3487#[gpui::test]
3488async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
3489 init_test(cx, |_| {});
3490
3491 let mut cx = EditorTestContext::new(cx).await;
3492
3493 let diff_base = r#"
3494 Line 0
3495 Line 1
3496 Line 2
3497 Line 3
3498 "#
3499 .unindent();
3500
3501 cx.set_state(
3502 &r#"
3503 ˇLine 0
3504 Line 1
3505 Line 2
3506 Line 3
3507 "#
3508 .unindent(),
3509 );
3510
3511 cx.set_head_text(&diff_base);
3512 executor.run_until_parked();
3513
3514 // Join lines
3515 cx.update_editor(|editor, window, cx| {
3516 editor.join_lines(&JoinLines, window, cx);
3517 });
3518 executor.run_until_parked();
3519
3520 cx.assert_editor_state(
3521 &r#"
3522 Line 0ˇ Line 1
3523 Line 2
3524 Line 3
3525 "#
3526 .unindent(),
3527 );
3528 // Join again
3529 cx.update_editor(|editor, window, cx| {
3530 editor.join_lines(&JoinLines, window, cx);
3531 });
3532 executor.run_until_parked();
3533
3534 cx.assert_editor_state(
3535 &r#"
3536 Line 0 Line 1ˇ Line 2
3537 Line 3
3538 "#
3539 .unindent(),
3540 );
3541}
3542
3543#[gpui::test]
3544async fn test_custom_newlines_cause_no_false_positive_diffs(
3545 executor: BackgroundExecutor,
3546 cx: &mut TestAppContext,
3547) {
3548 init_test(cx, |_| {});
3549 let mut cx = EditorTestContext::new(cx).await;
3550 cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
3551 cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
3552 executor.run_until_parked();
3553
3554 cx.update_editor(|editor, window, cx| {
3555 let snapshot = editor.snapshot(window, cx);
3556 assert_eq!(
3557 snapshot
3558 .buffer_snapshot
3559 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
3560 .collect::<Vec<_>>(),
3561 Vec::new(),
3562 "Should not have any diffs for files with custom newlines"
3563 );
3564 });
3565}
3566
3567#[gpui::test]
3568async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
3569 init_test(cx, |_| {});
3570
3571 let mut cx = EditorTestContext::new(cx).await;
3572
3573 // Test sort_lines_case_insensitive()
3574 cx.set_state(indoc! {"
3575 «z
3576 y
3577 x
3578 Z
3579 Y
3580 Xˇ»
3581 "});
3582 cx.update_editor(|e, window, cx| {
3583 e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
3584 });
3585 cx.assert_editor_state(indoc! {"
3586 «x
3587 X
3588 y
3589 Y
3590 z
3591 Zˇ»
3592 "});
3593
3594 // Test reverse_lines()
3595 cx.set_state(indoc! {"
3596 «5
3597 4
3598 3
3599 2
3600 1ˇ»
3601 "});
3602 cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
3603 cx.assert_editor_state(indoc! {"
3604 «1
3605 2
3606 3
3607 4
3608 5ˇ»
3609 "});
3610
3611 // Skip testing shuffle_line()
3612
3613 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
3614 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
3615
3616 // Don't manipulate when cursor is on single line, but expand the selection
3617 cx.set_state(indoc! {"
3618 ddˇdd
3619 ccc
3620 bb
3621 a
3622 "});
3623 cx.update_editor(|e, window, cx| {
3624 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3625 });
3626 cx.assert_editor_state(indoc! {"
3627 «ddddˇ»
3628 ccc
3629 bb
3630 a
3631 "});
3632
3633 // Basic manipulate case
3634 // Start selection moves to column 0
3635 // End of selection shrinks to fit shorter line
3636 cx.set_state(indoc! {"
3637 dd«d
3638 ccc
3639 bb
3640 aaaaaˇ»
3641 "});
3642 cx.update_editor(|e, window, cx| {
3643 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3644 });
3645 cx.assert_editor_state(indoc! {"
3646 «aaaaa
3647 bb
3648 ccc
3649 dddˇ»
3650 "});
3651
3652 // Manipulate case with newlines
3653 cx.set_state(indoc! {"
3654 dd«d
3655 ccc
3656
3657 bb
3658 aaaaa
3659
3660 ˇ»
3661 "});
3662 cx.update_editor(|e, window, cx| {
3663 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3664 });
3665 cx.assert_editor_state(indoc! {"
3666 «
3667
3668 aaaaa
3669 bb
3670 ccc
3671 dddˇ»
3672
3673 "});
3674
3675 // Adding new line
3676 cx.set_state(indoc! {"
3677 aa«a
3678 bbˇ»b
3679 "});
3680 cx.update_editor(|e, window, cx| {
3681 e.manipulate_lines(window, cx, |lines| lines.push("added_line"))
3682 });
3683 cx.assert_editor_state(indoc! {"
3684 «aaa
3685 bbb
3686 added_lineˇ»
3687 "});
3688
3689 // Removing line
3690 cx.set_state(indoc! {"
3691 aa«a
3692 bbbˇ»
3693 "});
3694 cx.update_editor(|e, window, cx| {
3695 e.manipulate_lines(window, cx, |lines| {
3696 lines.pop();
3697 })
3698 });
3699 cx.assert_editor_state(indoc! {"
3700 «aaaˇ»
3701 "});
3702
3703 // Removing all lines
3704 cx.set_state(indoc! {"
3705 aa«a
3706 bbbˇ»
3707 "});
3708 cx.update_editor(|e, window, cx| {
3709 e.manipulate_lines(window, cx, |lines| {
3710 lines.drain(..);
3711 })
3712 });
3713 cx.assert_editor_state(indoc! {"
3714 ˇ
3715 "});
3716}
3717
3718#[gpui::test]
3719async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
3720 init_test(cx, |_| {});
3721
3722 let mut cx = EditorTestContext::new(cx).await;
3723
3724 // Consider continuous selection as single selection
3725 cx.set_state(indoc! {"
3726 Aaa«aa
3727 cˇ»c«c
3728 bb
3729 aaaˇ»aa
3730 "});
3731 cx.update_editor(|e, window, cx| {
3732 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3733 });
3734 cx.assert_editor_state(indoc! {"
3735 «Aaaaa
3736 ccc
3737 bb
3738 aaaaaˇ»
3739 "});
3740
3741 cx.set_state(indoc! {"
3742 Aaa«aa
3743 cˇ»c«c
3744 bb
3745 aaaˇ»aa
3746 "});
3747 cx.update_editor(|e, window, cx| {
3748 e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
3749 });
3750 cx.assert_editor_state(indoc! {"
3751 «Aaaaa
3752 ccc
3753 bbˇ»
3754 "});
3755
3756 // Consider non continuous selection as distinct dedup operations
3757 cx.set_state(indoc! {"
3758 «aaaaa
3759 bb
3760 aaaaa
3761 aaaaaˇ»
3762
3763 aaa«aaˇ»
3764 "});
3765 cx.update_editor(|e, window, cx| {
3766 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3767 });
3768 cx.assert_editor_state(indoc! {"
3769 «aaaaa
3770 bbˇ»
3771
3772 «aaaaaˇ»
3773 "});
3774}
3775
3776#[gpui::test]
3777async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
3778 init_test(cx, |_| {});
3779
3780 let mut cx = EditorTestContext::new(cx).await;
3781
3782 cx.set_state(indoc! {"
3783 «Aaa
3784 aAa
3785 Aaaˇ»
3786 "});
3787 cx.update_editor(|e, window, cx| {
3788 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3789 });
3790 cx.assert_editor_state(indoc! {"
3791 «Aaa
3792 aAaˇ»
3793 "});
3794
3795 cx.set_state(indoc! {"
3796 «Aaa
3797 aAa
3798 aaAˇ»
3799 "});
3800 cx.update_editor(|e, window, cx| {
3801 e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
3802 });
3803 cx.assert_editor_state(indoc! {"
3804 «Aaaˇ»
3805 "});
3806}
3807
3808#[gpui::test]
3809async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
3810 init_test(cx, |_| {});
3811
3812 let mut cx = EditorTestContext::new(cx).await;
3813
3814 // Manipulate with multiple selections on a single line
3815 cx.set_state(indoc! {"
3816 dd«dd
3817 cˇ»c«c
3818 bb
3819 aaaˇ»aa
3820 "});
3821 cx.update_editor(|e, window, cx| {
3822 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3823 });
3824 cx.assert_editor_state(indoc! {"
3825 «aaaaa
3826 bb
3827 ccc
3828 ddddˇ»
3829 "});
3830
3831 // Manipulate with multiple disjoin selections
3832 cx.set_state(indoc! {"
3833 5«
3834 4
3835 3
3836 2
3837 1ˇ»
3838
3839 dd«dd
3840 ccc
3841 bb
3842 aaaˇ»aa
3843 "});
3844 cx.update_editor(|e, window, cx| {
3845 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3846 });
3847 cx.assert_editor_state(indoc! {"
3848 «1
3849 2
3850 3
3851 4
3852 5ˇ»
3853
3854 «aaaaa
3855 bb
3856 ccc
3857 ddddˇ»
3858 "});
3859
3860 // Adding lines on each selection
3861 cx.set_state(indoc! {"
3862 2«
3863 1ˇ»
3864
3865 bb«bb
3866 aaaˇ»aa
3867 "});
3868 cx.update_editor(|e, window, cx| {
3869 e.manipulate_lines(window, cx, |lines| lines.push("added line"))
3870 });
3871 cx.assert_editor_state(indoc! {"
3872 «2
3873 1
3874 added lineˇ»
3875
3876 «bbbb
3877 aaaaa
3878 added lineˇ»
3879 "});
3880
3881 // Removing lines on each selection
3882 cx.set_state(indoc! {"
3883 2«
3884 1ˇ»
3885
3886 bb«bb
3887 aaaˇ»aa
3888 "});
3889 cx.update_editor(|e, window, cx| {
3890 e.manipulate_lines(window, cx, |lines| {
3891 lines.pop();
3892 })
3893 });
3894 cx.assert_editor_state(indoc! {"
3895 «2ˇ»
3896
3897 «bbbbˇ»
3898 "});
3899}
3900
3901#[gpui::test]
3902async fn test_toggle_case(cx: &mut TestAppContext) {
3903 init_test(cx, |_| {});
3904
3905 let mut cx = EditorTestContext::new(cx).await;
3906
3907 // If all lower case -> upper case
3908 cx.set_state(indoc! {"
3909 «hello worldˇ»
3910 "});
3911 cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
3912 cx.assert_editor_state(indoc! {"
3913 «HELLO WORLDˇ»
3914 "});
3915
3916 // If all upper case -> lower case
3917 cx.set_state(indoc! {"
3918 «HELLO WORLDˇ»
3919 "});
3920 cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
3921 cx.assert_editor_state(indoc! {"
3922 «hello worldˇ»
3923 "});
3924
3925 // If any upper case characters are identified -> lower case
3926 // This matches JetBrains IDEs
3927 cx.set_state(indoc! {"
3928 «hEllo worldˇ»
3929 "});
3930 cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
3931 cx.assert_editor_state(indoc! {"
3932 «hello worldˇ»
3933 "});
3934}
3935
3936#[gpui::test]
3937async fn test_manipulate_text(cx: &mut TestAppContext) {
3938 init_test(cx, |_| {});
3939
3940 let mut cx = EditorTestContext::new(cx).await;
3941
3942 // Test convert_to_upper_case()
3943 cx.set_state(indoc! {"
3944 «hello worldˇ»
3945 "});
3946 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3947 cx.assert_editor_state(indoc! {"
3948 «HELLO WORLDˇ»
3949 "});
3950
3951 // Test convert_to_lower_case()
3952 cx.set_state(indoc! {"
3953 «HELLO WORLDˇ»
3954 "});
3955 cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
3956 cx.assert_editor_state(indoc! {"
3957 «hello worldˇ»
3958 "});
3959
3960 // Test multiple line, single selection case
3961 cx.set_state(indoc! {"
3962 «The quick brown
3963 fox jumps over
3964 the lazy dogˇ»
3965 "});
3966 cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
3967 cx.assert_editor_state(indoc! {"
3968 «The Quick Brown
3969 Fox Jumps Over
3970 The Lazy Dogˇ»
3971 "});
3972
3973 // Test multiple line, single selection case
3974 cx.set_state(indoc! {"
3975 «The quick brown
3976 fox jumps over
3977 the lazy dogˇ»
3978 "});
3979 cx.update_editor(|e, window, cx| {
3980 e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
3981 });
3982 cx.assert_editor_state(indoc! {"
3983 «TheQuickBrown
3984 FoxJumpsOver
3985 TheLazyDogˇ»
3986 "});
3987
3988 // From here on out, test more complex cases of manipulate_text()
3989
3990 // Test no selection case - should affect words cursors are in
3991 // Cursor at beginning, middle, and end of word
3992 cx.set_state(indoc! {"
3993 ˇhello big beauˇtiful worldˇ
3994 "});
3995 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3996 cx.assert_editor_state(indoc! {"
3997 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3998 "});
3999
4000 // Test multiple selections on a single line and across multiple lines
4001 cx.set_state(indoc! {"
4002 «Theˇ» quick «brown
4003 foxˇ» jumps «overˇ»
4004 the «lazyˇ» dog
4005 "});
4006 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
4007 cx.assert_editor_state(indoc! {"
4008 «THEˇ» quick «BROWN
4009 FOXˇ» jumps «OVERˇ»
4010 the «LAZYˇ» dog
4011 "});
4012
4013 // Test case where text length grows
4014 cx.set_state(indoc! {"
4015 «tschüߡ»
4016 "});
4017 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
4018 cx.assert_editor_state(indoc! {"
4019 «TSCHÜSSˇ»
4020 "});
4021
4022 // Test to make sure we don't crash when text shrinks
4023 cx.set_state(indoc! {"
4024 aaa_bbbˇ
4025 "});
4026 cx.update_editor(|e, window, cx| {
4027 e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
4028 });
4029 cx.assert_editor_state(indoc! {"
4030 «aaaBbbˇ»
4031 "});
4032
4033 // Test to make sure we all aware of the fact that each word can grow and shrink
4034 // Final selections should be aware of this fact
4035 cx.set_state(indoc! {"
4036 aaa_bˇbb bbˇb_ccc ˇccc_ddd
4037 "});
4038 cx.update_editor(|e, window, cx| {
4039 e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
4040 });
4041 cx.assert_editor_state(indoc! {"
4042 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
4043 "});
4044
4045 cx.set_state(indoc! {"
4046 «hElLo, WoRld!ˇ»
4047 "});
4048 cx.update_editor(|e, window, cx| {
4049 e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
4050 });
4051 cx.assert_editor_state(indoc! {"
4052 «HeLlO, wOrLD!ˇ»
4053 "});
4054}
4055
4056#[gpui::test]
4057fn test_duplicate_line(cx: &mut TestAppContext) {
4058 init_test(cx, |_| {});
4059
4060 let editor = cx.add_window(|window, cx| {
4061 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4062 build_editor(buffer, window, cx)
4063 });
4064 _ = editor.update(cx, |editor, window, cx| {
4065 editor.change_selections(None, window, cx, |s| {
4066 s.select_display_ranges([
4067 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4068 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4069 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4070 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
4071 ])
4072 });
4073 editor.duplicate_line_down(&DuplicateLineDown, window, cx);
4074 assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
4075 assert_eq!(
4076 editor.selections.display_ranges(cx),
4077 vec![
4078 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
4079 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
4080 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
4081 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
4082 ]
4083 );
4084 });
4085
4086 let editor = cx.add_window(|window, cx| {
4087 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4088 build_editor(buffer, window, cx)
4089 });
4090 _ = editor.update(cx, |editor, window, cx| {
4091 editor.change_selections(None, window, cx, |s| {
4092 s.select_display_ranges([
4093 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4094 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4095 ])
4096 });
4097 editor.duplicate_line_down(&DuplicateLineDown, window, cx);
4098 assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
4099 assert_eq!(
4100 editor.selections.display_ranges(cx),
4101 vec![
4102 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
4103 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
4104 ]
4105 );
4106 });
4107
4108 // With `move_upwards` the selections stay in place, except for
4109 // the lines inserted above them
4110 let editor = cx.add_window(|window, cx| {
4111 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4112 build_editor(buffer, window, cx)
4113 });
4114 _ = editor.update(cx, |editor, window, cx| {
4115 editor.change_selections(None, window, cx, |s| {
4116 s.select_display_ranges([
4117 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4118 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4119 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4120 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
4121 ])
4122 });
4123 editor.duplicate_line_up(&DuplicateLineUp, window, cx);
4124 assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
4125 assert_eq!(
4126 editor.selections.display_ranges(cx),
4127 vec![
4128 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4129 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4130 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
4131 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
4132 ]
4133 );
4134 });
4135
4136 let editor = cx.add_window(|window, cx| {
4137 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4138 build_editor(buffer, window, cx)
4139 });
4140 _ = editor.update(cx, |editor, window, cx| {
4141 editor.change_selections(None, window, cx, |s| {
4142 s.select_display_ranges([
4143 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4144 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4145 ])
4146 });
4147 editor.duplicate_line_up(&DuplicateLineUp, window, cx);
4148 assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
4149 assert_eq!(
4150 editor.selections.display_ranges(cx),
4151 vec![
4152 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4153 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4154 ]
4155 );
4156 });
4157
4158 let editor = cx.add_window(|window, cx| {
4159 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4160 build_editor(buffer, window, cx)
4161 });
4162 _ = editor.update(cx, |editor, window, cx| {
4163 editor.change_selections(None, window, cx, |s| {
4164 s.select_display_ranges([
4165 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4166 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4167 ])
4168 });
4169 editor.duplicate_selection(&DuplicateSelection, window, cx);
4170 assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
4171 assert_eq!(
4172 editor.selections.display_ranges(cx),
4173 vec![
4174 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4175 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
4176 ]
4177 );
4178 });
4179}
4180
4181#[gpui::test]
4182fn test_move_line_up_down(cx: &mut TestAppContext) {
4183 init_test(cx, |_| {});
4184
4185 let editor = cx.add_window(|window, cx| {
4186 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4187 build_editor(buffer, window, cx)
4188 });
4189 _ = editor.update(cx, |editor, window, cx| {
4190 editor.fold_creases(
4191 vec![
4192 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4193 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4194 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4195 ],
4196 true,
4197 window,
4198 cx,
4199 );
4200 editor.change_selections(None, window, cx, |s| {
4201 s.select_display_ranges([
4202 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4203 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4204 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4205 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
4206 ])
4207 });
4208 assert_eq!(
4209 editor.display_text(cx),
4210 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
4211 );
4212
4213 editor.move_line_up(&MoveLineUp, window, cx);
4214 assert_eq!(
4215 editor.display_text(cx),
4216 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
4217 );
4218 assert_eq!(
4219 editor.selections.display_ranges(cx),
4220 vec![
4221 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4222 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4223 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
4224 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
4225 ]
4226 );
4227 });
4228
4229 _ = editor.update(cx, |editor, window, cx| {
4230 editor.move_line_down(&MoveLineDown, window, cx);
4231 assert_eq!(
4232 editor.display_text(cx),
4233 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
4234 );
4235 assert_eq!(
4236 editor.selections.display_ranges(cx),
4237 vec![
4238 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
4239 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4240 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4241 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
4242 ]
4243 );
4244 });
4245
4246 _ = editor.update(cx, |editor, window, cx| {
4247 editor.move_line_down(&MoveLineDown, window, cx);
4248 assert_eq!(
4249 editor.display_text(cx),
4250 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
4251 );
4252 assert_eq!(
4253 editor.selections.display_ranges(cx),
4254 vec![
4255 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4256 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4257 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4258 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
4259 ]
4260 );
4261 });
4262
4263 _ = editor.update(cx, |editor, window, cx| {
4264 editor.move_line_up(&MoveLineUp, window, cx);
4265 assert_eq!(
4266 editor.display_text(cx),
4267 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
4268 );
4269 assert_eq!(
4270 editor.selections.display_ranges(cx),
4271 vec![
4272 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
4273 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4274 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
4275 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
4276 ]
4277 );
4278 });
4279}
4280
4281#[gpui::test]
4282fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
4283 init_test(cx, |_| {});
4284
4285 let editor = cx.add_window(|window, cx| {
4286 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4287 build_editor(buffer, window, cx)
4288 });
4289 _ = editor.update(cx, |editor, window, cx| {
4290 let snapshot = editor.buffer.read(cx).snapshot(cx);
4291 editor.insert_blocks(
4292 [BlockProperties {
4293 style: BlockStyle::Fixed,
4294 placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
4295 height: Some(1),
4296 render: Arc::new(|_| div().into_any()),
4297 priority: 0,
4298 }],
4299 Some(Autoscroll::fit()),
4300 cx,
4301 );
4302 editor.change_selections(None, window, cx, |s| {
4303 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
4304 });
4305 editor.move_line_down(&MoveLineDown, window, cx);
4306 });
4307}
4308
4309#[gpui::test]
4310async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
4311 init_test(cx, |_| {});
4312
4313 let mut cx = EditorTestContext::new(cx).await;
4314 cx.set_state(
4315 &"
4316 ˇzero
4317 one
4318 two
4319 three
4320 four
4321 five
4322 "
4323 .unindent(),
4324 );
4325
4326 // Create a four-line block that replaces three lines of text.
4327 cx.update_editor(|editor, window, cx| {
4328 let snapshot = editor.snapshot(window, cx);
4329 let snapshot = &snapshot.buffer_snapshot;
4330 let placement = BlockPlacement::Replace(
4331 snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
4332 );
4333 editor.insert_blocks(
4334 [BlockProperties {
4335 placement,
4336 height: Some(4),
4337 style: BlockStyle::Sticky,
4338 render: Arc::new(|_| gpui::div().into_any_element()),
4339 priority: 0,
4340 }],
4341 None,
4342 cx,
4343 );
4344 });
4345
4346 // Move down so that the cursor touches the block.
4347 cx.update_editor(|editor, window, cx| {
4348 editor.move_down(&Default::default(), window, cx);
4349 });
4350 cx.assert_editor_state(
4351 &"
4352 zero
4353 «one
4354 two
4355 threeˇ»
4356 four
4357 five
4358 "
4359 .unindent(),
4360 );
4361
4362 // Move down past the block.
4363 cx.update_editor(|editor, window, cx| {
4364 editor.move_down(&Default::default(), window, cx);
4365 });
4366 cx.assert_editor_state(
4367 &"
4368 zero
4369 one
4370 two
4371 three
4372 ˇfour
4373 five
4374 "
4375 .unindent(),
4376 );
4377}
4378
4379#[gpui::test]
4380fn test_transpose(cx: &mut TestAppContext) {
4381 init_test(cx, |_| {});
4382
4383 _ = cx.add_window(|window, cx| {
4384 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
4385 editor.set_style(EditorStyle::default(), window, cx);
4386 editor.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
4387 editor.transpose(&Default::default(), window, cx);
4388 assert_eq!(editor.text(cx), "bac");
4389 assert_eq!(editor.selections.ranges(cx), [2..2]);
4390
4391 editor.transpose(&Default::default(), window, cx);
4392 assert_eq!(editor.text(cx), "bca");
4393 assert_eq!(editor.selections.ranges(cx), [3..3]);
4394
4395 editor.transpose(&Default::default(), window, cx);
4396 assert_eq!(editor.text(cx), "bac");
4397 assert_eq!(editor.selections.ranges(cx), [3..3]);
4398
4399 editor
4400 });
4401
4402 _ = cx.add_window(|window, cx| {
4403 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
4404 editor.set_style(EditorStyle::default(), window, cx);
4405 editor.change_selections(None, window, cx, |s| s.select_ranges([3..3]));
4406 editor.transpose(&Default::default(), window, cx);
4407 assert_eq!(editor.text(cx), "acb\nde");
4408 assert_eq!(editor.selections.ranges(cx), [3..3]);
4409
4410 editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
4411 editor.transpose(&Default::default(), window, cx);
4412 assert_eq!(editor.text(cx), "acbd\ne");
4413 assert_eq!(editor.selections.ranges(cx), [5..5]);
4414
4415 editor.transpose(&Default::default(), window, cx);
4416 assert_eq!(editor.text(cx), "acbde\n");
4417 assert_eq!(editor.selections.ranges(cx), [6..6]);
4418
4419 editor.transpose(&Default::default(), window, cx);
4420 assert_eq!(editor.text(cx), "acbd\ne");
4421 assert_eq!(editor.selections.ranges(cx), [6..6]);
4422
4423 editor
4424 });
4425
4426 _ = cx.add_window(|window, cx| {
4427 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
4428 editor.set_style(EditorStyle::default(), window, cx);
4429 editor.change_selections(None, window, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
4430 editor.transpose(&Default::default(), window, cx);
4431 assert_eq!(editor.text(cx), "bacd\ne");
4432 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
4433
4434 editor.transpose(&Default::default(), window, cx);
4435 assert_eq!(editor.text(cx), "bcade\n");
4436 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
4437
4438 editor.transpose(&Default::default(), window, cx);
4439 assert_eq!(editor.text(cx), "bcda\ne");
4440 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4441
4442 editor.transpose(&Default::default(), window, cx);
4443 assert_eq!(editor.text(cx), "bcade\n");
4444 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4445
4446 editor.transpose(&Default::default(), window, cx);
4447 assert_eq!(editor.text(cx), "bcaed\n");
4448 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
4449
4450 editor
4451 });
4452
4453 _ = cx.add_window(|window, cx| {
4454 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
4455 editor.set_style(EditorStyle::default(), window, cx);
4456 editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
4457 editor.transpose(&Default::default(), window, cx);
4458 assert_eq!(editor.text(cx), "🏀🍐✋");
4459 assert_eq!(editor.selections.ranges(cx), [8..8]);
4460
4461 editor.transpose(&Default::default(), window, cx);
4462 assert_eq!(editor.text(cx), "🏀✋🍐");
4463 assert_eq!(editor.selections.ranges(cx), [11..11]);
4464
4465 editor.transpose(&Default::default(), window, cx);
4466 assert_eq!(editor.text(cx), "🏀🍐✋");
4467 assert_eq!(editor.selections.ranges(cx), [11..11]);
4468
4469 editor
4470 });
4471}
4472
4473#[gpui::test]
4474async fn test_rewrap(cx: &mut TestAppContext) {
4475 init_test(cx, |settings| {
4476 settings.languages.extend([
4477 (
4478 "Markdown".into(),
4479 LanguageSettingsContent {
4480 allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
4481 ..Default::default()
4482 },
4483 ),
4484 (
4485 "Plain Text".into(),
4486 LanguageSettingsContent {
4487 allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
4488 ..Default::default()
4489 },
4490 ),
4491 ])
4492 });
4493
4494 let mut cx = EditorTestContext::new(cx).await;
4495
4496 let language_with_c_comments = Arc::new(Language::new(
4497 LanguageConfig {
4498 line_comments: vec!["// ".into()],
4499 ..LanguageConfig::default()
4500 },
4501 None,
4502 ));
4503 let language_with_pound_comments = Arc::new(Language::new(
4504 LanguageConfig {
4505 line_comments: vec!["# ".into()],
4506 ..LanguageConfig::default()
4507 },
4508 None,
4509 ));
4510 let markdown_language = Arc::new(Language::new(
4511 LanguageConfig {
4512 name: "Markdown".into(),
4513 ..LanguageConfig::default()
4514 },
4515 None,
4516 ));
4517 let language_with_doc_comments = Arc::new(Language::new(
4518 LanguageConfig {
4519 line_comments: vec!["// ".into(), "/// ".into()],
4520 ..LanguageConfig::default()
4521 },
4522 Some(tree_sitter_rust::LANGUAGE.into()),
4523 ));
4524
4525 let plaintext_language = Arc::new(Language::new(
4526 LanguageConfig {
4527 name: "Plain Text".into(),
4528 ..LanguageConfig::default()
4529 },
4530 None,
4531 ));
4532
4533 assert_rewrap(
4534 indoc! {"
4535 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4536 "},
4537 indoc! {"
4538 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4539 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4540 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4541 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4542 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4543 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4544 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4545 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4546 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4547 // porttitor id. Aliquam id accumsan eros.
4548 "},
4549 language_with_c_comments.clone(),
4550 &mut cx,
4551 );
4552
4553 // Test that rewrapping works inside of a selection
4554 assert_rewrap(
4555 indoc! {"
4556 «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.ˇ»
4557 "},
4558 indoc! {"
4559 «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4560 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4561 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4562 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4563 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4564 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4565 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4566 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4567 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4568 // porttitor id. Aliquam id accumsan eros.ˇ»
4569 "},
4570 language_with_c_comments.clone(),
4571 &mut cx,
4572 );
4573
4574 // Test that cursors that expand to the same region are collapsed.
4575 assert_rewrap(
4576 indoc! {"
4577 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4578 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4579 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4580 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4581 "},
4582 indoc! {"
4583 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4584 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4585 // auctor, eu lacinia sapien scelerisque. ˇVivamus sit amet neque et quam
4586 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4587 // Pellentesque odio lectus, iaculis ac volutpat et, ˇblandit quis urna. Sed
4588 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4589 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4590 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4591 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4592 // porttitor id. Aliquam id accumsan eros.
4593 "},
4594 language_with_c_comments.clone(),
4595 &mut cx,
4596 );
4597
4598 // Test that non-contiguous selections are treated separately.
4599 assert_rewrap(
4600 indoc! {"
4601 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4602 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4603 //
4604 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4605 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4606 "},
4607 indoc! {"
4608 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4609 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4610 // auctor, eu lacinia sapien scelerisque.
4611 //
4612 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
4613 // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4614 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
4615 // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
4616 // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
4617 // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
4618 // vulputate turpis porttitor id. Aliquam id accumsan eros.
4619 "},
4620 language_with_c_comments.clone(),
4621 &mut cx,
4622 );
4623
4624 // Test that different comment prefixes are supported.
4625 assert_rewrap(
4626 indoc! {"
4627 # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4628 "},
4629 indoc! {"
4630 # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4631 # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4632 # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4633 # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4634 # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
4635 # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
4636 # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
4637 # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
4638 # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
4639 # accumsan eros.
4640 "},
4641 language_with_pound_comments.clone(),
4642 &mut cx,
4643 );
4644
4645 // Test that rewrapping is ignored outside of comments in most languages.
4646 assert_rewrap(
4647 indoc! {"
4648 /// Adds two numbers.
4649 /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4650 fn add(a: u32, b: u32) -> u32 {
4651 a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + bˇ
4652 }
4653 "},
4654 indoc! {"
4655 /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
4656 /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4657 fn add(a: u32, b: u32) -> u32 {
4658 a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + bˇ
4659 }
4660 "},
4661 language_with_doc_comments.clone(),
4662 &mut cx,
4663 );
4664
4665 // Test that rewrapping works in Markdown and Plain Text languages.
4666 assert_rewrap(
4667 indoc! {"
4668 # Hello
4669
4670 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi.
4671 "},
4672 indoc! {"
4673 # Hello
4674
4675 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4676 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4677 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4678 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4679 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4680 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4681 Integer sit amet scelerisque nisi.
4682 "},
4683 markdown_language,
4684 &mut cx,
4685 );
4686
4687 assert_rewrap(
4688 indoc! {"
4689 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi.
4690 "},
4691 indoc! {"
4692 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4693 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4694 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4695 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4696 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4697 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4698 Integer sit amet scelerisque nisi.
4699 "},
4700 plaintext_language,
4701 &mut cx,
4702 );
4703
4704 // Test rewrapping unaligned comments in a selection.
4705 assert_rewrap(
4706 indoc! {"
4707 fn foo() {
4708 if true {
4709 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4710 // Praesent semper egestas tellus id dignissim.ˇ»
4711 do_something();
4712 } else {
4713 //
4714 }
4715 }
4716 "},
4717 indoc! {"
4718 fn foo() {
4719 if true {
4720 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4721 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4722 // egestas tellus id dignissim.ˇ»
4723 do_something();
4724 } else {
4725 //
4726 }
4727 }
4728 "},
4729 language_with_doc_comments.clone(),
4730 &mut cx,
4731 );
4732
4733 assert_rewrap(
4734 indoc! {"
4735 fn foo() {
4736 if true {
4737 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4738 // Praesent semper egestas tellus id dignissim.»
4739 do_something();
4740 } else {
4741 //
4742 }
4743
4744 }
4745 "},
4746 indoc! {"
4747 fn foo() {
4748 if true {
4749 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4750 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4751 // egestas tellus id dignissim.»
4752 do_something();
4753 } else {
4754 //
4755 }
4756
4757 }
4758 "},
4759 language_with_doc_comments.clone(),
4760 &mut cx,
4761 );
4762
4763 #[track_caller]
4764 fn assert_rewrap(
4765 unwrapped_text: &str,
4766 wrapped_text: &str,
4767 language: Arc<Language>,
4768 cx: &mut EditorTestContext,
4769 ) {
4770 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4771 cx.set_state(unwrapped_text);
4772 cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
4773 cx.assert_editor_state(wrapped_text);
4774 }
4775}
4776
4777#[gpui::test]
4778async fn test_hard_wrap(cx: &mut TestAppContext) {
4779 init_test(cx, |_| {});
4780 let mut cx = EditorTestContext::new(cx).await;
4781
4782 cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
4783 cx.update_editor(|editor, _, cx| {
4784 editor.set_hard_wrap(Some(14), cx);
4785 });
4786
4787 cx.set_state(indoc!(
4788 "
4789 one two three ˇ
4790 "
4791 ));
4792 cx.simulate_input("four");
4793 cx.run_until_parked();
4794
4795 cx.assert_editor_state(indoc!(
4796 "
4797 one two three
4798 fourˇ
4799 "
4800 ));
4801
4802 cx.update_editor(|editor, window, cx| {
4803 editor.newline(&Default::default(), window, cx);
4804 });
4805 cx.run_until_parked();
4806 cx.assert_editor_state(indoc!(
4807 "
4808 one two three
4809 four
4810 ˇ
4811 "
4812 ));
4813
4814 cx.simulate_input("five");
4815 cx.run_until_parked();
4816 cx.assert_editor_state(indoc!(
4817 "
4818 one two three
4819 four
4820 fiveˇ
4821 "
4822 ));
4823
4824 cx.update_editor(|editor, window, cx| {
4825 editor.newline(&Default::default(), window, cx);
4826 });
4827 cx.run_until_parked();
4828 cx.simulate_input("# ");
4829 cx.run_until_parked();
4830 cx.assert_editor_state(indoc!(
4831 "
4832 one two three
4833 four
4834 five
4835 # ˇ
4836 "
4837 ));
4838
4839 cx.update_editor(|editor, window, cx| {
4840 editor.newline(&Default::default(), window, cx);
4841 });
4842 cx.run_until_parked();
4843 cx.assert_editor_state(indoc!(
4844 "
4845 one two three
4846 four
4847 five
4848 #\x20
4849 #ˇ
4850 "
4851 ));
4852
4853 cx.simulate_input(" 6");
4854 cx.run_until_parked();
4855 cx.assert_editor_state(indoc!(
4856 "
4857 one two three
4858 four
4859 five
4860 #
4861 # 6ˇ
4862 "
4863 ));
4864}
4865
4866#[gpui::test]
4867async fn test_clipboard(cx: &mut TestAppContext) {
4868 init_test(cx, |_| {});
4869
4870 let mut cx = EditorTestContext::new(cx).await;
4871
4872 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
4873 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4874 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
4875
4876 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
4877 cx.set_state("two ˇfour ˇsix ˇ");
4878 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4879 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
4880
4881 // Paste again but with only two cursors. Since the number of cursors doesn't
4882 // match the number of slices in the clipboard, the entire clipboard text
4883 // is pasted at each cursor.
4884 cx.set_state("ˇtwo one✅ four three six five ˇ");
4885 cx.update_editor(|e, window, cx| {
4886 e.handle_input("( ", window, cx);
4887 e.paste(&Paste, window, cx);
4888 e.handle_input(") ", window, cx);
4889 });
4890 cx.assert_editor_state(
4891 &([
4892 "( one✅ ",
4893 "three ",
4894 "five ) ˇtwo one✅ four three six five ( one✅ ",
4895 "three ",
4896 "five ) ˇ",
4897 ]
4898 .join("\n")),
4899 );
4900
4901 // Cut with three selections, one of which is full-line.
4902 cx.set_state(indoc! {"
4903 1«2ˇ»3
4904 4ˇ567
4905 «8ˇ»9"});
4906 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4907 cx.assert_editor_state(indoc! {"
4908 1ˇ3
4909 ˇ9"});
4910
4911 // Paste with three selections, noticing how the copied selection that was full-line
4912 // gets inserted before the second cursor.
4913 cx.set_state(indoc! {"
4914 1ˇ3
4915 9ˇ
4916 «oˇ»ne"});
4917 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4918 cx.assert_editor_state(indoc! {"
4919 12ˇ3
4920 4567
4921 9ˇ
4922 8ˇne"});
4923
4924 // Copy with a single cursor only, which writes the whole line into the clipboard.
4925 cx.set_state(indoc! {"
4926 The quick brown
4927 fox juˇmps over
4928 the lazy dog"});
4929 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
4930 assert_eq!(
4931 cx.read_from_clipboard()
4932 .and_then(|item| item.text().as_deref().map(str::to_string)),
4933 Some("fox jumps over\n".to_string())
4934 );
4935
4936 // Paste with three selections, noticing how the copied full-line selection is inserted
4937 // before the empty selections but replaces the selection that is non-empty.
4938 cx.set_state(indoc! {"
4939 Tˇhe quick brown
4940 «foˇ»x jumps over
4941 tˇhe lazy dog"});
4942 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4943 cx.assert_editor_state(indoc! {"
4944 fox jumps over
4945 Tˇhe quick brown
4946 fox jumps over
4947 ˇx jumps over
4948 fox jumps over
4949 tˇhe lazy dog"});
4950}
4951
4952#[gpui::test]
4953async fn test_copy_trim(cx: &mut TestAppContext) {
4954 init_test(cx, |_| {});
4955
4956 let mut cx = EditorTestContext::new(cx).await;
4957 cx.set_state(
4958 r#" «for selection in selections.iter() {
4959 let mut start = selection.start;
4960 let mut end = selection.end;
4961 let is_entire_line = selection.is_empty();
4962 if is_entire_line {
4963 start = Point::new(start.row, 0);ˇ»
4964 end = cmp::min(max_point, Point::new(end.row + 1, 0));
4965 }
4966 "#,
4967 );
4968 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
4969 assert_eq!(
4970 cx.read_from_clipboard()
4971 .and_then(|item| item.text().as_deref().map(str::to_string)),
4972 Some(
4973 "for selection in selections.iter() {
4974 let mut start = selection.start;
4975 let mut end = selection.end;
4976 let is_entire_line = selection.is_empty();
4977 if is_entire_line {
4978 start = Point::new(start.row, 0);"
4979 .to_string()
4980 ),
4981 "Regular copying preserves all indentation selected",
4982 );
4983 cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
4984 assert_eq!(
4985 cx.read_from_clipboard()
4986 .and_then(|item| item.text().as_deref().map(str::to_string)),
4987 Some(
4988 "for selection in selections.iter() {
4989let mut start = selection.start;
4990let mut end = selection.end;
4991let is_entire_line = selection.is_empty();
4992if is_entire_line {
4993 start = Point::new(start.row, 0);"
4994 .to_string()
4995 ),
4996 "Copying with stripping should strip all leading whitespaces"
4997 );
4998
4999 cx.set_state(
5000 r#" « for selection in selections.iter() {
5001 let mut start = selection.start;
5002 let mut end = selection.end;
5003 let is_entire_line = selection.is_empty();
5004 if is_entire_line {
5005 start = Point::new(start.row, 0);ˇ»
5006 end = cmp::min(max_point, Point::new(end.row + 1, 0));
5007 }
5008 "#,
5009 );
5010 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
5011 assert_eq!(
5012 cx.read_from_clipboard()
5013 .and_then(|item| item.text().as_deref().map(str::to_string)),
5014 Some(
5015 " for selection in selections.iter() {
5016 let mut start = selection.start;
5017 let mut end = selection.end;
5018 let is_entire_line = selection.is_empty();
5019 if is_entire_line {
5020 start = Point::new(start.row, 0);"
5021 .to_string()
5022 ),
5023 "Regular copying preserves all indentation selected",
5024 );
5025 cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
5026 assert_eq!(
5027 cx.read_from_clipboard()
5028 .and_then(|item| item.text().as_deref().map(str::to_string)),
5029 Some(
5030 "for selection in selections.iter() {
5031let mut start = selection.start;
5032let mut end = selection.end;
5033let is_entire_line = selection.is_empty();
5034if is_entire_line {
5035 start = Point::new(start.row, 0);"
5036 .to_string()
5037 ),
5038 "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
5039 );
5040
5041 cx.set_state(
5042 r#" «ˇ for selection in selections.iter() {
5043 let mut start = selection.start;
5044 let mut end = selection.end;
5045 let is_entire_line = selection.is_empty();
5046 if is_entire_line {
5047 start = Point::new(start.row, 0);»
5048 end = cmp::min(max_point, Point::new(end.row + 1, 0));
5049 }
5050 "#,
5051 );
5052 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
5053 assert_eq!(
5054 cx.read_from_clipboard()
5055 .and_then(|item| item.text().as_deref().map(str::to_string)),
5056 Some(
5057 " for selection in selections.iter() {
5058 let mut start = selection.start;
5059 let mut end = selection.end;
5060 let is_entire_line = selection.is_empty();
5061 if is_entire_line {
5062 start = Point::new(start.row, 0);"
5063 .to_string()
5064 ),
5065 "Regular copying for reverse selection works the same",
5066 );
5067 cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
5068 assert_eq!(
5069 cx.read_from_clipboard()
5070 .and_then(|item| item.text().as_deref().map(str::to_string)),
5071 Some(
5072 "for selection in selections.iter() {
5073let mut start = selection.start;
5074let mut end = selection.end;
5075let is_entire_line = selection.is_empty();
5076if is_entire_line {
5077 start = Point::new(start.row, 0);"
5078 .to_string()
5079 ),
5080 "Copying with stripping for reverse selection works the same"
5081 );
5082
5083 cx.set_state(
5084 r#" for selection «in selections.iter() {
5085 let mut start = selection.start;
5086 let mut end = selection.end;
5087 let is_entire_line = selection.is_empty();
5088 if is_entire_line {
5089 start = Point::new(start.row, 0);ˇ»
5090 end = cmp::min(max_point, Point::new(end.row + 1, 0));
5091 }
5092 "#,
5093 );
5094 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
5095 assert_eq!(
5096 cx.read_from_clipboard()
5097 .and_then(|item| item.text().as_deref().map(str::to_string)),
5098 Some(
5099 "in selections.iter() {
5100 let mut start = selection.start;
5101 let mut end = selection.end;
5102 let is_entire_line = selection.is_empty();
5103 if is_entire_line {
5104 start = Point::new(start.row, 0);"
5105 .to_string()
5106 ),
5107 "When selecting past the indent, the copying works as usual",
5108 );
5109 cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
5110 assert_eq!(
5111 cx.read_from_clipboard()
5112 .and_then(|item| item.text().as_deref().map(str::to_string)),
5113 Some(
5114 "in selections.iter() {
5115 let mut start = selection.start;
5116 let mut end = selection.end;
5117 let is_entire_line = selection.is_empty();
5118 if is_entire_line {
5119 start = Point::new(start.row, 0);"
5120 .to_string()
5121 ),
5122 "When selecting past the indent, nothing is trimmed"
5123 );
5124
5125 cx.set_state(
5126 r#" «for selection in selections.iter() {
5127 let mut start = selection.start;
5128
5129 let mut end = selection.end;
5130 let is_entire_line = selection.is_empty();
5131 if is_entire_line {
5132 start = Point::new(start.row, 0);
5133ˇ» end = cmp::min(max_point, Point::new(end.row + 1, 0));
5134 }
5135 "#,
5136 );
5137 cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
5138 assert_eq!(
5139 cx.read_from_clipboard()
5140 .and_then(|item| item.text().as_deref().map(str::to_string)),
5141 Some(
5142 "for selection in selections.iter() {
5143let mut start = selection.start;
5144
5145let mut end = selection.end;
5146let is_entire_line = selection.is_empty();
5147if is_entire_line {
5148 start = Point::new(start.row, 0);
5149"
5150 .to_string()
5151 ),
5152 "Copying with stripping should ignore empty lines"
5153 );
5154}
5155
5156#[gpui::test]
5157async fn test_paste_multiline(cx: &mut TestAppContext) {
5158 init_test(cx, |_| {});
5159
5160 let mut cx = EditorTestContext::new(cx).await;
5161 cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
5162
5163 // Cut an indented block, without the leading whitespace.
5164 cx.set_state(indoc! {"
5165 const a: B = (
5166 c(),
5167 «d(
5168 e,
5169 f
5170 )ˇ»
5171 );
5172 "});
5173 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
5174 cx.assert_editor_state(indoc! {"
5175 const a: B = (
5176 c(),
5177 ˇ
5178 );
5179 "});
5180
5181 // Paste it at the same position.
5182 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
5183 cx.assert_editor_state(indoc! {"
5184 const a: B = (
5185 c(),
5186 d(
5187 e,
5188 f
5189 )ˇ
5190 );
5191 "});
5192
5193 // Paste it at a line with a lower indent level.
5194 cx.set_state(indoc! {"
5195 ˇ
5196 const a: B = (
5197 c(),
5198 );
5199 "});
5200 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
5201 cx.assert_editor_state(indoc! {"
5202 d(
5203 e,
5204 f
5205 )ˇ
5206 const a: B = (
5207 c(),
5208 );
5209 "});
5210
5211 // Cut an indented block, with the leading whitespace.
5212 cx.set_state(indoc! {"
5213 const a: B = (
5214 c(),
5215 « d(
5216 e,
5217 f
5218 )
5219 ˇ»);
5220 "});
5221 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
5222 cx.assert_editor_state(indoc! {"
5223 const a: B = (
5224 c(),
5225 ˇ);
5226 "});
5227
5228 // Paste it at the same position.
5229 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
5230 cx.assert_editor_state(indoc! {"
5231 const a: B = (
5232 c(),
5233 d(
5234 e,
5235 f
5236 )
5237 ˇ);
5238 "});
5239
5240 // Paste it at a line with a higher indent level.
5241 cx.set_state(indoc! {"
5242 const a: B = (
5243 c(),
5244 d(
5245 e,
5246 fˇ
5247 )
5248 );
5249 "});
5250 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
5251 cx.assert_editor_state(indoc! {"
5252 const a: B = (
5253 c(),
5254 d(
5255 e,
5256 f d(
5257 e,
5258 f
5259 )
5260 ˇ
5261 )
5262 );
5263 "});
5264
5265 // Copy an indented block, starting mid-line
5266 cx.set_state(indoc! {"
5267 const a: B = (
5268 c(),
5269 somethin«g(
5270 e,
5271 f
5272 )ˇ»
5273 );
5274 "});
5275 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
5276
5277 // Paste it on a line with a lower indent level
5278 cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
5279 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
5280 cx.assert_editor_state(indoc! {"
5281 const a: B = (
5282 c(),
5283 something(
5284 e,
5285 f
5286 )
5287 );
5288 g(
5289 e,
5290 f
5291 )ˇ"});
5292}
5293
5294#[gpui::test]
5295async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
5296 init_test(cx, |_| {});
5297
5298 cx.write_to_clipboard(ClipboardItem::new_string(
5299 " d(\n e\n );\n".into(),
5300 ));
5301
5302 let mut cx = EditorTestContext::new(cx).await;
5303 cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
5304
5305 cx.set_state(indoc! {"
5306 fn a() {
5307 b();
5308 if c() {
5309 ˇ
5310 }
5311 }
5312 "});
5313
5314 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
5315 cx.assert_editor_state(indoc! {"
5316 fn a() {
5317 b();
5318 if c() {
5319 d(
5320 e
5321 );
5322 ˇ
5323 }
5324 }
5325 "});
5326
5327 cx.set_state(indoc! {"
5328 fn a() {
5329 b();
5330 ˇ
5331 }
5332 "});
5333
5334 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
5335 cx.assert_editor_state(indoc! {"
5336 fn a() {
5337 b();
5338 d(
5339 e
5340 );
5341 ˇ
5342 }
5343 "});
5344}
5345
5346#[gpui::test]
5347fn test_select_all(cx: &mut TestAppContext) {
5348 init_test(cx, |_| {});
5349
5350 let editor = cx.add_window(|window, cx| {
5351 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
5352 build_editor(buffer, window, cx)
5353 });
5354 _ = editor.update(cx, |editor, window, cx| {
5355 editor.select_all(&SelectAll, window, cx);
5356 assert_eq!(
5357 editor.selections.display_ranges(cx),
5358 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
5359 );
5360 });
5361}
5362
5363#[gpui::test]
5364fn test_select_line(cx: &mut TestAppContext) {
5365 init_test(cx, |_| {});
5366
5367 let editor = cx.add_window(|window, cx| {
5368 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
5369 build_editor(buffer, window, cx)
5370 });
5371 _ = editor.update(cx, |editor, window, cx| {
5372 editor.change_selections(None, window, cx, |s| {
5373 s.select_display_ranges([
5374 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5375 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
5376 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
5377 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
5378 ])
5379 });
5380 editor.select_line(&SelectLine, window, cx);
5381 assert_eq!(
5382 editor.selections.display_ranges(cx),
5383 vec![
5384 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
5385 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
5386 ]
5387 );
5388 });
5389
5390 _ = editor.update(cx, |editor, window, cx| {
5391 editor.select_line(&SelectLine, window, cx);
5392 assert_eq!(
5393 editor.selections.display_ranges(cx),
5394 vec![
5395 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
5396 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
5397 ]
5398 );
5399 });
5400
5401 _ = editor.update(cx, |editor, window, cx| {
5402 editor.select_line(&SelectLine, window, cx);
5403 assert_eq!(
5404 editor.selections.display_ranges(cx),
5405 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
5406 );
5407 });
5408}
5409
5410#[gpui::test]
5411async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
5412 init_test(cx, |_| {});
5413 let mut cx = EditorTestContext::new(cx).await;
5414
5415 #[track_caller]
5416 fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
5417 cx.set_state(initial_state);
5418 cx.update_editor(|e, window, cx| {
5419 e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
5420 });
5421 cx.assert_editor_state(expected_state);
5422 }
5423
5424 // Selection starts and ends at the middle of lines, left-to-right
5425 test(
5426 &mut cx,
5427 "aa\nb«ˇb\ncc\ndd\ne»e\nff",
5428 "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
5429 );
5430 // Same thing, right-to-left
5431 test(
5432 &mut cx,
5433 "aa\nb«b\ncc\ndd\neˇ»e\nff",
5434 "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
5435 );
5436
5437 // Whole buffer, left-to-right, last line *doesn't* end with newline
5438 test(
5439 &mut cx,
5440 "«ˇaa\nbb\ncc\ndd\nee\nff»",
5441 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
5442 );
5443 // Same thing, right-to-left
5444 test(
5445 &mut cx,
5446 "«aa\nbb\ncc\ndd\nee\nffˇ»",
5447 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
5448 );
5449
5450 // Whole buffer, left-to-right, last line ends with newline
5451 test(
5452 &mut cx,
5453 "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
5454 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
5455 );
5456 // Same thing, right-to-left
5457 test(
5458 &mut cx,
5459 "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
5460 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
5461 );
5462
5463 // Starts at the end of a line, ends at the start of another
5464 test(
5465 &mut cx,
5466 "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
5467 "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
5468 );
5469}
5470
5471#[gpui::test]
5472async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
5473 init_test(cx, |_| {});
5474
5475 let editor = cx.add_window(|window, cx| {
5476 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
5477 build_editor(buffer, window, cx)
5478 });
5479
5480 // setup
5481 _ = editor.update(cx, |editor, window, cx| {
5482 editor.fold_creases(
5483 vec![
5484 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
5485 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
5486 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
5487 ],
5488 true,
5489 window,
5490 cx,
5491 );
5492 assert_eq!(
5493 editor.display_text(cx),
5494 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
5495 );
5496 });
5497
5498 _ = editor.update(cx, |editor, window, cx| {
5499 editor.change_selections(None, window, cx, |s| {
5500 s.select_display_ranges([
5501 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5502 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
5503 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
5504 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
5505 ])
5506 });
5507 editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
5508 assert_eq!(
5509 editor.display_text(cx),
5510 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
5511 );
5512 });
5513 EditorTestContext::for_editor(editor, cx)
5514 .await
5515 .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
5516
5517 _ = editor.update(cx, |editor, window, cx| {
5518 editor.change_selections(None, window, cx, |s| {
5519 s.select_display_ranges([
5520 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
5521 ])
5522 });
5523 editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
5524 assert_eq!(
5525 editor.display_text(cx),
5526 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
5527 );
5528 assert_eq!(
5529 editor.selections.display_ranges(cx),
5530 [
5531 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
5532 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
5533 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
5534 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
5535 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
5536 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
5537 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
5538 ]
5539 );
5540 });
5541 EditorTestContext::for_editor(editor, cx)
5542 .await
5543 .assert_editor_state(
5544 "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
5545 );
5546}
5547
5548#[gpui::test]
5549async fn test_add_selection_above_below(cx: &mut TestAppContext) {
5550 init_test(cx, |_| {});
5551
5552 let mut cx = EditorTestContext::new(cx).await;
5553
5554 cx.set_state(indoc!(
5555 r#"abc
5556 defˇghi
5557
5558 jk
5559 nlmo
5560 "#
5561 ));
5562
5563 cx.update_editor(|editor, window, cx| {
5564 editor.add_selection_above(&Default::default(), window, cx);
5565 });
5566
5567 cx.assert_editor_state(indoc!(
5568 r#"abcˇ
5569 defˇghi
5570
5571 jk
5572 nlmo
5573 "#
5574 ));
5575
5576 cx.update_editor(|editor, window, cx| {
5577 editor.add_selection_above(&Default::default(), window, cx);
5578 });
5579
5580 cx.assert_editor_state(indoc!(
5581 r#"abcˇ
5582 defˇghi
5583
5584 jk
5585 nlmo
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ˇghi
5596
5597 jk
5598 nlmo
5599 "#
5600 ));
5601
5602 cx.update_editor(|editor, window, cx| {
5603 editor.undo_selection(&Default::default(), window, cx);
5604 });
5605
5606 cx.assert_editor_state(indoc!(
5607 r#"abcˇ
5608 defˇghi
5609
5610 jk
5611 nlmo
5612 "#
5613 ));
5614
5615 cx.update_editor(|editor, window, cx| {
5616 editor.redo_selection(&Default::default(), window, cx);
5617 });
5618
5619 cx.assert_editor_state(indoc!(
5620 r#"abc
5621 defˇghi
5622
5623 jk
5624 nlmo
5625 "#
5626 ));
5627
5628 cx.update_editor(|editor, window, cx| {
5629 editor.add_selection_below(&Default::default(), window, cx);
5630 });
5631
5632 cx.assert_editor_state(indoc!(
5633 r#"abc
5634 defˇghi
5635
5636 jk
5637 nlmˇo
5638 "#
5639 ));
5640
5641 cx.update_editor(|editor, window, cx| {
5642 editor.add_selection_below(&Default::default(), window, cx);
5643 });
5644
5645 cx.assert_editor_state(indoc!(
5646 r#"abc
5647 defˇghi
5648
5649 jk
5650 nlmˇo
5651 "#
5652 ));
5653
5654 // change selections
5655 cx.set_state(indoc!(
5656 r#"abc
5657 def«ˇg»hi
5658
5659 jk
5660 nlmo
5661 "#
5662 ));
5663
5664 cx.update_editor(|editor, window, cx| {
5665 editor.add_selection_below(&Default::default(), window, cx);
5666 });
5667
5668 cx.assert_editor_state(indoc!(
5669 r#"abc
5670 def«ˇg»hi
5671
5672 jk
5673 nlm«ˇo»
5674 "#
5675 ));
5676
5677 cx.update_editor(|editor, window, cx| {
5678 editor.add_selection_below(&Default::default(), window, cx);
5679 });
5680
5681 cx.assert_editor_state(indoc!(
5682 r#"abc
5683 def«ˇg»hi
5684
5685 jk
5686 nlm«ˇo»
5687 "#
5688 ));
5689
5690 cx.update_editor(|editor, window, cx| {
5691 editor.add_selection_above(&Default::default(), window, cx);
5692 });
5693
5694 cx.assert_editor_state(indoc!(
5695 r#"abc
5696 def«ˇg»hi
5697
5698 jk
5699 nlmo
5700 "#
5701 ));
5702
5703 cx.update_editor(|editor, window, cx| {
5704 editor.add_selection_above(&Default::default(), window, cx);
5705 });
5706
5707 cx.assert_editor_state(indoc!(
5708 r#"abc
5709 def«ˇg»hi
5710
5711 jk
5712 nlmo
5713 "#
5714 ));
5715
5716 // Change selections again
5717 cx.set_state(indoc!(
5718 r#"a«bc
5719 defgˇ»hi
5720
5721 jk
5722 nlmo
5723 "#
5724 ));
5725
5726 cx.update_editor(|editor, window, cx| {
5727 editor.add_selection_below(&Default::default(), window, cx);
5728 });
5729
5730 cx.assert_editor_state(indoc!(
5731 r#"a«bcˇ»
5732 d«efgˇ»hi
5733
5734 j«kˇ»
5735 nlmo
5736 "#
5737 ));
5738
5739 cx.update_editor(|editor, window, cx| {
5740 editor.add_selection_below(&Default::default(), window, cx);
5741 });
5742 cx.assert_editor_state(indoc!(
5743 r#"a«bcˇ»
5744 d«efgˇ»hi
5745
5746 j«kˇ»
5747 n«lmoˇ»
5748 "#
5749 ));
5750 cx.update_editor(|editor, window, cx| {
5751 editor.add_selection_above(&Default::default(), window, cx);
5752 });
5753
5754 cx.assert_editor_state(indoc!(
5755 r#"a«bcˇ»
5756 d«efgˇ»hi
5757
5758 j«kˇ»
5759 nlmo
5760 "#
5761 ));
5762
5763 // Change selections again
5764 cx.set_state(indoc!(
5765 r#"abc
5766 d«ˇefghi
5767
5768 jk
5769 nlm»o
5770 "#
5771 ));
5772
5773 cx.update_editor(|editor, window, cx| {
5774 editor.add_selection_above(&Default::default(), window, cx);
5775 });
5776
5777 cx.assert_editor_state(indoc!(
5778 r#"a«ˇbc»
5779 d«ˇef»ghi
5780
5781 j«ˇk»
5782 n«ˇlm»o
5783 "#
5784 ));
5785
5786 cx.update_editor(|editor, window, cx| {
5787 editor.add_selection_below(&Default::default(), window, cx);
5788 });
5789
5790 cx.assert_editor_state(indoc!(
5791 r#"abc
5792 d«ˇef»ghi
5793
5794 j«ˇk»
5795 n«ˇlm»o
5796 "#
5797 ));
5798}
5799
5800#[gpui::test]
5801async fn test_select_next(cx: &mut TestAppContext) {
5802 init_test(cx, |_| {});
5803
5804 let mut cx = EditorTestContext::new(cx).await;
5805 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5806
5807 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5808 .unwrap();
5809 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5810
5811 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5812 .unwrap();
5813 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5814
5815 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5816 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5817
5818 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5819 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5820
5821 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5822 .unwrap();
5823 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5824
5825 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5826 .unwrap();
5827 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5828
5829 // Test selection direction should be preserved
5830 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
5831
5832 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5833 .unwrap();
5834 cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
5835}
5836
5837#[gpui::test]
5838async fn test_select_all_matches(cx: &mut TestAppContext) {
5839 init_test(cx, |_| {});
5840
5841 let mut cx = EditorTestContext::new(cx).await;
5842
5843 // Test caret-only selections
5844 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5845 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5846 .unwrap();
5847 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5848
5849 // Test left-to-right selections
5850 cx.set_state("abc\n«abcˇ»\nabc");
5851 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5852 .unwrap();
5853 cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
5854
5855 // Test right-to-left selections
5856 cx.set_state("abc\n«ˇabc»\nabc");
5857 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5858 .unwrap();
5859 cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
5860
5861 // Test selecting whitespace with caret selection
5862 cx.set_state("abc\nˇ abc\nabc");
5863 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5864 .unwrap();
5865 cx.assert_editor_state("abc\n« ˇ»abc\nabc");
5866
5867 // Test selecting whitespace with left-to-right selection
5868 cx.set_state("abc\n«ˇ »abc\nabc");
5869 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5870 .unwrap();
5871 cx.assert_editor_state("abc\n«ˇ »abc\nabc");
5872
5873 // Test no matches with right-to-left selection
5874 cx.set_state("abc\n« ˇ»abc\nabc");
5875 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5876 .unwrap();
5877 cx.assert_editor_state("abc\n« ˇ»abc\nabc");
5878}
5879
5880#[gpui::test]
5881async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
5882 init_test(cx, |_| {});
5883
5884 let mut cx = EditorTestContext::new(cx).await;
5885
5886 let large_body_1 = "\nd".repeat(200);
5887 let large_body_2 = "\ne".repeat(200);
5888
5889 cx.set_state(&format!(
5890 "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
5891 ));
5892 let initial_scroll_position = cx.update_editor(|editor, _, cx| {
5893 let scroll_position = editor.scroll_position(cx);
5894 assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
5895 scroll_position
5896 });
5897
5898 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5899 .unwrap();
5900 cx.assert_editor_state(&format!(
5901 "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
5902 ));
5903 let scroll_position_after_selection =
5904 cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
5905 assert_eq!(
5906 initial_scroll_position, scroll_position_after_selection,
5907 "Scroll position should not change after selecting all matches"
5908 );
5909}
5910
5911#[gpui::test]
5912async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
5913 init_test(cx, |_| {});
5914
5915 let mut cx = EditorLspTestContext::new_rust(
5916 lsp::ServerCapabilities {
5917 document_formatting_provider: Some(lsp::OneOf::Left(true)),
5918 ..Default::default()
5919 },
5920 cx,
5921 )
5922 .await;
5923
5924 cx.set_state(indoc! {"
5925 line 1
5926 line 2
5927 linˇe 3
5928 line 4
5929 line 5
5930 "});
5931
5932 // Make an edit
5933 cx.update_editor(|editor, window, cx| {
5934 editor.handle_input("X", window, cx);
5935 });
5936
5937 // Move cursor to a different position
5938 cx.update_editor(|editor, window, cx| {
5939 editor.change_selections(None, window, cx, |s| {
5940 s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
5941 });
5942 });
5943
5944 cx.assert_editor_state(indoc! {"
5945 line 1
5946 line 2
5947 linXe 3
5948 line 4
5949 liˇne 5
5950 "});
5951
5952 cx.lsp
5953 .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
5954 Ok(Some(vec![lsp::TextEdit::new(
5955 lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
5956 "PREFIX ".to_string(),
5957 )]))
5958 });
5959
5960 cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
5961 .unwrap()
5962 .await
5963 .unwrap();
5964
5965 cx.assert_editor_state(indoc! {"
5966 PREFIX line 1
5967 line 2
5968 linXe 3
5969 line 4
5970 liˇne 5
5971 "});
5972
5973 // Undo formatting
5974 cx.update_editor(|editor, window, cx| {
5975 editor.undo(&Default::default(), window, cx);
5976 });
5977
5978 // Verify cursor moved back to position after edit
5979 cx.assert_editor_state(indoc! {"
5980 line 1
5981 line 2
5982 linXˇe 3
5983 line 4
5984 line 5
5985 "});
5986}
5987
5988#[gpui::test]
5989async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
5990 init_test(cx, |_| {});
5991
5992 let mut cx = EditorTestContext::new(cx).await;
5993 cx.set_state(
5994 r#"let foo = 2;
5995lˇet foo = 2;
5996let fooˇ = 2;
5997let foo = 2;
5998let foo = ˇ2;"#,
5999 );
6000
6001 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
6002 .unwrap();
6003 cx.assert_editor_state(
6004 r#"let foo = 2;
6005«letˇ» foo = 2;
6006let «fooˇ» = 2;
6007let foo = 2;
6008let foo = «2ˇ»;"#,
6009 );
6010
6011 // noop for multiple selections with different contents
6012 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
6013 .unwrap();
6014 cx.assert_editor_state(
6015 r#"let foo = 2;
6016«letˇ» foo = 2;
6017let «fooˇ» = 2;
6018let foo = 2;
6019let foo = «2ˇ»;"#,
6020 );
6021
6022 // Test last selection direction should be preserved
6023 cx.set_state(
6024 r#"let foo = 2;
6025let foo = 2;
6026let «fooˇ» = 2;
6027let «ˇfoo» = 2;
6028let foo = 2;"#,
6029 );
6030
6031 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
6032 .unwrap();
6033 cx.assert_editor_state(
6034 r#"let foo = 2;
6035let foo = 2;
6036let «fooˇ» = 2;
6037let «ˇfoo» = 2;
6038let «ˇfoo» = 2;"#,
6039 );
6040}
6041
6042#[gpui::test]
6043async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
6044 init_test(cx, |_| {});
6045
6046 let mut cx =
6047 EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
6048
6049 cx.assert_editor_state(indoc! {"
6050 ˇbbb
6051 ccc
6052
6053 bbb
6054 ccc
6055 "});
6056 cx.dispatch_action(SelectPrevious::default());
6057 cx.assert_editor_state(indoc! {"
6058 «bbbˇ»
6059 ccc
6060
6061 bbb
6062 ccc
6063 "});
6064 cx.dispatch_action(SelectPrevious::default());
6065 cx.assert_editor_state(indoc! {"
6066 «bbbˇ»
6067 ccc
6068
6069 «bbbˇ»
6070 ccc
6071 "});
6072}
6073
6074#[gpui::test]
6075async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
6076 init_test(cx, |_| {});
6077
6078 let mut cx = EditorTestContext::new(cx).await;
6079 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
6080
6081 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6082 .unwrap();
6083 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
6084
6085 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6086 .unwrap();
6087 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
6088
6089 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
6090 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
6091
6092 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
6093 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
6094
6095 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6096 .unwrap();
6097 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
6098
6099 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6100 .unwrap();
6101 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
6102
6103 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6104 .unwrap();
6105 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
6106}
6107
6108#[gpui::test]
6109async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
6110 init_test(cx, |_| {});
6111
6112 let mut cx = EditorTestContext::new(cx).await;
6113 cx.set_state("aˇ");
6114
6115 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6116 .unwrap();
6117 cx.assert_editor_state("«aˇ»");
6118 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6119 .unwrap();
6120 cx.assert_editor_state("«aˇ»");
6121}
6122
6123#[gpui::test]
6124async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
6125 init_test(cx, |_| {});
6126
6127 let mut cx = EditorTestContext::new(cx).await;
6128 cx.set_state(
6129 r#"let foo = 2;
6130lˇet foo = 2;
6131let fooˇ = 2;
6132let foo = 2;
6133let foo = ˇ2;"#,
6134 );
6135
6136 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6137 .unwrap();
6138 cx.assert_editor_state(
6139 r#"let foo = 2;
6140«letˇ» foo = 2;
6141let «fooˇ» = 2;
6142let foo = 2;
6143let foo = «2ˇ»;"#,
6144 );
6145
6146 // noop for multiple selections with different contents
6147 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6148 .unwrap();
6149 cx.assert_editor_state(
6150 r#"let foo = 2;
6151«letˇ» foo = 2;
6152let «fooˇ» = 2;
6153let foo = 2;
6154let foo = «2ˇ»;"#,
6155 );
6156}
6157
6158#[gpui::test]
6159async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
6160 init_test(cx, |_| {});
6161
6162 let mut cx = EditorTestContext::new(cx).await;
6163 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
6164
6165 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6166 .unwrap();
6167 // selection direction is preserved
6168 cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
6169
6170 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6171 .unwrap();
6172 cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
6173
6174 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
6175 cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
6176
6177 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
6178 cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
6179
6180 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6181 .unwrap();
6182 cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
6183
6184 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6185 .unwrap();
6186 cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
6187}
6188
6189#[gpui::test]
6190async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
6191 init_test(cx, |_| {});
6192
6193 let language = Arc::new(Language::new(
6194 LanguageConfig::default(),
6195 Some(tree_sitter_rust::LANGUAGE.into()),
6196 ));
6197
6198 let text = r#"
6199 use mod1::mod2::{mod3, mod4};
6200
6201 fn fn_1(param1: bool, param2: &str) {
6202 let var1 = "text";
6203 }
6204 "#
6205 .unindent();
6206
6207 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6208 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6209 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6210
6211 editor
6212 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6213 .await;
6214
6215 editor.update_in(cx, |editor, window, cx| {
6216 editor.change_selections(None, window, cx, |s| {
6217 s.select_display_ranges([
6218 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
6219 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
6220 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
6221 ]);
6222 });
6223 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6224 });
6225 editor.update(cx, |editor, cx| {
6226 assert_text_with_selections(
6227 editor,
6228 indoc! {r#"
6229 use mod1::mod2::{mod3, «mod4ˇ»};
6230
6231 fn fn_1«ˇ(param1: bool, param2: &str)» {
6232 let var1 = "«ˇtext»";
6233 }
6234 "#},
6235 cx,
6236 );
6237 });
6238
6239 editor.update_in(cx, |editor, window, cx| {
6240 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6241 });
6242 editor.update(cx, |editor, cx| {
6243 assert_text_with_selections(
6244 editor,
6245 indoc! {r#"
6246 use mod1::mod2::«{mod3, mod4}ˇ»;
6247
6248 «ˇfn fn_1(param1: bool, param2: &str) {
6249 let var1 = "text";
6250 }»
6251 "#},
6252 cx,
6253 );
6254 });
6255
6256 editor.update_in(cx, |editor, window, cx| {
6257 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6258 });
6259 assert_eq!(
6260 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
6261 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
6262 );
6263
6264 // Trying to expand the selected syntax node one more time has no effect.
6265 editor.update_in(cx, |editor, window, cx| {
6266 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6267 });
6268 assert_eq!(
6269 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
6270 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
6271 );
6272
6273 editor.update_in(cx, |editor, window, cx| {
6274 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
6275 });
6276 editor.update(cx, |editor, cx| {
6277 assert_text_with_selections(
6278 editor,
6279 indoc! {r#"
6280 use mod1::mod2::«{mod3, mod4}ˇ»;
6281
6282 «ˇfn fn_1(param1: bool, param2: &str) {
6283 let var1 = "text";
6284 }»
6285 "#},
6286 cx,
6287 );
6288 });
6289
6290 editor.update_in(cx, |editor, window, cx| {
6291 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
6292 });
6293 editor.update(cx, |editor, cx| {
6294 assert_text_with_selections(
6295 editor,
6296 indoc! {r#"
6297 use mod1::mod2::{mod3, «mod4ˇ»};
6298
6299 fn fn_1«ˇ(param1: bool, param2: &str)» {
6300 let var1 = "«ˇtext»";
6301 }
6302 "#},
6303 cx,
6304 );
6305 });
6306
6307 editor.update_in(cx, |editor, window, cx| {
6308 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
6309 });
6310 editor.update(cx, |editor, cx| {
6311 assert_text_with_selections(
6312 editor,
6313 indoc! {r#"
6314 use mod1::mod2::{mod3, mo«ˇ»d4};
6315
6316 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
6317 let var1 = "te«ˇ»xt";
6318 }
6319 "#},
6320 cx,
6321 );
6322 });
6323
6324 // Trying to shrink the selected syntax node one more time has no effect.
6325 editor.update_in(cx, |editor, window, cx| {
6326 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
6327 });
6328 editor.update_in(cx, |editor, _, cx| {
6329 assert_text_with_selections(
6330 editor,
6331 indoc! {r#"
6332 use mod1::mod2::{mod3, mo«ˇ»d4};
6333
6334 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
6335 let var1 = "te«ˇ»xt";
6336 }
6337 "#},
6338 cx,
6339 );
6340 });
6341
6342 // Ensure that we keep expanding the selection if the larger selection starts or ends within
6343 // a fold.
6344 editor.update_in(cx, |editor, window, cx| {
6345 editor.fold_creases(
6346 vec![
6347 Crease::simple(
6348 Point::new(0, 21)..Point::new(0, 24),
6349 FoldPlaceholder::test(),
6350 ),
6351 Crease::simple(
6352 Point::new(3, 20)..Point::new(3, 22),
6353 FoldPlaceholder::test(),
6354 ),
6355 ],
6356 true,
6357 window,
6358 cx,
6359 );
6360 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6361 });
6362 editor.update(cx, |editor, cx| {
6363 assert_text_with_selections(
6364 editor,
6365 indoc! {r#"
6366 use mod1::mod2::«{mod3, mod4}ˇ»;
6367
6368 fn fn_1«ˇ(param1: bool, param2: &str)» {
6369 let var1 = "«ˇtext»";
6370 }
6371 "#},
6372 cx,
6373 );
6374 });
6375}
6376
6377#[gpui::test]
6378async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
6379 init_test(cx, |_| {});
6380
6381 let language = Arc::new(Language::new(
6382 LanguageConfig::default(),
6383 Some(tree_sitter_rust::LANGUAGE.into()),
6384 ));
6385
6386 let text = r#"
6387 use mod1::mod2::{mod3, mod4};
6388
6389 fn fn_1(param1: bool, param2: &str) {
6390 let var1 = "hello world";
6391 }
6392 "#
6393 .unindent();
6394
6395 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6396 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6397 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6398
6399 editor
6400 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6401 .await;
6402
6403 // Test 1: Cursor on a letter of a string word
6404 editor.update_in(cx, |editor, window, cx| {
6405 editor.change_selections(None, window, cx, |s| {
6406 s.select_display_ranges([
6407 DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
6408 ]);
6409 });
6410 });
6411 editor.update_in(cx, |editor, window, cx| {
6412 assert_text_with_selections(
6413 editor,
6414 indoc! {r#"
6415 use mod1::mod2::{mod3, mod4};
6416
6417 fn fn_1(param1: bool, param2: &str) {
6418 let var1 = "hˇello world";
6419 }
6420 "#},
6421 cx,
6422 );
6423 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6424 assert_text_with_selections(
6425 editor,
6426 indoc! {r#"
6427 use mod1::mod2::{mod3, mod4};
6428
6429 fn fn_1(param1: bool, param2: &str) {
6430 let var1 = "«ˇhello» world";
6431 }
6432 "#},
6433 cx,
6434 );
6435 });
6436
6437 // Test 2: Partial selection within a word
6438 editor.update_in(cx, |editor, window, cx| {
6439 editor.change_selections(None, window, cx, |s| {
6440 s.select_display_ranges([
6441 DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
6442 ]);
6443 });
6444 });
6445 editor.update_in(cx, |editor, window, cx| {
6446 assert_text_with_selections(
6447 editor,
6448 indoc! {r#"
6449 use mod1::mod2::{mod3, mod4};
6450
6451 fn fn_1(param1: bool, param2: &str) {
6452 let var1 = "h«elˇ»lo world";
6453 }
6454 "#},
6455 cx,
6456 );
6457 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6458 assert_text_with_selections(
6459 editor,
6460 indoc! {r#"
6461 use mod1::mod2::{mod3, mod4};
6462
6463 fn fn_1(param1: bool, param2: &str) {
6464 let var1 = "«ˇhello» world";
6465 }
6466 "#},
6467 cx,
6468 );
6469 });
6470
6471 // Test 3: Complete word already selected
6472 editor.update_in(cx, |editor, window, cx| {
6473 editor.change_selections(None, window, cx, |s| {
6474 s.select_display_ranges([
6475 DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
6476 ]);
6477 });
6478 });
6479 editor.update_in(cx, |editor, window, cx| {
6480 assert_text_with_selections(
6481 editor,
6482 indoc! {r#"
6483 use mod1::mod2::{mod3, mod4};
6484
6485 fn fn_1(param1: bool, param2: &str) {
6486 let var1 = "«helloˇ» world";
6487 }
6488 "#},
6489 cx,
6490 );
6491 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6492 assert_text_with_selections(
6493 editor,
6494 indoc! {r#"
6495 use mod1::mod2::{mod3, mod4};
6496
6497 fn fn_1(param1: bool, param2: &str) {
6498 let var1 = "«hello worldˇ»";
6499 }
6500 "#},
6501 cx,
6502 );
6503 });
6504
6505 // Test 4: Selection spanning across words
6506 editor.update_in(cx, |editor, window, cx| {
6507 editor.change_selections(None, window, cx, |s| {
6508 s.select_display_ranges([
6509 DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
6510 ]);
6511 });
6512 });
6513 editor.update_in(cx, |editor, window, cx| {
6514 assert_text_with_selections(
6515 editor,
6516 indoc! {r#"
6517 use mod1::mod2::{mod3, mod4};
6518
6519 fn fn_1(param1: bool, param2: &str) {
6520 let var1 = "hel«lo woˇ»rld";
6521 }
6522 "#},
6523 cx,
6524 );
6525 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6526 assert_text_with_selections(
6527 editor,
6528 indoc! {r#"
6529 use mod1::mod2::{mod3, mod4};
6530
6531 fn fn_1(param1: bool, param2: &str) {
6532 let var1 = "«ˇhello world»";
6533 }
6534 "#},
6535 cx,
6536 );
6537 });
6538
6539 // Test 5: Expansion beyond string
6540 editor.update_in(cx, |editor, window, cx| {
6541 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6542 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6543 assert_text_with_selections(
6544 editor,
6545 indoc! {r#"
6546 use mod1::mod2::{mod3, mod4};
6547
6548 fn fn_1(param1: bool, param2: &str) {
6549 «ˇlet var1 = "hello world";»
6550 }
6551 "#},
6552 cx,
6553 );
6554 });
6555}
6556
6557#[gpui::test]
6558async fn test_fold_function_bodies(cx: &mut TestAppContext) {
6559 init_test(cx, |_| {});
6560
6561 let base_text = r#"
6562 impl A {
6563 // this is an uncommitted comment
6564
6565 fn b() {
6566 c();
6567 }
6568
6569 // this is another uncommitted comment
6570
6571 fn d() {
6572 // e
6573 // f
6574 }
6575 }
6576
6577 fn g() {
6578 // h
6579 }
6580 "#
6581 .unindent();
6582
6583 let text = r#"
6584 ˇimpl A {
6585
6586 fn b() {
6587 c();
6588 }
6589
6590 fn d() {
6591 // e
6592 // f
6593 }
6594 }
6595
6596 fn g() {
6597 // h
6598 }
6599 "#
6600 .unindent();
6601
6602 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
6603 cx.set_state(&text);
6604 cx.set_head_text(&base_text);
6605 cx.update_editor(|editor, window, cx| {
6606 editor.expand_all_diff_hunks(&Default::default(), window, cx);
6607 });
6608
6609 cx.assert_state_with_diff(
6610 "
6611 ˇimpl A {
6612 - // this is an uncommitted comment
6613
6614 fn b() {
6615 c();
6616 }
6617
6618 - // this is another uncommitted comment
6619 -
6620 fn d() {
6621 // e
6622 // f
6623 }
6624 }
6625
6626 fn g() {
6627 // h
6628 }
6629 "
6630 .unindent(),
6631 );
6632
6633 let expected_display_text = "
6634 impl A {
6635 // this is an uncommitted comment
6636
6637 fn b() {
6638 ⋯
6639 }
6640
6641 // this is another uncommitted comment
6642
6643 fn d() {
6644 ⋯
6645 }
6646 }
6647
6648 fn g() {
6649 ⋯
6650 }
6651 "
6652 .unindent();
6653
6654 cx.update_editor(|editor, window, cx| {
6655 editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
6656 assert_eq!(editor.display_text(cx), expected_display_text);
6657 });
6658}
6659
6660#[gpui::test]
6661async fn test_autoindent(cx: &mut TestAppContext) {
6662 init_test(cx, |_| {});
6663
6664 let language = Arc::new(
6665 Language::new(
6666 LanguageConfig {
6667 brackets: BracketPairConfig {
6668 pairs: vec![
6669 BracketPair {
6670 start: "{".to_string(),
6671 end: "}".to_string(),
6672 close: false,
6673 surround: false,
6674 newline: true,
6675 },
6676 BracketPair {
6677 start: "(".to_string(),
6678 end: ")".to_string(),
6679 close: false,
6680 surround: false,
6681 newline: true,
6682 },
6683 ],
6684 ..Default::default()
6685 },
6686 ..Default::default()
6687 },
6688 Some(tree_sitter_rust::LANGUAGE.into()),
6689 )
6690 .with_indents_query(
6691 r#"
6692 (_ "(" ")" @end) @indent
6693 (_ "{" "}" @end) @indent
6694 "#,
6695 )
6696 .unwrap(),
6697 );
6698
6699 let text = "fn a() {}";
6700
6701 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6702 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6703 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6704 editor
6705 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6706 .await;
6707
6708 editor.update_in(cx, |editor, window, cx| {
6709 editor.change_selections(None, window, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
6710 editor.newline(&Newline, window, cx);
6711 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
6712 assert_eq!(
6713 editor.selections.ranges(cx),
6714 &[
6715 Point::new(1, 4)..Point::new(1, 4),
6716 Point::new(3, 4)..Point::new(3, 4),
6717 Point::new(5, 0)..Point::new(5, 0)
6718 ]
6719 );
6720 });
6721}
6722
6723#[gpui::test]
6724async fn test_autoindent_selections(cx: &mut TestAppContext) {
6725 init_test(cx, |_| {});
6726
6727 {
6728 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
6729 cx.set_state(indoc! {"
6730 impl A {
6731
6732 fn b() {}
6733
6734 «fn c() {
6735
6736 }ˇ»
6737 }
6738 "});
6739
6740 cx.update_editor(|editor, window, cx| {
6741 editor.autoindent(&Default::default(), window, cx);
6742 });
6743
6744 cx.assert_editor_state(indoc! {"
6745 impl A {
6746
6747 fn b() {}
6748
6749 «fn c() {
6750
6751 }ˇ»
6752 }
6753 "});
6754 }
6755
6756 {
6757 let mut cx = EditorTestContext::new_multibuffer(
6758 cx,
6759 [indoc! { "
6760 impl A {
6761 «
6762 // a
6763 fn b(){}
6764 »
6765 «
6766 }
6767 fn c(){}
6768 »
6769 "}],
6770 );
6771
6772 let buffer = cx.update_editor(|editor, _, cx| {
6773 let buffer = editor.buffer().update(cx, |buffer, _| {
6774 buffer.all_buffers().iter().next().unwrap().clone()
6775 });
6776 buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
6777 buffer
6778 });
6779
6780 cx.run_until_parked();
6781 cx.update_editor(|editor, window, cx| {
6782 editor.select_all(&Default::default(), window, cx);
6783 editor.autoindent(&Default::default(), window, cx)
6784 });
6785 cx.run_until_parked();
6786
6787 cx.update(|_, cx| {
6788 assert_eq!(
6789 buffer.read(cx).text(),
6790 indoc! { "
6791 impl A {
6792
6793 // a
6794 fn b(){}
6795
6796
6797 }
6798 fn c(){}
6799
6800 " }
6801 )
6802 });
6803 }
6804}
6805
6806#[gpui::test]
6807async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
6808 init_test(cx, |_| {});
6809
6810 let mut cx = EditorTestContext::new(cx).await;
6811
6812 let language = Arc::new(Language::new(
6813 LanguageConfig {
6814 brackets: BracketPairConfig {
6815 pairs: vec![
6816 BracketPair {
6817 start: "{".to_string(),
6818 end: "}".to_string(),
6819 close: true,
6820 surround: true,
6821 newline: true,
6822 },
6823 BracketPair {
6824 start: "(".to_string(),
6825 end: ")".to_string(),
6826 close: true,
6827 surround: true,
6828 newline: true,
6829 },
6830 BracketPair {
6831 start: "/*".to_string(),
6832 end: " */".to_string(),
6833 close: true,
6834 surround: true,
6835 newline: true,
6836 },
6837 BracketPair {
6838 start: "[".to_string(),
6839 end: "]".to_string(),
6840 close: false,
6841 surround: false,
6842 newline: true,
6843 },
6844 BracketPair {
6845 start: "\"".to_string(),
6846 end: "\"".to_string(),
6847 close: true,
6848 surround: true,
6849 newline: false,
6850 },
6851 BracketPair {
6852 start: "<".to_string(),
6853 end: ">".to_string(),
6854 close: false,
6855 surround: true,
6856 newline: true,
6857 },
6858 ],
6859 ..Default::default()
6860 },
6861 autoclose_before: "})]".to_string(),
6862 ..Default::default()
6863 },
6864 Some(tree_sitter_rust::LANGUAGE.into()),
6865 ));
6866
6867 cx.language_registry().add(language.clone());
6868 cx.update_buffer(|buffer, cx| {
6869 buffer.set_language(Some(language), cx);
6870 });
6871
6872 cx.set_state(
6873 &r#"
6874 🏀ˇ
6875 εˇ
6876 ❤️ˇ
6877 "#
6878 .unindent(),
6879 );
6880
6881 // autoclose multiple nested brackets at multiple cursors
6882 cx.update_editor(|editor, window, cx| {
6883 editor.handle_input("{", window, cx);
6884 editor.handle_input("{", window, cx);
6885 editor.handle_input("{", window, cx);
6886 });
6887 cx.assert_editor_state(
6888 &"
6889 🏀{{{ˇ}}}
6890 ε{{{ˇ}}}
6891 ❤️{{{ˇ}}}
6892 "
6893 .unindent(),
6894 );
6895
6896 // insert a different closing bracket
6897 cx.update_editor(|editor, window, cx| {
6898 editor.handle_input(")", window, cx);
6899 });
6900 cx.assert_editor_state(
6901 &"
6902 🏀{{{)ˇ}}}
6903 ε{{{)ˇ}}}
6904 ❤️{{{)ˇ}}}
6905 "
6906 .unindent(),
6907 );
6908
6909 // skip over the auto-closed brackets when typing a closing bracket
6910 cx.update_editor(|editor, window, cx| {
6911 editor.move_right(&MoveRight, window, cx);
6912 editor.handle_input("}", window, cx);
6913 editor.handle_input("}", window, cx);
6914 editor.handle_input("}", window, cx);
6915 });
6916 cx.assert_editor_state(
6917 &"
6918 🏀{{{)}}}}ˇ
6919 ε{{{)}}}}ˇ
6920 ❤️{{{)}}}}ˇ
6921 "
6922 .unindent(),
6923 );
6924
6925 // autoclose multi-character pairs
6926 cx.set_state(
6927 &"
6928 ˇ
6929 ˇ
6930 "
6931 .unindent(),
6932 );
6933 cx.update_editor(|editor, window, cx| {
6934 editor.handle_input("/", window, cx);
6935 editor.handle_input("*", window, cx);
6936 });
6937 cx.assert_editor_state(
6938 &"
6939 /*ˇ */
6940 /*ˇ */
6941 "
6942 .unindent(),
6943 );
6944
6945 // one cursor autocloses a multi-character pair, one cursor
6946 // does not autoclose.
6947 cx.set_state(
6948 &"
6949 /ˇ
6950 ˇ
6951 "
6952 .unindent(),
6953 );
6954 cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
6955 cx.assert_editor_state(
6956 &"
6957 /*ˇ */
6958 *ˇ
6959 "
6960 .unindent(),
6961 );
6962
6963 // Don't autoclose if the next character isn't whitespace and isn't
6964 // listed in the language's "autoclose_before" section.
6965 cx.set_state("ˇa b");
6966 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
6967 cx.assert_editor_state("{ˇa b");
6968
6969 // Don't autoclose if `close` is false for the bracket pair
6970 cx.set_state("ˇ");
6971 cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
6972 cx.assert_editor_state("[ˇ");
6973
6974 // Surround with brackets if text is selected
6975 cx.set_state("«aˇ» b");
6976 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
6977 cx.assert_editor_state("{«aˇ»} b");
6978
6979 // Autoclose when not immediately after a word character
6980 cx.set_state("a ˇ");
6981 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6982 cx.assert_editor_state("a \"ˇ\"");
6983
6984 // Autoclose pair where the start and end characters are the same
6985 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6986 cx.assert_editor_state("a \"\"ˇ");
6987
6988 // Don't autoclose when immediately after a word character
6989 cx.set_state("aˇ");
6990 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6991 cx.assert_editor_state("a\"ˇ");
6992
6993 // Do autoclose when after a non-word character
6994 cx.set_state("{ˇ");
6995 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6996 cx.assert_editor_state("{\"ˇ\"");
6997
6998 // Non identical pairs autoclose regardless of preceding character
6999 cx.set_state("aˇ");
7000 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
7001 cx.assert_editor_state("a{ˇ}");
7002
7003 // Don't autoclose pair if autoclose is disabled
7004 cx.set_state("ˇ");
7005 cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
7006 cx.assert_editor_state("<ˇ");
7007
7008 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
7009 cx.set_state("«aˇ» b");
7010 cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
7011 cx.assert_editor_state("<«aˇ»> b");
7012}
7013
7014#[gpui::test]
7015async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
7016 init_test(cx, |settings| {
7017 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
7018 });
7019
7020 let mut cx = EditorTestContext::new(cx).await;
7021
7022 let language = Arc::new(Language::new(
7023 LanguageConfig {
7024 brackets: BracketPairConfig {
7025 pairs: vec![
7026 BracketPair {
7027 start: "{".to_string(),
7028 end: "}".to_string(),
7029 close: true,
7030 surround: true,
7031 newline: true,
7032 },
7033 BracketPair {
7034 start: "(".to_string(),
7035 end: ")".to_string(),
7036 close: true,
7037 surround: true,
7038 newline: true,
7039 },
7040 BracketPair {
7041 start: "[".to_string(),
7042 end: "]".to_string(),
7043 close: false,
7044 surround: false,
7045 newline: true,
7046 },
7047 ],
7048 ..Default::default()
7049 },
7050 autoclose_before: "})]".to_string(),
7051 ..Default::default()
7052 },
7053 Some(tree_sitter_rust::LANGUAGE.into()),
7054 ));
7055
7056 cx.language_registry().add(language.clone());
7057 cx.update_buffer(|buffer, cx| {
7058 buffer.set_language(Some(language), cx);
7059 });
7060
7061 cx.set_state(
7062 &"
7063 ˇ
7064 ˇ
7065 ˇ
7066 "
7067 .unindent(),
7068 );
7069
7070 // ensure only matching closing brackets are skipped over
7071 cx.update_editor(|editor, window, cx| {
7072 editor.handle_input("}", window, cx);
7073 editor.move_left(&MoveLeft, window, cx);
7074 editor.handle_input(")", window, cx);
7075 editor.move_left(&MoveLeft, window, cx);
7076 });
7077 cx.assert_editor_state(
7078 &"
7079 ˇ)}
7080 ˇ)}
7081 ˇ)}
7082 "
7083 .unindent(),
7084 );
7085
7086 // skip-over closing brackets at multiple cursors
7087 cx.update_editor(|editor, window, cx| {
7088 editor.handle_input(")", window, cx);
7089 editor.handle_input("}", window, cx);
7090 });
7091 cx.assert_editor_state(
7092 &"
7093 )}ˇ
7094 )}ˇ
7095 )}ˇ
7096 "
7097 .unindent(),
7098 );
7099
7100 // ignore non-close brackets
7101 cx.update_editor(|editor, window, cx| {
7102 editor.handle_input("]", window, cx);
7103 editor.move_left(&MoveLeft, window, cx);
7104 editor.handle_input("]", window, cx);
7105 });
7106 cx.assert_editor_state(
7107 &"
7108 )}]ˇ]
7109 )}]ˇ]
7110 )}]ˇ]
7111 "
7112 .unindent(),
7113 );
7114}
7115
7116#[gpui::test]
7117async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
7118 init_test(cx, |_| {});
7119
7120 let mut cx = EditorTestContext::new(cx).await;
7121
7122 let html_language = Arc::new(
7123 Language::new(
7124 LanguageConfig {
7125 name: "HTML".into(),
7126 brackets: BracketPairConfig {
7127 pairs: vec![
7128 BracketPair {
7129 start: "<".into(),
7130 end: ">".into(),
7131 close: true,
7132 ..Default::default()
7133 },
7134 BracketPair {
7135 start: "{".into(),
7136 end: "}".into(),
7137 close: true,
7138 ..Default::default()
7139 },
7140 BracketPair {
7141 start: "(".into(),
7142 end: ")".into(),
7143 close: true,
7144 ..Default::default()
7145 },
7146 ],
7147 ..Default::default()
7148 },
7149 autoclose_before: "})]>".into(),
7150 ..Default::default()
7151 },
7152 Some(tree_sitter_html::LANGUAGE.into()),
7153 )
7154 .with_injection_query(
7155 r#"
7156 (script_element
7157 (raw_text) @injection.content
7158 (#set! injection.language "javascript"))
7159 "#,
7160 )
7161 .unwrap(),
7162 );
7163
7164 let javascript_language = Arc::new(Language::new(
7165 LanguageConfig {
7166 name: "JavaScript".into(),
7167 brackets: BracketPairConfig {
7168 pairs: vec![
7169 BracketPair {
7170 start: "/*".into(),
7171 end: " */".into(),
7172 close: true,
7173 ..Default::default()
7174 },
7175 BracketPair {
7176 start: "{".into(),
7177 end: "}".into(),
7178 close: true,
7179 ..Default::default()
7180 },
7181 BracketPair {
7182 start: "(".into(),
7183 end: ")".into(),
7184 close: true,
7185 ..Default::default()
7186 },
7187 ],
7188 ..Default::default()
7189 },
7190 autoclose_before: "})]>".into(),
7191 ..Default::default()
7192 },
7193 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
7194 ));
7195
7196 cx.language_registry().add(html_language.clone());
7197 cx.language_registry().add(javascript_language.clone());
7198
7199 cx.update_buffer(|buffer, cx| {
7200 buffer.set_language(Some(html_language), cx);
7201 });
7202
7203 cx.set_state(
7204 &r#"
7205 <body>ˇ
7206 <script>
7207 var x = 1;ˇ
7208 </script>
7209 </body>ˇ
7210 "#
7211 .unindent(),
7212 );
7213
7214 // Precondition: different languages are active at different locations.
7215 cx.update_editor(|editor, window, cx| {
7216 let snapshot = editor.snapshot(window, cx);
7217 let cursors = editor.selections.ranges::<usize>(cx);
7218 let languages = cursors
7219 .iter()
7220 .map(|c| snapshot.language_at(c.start).unwrap().name())
7221 .collect::<Vec<_>>();
7222 assert_eq!(
7223 languages,
7224 &["HTML".into(), "JavaScript".into(), "HTML".into()]
7225 );
7226 });
7227
7228 // Angle brackets autoclose in HTML, but not JavaScript.
7229 cx.update_editor(|editor, window, cx| {
7230 editor.handle_input("<", window, cx);
7231 editor.handle_input("a", window, cx);
7232 });
7233 cx.assert_editor_state(
7234 &r#"
7235 <body><aˇ>
7236 <script>
7237 var x = 1;<aˇ
7238 </script>
7239 </body><aˇ>
7240 "#
7241 .unindent(),
7242 );
7243
7244 // Curly braces and parens autoclose in both HTML and JavaScript.
7245 cx.update_editor(|editor, window, cx| {
7246 editor.handle_input(" b=", window, cx);
7247 editor.handle_input("{", window, cx);
7248 editor.handle_input("c", window, cx);
7249 editor.handle_input("(", window, cx);
7250 });
7251 cx.assert_editor_state(
7252 &r#"
7253 <body><a b={c(ˇ)}>
7254 <script>
7255 var x = 1;<a b={c(ˇ)}
7256 </script>
7257 </body><a b={c(ˇ)}>
7258 "#
7259 .unindent(),
7260 );
7261
7262 // Brackets that were already autoclosed are skipped.
7263 cx.update_editor(|editor, window, cx| {
7264 editor.handle_input(")", window, cx);
7265 editor.handle_input("d", window, cx);
7266 editor.handle_input("}", window, cx);
7267 });
7268 cx.assert_editor_state(
7269 &r#"
7270 <body><a b={c()d}ˇ>
7271 <script>
7272 var x = 1;<a b={c()d}ˇ
7273 </script>
7274 </body><a b={c()d}ˇ>
7275 "#
7276 .unindent(),
7277 );
7278 cx.update_editor(|editor, window, cx| {
7279 editor.handle_input(">", window, cx);
7280 });
7281 cx.assert_editor_state(
7282 &r#"
7283 <body><a b={c()d}>ˇ
7284 <script>
7285 var x = 1;<a b={c()d}>ˇ
7286 </script>
7287 </body><a b={c()d}>ˇ
7288 "#
7289 .unindent(),
7290 );
7291
7292 // Reset
7293 cx.set_state(
7294 &r#"
7295 <body>ˇ
7296 <script>
7297 var x = 1;ˇ
7298 </script>
7299 </body>ˇ
7300 "#
7301 .unindent(),
7302 );
7303
7304 cx.update_editor(|editor, window, cx| {
7305 editor.handle_input("<", window, cx);
7306 });
7307 cx.assert_editor_state(
7308 &r#"
7309 <body><ˇ>
7310 <script>
7311 var x = 1;<ˇ
7312 </script>
7313 </body><ˇ>
7314 "#
7315 .unindent(),
7316 );
7317
7318 // When backspacing, the closing angle brackets are removed.
7319 cx.update_editor(|editor, window, cx| {
7320 editor.backspace(&Backspace, window, cx);
7321 });
7322 cx.assert_editor_state(
7323 &r#"
7324 <body>ˇ
7325 <script>
7326 var x = 1;ˇ
7327 </script>
7328 </body>ˇ
7329 "#
7330 .unindent(),
7331 );
7332
7333 // Block comments autoclose in JavaScript, but not HTML.
7334 cx.update_editor(|editor, window, cx| {
7335 editor.handle_input("/", window, cx);
7336 editor.handle_input("*", window, cx);
7337 });
7338 cx.assert_editor_state(
7339 &r#"
7340 <body>/*ˇ
7341 <script>
7342 var x = 1;/*ˇ */
7343 </script>
7344 </body>/*ˇ
7345 "#
7346 .unindent(),
7347 );
7348}
7349
7350#[gpui::test]
7351async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
7352 init_test(cx, |_| {});
7353
7354 let mut cx = EditorTestContext::new(cx).await;
7355
7356 let rust_language = Arc::new(
7357 Language::new(
7358 LanguageConfig {
7359 name: "Rust".into(),
7360 brackets: serde_json::from_value(json!([
7361 { "start": "{", "end": "}", "close": true, "newline": true },
7362 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
7363 ]))
7364 .unwrap(),
7365 autoclose_before: "})]>".into(),
7366 ..Default::default()
7367 },
7368 Some(tree_sitter_rust::LANGUAGE.into()),
7369 )
7370 .with_override_query("(string_literal) @string")
7371 .unwrap(),
7372 );
7373
7374 cx.language_registry().add(rust_language.clone());
7375 cx.update_buffer(|buffer, cx| {
7376 buffer.set_language(Some(rust_language), cx);
7377 });
7378
7379 cx.set_state(
7380 &r#"
7381 let x = ˇ
7382 "#
7383 .unindent(),
7384 );
7385
7386 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
7387 cx.update_editor(|editor, window, cx| {
7388 editor.handle_input("\"", window, cx);
7389 });
7390 cx.assert_editor_state(
7391 &r#"
7392 let x = "ˇ"
7393 "#
7394 .unindent(),
7395 );
7396
7397 // Inserting another quotation mark. The cursor moves across the existing
7398 // automatically-inserted quotation mark.
7399 cx.update_editor(|editor, window, cx| {
7400 editor.handle_input("\"", window, cx);
7401 });
7402 cx.assert_editor_state(
7403 &r#"
7404 let x = ""ˇ
7405 "#
7406 .unindent(),
7407 );
7408
7409 // Reset
7410 cx.set_state(
7411 &r#"
7412 let x = ˇ
7413 "#
7414 .unindent(),
7415 );
7416
7417 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
7418 cx.update_editor(|editor, window, cx| {
7419 editor.handle_input("\"", window, cx);
7420 editor.handle_input(" ", window, cx);
7421 editor.move_left(&Default::default(), window, cx);
7422 editor.handle_input("\\", window, cx);
7423 editor.handle_input("\"", window, cx);
7424 });
7425 cx.assert_editor_state(
7426 &r#"
7427 let x = "\"ˇ "
7428 "#
7429 .unindent(),
7430 );
7431
7432 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
7433 // mark. Nothing is inserted.
7434 cx.update_editor(|editor, window, cx| {
7435 editor.move_right(&Default::default(), window, cx);
7436 editor.handle_input("\"", window, cx);
7437 });
7438 cx.assert_editor_state(
7439 &r#"
7440 let x = "\" "ˇ
7441 "#
7442 .unindent(),
7443 );
7444}
7445
7446#[gpui::test]
7447async fn test_surround_with_pair(cx: &mut TestAppContext) {
7448 init_test(cx, |_| {});
7449
7450 let language = Arc::new(Language::new(
7451 LanguageConfig {
7452 brackets: BracketPairConfig {
7453 pairs: vec![
7454 BracketPair {
7455 start: "{".to_string(),
7456 end: "}".to_string(),
7457 close: true,
7458 surround: true,
7459 newline: true,
7460 },
7461 BracketPair {
7462 start: "/* ".to_string(),
7463 end: "*/".to_string(),
7464 close: true,
7465 surround: true,
7466 ..Default::default()
7467 },
7468 ],
7469 ..Default::default()
7470 },
7471 ..Default::default()
7472 },
7473 Some(tree_sitter_rust::LANGUAGE.into()),
7474 ));
7475
7476 let text = r#"
7477 a
7478 b
7479 c
7480 "#
7481 .unindent();
7482
7483 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
7484 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7485 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7486 editor
7487 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
7488 .await;
7489
7490 editor.update_in(cx, |editor, window, cx| {
7491 editor.change_selections(None, window, cx, |s| {
7492 s.select_display_ranges([
7493 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
7494 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
7495 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
7496 ])
7497 });
7498
7499 editor.handle_input("{", window, cx);
7500 editor.handle_input("{", window, cx);
7501 editor.handle_input("{", window, cx);
7502 assert_eq!(
7503 editor.text(cx),
7504 "
7505 {{{a}}}
7506 {{{b}}}
7507 {{{c}}}
7508 "
7509 .unindent()
7510 );
7511 assert_eq!(
7512 editor.selections.display_ranges(cx),
7513 [
7514 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
7515 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
7516 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
7517 ]
7518 );
7519
7520 editor.undo(&Undo, window, cx);
7521 editor.undo(&Undo, window, cx);
7522 editor.undo(&Undo, window, cx);
7523 assert_eq!(
7524 editor.text(cx),
7525 "
7526 a
7527 b
7528 c
7529 "
7530 .unindent()
7531 );
7532 assert_eq!(
7533 editor.selections.display_ranges(cx),
7534 [
7535 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
7536 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
7537 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
7538 ]
7539 );
7540
7541 // Ensure inserting the first character of a multi-byte bracket pair
7542 // doesn't surround the selections with the bracket.
7543 editor.handle_input("/", window, cx);
7544 assert_eq!(
7545 editor.text(cx),
7546 "
7547 /
7548 /
7549 /
7550 "
7551 .unindent()
7552 );
7553 assert_eq!(
7554 editor.selections.display_ranges(cx),
7555 [
7556 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
7557 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
7558 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
7559 ]
7560 );
7561
7562 editor.undo(&Undo, window, cx);
7563 assert_eq!(
7564 editor.text(cx),
7565 "
7566 a
7567 b
7568 c
7569 "
7570 .unindent()
7571 );
7572 assert_eq!(
7573 editor.selections.display_ranges(cx),
7574 [
7575 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
7576 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
7577 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
7578 ]
7579 );
7580
7581 // Ensure inserting the last character of a multi-byte bracket pair
7582 // doesn't surround the selections with the bracket.
7583 editor.handle_input("*", window, cx);
7584 assert_eq!(
7585 editor.text(cx),
7586 "
7587 *
7588 *
7589 *
7590 "
7591 .unindent()
7592 );
7593 assert_eq!(
7594 editor.selections.display_ranges(cx),
7595 [
7596 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
7597 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
7598 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
7599 ]
7600 );
7601 });
7602}
7603
7604#[gpui::test]
7605async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
7606 init_test(cx, |_| {});
7607
7608 let language = Arc::new(Language::new(
7609 LanguageConfig {
7610 brackets: BracketPairConfig {
7611 pairs: vec![BracketPair {
7612 start: "{".to_string(),
7613 end: "}".to_string(),
7614 close: true,
7615 surround: true,
7616 newline: true,
7617 }],
7618 ..Default::default()
7619 },
7620 autoclose_before: "}".to_string(),
7621 ..Default::default()
7622 },
7623 Some(tree_sitter_rust::LANGUAGE.into()),
7624 ));
7625
7626 let text = r#"
7627 a
7628 b
7629 c
7630 "#
7631 .unindent();
7632
7633 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
7634 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7635 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7636 editor
7637 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
7638 .await;
7639
7640 editor.update_in(cx, |editor, window, cx| {
7641 editor.change_selections(None, window, cx, |s| {
7642 s.select_ranges([
7643 Point::new(0, 1)..Point::new(0, 1),
7644 Point::new(1, 1)..Point::new(1, 1),
7645 Point::new(2, 1)..Point::new(2, 1),
7646 ])
7647 });
7648
7649 editor.handle_input("{", window, cx);
7650 editor.handle_input("{", window, cx);
7651 editor.handle_input("_", window, cx);
7652 assert_eq!(
7653 editor.text(cx),
7654 "
7655 a{{_}}
7656 b{{_}}
7657 c{{_}}
7658 "
7659 .unindent()
7660 );
7661 assert_eq!(
7662 editor.selections.ranges::<Point>(cx),
7663 [
7664 Point::new(0, 4)..Point::new(0, 4),
7665 Point::new(1, 4)..Point::new(1, 4),
7666 Point::new(2, 4)..Point::new(2, 4)
7667 ]
7668 );
7669
7670 editor.backspace(&Default::default(), window, cx);
7671 editor.backspace(&Default::default(), window, cx);
7672 assert_eq!(
7673 editor.text(cx),
7674 "
7675 a{}
7676 b{}
7677 c{}
7678 "
7679 .unindent()
7680 );
7681 assert_eq!(
7682 editor.selections.ranges::<Point>(cx),
7683 [
7684 Point::new(0, 2)..Point::new(0, 2),
7685 Point::new(1, 2)..Point::new(1, 2),
7686 Point::new(2, 2)..Point::new(2, 2)
7687 ]
7688 );
7689
7690 editor.delete_to_previous_word_start(&Default::default(), window, cx);
7691 assert_eq!(
7692 editor.text(cx),
7693 "
7694 a
7695 b
7696 c
7697 "
7698 .unindent()
7699 );
7700 assert_eq!(
7701 editor.selections.ranges::<Point>(cx),
7702 [
7703 Point::new(0, 1)..Point::new(0, 1),
7704 Point::new(1, 1)..Point::new(1, 1),
7705 Point::new(2, 1)..Point::new(2, 1)
7706 ]
7707 );
7708 });
7709}
7710
7711#[gpui::test]
7712async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
7713 init_test(cx, |settings| {
7714 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
7715 });
7716
7717 let mut cx = EditorTestContext::new(cx).await;
7718
7719 let language = Arc::new(Language::new(
7720 LanguageConfig {
7721 brackets: BracketPairConfig {
7722 pairs: vec![
7723 BracketPair {
7724 start: "{".to_string(),
7725 end: "}".to_string(),
7726 close: true,
7727 surround: true,
7728 newline: true,
7729 },
7730 BracketPair {
7731 start: "(".to_string(),
7732 end: ")".to_string(),
7733 close: true,
7734 surround: true,
7735 newline: true,
7736 },
7737 BracketPair {
7738 start: "[".to_string(),
7739 end: "]".to_string(),
7740 close: false,
7741 surround: true,
7742 newline: true,
7743 },
7744 ],
7745 ..Default::default()
7746 },
7747 autoclose_before: "})]".to_string(),
7748 ..Default::default()
7749 },
7750 Some(tree_sitter_rust::LANGUAGE.into()),
7751 ));
7752
7753 cx.language_registry().add(language.clone());
7754 cx.update_buffer(|buffer, cx| {
7755 buffer.set_language(Some(language), cx);
7756 });
7757
7758 cx.set_state(
7759 &"
7760 {(ˇ)}
7761 [[ˇ]]
7762 {(ˇ)}
7763 "
7764 .unindent(),
7765 );
7766
7767 cx.update_editor(|editor, window, cx| {
7768 editor.backspace(&Default::default(), window, cx);
7769 editor.backspace(&Default::default(), window, cx);
7770 });
7771
7772 cx.assert_editor_state(
7773 &"
7774 ˇ
7775 ˇ]]
7776 ˇ
7777 "
7778 .unindent(),
7779 );
7780
7781 cx.update_editor(|editor, window, cx| {
7782 editor.handle_input("{", window, cx);
7783 editor.handle_input("{", window, cx);
7784 editor.move_right(&MoveRight, window, cx);
7785 editor.move_right(&MoveRight, window, cx);
7786 editor.move_left(&MoveLeft, window, cx);
7787 editor.move_left(&MoveLeft, window, cx);
7788 editor.backspace(&Default::default(), window, cx);
7789 });
7790
7791 cx.assert_editor_state(
7792 &"
7793 {ˇ}
7794 {ˇ}]]
7795 {ˇ}
7796 "
7797 .unindent(),
7798 );
7799
7800 cx.update_editor(|editor, window, cx| {
7801 editor.backspace(&Default::default(), window, cx);
7802 });
7803
7804 cx.assert_editor_state(
7805 &"
7806 ˇ
7807 ˇ]]
7808 ˇ
7809 "
7810 .unindent(),
7811 );
7812}
7813
7814#[gpui::test]
7815async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
7816 init_test(cx, |_| {});
7817
7818 let language = Arc::new(Language::new(
7819 LanguageConfig::default(),
7820 Some(tree_sitter_rust::LANGUAGE.into()),
7821 ));
7822
7823 let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
7824 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7825 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7826 editor
7827 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
7828 .await;
7829
7830 editor.update_in(cx, |editor, window, cx| {
7831 editor.set_auto_replace_emoji_shortcode(true);
7832
7833 editor.handle_input("Hello ", window, cx);
7834 editor.handle_input(":wave", window, cx);
7835 assert_eq!(editor.text(cx), "Hello :wave".unindent());
7836
7837 editor.handle_input(":", window, cx);
7838 assert_eq!(editor.text(cx), "Hello 👋".unindent());
7839
7840 editor.handle_input(" :smile", window, cx);
7841 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
7842
7843 editor.handle_input(":", window, cx);
7844 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
7845
7846 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
7847 editor.handle_input(":wave", window, cx);
7848 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
7849
7850 editor.handle_input(":", window, cx);
7851 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
7852
7853 editor.handle_input(":1", window, cx);
7854 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
7855
7856 editor.handle_input(":", window, cx);
7857 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
7858
7859 // Ensure shortcode does not get replaced when it is part of a word
7860 editor.handle_input(" Test:wave", window, cx);
7861 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
7862
7863 editor.handle_input(":", window, cx);
7864 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
7865
7866 editor.set_auto_replace_emoji_shortcode(false);
7867
7868 // Ensure shortcode does not get replaced when auto replace is off
7869 editor.handle_input(" :wave", window, cx);
7870 assert_eq!(
7871 editor.text(cx),
7872 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
7873 );
7874
7875 editor.handle_input(":", window, cx);
7876 assert_eq!(
7877 editor.text(cx),
7878 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
7879 );
7880 });
7881}
7882
7883#[gpui::test]
7884async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
7885 init_test(cx, |_| {});
7886
7887 let (text, insertion_ranges) = marked_text_ranges(
7888 indoc! {"
7889 ˇ
7890 "},
7891 false,
7892 );
7893
7894 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
7895 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7896
7897 _ = editor.update_in(cx, |editor, window, cx| {
7898 let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
7899
7900 editor
7901 .insert_snippet(&insertion_ranges, snippet, window, cx)
7902 .unwrap();
7903
7904 fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
7905 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
7906 assert_eq!(editor.text(cx), expected_text);
7907 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
7908 }
7909
7910 assert(
7911 editor,
7912 cx,
7913 indoc! {"
7914 type «» =•
7915 "},
7916 );
7917
7918 assert!(editor.context_menu_visible(), "There should be a matches");
7919 });
7920}
7921
7922#[gpui::test]
7923async fn test_snippets(cx: &mut TestAppContext) {
7924 init_test(cx, |_| {});
7925
7926 let (text, insertion_ranges) = marked_text_ranges(
7927 indoc! {"
7928 a.ˇ b
7929 a.ˇ b
7930 a.ˇ b
7931 "},
7932 false,
7933 );
7934
7935 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
7936 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7937
7938 editor.update_in(cx, |editor, window, cx| {
7939 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
7940
7941 editor
7942 .insert_snippet(&insertion_ranges, snippet, window, cx)
7943 .unwrap();
7944
7945 fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
7946 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
7947 assert_eq!(editor.text(cx), expected_text);
7948 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
7949 }
7950
7951 assert(
7952 editor,
7953 cx,
7954 indoc! {"
7955 a.f(«one», two, «three») b
7956 a.f(«one», two, «three») b
7957 a.f(«one», two, «three») b
7958 "},
7959 );
7960
7961 // Can't move earlier than the first tab stop
7962 assert!(!editor.move_to_prev_snippet_tabstop(window, cx));
7963 assert(
7964 editor,
7965 cx,
7966 indoc! {"
7967 a.f(«one», two, «three») b
7968 a.f(«one», two, «three») b
7969 a.f(«one», two, «three») b
7970 "},
7971 );
7972
7973 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7974 assert(
7975 editor,
7976 cx,
7977 indoc! {"
7978 a.f(one, «two», three) b
7979 a.f(one, «two», three) b
7980 a.f(one, «two», three) b
7981 "},
7982 );
7983
7984 editor.move_to_prev_snippet_tabstop(window, cx);
7985 assert(
7986 editor,
7987 cx,
7988 indoc! {"
7989 a.f(«one», two, «three») b
7990 a.f(«one», two, «three») b
7991 a.f(«one», two, «three») b
7992 "},
7993 );
7994
7995 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7996 assert(
7997 editor,
7998 cx,
7999 indoc! {"
8000 a.f(one, «two», three) b
8001 a.f(one, «two», three) b
8002 a.f(one, «two», three) b
8003 "},
8004 );
8005 assert!(editor.move_to_next_snippet_tabstop(window, cx));
8006 assert(
8007 editor,
8008 cx,
8009 indoc! {"
8010 a.f(one, two, three)ˇ b
8011 a.f(one, two, three)ˇ b
8012 a.f(one, two, three)ˇ b
8013 "},
8014 );
8015
8016 // As soon as the last tab stop is reached, snippet state is gone
8017 editor.move_to_prev_snippet_tabstop(window, cx);
8018 assert(
8019 editor,
8020 cx,
8021 indoc! {"
8022 a.f(one, two, three)ˇ b
8023 a.f(one, two, three)ˇ b
8024 a.f(one, two, three)ˇ b
8025 "},
8026 );
8027 });
8028}
8029
8030#[gpui::test]
8031async fn test_document_format_during_save(cx: &mut TestAppContext) {
8032 init_test(cx, |_| {});
8033
8034 let fs = FakeFs::new(cx.executor());
8035 fs.insert_file(path!("/file.rs"), Default::default()).await;
8036
8037 let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
8038
8039 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8040 language_registry.add(rust_lang());
8041 let mut fake_servers = language_registry.register_fake_lsp(
8042 "Rust",
8043 FakeLspAdapter {
8044 capabilities: lsp::ServerCapabilities {
8045 document_formatting_provider: Some(lsp::OneOf::Left(true)),
8046 ..Default::default()
8047 },
8048 ..Default::default()
8049 },
8050 );
8051
8052 let buffer = project
8053 .update(cx, |project, cx| {
8054 project.open_local_buffer(path!("/file.rs"), cx)
8055 })
8056 .await
8057 .unwrap();
8058
8059 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
8060 let (editor, cx) = cx.add_window_view(|window, cx| {
8061 build_editor_with_project(project.clone(), buffer, window, cx)
8062 });
8063 editor.update_in(cx, |editor, window, cx| {
8064 editor.set_text("one\ntwo\nthree\n", window, cx)
8065 });
8066 assert!(cx.read(|cx| editor.is_dirty(cx)));
8067
8068 cx.executor().start_waiting();
8069 let fake_server = fake_servers.next().await.unwrap();
8070
8071 {
8072 fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
8073 move |params, _| async move {
8074 assert_eq!(
8075 params.text_document.uri,
8076 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
8077 );
8078 assert_eq!(params.options.tab_size, 4);
8079 Ok(Some(vec![lsp::TextEdit::new(
8080 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
8081 ", ".to_string(),
8082 )]))
8083 },
8084 );
8085 let save = editor
8086 .update_in(cx, |editor, window, cx| {
8087 editor.save(true, project.clone(), window, cx)
8088 })
8089 .unwrap();
8090 cx.executor().start_waiting();
8091 save.await;
8092
8093 assert_eq!(
8094 editor.update(cx, |editor, cx| editor.text(cx)),
8095 "one, two\nthree\n"
8096 );
8097 assert!(!cx.read(|cx| editor.is_dirty(cx)));
8098 }
8099
8100 {
8101 editor.update_in(cx, |editor, window, cx| {
8102 editor.set_text("one\ntwo\nthree\n", window, cx)
8103 });
8104 assert!(cx.read(|cx| editor.is_dirty(cx)));
8105
8106 // Ensure we can still save even if formatting hangs.
8107 fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
8108 move |params, _| async move {
8109 assert_eq!(
8110 params.text_document.uri,
8111 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
8112 );
8113 futures::future::pending::<()>().await;
8114 unreachable!()
8115 },
8116 );
8117 let save = editor
8118 .update_in(cx, |editor, window, cx| {
8119 editor.save(true, project.clone(), window, cx)
8120 })
8121 .unwrap();
8122 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
8123 cx.executor().start_waiting();
8124 save.await;
8125 assert_eq!(
8126 editor.update(cx, |editor, cx| editor.text(cx)),
8127 "one\ntwo\nthree\n"
8128 );
8129 }
8130
8131 // For non-dirty buffer, no formatting request should be sent
8132 {
8133 assert!(!cx.read(|cx| editor.is_dirty(cx)));
8134
8135 fake_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
8136 panic!("Should not be invoked on non-dirty buffer");
8137 });
8138 let save = editor
8139 .update_in(cx, |editor, window, cx| {
8140 editor.save(true, project.clone(), window, cx)
8141 })
8142 .unwrap();
8143 cx.executor().start_waiting();
8144 save.await;
8145 }
8146
8147 // Set rust language override and assert overridden tabsize is sent to language server
8148 update_test_language_settings(cx, |settings| {
8149 settings.languages.insert(
8150 "Rust".into(),
8151 LanguageSettingsContent {
8152 tab_size: NonZeroU32::new(8),
8153 ..Default::default()
8154 },
8155 );
8156 });
8157
8158 {
8159 editor.update_in(cx, |editor, window, cx| {
8160 editor.set_text("somehting_new\n", window, cx)
8161 });
8162 assert!(cx.read(|cx| editor.is_dirty(cx)));
8163 let _formatting_request_signal = fake_server
8164 .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
8165 assert_eq!(
8166 params.text_document.uri,
8167 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
8168 );
8169 assert_eq!(params.options.tab_size, 8);
8170 Ok(Some(vec![]))
8171 });
8172 let save = editor
8173 .update_in(cx, |editor, window, cx| {
8174 editor.save(true, project.clone(), window, cx)
8175 })
8176 .unwrap();
8177 cx.executor().start_waiting();
8178 save.await;
8179 }
8180}
8181
8182#[gpui::test]
8183async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
8184 init_test(cx, |_| {});
8185
8186 let cols = 4;
8187 let rows = 10;
8188 let sample_text_1 = sample_text(rows, cols, 'a');
8189 assert_eq!(
8190 sample_text_1,
8191 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
8192 );
8193 let sample_text_2 = sample_text(rows, cols, 'l');
8194 assert_eq!(
8195 sample_text_2,
8196 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
8197 );
8198 let sample_text_3 = sample_text(rows, cols, 'v');
8199 assert_eq!(
8200 sample_text_3,
8201 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
8202 );
8203
8204 let fs = FakeFs::new(cx.executor());
8205 fs.insert_tree(
8206 path!("/a"),
8207 json!({
8208 "main.rs": sample_text_1,
8209 "other.rs": sample_text_2,
8210 "lib.rs": sample_text_3,
8211 }),
8212 )
8213 .await;
8214
8215 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
8216 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
8217 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
8218
8219 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8220 language_registry.add(rust_lang());
8221 let mut fake_servers = language_registry.register_fake_lsp(
8222 "Rust",
8223 FakeLspAdapter {
8224 capabilities: lsp::ServerCapabilities {
8225 document_formatting_provider: Some(lsp::OneOf::Left(true)),
8226 ..Default::default()
8227 },
8228 ..Default::default()
8229 },
8230 );
8231
8232 let worktree = project.update(cx, |project, cx| {
8233 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
8234 assert_eq!(worktrees.len(), 1);
8235 worktrees.pop().unwrap()
8236 });
8237 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
8238
8239 let buffer_1 = project
8240 .update(cx, |project, cx| {
8241 project.open_buffer((worktree_id, "main.rs"), cx)
8242 })
8243 .await
8244 .unwrap();
8245 let buffer_2 = project
8246 .update(cx, |project, cx| {
8247 project.open_buffer((worktree_id, "other.rs"), cx)
8248 })
8249 .await
8250 .unwrap();
8251 let buffer_3 = project
8252 .update(cx, |project, cx| {
8253 project.open_buffer((worktree_id, "lib.rs"), cx)
8254 })
8255 .await
8256 .unwrap();
8257
8258 let multi_buffer = cx.new(|cx| {
8259 let mut multi_buffer = MultiBuffer::new(ReadWrite);
8260 multi_buffer.push_excerpts(
8261 buffer_1.clone(),
8262 [
8263 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
8264 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
8265 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
8266 ],
8267 cx,
8268 );
8269 multi_buffer.push_excerpts(
8270 buffer_2.clone(),
8271 [
8272 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
8273 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
8274 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
8275 ],
8276 cx,
8277 );
8278 multi_buffer.push_excerpts(
8279 buffer_3.clone(),
8280 [
8281 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
8282 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
8283 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
8284 ],
8285 cx,
8286 );
8287 multi_buffer
8288 });
8289 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
8290 Editor::new(
8291 EditorMode::full(),
8292 multi_buffer,
8293 Some(project.clone()),
8294 window,
8295 cx,
8296 )
8297 });
8298
8299 multi_buffer_editor.update_in(cx, |editor, window, cx| {
8300 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
8301 s.select_ranges(Some(1..2))
8302 });
8303 editor.insert("|one|two|three|", window, cx);
8304 });
8305 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
8306 multi_buffer_editor.update_in(cx, |editor, window, cx| {
8307 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
8308 s.select_ranges(Some(60..70))
8309 });
8310 editor.insert("|four|five|six|", window, cx);
8311 });
8312 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
8313
8314 // First two buffers should be edited, but not the third one.
8315 assert_eq!(
8316 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
8317 "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}",
8318 );
8319 buffer_1.update(cx, |buffer, _| {
8320 assert!(buffer.is_dirty());
8321 assert_eq!(
8322 buffer.text(),
8323 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
8324 )
8325 });
8326 buffer_2.update(cx, |buffer, _| {
8327 assert!(buffer.is_dirty());
8328 assert_eq!(
8329 buffer.text(),
8330 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
8331 )
8332 });
8333 buffer_3.update(cx, |buffer, _| {
8334 assert!(!buffer.is_dirty());
8335 assert_eq!(buffer.text(), sample_text_3,)
8336 });
8337 cx.executor().run_until_parked();
8338
8339 cx.executor().start_waiting();
8340 let save = multi_buffer_editor
8341 .update_in(cx, |editor, window, cx| {
8342 editor.save(true, project.clone(), window, cx)
8343 })
8344 .unwrap();
8345
8346 let fake_server = fake_servers.next().await.unwrap();
8347 fake_server
8348 .server
8349 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
8350 Ok(Some(vec![lsp::TextEdit::new(
8351 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
8352 format!("[{} formatted]", params.text_document.uri),
8353 )]))
8354 })
8355 .detach();
8356 save.await;
8357
8358 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
8359 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
8360 assert_eq!(
8361 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
8362 uri!(
8363 "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}"
8364 ),
8365 );
8366 buffer_1.update(cx, |buffer, _| {
8367 assert!(!buffer.is_dirty());
8368 assert_eq!(
8369 buffer.text(),
8370 uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
8371 )
8372 });
8373 buffer_2.update(cx, |buffer, _| {
8374 assert!(!buffer.is_dirty());
8375 assert_eq!(
8376 buffer.text(),
8377 uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
8378 )
8379 });
8380 buffer_3.update(cx, |buffer, _| {
8381 assert!(!buffer.is_dirty());
8382 assert_eq!(buffer.text(), sample_text_3,)
8383 });
8384}
8385
8386#[gpui::test]
8387async fn test_range_format_during_save(cx: &mut TestAppContext) {
8388 init_test(cx, |_| {});
8389
8390 let fs = FakeFs::new(cx.executor());
8391 fs.insert_file(path!("/file.rs"), Default::default()).await;
8392
8393 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
8394
8395 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8396 language_registry.add(rust_lang());
8397 let mut fake_servers = language_registry.register_fake_lsp(
8398 "Rust",
8399 FakeLspAdapter {
8400 capabilities: lsp::ServerCapabilities {
8401 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
8402 ..Default::default()
8403 },
8404 ..Default::default()
8405 },
8406 );
8407
8408 let buffer = project
8409 .update(cx, |project, cx| {
8410 project.open_local_buffer(path!("/file.rs"), cx)
8411 })
8412 .await
8413 .unwrap();
8414
8415 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
8416 let (editor, cx) = cx.add_window_view(|window, cx| {
8417 build_editor_with_project(project.clone(), buffer, window, cx)
8418 });
8419 editor.update_in(cx, |editor, window, cx| {
8420 editor.set_text("one\ntwo\nthree\n", window, cx)
8421 });
8422 assert!(cx.read(|cx| editor.is_dirty(cx)));
8423
8424 cx.executor().start_waiting();
8425 let fake_server = fake_servers.next().await.unwrap();
8426
8427 let save = editor
8428 .update_in(cx, |editor, window, cx| {
8429 editor.save(true, project.clone(), window, cx)
8430 })
8431 .unwrap();
8432 fake_server
8433 .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
8434 assert_eq!(
8435 params.text_document.uri,
8436 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
8437 );
8438 assert_eq!(params.options.tab_size, 4);
8439 Ok(Some(vec![lsp::TextEdit::new(
8440 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
8441 ", ".to_string(),
8442 )]))
8443 })
8444 .next()
8445 .await;
8446 cx.executor().start_waiting();
8447 save.await;
8448 assert_eq!(
8449 editor.update(cx, |editor, cx| editor.text(cx)),
8450 "one, two\nthree\n"
8451 );
8452 assert!(!cx.read(|cx| editor.is_dirty(cx)));
8453
8454 editor.update_in(cx, |editor, window, cx| {
8455 editor.set_text("one\ntwo\nthree\n", window, cx)
8456 });
8457 assert!(cx.read(|cx| editor.is_dirty(cx)));
8458
8459 // Ensure we can still save even if formatting hangs.
8460 fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
8461 move |params, _| async move {
8462 assert_eq!(
8463 params.text_document.uri,
8464 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
8465 );
8466 futures::future::pending::<()>().await;
8467 unreachable!()
8468 },
8469 );
8470 let save = editor
8471 .update_in(cx, |editor, window, cx| {
8472 editor.save(true, project.clone(), window, cx)
8473 })
8474 .unwrap();
8475 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
8476 cx.executor().start_waiting();
8477 save.await;
8478 assert_eq!(
8479 editor.update(cx, |editor, cx| editor.text(cx)),
8480 "one\ntwo\nthree\n"
8481 );
8482 assert!(!cx.read(|cx| editor.is_dirty(cx)));
8483
8484 // For non-dirty buffer, no formatting request should be sent
8485 let save = editor
8486 .update_in(cx, |editor, window, cx| {
8487 editor.save(true, project.clone(), window, cx)
8488 })
8489 .unwrap();
8490 let _pending_format_request = fake_server
8491 .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
8492 panic!("Should not be invoked on non-dirty buffer");
8493 })
8494 .next();
8495 cx.executor().start_waiting();
8496 save.await;
8497
8498 // Set Rust language override and assert overridden tabsize is sent to language server
8499 update_test_language_settings(cx, |settings| {
8500 settings.languages.insert(
8501 "Rust".into(),
8502 LanguageSettingsContent {
8503 tab_size: NonZeroU32::new(8),
8504 ..Default::default()
8505 },
8506 );
8507 });
8508
8509 editor.update_in(cx, |editor, window, cx| {
8510 editor.set_text("somehting_new\n", window, cx)
8511 });
8512 assert!(cx.read(|cx| editor.is_dirty(cx)));
8513 let save = editor
8514 .update_in(cx, |editor, window, cx| {
8515 editor.save(true, project.clone(), window, cx)
8516 })
8517 .unwrap();
8518 fake_server
8519 .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
8520 assert_eq!(
8521 params.text_document.uri,
8522 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
8523 );
8524 assert_eq!(params.options.tab_size, 8);
8525 Ok(Some(vec![]))
8526 })
8527 .next()
8528 .await;
8529 cx.executor().start_waiting();
8530 save.await;
8531}
8532
8533#[gpui::test]
8534async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
8535 init_test(cx, |settings| {
8536 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
8537 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
8538 ))
8539 });
8540
8541 let fs = FakeFs::new(cx.executor());
8542 fs.insert_file(path!("/file.rs"), Default::default()).await;
8543
8544 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
8545
8546 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8547 language_registry.add(Arc::new(Language::new(
8548 LanguageConfig {
8549 name: "Rust".into(),
8550 matcher: LanguageMatcher {
8551 path_suffixes: vec!["rs".to_string()],
8552 ..Default::default()
8553 },
8554 ..LanguageConfig::default()
8555 },
8556 Some(tree_sitter_rust::LANGUAGE.into()),
8557 )));
8558 update_test_language_settings(cx, |settings| {
8559 // Enable Prettier formatting for the same buffer, and ensure
8560 // LSP is called instead of Prettier.
8561 settings.defaults.prettier = Some(PrettierSettings {
8562 allowed: true,
8563 ..PrettierSettings::default()
8564 });
8565 });
8566 let mut fake_servers = language_registry.register_fake_lsp(
8567 "Rust",
8568 FakeLspAdapter {
8569 capabilities: lsp::ServerCapabilities {
8570 document_formatting_provider: Some(lsp::OneOf::Left(true)),
8571 ..Default::default()
8572 },
8573 ..Default::default()
8574 },
8575 );
8576
8577 let buffer = project
8578 .update(cx, |project, cx| {
8579 project.open_local_buffer(path!("/file.rs"), cx)
8580 })
8581 .await
8582 .unwrap();
8583
8584 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
8585 let (editor, cx) = cx.add_window_view(|window, cx| {
8586 build_editor_with_project(project.clone(), buffer, window, cx)
8587 });
8588 editor.update_in(cx, |editor, window, cx| {
8589 editor.set_text("one\ntwo\nthree\n", window, cx)
8590 });
8591
8592 cx.executor().start_waiting();
8593 let fake_server = fake_servers.next().await.unwrap();
8594
8595 let format = editor
8596 .update_in(cx, |editor, window, cx| {
8597 editor.perform_format(
8598 project.clone(),
8599 FormatTrigger::Manual,
8600 FormatTarget::Buffers,
8601 window,
8602 cx,
8603 )
8604 })
8605 .unwrap();
8606 fake_server
8607 .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
8608 assert_eq!(
8609 params.text_document.uri,
8610 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
8611 );
8612 assert_eq!(params.options.tab_size, 4);
8613 Ok(Some(vec![lsp::TextEdit::new(
8614 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
8615 ", ".to_string(),
8616 )]))
8617 })
8618 .next()
8619 .await;
8620 cx.executor().start_waiting();
8621 format.await;
8622 assert_eq!(
8623 editor.update(cx, |editor, cx| editor.text(cx)),
8624 "one, two\nthree\n"
8625 );
8626
8627 editor.update_in(cx, |editor, window, cx| {
8628 editor.set_text("one\ntwo\nthree\n", window, cx)
8629 });
8630 // Ensure we don't lock if formatting hangs.
8631 fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
8632 move |params, _| async move {
8633 assert_eq!(
8634 params.text_document.uri,
8635 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
8636 );
8637 futures::future::pending::<()>().await;
8638 unreachable!()
8639 },
8640 );
8641 let format = editor
8642 .update_in(cx, |editor, window, cx| {
8643 editor.perform_format(
8644 project,
8645 FormatTrigger::Manual,
8646 FormatTarget::Buffers,
8647 window,
8648 cx,
8649 )
8650 })
8651 .unwrap();
8652 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
8653 cx.executor().start_waiting();
8654 format.await;
8655 assert_eq!(
8656 editor.update(cx, |editor, cx| editor.text(cx)),
8657 "one\ntwo\nthree\n"
8658 );
8659}
8660
8661#[gpui::test]
8662async fn test_multiple_formatters(cx: &mut TestAppContext) {
8663 init_test(cx, |settings| {
8664 settings.defaults.remove_trailing_whitespace_on_save = Some(true);
8665 settings.defaults.formatter =
8666 Some(language_settings::SelectedFormatter::List(FormatterList(
8667 vec![
8668 Formatter::LanguageServer { name: None },
8669 Formatter::CodeActions(
8670 [
8671 ("code-action-1".into(), true),
8672 ("code-action-2".into(), true),
8673 ]
8674 .into_iter()
8675 .collect(),
8676 ),
8677 ]
8678 .into(),
8679 )))
8680 });
8681
8682 let fs = FakeFs::new(cx.executor());
8683 fs.insert_file(path!("/file.rs"), "one \ntwo \nthree".into())
8684 .await;
8685
8686 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
8687 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8688 language_registry.add(rust_lang());
8689
8690 let mut fake_servers = language_registry.register_fake_lsp(
8691 "Rust",
8692 FakeLspAdapter {
8693 capabilities: lsp::ServerCapabilities {
8694 document_formatting_provider: Some(lsp::OneOf::Left(true)),
8695 execute_command_provider: Some(lsp::ExecuteCommandOptions {
8696 commands: vec!["the-command-for-code-action-1".into()],
8697 ..Default::default()
8698 }),
8699 code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
8700 ..Default::default()
8701 },
8702 ..Default::default()
8703 },
8704 );
8705
8706 let buffer = project
8707 .update(cx, |project, cx| {
8708 project.open_local_buffer(path!("/file.rs"), cx)
8709 })
8710 .await
8711 .unwrap();
8712
8713 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
8714 let (editor, cx) = cx.add_window_view(|window, cx| {
8715 build_editor_with_project(project.clone(), buffer, window, cx)
8716 });
8717
8718 cx.executor().start_waiting();
8719
8720 let fake_server = fake_servers.next().await.unwrap();
8721 fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
8722 move |_params, _| async move {
8723 Ok(Some(vec![lsp::TextEdit::new(
8724 lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
8725 "applied-formatting\n".to_string(),
8726 )]))
8727 },
8728 );
8729 fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
8730 move |params, _| async move {
8731 assert_eq!(
8732 params.context.only,
8733 Some(vec!["code-action-1".into(), "code-action-2".into()])
8734 );
8735 let uri = lsp::Url::from_file_path(path!("/file.rs")).unwrap();
8736 Ok(Some(vec![
8737 lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
8738 kind: Some("code-action-1".into()),
8739 edit: Some(lsp::WorkspaceEdit::new(
8740 [(
8741 uri.clone(),
8742 vec![lsp::TextEdit::new(
8743 lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
8744 "applied-code-action-1-edit\n".to_string(),
8745 )],
8746 )]
8747 .into_iter()
8748 .collect(),
8749 )),
8750 command: Some(lsp::Command {
8751 command: "the-command-for-code-action-1".into(),
8752 ..Default::default()
8753 }),
8754 ..Default::default()
8755 }),
8756 lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
8757 kind: Some("code-action-2".into()),
8758 edit: Some(lsp::WorkspaceEdit::new(
8759 [(
8760 uri.clone(),
8761 vec![lsp::TextEdit::new(
8762 lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
8763 "applied-code-action-2-edit\n".to_string(),
8764 )],
8765 )]
8766 .into_iter()
8767 .collect(),
8768 )),
8769 ..Default::default()
8770 }),
8771 ]))
8772 },
8773 );
8774
8775 fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
8776 move |params, _| async move { Ok(params) }
8777 });
8778
8779 let command_lock = Arc::new(futures::lock::Mutex::new(()));
8780 fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
8781 let fake = fake_server.clone();
8782 let lock = command_lock.clone();
8783 move |params, _| {
8784 assert_eq!(params.command, "the-command-for-code-action-1");
8785 let fake = fake.clone();
8786 let lock = lock.clone();
8787 async move {
8788 lock.lock().await;
8789 fake.server
8790 .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
8791 label: None,
8792 edit: lsp::WorkspaceEdit {
8793 changes: Some(
8794 [(
8795 lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
8796 vec![lsp::TextEdit {
8797 range: lsp::Range::new(
8798 lsp::Position::new(0, 0),
8799 lsp::Position::new(0, 0),
8800 ),
8801 new_text: "applied-code-action-1-command\n".into(),
8802 }],
8803 )]
8804 .into_iter()
8805 .collect(),
8806 ),
8807 ..Default::default()
8808 },
8809 })
8810 .await
8811 .unwrap();
8812 Ok(Some(json!(null)))
8813 }
8814 }
8815 });
8816
8817 cx.executor().start_waiting();
8818 editor
8819 .update_in(cx, |editor, window, cx| {
8820 editor.perform_format(
8821 project.clone(),
8822 FormatTrigger::Manual,
8823 FormatTarget::Buffers,
8824 window,
8825 cx,
8826 )
8827 })
8828 .unwrap()
8829 .await;
8830 editor.update(cx, |editor, cx| {
8831 assert_eq!(
8832 editor.text(cx),
8833 r#"
8834 applied-code-action-2-edit
8835 applied-code-action-1-command
8836 applied-code-action-1-edit
8837 applied-formatting
8838 one
8839 two
8840 three
8841 "#
8842 .unindent()
8843 );
8844 });
8845
8846 editor.update_in(cx, |editor, window, cx| {
8847 editor.undo(&Default::default(), window, cx);
8848 assert_eq!(editor.text(cx), "one \ntwo \nthree");
8849 });
8850
8851 // Perform a manual edit while waiting for an LSP command
8852 // that's being run as part of a formatting code action.
8853 let lock_guard = command_lock.lock().await;
8854 let format = editor
8855 .update_in(cx, |editor, window, cx| {
8856 editor.perform_format(
8857 project.clone(),
8858 FormatTrigger::Manual,
8859 FormatTarget::Buffers,
8860 window,
8861 cx,
8862 )
8863 })
8864 .unwrap();
8865 cx.run_until_parked();
8866 editor.update(cx, |editor, cx| {
8867 assert_eq!(
8868 editor.text(cx),
8869 r#"
8870 applied-code-action-1-edit
8871 applied-formatting
8872 one
8873 two
8874 three
8875 "#
8876 .unindent()
8877 );
8878
8879 editor.buffer.update(cx, |buffer, cx| {
8880 let ix = buffer.len(cx);
8881 buffer.edit([(ix..ix, "edited\n")], None, cx);
8882 });
8883 });
8884
8885 // Allow the LSP command to proceed. Because the buffer was edited,
8886 // the second code action will not be run.
8887 drop(lock_guard);
8888 format.await;
8889 editor.update_in(cx, |editor, window, cx| {
8890 assert_eq!(
8891 editor.text(cx),
8892 r#"
8893 applied-code-action-1-command
8894 applied-code-action-1-edit
8895 applied-formatting
8896 one
8897 two
8898 three
8899 edited
8900 "#
8901 .unindent()
8902 );
8903
8904 // The manual edit is undone first, because it is the last thing the user did
8905 // (even though the command completed afterwards).
8906 editor.undo(&Default::default(), window, cx);
8907 assert_eq!(
8908 editor.text(cx),
8909 r#"
8910 applied-code-action-1-command
8911 applied-code-action-1-edit
8912 applied-formatting
8913 one
8914 two
8915 three
8916 "#
8917 .unindent()
8918 );
8919
8920 // All the formatting (including the command, which completed after the manual edit)
8921 // is undone together.
8922 editor.undo(&Default::default(), window, cx);
8923 assert_eq!(editor.text(cx), "one \ntwo \nthree");
8924 });
8925}
8926
8927#[gpui::test]
8928async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
8929 init_test(cx, |settings| {
8930 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
8931 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
8932 ))
8933 });
8934
8935 let fs = FakeFs::new(cx.executor());
8936 fs.insert_file(path!("/file.ts"), Default::default()).await;
8937
8938 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
8939
8940 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8941 language_registry.add(Arc::new(Language::new(
8942 LanguageConfig {
8943 name: "TypeScript".into(),
8944 matcher: LanguageMatcher {
8945 path_suffixes: vec!["ts".to_string()],
8946 ..Default::default()
8947 },
8948 ..LanguageConfig::default()
8949 },
8950 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
8951 )));
8952 update_test_language_settings(cx, |settings| {
8953 settings.defaults.prettier = Some(PrettierSettings {
8954 allowed: true,
8955 ..PrettierSettings::default()
8956 });
8957 });
8958 let mut fake_servers = language_registry.register_fake_lsp(
8959 "TypeScript",
8960 FakeLspAdapter {
8961 capabilities: lsp::ServerCapabilities {
8962 code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
8963 ..Default::default()
8964 },
8965 ..Default::default()
8966 },
8967 );
8968
8969 let buffer = project
8970 .update(cx, |project, cx| {
8971 project.open_local_buffer(path!("/file.ts"), cx)
8972 })
8973 .await
8974 .unwrap();
8975
8976 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
8977 let (editor, cx) = cx.add_window_view(|window, cx| {
8978 build_editor_with_project(project.clone(), buffer, window, cx)
8979 });
8980 editor.update_in(cx, |editor, window, cx| {
8981 editor.set_text(
8982 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
8983 window,
8984 cx,
8985 )
8986 });
8987
8988 cx.executor().start_waiting();
8989 let fake_server = fake_servers.next().await.unwrap();
8990
8991 let format = editor
8992 .update_in(cx, |editor, window, cx| {
8993 editor.perform_code_action_kind(
8994 project.clone(),
8995 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
8996 window,
8997 cx,
8998 )
8999 })
9000 .unwrap();
9001 fake_server
9002 .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
9003 assert_eq!(
9004 params.text_document.uri,
9005 lsp::Url::from_file_path(path!("/file.ts")).unwrap()
9006 );
9007 Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
9008 lsp::CodeAction {
9009 title: "Organize Imports".to_string(),
9010 kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
9011 edit: Some(lsp::WorkspaceEdit {
9012 changes: Some(
9013 [(
9014 params.text_document.uri.clone(),
9015 vec![lsp::TextEdit::new(
9016 lsp::Range::new(
9017 lsp::Position::new(1, 0),
9018 lsp::Position::new(2, 0),
9019 ),
9020 "".to_string(),
9021 )],
9022 )]
9023 .into_iter()
9024 .collect(),
9025 ),
9026 ..Default::default()
9027 }),
9028 ..Default::default()
9029 },
9030 )]))
9031 })
9032 .next()
9033 .await;
9034 cx.executor().start_waiting();
9035 format.await;
9036 assert_eq!(
9037 editor.update(cx, |editor, cx| editor.text(cx)),
9038 "import { a } from 'module';\n\nconst x = a;\n"
9039 );
9040
9041 editor.update_in(cx, |editor, window, cx| {
9042 editor.set_text(
9043 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
9044 window,
9045 cx,
9046 )
9047 });
9048 // Ensure we don't lock if code action hangs.
9049 fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
9050 move |params, _| async move {
9051 assert_eq!(
9052 params.text_document.uri,
9053 lsp::Url::from_file_path(path!("/file.ts")).unwrap()
9054 );
9055 futures::future::pending::<()>().await;
9056 unreachable!()
9057 },
9058 );
9059 let format = editor
9060 .update_in(cx, |editor, window, cx| {
9061 editor.perform_code_action_kind(
9062 project,
9063 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
9064 window,
9065 cx,
9066 )
9067 })
9068 .unwrap();
9069 cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
9070 cx.executor().start_waiting();
9071 format.await;
9072 assert_eq!(
9073 editor.update(cx, |editor, cx| editor.text(cx)),
9074 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
9075 );
9076}
9077
9078#[gpui::test]
9079async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
9080 init_test(cx, |_| {});
9081
9082 let mut cx = EditorLspTestContext::new_rust(
9083 lsp::ServerCapabilities {
9084 document_formatting_provider: Some(lsp::OneOf::Left(true)),
9085 ..Default::default()
9086 },
9087 cx,
9088 )
9089 .await;
9090
9091 cx.set_state(indoc! {"
9092 one.twoˇ
9093 "});
9094
9095 // The format request takes a long time. When it completes, it inserts
9096 // a newline and an indent before the `.`
9097 cx.lsp
9098 .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
9099 let executor = cx.background_executor().clone();
9100 async move {
9101 executor.timer(Duration::from_millis(100)).await;
9102 Ok(Some(vec![lsp::TextEdit {
9103 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
9104 new_text: "\n ".into(),
9105 }]))
9106 }
9107 });
9108
9109 // Submit a format request.
9110 let format_1 = cx
9111 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
9112 .unwrap();
9113 cx.executor().run_until_parked();
9114
9115 // Submit a second format request.
9116 let format_2 = cx
9117 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
9118 .unwrap();
9119 cx.executor().run_until_parked();
9120
9121 // Wait for both format requests to complete
9122 cx.executor().advance_clock(Duration::from_millis(200));
9123 cx.executor().start_waiting();
9124 format_1.await.unwrap();
9125 cx.executor().start_waiting();
9126 format_2.await.unwrap();
9127
9128 // The formatting edits only happens once.
9129 cx.assert_editor_state(indoc! {"
9130 one
9131 .twoˇ
9132 "});
9133}
9134
9135#[gpui::test]
9136async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
9137 init_test(cx, |settings| {
9138 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
9139 });
9140
9141 let mut cx = EditorLspTestContext::new_rust(
9142 lsp::ServerCapabilities {
9143 document_formatting_provider: Some(lsp::OneOf::Left(true)),
9144 ..Default::default()
9145 },
9146 cx,
9147 )
9148 .await;
9149
9150 // Set up a buffer white some trailing whitespace and no trailing newline.
9151 cx.set_state(
9152 &[
9153 "one ", //
9154 "twoˇ", //
9155 "three ", //
9156 "four", //
9157 ]
9158 .join("\n"),
9159 );
9160
9161 // Submit a format request.
9162 let format = cx
9163 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
9164 .unwrap();
9165
9166 // Record which buffer changes have been sent to the language server
9167 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
9168 cx.lsp
9169 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
9170 let buffer_changes = buffer_changes.clone();
9171 move |params, _| {
9172 buffer_changes.lock().extend(
9173 params
9174 .content_changes
9175 .into_iter()
9176 .map(|e| (e.range.unwrap(), e.text)),
9177 );
9178 }
9179 });
9180
9181 // Handle formatting requests to the language server.
9182 cx.lsp
9183 .set_request_handler::<lsp::request::Formatting, _, _>({
9184 let buffer_changes = buffer_changes.clone();
9185 move |_, _| {
9186 // When formatting is requested, trailing whitespace has already been stripped,
9187 // and the trailing newline has already been added.
9188 assert_eq!(
9189 &buffer_changes.lock()[1..],
9190 &[
9191 (
9192 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
9193 "".into()
9194 ),
9195 (
9196 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
9197 "".into()
9198 ),
9199 (
9200 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
9201 "\n".into()
9202 ),
9203 ]
9204 );
9205
9206 // Insert blank lines between each line of the buffer.
9207 async move {
9208 Ok(Some(vec![
9209 lsp::TextEdit {
9210 range: lsp::Range::new(
9211 lsp::Position::new(1, 0),
9212 lsp::Position::new(1, 0),
9213 ),
9214 new_text: "\n".into(),
9215 },
9216 lsp::TextEdit {
9217 range: lsp::Range::new(
9218 lsp::Position::new(2, 0),
9219 lsp::Position::new(2, 0),
9220 ),
9221 new_text: "\n".into(),
9222 },
9223 ]))
9224 }
9225 }
9226 });
9227
9228 // After formatting the buffer, the trailing whitespace is stripped,
9229 // a newline is appended, and the edits provided by the language server
9230 // have been applied.
9231 format.await.unwrap();
9232 cx.assert_editor_state(
9233 &[
9234 "one", //
9235 "", //
9236 "twoˇ", //
9237 "", //
9238 "three", //
9239 "four", //
9240 "", //
9241 ]
9242 .join("\n"),
9243 );
9244
9245 // Undoing the formatting undoes the trailing whitespace removal, the
9246 // trailing newline, and the LSP edits.
9247 cx.update_buffer(|buffer, cx| buffer.undo(cx));
9248 cx.assert_editor_state(
9249 &[
9250 "one ", //
9251 "twoˇ", //
9252 "three ", //
9253 "four", //
9254 ]
9255 .join("\n"),
9256 );
9257}
9258
9259#[gpui::test]
9260async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
9261 cx: &mut TestAppContext,
9262) {
9263 init_test(cx, |_| {});
9264
9265 cx.update(|cx| {
9266 cx.update_global::<SettingsStore, _>(|settings, cx| {
9267 settings.update_user_settings::<EditorSettings>(cx, |settings| {
9268 settings.auto_signature_help = Some(true);
9269 });
9270 });
9271 });
9272
9273 let mut cx = EditorLspTestContext::new_rust(
9274 lsp::ServerCapabilities {
9275 signature_help_provider: Some(lsp::SignatureHelpOptions {
9276 ..Default::default()
9277 }),
9278 ..Default::default()
9279 },
9280 cx,
9281 )
9282 .await;
9283
9284 let language = Language::new(
9285 LanguageConfig {
9286 name: "Rust".into(),
9287 brackets: BracketPairConfig {
9288 pairs: vec![
9289 BracketPair {
9290 start: "{".to_string(),
9291 end: "}".to_string(),
9292 close: true,
9293 surround: true,
9294 newline: true,
9295 },
9296 BracketPair {
9297 start: "(".to_string(),
9298 end: ")".to_string(),
9299 close: true,
9300 surround: true,
9301 newline: true,
9302 },
9303 BracketPair {
9304 start: "/*".to_string(),
9305 end: " */".to_string(),
9306 close: true,
9307 surround: true,
9308 newline: true,
9309 },
9310 BracketPair {
9311 start: "[".to_string(),
9312 end: "]".to_string(),
9313 close: false,
9314 surround: false,
9315 newline: true,
9316 },
9317 BracketPair {
9318 start: "\"".to_string(),
9319 end: "\"".to_string(),
9320 close: true,
9321 surround: true,
9322 newline: false,
9323 },
9324 BracketPair {
9325 start: "<".to_string(),
9326 end: ">".to_string(),
9327 close: false,
9328 surround: true,
9329 newline: true,
9330 },
9331 ],
9332 ..Default::default()
9333 },
9334 autoclose_before: "})]".to_string(),
9335 ..Default::default()
9336 },
9337 Some(tree_sitter_rust::LANGUAGE.into()),
9338 );
9339 let language = Arc::new(language);
9340
9341 cx.language_registry().add(language.clone());
9342 cx.update_buffer(|buffer, cx| {
9343 buffer.set_language(Some(language), cx);
9344 });
9345
9346 cx.set_state(
9347 &r#"
9348 fn main() {
9349 sampleˇ
9350 }
9351 "#
9352 .unindent(),
9353 );
9354
9355 cx.update_editor(|editor, window, cx| {
9356 editor.handle_input("(", window, cx);
9357 });
9358 cx.assert_editor_state(
9359 &"
9360 fn main() {
9361 sample(ˇ)
9362 }
9363 "
9364 .unindent(),
9365 );
9366
9367 let mocked_response = lsp::SignatureHelp {
9368 signatures: vec![lsp::SignatureInformation {
9369 label: "fn sample(param1: u8, param2: u8)".to_string(),
9370 documentation: None,
9371 parameters: Some(vec![
9372 lsp::ParameterInformation {
9373 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
9374 documentation: None,
9375 },
9376 lsp::ParameterInformation {
9377 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
9378 documentation: None,
9379 },
9380 ]),
9381 active_parameter: None,
9382 }],
9383 active_signature: Some(0),
9384 active_parameter: Some(0),
9385 };
9386 handle_signature_help_request(&mut cx, mocked_response).await;
9387
9388 cx.condition(|editor, _| editor.signature_help_state.is_shown())
9389 .await;
9390
9391 cx.editor(|editor, _, _| {
9392 let signature_help_state = editor.signature_help_state.popover().cloned();
9393 assert_eq!(
9394 signature_help_state.unwrap().label,
9395 "param1: u8, param2: u8"
9396 );
9397 });
9398}
9399
9400#[gpui::test]
9401async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
9402 init_test(cx, |_| {});
9403
9404 cx.update(|cx| {
9405 cx.update_global::<SettingsStore, _>(|settings, cx| {
9406 settings.update_user_settings::<EditorSettings>(cx, |settings| {
9407 settings.auto_signature_help = Some(false);
9408 settings.show_signature_help_after_edits = Some(false);
9409 });
9410 });
9411 });
9412
9413 let mut cx = EditorLspTestContext::new_rust(
9414 lsp::ServerCapabilities {
9415 signature_help_provider: Some(lsp::SignatureHelpOptions {
9416 ..Default::default()
9417 }),
9418 ..Default::default()
9419 },
9420 cx,
9421 )
9422 .await;
9423
9424 let language = Language::new(
9425 LanguageConfig {
9426 name: "Rust".into(),
9427 brackets: BracketPairConfig {
9428 pairs: vec![
9429 BracketPair {
9430 start: "{".to_string(),
9431 end: "}".to_string(),
9432 close: true,
9433 surround: true,
9434 newline: true,
9435 },
9436 BracketPair {
9437 start: "(".to_string(),
9438 end: ")".to_string(),
9439 close: true,
9440 surround: true,
9441 newline: true,
9442 },
9443 BracketPair {
9444 start: "/*".to_string(),
9445 end: " */".to_string(),
9446 close: true,
9447 surround: true,
9448 newline: true,
9449 },
9450 BracketPair {
9451 start: "[".to_string(),
9452 end: "]".to_string(),
9453 close: false,
9454 surround: false,
9455 newline: true,
9456 },
9457 BracketPair {
9458 start: "\"".to_string(),
9459 end: "\"".to_string(),
9460 close: true,
9461 surround: true,
9462 newline: false,
9463 },
9464 BracketPair {
9465 start: "<".to_string(),
9466 end: ">".to_string(),
9467 close: false,
9468 surround: true,
9469 newline: true,
9470 },
9471 ],
9472 ..Default::default()
9473 },
9474 autoclose_before: "})]".to_string(),
9475 ..Default::default()
9476 },
9477 Some(tree_sitter_rust::LANGUAGE.into()),
9478 );
9479 let language = Arc::new(language);
9480
9481 cx.language_registry().add(language.clone());
9482 cx.update_buffer(|buffer, cx| {
9483 buffer.set_language(Some(language), cx);
9484 });
9485
9486 // Ensure that signature_help is not called when no signature help is enabled.
9487 cx.set_state(
9488 &r#"
9489 fn main() {
9490 sampleˇ
9491 }
9492 "#
9493 .unindent(),
9494 );
9495 cx.update_editor(|editor, window, cx| {
9496 editor.handle_input("(", window, cx);
9497 });
9498 cx.assert_editor_state(
9499 &"
9500 fn main() {
9501 sample(ˇ)
9502 }
9503 "
9504 .unindent(),
9505 );
9506 cx.editor(|editor, _, _| {
9507 assert!(editor.signature_help_state.task().is_none());
9508 });
9509
9510 let mocked_response = lsp::SignatureHelp {
9511 signatures: vec![lsp::SignatureInformation {
9512 label: "fn sample(param1: u8, param2: u8)".to_string(),
9513 documentation: None,
9514 parameters: Some(vec![
9515 lsp::ParameterInformation {
9516 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
9517 documentation: None,
9518 },
9519 lsp::ParameterInformation {
9520 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
9521 documentation: None,
9522 },
9523 ]),
9524 active_parameter: None,
9525 }],
9526 active_signature: Some(0),
9527 active_parameter: Some(0),
9528 };
9529
9530 // Ensure that signature_help is called when enabled afte edits
9531 cx.update(|_, cx| {
9532 cx.update_global::<SettingsStore, _>(|settings, cx| {
9533 settings.update_user_settings::<EditorSettings>(cx, |settings| {
9534 settings.auto_signature_help = Some(false);
9535 settings.show_signature_help_after_edits = Some(true);
9536 });
9537 });
9538 });
9539 cx.set_state(
9540 &r#"
9541 fn main() {
9542 sampleˇ
9543 }
9544 "#
9545 .unindent(),
9546 );
9547 cx.update_editor(|editor, window, cx| {
9548 editor.handle_input("(", window, cx);
9549 });
9550 cx.assert_editor_state(
9551 &"
9552 fn main() {
9553 sample(ˇ)
9554 }
9555 "
9556 .unindent(),
9557 );
9558 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
9559 cx.condition(|editor, _| editor.signature_help_state.is_shown())
9560 .await;
9561 cx.update_editor(|editor, _, _| {
9562 let signature_help_state = editor.signature_help_state.popover().cloned();
9563 assert!(signature_help_state.is_some());
9564 assert_eq!(
9565 signature_help_state.unwrap().label,
9566 "param1: u8, param2: u8"
9567 );
9568 editor.signature_help_state = SignatureHelpState::default();
9569 });
9570
9571 // Ensure that signature_help is called when auto signature help override is enabled
9572 cx.update(|_, cx| {
9573 cx.update_global::<SettingsStore, _>(|settings, cx| {
9574 settings.update_user_settings::<EditorSettings>(cx, |settings| {
9575 settings.auto_signature_help = Some(true);
9576 settings.show_signature_help_after_edits = Some(false);
9577 });
9578 });
9579 });
9580 cx.set_state(
9581 &r#"
9582 fn main() {
9583 sampleˇ
9584 }
9585 "#
9586 .unindent(),
9587 );
9588 cx.update_editor(|editor, window, cx| {
9589 editor.handle_input("(", window, cx);
9590 });
9591 cx.assert_editor_state(
9592 &"
9593 fn main() {
9594 sample(ˇ)
9595 }
9596 "
9597 .unindent(),
9598 );
9599 handle_signature_help_request(&mut cx, mocked_response).await;
9600 cx.condition(|editor, _| editor.signature_help_state.is_shown())
9601 .await;
9602 cx.editor(|editor, _, _| {
9603 let signature_help_state = editor.signature_help_state.popover().cloned();
9604 assert!(signature_help_state.is_some());
9605 assert_eq!(
9606 signature_help_state.unwrap().label,
9607 "param1: u8, param2: u8"
9608 );
9609 });
9610}
9611
9612#[gpui::test]
9613async fn test_signature_help(cx: &mut TestAppContext) {
9614 init_test(cx, |_| {});
9615 cx.update(|cx| {
9616 cx.update_global::<SettingsStore, _>(|settings, cx| {
9617 settings.update_user_settings::<EditorSettings>(cx, |settings| {
9618 settings.auto_signature_help = Some(true);
9619 });
9620 });
9621 });
9622
9623 let mut cx = EditorLspTestContext::new_rust(
9624 lsp::ServerCapabilities {
9625 signature_help_provider: Some(lsp::SignatureHelpOptions {
9626 ..Default::default()
9627 }),
9628 ..Default::default()
9629 },
9630 cx,
9631 )
9632 .await;
9633
9634 // A test that directly calls `show_signature_help`
9635 cx.update_editor(|editor, window, cx| {
9636 editor.show_signature_help(&ShowSignatureHelp, window, cx);
9637 });
9638
9639 let mocked_response = lsp::SignatureHelp {
9640 signatures: vec![lsp::SignatureInformation {
9641 label: "fn sample(param1: u8, param2: u8)".to_string(),
9642 documentation: None,
9643 parameters: Some(vec![
9644 lsp::ParameterInformation {
9645 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
9646 documentation: None,
9647 },
9648 lsp::ParameterInformation {
9649 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
9650 documentation: None,
9651 },
9652 ]),
9653 active_parameter: None,
9654 }],
9655 active_signature: Some(0),
9656 active_parameter: Some(0),
9657 };
9658 handle_signature_help_request(&mut cx, mocked_response).await;
9659
9660 cx.condition(|editor, _| editor.signature_help_state.is_shown())
9661 .await;
9662
9663 cx.editor(|editor, _, _| {
9664 let signature_help_state = editor.signature_help_state.popover().cloned();
9665 assert!(signature_help_state.is_some());
9666 assert_eq!(
9667 signature_help_state.unwrap().label,
9668 "param1: u8, param2: u8"
9669 );
9670 });
9671
9672 // When exiting outside from inside the brackets, `signature_help` is closed.
9673 cx.set_state(indoc! {"
9674 fn main() {
9675 sample(ˇ);
9676 }
9677
9678 fn sample(param1: u8, param2: u8) {}
9679 "});
9680
9681 cx.update_editor(|editor, window, cx| {
9682 editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
9683 });
9684
9685 let mocked_response = lsp::SignatureHelp {
9686 signatures: Vec::new(),
9687 active_signature: None,
9688 active_parameter: None,
9689 };
9690 handle_signature_help_request(&mut cx, mocked_response).await;
9691
9692 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
9693 .await;
9694
9695 cx.editor(|editor, _, _| {
9696 assert!(!editor.signature_help_state.is_shown());
9697 });
9698
9699 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
9700 cx.set_state(indoc! {"
9701 fn main() {
9702 sample(ˇ);
9703 }
9704
9705 fn sample(param1: u8, param2: u8) {}
9706 "});
9707
9708 let mocked_response = lsp::SignatureHelp {
9709 signatures: vec![lsp::SignatureInformation {
9710 label: "fn sample(param1: u8, param2: u8)".to_string(),
9711 documentation: None,
9712 parameters: Some(vec![
9713 lsp::ParameterInformation {
9714 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
9715 documentation: None,
9716 },
9717 lsp::ParameterInformation {
9718 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
9719 documentation: None,
9720 },
9721 ]),
9722 active_parameter: None,
9723 }],
9724 active_signature: Some(0),
9725 active_parameter: Some(0),
9726 };
9727 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
9728 cx.condition(|editor, _| editor.signature_help_state.is_shown())
9729 .await;
9730 cx.editor(|editor, _, _| {
9731 assert!(editor.signature_help_state.is_shown());
9732 });
9733
9734 // Restore the popover with more parameter input
9735 cx.set_state(indoc! {"
9736 fn main() {
9737 sample(param1, param2ˇ);
9738 }
9739
9740 fn sample(param1: u8, param2: u8) {}
9741 "});
9742
9743 let mocked_response = lsp::SignatureHelp {
9744 signatures: vec![lsp::SignatureInformation {
9745 label: "fn sample(param1: u8, param2: u8)".to_string(),
9746 documentation: None,
9747 parameters: Some(vec![
9748 lsp::ParameterInformation {
9749 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
9750 documentation: None,
9751 },
9752 lsp::ParameterInformation {
9753 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
9754 documentation: None,
9755 },
9756 ]),
9757 active_parameter: None,
9758 }],
9759 active_signature: Some(0),
9760 active_parameter: Some(1),
9761 };
9762 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
9763 cx.condition(|editor, _| editor.signature_help_state.is_shown())
9764 .await;
9765
9766 // When selecting a range, the popover is gone.
9767 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
9768 cx.update_editor(|editor, window, cx| {
9769 editor.change_selections(None, window, cx, |s| {
9770 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
9771 })
9772 });
9773 cx.assert_editor_state(indoc! {"
9774 fn main() {
9775 sample(param1, «ˇparam2»);
9776 }
9777
9778 fn sample(param1: u8, param2: u8) {}
9779 "});
9780 cx.editor(|editor, _, _| {
9781 assert!(!editor.signature_help_state.is_shown());
9782 });
9783
9784 // When unselecting again, the popover is back if within the brackets.
9785 cx.update_editor(|editor, window, cx| {
9786 editor.change_selections(None, window, cx, |s| {
9787 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
9788 })
9789 });
9790 cx.assert_editor_state(indoc! {"
9791 fn main() {
9792 sample(param1, ˇparam2);
9793 }
9794
9795 fn sample(param1: u8, param2: u8) {}
9796 "});
9797 handle_signature_help_request(&mut cx, mocked_response).await;
9798 cx.condition(|editor, _| editor.signature_help_state.is_shown())
9799 .await;
9800 cx.editor(|editor, _, _| {
9801 assert!(editor.signature_help_state.is_shown());
9802 });
9803
9804 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
9805 cx.update_editor(|editor, window, cx| {
9806 editor.change_selections(None, window, cx, |s| {
9807 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
9808 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
9809 })
9810 });
9811 cx.assert_editor_state(indoc! {"
9812 fn main() {
9813 sample(param1, ˇparam2);
9814 }
9815
9816 fn sample(param1: u8, param2: u8) {}
9817 "});
9818
9819 let mocked_response = lsp::SignatureHelp {
9820 signatures: vec![lsp::SignatureInformation {
9821 label: "fn sample(param1: u8, param2: u8)".to_string(),
9822 documentation: None,
9823 parameters: Some(vec![
9824 lsp::ParameterInformation {
9825 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
9826 documentation: None,
9827 },
9828 lsp::ParameterInformation {
9829 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
9830 documentation: None,
9831 },
9832 ]),
9833 active_parameter: None,
9834 }],
9835 active_signature: Some(0),
9836 active_parameter: Some(1),
9837 };
9838 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
9839 cx.condition(|editor, _| editor.signature_help_state.is_shown())
9840 .await;
9841 cx.update_editor(|editor, _, cx| {
9842 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
9843 });
9844 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
9845 .await;
9846 cx.update_editor(|editor, window, cx| {
9847 editor.change_selections(None, window, cx, |s| {
9848 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
9849 })
9850 });
9851 cx.assert_editor_state(indoc! {"
9852 fn main() {
9853 sample(param1, «ˇparam2»);
9854 }
9855
9856 fn sample(param1: u8, param2: u8) {}
9857 "});
9858 cx.update_editor(|editor, window, cx| {
9859 editor.change_selections(None, window, cx, |s| {
9860 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
9861 })
9862 });
9863 cx.assert_editor_state(indoc! {"
9864 fn main() {
9865 sample(param1, ˇparam2);
9866 }
9867
9868 fn sample(param1: u8, param2: u8) {}
9869 "});
9870 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
9871 .await;
9872}
9873
9874#[gpui::test]
9875async fn test_completion_mode(cx: &mut TestAppContext) {
9876 init_test(cx, |_| {});
9877 let mut cx = EditorLspTestContext::new_rust(
9878 lsp::ServerCapabilities {
9879 completion_provider: Some(lsp::CompletionOptions {
9880 resolve_provider: Some(true),
9881 ..Default::default()
9882 }),
9883 ..Default::default()
9884 },
9885 cx,
9886 )
9887 .await;
9888
9889 struct Run {
9890 run_description: &'static str,
9891 initial_state: String,
9892 buffer_marked_text: String,
9893 completion_text: &'static str,
9894 expected_with_insert_mode: String,
9895 expected_with_replace_mode: String,
9896 expected_with_replace_subsequence_mode: String,
9897 expected_with_replace_suffix_mode: String,
9898 }
9899
9900 let runs = [
9901 Run {
9902 run_description: "Start of word matches completion text",
9903 initial_state: "before ediˇ after".into(),
9904 buffer_marked_text: "before <edi|> after".into(),
9905 completion_text: "editor",
9906 expected_with_insert_mode: "before editorˇ after".into(),
9907 expected_with_replace_mode: "before editorˇ after".into(),
9908 expected_with_replace_subsequence_mode: "before editorˇ after".into(),
9909 expected_with_replace_suffix_mode: "before editorˇ after".into(),
9910 },
9911 Run {
9912 run_description: "Accept same text at the middle of the word",
9913 initial_state: "before ediˇtor after".into(),
9914 buffer_marked_text: "before <edi|tor> after".into(),
9915 completion_text: "editor",
9916 expected_with_insert_mode: "before editorˇtor after".into(),
9917 expected_with_replace_mode: "before editorˇ after".into(),
9918 expected_with_replace_subsequence_mode: "before editorˇ after".into(),
9919 expected_with_replace_suffix_mode: "before editorˇ after".into(),
9920 },
9921 Run {
9922 run_description: "End of word matches completion text -- cursor at end",
9923 initial_state: "before torˇ after".into(),
9924 buffer_marked_text: "before <tor|> after".into(),
9925 completion_text: "editor",
9926 expected_with_insert_mode: "before editorˇ after".into(),
9927 expected_with_replace_mode: "before editorˇ after".into(),
9928 expected_with_replace_subsequence_mode: "before editorˇ after".into(),
9929 expected_with_replace_suffix_mode: "before editorˇ after".into(),
9930 },
9931 Run {
9932 run_description: "End of word matches completion text -- cursor at start",
9933 initial_state: "before ˇtor after".into(),
9934 buffer_marked_text: "before <|tor> after".into(),
9935 completion_text: "editor",
9936 expected_with_insert_mode: "before editorˇtor after".into(),
9937 expected_with_replace_mode: "before editorˇ after".into(),
9938 expected_with_replace_subsequence_mode: "before editorˇ after".into(),
9939 expected_with_replace_suffix_mode: "before editorˇ after".into(),
9940 },
9941 Run {
9942 run_description: "Prepend text containing whitespace",
9943 initial_state: "pˇfield: bool".into(),
9944 buffer_marked_text: "<p|field>: bool".into(),
9945 completion_text: "pub ",
9946 expected_with_insert_mode: "pub ˇfield: bool".into(),
9947 expected_with_replace_mode: "pub ˇ: bool".into(),
9948 expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
9949 expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
9950 },
9951 Run {
9952 run_description: "Add element to start of list",
9953 initial_state: "[element_ˇelement_2]".into(),
9954 buffer_marked_text: "[<element_|element_2>]".into(),
9955 completion_text: "element_1",
9956 expected_with_insert_mode: "[element_1ˇelement_2]".into(),
9957 expected_with_replace_mode: "[element_1ˇ]".into(),
9958 expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
9959 expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
9960 },
9961 Run {
9962 run_description: "Add element to start of list -- first and second elements are equal",
9963 initial_state: "[elˇelement]".into(),
9964 buffer_marked_text: "[<el|element>]".into(),
9965 completion_text: "element",
9966 expected_with_insert_mode: "[elementˇelement]".into(),
9967 expected_with_replace_mode: "[elementˇ]".into(),
9968 expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
9969 expected_with_replace_suffix_mode: "[elementˇ]".into(),
9970 },
9971 Run {
9972 run_description: "Ends with matching suffix",
9973 initial_state: "SubˇError".into(),
9974 buffer_marked_text: "<Sub|Error>".into(),
9975 completion_text: "SubscriptionError",
9976 expected_with_insert_mode: "SubscriptionErrorˇError".into(),
9977 expected_with_replace_mode: "SubscriptionErrorˇ".into(),
9978 expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
9979 expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
9980 },
9981 Run {
9982 run_description: "Suffix is a subsequence -- contiguous",
9983 initial_state: "SubˇErr".into(),
9984 buffer_marked_text: "<Sub|Err>".into(),
9985 completion_text: "SubscriptionError",
9986 expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
9987 expected_with_replace_mode: "SubscriptionErrorˇ".into(),
9988 expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
9989 expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
9990 },
9991 Run {
9992 run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
9993 initial_state: "Suˇscrirr".into(),
9994 buffer_marked_text: "<Su|scrirr>".into(),
9995 completion_text: "SubscriptionError",
9996 expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
9997 expected_with_replace_mode: "SubscriptionErrorˇ".into(),
9998 expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
9999 expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
10000 },
10001 Run {
10002 run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
10003 initial_state: "foo(indˇix)".into(),
10004 buffer_marked_text: "foo(<ind|ix>)".into(),
10005 completion_text: "node_index",
10006 expected_with_insert_mode: "foo(node_indexˇix)".into(),
10007 expected_with_replace_mode: "foo(node_indexˇ)".into(),
10008 expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
10009 expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
10010 },
10011 ];
10012
10013 for run in runs {
10014 let run_variations = [
10015 (LspInsertMode::Insert, run.expected_with_insert_mode),
10016 (LspInsertMode::Replace, run.expected_with_replace_mode),
10017 (
10018 LspInsertMode::ReplaceSubsequence,
10019 run.expected_with_replace_subsequence_mode,
10020 ),
10021 (
10022 LspInsertMode::ReplaceSuffix,
10023 run.expected_with_replace_suffix_mode,
10024 ),
10025 ];
10026
10027 for (lsp_insert_mode, expected_text) in run_variations {
10028 eprintln!(
10029 "run = {:?}, mode = {lsp_insert_mode:.?}",
10030 run.run_description,
10031 );
10032
10033 update_test_language_settings(&mut cx, |settings| {
10034 settings.defaults.completions = Some(CompletionSettings {
10035 lsp_insert_mode,
10036 words: WordsCompletionMode::Disabled,
10037 lsp: true,
10038 lsp_fetch_timeout_ms: 0,
10039 });
10040 });
10041
10042 cx.set_state(&run.initial_state);
10043 cx.update_editor(|editor, window, cx| {
10044 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10045 });
10046
10047 let counter = Arc::new(AtomicUsize::new(0));
10048 handle_completion_request_with_insert_and_replace(
10049 &mut cx,
10050 &run.buffer_marked_text,
10051 vec![run.completion_text],
10052 counter.clone(),
10053 )
10054 .await;
10055 cx.condition(|editor, _| editor.context_menu_visible())
10056 .await;
10057 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
10058
10059 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10060 editor
10061 .confirm_completion(&ConfirmCompletion::default(), window, cx)
10062 .unwrap()
10063 });
10064 cx.assert_editor_state(&expected_text);
10065 handle_resolve_completion_request(&mut cx, None).await;
10066 apply_additional_edits.await.unwrap();
10067 }
10068 }
10069}
10070
10071#[gpui::test]
10072async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
10073 init_test(cx, |_| {});
10074 let mut cx = EditorLspTestContext::new_rust(
10075 lsp::ServerCapabilities {
10076 completion_provider: Some(lsp::CompletionOptions {
10077 resolve_provider: Some(true),
10078 ..Default::default()
10079 }),
10080 ..Default::default()
10081 },
10082 cx,
10083 )
10084 .await;
10085
10086 let initial_state = "SubˇError";
10087 let buffer_marked_text = "<Sub|Error>";
10088 let completion_text = "SubscriptionError";
10089 let expected_with_insert_mode = "SubscriptionErrorˇError";
10090 let expected_with_replace_mode = "SubscriptionErrorˇ";
10091
10092 update_test_language_settings(&mut cx, |settings| {
10093 settings.defaults.completions = Some(CompletionSettings {
10094 words: WordsCompletionMode::Disabled,
10095 // set the opposite here to ensure that the action is overriding the default behavior
10096 lsp_insert_mode: LspInsertMode::Insert,
10097 lsp: true,
10098 lsp_fetch_timeout_ms: 0,
10099 });
10100 });
10101
10102 cx.set_state(initial_state);
10103 cx.update_editor(|editor, window, cx| {
10104 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10105 });
10106
10107 let counter = Arc::new(AtomicUsize::new(0));
10108 handle_completion_request_with_insert_and_replace(
10109 &mut cx,
10110 &buffer_marked_text,
10111 vec![completion_text],
10112 counter.clone(),
10113 )
10114 .await;
10115 cx.condition(|editor, _| editor.context_menu_visible())
10116 .await;
10117 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
10118
10119 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10120 editor
10121 .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10122 .unwrap()
10123 });
10124 cx.assert_editor_state(&expected_with_replace_mode);
10125 handle_resolve_completion_request(&mut cx, None).await;
10126 apply_additional_edits.await.unwrap();
10127
10128 update_test_language_settings(&mut cx, |settings| {
10129 settings.defaults.completions = Some(CompletionSettings {
10130 words: WordsCompletionMode::Disabled,
10131 // set the opposite here to ensure that the action is overriding the default behavior
10132 lsp_insert_mode: LspInsertMode::Replace,
10133 lsp: true,
10134 lsp_fetch_timeout_ms: 0,
10135 });
10136 });
10137
10138 cx.set_state(initial_state);
10139 cx.update_editor(|editor, window, cx| {
10140 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10141 });
10142 handle_completion_request_with_insert_and_replace(
10143 &mut cx,
10144 &buffer_marked_text,
10145 vec![completion_text],
10146 counter.clone(),
10147 )
10148 .await;
10149 cx.condition(|editor, _| editor.context_menu_visible())
10150 .await;
10151 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
10152
10153 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10154 editor
10155 .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
10156 .unwrap()
10157 });
10158 cx.assert_editor_state(&expected_with_insert_mode);
10159 handle_resolve_completion_request(&mut cx, None).await;
10160 apply_additional_edits.await.unwrap();
10161}
10162
10163#[gpui::test]
10164async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
10165 init_test(cx, |_| {});
10166 let mut cx = EditorLspTestContext::new_rust(
10167 lsp::ServerCapabilities {
10168 completion_provider: Some(lsp::CompletionOptions {
10169 resolve_provider: Some(true),
10170 ..Default::default()
10171 }),
10172 ..Default::default()
10173 },
10174 cx,
10175 )
10176 .await;
10177
10178 // scenario: surrounding text matches completion text
10179 let completion_text = "to_offset";
10180 let initial_state = indoc! {"
10181 1. buf.to_offˇsuffix
10182 2. buf.to_offˇsuf
10183 3. buf.to_offˇfix
10184 4. buf.to_offˇ
10185 5. into_offˇensive
10186 6. ˇsuffix
10187 7. let ˇ //
10188 8. aaˇzz
10189 9. buf.to_off«zzzzzˇ»suffix
10190 10. buf.«ˇzzzzz»suffix
10191 11. to_off«ˇzzzzz»
10192
10193 buf.to_offˇsuffix // newest cursor
10194 "};
10195 let completion_marked_buffer = indoc! {"
10196 1. buf.to_offsuffix
10197 2. buf.to_offsuf
10198 3. buf.to_offfix
10199 4. buf.to_off
10200 5. into_offensive
10201 6. suffix
10202 7. let //
10203 8. aazz
10204 9. buf.to_offzzzzzsuffix
10205 10. buf.zzzzzsuffix
10206 11. to_offzzzzz
10207
10208 buf.<to_off|suffix> // newest cursor
10209 "};
10210 let expected = indoc! {"
10211 1. buf.to_offsetˇ
10212 2. buf.to_offsetˇsuf
10213 3. buf.to_offsetˇfix
10214 4. buf.to_offsetˇ
10215 5. into_offsetˇensive
10216 6. to_offsetˇsuffix
10217 7. let to_offsetˇ //
10218 8. aato_offsetˇzz
10219 9. buf.to_offsetˇ
10220 10. buf.to_offsetˇsuffix
10221 11. to_offsetˇ
10222
10223 buf.to_offsetˇ // newest cursor
10224 "};
10225 cx.set_state(initial_state);
10226 cx.update_editor(|editor, window, cx| {
10227 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10228 });
10229 handle_completion_request_with_insert_and_replace(
10230 &mut cx,
10231 completion_marked_buffer,
10232 vec![completion_text],
10233 Arc::new(AtomicUsize::new(0)),
10234 )
10235 .await;
10236 cx.condition(|editor, _| editor.context_menu_visible())
10237 .await;
10238 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10239 editor
10240 .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10241 .unwrap()
10242 });
10243 cx.assert_editor_state(expected);
10244 handle_resolve_completion_request(&mut cx, None).await;
10245 apply_additional_edits.await.unwrap();
10246
10247 // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
10248 let completion_text = "foo_and_bar";
10249 let initial_state = indoc! {"
10250 1. ooanbˇ
10251 2. zooanbˇ
10252 3. ooanbˇz
10253 4. zooanbˇz
10254 5. ooanˇ
10255 6. oanbˇ
10256
10257 ooanbˇ
10258 "};
10259 let completion_marked_buffer = indoc! {"
10260 1. ooanb
10261 2. zooanb
10262 3. ooanbz
10263 4. zooanbz
10264 5. ooan
10265 6. oanb
10266
10267 <ooanb|>
10268 "};
10269 let expected = indoc! {"
10270 1. foo_and_barˇ
10271 2. zfoo_and_barˇ
10272 3. foo_and_barˇz
10273 4. zfoo_and_barˇz
10274 5. ooanfoo_and_barˇ
10275 6. oanbfoo_and_barˇ
10276
10277 foo_and_barˇ
10278 "};
10279 cx.set_state(initial_state);
10280 cx.update_editor(|editor, window, cx| {
10281 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10282 });
10283 handle_completion_request_with_insert_and_replace(
10284 &mut cx,
10285 completion_marked_buffer,
10286 vec![completion_text],
10287 Arc::new(AtomicUsize::new(0)),
10288 )
10289 .await;
10290 cx.condition(|editor, _| editor.context_menu_visible())
10291 .await;
10292 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10293 editor
10294 .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10295 .unwrap()
10296 });
10297 cx.assert_editor_state(expected);
10298 handle_resolve_completion_request(&mut cx, None).await;
10299 apply_additional_edits.await.unwrap();
10300
10301 // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
10302 // (expects the same as if it was inserted at the end)
10303 let completion_text = "foo_and_bar";
10304 let initial_state = indoc! {"
10305 1. ooˇanb
10306 2. zooˇanb
10307 3. ooˇanbz
10308 4. zooˇanbz
10309
10310 ooˇanb
10311 "};
10312 let completion_marked_buffer = indoc! {"
10313 1. ooanb
10314 2. zooanb
10315 3. ooanbz
10316 4. zooanbz
10317
10318 <oo|anb>
10319 "};
10320 let expected = indoc! {"
10321 1. foo_and_barˇ
10322 2. zfoo_and_barˇ
10323 3. foo_and_barˇz
10324 4. zfoo_and_barˇz
10325
10326 foo_and_barˇ
10327 "};
10328 cx.set_state(initial_state);
10329 cx.update_editor(|editor, window, cx| {
10330 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10331 });
10332 handle_completion_request_with_insert_and_replace(
10333 &mut cx,
10334 completion_marked_buffer,
10335 vec![completion_text],
10336 Arc::new(AtomicUsize::new(0)),
10337 )
10338 .await;
10339 cx.condition(|editor, _| editor.context_menu_visible())
10340 .await;
10341 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10342 editor
10343 .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10344 .unwrap()
10345 });
10346 cx.assert_editor_state(expected);
10347 handle_resolve_completion_request(&mut cx, None).await;
10348 apply_additional_edits.await.unwrap();
10349}
10350
10351// This used to crash
10352#[gpui::test]
10353async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
10354 init_test(cx, |_| {});
10355
10356 let buffer_text = indoc! {"
10357 fn main() {
10358 10.satu;
10359
10360 //
10361 // separate cursors so they open in different excerpts (manually reproducible)
10362 //
10363
10364 10.satu20;
10365 }
10366 "};
10367 let multibuffer_text_with_selections = indoc! {"
10368 fn main() {
10369 10.satuˇ;
10370
10371 //
10372
10373 //
10374
10375 10.satuˇ20;
10376 }
10377 "};
10378 let expected_multibuffer = indoc! {"
10379 fn main() {
10380 10.saturating_sub()ˇ;
10381
10382 //
10383
10384 //
10385
10386 10.saturating_sub()ˇ;
10387 }
10388 "};
10389
10390 let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
10391 let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
10392
10393 let fs = FakeFs::new(cx.executor());
10394 fs.insert_tree(
10395 path!("/a"),
10396 json!({
10397 "main.rs": buffer_text,
10398 }),
10399 )
10400 .await;
10401
10402 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
10403 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10404 language_registry.add(rust_lang());
10405 let mut fake_servers = language_registry.register_fake_lsp(
10406 "Rust",
10407 FakeLspAdapter {
10408 capabilities: lsp::ServerCapabilities {
10409 completion_provider: Some(lsp::CompletionOptions {
10410 resolve_provider: None,
10411 ..lsp::CompletionOptions::default()
10412 }),
10413 ..lsp::ServerCapabilities::default()
10414 },
10415 ..FakeLspAdapter::default()
10416 },
10417 );
10418 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
10419 let cx = &mut VisualTestContext::from_window(*workspace, cx);
10420 let buffer = project
10421 .update(cx, |project, cx| {
10422 project.open_local_buffer(path!("/a/main.rs"), cx)
10423 })
10424 .await
10425 .unwrap();
10426
10427 let multi_buffer = cx.new(|cx| {
10428 let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
10429 multi_buffer.push_excerpts(
10430 buffer.clone(),
10431 [ExcerptRange::new(0..first_excerpt_end)],
10432 cx,
10433 );
10434 multi_buffer.push_excerpts(
10435 buffer.clone(),
10436 [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
10437 cx,
10438 );
10439 multi_buffer
10440 });
10441
10442 let editor = workspace
10443 .update(cx, |_, window, cx| {
10444 cx.new(|cx| {
10445 Editor::new(
10446 EditorMode::Full {
10447 scale_ui_elements_with_buffer_font_size: false,
10448 show_active_line_background: false,
10449 sized_by_content: false,
10450 },
10451 multi_buffer.clone(),
10452 Some(project.clone()),
10453 window,
10454 cx,
10455 )
10456 })
10457 })
10458 .unwrap();
10459
10460 let pane = workspace
10461 .update(cx, |workspace, _, _| workspace.active_pane().clone())
10462 .unwrap();
10463 pane.update_in(cx, |pane, window, cx| {
10464 pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
10465 });
10466
10467 let fake_server = fake_servers.next().await.unwrap();
10468
10469 editor.update_in(cx, |editor, window, cx| {
10470 editor.change_selections(None, window, cx, |s| {
10471 s.select_ranges([
10472 Point::new(1, 11)..Point::new(1, 11),
10473 Point::new(7, 11)..Point::new(7, 11),
10474 ])
10475 });
10476
10477 assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
10478 });
10479
10480 editor.update_in(cx, |editor, window, cx| {
10481 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10482 });
10483
10484 fake_server
10485 .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
10486 let completion_item = lsp::CompletionItem {
10487 label: "saturating_sub()".into(),
10488 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
10489 lsp::InsertReplaceEdit {
10490 new_text: "saturating_sub()".to_owned(),
10491 insert: lsp::Range::new(
10492 lsp::Position::new(7, 7),
10493 lsp::Position::new(7, 11),
10494 ),
10495 replace: lsp::Range::new(
10496 lsp::Position::new(7, 7),
10497 lsp::Position::new(7, 13),
10498 ),
10499 },
10500 )),
10501 ..lsp::CompletionItem::default()
10502 };
10503
10504 Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
10505 })
10506 .next()
10507 .await
10508 .unwrap();
10509
10510 cx.condition(&editor, |editor, _| editor.context_menu_visible())
10511 .await;
10512
10513 editor
10514 .update_in(cx, |editor, window, cx| {
10515 editor
10516 .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10517 .unwrap()
10518 })
10519 .await
10520 .unwrap();
10521
10522 editor.update(cx, |editor, cx| {
10523 assert_text_with_selections(editor, expected_multibuffer, cx);
10524 })
10525}
10526
10527#[gpui::test]
10528async fn test_completion(cx: &mut TestAppContext) {
10529 init_test(cx, |_| {});
10530
10531 let mut cx = EditorLspTestContext::new_rust(
10532 lsp::ServerCapabilities {
10533 completion_provider: Some(lsp::CompletionOptions {
10534 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
10535 resolve_provider: Some(true),
10536 ..Default::default()
10537 }),
10538 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
10539 ..Default::default()
10540 },
10541 cx,
10542 )
10543 .await;
10544 let counter = Arc::new(AtomicUsize::new(0));
10545
10546 cx.set_state(indoc! {"
10547 oneˇ
10548 two
10549 three
10550 "});
10551 cx.simulate_keystroke(".");
10552 handle_completion_request(
10553 &mut cx,
10554 indoc! {"
10555 one.|<>
10556 two
10557 three
10558 "},
10559 vec!["first_completion", "second_completion"],
10560 counter.clone(),
10561 )
10562 .await;
10563 cx.condition(|editor, _| editor.context_menu_visible())
10564 .await;
10565 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
10566
10567 let _handler = handle_signature_help_request(
10568 &mut cx,
10569 lsp::SignatureHelp {
10570 signatures: vec![lsp::SignatureInformation {
10571 label: "test signature".to_string(),
10572 documentation: None,
10573 parameters: Some(vec![lsp::ParameterInformation {
10574 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
10575 documentation: None,
10576 }]),
10577 active_parameter: None,
10578 }],
10579 active_signature: None,
10580 active_parameter: None,
10581 },
10582 );
10583 cx.update_editor(|editor, window, cx| {
10584 assert!(
10585 !editor.signature_help_state.is_shown(),
10586 "No signature help was called for"
10587 );
10588 editor.show_signature_help(&ShowSignatureHelp, window, cx);
10589 });
10590 cx.run_until_parked();
10591 cx.update_editor(|editor, _, _| {
10592 assert!(
10593 !editor.signature_help_state.is_shown(),
10594 "No signature help should be shown when completions menu is open"
10595 );
10596 });
10597
10598 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10599 editor.context_menu_next(&Default::default(), window, cx);
10600 editor
10601 .confirm_completion(&ConfirmCompletion::default(), window, cx)
10602 .unwrap()
10603 });
10604 cx.assert_editor_state(indoc! {"
10605 one.second_completionˇ
10606 two
10607 three
10608 "});
10609
10610 handle_resolve_completion_request(
10611 &mut cx,
10612 Some(vec![
10613 (
10614 //This overlaps with the primary completion edit which is
10615 //misbehavior from the LSP spec, test that we filter it out
10616 indoc! {"
10617 one.second_ˇcompletion
10618 two
10619 threeˇ
10620 "},
10621 "overlapping additional edit",
10622 ),
10623 (
10624 indoc! {"
10625 one.second_completion
10626 two
10627 threeˇ
10628 "},
10629 "\nadditional edit",
10630 ),
10631 ]),
10632 )
10633 .await;
10634 apply_additional_edits.await.unwrap();
10635 cx.assert_editor_state(indoc! {"
10636 one.second_completionˇ
10637 two
10638 three
10639 additional edit
10640 "});
10641
10642 cx.set_state(indoc! {"
10643 one.second_completion
10644 twoˇ
10645 threeˇ
10646 additional edit
10647 "});
10648 cx.simulate_keystroke(" ");
10649 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
10650 cx.simulate_keystroke("s");
10651 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
10652
10653 cx.assert_editor_state(indoc! {"
10654 one.second_completion
10655 two sˇ
10656 three sˇ
10657 additional edit
10658 "});
10659 handle_completion_request(
10660 &mut cx,
10661 indoc! {"
10662 one.second_completion
10663 two s
10664 three <s|>
10665 additional edit
10666 "},
10667 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
10668 counter.clone(),
10669 )
10670 .await;
10671 cx.condition(|editor, _| editor.context_menu_visible())
10672 .await;
10673 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
10674
10675 cx.simulate_keystroke("i");
10676
10677 handle_completion_request(
10678 &mut cx,
10679 indoc! {"
10680 one.second_completion
10681 two si
10682 three <si|>
10683 additional edit
10684 "},
10685 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
10686 counter.clone(),
10687 )
10688 .await;
10689 cx.condition(|editor, _| editor.context_menu_visible())
10690 .await;
10691 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
10692
10693 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10694 editor
10695 .confirm_completion(&ConfirmCompletion::default(), window, cx)
10696 .unwrap()
10697 });
10698 cx.assert_editor_state(indoc! {"
10699 one.second_completion
10700 two sixth_completionˇ
10701 three sixth_completionˇ
10702 additional edit
10703 "});
10704
10705 apply_additional_edits.await.unwrap();
10706
10707 update_test_language_settings(&mut cx, |settings| {
10708 settings.defaults.show_completions_on_input = Some(false);
10709 });
10710 cx.set_state("editorˇ");
10711 cx.simulate_keystroke(".");
10712 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
10713 cx.simulate_keystrokes("c l o");
10714 cx.assert_editor_state("editor.cloˇ");
10715 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
10716 cx.update_editor(|editor, window, cx| {
10717 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10718 });
10719 handle_completion_request(
10720 &mut cx,
10721 "editor.<clo|>",
10722 vec!["close", "clobber"],
10723 counter.clone(),
10724 )
10725 .await;
10726 cx.condition(|editor, _| editor.context_menu_visible())
10727 .await;
10728 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
10729
10730 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10731 editor
10732 .confirm_completion(&ConfirmCompletion::default(), window, cx)
10733 .unwrap()
10734 });
10735 cx.assert_editor_state("editor.closeˇ");
10736 handle_resolve_completion_request(&mut cx, None).await;
10737 apply_additional_edits.await.unwrap();
10738}
10739
10740#[gpui::test]
10741async fn test_word_completion(cx: &mut TestAppContext) {
10742 let lsp_fetch_timeout_ms = 10;
10743 init_test(cx, |language_settings| {
10744 language_settings.defaults.completions = Some(CompletionSettings {
10745 words: WordsCompletionMode::Fallback,
10746 lsp: true,
10747 lsp_fetch_timeout_ms: 10,
10748 lsp_insert_mode: LspInsertMode::Insert,
10749 });
10750 });
10751
10752 let mut cx = EditorLspTestContext::new_rust(
10753 lsp::ServerCapabilities {
10754 completion_provider: Some(lsp::CompletionOptions {
10755 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
10756 ..lsp::CompletionOptions::default()
10757 }),
10758 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
10759 ..lsp::ServerCapabilities::default()
10760 },
10761 cx,
10762 )
10763 .await;
10764
10765 let throttle_completions = Arc::new(AtomicBool::new(false));
10766
10767 let lsp_throttle_completions = throttle_completions.clone();
10768 let _completion_requests_handler =
10769 cx.lsp
10770 .server
10771 .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
10772 let lsp_throttle_completions = lsp_throttle_completions.clone();
10773 let cx = cx.clone();
10774 async move {
10775 if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
10776 cx.background_executor()
10777 .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
10778 .await;
10779 }
10780 Ok(Some(lsp::CompletionResponse::Array(vec![
10781 lsp::CompletionItem {
10782 label: "first".into(),
10783 ..lsp::CompletionItem::default()
10784 },
10785 lsp::CompletionItem {
10786 label: "last".into(),
10787 ..lsp::CompletionItem::default()
10788 },
10789 ])))
10790 }
10791 });
10792
10793 cx.set_state(indoc! {"
10794 oneˇ
10795 two
10796 three
10797 "});
10798 cx.simulate_keystroke(".");
10799 cx.executor().run_until_parked();
10800 cx.condition(|editor, _| editor.context_menu_visible())
10801 .await;
10802 cx.update_editor(|editor, window, cx| {
10803 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10804 {
10805 assert_eq!(
10806 completion_menu_entries(&menu),
10807 &["first", "last"],
10808 "When LSP server is fast to reply, no fallback word completions are used"
10809 );
10810 } else {
10811 panic!("expected completion menu to be open");
10812 }
10813 editor.cancel(&Cancel, window, cx);
10814 });
10815 cx.executor().run_until_parked();
10816 cx.condition(|editor, _| !editor.context_menu_visible())
10817 .await;
10818
10819 throttle_completions.store(true, atomic::Ordering::Release);
10820 cx.simulate_keystroke(".");
10821 cx.executor()
10822 .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
10823 cx.executor().run_until_parked();
10824 cx.condition(|editor, _| editor.context_menu_visible())
10825 .await;
10826 cx.update_editor(|editor, _, _| {
10827 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10828 {
10829 assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
10830 "When LSP server is slow, document words can be shown instead, if configured accordingly");
10831 } else {
10832 panic!("expected completion menu to be open");
10833 }
10834 });
10835}
10836
10837#[gpui::test]
10838async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
10839 init_test(cx, |language_settings| {
10840 language_settings.defaults.completions = Some(CompletionSettings {
10841 words: WordsCompletionMode::Enabled,
10842 lsp: true,
10843 lsp_fetch_timeout_ms: 0,
10844 lsp_insert_mode: LspInsertMode::Insert,
10845 });
10846 });
10847
10848 let mut cx = EditorLspTestContext::new_rust(
10849 lsp::ServerCapabilities {
10850 completion_provider: Some(lsp::CompletionOptions {
10851 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
10852 ..lsp::CompletionOptions::default()
10853 }),
10854 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
10855 ..lsp::ServerCapabilities::default()
10856 },
10857 cx,
10858 )
10859 .await;
10860
10861 let _completion_requests_handler =
10862 cx.lsp
10863 .server
10864 .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
10865 Ok(Some(lsp::CompletionResponse::Array(vec![
10866 lsp::CompletionItem {
10867 label: "first".into(),
10868 ..lsp::CompletionItem::default()
10869 },
10870 lsp::CompletionItem {
10871 label: "last".into(),
10872 ..lsp::CompletionItem::default()
10873 },
10874 ])))
10875 });
10876
10877 cx.set_state(indoc! {"ˇ
10878 first
10879 last
10880 second
10881 "});
10882 cx.simulate_keystroke(".");
10883 cx.executor().run_until_parked();
10884 cx.condition(|editor, _| editor.context_menu_visible())
10885 .await;
10886 cx.update_editor(|editor, _, _| {
10887 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10888 {
10889 assert_eq!(
10890 completion_menu_entries(&menu),
10891 &["first", "last", "second"],
10892 "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
10893 );
10894 } else {
10895 panic!("expected completion menu to be open");
10896 }
10897 });
10898}
10899
10900#[gpui::test]
10901async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
10902 init_test(cx, |language_settings| {
10903 language_settings.defaults.completions = Some(CompletionSettings {
10904 words: WordsCompletionMode::Disabled,
10905 lsp: true,
10906 lsp_fetch_timeout_ms: 0,
10907 lsp_insert_mode: LspInsertMode::Insert,
10908 });
10909 });
10910
10911 let mut cx = EditorLspTestContext::new_rust(
10912 lsp::ServerCapabilities {
10913 completion_provider: Some(lsp::CompletionOptions {
10914 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
10915 ..lsp::CompletionOptions::default()
10916 }),
10917 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
10918 ..lsp::ServerCapabilities::default()
10919 },
10920 cx,
10921 )
10922 .await;
10923
10924 let _completion_requests_handler =
10925 cx.lsp
10926 .server
10927 .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
10928 panic!("LSP completions should not be queried when dealing with word completions")
10929 });
10930
10931 cx.set_state(indoc! {"ˇ
10932 first
10933 last
10934 second
10935 "});
10936 cx.update_editor(|editor, window, cx| {
10937 editor.show_word_completions(&ShowWordCompletions, window, cx);
10938 });
10939 cx.executor().run_until_parked();
10940 cx.condition(|editor, _| editor.context_menu_visible())
10941 .await;
10942 cx.update_editor(|editor, _, _| {
10943 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10944 {
10945 assert_eq!(
10946 completion_menu_entries(&menu),
10947 &["first", "last", "second"],
10948 "`ShowWordCompletions` action should show word completions"
10949 );
10950 } else {
10951 panic!("expected completion menu to be open");
10952 }
10953 });
10954
10955 cx.simulate_keystroke("l");
10956 cx.executor().run_until_parked();
10957 cx.condition(|editor, _| editor.context_menu_visible())
10958 .await;
10959 cx.update_editor(|editor, _, _| {
10960 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10961 {
10962 assert_eq!(
10963 completion_menu_entries(&menu),
10964 &["last"],
10965 "After showing word completions, further editing should filter them and not query the LSP"
10966 );
10967 } else {
10968 panic!("expected completion menu to be open");
10969 }
10970 });
10971}
10972
10973#[gpui::test]
10974async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
10975 init_test(cx, |language_settings| {
10976 language_settings.defaults.completions = Some(CompletionSettings {
10977 words: WordsCompletionMode::Fallback,
10978 lsp: false,
10979 lsp_fetch_timeout_ms: 0,
10980 lsp_insert_mode: LspInsertMode::Insert,
10981 });
10982 });
10983
10984 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10985
10986 cx.set_state(indoc! {"ˇ
10987 0_usize
10988 let
10989 33
10990 4.5f32
10991 "});
10992 cx.update_editor(|editor, window, cx| {
10993 editor.show_completions(&ShowCompletions::default(), window, cx);
10994 });
10995 cx.executor().run_until_parked();
10996 cx.condition(|editor, _| editor.context_menu_visible())
10997 .await;
10998 cx.update_editor(|editor, window, cx| {
10999 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11000 {
11001 assert_eq!(
11002 completion_menu_entries(&menu),
11003 &["let"],
11004 "With no digits in the completion query, no digits should be in the word completions"
11005 );
11006 } else {
11007 panic!("expected completion menu to be open");
11008 }
11009 editor.cancel(&Cancel, window, cx);
11010 });
11011
11012 cx.set_state(indoc! {"3ˇ
11013 0_usize
11014 let
11015 3
11016 33.35f32
11017 "});
11018 cx.update_editor(|editor, window, cx| {
11019 editor.show_completions(&ShowCompletions::default(), window, cx);
11020 });
11021 cx.executor().run_until_parked();
11022 cx.condition(|editor, _| editor.context_menu_visible())
11023 .await;
11024 cx.update_editor(|editor, _, _| {
11025 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11026 {
11027 assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
11028 return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
11029 } else {
11030 panic!("expected completion menu to be open");
11031 }
11032 });
11033}
11034
11035fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
11036 let position = || lsp::Position {
11037 line: params.text_document_position.position.line,
11038 character: params.text_document_position.position.character,
11039 };
11040 Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11041 range: lsp::Range {
11042 start: position(),
11043 end: position(),
11044 },
11045 new_text: text.to_string(),
11046 }))
11047}
11048
11049#[gpui::test]
11050async fn test_multiline_completion(cx: &mut TestAppContext) {
11051 init_test(cx, |_| {});
11052
11053 let fs = FakeFs::new(cx.executor());
11054 fs.insert_tree(
11055 path!("/a"),
11056 json!({
11057 "main.ts": "a",
11058 }),
11059 )
11060 .await;
11061
11062 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11063 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11064 let typescript_language = Arc::new(Language::new(
11065 LanguageConfig {
11066 name: "TypeScript".into(),
11067 matcher: LanguageMatcher {
11068 path_suffixes: vec!["ts".to_string()],
11069 ..LanguageMatcher::default()
11070 },
11071 line_comments: vec!["// ".into()],
11072 ..LanguageConfig::default()
11073 },
11074 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
11075 ));
11076 language_registry.add(typescript_language.clone());
11077 let mut fake_servers = language_registry.register_fake_lsp(
11078 "TypeScript",
11079 FakeLspAdapter {
11080 capabilities: lsp::ServerCapabilities {
11081 completion_provider: Some(lsp::CompletionOptions {
11082 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11083 ..lsp::CompletionOptions::default()
11084 }),
11085 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11086 ..lsp::ServerCapabilities::default()
11087 },
11088 // Emulate vtsls label generation
11089 label_for_completion: Some(Box::new(|item, _| {
11090 let text = if let Some(description) = item
11091 .label_details
11092 .as_ref()
11093 .and_then(|label_details| label_details.description.as_ref())
11094 {
11095 format!("{} {}", item.label, description)
11096 } else if let Some(detail) = &item.detail {
11097 format!("{} {}", item.label, detail)
11098 } else {
11099 item.label.clone()
11100 };
11101 let len = text.len();
11102 Some(language::CodeLabel {
11103 text,
11104 runs: Vec::new(),
11105 filter_range: 0..len,
11106 })
11107 })),
11108 ..FakeLspAdapter::default()
11109 },
11110 );
11111 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11112 let cx = &mut VisualTestContext::from_window(*workspace, cx);
11113 let worktree_id = workspace
11114 .update(cx, |workspace, _window, cx| {
11115 workspace.project().update(cx, |project, cx| {
11116 project.worktrees(cx).next().unwrap().read(cx).id()
11117 })
11118 })
11119 .unwrap();
11120 let _buffer = project
11121 .update(cx, |project, cx| {
11122 project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
11123 })
11124 .await
11125 .unwrap();
11126 let editor = workspace
11127 .update(cx, |workspace, window, cx| {
11128 workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
11129 })
11130 .unwrap()
11131 .await
11132 .unwrap()
11133 .downcast::<Editor>()
11134 .unwrap();
11135 let fake_server = fake_servers.next().await.unwrap();
11136
11137 let multiline_label = "StickyHeaderExcerpt {\n excerpt,\n next_excerpt_controls_present,\n next_buffer_row,\n }: StickyHeaderExcerpt<'_>,";
11138 let multiline_label_2 = "a\nb\nc\n";
11139 let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
11140 let multiline_description = "d\ne\nf\n";
11141 let multiline_detail_2 = "g\nh\ni\n";
11142
11143 let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
11144 move |params, _| async move {
11145 Ok(Some(lsp::CompletionResponse::Array(vec![
11146 lsp::CompletionItem {
11147 label: multiline_label.to_string(),
11148 text_edit: gen_text_edit(¶ms, "new_text_1"),
11149 ..lsp::CompletionItem::default()
11150 },
11151 lsp::CompletionItem {
11152 label: "single line label 1".to_string(),
11153 detail: Some(multiline_detail.to_string()),
11154 text_edit: gen_text_edit(¶ms, "new_text_2"),
11155 ..lsp::CompletionItem::default()
11156 },
11157 lsp::CompletionItem {
11158 label: "single line label 2".to_string(),
11159 label_details: Some(lsp::CompletionItemLabelDetails {
11160 description: Some(multiline_description.to_string()),
11161 detail: None,
11162 }),
11163 text_edit: gen_text_edit(¶ms, "new_text_2"),
11164 ..lsp::CompletionItem::default()
11165 },
11166 lsp::CompletionItem {
11167 label: multiline_label_2.to_string(),
11168 detail: Some(multiline_detail_2.to_string()),
11169 text_edit: gen_text_edit(¶ms, "new_text_3"),
11170 ..lsp::CompletionItem::default()
11171 },
11172 lsp::CompletionItem {
11173 label: "Label with many spaces and \t but without newlines".to_string(),
11174 detail: Some(
11175 "Details with many spaces and \t but without newlines".to_string(),
11176 ),
11177 text_edit: gen_text_edit(¶ms, "new_text_4"),
11178 ..lsp::CompletionItem::default()
11179 },
11180 ])))
11181 },
11182 );
11183
11184 editor.update_in(cx, |editor, window, cx| {
11185 cx.focus_self(window);
11186 editor.move_to_end(&MoveToEnd, window, cx);
11187 editor.handle_input(".", window, cx);
11188 });
11189 cx.run_until_parked();
11190 completion_handle.next().await.unwrap();
11191
11192 editor.update(cx, |editor, _| {
11193 assert!(editor.context_menu_visible());
11194 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11195 {
11196 let completion_labels = menu
11197 .completions
11198 .borrow()
11199 .iter()
11200 .map(|c| c.label.text.clone())
11201 .collect::<Vec<_>>();
11202 assert_eq!(
11203 completion_labels,
11204 &[
11205 "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
11206 "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
11207 "single line label 2 d e f ",
11208 "a b c g h i ",
11209 "Label with many spaces and \t but without newlines Details with many spaces and \t but without newlines",
11210 ],
11211 "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
11212 );
11213
11214 for completion in menu
11215 .completions
11216 .borrow()
11217 .iter() {
11218 assert_eq!(
11219 completion.label.filter_range,
11220 0..completion.label.text.len(),
11221 "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
11222 );
11223 }
11224 } else {
11225 panic!("expected completion menu to be open");
11226 }
11227 });
11228}
11229
11230#[gpui::test]
11231async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
11232 init_test(cx, |_| {});
11233 let mut cx = EditorLspTestContext::new_rust(
11234 lsp::ServerCapabilities {
11235 completion_provider: Some(lsp::CompletionOptions {
11236 trigger_characters: Some(vec![".".to_string()]),
11237 ..Default::default()
11238 }),
11239 ..Default::default()
11240 },
11241 cx,
11242 )
11243 .await;
11244 cx.lsp
11245 .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
11246 Ok(Some(lsp::CompletionResponse::Array(vec![
11247 lsp::CompletionItem {
11248 label: "first".into(),
11249 ..Default::default()
11250 },
11251 lsp::CompletionItem {
11252 label: "last".into(),
11253 ..Default::default()
11254 },
11255 ])))
11256 });
11257 cx.set_state("variableˇ");
11258 cx.simulate_keystroke(".");
11259 cx.executor().run_until_parked();
11260
11261 cx.update_editor(|editor, _, _| {
11262 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11263 {
11264 assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
11265 } else {
11266 panic!("expected completion menu to be open");
11267 }
11268 });
11269
11270 cx.update_editor(|editor, window, cx| {
11271 editor.move_page_down(&MovePageDown::default(), window, cx);
11272 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11273 {
11274 assert!(
11275 menu.selected_item == 1,
11276 "expected PageDown to select the last item from the context menu"
11277 );
11278 } else {
11279 panic!("expected completion menu to stay open after PageDown");
11280 }
11281 });
11282
11283 cx.update_editor(|editor, window, cx| {
11284 editor.move_page_up(&MovePageUp::default(), window, cx);
11285 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11286 {
11287 assert!(
11288 menu.selected_item == 0,
11289 "expected PageUp to select the first item from the context menu"
11290 );
11291 } else {
11292 panic!("expected completion menu to stay open after PageUp");
11293 }
11294 });
11295}
11296
11297#[gpui::test]
11298async fn test_as_is_completions(cx: &mut TestAppContext) {
11299 init_test(cx, |_| {});
11300 let mut cx = EditorLspTestContext::new_rust(
11301 lsp::ServerCapabilities {
11302 completion_provider: Some(lsp::CompletionOptions {
11303 ..Default::default()
11304 }),
11305 ..Default::default()
11306 },
11307 cx,
11308 )
11309 .await;
11310 cx.lsp
11311 .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
11312 Ok(Some(lsp::CompletionResponse::Array(vec![
11313 lsp::CompletionItem {
11314 label: "unsafe".into(),
11315 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11316 range: lsp::Range {
11317 start: lsp::Position {
11318 line: 1,
11319 character: 2,
11320 },
11321 end: lsp::Position {
11322 line: 1,
11323 character: 3,
11324 },
11325 },
11326 new_text: "unsafe".to_string(),
11327 })),
11328 insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
11329 ..Default::default()
11330 },
11331 ])))
11332 });
11333 cx.set_state("fn a() {}\n nˇ");
11334 cx.executor().run_until_parked();
11335 cx.update_editor(|editor, window, cx| {
11336 editor.show_completions(
11337 &ShowCompletions {
11338 trigger: Some("\n".into()),
11339 },
11340 window,
11341 cx,
11342 );
11343 });
11344 cx.executor().run_until_parked();
11345
11346 cx.update_editor(|editor, window, cx| {
11347 editor.confirm_completion(&Default::default(), window, cx)
11348 });
11349 cx.executor().run_until_parked();
11350 cx.assert_editor_state("fn a() {}\n unsafeˇ");
11351}
11352
11353#[gpui::test]
11354async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
11355 init_test(cx, |_| {});
11356
11357 let mut cx = EditorLspTestContext::new_rust(
11358 lsp::ServerCapabilities {
11359 completion_provider: Some(lsp::CompletionOptions {
11360 trigger_characters: Some(vec![".".to_string()]),
11361 resolve_provider: Some(true),
11362 ..Default::default()
11363 }),
11364 ..Default::default()
11365 },
11366 cx,
11367 )
11368 .await;
11369
11370 cx.set_state("fn main() { let a = 2ˇ; }");
11371 cx.simulate_keystroke(".");
11372 let completion_item = lsp::CompletionItem {
11373 label: "Some".into(),
11374 kind: Some(lsp::CompletionItemKind::SNIPPET),
11375 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
11376 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
11377 kind: lsp::MarkupKind::Markdown,
11378 value: "```rust\nSome(2)\n```".to_string(),
11379 })),
11380 deprecated: Some(false),
11381 sort_text: Some("Some".to_string()),
11382 filter_text: Some("Some".to_string()),
11383 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
11384 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11385 range: lsp::Range {
11386 start: lsp::Position {
11387 line: 0,
11388 character: 22,
11389 },
11390 end: lsp::Position {
11391 line: 0,
11392 character: 22,
11393 },
11394 },
11395 new_text: "Some(2)".to_string(),
11396 })),
11397 additional_text_edits: Some(vec![lsp::TextEdit {
11398 range: lsp::Range {
11399 start: lsp::Position {
11400 line: 0,
11401 character: 20,
11402 },
11403 end: lsp::Position {
11404 line: 0,
11405 character: 22,
11406 },
11407 },
11408 new_text: "".to_string(),
11409 }]),
11410 ..Default::default()
11411 };
11412
11413 let closure_completion_item = completion_item.clone();
11414 let counter = Arc::new(AtomicUsize::new(0));
11415 let counter_clone = counter.clone();
11416 let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
11417 let task_completion_item = closure_completion_item.clone();
11418 counter_clone.fetch_add(1, atomic::Ordering::Release);
11419 async move {
11420 Ok(Some(lsp::CompletionResponse::Array(vec![
11421 task_completion_item,
11422 ])))
11423 }
11424 });
11425
11426 cx.condition(|editor, _| editor.context_menu_visible())
11427 .await;
11428 cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
11429 assert!(request.next().await.is_some());
11430 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11431
11432 cx.simulate_keystrokes("S o m");
11433 cx.condition(|editor, _| editor.context_menu_visible())
11434 .await;
11435 cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
11436 assert!(request.next().await.is_some());
11437 assert!(request.next().await.is_some());
11438 assert!(request.next().await.is_some());
11439 request.close();
11440 assert!(request.next().await.is_none());
11441 assert_eq!(
11442 counter.load(atomic::Ordering::Acquire),
11443 4,
11444 "With the completions menu open, only one LSP request should happen per input"
11445 );
11446}
11447
11448#[gpui::test]
11449async fn test_toggle_comment(cx: &mut TestAppContext) {
11450 init_test(cx, |_| {});
11451 let mut cx = EditorTestContext::new(cx).await;
11452 let language = Arc::new(Language::new(
11453 LanguageConfig {
11454 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
11455 ..Default::default()
11456 },
11457 Some(tree_sitter_rust::LANGUAGE.into()),
11458 ));
11459 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
11460
11461 // If multiple selections intersect a line, the line is only toggled once.
11462 cx.set_state(indoc! {"
11463 fn a() {
11464 «//b();
11465 ˇ»// «c();
11466 //ˇ» d();
11467 }
11468 "});
11469
11470 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
11471
11472 cx.assert_editor_state(indoc! {"
11473 fn a() {
11474 «b();
11475 c();
11476 ˇ» d();
11477 }
11478 "});
11479
11480 // The comment prefix is inserted at the same column for every line in a
11481 // selection.
11482 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
11483
11484 cx.assert_editor_state(indoc! {"
11485 fn a() {
11486 // «b();
11487 // c();
11488 ˇ»// d();
11489 }
11490 "});
11491
11492 // If a selection ends at the beginning of a line, that line is not toggled.
11493 cx.set_selections_state(indoc! {"
11494 fn a() {
11495 // b();
11496 «// c();
11497 ˇ» // d();
11498 }
11499 "});
11500
11501 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
11502
11503 cx.assert_editor_state(indoc! {"
11504 fn a() {
11505 // b();
11506 «c();
11507 ˇ» // d();
11508 }
11509 "});
11510
11511 // If a selection span a single line and is empty, the line is toggled.
11512 cx.set_state(indoc! {"
11513 fn a() {
11514 a();
11515 b();
11516 ˇ
11517 }
11518 "});
11519
11520 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
11521
11522 cx.assert_editor_state(indoc! {"
11523 fn a() {
11524 a();
11525 b();
11526 //•ˇ
11527 }
11528 "});
11529
11530 // If a selection span multiple lines, empty lines are not toggled.
11531 cx.set_state(indoc! {"
11532 fn a() {
11533 «a();
11534
11535 c();ˇ»
11536 }
11537 "});
11538
11539 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
11540
11541 cx.assert_editor_state(indoc! {"
11542 fn a() {
11543 // «a();
11544
11545 // c();ˇ»
11546 }
11547 "});
11548
11549 // If a selection includes multiple comment prefixes, all lines are uncommented.
11550 cx.set_state(indoc! {"
11551 fn a() {
11552 «// a();
11553 /// b();
11554 //! c();ˇ»
11555 }
11556 "});
11557
11558 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
11559
11560 cx.assert_editor_state(indoc! {"
11561 fn a() {
11562 «a();
11563 b();
11564 c();ˇ»
11565 }
11566 "});
11567}
11568
11569#[gpui::test]
11570async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
11571 init_test(cx, |_| {});
11572 let mut cx = EditorTestContext::new(cx).await;
11573 let language = Arc::new(Language::new(
11574 LanguageConfig {
11575 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
11576 ..Default::default()
11577 },
11578 Some(tree_sitter_rust::LANGUAGE.into()),
11579 ));
11580 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
11581
11582 let toggle_comments = &ToggleComments {
11583 advance_downwards: false,
11584 ignore_indent: true,
11585 };
11586
11587 // If multiple selections intersect a line, the line is only toggled once.
11588 cx.set_state(indoc! {"
11589 fn a() {
11590 // «b();
11591 // c();
11592 // ˇ» d();
11593 }
11594 "});
11595
11596 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
11597
11598 cx.assert_editor_state(indoc! {"
11599 fn a() {
11600 «b();
11601 c();
11602 ˇ» d();
11603 }
11604 "});
11605
11606 // The comment prefix is inserted at the beginning of each line
11607 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
11608
11609 cx.assert_editor_state(indoc! {"
11610 fn a() {
11611 // «b();
11612 // c();
11613 // ˇ» d();
11614 }
11615 "});
11616
11617 // If a selection ends at the beginning of a line, that line is not toggled.
11618 cx.set_selections_state(indoc! {"
11619 fn a() {
11620 // b();
11621 // «c();
11622 ˇ»// d();
11623 }
11624 "});
11625
11626 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
11627
11628 cx.assert_editor_state(indoc! {"
11629 fn a() {
11630 // b();
11631 «c();
11632 ˇ»// d();
11633 }
11634 "});
11635
11636 // If a selection span a single line and is empty, the line is toggled.
11637 cx.set_state(indoc! {"
11638 fn a() {
11639 a();
11640 b();
11641 ˇ
11642 }
11643 "});
11644
11645 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
11646
11647 cx.assert_editor_state(indoc! {"
11648 fn a() {
11649 a();
11650 b();
11651 //ˇ
11652 }
11653 "});
11654
11655 // If a selection span multiple lines, empty lines are not toggled.
11656 cx.set_state(indoc! {"
11657 fn a() {
11658 «a();
11659
11660 c();ˇ»
11661 }
11662 "});
11663
11664 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
11665
11666 cx.assert_editor_state(indoc! {"
11667 fn a() {
11668 // «a();
11669
11670 // c();ˇ»
11671 }
11672 "});
11673
11674 // If a selection includes multiple comment prefixes, all lines are uncommented.
11675 cx.set_state(indoc! {"
11676 fn a() {
11677 // «a();
11678 /// b();
11679 //! c();ˇ»
11680 }
11681 "});
11682
11683 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
11684
11685 cx.assert_editor_state(indoc! {"
11686 fn a() {
11687 «a();
11688 b();
11689 c();ˇ»
11690 }
11691 "});
11692}
11693
11694#[gpui::test]
11695async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
11696 init_test(cx, |_| {});
11697
11698 let language = Arc::new(Language::new(
11699 LanguageConfig {
11700 line_comments: vec!["// ".into()],
11701 ..Default::default()
11702 },
11703 Some(tree_sitter_rust::LANGUAGE.into()),
11704 ));
11705
11706 let mut cx = EditorTestContext::new(cx).await;
11707
11708 cx.language_registry().add(language.clone());
11709 cx.update_buffer(|buffer, cx| {
11710 buffer.set_language(Some(language), cx);
11711 });
11712
11713 let toggle_comments = &ToggleComments {
11714 advance_downwards: true,
11715 ignore_indent: false,
11716 };
11717
11718 // Single cursor on one line -> advance
11719 // Cursor moves horizontally 3 characters as well on non-blank line
11720 cx.set_state(indoc!(
11721 "fn a() {
11722 ˇdog();
11723 cat();
11724 }"
11725 ));
11726 cx.update_editor(|editor, window, cx| {
11727 editor.toggle_comments(toggle_comments, window, cx);
11728 });
11729 cx.assert_editor_state(indoc!(
11730 "fn a() {
11731 // dog();
11732 catˇ();
11733 }"
11734 ));
11735
11736 // Single selection on one line -> don't advance
11737 cx.set_state(indoc!(
11738 "fn a() {
11739 «dog()ˇ»;
11740 cat();
11741 }"
11742 ));
11743 cx.update_editor(|editor, window, cx| {
11744 editor.toggle_comments(toggle_comments, window, cx);
11745 });
11746 cx.assert_editor_state(indoc!(
11747 "fn a() {
11748 // «dog()ˇ»;
11749 cat();
11750 }"
11751 ));
11752
11753 // Multiple cursors on one line -> advance
11754 cx.set_state(indoc!(
11755 "fn a() {
11756 ˇdˇog();
11757 cat();
11758 }"
11759 ));
11760 cx.update_editor(|editor, window, cx| {
11761 editor.toggle_comments(toggle_comments, window, cx);
11762 });
11763 cx.assert_editor_state(indoc!(
11764 "fn a() {
11765 // dog();
11766 catˇ(ˇ);
11767 }"
11768 ));
11769
11770 // Multiple cursors on one line, with selection -> don't advance
11771 cx.set_state(indoc!(
11772 "fn a() {
11773 ˇdˇog«()ˇ»;
11774 cat();
11775 }"
11776 ));
11777 cx.update_editor(|editor, window, cx| {
11778 editor.toggle_comments(toggle_comments, window, cx);
11779 });
11780 cx.assert_editor_state(indoc!(
11781 "fn a() {
11782 // ˇdˇog«()ˇ»;
11783 cat();
11784 }"
11785 ));
11786
11787 // Single cursor on one line -> advance
11788 // Cursor moves to column 0 on blank line
11789 cx.set_state(indoc!(
11790 "fn a() {
11791 ˇdog();
11792
11793 cat();
11794 }"
11795 ));
11796 cx.update_editor(|editor, window, cx| {
11797 editor.toggle_comments(toggle_comments, window, cx);
11798 });
11799 cx.assert_editor_state(indoc!(
11800 "fn a() {
11801 // dog();
11802 ˇ
11803 cat();
11804 }"
11805 ));
11806
11807 // Single cursor on one line -> advance
11808 // Cursor starts and ends at column 0
11809 cx.set_state(indoc!(
11810 "fn a() {
11811 ˇ dog();
11812 cat();
11813 }"
11814 ));
11815 cx.update_editor(|editor, window, cx| {
11816 editor.toggle_comments(toggle_comments, window, cx);
11817 });
11818 cx.assert_editor_state(indoc!(
11819 "fn a() {
11820 // dog();
11821 ˇ cat();
11822 }"
11823 ));
11824}
11825
11826#[gpui::test]
11827async fn test_toggle_block_comment(cx: &mut TestAppContext) {
11828 init_test(cx, |_| {});
11829
11830 let mut cx = EditorTestContext::new(cx).await;
11831
11832 let html_language = Arc::new(
11833 Language::new(
11834 LanguageConfig {
11835 name: "HTML".into(),
11836 block_comment: Some(("<!-- ".into(), " -->".into())),
11837 ..Default::default()
11838 },
11839 Some(tree_sitter_html::LANGUAGE.into()),
11840 )
11841 .with_injection_query(
11842 r#"
11843 (script_element
11844 (raw_text) @injection.content
11845 (#set! injection.language "javascript"))
11846 "#,
11847 )
11848 .unwrap(),
11849 );
11850
11851 let javascript_language = Arc::new(Language::new(
11852 LanguageConfig {
11853 name: "JavaScript".into(),
11854 line_comments: vec!["// ".into()],
11855 ..Default::default()
11856 },
11857 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
11858 ));
11859
11860 cx.language_registry().add(html_language.clone());
11861 cx.language_registry().add(javascript_language.clone());
11862 cx.update_buffer(|buffer, cx| {
11863 buffer.set_language(Some(html_language), cx);
11864 });
11865
11866 // Toggle comments for empty selections
11867 cx.set_state(
11868 &r#"
11869 <p>A</p>ˇ
11870 <p>B</p>ˇ
11871 <p>C</p>ˇ
11872 "#
11873 .unindent(),
11874 );
11875 cx.update_editor(|editor, window, cx| {
11876 editor.toggle_comments(&ToggleComments::default(), window, cx)
11877 });
11878 cx.assert_editor_state(
11879 &r#"
11880 <!-- <p>A</p>ˇ -->
11881 <!-- <p>B</p>ˇ -->
11882 <!-- <p>C</p>ˇ -->
11883 "#
11884 .unindent(),
11885 );
11886 cx.update_editor(|editor, window, cx| {
11887 editor.toggle_comments(&ToggleComments::default(), window, cx)
11888 });
11889 cx.assert_editor_state(
11890 &r#"
11891 <p>A</p>ˇ
11892 <p>B</p>ˇ
11893 <p>C</p>ˇ
11894 "#
11895 .unindent(),
11896 );
11897
11898 // Toggle comments for mixture of empty and non-empty selections, where
11899 // multiple selections occupy a given line.
11900 cx.set_state(
11901 &r#"
11902 <p>A«</p>
11903 <p>ˇ»B</p>ˇ
11904 <p>C«</p>
11905 <p>ˇ»D</p>ˇ
11906 "#
11907 .unindent(),
11908 );
11909
11910 cx.update_editor(|editor, window, cx| {
11911 editor.toggle_comments(&ToggleComments::default(), window, cx)
11912 });
11913 cx.assert_editor_state(
11914 &r#"
11915 <!-- <p>A«</p>
11916 <p>ˇ»B</p>ˇ -->
11917 <!-- <p>C«</p>
11918 <p>ˇ»D</p>ˇ -->
11919 "#
11920 .unindent(),
11921 );
11922 cx.update_editor(|editor, window, cx| {
11923 editor.toggle_comments(&ToggleComments::default(), window, cx)
11924 });
11925 cx.assert_editor_state(
11926 &r#"
11927 <p>A«</p>
11928 <p>ˇ»B</p>ˇ
11929 <p>C«</p>
11930 <p>ˇ»D</p>ˇ
11931 "#
11932 .unindent(),
11933 );
11934
11935 // Toggle comments when different languages are active for different
11936 // selections.
11937 cx.set_state(
11938 &r#"
11939 ˇ<script>
11940 ˇvar x = new Y();
11941 ˇ</script>
11942 "#
11943 .unindent(),
11944 );
11945 cx.executor().run_until_parked();
11946 cx.update_editor(|editor, window, cx| {
11947 editor.toggle_comments(&ToggleComments::default(), window, cx)
11948 });
11949 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
11950 // Uncommenting and commenting from this position brings in even more wrong artifacts.
11951 cx.assert_editor_state(
11952 &r#"
11953 <!-- ˇ<script> -->
11954 // ˇvar x = new Y();
11955 <!-- ˇ</script> -->
11956 "#
11957 .unindent(),
11958 );
11959}
11960
11961#[gpui::test]
11962fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
11963 init_test(cx, |_| {});
11964
11965 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
11966 let multibuffer = cx.new(|cx| {
11967 let mut multibuffer = MultiBuffer::new(ReadWrite);
11968 multibuffer.push_excerpts(
11969 buffer.clone(),
11970 [
11971 ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
11972 ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
11973 ],
11974 cx,
11975 );
11976 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
11977 multibuffer
11978 });
11979
11980 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
11981 editor.update_in(cx, |editor, window, cx| {
11982 assert_eq!(editor.text(cx), "aaaa\nbbbb");
11983 editor.change_selections(None, window, cx, |s| {
11984 s.select_ranges([
11985 Point::new(0, 0)..Point::new(0, 0),
11986 Point::new(1, 0)..Point::new(1, 0),
11987 ])
11988 });
11989
11990 editor.handle_input("X", window, cx);
11991 assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
11992 assert_eq!(
11993 editor.selections.ranges(cx),
11994 [
11995 Point::new(0, 1)..Point::new(0, 1),
11996 Point::new(1, 1)..Point::new(1, 1),
11997 ]
11998 );
11999
12000 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
12001 editor.change_selections(None, window, cx, |s| {
12002 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
12003 });
12004 editor.backspace(&Default::default(), window, cx);
12005 assert_eq!(editor.text(cx), "Xa\nbbb");
12006 assert_eq!(
12007 editor.selections.ranges(cx),
12008 [Point::new(1, 0)..Point::new(1, 0)]
12009 );
12010
12011 editor.change_selections(None, window, cx, |s| {
12012 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
12013 });
12014 editor.backspace(&Default::default(), window, cx);
12015 assert_eq!(editor.text(cx), "X\nbb");
12016 assert_eq!(
12017 editor.selections.ranges(cx),
12018 [Point::new(0, 1)..Point::new(0, 1)]
12019 );
12020 });
12021}
12022
12023#[gpui::test]
12024fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
12025 init_test(cx, |_| {});
12026
12027 let markers = vec![('[', ']').into(), ('(', ')').into()];
12028 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
12029 indoc! {"
12030 [aaaa
12031 (bbbb]
12032 cccc)",
12033 },
12034 markers.clone(),
12035 );
12036 let excerpt_ranges = markers.into_iter().map(|marker| {
12037 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
12038 ExcerptRange::new(context.clone())
12039 });
12040 let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
12041 let multibuffer = cx.new(|cx| {
12042 let mut multibuffer = MultiBuffer::new(ReadWrite);
12043 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
12044 multibuffer
12045 });
12046
12047 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
12048 editor.update_in(cx, |editor, window, cx| {
12049 let (expected_text, selection_ranges) = marked_text_ranges(
12050 indoc! {"
12051 aaaa
12052 bˇbbb
12053 bˇbbˇb
12054 cccc"
12055 },
12056 true,
12057 );
12058 assert_eq!(editor.text(cx), expected_text);
12059 editor.change_selections(None, window, cx, |s| s.select_ranges(selection_ranges));
12060
12061 editor.handle_input("X", window, cx);
12062
12063 let (expected_text, expected_selections) = marked_text_ranges(
12064 indoc! {"
12065 aaaa
12066 bXˇbbXb
12067 bXˇbbXˇb
12068 cccc"
12069 },
12070 false,
12071 );
12072 assert_eq!(editor.text(cx), expected_text);
12073 assert_eq!(editor.selections.ranges(cx), expected_selections);
12074
12075 editor.newline(&Newline, window, cx);
12076 let (expected_text, expected_selections) = marked_text_ranges(
12077 indoc! {"
12078 aaaa
12079 bX
12080 ˇbbX
12081 b
12082 bX
12083 ˇbbX
12084 ˇb
12085 cccc"
12086 },
12087 false,
12088 );
12089 assert_eq!(editor.text(cx), expected_text);
12090 assert_eq!(editor.selections.ranges(cx), expected_selections);
12091 });
12092}
12093
12094#[gpui::test]
12095fn test_refresh_selections(cx: &mut TestAppContext) {
12096 init_test(cx, |_| {});
12097
12098 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
12099 let mut excerpt1_id = None;
12100 let multibuffer = cx.new(|cx| {
12101 let mut multibuffer = MultiBuffer::new(ReadWrite);
12102 excerpt1_id = multibuffer
12103 .push_excerpts(
12104 buffer.clone(),
12105 [
12106 ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
12107 ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
12108 ],
12109 cx,
12110 )
12111 .into_iter()
12112 .next();
12113 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
12114 multibuffer
12115 });
12116
12117 let editor = cx.add_window(|window, cx| {
12118 let mut editor = build_editor(multibuffer.clone(), window, cx);
12119 let snapshot = editor.snapshot(window, cx);
12120 editor.change_selections(None, window, cx, |s| {
12121 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
12122 });
12123 editor.begin_selection(
12124 Point::new(2, 1).to_display_point(&snapshot),
12125 true,
12126 1,
12127 window,
12128 cx,
12129 );
12130 assert_eq!(
12131 editor.selections.ranges(cx),
12132 [
12133 Point::new(1, 3)..Point::new(1, 3),
12134 Point::new(2, 1)..Point::new(2, 1),
12135 ]
12136 );
12137 editor
12138 });
12139
12140 // Refreshing selections is a no-op when excerpts haven't changed.
12141 _ = editor.update(cx, |editor, window, cx| {
12142 editor.change_selections(None, window, cx, |s| s.refresh());
12143 assert_eq!(
12144 editor.selections.ranges(cx),
12145 [
12146 Point::new(1, 3)..Point::new(1, 3),
12147 Point::new(2, 1)..Point::new(2, 1),
12148 ]
12149 );
12150 });
12151
12152 multibuffer.update(cx, |multibuffer, cx| {
12153 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
12154 });
12155 _ = editor.update(cx, |editor, window, cx| {
12156 // Removing an excerpt causes the first selection to become degenerate.
12157 assert_eq!(
12158 editor.selections.ranges(cx),
12159 [
12160 Point::new(0, 0)..Point::new(0, 0),
12161 Point::new(0, 1)..Point::new(0, 1)
12162 ]
12163 );
12164
12165 // Refreshing selections will relocate the first selection to the original buffer
12166 // location.
12167 editor.change_selections(None, window, cx, |s| s.refresh());
12168 assert_eq!(
12169 editor.selections.ranges(cx),
12170 [
12171 Point::new(0, 1)..Point::new(0, 1),
12172 Point::new(0, 3)..Point::new(0, 3)
12173 ]
12174 );
12175 assert!(editor.selections.pending_anchor().is_some());
12176 });
12177}
12178
12179#[gpui::test]
12180fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
12181 init_test(cx, |_| {});
12182
12183 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
12184 let mut excerpt1_id = None;
12185 let multibuffer = cx.new(|cx| {
12186 let mut multibuffer = MultiBuffer::new(ReadWrite);
12187 excerpt1_id = multibuffer
12188 .push_excerpts(
12189 buffer.clone(),
12190 [
12191 ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
12192 ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
12193 ],
12194 cx,
12195 )
12196 .into_iter()
12197 .next();
12198 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
12199 multibuffer
12200 });
12201
12202 let editor = cx.add_window(|window, cx| {
12203 let mut editor = build_editor(multibuffer.clone(), window, cx);
12204 let snapshot = editor.snapshot(window, cx);
12205 editor.begin_selection(
12206 Point::new(1, 3).to_display_point(&snapshot),
12207 false,
12208 1,
12209 window,
12210 cx,
12211 );
12212 assert_eq!(
12213 editor.selections.ranges(cx),
12214 [Point::new(1, 3)..Point::new(1, 3)]
12215 );
12216 editor
12217 });
12218
12219 multibuffer.update(cx, |multibuffer, cx| {
12220 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
12221 });
12222 _ = editor.update(cx, |editor, window, cx| {
12223 assert_eq!(
12224 editor.selections.ranges(cx),
12225 [Point::new(0, 0)..Point::new(0, 0)]
12226 );
12227
12228 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
12229 editor.change_selections(None, window, cx, |s| s.refresh());
12230 assert_eq!(
12231 editor.selections.ranges(cx),
12232 [Point::new(0, 3)..Point::new(0, 3)]
12233 );
12234 assert!(editor.selections.pending_anchor().is_some());
12235 });
12236}
12237
12238#[gpui::test]
12239async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
12240 init_test(cx, |_| {});
12241
12242 let language = Arc::new(
12243 Language::new(
12244 LanguageConfig {
12245 brackets: BracketPairConfig {
12246 pairs: vec![
12247 BracketPair {
12248 start: "{".to_string(),
12249 end: "}".to_string(),
12250 close: true,
12251 surround: true,
12252 newline: true,
12253 },
12254 BracketPair {
12255 start: "/* ".to_string(),
12256 end: " */".to_string(),
12257 close: true,
12258 surround: true,
12259 newline: true,
12260 },
12261 ],
12262 ..Default::default()
12263 },
12264 ..Default::default()
12265 },
12266 Some(tree_sitter_rust::LANGUAGE.into()),
12267 )
12268 .with_indents_query("")
12269 .unwrap(),
12270 );
12271
12272 let text = concat!(
12273 "{ }\n", //
12274 " x\n", //
12275 " /* */\n", //
12276 "x\n", //
12277 "{{} }\n", //
12278 );
12279
12280 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
12281 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12282 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
12283 editor
12284 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
12285 .await;
12286
12287 editor.update_in(cx, |editor, window, cx| {
12288 editor.change_selections(None, window, cx, |s| {
12289 s.select_display_ranges([
12290 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
12291 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
12292 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
12293 ])
12294 });
12295 editor.newline(&Newline, window, cx);
12296
12297 assert_eq!(
12298 editor.buffer().read(cx).read(cx).text(),
12299 concat!(
12300 "{ \n", // Suppress rustfmt
12301 "\n", //
12302 "}\n", //
12303 " x\n", //
12304 " /* \n", //
12305 " \n", //
12306 " */\n", //
12307 "x\n", //
12308 "{{} \n", //
12309 "}\n", //
12310 )
12311 );
12312 });
12313}
12314
12315#[gpui::test]
12316fn test_highlighted_ranges(cx: &mut TestAppContext) {
12317 init_test(cx, |_| {});
12318
12319 let editor = cx.add_window(|window, cx| {
12320 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
12321 build_editor(buffer.clone(), window, cx)
12322 });
12323
12324 _ = editor.update(cx, |editor, window, cx| {
12325 struct Type1;
12326 struct Type2;
12327
12328 let buffer = editor.buffer.read(cx).snapshot(cx);
12329
12330 let anchor_range =
12331 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
12332
12333 editor.highlight_background::<Type1>(
12334 &[
12335 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
12336 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
12337 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
12338 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
12339 ],
12340 |_| Hsla::red(),
12341 cx,
12342 );
12343 editor.highlight_background::<Type2>(
12344 &[
12345 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
12346 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
12347 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
12348 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
12349 ],
12350 |_| Hsla::green(),
12351 cx,
12352 );
12353
12354 let snapshot = editor.snapshot(window, cx);
12355 let mut highlighted_ranges = editor.background_highlights_in_range(
12356 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
12357 &snapshot,
12358 cx.theme().colors(),
12359 );
12360 // Enforce a consistent ordering based on color without relying on the ordering of the
12361 // highlight's `TypeId` which is non-executor.
12362 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
12363 assert_eq!(
12364 highlighted_ranges,
12365 &[
12366 (
12367 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
12368 Hsla::red(),
12369 ),
12370 (
12371 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
12372 Hsla::red(),
12373 ),
12374 (
12375 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
12376 Hsla::green(),
12377 ),
12378 (
12379 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
12380 Hsla::green(),
12381 ),
12382 ]
12383 );
12384 assert_eq!(
12385 editor.background_highlights_in_range(
12386 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
12387 &snapshot,
12388 cx.theme().colors(),
12389 ),
12390 &[(
12391 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
12392 Hsla::red(),
12393 )]
12394 );
12395 });
12396}
12397
12398#[gpui::test]
12399async fn test_following(cx: &mut TestAppContext) {
12400 init_test(cx, |_| {});
12401
12402 let fs = FakeFs::new(cx.executor());
12403 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
12404
12405 let buffer = project.update(cx, |project, cx| {
12406 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
12407 cx.new(|cx| MultiBuffer::singleton(buffer, cx))
12408 });
12409 let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
12410 let follower = cx.update(|cx| {
12411 cx.open_window(
12412 WindowOptions {
12413 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
12414 gpui::Point::new(px(0.), px(0.)),
12415 gpui::Point::new(px(10.), px(80.)),
12416 ))),
12417 ..Default::default()
12418 },
12419 |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
12420 )
12421 .unwrap()
12422 });
12423
12424 let is_still_following = Rc::new(RefCell::new(true));
12425 let follower_edit_event_count = Rc::new(RefCell::new(0));
12426 let pending_update = Rc::new(RefCell::new(None));
12427 let leader_entity = leader.root(cx).unwrap();
12428 let follower_entity = follower.root(cx).unwrap();
12429 _ = follower.update(cx, {
12430 let update = pending_update.clone();
12431 let is_still_following = is_still_following.clone();
12432 let follower_edit_event_count = follower_edit_event_count.clone();
12433 |_, window, cx| {
12434 cx.subscribe_in(
12435 &leader_entity,
12436 window,
12437 move |_, leader, event, window, cx| {
12438 leader.read(cx).add_event_to_update_proto(
12439 event,
12440 &mut update.borrow_mut(),
12441 window,
12442 cx,
12443 );
12444 },
12445 )
12446 .detach();
12447
12448 cx.subscribe_in(
12449 &follower_entity,
12450 window,
12451 move |_, _, event: &EditorEvent, _window, _cx| {
12452 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
12453 *is_still_following.borrow_mut() = false;
12454 }
12455
12456 if let EditorEvent::BufferEdited = event {
12457 *follower_edit_event_count.borrow_mut() += 1;
12458 }
12459 },
12460 )
12461 .detach();
12462 }
12463 });
12464
12465 // Update the selections only
12466 _ = leader.update(cx, |leader, window, cx| {
12467 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
12468 });
12469 follower
12470 .update(cx, |follower, window, cx| {
12471 follower.apply_update_proto(
12472 &project,
12473 pending_update.borrow_mut().take().unwrap(),
12474 window,
12475 cx,
12476 )
12477 })
12478 .unwrap()
12479 .await
12480 .unwrap();
12481 _ = follower.update(cx, |follower, _, cx| {
12482 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
12483 });
12484 assert!(*is_still_following.borrow());
12485 assert_eq!(*follower_edit_event_count.borrow(), 0);
12486
12487 // Update the scroll position only
12488 _ = leader.update(cx, |leader, window, cx| {
12489 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
12490 });
12491 follower
12492 .update(cx, |follower, window, cx| {
12493 follower.apply_update_proto(
12494 &project,
12495 pending_update.borrow_mut().take().unwrap(),
12496 window,
12497 cx,
12498 )
12499 })
12500 .unwrap()
12501 .await
12502 .unwrap();
12503 assert_eq!(
12504 follower
12505 .update(cx, |follower, _, cx| follower.scroll_position(cx))
12506 .unwrap(),
12507 gpui::Point::new(1.5, 3.5)
12508 );
12509 assert!(*is_still_following.borrow());
12510 assert_eq!(*follower_edit_event_count.borrow(), 0);
12511
12512 // Update the selections and scroll position. The follower's scroll position is updated
12513 // via autoscroll, not via the leader's exact scroll position.
12514 _ = leader.update(cx, |leader, window, cx| {
12515 leader.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
12516 leader.request_autoscroll(Autoscroll::newest(), cx);
12517 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
12518 });
12519 follower
12520 .update(cx, |follower, window, cx| {
12521 follower.apply_update_proto(
12522 &project,
12523 pending_update.borrow_mut().take().unwrap(),
12524 window,
12525 cx,
12526 )
12527 })
12528 .unwrap()
12529 .await
12530 .unwrap();
12531 _ = follower.update(cx, |follower, _, cx| {
12532 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
12533 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
12534 });
12535 assert!(*is_still_following.borrow());
12536
12537 // Creating a pending selection that precedes another selection
12538 _ = leader.update(cx, |leader, window, cx| {
12539 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
12540 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
12541 });
12542 follower
12543 .update(cx, |follower, window, cx| {
12544 follower.apply_update_proto(
12545 &project,
12546 pending_update.borrow_mut().take().unwrap(),
12547 window,
12548 cx,
12549 )
12550 })
12551 .unwrap()
12552 .await
12553 .unwrap();
12554 _ = follower.update(cx, |follower, _, cx| {
12555 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
12556 });
12557 assert!(*is_still_following.borrow());
12558
12559 // Extend the pending selection so that it surrounds another selection
12560 _ = leader.update(cx, |leader, window, cx| {
12561 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
12562 });
12563 follower
12564 .update(cx, |follower, window, cx| {
12565 follower.apply_update_proto(
12566 &project,
12567 pending_update.borrow_mut().take().unwrap(),
12568 window,
12569 cx,
12570 )
12571 })
12572 .unwrap()
12573 .await
12574 .unwrap();
12575 _ = follower.update(cx, |follower, _, cx| {
12576 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
12577 });
12578
12579 // Scrolling locally breaks the follow
12580 _ = follower.update(cx, |follower, window, cx| {
12581 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
12582 follower.set_scroll_anchor(
12583 ScrollAnchor {
12584 anchor: top_anchor,
12585 offset: gpui::Point::new(0.0, 0.5),
12586 },
12587 window,
12588 cx,
12589 );
12590 });
12591 assert!(!(*is_still_following.borrow()));
12592}
12593
12594#[gpui::test]
12595async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
12596 init_test(cx, |_| {});
12597
12598 let fs = FakeFs::new(cx.executor());
12599 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
12600 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12601 let pane = workspace
12602 .update(cx, |workspace, _, _| workspace.active_pane().clone())
12603 .unwrap();
12604
12605 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
12606
12607 let leader = pane.update_in(cx, |_, window, cx| {
12608 let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
12609 cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
12610 });
12611
12612 // Start following the editor when it has no excerpts.
12613 let mut state_message =
12614 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
12615 let workspace_entity = workspace.root(cx).unwrap();
12616 let follower_1 = cx
12617 .update_window(*workspace.deref(), |_, window, cx| {
12618 Editor::from_state_proto(
12619 workspace_entity,
12620 ViewId {
12621 creator: Default::default(),
12622 id: 0,
12623 },
12624 &mut state_message,
12625 window,
12626 cx,
12627 )
12628 })
12629 .unwrap()
12630 .unwrap()
12631 .await
12632 .unwrap();
12633
12634 let update_message = Rc::new(RefCell::new(None));
12635 follower_1.update_in(cx, {
12636 let update = update_message.clone();
12637 |_, window, cx| {
12638 cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
12639 leader.read(cx).add_event_to_update_proto(
12640 event,
12641 &mut update.borrow_mut(),
12642 window,
12643 cx,
12644 );
12645 })
12646 .detach();
12647 }
12648 });
12649
12650 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
12651 (
12652 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
12653 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
12654 )
12655 });
12656
12657 // Insert some excerpts.
12658 leader.update(cx, |leader, cx| {
12659 leader.buffer.update(cx, |multibuffer, cx| {
12660 multibuffer.set_excerpts_for_path(
12661 PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
12662 buffer_1.clone(),
12663 vec![
12664 Point::row_range(0..3),
12665 Point::row_range(1..6),
12666 Point::row_range(12..15),
12667 ],
12668 0,
12669 cx,
12670 );
12671 multibuffer.set_excerpts_for_path(
12672 PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
12673 buffer_2.clone(),
12674 vec![Point::row_range(0..6), Point::row_range(8..12)],
12675 0,
12676 cx,
12677 );
12678 });
12679 });
12680
12681 // Apply the update of adding the excerpts.
12682 follower_1
12683 .update_in(cx, |follower, window, cx| {
12684 follower.apply_update_proto(
12685 &project,
12686 update_message.borrow().clone().unwrap(),
12687 window,
12688 cx,
12689 )
12690 })
12691 .await
12692 .unwrap();
12693 assert_eq!(
12694 follower_1.update(cx, |editor, cx| editor.text(cx)),
12695 leader.update(cx, |editor, cx| editor.text(cx))
12696 );
12697 update_message.borrow_mut().take();
12698
12699 // Start following separately after it already has excerpts.
12700 let mut state_message =
12701 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
12702 let workspace_entity = workspace.root(cx).unwrap();
12703 let follower_2 = cx
12704 .update_window(*workspace.deref(), |_, window, cx| {
12705 Editor::from_state_proto(
12706 workspace_entity,
12707 ViewId {
12708 creator: Default::default(),
12709 id: 0,
12710 },
12711 &mut state_message,
12712 window,
12713 cx,
12714 )
12715 })
12716 .unwrap()
12717 .unwrap()
12718 .await
12719 .unwrap();
12720 assert_eq!(
12721 follower_2.update(cx, |editor, cx| editor.text(cx)),
12722 leader.update(cx, |editor, cx| editor.text(cx))
12723 );
12724
12725 // Remove some excerpts.
12726 leader.update(cx, |leader, cx| {
12727 leader.buffer.update(cx, |multibuffer, cx| {
12728 let excerpt_ids = multibuffer.excerpt_ids();
12729 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
12730 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
12731 });
12732 });
12733
12734 // Apply the update of removing the excerpts.
12735 follower_1
12736 .update_in(cx, |follower, window, cx| {
12737 follower.apply_update_proto(
12738 &project,
12739 update_message.borrow().clone().unwrap(),
12740 window,
12741 cx,
12742 )
12743 })
12744 .await
12745 .unwrap();
12746 follower_2
12747 .update_in(cx, |follower, window, cx| {
12748 follower.apply_update_proto(
12749 &project,
12750 update_message.borrow().clone().unwrap(),
12751 window,
12752 cx,
12753 )
12754 })
12755 .await
12756 .unwrap();
12757 update_message.borrow_mut().take();
12758 assert_eq!(
12759 follower_1.update(cx, |editor, cx| editor.text(cx)),
12760 leader.update(cx, |editor, cx| editor.text(cx))
12761 );
12762}
12763
12764#[gpui::test]
12765async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
12766 init_test(cx, |_| {});
12767
12768 let mut cx = EditorTestContext::new(cx).await;
12769 let lsp_store =
12770 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
12771
12772 cx.set_state(indoc! {"
12773 ˇfn func(abc def: i32) -> u32 {
12774 }
12775 "});
12776
12777 cx.update(|_, cx| {
12778 lsp_store.update(cx, |lsp_store, cx| {
12779 lsp_store
12780 .update_diagnostics(
12781 LanguageServerId(0),
12782 lsp::PublishDiagnosticsParams {
12783 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
12784 version: None,
12785 diagnostics: vec![
12786 lsp::Diagnostic {
12787 range: lsp::Range::new(
12788 lsp::Position::new(0, 11),
12789 lsp::Position::new(0, 12),
12790 ),
12791 severity: Some(lsp::DiagnosticSeverity::ERROR),
12792 ..Default::default()
12793 },
12794 lsp::Diagnostic {
12795 range: lsp::Range::new(
12796 lsp::Position::new(0, 12),
12797 lsp::Position::new(0, 15),
12798 ),
12799 severity: Some(lsp::DiagnosticSeverity::ERROR),
12800 ..Default::default()
12801 },
12802 lsp::Diagnostic {
12803 range: lsp::Range::new(
12804 lsp::Position::new(0, 25),
12805 lsp::Position::new(0, 28),
12806 ),
12807 severity: Some(lsp::DiagnosticSeverity::ERROR),
12808 ..Default::default()
12809 },
12810 ],
12811 },
12812 &[],
12813 cx,
12814 )
12815 .unwrap()
12816 });
12817 });
12818
12819 executor.run_until_parked();
12820
12821 cx.update_editor(|editor, window, cx| {
12822 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
12823 });
12824
12825 cx.assert_editor_state(indoc! {"
12826 fn func(abc def: i32) -> ˇu32 {
12827 }
12828 "});
12829
12830 cx.update_editor(|editor, window, cx| {
12831 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
12832 });
12833
12834 cx.assert_editor_state(indoc! {"
12835 fn func(abc ˇdef: i32) -> u32 {
12836 }
12837 "});
12838
12839 cx.update_editor(|editor, window, cx| {
12840 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
12841 });
12842
12843 cx.assert_editor_state(indoc! {"
12844 fn func(abcˇ def: i32) -> u32 {
12845 }
12846 "});
12847
12848 cx.update_editor(|editor, window, cx| {
12849 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
12850 });
12851
12852 cx.assert_editor_state(indoc! {"
12853 fn func(abc def: i32) -> ˇu32 {
12854 }
12855 "});
12856}
12857
12858#[gpui::test]
12859async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
12860 init_test(cx, |_| {});
12861
12862 let mut cx = EditorTestContext::new(cx).await;
12863
12864 let diff_base = r#"
12865 use some::mod;
12866
12867 const A: u32 = 42;
12868
12869 fn main() {
12870 println!("hello");
12871
12872 println!("world");
12873 }
12874 "#
12875 .unindent();
12876
12877 // Edits are modified, removed, modified, added
12878 cx.set_state(
12879 &r#"
12880 use some::modified;
12881
12882 ˇ
12883 fn main() {
12884 println!("hello there");
12885
12886 println!("around the");
12887 println!("world");
12888 }
12889 "#
12890 .unindent(),
12891 );
12892
12893 cx.set_head_text(&diff_base);
12894 executor.run_until_parked();
12895
12896 cx.update_editor(|editor, window, cx| {
12897 //Wrap around the bottom of the buffer
12898 for _ in 0..3 {
12899 editor.go_to_next_hunk(&GoToHunk, window, cx);
12900 }
12901 });
12902
12903 cx.assert_editor_state(
12904 &r#"
12905 ˇuse some::modified;
12906
12907
12908 fn main() {
12909 println!("hello there");
12910
12911 println!("around the");
12912 println!("world");
12913 }
12914 "#
12915 .unindent(),
12916 );
12917
12918 cx.update_editor(|editor, window, cx| {
12919 //Wrap around the top of the buffer
12920 for _ in 0..2 {
12921 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
12922 }
12923 });
12924
12925 cx.assert_editor_state(
12926 &r#"
12927 use some::modified;
12928
12929
12930 fn main() {
12931 ˇ println!("hello there");
12932
12933 println!("around the");
12934 println!("world");
12935 }
12936 "#
12937 .unindent(),
12938 );
12939
12940 cx.update_editor(|editor, window, cx| {
12941 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
12942 });
12943
12944 cx.assert_editor_state(
12945 &r#"
12946 use some::modified;
12947
12948 ˇ
12949 fn main() {
12950 println!("hello there");
12951
12952 println!("around the");
12953 println!("world");
12954 }
12955 "#
12956 .unindent(),
12957 );
12958
12959 cx.update_editor(|editor, window, cx| {
12960 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
12961 });
12962
12963 cx.assert_editor_state(
12964 &r#"
12965 ˇuse some::modified;
12966
12967
12968 fn main() {
12969 println!("hello there");
12970
12971 println!("around the");
12972 println!("world");
12973 }
12974 "#
12975 .unindent(),
12976 );
12977
12978 cx.update_editor(|editor, window, cx| {
12979 for _ in 0..2 {
12980 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
12981 }
12982 });
12983
12984 cx.assert_editor_state(
12985 &r#"
12986 use some::modified;
12987
12988
12989 fn main() {
12990 ˇ println!("hello there");
12991
12992 println!("around the");
12993 println!("world");
12994 }
12995 "#
12996 .unindent(),
12997 );
12998
12999 cx.update_editor(|editor, window, cx| {
13000 editor.fold(&Fold, window, cx);
13001 });
13002
13003 cx.update_editor(|editor, window, cx| {
13004 editor.go_to_next_hunk(&GoToHunk, window, cx);
13005 });
13006
13007 cx.assert_editor_state(
13008 &r#"
13009 ˇuse some::modified;
13010
13011
13012 fn main() {
13013 println!("hello there");
13014
13015 println!("around the");
13016 println!("world");
13017 }
13018 "#
13019 .unindent(),
13020 );
13021}
13022
13023#[test]
13024fn test_split_words() {
13025 fn split(text: &str) -> Vec<&str> {
13026 split_words(text).collect()
13027 }
13028
13029 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
13030 assert_eq!(split("hello_world"), &["hello_", "world"]);
13031 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
13032 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
13033 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
13034 assert_eq!(split("helloworld"), &["helloworld"]);
13035
13036 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
13037}
13038
13039#[gpui::test]
13040async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
13041 init_test(cx, |_| {});
13042
13043 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
13044 let mut assert = |before, after| {
13045 let _state_context = cx.set_state(before);
13046 cx.run_until_parked();
13047 cx.update_editor(|editor, window, cx| {
13048 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
13049 });
13050 cx.run_until_parked();
13051 cx.assert_editor_state(after);
13052 };
13053
13054 // Outside bracket jumps to outside of matching bracket
13055 assert("console.logˇ(var);", "console.log(var)ˇ;");
13056 assert("console.log(var)ˇ;", "console.logˇ(var);");
13057
13058 // Inside bracket jumps to inside of matching bracket
13059 assert("console.log(ˇvar);", "console.log(varˇ);");
13060 assert("console.log(varˇ);", "console.log(ˇvar);");
13061
13062 // When outside a bracket and inside, favor jumping to the inside bracket
13063 assert(
13064 "console.log('foo', [1, 2, 3]ˇ);",
13065 "console.log(ˇ'foo', [1, 2, 3]);",
13066 );
13067 assert(
13068 "console.log(ˇ'foo', [1, 2, 3]);",
13069 "console.log('foo', [1, 2, 3]ˇ);",
13070 );
13071
13072 // Bias forward if two options are equally likely
13073 assert(
13074 "let result = curried_fun()ˇ();",
13075 "let result = curried_fun()()ˇ;",
13076 );
13077
13078 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
13079 assert(
13080 indoc! {"
13081 function test() {
13082 console.log('test')ˇ
13083 }"},
13084 indoc! {"
13085 function test() {
13086 console.logˇ('test')
13087 }"},
13088 );
13089}
13090
13091#[gpui::test]
13092async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
13093 init_test(cx, |_| {});
13094
13095 let fs = FakeFs::new(cx.executor());
13096 fs.insert_tree(
13097 path!("/a"),
13098 json!({
13099 "main.rs": "fn main() { let a = 5; }",
13100 "other.rs": "// Test file",
13101 }),
13102 )
13103 .await;
13104 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13105
13106 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13107 language_registry.add(Arc::new(Language::new(
13108 LanguageConfig {
13109 name: "Rust".into(),
13110 matcher: LanguageMatcher {
13111 path_suffixes: vec!["rs".to_string()],
13112 ..Default::default()
13113 },
13114 brackets: BracketPairConfig {
13115 pairs: vec![BracketPair {
13116 start: "{".to_string(),
13117 end: "}".to_string(),
13118 close: true,
13119 surround: true,
13120 newline: true,
13121 }],
13122 disabled_scopes_by_bracket_ix: Vec::new(),
13123 },
13124 ..Default::default()
13125 },
13126 Some(tree_sitter_rust::LANGUAGE.into()),
13127 )));
13128 let mut fake_servers = language_registry.register_fake_lsp(
13129 "Rust",
13130 FakeLspAdapter {
13131 capabilities: lsp::ServerCapabilities {
13132 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
13133 first_trigger_character: "{".to_string(),
13134 more_trigger_character: None,
13135 }),
13136 ..Default::default()
13137 },
13138 ..Default::default()
13139 },
13140 );
13141
13142 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13143
13144 let cx = &mut VisualTestContext::from_window(*workspace, cx);
13145
13146 let worktree_id = workspace
13147 .update(cx, |workspace, _, cx| {
13148 workspace.project().update(cx, |project, cx| {
13149 project.worktrees(cx).next().unwrap().read(cx).id()
13150 })
13151 })
13152 .unwrap();
13153
13154 let buffer = project
13155 .update(cx, |project, cx| {
13156 project.open_local_buffer(path!("/a/main.rs"), cx)
13157 })
13158 .await
13159 .unwrap();
13160 let editor_handle = workspace
13161 .update(cx, |workspace, window, cx| {
13162 workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
13163 })
13164 .unwrap()
13165 .await
13166 .unwrap()
13167 .downcast::<Editor>()
13168 .unwrap();
13169
13170 cx.executor().start_waiting();
13171 let fake_server = fake_servers.next().await.unwrap();
13172
13173 fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
13174 |params, _| async move {
13175 assert_eq!(
13176 params.text_document_position.text_document.uri,
13177 lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
13178 );
13179 assert_eq!(
13180 params.text_document_position.position,
13181 lsp::Position::new(0, 21),
13182 );
13183
13184 Ok(Some(vec![lsp::TextEdit {
13185 new_text: "]".to_string(),
13186 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
13187 }]))
13188 },
13189 );
13190
13191 editor_handle.update_in(cx, |editor, window, cx| {
13192 window.focus(&editor.focus_handle(cx));
13193 editor.change_selections(None, window, cx, |s| {
13194 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
13195 });
13196 editor.handle_input("{", window, cx);
13197 });
13198
13199 cx.executor().run_until_parked();
13200
13201 buffer.update(cx, |buffer, _| {
13202 assert_eq!(
13203 buffer.text(),
13204 "fn main() { let a = {5}; }",
13205 "No extra braces from on type formatting should appear in the buffer"
13206 )
13207 });
13208}
13209
13210#[gpui::test]
13211async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
13212 init_test(cx, |_| {});
13213
13214 let fs = FakeFs::new(cx.executor());
13215 fs.insert_tree(
13216 path!("/a"),
13217 json!({
13218 "main.rs": "fn main() { let a = 5; }",
13219 "other.rs": "// Test file",
13220 }),
13221 )
13222 .await;
13223
13224 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13225
13226 let server_restarts = Arc::new(AtomicUsize::new(0));
13227 let closure_restarts = Arc::clone(&server_restarts);
13228 let language_server_name = "test language server";
13229 let language_name: LanguageName = "Rust".into();
13230
13231 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13232 language_registry.add(Arc::new(Language::new(
13233 LanguageConfig {
13234 name: language_name.clone(),
13235 matcher: LanguageMatcher {
13236 path_suffixes: vec!["rs".to_string()],
13237 ..Default::default()
13238 },
13239 ..Default::default()
13240 },
13241 Some(tree_sitter_rust::LANGUAGE.into()),
13242 )));
13243 let mut fake_servers = language_registry.register_fake_lsp(
13244 "Rust",
13245 FakeLspAdapter {
13246 name: language_server_name,
13247 initialization_options: Some(json!({
13248 "testOptionValue": true
13249 })),
13250 initializer: Some(Box::new(move |fake_server| {
13251 let task_restarts = Arc::clone(&closure_restarts);
13252 fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
13253 task_restarts.fetch_add(1, atomic::Ordering::Release);
13254 futures::future::ready(Ok(()))
13255 });
13256 })),
13257 ..Default::default()
13258 },
13259 );
13260
13261 let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13262 let _buffer = project
13263 .update(cx, |project, cx| {
13264 project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
13265 })
13266 .await
13267 .unwrap();
13268 let _fake_server = fake_servers.next().await.unwrap();
13269 update_test_language_settings(cx, |language_settings| {
13270 language_settings.languages.insert(
13271 language_name.clone(),
13272 LanguageSettingsContent {
13273 tab_size: NonZeroU32::new(8),
13274 ..Default::default()
13275 },
13276 );
13277 });
13278 cx.executor().run_until_parked();
13279 assert_eq!(
13280 server_restarts.load(atomic::Ordering::Acquire),
13281 0,
13282 "Should not restart LSP server on an unrelated change"
13283 );
13284
13285 update_test_project_settings(cx, |project_settings| {
13286 project_settings.lsp.insert(
13287 "Some other server name".into(),
13288 LspSettings {
13289 binary: None,
13290 settings: None,
13291 initialization_options: Some(json!({
13292 "some other init value": false
13293 })),
13294 enable_lsp_tasks: false,
13295 },
13296 );
13297 });
13298 cx.executor().run_until_parked();
13299 assert_eq!(
13300 server_restarts.load(atomic::Ordering::Acquire),
13301 0,
13302 "Should not restart LSP server on an unrelated LSP settings change"
13303 );
13304
13305 update_test_project_settings(cx, |project_settings| {
13306 project_settings.lsp.insert(
13307 language_server_name.into(),
13308 LspSettings {
13309 binary: None,
13310 settings: None,
13311 initialization_options: Some(json!({
13312 "anotherInitValue": false
13313 })),
13314 enable_lsp_tasks: false,
13315 },
13316 );
13317 });
13318 cx.executor().run_until_parked();
13319 assert_eq!(
13320 server_restarts.load(atomic::Ordering::Acquire),
13321 1,
13322 "Should restart LSP server on a related LSP settings change"
13323 );
13324
13325 update_test_project_settings(cx, |project_settings| {
13326 project_settings.lsp.insert(
13327 language_server_name.into(),
13328 LspSettings {
13329 binary: None,
13330 settings: None,
13331 initialization_options: Some(json!({
13332 "anotherInitValue": false
13333 })),
13334 enable_lsp_tasks: false,
13335 },
13336 );
13337 });
13338 cx.executor().run_until_parked();
13339 assert_eq!(
13340 server_restarts.load(atomic::Ordering::Acquire),
13341 1,
13342 "Should not restart LSP server on a related LSP settings change that is the same"
13343 );
13344
13345 update_test_project_settings(cx, |project_settings| {
13346 project_settings.lsp.insert(
13347 language_server_name.into(),
13348 LspSettings {
13349 binary: None,
13350 settings: None,
13351 initialization_options: None,
13352 enable_lsp_tasks: false,
13353 },
13354 );
13355 });
13356 cx.executor().run_until_parked();
13357 assert_eq!(
13358 server_restarts.load(atomic::Ordering::Acquire),
13359 2,
13360 "Should restart LSP server on another related LSP settings change"
13361 );
13362}
13363
13364#[gpui::test]
13365async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
13366 init_test(cx, |_| {});
13367
13368 let mut cx = EditorLspTestContext::new_rust(
13369 lsp::ServerCapabilities {
13370 completion_provider: Some(lsp::CompletionOptions {
13371 trigger_characters: Some(vec![".".to_string()]),
13372 resolve_provider: Some(true),
13373 ..Default::default()
13374 }),
13375 ..Default::default()
13376 },
13377 cx,
13378 )
13379 .await;
13380
13381 cx.set_state("fn main() { let a = 2ˇ; }");
13382 cx.simulate_keystroke(".");
13383 let completion_item = lsp::CompletionItem {
13384 label: "some".into(),
13385 kind: Some(lsp::CompletionItemKind::SNIPPET),
13386 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
13387 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
13388 kind: lsp::MarkupKind::Markdown,
13389 value: "```rust\nSome(2)\n```".to_string(),
13390 })),
13391 deprecated: Some(false),
13392 sort_text: Some("fffffff2".to_string()),
13393 filter_text: Some("some".to_string()),
13394 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
13395 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13396 range: lsp::Range {
13397 start: lsp::Position {
13398 line: 0,
13399 character: 22,
13400 },
13401 end: lsp::Position {
13402 line: 0,
13403 character: 22,
13404 },
13405 },
13406 new_text: "Some(2)".to_string(),
13407 })),
13408 additional_text_edits: Some(vec![lsp::TextEdit {
13409 range: lsp::Range {
13410 start: lsp::Position {
13411 line: 0,
13412 character: 20,
13413 },
13414 end: lsp::Position {
13415 line: 0,
13416 character: 22,
13417 },
13418 },
13419 new_text: "".to_string(),
13420 }]),
13421 ..Default::default()
13422 };
13423
13424 let closure_completion_item = completion_item.clone();
13425 let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
13426 let task_completion_item = closure_completion_item.clone();
13427 async move {
13428 Ok(Some(lsp::CompletionResponse::Array(vec![
13429 task_completion_item,
13430 ])))
13431 }
13432 });
13433
13434 request.next().await;
13435
13436 cx.condition(|editor, _| editor.context_menu_visible())
13437 .await;
13438 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13439 editor
13440 .confirm_completion(&ConfirmCompletion::default(), window, cx)
13441 .unwrap()
13442 });
13443 cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
13444
13445 cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
13446 let task_completion_item = completion_item.clone();
13447 async move { Ok(task_completion_item) }
13448 })
13449 .next()
13450 .await
13451 .unwrap();
13452 apply_additional_edits.await.unwrap();
13453 cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
13454}
13455
13456#[gpui::test]
13457async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
13458 init_test(cx, |_| {});
13459
13460 let mut cx = EditorLspTestContext::new_rust(
13461 lsp::ServerCapabilities {
13462 completion_provider: Some(lsp::CompletionOptions {
13463 trigger_characters: Some(vec![".".to_string()]),
13464 resolve_provider: Some(true),
13465 ..Default::default()
13466 }),
13467 ..Default::default()
13468 },
13469 cx,
13470 )
13471 .await;
13472
13473 cx.set_state("fn main() { let a = 2ˇ; }");
13474 cx.simulate_keystroke(".");
13475
13476 let item1 = lsp::CompletionItem {
13477 label: "method id()".to_string(),
13478 filter_text: Some("id".to_string()),
13479 detail: None,
13480 documentation: None,
13481 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13482 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
13483 new_text: ".id".to_string(),
13484 })),
13485 ..lsp::CompletionItem::default()
13486 };
13487
13488 let item2 = lsp::CompletionItem {
13489 label: "other".to_string(),
13490 filter_text: Some("other".to_string()),
13491 detail: None,
13492 documentation: None,
13493 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13494 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
13495 new_text: ".other".to_string(),
13496 })),
13497 ..lsp::CompletionItem::default()
13498 };
13499
13500 let item1 = item1.clone();
13501 cx.set_request_handler::<lsp::request::Completion, _, _>({
13502 let item1 = item1.clone();
13503 move |_, _, _| {
13504 let item1 = item1.clone();
13505 let item2 = item2.clone();
13506 async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
13507 }
13508 })
13509 .next()
13510 .await;
13511
13512 cx.condition(|editor, _| editor.context_menu_visible())
13513 .await;
13514 cx.update_editor(|editor, _, _| {
13515 let context_menu = editor.context_menu.borrow_mut();
13516 let context_menu = context_menu
13517 .as_ref()
13518 .expect("Should have the context menu deployed");
13519 match context_menu {
13520 CodeContextMenu::Completions(completions_menu) => {
13521 let completions = completions_menu.completions.borrow_mut();
13522 assert_eq!(
13523 completions
13524 .iter()
13525 .map(|completion| &completion.label.text)
13526 .collect::<Vec<_>>(),
13527 vec!["method id()", "other"]
13528 )
13529 }
13530 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
13531 }
13532 });
13533
13534 cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
13535 let item1 = item1.clone();
13536 move |_, item_to_resolve, _| {
13537 let item1 = item1.clone();
13538 async move {
13539 if item1 == item_to_resolve {
13540 Ok(lsp::CompletionItem {
13541 label: "method id()".to_string(),
13542 filter_text: Some("id".to_string()),
13543 detail: Some("Now resolved!".to_string()),
13544 documentation: Some(lsp::Documentation::String("Docs".to_string())),
13545 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13546 range: lsp::Range::new(
13547 lsp::Position::new(0, 22),
13548 lsp::Position::new(0, 22),
13549 ),
13550 new_text: ".id".to_string(),
13551 })),
13552 ..lsp::CompletionItem::default()
13553 })
13554 } else {
13555 Ok(item_to_resolve)
13556 }
13557 }
13558 }
13559 })
13560 .next()
13561 .await
13562 .unwrap();
13563 cx.run_until_parked();
13564
13565 cx.update_editor(|editor, window, cx| {
13566 editor.context_menu_next(&Default::default(), window, cx);
13567 });
13568
13569 cx.update_editor(|editor, _, _| {
13570 let context_menu = editor.context_menu.borrow_mut();
13571 let context_menu = context_menu
13572 .as_ref()
13573 .expect("Should have the context menu deployed");
13574 match context_menu {
13575 CodeContextMenu::Completions(completions_menu) => {
13576 let completions = completions_menu.completions.borrow_mut();
13577 assert_eq!(
13578 completions
13579 .iter()
13580 .map(|completion| &completion.label.text)
13581 .collect::<Vec<_>>(),
13582 vec!["method id() Now resolved!", "other"],
13583 "Should update first completion label, but not second as the filter text did not match."
13584 );
13585 }
13586 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
13587 }
13588 });
13589}
13590
13591#[gpui::test]
13592async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
13593 init_test(cx, |_| {});
13594
13595 let mut cx = EditorLspTestContext::new_rust(
13596 lsp::ServerCapabilities {
13597 completion_provider: Some(lsp::CompletionOptions {
13598 trigger_characters: Some(vec![".".to_string()]),
13599 resolve_provider: Some(true),
13600 ..Default::default()
13601 }),
13602 ..Default::default()
13603 },
13604 cx,
13605 )
13606 .await;
13607
13608 cx.set_state("fn main() { let a = 2ˇ; }");
13609 cx.simulate_keystroke(".");
13610
13611 let unresolved_item_1 = lsp::CompletionItem {
13612 label: "id".to_string(),
13613 filter_text: Some("id".to_string()),
13614 detail: None,
13615 documentation: None,
13616 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13617 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
13618 new_text: ".id".to_string(),
13619 })),
13620 ..lsp::CompletionItem::default()
13621 };
13622 let resolved_item_1 = lsp::CompletionItem {
13623 additional_text_edits: Some(vec![lsp::TextEdit {
13624 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
13625 new_text: "!!".to_string(),
13626 }]),
13627 ..unresolved_item_1.clone()
13628 };
13629 let unresolved_item_2 = lsp::CompletionItem {
13630 label: "other".to_string(),
13631 filter_text: Some("other".to_string()),
13632 detail: None,
13633 documentation: None,
13634 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13635 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
13636 new_text: ".other".to_string(),
13637 })),
13638 ..lsp::CompletionItem::default()
13639 };
13640 let resolved_item_2 = lsp::CompletionItem {
13641 additional_text_edits: Some(vec![lsp::TextEdit {
13642 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
13643 new_text: "??".to_string(),
13644 }]),
13645 ..unresolved_item_2.clone()
13646 };
13647
13648 let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
13649 let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
13650 cx.lsp
13651 .server
13652 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
13653 let unresolved_item_1 = unresolved_item_1.clone();
13654 let resolved_item_1 = resolved_item_1.clone();
13655 let unresolved_item_2 = unresolved_item_2.clone();
13656 let resolved_item_2 = resolved_item_2.clone();
13657 let resolve_requests_1 = resolve_requests_1.clone();
13658 let resolve_requests_2 = resolve_requests_2.clone();
13659 move |unresolved_request, _| {
13660 let unresolved_item_1 = unresolved_item_1.clone();
13661 let resolved_item_1 = resolved_item_1.clone();
13662 let unresolved_item_2 = unresolved_item_2.clone();
13663 let resolved_item_2 = resolved_item_2.clone();
13664 let resolve_requests_1 = resolve_requests_1.clone();
13665 let resolve_requests_2 = resolve_requests_2.clone();
13666 async move {
13667 if unresolved_request == unresolved_item_1 {
13668 resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
13669 Ok(resolved_item_1.clone())
13670 } else if unresolved_request == unresolved_item_2 {
13671 resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
13672 Ok(resolved_item_2.clone())
13673 } else {
13674 panic!("Unexpected completion item {unresolved_request:?}")
13675 }
13676 }
13677 }
13678 })
13679 .detach();
13680
13681 cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
13682 let unresolved_item_1 = unresolved_item_1.clone();
13683 let unresolved_item_2 = unresolved_item_2.clone();
13684 async move {
13685 Ok(Some(lsp::CompletionResponse::Array(vec![
13686 unresolved_item_1,
13687 unresolved_item_2,
13688 ])))
13689 }
13690 })
13691 .next()
13692 .await;
13693
13694 cx.condition(|editor, _| editor.context_menu_visible())
13695 .await;
13696 cx.update_editor(|editor, _, _| {
13697 let context_menu = editor.context_menu.borrow_mut();
13698 let context_menu = context_menu
13699 .as_ref()
13700 .expect("Should have the context menu deployed");
13701 match context_menu {
13702 CodeContextMenu::Completions(completions_menu) => {
13703 let completions = completions_menu.completions.borrow_mut();
13704 assert_eq!(
13705 completions
13706 .iter()
13707 .map(|completion| &completion.label.text)
13708 .collect::<Vec<_>>(),
13709 vec!["id", "other"]
13710 )
13711 }
13712 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
13713 }
13714 });
13715 cx.run_until_parked();
13716
13717 cx.update_editor(|editor, window, cx| {
13718 editor.context_menu_next(&ContextMenuNext, window, cx);
13719 });
13720 cx.run_until_parked();
13721 cx.update_editor(|editor, window, cx| {
13722 editor.context_menu_prev(&ContextMenuPrevious, window, cx);
13723 });
13724 cx.run_until_parked();
13725 cx.update_editor(|editor, window, cx| {
13726 editor.context_menu_next(&ContextMenuNext, window, cx);
13727 });
13728 cx.run_until_parked();
13729 cx.update_editor(|editor, window, cx| {
13730 editor
13731 .compose_completion(&ComposeCompletion::default(), window, cx)
13732 .expect("No task returned")
13733 })
13734 .await
13735 .expect("Completion failed");
13736 cx.run_until_parked();
13737
13738 cx.update_editor(|editor, _, cx| {
13739 assert_eq!(
13740 resolve_requests_1.load(atomic::Ordering::Acquire),
13741 1,
13742 "Should always resolve once despite multiple selections"
13743 );
13744 assert_eq!(
13745 resolve_requests_2.load(atomic::Ordering::Acquire),
13746 1,
13747 "Should always resolve once after multiple selections and applying the completion"
13748 );
13749 assert_eq!(
13750 editor.text(cx),
13751 "fn main() { let a = ??.other; }",
13752 "Should use resolved data when applying the completion"
13753 );
13754 });
13755}
13756
13757#[gpui::test]
13758async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
13759 init_test(cx, |_| {});
13760
13761 let item_0 = lsp::CompletionItem {
13762 label: "abs".into(),
13763 insert_text: Some("abs".into()),
13764 data: Some(json!({ "very": "special"})),
13765 insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
13766 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
13767 lsp::InsertReplaceEdit {
13768 new_text: "abs".to_string(),
13769 insert: lsp::Range::default(),
13770 replace: lsp::Range::default(),
13771 },
13772 )),
13773 ..lsp::CompletionItem::default()
13774 };
13775 let items = iter::once(item_0.clone())
13776 .chain((11..51).map(|i| lsp::CompletionItem {
13777 label: format!("item_{}", i),
13778 insert_text: Some(format!("item_{}", i)),
13779 insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
13780 ..lsp::CompletionItem::default()
13781 }))
13782 .collect::<Vec<_>>();
13783
13784 let default_commit_characters = vec!["?".to_string()];
13785 let default_data = json!({ "default": "data"});
13786 let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
13787 let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
13788 let default_edit_range = lsp::Range {
13789 start: lsp::Position {
13790 line: 0,
13791 character: 5,
13792 },
13793 end: lsp::Position {
13794 line: 0,
13795 character: 5,
13796 },
13797 };
13798
13799 let mut cx = EditorLspTestContext::new_rust(
13800 lsp::ServerCapabilities {
13801 completion_provider: Some(lsp::CompletionOptions {
13802 trigger_characters: Some(vec![".".to_string()]),
13803 resolve_provider: Some(true),
13804 ..Default::default()
13805 }),
13806 ..Default::default()
13807 },
13808 cx,
13809 )
13810 .await;
13811
13812 cx.set_state("fn main() { let a = 2ˇ; }");
13813 cx.simulate_keystroke(".");
13814
13815 let completion_data = default_data.clone();
13816 let completion_characters = default_commit_characters.clone();
13817 let completion_items = items.clone();
13818 cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
13819 let default_data = completion_data.clone();
13820 let default_commit_characters = completion_characters.clone();
13821 let items = completion_items.clone();
13822 async move {
13823 Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13824 items,
13825 item_defaults: Some(lsp::CompletionListItemDefaults {
13826 data: Some(default_data.clone()),
13827 commit_characters: Some(default_commit_characters.clone()),
13828 edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
13829 default_edit_range,
13830 )),
13831 insert_text_format: Some(default_insert_text_format),
13832 insert_text_mode: Some(default_insert_text_mode),
13833 }),
13834 ..lsp::CompletionList::default()
13835 })))
13836 }
13837 })
13838 .next()
13839 .await;
13840
13841 let resolved_items = Arc::new(Mutex::new(Vec::new()));
13842 cx.lsp
13843 .server
13844 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
13845 let closure_resolved_items = resolved_items.clone();
13846 move |item_to_resolve, _| {
13847 let closure_resolved_items = closure_resolved_items.clone();
13848 async move {
13849 closure_resolved_items.lock().push(item_to_resolve.clone());
13850 Ok(item_to_resolve)
13851 }
13852 }
13853 })
13854 .detach();
13855
13856 cx.condition(|editor, _| editor.context_menu_visible())
13857 .await;
13858 cx.run_until_parked();
13859 cx.update_editor(|editor, _, _| {
13860 let menu = editor.context_menu.borrow_mut();
13861 match menu.as_ref().expect("should have the completions menu") {
13862 CodeContextMenu::Completions(completions_menu) => {
13863 assert_eq!(
13864 completions_menu
13865 .entries
13866 .borrow()
13867 .iter()
13868 .map(|mat| mat.string.clone())
13869 .collect::<Vec<String>>(),
13870 items
13871 .iter()
13872 .map(|completion| completion.label.clone())
13873 .collect::<Vec<String>>()
13874 );
13875 }
13876 CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
13877 }
13878 });
13879 // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
13880 // with 4 from the end.
13881 assert_eq!(
13882 *resolved_items.lock(),
13883 [&items[0..16], &items[items.len() - 4..items.len()]]
13884 .concat()
13885 .iter()
13886 .cloned()
13887 .map(|mut item| {
13888 if item.data.is_none() {
13889 item.data = Some(default_data.clone());
13890 }
13891 item
13892 })
13893 .collect::<Vec<lsp::CompletionItem>>(),
13894 "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
13895 );
13896 resolved_items.lock().clear();
13897
13898 cx.update_editor(|editor, window, cx| {
13899 editor.context_menu_prev(&ContextMenuPrevious, window, cx);
13900 });
13901 cx.run_until_parked();
13902 // Completions that have already been resolved are skipped.
13903 assert_eq!(
13904 *resolved_items.lock(),
13905 items[items.len() - 16..items.len() - 4]
13906 .iter()
13907 .cloned()
13908 .map(|mut item| {
13909 if item.data.is_none() {
13910 item.data = Some(default_data.clone());
13911 }
13912 item
13913 })
13914 .collect::<Vec<lsp::CompletionItem>>()
13915 );
13916 resolved_items.lock().clear();
13917}
13918
13919#[gpui::test]
13920async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
13921 init_test(cx, |_| {});
13922
13923 let mut cx = EditorLspTestContext::new(
13924 Language::new(
13925 LanguageConfig {
13926 matcher: LanguageMatcher {
13927 path_suffixes: vec!["jsx".into()],
13928 ..Default::default()
13929 },
13930 overrides: [(
13931 "element".into(),
13932 LanguageConfigOverride {
13933 completion_query_characters: Override::Set(['-'].into_iter().collect()),
13934 ..Default::default()
13935 },
13936 )]
13937 .into_iter()
13938 .collect(),
13939 ..Default::default()
13940 },
13941 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
13942 )
13943 .with_override_query("(jsx_self_closing_element) @element")
13944 .unwrap(),
13945 lsp::ServerCapabilities {
13946 completion_provider: Some(lsp::CompletionOptions {
13947 trigger_characters: Some(vec![":".to_string()]),
13948 ..Default::default()
13949 }),
13950 ..Default::default()
13951 },
13952 cx,
13953 )
13954 .await;
13955
13956 cx.lsp
13957 .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13958 Ok(Some(lsp::CompletionResponse::Array(vec![
13959 lsp::CompletionItem {
13960 label: "bg-blue".into(),
13961 ..Default::default()
13962 },
13963 lsp::CompletionItem {
13964 label: "bg-red".into(),
13965 ..Default::default()
13966 },
13967 lsp::CompletionItem {
13968 label: "bg-yellow".into(),
13969 ..Default::default()
13970 },
13971 ])))
13972 });
13973
13974 cx.set_state(r#"<p class="bgˇ" />"#);
13975
13976 // Trigger completion when typing a dash, because the dash is an extra
13977 // word character in the 'element' scope, which contains the cursor.
13978 cx.simulate_keystroke("-");
13979 cx.executor().run_until_parked();
13980 cx.update_editor(|editor, _, _| {
13981 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13982 {
13983 assert_eq!(
13984 completion_menu_entries(&menu),
13985 &["bg-red", "bg-blue", "bg-yellow"]
13986 );
13987 } else {
13988 panic!("expected completion menu to be open");
13989 }
13990 });
13991
13992 cx.simulate_keystroke("l");
13993 cx.executor().run_until_parked();
13994 cx.update_editor(|editor, _, _| {
13995 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13996 {
13997 assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
13998 } else {
13999 panic!("expected completion menu to be open");
14000 }
14001 });
14002
14003 // When filtering completions, consider the character after the '-' to
14004 // be the start of a subword.
14005 cx.set_state(r#"<p class="yelˇ" />"#);
14006 cx.simulate_keystroke("l");
14007 cx.executor().run_until_parked();
14008 cx.update_editor(|editor, _, _| {
14009 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14010 {
14011 assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
14012 } else {
14013 panic!("expected completion menu to be open");
14014 }
14015 });
14016}
14017
14018fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
14019 let entries = menu.entries.borrow();
14020 entries.iter().map(|mat| mat.string.clone()).collect()
14021}
14022
14023#[gpui::test]
14024async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
14025 init_test(cx, |settings| {
14026 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
14027 FormatterList(vec![Formatter::Prettier].into()),
14028 ))
14029 });
14030
14031 let fs = FakeFs::new(cx.executor());
14032 fs.insert_file(path!("/file.ts"), Default::default()).await;
14033
14034 let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
14035 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14036
14037 language_registry.add(Arc::new(Language::new(
14038 LanguageConfig {
14039 name: "TypeScript".into(),
14040 matcher: LanguageMatcher {
14041 path_suffixes: vec!["ts".to_string()],
14042 ..Default::default()
14043 },
14044 ..Default::default()
14045 },
14046 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
14047 )));
14048 update_test_language_settings(cx, |settings| {
14049 settings.defaults.prettier = Some(PrettierSettings {
14050 allowed: true,
14051 ..PrettierSettings::default()
14052 });
14053 });
14054
14055 let test_plugin = "test_plugin";
14056 let _ = language_registry.register_fake_lsp(
14057 "TypeScript",
14058 FakeLspAdapter {
14059 prettier_plugins: vec![test_plugin],
14060 ..Default::default()
14061 },
14062 );
14063
14064 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
14065 let buffer = project
14066 .update(cx, |project, cx| {
14067 project.open_local_buffer(path!("/file.ts"), cx)
14068 })
14069 .await
14070 .unwrap();
14071
14072 let buffer_text = "one\ntwo\nthree\n";
14073 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
14074 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
14075 editor.update_in(cx, |editor, window, cx| {
14076 editor.set_text(buffer_text, window, cx)
14077 });
14078
14079 editor
14080 .update_in(cx, |editor, window, cx| {
14081 editor.perform_format(
14082 project.clone(),
14083 FormatTrigger::Manual,
14084 FormatTarget::Buffers,
14085 window,
14086 cx,
14087 )
14088 })
14089 .unwrap()
14090 .await;
14091 assert_eq!(
14092 editor.update(cx, |editor, cx| editor.text(cx)),
14093 buffer_text.to_string() + prettier_format_suffix,
14094 "Test prettier formatting was not applied to the original buffer text",
14095 );
14096
14097 update_test_language_settings(cx, |settings| {
14098 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
14099 });
14100 let format = editor.update_in(cx, |editor, window, cx| {
14101 editor.perform_format(
14102 project.clone(),
14103 FormatTrigger::Manual,
14104 FormatTarget::Buffers,
14105 window,
14106 cx,
14107 )
14108 });
14109 format.await.unwrap();
14110 assert_eq!(
14111 editor.update(cx, |editor, cx| editor.text(cx)),
14112 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
14113 "Autoformatting (via test prettier) was not applied to the original buffer text",
14114 );
14115}
14116
14117#[gpui::test]
14118async fn test_addition_reverts(cx: &mut TestAppContext) {
14119 init_test(cx, |_| {});
14120 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14121 let base_text = indoc! {r#"
14122 struct Row;
14123 struct Row1;
14124 struct Row2;
14125
14126 struct Row4;
14127 struct Row5;
14128 struct Row6;
14129
14130 struct Row8;
14131 struct Row9;
14132 struct Row10;"#};
14133
14134 // When addition hunks are not adjacent to carets, no hunk revert is performed
14135 assert_hunk_revert(
14136 indoc! {r#"struct Row;
14137 struct Row1;
14138 struct Row1.1;
14139 struct Row1.2;
14140 struct Row2;ˇ
14141
14142 struct Row4;
14143 struct Row5;
14144 struct Row6;
14145
14146 struct Row8;
14147 ˇstruct Row9;
14148 struct Row9.1;
14149 struct Row9.2;
14150 struct Row9.3;
14151 struct Row10;"#},
14152 vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
14153 indoc! {r#"struct Row;
14154 struct Row1;
14155 struct Row1.1;
14156 struct Row1.2;
14157 struct Row2;ˇ
14158
14159 struct Row4;
14160 struct Row5;
14161 struct Row6;
14162
14163 struct Row8;
14164 ˇstruct Row9;
14165 struct Row9.1;
14166 struct Row9.2;
14167 struct Row9.3;
14168 struct Row10;"#},
14169 base_text,
14170 &mut cx,
14171 );
14172 // Same for selections
14173 assert_hunk_revert(
14174 indoc! {r#"struct Row;
14175 struct Row1;
14176 struct Row2;
14177 struct Row2.1;
14178 struct Row2.2;
14179 «ˇ
14180 struct Row4;
14181 struct» Row5;
14182 «struct Row6;
14183 ˇ»
14184 struct Row9.1;
14185 struct Row9.2;
14186 struct Row9.3;
14187 struct Row8;
14188 struct Row9;
14189 struct Row10;"#},
14190 vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
14191 indoc! {r#"struct Row;
14192 struct Row1;
14193 struct Row2;
14194 struct Row2.1;
14195 struct Row2.2;
14196 «ˇ
14197 struct Row4;
14198 struct» Row5;
14199 «struct Row6;
14200 ˇ»
14201 struct Row9.1;
14202 struct Row9.2;
14203 struct Row9.3;
14204 struct Row8;
14205 struct Row9;
14206 struct Row10;"#},
14207 base_text,
14208 &mut cx,
14209 );
14210
14211 // When carets and selections intersect the addition hunks, those are reverted.
14212 // Adjacent carets got merged.
14213 assert_hunk_revert(
14214 indoc! {r#"struct Row;
14215 ˇ// something on the top
14216 struct Row1;
14217 struct Row2;
14218 struct Roˇw3.1;
14219 struct Row2.2;
14220 struct Row2.3;ˇ
14221
14222 struct Row4;
14223 struct ˇRow5.1;
14224 struct Row5.2;
14225 struct «Rowˇ»5.3;
14226 struct Row5;
14227 struct Row6;
14228 ˇ
14229 struct Row9.1;
14230 struct «Rowˇ»9.2;
14231 struct «ˇRow»9.3;
14232 struct Row8;
14233 struct Row9;
14234 «ˇ// something on bottom»
14235 struct Row10;"#},
14236 vec![
14237 DiffHunkStatusKind::Added,
14238 DiffHunkStatusKind::Added,
14239 DiffHunkStatusKind::Added,
14240 DiffHunkStatusKind::Added,
14241 DiffHunkStatusKind::Added,
14242 ],
14243 indoc! {r#"struct Row;
14244 ˇstruct Row1;
14245 struct Row2;
14246 ˇ
14247 struct Row4;
14248 ˇstruct Row5;
14249 struct Row6;
14250 ˇ
14251 ˇstruct Row8;
14252 struct Row9;
14253 ˇstruct Row10;"#},
14254 base_text,
14255 &mut cx,
14256 );
14257}
14258
14259#[gpui::test]
14260async fn test_modification_reverts(cx: &mut TestAppContext) {
14261 init_test(cx, |_| {});
14262 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14263 let base_text = indoc! {r#"
14264 struct Row;
14265 struct Row1;
14266 struct Row2;
14267
14268 struct Row4;
14269 struct Row5;
14270 struct Row6;
14271
14272 struct Row8;
14273 struct Row9;
14274 struct Row10;"#};
14275
14276 // Modification hunks behave the same as the addition ones.
14277 assert_hunk_revert(
14278 indoc! {r#"struct Row;
14279 struct Row1;
14280 struct Row33;
14281 ˇ
14282 struct Row4;
14283 struct Row5;
14284 struct Row6;
14285 ˇ
14286 struct Row99;
14287 struct Row9;
14288 struct Row10;"#},
14289 vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
14290 indoc! {r#"struct Row;
14291 struct Row1;
14292 struct Row33;
14293 ˇ
14294 struct Row4;
14295 struct Row5;
14296 struct Row6;
14297 ˇ
14298 struct Row99;
14299 struct Row9;
14300 struct Row10;"#},
14301 base_text,
14302 &mut cx,
14303 );
14304 assert_hunk_revert(
14305 indoc! {r#"struct Row;
14306 struct Row1;
14307 struct Row33;
14308 «ˇ
14309 struct Row4;
14310 struct» Row5;
14311 «struct Row6;
14312 ˇ»
14313 struct Row99;
14314 struct Row9;
14315 struct Row10;"#},
14316 vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
14317 indoc! {r#"struct Row;
14318 struct Row1;
14319 struct Row33;
14320 «ˇ
14321 struct Row4;
14322 struct» Row5;
14323 «struct Row6;
14324 ˇ»
14325 struct Row99;
14326 struct Row9;
14327 struct Row10;"#},
14328 base_text,
14329 &mut cx,
14330 );
14331
14332 assert_hunk_revert(
14333 indoc! {r#"ˇstruct Row1.1;
14334 struct Row1;
14335 «ˇstr»uct Row22;
14336
14337 struct ˇRow44;
14338 struct Row5;
14339 struct «Rˇ»ow66;ˇ
14340
14341 «struˇ»ct Row88;
14342 struct Row9;
14343 struct Row1011;ˇ"#},
14344 vec![
14345 DiffHunkStatusKind::Modified,
14346 DiffHunkStatusKind::Modified,
14347 DiffHunkStatusKind::Modified,
14348 DiffHunkStatusKind::Modified,
14349 DiffHunkStatusKind::Modified,
14350 DiffHunkStatusKind::Modified,
14351 ],
14352 indoc! {r#"struct Row;
14353 ˇstruct Row1;
14354 struct Row2;
14355 ˇ
14356 struct Row4;
14357 ˇstruct Row5;
14358 struct Row6;
14359 ˇ
14360 struct Row8;
14361 ˇstruct Row9;
14362 struct Row10;ˇ"#},
14363 base_text,
14364 &mut cx,
14365 );
14366}
14367
14368#[gpui::test]
14369async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
14370 init_test(cx, |_| {});
14371 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14372 let base_text = indoc! {r#"
14373 one
14374
14375 two
14376 three
14377 "#};
14378
14379 cx.set_head_text(base_text);
14380 cx.set_state("\nˇ\n");
14381 cx.executor().run_until_parked();
14382 cx.update_editor(|editor, _window, cx| {
14383 editor.expand_selected_diff_hunks(cx);
14384 });
14385 cx.executor().run_until_parked();
14386 cx.update_editor(|editor, window, cx| {
14387 editor.backspace(&Default::default(), window, cx);
14388 });
14389 cx.run_until_parked();
14390 cx.assert_state_with_diff(
14391 indoc! {r#"
14392
14393 - two
14394 - threeˇ
14395 +
14396 "#}
14397 .to_string(),
14398 );
14399}
14400
14401#[gpui::test]
14402async fn test_deletion_reverts(cx: &mut TestAppContext) {
14403 init_test(cx, |_| {});
14404 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14405 let base_text = indoc! {r#"struct Row;
14406struct Row1;
14407struct Row2;
14408
14409struct Row4;
14410struct Row5;
14411struct Row6;
14412
14413struct Row8;
14414struct Row9;
14415struct Row10;"#};
14416
14417 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
14418 assert_hunk_revert(
14419 indoc! {r#"struct Row;
14420 struct Row2;
14421
14422 ˇstruct Row4;
14423 struct Row5;
14424 struct Row6;
14425 ˇ
14426 struct Row8;
14427 struct Row10;"#},
14428 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
14429 indoc! {r#"struct Row;
14430 struct Row2;
14431
14432 ˇstruct Row4;
14433 struct Row5;
14434 struct Row6;
14435 ˇ
14436 struct Row8;
14437 struct Row10;"#},
14438 base_text,
14439 &mut cx,
14440 );
14441 assert_hunk_revert(
14442 indoc! {r#"struct Row;
14443 struct Row2;
14444
14445 «ˇstruct Row4;
14446 struct» Row5;
14447 «struct Row6;
14448 ˇ»
14449 struct Row8;
14450 struct Row10;"#},
14451 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
14452 indoc! {r#"struct Row;
14453 struct Row2;
14454
14455 «ˇstruct Row4;
14456 struct» Row5;
14457 «struct Row6;
14458 ˇ»
14459 struct Row8;
14460 struct Row10;"#},
14461 base_text,
14462 &mut cx,
14463 );
14464
14465 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
14466 assert_hunk_revert(
14467 indoc! {r#"struct Row;
14468 ˇstruct Row2;
14469
14470 struct Row4;
14471 struct Row5;
14472 struct Row6;
14473
14474 struct Row8;ˇ
14475 struct Row10;"#},
14476 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
14477 indoc! {r#"struct Row;
14478 struct Row1;
14479 ˇstruct Row2;
14480
14481 struct Row4;
14482 struct Row5;
14483 struct Row6;
14484
14485 struct Row8;ˇ
14486 struct Row9;
14487 struct Row10;"#},
14488 base_text,
14489 &mut cx,
14490 );
14491 assert_hunk_revert(
14492 indoc! {r#"struct Row;
14493 struct Row2«ˇ;
14494 struct Row4;
14495 struct» Row5;
14496 «struct Row6;
14497
14498 struct Row8;ˇ»
14499 struct Row10;"#},
14500 vec![
14501 DiffHunkStatusKind::Deleted,
14502 DiffHunkStatusKind::Deleted,
14503 DiffHunkStatusKind::Deleted,
14504 ],
14505 indoc! {r#"struct Row;
14506 struct Row1;
14507 struct Row2«ˇ;
14508
14509 struct Row4;
14510 struct» Row5;
14511 «struct Row6;
14512
14513 struct Row8;ˇ»
14514 struct Row9;
14515 struct Row10;"#},
14516 base_text,
14517 &mut cx,
14518 );
14519}
14520
14521#[gpui::test]
14522async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
14523 init_test(cx, |_| {});
14524
14525 let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
14526 let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
14527 let base_text_3 =
14528 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
14529
14530 let text_1 = edit_first_char_of_every_line(base_text_1);
14531 let text_2 = edit_first_char_of_every_line(base_text_2);
14532 let text_3 = edit_first_char_of_every_line(base_text_3);
14533
14534 let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
14535 let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
14536 let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
14537
14538 let multibuffer = cx.new(|cx| {
14539 let mut multibuffer = MultiBuffer::new(ReadWrite);
14540 multibuffer.push_excerpts(
14541 buffer_1.clone(),
14542 [
14543 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14544 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14545 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
14546 ],
14547 cx,
14548 );
14549 multibuffer.push_excerpts(
14550 buffer_2.clone(),
14551 [
14552 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14553 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14554 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
14555 ],
14556 cx,
14557 );
14558 multibuffer.push_excerpts(
14559 buffer_3.clone(),
14560 [
14561 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14562 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14563 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
14564 ],
14565 cx,
14566 );
14567 multibuffer
14568 });
14569
14570 let fs = FakeFs::new(cx.executor());
14571 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
14572 let (editor, cx) = cx
14573 .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
14574 editor.update_in(cx, |editor, _window, cx| {
14575 for (buffer, diff_base) in [
14576 (buffer_1.clone(), base_text_1),
14577 (buffer_2.clone(), base_text_2),
14578 (buffer_3.clone(), base_text_3),
14579 ] {
14580 let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
14581 editor
14582 .buffer
14583 .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
14584 }
14585 });
14586 cx.executor().run_until_parked();
14587
14588 editor.update_in(cx, |editor, window, cx| {
14589 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}");
14590 editor.select_all(&SelectAll, window, cx);
14591 editor.git_restore(&Default::default(), window, cx);
14592 });
14593 cx.executor().run_until_parked();
14594
14595 // When all ranges are selected, all buffer hunks are reverted.
14596 editor.update(cx, |editor, cx| {
14597 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");
14598 });
14599 buffer_1.update(cx, |buffer, _| {
14600 assert_eq!(buffer.text(), base_text_1);
14601 });
14602 buffer_2.update(cx, |buffer, _| {
14603 assert_eq!(buffer.text(), base_text_2);
14604 });
14605 buffer_3.update(cx, |buffer, _| {
14606 assert_eq!(buffer.text(), base_text_3);
14607 });
14608
14609 editor.update_in(cx, |editor, window, cx| {
14610 editor.undo(&Default::default(), window, cx);
14611 });
14612
14613 editor.update_in(cx, |editor, window, cx| {
14614 editor.change_selections(None, window, cx, |s| {
14615 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
14616 });
14617 editor.git_restore(&Default::default(), window, cx);
14618 });
14619
14620 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
14621 // but not affect buffer_2 and its related excerpts.
14622 editor.update(cx, |editor, cx| {
14623 assert_eq!(
14624 editor.text(cx),
14625 "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}"
14626 );
14627 });
14628 buffer_1.update(cx, |buffer, _| {
14629 assert_eq!(buffer.text(), base_text_1);
14630 });
14631 buffer_2.update(cx, |buffer, _| {
14632 assert_eq!(
14633 buffer.text(),
14634 "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
14635 );
14636 });
14637 buffer_3.update(cx, |buffer, _| {
14638 assert_eq!(
14639 buffer.text(),
14640 "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
14641 );
14642 });
14643
14644 fn edit_first_char_of_every_line(text: &str) -> String {
14645 text.split('\n')
14646 .map(|line| format!("X{}", &line[1..]))
14647 .collect::<Vec<_>>()
14648 .join("\n")
14649 }
14650}
14651
14652#[gpui::test]
14653async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
14654 init_test(cx, |_| {});
14655
14656 let cols = 4;
14657 let rows = 10;
14658 let sample_text_1 = sample_text(rows, cols, 'a');
14659 assert_eq!(
14660 sample_text_1,
14661 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
14662 );
14663 let sample_text_2 = sample_text(rows, cols, 'l');
14664 assert_eq!(
14665 sample_text_2,
14666 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
14667 );
14668 let sample_text_3 = sample_text(rows, cols, 'v');
14669 assert_eq!(
14670 sample_text_3,
14671 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
14672 );
14673
14674 let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
14675 let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
14676 let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
14677
14678 let multi_buffer = cx.new(|cx| {
14679 let mut multibuffer = MultiBuffer::new(ReadWrite);
14680 multibuffer.push_excerpts(
14681 buffer_1.clone(),
14682 [
14683 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14684 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14685 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
14686 ],
14687 cx,
14688 );
14689 multibuffer.push_excerpts(
14690 buffer_2.clone(),
14691 [
14692 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14693 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14694 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
14695 ],
14696 cx,
14697 );
14698 multibuffer.push_excerpts(
14699 buffer_3.clone(),
14700 [
14701 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14702 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14703 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
14704 ],
14705 cx,
14706 );
14707 multibuffer
14708 });
14709
14710 let fs = FakeFs::new(cx.executor());
14711 fs.insert_tree(
14712 "/a",
14713 json!({
14714 "main.rs": sample_text_1,
14715 "other.rs": sample_text_2,
14716 "lib.rs": sample_text_3,
14717 }),
14718 )
14719 .await;
14720 let project = Project::test(fs, ["/a".as_ref()], cx).await;
14721 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14722 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14723 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
14724 Editor::new(
14725 EditorMode::full(),
14726 multi_buffer,
14727 Some(project.clone()),
14728 window,
14729 cx,
14730 )
14731 });
14732 let multibuffer_item_id = workspace
14733 .update(cx, |workspace, window, cx| {
14734 assert!(
14735 workspace.active_item(cx).is_none(),
14736 "active item should be None before the first item is added"
14737 );
14738 workspace.add_item_to_active_pane(
14739 Box::new(multi_buffer_editor.clone()),
14740 None,
14741 true,
14742 window,
14743 cx,
14744 );
14745 let active_item = workspace
14746 .active_item(cx)
14747 .expect("should have an active item after adding the multi buffer");
14748 assert!(
14749 !active_item.is_singleton(cx),
14750 "A multi buffer was expected to active after adding"
14751 );
14752 active_item.item_id()
14753 })
14754 .unwrap();
14755 cx.executor().run_until_parked();
14756
14757 multi_buffer_editor.update_in(cx, |editor, window, cx| {
14758 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
14759 s.select_ranges(Some(1..2))
14760 });
14761 editor.open_excerpts(&OpenExcerpts, window, cx);
14762 });
14763 cx.executor().run_until_parked();
14764 let first_item_id = workspace
14765 .update(cx, |workspace, window, cx| {
14766 let active_item = workspace
14767 .active_item(cx)
14768 .expect("should have an active item after navigating into the 1st buffer");
14769 let first_item_id = active_item.item_id();
14770 assert_ne!(
14771 first_item_id, multibuffer_item_id,
14772 "Should navigate into the 1st buffer and activate it"
14773 );
14774 assert!(
14775 active_item.is_singleton(cx),
14776 "New active item should be a singleton buffer"
14777 );
14778 assert_eq!(
14779 active_item
14780 .act_as::<Editor>(cx)
14781 .expect("should have navigated into an editor for the 1st buffer")
14782 .read(cx)
14783 .text(cx),
14784 sample_text_1
14785 );
14786
14787 workspace
14788 .go_back(workspace.active_pane().downgrade(), window, cx)
14789 .detach_and_log_err(cx);
14790
14791 first_item_id
14792 })
14793 .unwrap();
14794 cx.executor().run_until_parked();
14795 workspace
14796 .update(cx, |workspace, _, cx| {
14797 let active_item = workspace
14798 .active_item(cx)
14799 .expect("should have an active item after navigating back");
14800 assert_eq!(
14801 active_item.item_id(),
14802 multibuffer_item_id,
14803 "Should navigate back to the multi buffer"
14804 );
14805 assert!(!active_item.is_singleton(cx));
14806 })
14807 .unwrap();
14808
14809 multi_buffer_editor.update_in(cx, |editor, window, cx| {
14810 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
14811 s.select_ranges(Some(39..40))
14812 });
14813 editor.open_excerpts(&OpenExcerpts, window, cx);
14814 });
14815 cx.executor().run_until_parked();
14816 let second_item_id = workspace
14817 .update(cx, |workspace, window, cx| {
14818 let active_item = workspace
14819 .active_item(cx)
14820 .expect("should have an active item after navigating into the 2nd buffer");
14821 let second_item_id = active_item.item_id();
14822 assert_ne!(
14823 second_item_id, multibuffer_item_id,
14824 "Should navigate away from the multibuffer"
14825 );
14826 assert_ne!(
14827 second_item_id, first_item_id,
14828 "Should navigate into the 2nd buffer and activate it"
14829 );
14830 assert!(
14831 active_item.is_singleton(cx),
14832 "New active item should be a singleton buffer"
14833 );
14834 assert_eq!(
14835 active_item
14836 .act_as::<Editor>(cx)
14837 .expect("should have navigated into an editor")
14838 .read(cx)
14839 .text(cx),
14840 sample_text_2
14841 );
14842
14843 workspace
14844 .go_back(workspace.active_pane().downgrade(), window, cx)
14845 .detach_and_log_err(cx);
14846
14847 second_item_id
14848 })
14849 .unwrap();
14850 cx.executor().run_until_parked();
14851 workspace
14852 .update(cx, |workspace, _, cx| {
14853 let active_item = workspace
14854 .active_item(cx)
14855 .expect("should have an active item after navigating back from the 2nd buffer");
14856 assert_eq!(
14857 active_item.item_id(),
14858 multibuffer_item_id,
14859 "Should navigate back from the 2nd buffer to the multi buffer"
14860 );
14861 assert!(!active_item.is_singleton(cx));
14862 })
14863 .unwrap();
14864
14865 multi_buffer_editor.update_in(cx, |editor, window, cx| {
14866 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
14867 s.select_ranges(Some(70..70))
14868 });
14869 editor.open_excerpts(&OpenExcerpts, window, cx);
14870 });
14871 cx.executor().run_until_parked();
14872 workspace
14873 .update(cx, |workspace, window, cx| {
14874 let active_item = workspace
14875 .active_item(cx)
14876 .expect("should have an active item after navigating into the 3rd buffer");
14877 let third_item_id = active_item.item_id();
14878 assert_ne!(
14879 third_item_id, multibuffer_item_id,
14880 "Should navigate into the 3rd buffer and activate it"
14881 );
14882 assert_ne!(third_item_id, first_item_id);
14883 assert_ne!(third_item_id, second_item_id);
14884 assert!(
14885 active_item.is_singleton(cx),
14886 "New active item should be a singleton buffer"
14887 );
14888 assert_eq!(
14889 active_item
14890 .act_as::<Editor>(cx)
14891 .expect("should have navigated into an editor")
14892 .read(cx)
14893 .text(cx),
14894 sample_text_3
14895 );
14896
14897 workspace
14898 .go_back(workspace.active_pane().downgrade(), window, cx)
14899 .detach_and_log_err(cx);
14900 })
14901 .unwrap();
14902 cx.executor().run_until_parked();
14903 workspace
14904 .update(cx, |workspace, _, cx| {
14905 let active_item = workspace
14906 .active_item(cx)
14907 .expect("should have an active item after navigating back from the 3rd buffer");
14908 assert_eq!(
14909 active_item.item_id(),
14910 multibuffer_item_id,
14911 "Should navigate back from the 3rd buffer to the multi buffer"
14912 );
14913 assert!(!active_item.is_singleton(cx));
14914 })
14915 .unwrap();
14916}
14917
14918#[gpui::test]
14919async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14920 init_test(cx, |_| {});
14921
14922 let mut cx = EditorTestContext::new(cx).await;
14923
14924 let diff_base = r#"
14925 use some::mod;
14926
14927 const A: u32 = 42;
14928
14929 fn main() {
14930 println!("hello");
14931
14932 println!("world");
14933 }
14934 "#
14935 .unindent();
14936
14937 cx.set_state(
14938 &r#"
14939 use some::modified;
14940
14941 ˇ
14942 fn main() {
14943 println!("hello there");
14944
14945 println!("around the");
14946 println!("world");
14947 }
14948 "#
14949 .unindent(),
14950 );
14951
14952 cx.set_head_text(&diff_base);
14953 executor.run_until_parked();
14954
14955 cx.update_editor(|editor, window, cx| {
14956 editor.go_to_next_hunk(&GoToHunk, window, cx);
14957 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
14958 });
14959 executor.run_until_parked();
14960 cx.assert_state_with_diff(
14961 r#"
14962 use some::modified;
14963
14964
14965 fn main() {
14966 - println!("hello");
14967 + ˇ println!("hello there");
14968
14969 println!("around the");
14970 println!("world");
14971 }
14972 "#
14973 .unindent(),
14974 );
14975
14976 cx.update_editor(|editor, window, cx| {
14977 for _ in 0..2 {
14978 editor.go_to_next_hunk(&GoToHunk, window, cx);
14979 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
14980 }
14981 });
14982 executor.run_until_parked();
14983 cx.assert_state_with_diff(
14984 r#"
14985 - use some::mod;
14986 + ˇuse some::modified;
14987
14988
14989 fn main() {
14990 - println!("hello");
14991 + println!("hello there");
14992
14993 + println!("around the");
14994 println!("world");
14995 }
14996 "#
14997 .unindent(),
14998 );
14999
15000 cx.update_editor(|editor, window, cx| {
15001 editor.go_to_next_hunk(&GoToHunk, window, cx);
15002 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
15003 });
15004 executor.run_until_parked();
15005 cx.assert_state_with_diff(
15006 r#"
15007 - use some::mod;
15008 + use some::modified;
15009
15010 - const A: u32 = 42;
15011 ˇ
15012 fn main() {
15013 - println!("hello");
15014 + println!("hello there");
15015
15016 + println!("around the");
15017 println!("world");
15018 }
15019 "#
15020 .unindent(),
15021 );
15022
15023 cx.update_editor(|editor, window, cx| {
15024 editor.cancel(&Cancel, window, cx);
15025 });
15026
15027 cx.assert_state_with_diff(
15028 r#"
15029 use some::modified;
15030
15031 ˇ
15032 fn main() {
15033 println!("hello there");
15034
15035 println!("around the");
15036 println!("world");
15037 }
15038 "#
15039 .unindent(),
15040 );
15041}
15042
15043#[gpui::test]
15044async fn test_diff_base_change_with_expanded_diff_hunks(
15045 executor: BackgroundExecutor,
15046 cx: &mut TestAppContext,
15047) {
15048 init_test(cx, |_| {});
15049
15050 let mut cx = EditorTestContext::new(cx).await;
15051
15052 let diff_base = r#"
15053 use some::mod1;
15054 use some::mod2;
15055
15056 const A: u32 = 42;
15057 const B: u32 = 42;
15058 const C: u32 = 42;
15059
15060 fn main() {
15061 println!("hello");
15062
15063 println!("world");
15064 }
15065 "#
15066 .unindent();
15067
15068 cx.set_state(
15069 &r#"
15070 use some::mod2;
15071
15072 const A: u32 = 42;
15073 const C: u32 = 42;
15074
15075 fn main(ˇ) {
15076 //println!("hello");
15077
15078 println!("world");
15079 //
15080 //
15081 }
15082 "#
15083 .unindent(),
15084 );
15085
15086 cx.set_head_text(&diff_base);
15087 executor.run_until_parked();
15088
15089 cx.update_editor(|editor, window, cx| {
15090 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15091 });
15092 executor.run_until_parked();
15093 cx.assert_state_with_diff(
15094 r#"
15095 - use some::mod1;
15096 use some::mod2;
15097
15098 const A: u32 = 42;
15099 - const B: u32 = 42;
15100 const C: u32 = 42;
15101
15102 fn main(ˇ) {
15103 - println!("hello");
15104 + //println!("hello");
15105
15106 println!("world");
15107 + //
15108 + //
15109 }
15110 "#
15111 .unindent(),
15112 );
15113
15114 cx.set_head_text("new diff base!");
15115 executor.run_until_parked();
15116 cx.assert_state_with_diff(
15117 r#"
15118 - new diff base!
15119 + use some::mod2;
15120 +
15121 + const A: u32 = 42;
15122 + const C: u32 = 42;
15123 +
15124 + fn main(ˇ) {
15125 + //println!("hello");
15126 +
15127 + println!("world");
15128 + //
15129 + //
15130 + }
15131 "#
15132 .unindent(),
15133 );
15134}
15135
15136#[gpui::test]
15137async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
15138 init_test(cx, |_| {});
15139
15140 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
15141 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
15142 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
15143 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
15144 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
15145 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
15146
15147 let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
15148 let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
15149 let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
15150
15151 let multi_buffer = cx.new(|cx| {
15152 let mut multibuffer = MultiBuffer::new(ReadWrite);
15153 multibuffer.push_excerpts(
15154 buffer_1.clone(),
15155 [
15156 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15157 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15158 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
15159 ],
15160 cx,
15161 );
15162 multibuffer.push_excerpts(
15163 buffer_2.clone(),
15164 [
15165 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15166 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15167 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
15168 ],
15169 cx,
15170 );
15171 multibuffer.push_excerpts(
15172 buffer_3.clone(),
15173 [
15174 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15175 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15176 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
15177 ],
15178 cx,
15179 );
15180 multibuffer
15181 });
15182
15183 let editor =
15184 cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
15185 editor
15186 .update(cx, |editor, _window, cx| {
15187 for (buffer, diff_base) in [
15188 (buffer_1.clone(), file_1_old),
15189 (buffer_2.clone(), file_2_old),
15190 (buffer_3.clone(), file_3_old),
15191 ] {
15192 let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
15193 editor
15194 .buffer
15195 .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
15196 }
15197 })
15198 .unwrap();
15199
15200 let mut cx = EditorTestContext::for_editor(editor, cx).await;
15201 cx.run_until_parked();
15202
15203 cx.assert_editor_state(
15204 &"
15205 ˇaaa
15206 ccc
15207 ddd
15208
15209 ggg
15210 hhh
15211
15212
15213 lll
15214 mmm
15215 NNN
15216
15217 qqq
15218 rrr
15219
15220 uuu
15221 111
15222 222
15223 333
15224
15225 666
15226 777
15227
15228 000
15229 !!!"
15230 .unindent(),
15231 );
15232
15233 cx.update_editor(|editor, window, cx| {
15234 editor.select_all(&SelectAll, window, cx);
15235 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
15236 });
15237 cx.executor().run_until_parked();
15238
15239 cx.assert_state_with_diff(
15240 "
15241 «aaa
15242 - bbb
15243 ccc
15244 ddd
15245
15246 ggg
15247 hhh
15248
15249
15250 lll
15251 mmm
15252 - nnn
15253 + NNN
15254
15255 qqq
15256 rrr
15257
15258 uuu
15259 111
15260 222
15261 333
15262
15263 + 666
15264 777
15265
15266 000
15267 !!!ˇ»"
15268 .unindent(),
15269 );
15270}
15271
15272#[gpui::test]
15273async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
15274 init_test(cx, |_| {});
15275
15276 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
15277 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
15278
15279 let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
15280 let multi_buffer = cx.new(|cx| {
15281 let mut multibuffer = MultiBuffer::new(ReadWrite);
15282 multibuffer.push_excerpts(
15283 buffer.clone(),
15284 [
15285 ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
15286 ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
15287 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
15288 ],
15289 cx,
15290 );
15291 multibuffer
15292 });
15293
15294 let editor =
15295 cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
15296 editor
15297 .update(cx, |editor, _window, cx| {
15298 let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
15299 editor
15300 .buffer
15301 .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
15302 })
15303 .unwrap();
15304
15305 let mut cx = EditorTestContext::for_editor(editor, cx).await;
15306 cx.run_until_parked();
15307
15308 cx.update_editor(|editor, window, cx| {
15309 editor.expand_all_diff_hunks(&Default::default(), window, cx)
15310 });
15311 cx.executor().run_until_parked();
15312
15313 // When the start of a hunk coincides with the start of its excerpt,
15314 // the hunk is expanded. When the start of a a hunk is earlier than
15315 // the start of its excerpt, the hunk is not expanded.
15316 cx.assert_state_with_diff(
15317 "
15318 ˇaaa
15319 - bbb
15320 + BBB
15321
15322 - ddd
15323 - eee
15324 + DDD
15325 + EEE
15326 fff
15327
15328 iii
15329 "
15330 .unindent(),
15331 );
15332}
15333
15334#[gpui::test]
15335async fn test_edits_around_expanded_insertion_hunks(
15336 executor: BackgroundExecutor,
15337 cx: &mut TestAppContext,
15338) {
15339 init_test(cx, |_| {});
15340
15341 let mut cx = EditorTestContext::new(cx).await;
15342
15343 let diff_base = r#"
15344 use some::mod1;
15345 use some::mod2;
15346
15347 const A: u32 = 42;
15348
15349 fn main() {
15350 println!("hello");
15351
15352 println!("world");
15353 }
15354 "#
15355 .unindent();
15356 executor.run_until_parked();
15357 cx.set_state(
15358 &r#"
15359 use some::mod1;
15360 use some::mod2;
15361
15362 const A: u32 = 42;
15363 const B: u32 = 42;
15364 const C: u32 = 42;
15365 ˇ
15366
15367 fn main() {
15368 println!("hello");
15369
15370 println!("world");
15371 }
15372 "#
15373 .unindent(),
15374 );
15375
15376 cx.set_head_text(&diff_base);
15377 executor.run_until_parked();
15378
15379 cx.update_editor(|editor, window, cx| {
15380 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15381 });
15382 executor.run_until_parked();
15383
15384 cx.assert_state_with_diff(
15385 r#"
15386 use some::mod1;
15387 use some::mod2;
15388
15389 const A: u32 = 42;
15390 + const B: u32 = 42;
15391 + const C: u32 = 42;
15392 + ˇ
15393
15394 fn main() {
15395 println!("hello");
15396
15397 println!("world");
15398 }
15399 "#
15400 .unindent(),
15401 );
15402
15403 cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
15404 executor.run_until_parked();
15405
15406 cx.assert_state_with_diff(
15407 r#"
15408 use some::mod1;
15409 use some::mod2;
15410
15411 const A: u32 = 42;
15412 + const B: u32 = 42;
15413 + const C: u32 = 42;
15414 + const D: u32 = 42;
15415 + ˇ
15416
15417 fn main() {
15418 println!("hello");
15419
15420 println!("world");
15421 }
15422 "#
15423 .unindent(),
15424 );
15425
15426 cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
15427 executor.run_until_parked();
15428
15429 cx.assert_state_with_diff(
15430 r#"
15431 use some::mod1;
15432 use some::mod2;
15433
15434 const A: u32 = 42;
15435 + const B: u32 = 42;
15436 + const C: u32 = 42;
15437 + const D: u32 = 42;
15438 + const E: u32 = 42;
15439 + ˇ
15440
15441 fn main() {
15442 println!("hello");
15443
15444 println!("world");
15445 }
15446 "#
15447 .unindent(),
15448 );
15449
15450 cx.update_editor(|editor, window, cx| {
15451 editor.delete_line(&DeleteLine, window, cx);
15452 });
15453 executor.run_until_parked();
15454
15455 cx.assert_state_with_diff(
15456 r#"
15457 use some::mod1;
15458 use some::mod2;
15459
15460 const A: u32 = 42;
15461 + const B: u32 = 42;
15462 + const C: u32 = 42;
15463 + const D: u32 = 42;
15464 + const E: u32 = 42;
15465 ˇ
15466 fn main() {
15467 println!("hello");
15468
15469 println!("world");
15470 }
15471 "#
15472 .unindent(),
15473 );
15474
15475 cx.update_editor(|editor, window, cx| {
15476 editor.move_up(&MoveUp, window, cx);
15477 editor.delete_line(&DeleteLine, window, cx);
15478 editor.move_up(&MoveUp, window, cx);
15479 editor.delete_line(&DeleteLine, window, cx);
15480 editor.move_up(&MoveUp, window, cx);
15481 editor.delete_line(&DeleteLine, window, cx);
15482 });
15483 executor.run_until_parked();
15484 cx.assert_state_with_diff(
15485 r#"
15486 use some::mod1;
15487 use some::mod2;
15488
15489 const A: u32 = 42;
15490 + const B: u32 = 42;
15491 ˇ
15492 fn main() {
15493 println!("hello");
15494
15495 println!("world");
15496 }
15497 "#
15498 .unindent(),
15499 );
15500
15501 cx.update_editor(|editor, window, cx| {
15502 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
15503 editor.delete_line(&DeleteLine, window, cx);
15504 });
15505 executor.run_until_parked();
15506 cx.assert_state_with_diff(
15507 r#"
15508 ˇ
15509 fn main() {
15510 println!("hello");
15511
15512 println!("world");
15513 }
15514 "#
15515 .unindent(),
15516 );
15517}
15518
15519#[gpui::test]
15520async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
15521 init_test(cx, |_| {});
15522
15523 let mut cx = EditorTestContext::new(cx).await;
15524 cx.set_head_text(indoc! { "
15525 one
15526 two
15527 three
15528 four
15529 five
15530 "
15531 });
15532 cx.set_state(indoc! { "
15533 one
15534 ˇthree
15535 five
15536 "});
15537 cx.run_until_parked();
15538 cx.update_editor(|editor, window, cx| {
15539 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
15540 });
15541 cx.assert_state_with_diff(
15542 indoc! { "
15543 one
15544 - two
15545 ˇthree
15546 - four
15547 five
15548 "}
15549 .to_string(),
15550 );
15551 cx.update_editor(|editor, window, cx| {
15552 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
15553 });
15554
15555 cx.assert_state_with_diff(
15556 indoc! { "
15557 one
15558 ˇthree
15559 five
15560 "}
15561 .to_string(),
15562 );
15563
15564 cx.set_state(indoc! { "
15565 one
15566 ˇTWO
15567 three
15568 four
15569 five
15570 "});
15571 cx.run_until_parked();
15572 cx.update_editor(|editor, window, cx| {
15573 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
15574 });
15575
15576 cx.assert_state_with_diff(
15577 indoc! { "
15578 one
15579 - two
15580 + ˇTWO
15581 three
15582 four
15583 five
15584 "}
15585 .to_string(),
15586 );
15587 cx.update_editor(|editor, window, cx| {
15588 editor.move_up(&Default::default(), window, cx);
15589 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
15590 });
15591 cx.assert_state_with_diff(
15592 indoc! { "
15593 one
15594 ˇTWO
15595 three
15596 four
15597 five
15598 "}
15599 .to_string(),
15600 );
15601}
15602
15603#[gpui::test]
15604async fn test_edits_around_expanded_deletion_hunks(
15605 executor: BackgroundExecutor,
15606 cx: &mut TestAppContext,
15607) {
15608 init_test(cx, |_| {});
15609
15610 let mut cx = EditorTestContext::new(cx).await;
15611
15612 let diff_base = r#"
15613 use some::mod1;
15614 use some::mod2;
15615
15616 const A: u32 = 42;
15617 const B: u32 = 42;
15618 const C: u32 = 42;
15619
15620
15621 fn main() {
15622 println!("hello");
15623
15624 println!("world");
15625 }
15626 "#
15627 .unindent();
15628 executor.run_until_parked();
15629 cx.set_state(
15630 &r#"
15631 use some::mod1;
15632 use some::mod2;
15633
15634 ˇconst B: u32 = 42;
15635 const C: u32 = 42;
15636
15637
15638 fn main() {
15639 println!("hello");
15640
15641 println!("world");
15642 }
15643 "#
15644 .unindent(),
15645 );
15646
15647 cx.set_head_text(&diff_base);
15648 executor.run_until_parked();
15649
15650 cx.update_editor(|editor, window, cx| {
15651 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15652 });
15653 executor.run_until_parked();
15654
15655 cx.assert_state_with_diff(
15656 r#"
15657 use some::mod1;
15658 use some::mod2;
15659
15660 - const A: u32 = 42;
15661 ˇconst B: u32 = 42;
15662 const C: u32 = 42;
15663
15664
15665 fn main() {
15666 println!("hello");
15667
15668 println!("world");
15669 }
15670 "#
15671 .unindent(),
15672 );
15673
15674 cx.update_editor(|editor, window, cx| {
15675 editor.delete_line(&DeleteLine, window, cx);
15676 });
15677 executor.run_until_parked();
15678 cx.assert_state_with_diff(
15679 r#"
15680 use some::mod1;
15681 use some::mod2;
15682
15683 - const A: u32 = 42;
15684 - const B: u32 = 42;
15685 ˇconst C: u32 = 42;
15686
15687
15688 fn main() {
15689 println!("hello");
15690
15691 println!("world");
15692 }
15693 "#
15694 .unindent(),
15695 );
15696
15697 cx.update_editor(|editor, window, cx| {
15698 editor.delete_line(&DeleteLine, window, cx);
15699 });
15700 executor.run_until_parked();
15701 cx.assert_state_with_diff(
15702 r#"
15703 use some::mod1;
15704 use some::mod2;
15705
15706 - const A: u32 = 42;
15707 - const B: u32 = 42;
15708 - const C: u32 = 42;
15709 ˇ
15710
15711 fn main() {
15712 println!("hello");
15713
15714 println!("world");
15715 }
15716 "#
15717 .unindent(),
15718 );
15719
15720 cx.update_editor(|editor, window, cx| {
15721 editor.handle_input("replacement", window, cx);
15722 });
15723 executor.run_until_parked();
15724 cx.assert_state_with_diff(
15725 r#"
15726 use some::mod1;
15727 use some::mod2;
15728
15729 - const A: u32 = 42;
15730 - const B: u32 = 42;
15731 - const C: u32 = 42;
15732 -
15733 + replacementˇ
15734
15735 fn main() {
15736 println!("hello");
15737
15738 println!("world");
15739 }
15740 "#
15741 .unindent(),
15742 );
15743}
15744
15745#[gpui::test]
15746async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15747 init_test(cx, |_| {});
15748
15749 let mut cx = EditorTestContext::new(cx).await;
15750
15751 let base_text = r#"
15752 one
15753 two
15754 three
15755 four
15756 five
15757 "#
15758 .unindent();
15759 executor.run_until_parked();
15760 cx.set_state(
15761 &r#"
15762 one
15763 two
15764 fˇour
15765 five
15766 "#
15767 .unindent(),
15768 );
15769
15770 cx.set_head_text(&base_text);
15771 executor.run_until_parked();
15772
15773 cx.update_editor(|editor, window, cx| {
15774 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15775 });
15776 executor.run_until_parked();
15777
15778 cx.assert_state_with_diff(
15779 r#"
15780 one
15781 two
15782 - three
15783 fˇour
15784 five
15785 "#
15786 .unindent(),
15787 );
15788
15789 cx.update_editor(|editor, window, cx| {
15790 editor.backspace(&Backspace, window, cx);
15791 editor.backspace(&Backspace, window, cx);
15792 });
15793 executor.run_until_parked();
15794 cx.assert_state_with_diff(
15795 r#"
15796 one
15797 two
15798 - threeˇ
15799 - four
15800 + our
15801 five
15802 "#
15803 .unindent(),
15804 );
15805}
15806
15807#[gpui::test]
15808async fn test_edit_after_expanded_modification_hunk(
15809 executor: BackgroundExecutor,
15810 cx: &mut TestAppContext,
15811) {
15812 init_test(cx, |_| {});
15813
15814 let mut cx = EditorTestContext::new(cx).await;
15815
15816 let diff_base = r#"
15817 use some::mod1;
15818 use some::mod2;
15819
15820 const A: u32 = 42;
15821 const B: u32 = 42;
15822 const C: u32 = 42;
15823 const D: u32 = 42;
15824
15825
15826 fn main() {
15827 println!("hello");
15828
15829 println!("world");
15830 }"#
15831 .unindent();
15832
15833 cx.set_state(
15834 &r#"
15835 use some::mod1;
15836 use some::mod2;
15837
15838 const A: u32 = 42;
15839 const B: u32 = 42;
15840 const C: u32 = 43ˇ
15841 const D: u32 = 42;
15842
15843
15844 fn main() {
15845 println!("hello");
15846
15847 println!("world");
15848 }"#
15849 .unindent(),
15850 );
15851
15852 cx.set_head_text(&diff_base);
15853 executor.run_until_parked();
15854 cx.update_editor(|editor, window, cx| {
15855 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15856 });
15857 executor.run_until_parked();
15858
15859 cx.assert_state_with_diff(
15860 r#"
15861 use some::mod1;
15862 use some::mod2;
15863
15864 const A: u32 = 42;
15865 const B: u32 = 42;
15866 - const C: u32 = 42;
15867 + const C: u32 = 43ˇ
15868 const D: u32 = 42;
15869
15870
15871 fn main() {
15872 println!("hello");
15873
15874 println!("world");
15875 }"#
15876 .unindent(),
15877 );
15878
15879 cx.update_editor(|editor, window, cx| {
15880 editor.handle_input("\nnew_line\n", window, cx);
15881 });
15882 executor.run_until_parked();
15883
15884 cx.assert_state_with_diff(
15885 r#"
15886 use some::mod1;
15887 use some::mod2;
15888
15889 const A: u32 = 42;
15890 const B: u32 = 42;
15891 - const C: u32 = 42;
15892 + const C: u32 = 43
15893 + new_line
15894 + ˇ
15895 const D: u32 = 42;
15896
15897
15898 fn main() {
15899 println!("hello");
15900
15901 println!("world");
15902 }"#
15903 .unindent(),
15904 );
15905}
15906
15907#[gpui::test]
15908async fn test_stage_and_unstage_added_file_hunk(
15909 executor: BackgroundExecutor,
15910 cx: &mut TestAppContext,
15911) {
15912 init_test(cx, |_| {});
15913
15914 let mut cx = EditorTestContext::new(cx).await;
15915 cx.update_editor(|editor, _, cx| {
15916 editor.set_expand_all_diff_hunks(cx);
15917 });
15918
15919 let working_copy = r#"
15920 ˇfn main() {
15921 println!("hello, world!");
15922 }
15923 "#
15924 .unindent();
15925
15926 cx.set_state(&working_copy);
15927 executor.run_until_parked();
15928
15929 cx.assert_state_with_diff(
15930 r#"
15931 + ˇfn main() {
15932 + println!("hello, world!");
15933 + }
15934 "#
15935 .unindent(),
15936 );
15937 cx.assert_index_text(None);
15938
15939 cx.update_editor(|editor, window, cx| {
15940 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
15941 });
15942 executor.run_until_parked();
15943 cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
15944 cx.assert_state_with_diff(
15945 r#"
15946 + ˇfn main() {
15947 + println!("hello, world!");
15948 + }
15949 "#
15950 .unindent(),
15951 );
15952
15953 cx.update_editor(|editor, window, cx| {
15954 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
15955 });
15956 executor.run_until_parked();
15957 cx.assert_index_text(None);
15958}
15959
15960async fn setup_indent_guides_editor(
15961 text: &str,
15962 cx: &mut TestAppContext,
15963) -> (BufferId, EditorTestContext) {
15964 init_test(cx, |_| {});
15965
15966 let mut cx = EditorTestContext::new(cx).await;
15967
15968 let buffer_id = cx.update_editor(|editor, window, cx| {
15969 editor.set_text(text, window, cx);
15970 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
15971
15972 buffer_ids[0]
15973 });
15974
15975 (buffer_id, cx)
15976}
15977
15978fn assert_indent_guides(
15979 range: Range<u32>,
15980 expected: Vec<IndentGuide>,
15981 active_indices: Option<Vec<usize>>,
15982 cx: &mut EditorTestContext,
15983) {
15984 let indent_guides = cx.update_editor(|editor, window, cx| {
15985 let snapshot = editor.snapshot(window, cx).display_snapshot;
15986 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
15987 editor,
15988 MultiBufferRow(range.start)..MultiBufferRow(range.end),
15989 true,
15990 &snapshot,
15991 cx,
15992 );
15993
15994 indent_guides.sort_by(|a, b| {
15995 a.depth.cmp(&b.depth).then(
15996 a.start_row
15997 .cmp(&b.start_row)
15998 .then(a.end_row.cmp(&b.end_row)),
15999 )
16000 });
16001 indent_guides
16002 });
16003
16004 if let Some(expected) = active_indices {
16005 let active_indices = cx.update_editor(|editor, window, cx| {
16006 let snapshot = editor.snapshot(window, cx).display_snapshot;
16007 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
16008 });
16009
16010 assert_eq!(
16011 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
16012 expected,
16013 "Active indent guide indices do not match"
16014 );
16015 }
16016
16017 assert_eq!(indent_guides, expected, "Indent guides do not match");
16018}
16019
16020fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
16021 IndentGuide {
16022 buffer_id,
16023 start_row: MultiBufferRow(start_row),
16024 end_row: MultiBufferRow(end_row),
16025 depth,
16026 tab_size: 4,
16027 settings: IndentGuideSettings {
16028 enabled: true,
16029 line_width: 1,
16030 active_line_width: 1,
16031 ..Default::default()
16032 },
16033 }
16034}
16035
16036#[gpui::test]
16037async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
16038 let (buffer_id, mut cx) = setup_indent_guides_editor(
16039 &"
16040 fn main() {
16041 let a = 1;
16042 }"
16043 .unindent(),
16044 cx,
16045 )
16046 .await;
16047
16048 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
16049}
16050
16051#[gpui::test]
16052async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
16053 let (buffer_id, mut cx) = setup_indent_guides_editor(
16054 &"
16055 fn main() {
16056 let a = 1;
16057 let b = 2;
16058 }"
16059 .unindent(),
16060 cx,
16061 )
16062 .await;
16063
16064 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
16065}
16066
16067#[gpui::test]
16068async fn test_indent_guide_nested(cx: &mut TestAppContext) {
16069 let (buffer_id, mut cx) = setup_indent_guides_editor(
16070 &"
16071 fn main() {
16072 let a = 1;
16073 if a == 3 {
16074 let b = 2;
16075 } else {
16076 let c = 3;
16077 }
16078 }"
16079 .unindent(),
16080 cx,
16081 )
16082 .await;
16083
16084 assert_indent_guides(
16085 0..8,
16086 vec![
16087 indent_guide(buffer_id, 1, 6, 0),
16088 indent_guide(buffer_id, 3, 3, 1),
16089 indent_guide(buffer_id, 5, 5, 1),
16090 ],
16091 None,
16092 &mut cx,
16093 );
16094}
16095
16096#[gpui::test]
16097async fn test_indent_guide_tab(cx: &mut TestAppContext) {
16098 let (buffer_id, mut cx) = setup_indent_guides_editor(
16099 &"
16100 fn main() {
16101 let a = 1;
16102 let b = 2;
16103 let c = 3;
16104 }"
16105 .unindent(),
16106 cx,
16107 )
16108 .await;
16109
16110 assert_indent_guides(
16111 0..5,
16112 vec![
16113 indent_guide(buffer_id, 1, 3, 0),
16114 indent_guide(buffer_id, 2, 2, 1),
16115 ],
16116 None,
16117 &mut cx,
16118 );
16119}
16120
16121#[gpui::test]
16122async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
16123 let (buffer_id, mut cx) = setup_indent_guides_editor(
16124 &"
16125 fn main() {
16126 let a = 1;
16127
16128 let c = 3;
16129 }"
16130 .unindent(),
16131 cx,
16132 )
16133 .await;
16134
16135 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
16136}
16137
16138#[gpui::test]
16139async fn test_indent_guide_complex(cx: &mut TestAppContext) {
16140 let (buffer_id, mut cx) = setup_indent_guides_editor(
16141 &"
16142 fn main() {
16143 let a = 1;
16144
16145 let c = 3;
16146
16147 if a == 3 {
16148 let b = 2;
16149 } else {
16150 let c = 3;
16151 }
16152 }"
16153 .unindent(),
16154 cx,
16155 )
16156 .await;
16157
16158 assert_indent_guides(
16159 0..11,
16160 vec![
16161 indent_guide(buffer_id, 1, 9, 0),
16162 indent_guide(buffer_id, 6, 6, 1),
16163 indent_guide(buffer_id, 8, 8, 1),
16164 ],
16165 None,
16166 &mut cx,
16167 );
16168}
16169
16170#[gpui::test]
16171async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
16172 let (buffer_id, mut cx) = setup_indent_guides_editor(
16173 &"
16174 fn main() {
16175 let a = 1;
16176
16177 let c = 3;
16178
16179 if a == 3 {
16180 let b = 2;
16181 } else {
16182 let c = 3;
16183 }
16184 }"
16185 .unindent(),
16186 cx,
16187 )
16188 .await;
16189
16190 assert_indent_guides(
16191 1..11,
16192 vec![
16193 indent_guide(buffer_id, 1, 9, 0),
16194 indent_guide(buffer_id, 6, 6, 1),
16195 indent_guide(buffer_id, 8, 8, 1),
16196 ],
16197 None,
16198 &mut cx,
16199 );
16200}
16201
16202#[gpui::test]
16203async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
16204 let (buffer_id, mut cx) = setup_indent_guides_editor(
16205 &"
16206 fn main() {
16207 let a = 1;
16208
16209 let c = 3;
16210
16211 if a == 3 {
16212 let b = 2;
16213 } else {
16214 let c = 3;
16215 }
16216 }"
16217 .unindent(),
16218 cx,
16219 )
16220 .await;
16221
16222 assert_indent_guides(
16223 1..10,
16224 vec![
16225 indent_guide(buffer_id, 1, 9, 0),
16226 indent_guide(buffer_id, 6, 6, 1),
16227 indent_guide(buffer_id, 8, 8, 1),
16228 ],
16229 None,
16230 &mut cx,
16231 );
16232}
16233
16234#[gpui::test]
16235async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
16236 let (buffer_id, mut cx) = setup_indent_guides_editor(
16237 &"
16238 block1
16239 block2
16240 block3
16241 block4
16242 block2
16243 block1
16244 block1"
16245 .unindent(),
16246 cx,
16247 )
16248 .await;
16249
16250 assert_indent_guides(
16251 1..10,
16252 vec![
16253 indent_guide(buffer_id, 1, 4, 0),
16254 indent_guide(buffer_id, 2, 3, 1),
16255 indent_guide(buffer_id, 3, 3, 2),
16256 ],
16257 None,
16258 &mut cx,
16259 );
16260}
16261
16262#[gpui::test]
16263async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
16264 let (buffer_id, mut cx) = setup_indent_guides_editor(
16265 &"
16266 block1
16267 block2
16268 block3
16269
16270 block1
16271 block1"
16272 .unindent(),
16273 cx,
16274 )
16275 .await;
16276
16277 assert_indent_guides(
16278 0..6,
16279 vec![
16280 indent_guide(buffer_id, 1, 2, 0),
16281 indent_guide(buffer_id, 2, 2, 1),
16282 ],
16283 None,
16284 &mut cx,
16285 );
16286}
16287
16288#[gpui::test]
16289async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
16290 let (buffer_id, mut cx) = setup_indent_guides_editor(
16291 &"
16292 block1
16293
16294
16295
16296 block2
16297 "
16298 .unindent(),
16299 cx,
16300 )
16301 .await;
16302
16303 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
16304}
16305
16306#[gpui::test]
16307async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
16308 let (buffer_id, mut cx) = setup_indent_guides_editor(
16309 &"
16310 def a:
16311 \tb = 3
16312 \tif True:
16313 \t\tc = 4
16314 \t\td = 5
16315 \tprint(b)
16316 "
16317 .unindent(),
16318 cx,
16319 )
16320 .await;
16321
16322 assert_indent_guides(
16323 0..6,
16324 vec![
16325 indent_guide(buffer_id, 1, 6, 0),
16326 indent_guide(buffer_id, 3, 4, 1),
16327 ],
16328 None,
16329 &mut cx,
16330 );
16331}
16332
16333#[gpui::test]
16334async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
16335 let (buffer_id, mut cx) = setup_indent_guides_editor(
16336 &"
16337 fn main() {
16338 let a = 1;
16339 }"
16340 .unindent(),
16341 cx,
16342 )
16343 .await;
16344
16345 cx.update_editor(|editor, window, cx| {
16346 editor.change_selections(None, window, cx, |s| {
16347 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
16348 });
16349 });
16350
16351 assert_indent_guides(
16352 0..3,
16353 vec![indent_guide(buffer_id, 1, 1, 0)],
16354 Some(vec![0]),
16355 &mut cx,
16356 );
16357}
16358
16359#[gpui::test]
16360async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
16361 let (buffer_id, mut cx) = setup_indent_guides_editor(
16362 &"
16363 fn main() {
16364 if 1 == 2 {
16365 let a = 1;
16366 }
16367 }"
16368 .unindent(),
16369 cx,
16370 )
16371 .await;
16372
16373 cx.update_editor(|editor, window, cx| {
16374 editor.change_selections(None, window, cx, |s| {
16375 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
16376 });
16377 });
16378
16379 assert_indent_guides(
16380 0..4,
16381 vec![
16382 indent_guide(buffer_id, 1, 3, 0),
16383 indent_guide(buffer_id, 2, 2, 1),
16384 ],
16385 Some(vec![1]),
16386 &mut cx,
16387 );
16388
16389 cx.update_editor(|editor, window, cx| {
16390 editor.change_selections(None, window, cx, |s| {
16391 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
16392 });
16393 });
16394
16395 assert_indent_guides(
16396 0..4,
16397 vec![
16398 indent_guide(buffer_id, 1, 3, 0),
16399 indent_guide(buffer_id, 2, 2, 1),
16400 ],
16401 Some(vec![1]),
16402 &mut cx,
16403 );
16404
16405 cx.update_editor(|editor, window, cx| {
16406 editor.change_selections(None, window, cx, |s| {
16407 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
16408 });
16409 });
16410
16411 assert_indent_guides(
16412 0..4,
16413 vec![
16414 indent_guide(buffer_id, 1, 3, 0),
16415 indent_guide(buffer_id, 2, 2, 1),
16416 ],
16417 Some(vec![0]),
16418 &mut cx,
16419 );
16420}
16421
16422#[gpui::test]
16423async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
16424 let (buffer_id, mut cx) = setup_indent_guides_editor(
16425 &"
16426 fn main() {
16427 let a = 1;
16428
16429 let b = 2;
16430 }"
16431 .unindent(),
16432 cx,
16433 )
16434 .await;
16435
16436 cx.update_editor(|editor, window, cx| {
16437 editor.change_selections(None, window, cx, |s| {
16438 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
16439 });
16440 });
16441
16442 assert_indent_guides(
16443 0..5,
16444 vec![indent_guide(buffer_id, 1, 3, 0)],
16445 Some(vec![0]),
16446 &mut cx,
16447 );
16448}
16449
16450#[gpui::test]
16451async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
16452 let (buffer_id, mut cx) = setup_indent_guides_editor(
16453 &"
16454 def m:
16455 a = 1
16456 pass"
16457 .unindent(),
16458 cx,
16459 )
16460 .await;
16461
16462 cx.update_editor(|editor, window, cx| {
16463 editor.change_selections(None, window, cx, |s| {
16464 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
16465 });
16466 });
16467
16468 assert_indent_guides(
16469 0..3,
16470 vec![indent_guide(buffer_id, 1, 2, 0)],
16471 Some(vec![0]),
16472 &mut cx,
16473 );
16474}
16475
16476#[gpui::test]
16477async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
16478 init_test(cx, |_| {});
16479 let mut cx = EditorTestContext::new(cx).await;
16480 let text = indoc! {
16481 "
16482 impl A {
16483 fn b() {
16484 0;
16485 3;
16486 5;
16487 6;
16488 7;
16489 }
16490 }
16491 "
16492 };
16493 let base_text = indoc! {
16494 "
16495 impl A {
16496 fn b() {
16497 0;
16498 1;
16499 2;
16500 3;
16501 4;
16502 }
16503 fn c() {
16504 5;
16505 6;
16506 7;
16507 }
16508 }
16509 "
16510 };
16511
16512 cx.update_editor(|editor, window, cx| {
16513 editor.set_text(text, window, cx);
16514
16515 editor.buffer().update(cx, |multibuffer, cx| {
16516 let buffer = multibuffer.as_singleton().unwrap();
16517 let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
16518
16519 multibuffer.set_all_diff_hunks_expanded(cx);
16520 multibuffer.add_diff(diff, cx);
16521
16522 buffer.read(cx).remote_id()
16523 })
16524 });
16525 cx.run_until_parked();
16526
16527 cx.assert_state_with_diff(
16528 indoc! { "
16529 impl A {
16530 fn b() {
16531 0;
16532 - 1;
16533 - 2;
16534 3;
16535 - 4;
16536 - }
16537 - fn c() {
16538 5;
16539 6;
16540 7;
16541 }
16542 }
16543 ˇ"
16544 }
16545 .to_string(),
16546 );
16547
16548 let mut actual_guides = cx.update_editor(|editor, window, cx| {
16549 editor
16550 .snapshot(window, cx)
16551 .buffer_snapshot
16552 .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
16553 .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
16554 .collect::<Vec<_>>()
16555 });
16556 actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
16557 assert_eq!(
16558 actual_guides,
16559 vec![
16560 (MultiBufferRow(1)..=MultiBufferRow(12), 0),
16561 (MultiBufferRow(2)..=MultiBufferRow(6), 1),
16562 (MultiBufferRow(9)..=MultiBufferRow(11), 1),
16563 ]
16564 );
16565}
16566
16567#[gpui::test]
16568async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16569 init_test(cx, |_| {});
16570 let mut cx = EditorTestContext::new(cx).await;
16571
16572 let diff_base = r#"
16573 a
16574 b
16575 c
16576 "#
16577 .unindent();
16578
16579 cx.set_state(
16580 &r#"
16581 ˇA
16582 b
16583 C
16584 "#
16585 .unindent(),
16586 );
16587 cx.set_head_text(&diff_base);
16588 cx.update_editor(|editor, window, cx| {
16589 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16590 });
16591 executor.run_until_parked();
16592
16593 let both_hunks_expanded = r#"
16594 - a
16595 + ˇA
16596 b
16597 - c
16598 + C
16599 "#
16600 .unindent();
16601
16602 cx.assert_state_with_diff(both_hunks_expanded.clone());
16603
16604 let hunk_ranges = cx.update_editor(|editor, window, cx| {
16605 let snapshot = editor.snapshot(window, cx);
16606 let hunks = editor
16607 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
16608 .collect::<Vec<_>>();
16609 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
16610 let buffer_id = hunks[0].buffer_id;
16611 hunks
16612 .into_iter()
16613 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
16614 .collect::<Vec<_>>()
16615 });
16616 assert_eq!(hunk_ranges.len(), 2);
16617
16618 cx.update_editor(|editor, _, cx| {
16619 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
16620 });
16621 executor.run_until_parked();
16622
16623 let second_hunk_expanded = r#"
16624 ˇA
16625 b
16626 - c
16627 + C
16628 "#
16629 .unindent();
16630
16631 cx.assert_state_with_diff(second_hunk_expanded);
16632
16633 cx.update_editor(|editor, _, cx| {
16634 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
16635 });
16636 executor.run_until_parked();
16637
16638 cx.assert_state_with_diff(both_hunks_expanded.clone());
16639
16640 cx.update_editor(|editor, _, cx| {
16641 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
16642 });
16643 executor.run_until_parked();
16644
16645 let first_hunk_expanded = r#"
16646 - a
16647 + ˇA
16648 b
16649 C
16650 "#
16651 .unindent();
16652
16653 cx.assert_state_with_diff(first_hunk_expanded);
16654
16655 cx.update_editor(|editor, _, cx| {
16656 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
16657 });
16658 executor.run_until_parked();
16659
16660 cx.assert_state_with_diff(both_hunks_expanded);
16661
16662 cx.set_state(
16663 &r#"
16664 ˇA
16665 b
16666 "#
16667 .unindent(),
16668 );
16669 cx.run_until_parked();
16670
16671 // TODO this cursor position seems bad
16672 cx.assert_state_with_diff(
16673 r#"
16674 - ˇa
16675 + A
16676 b
16677 "#
16678 .unindent(),
16679 );
16680
16681 cx.update_editor(|editor, window, cx| {
16682 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16683 });
16684
16685 cx.assert_state_with_diff(
16686 r#"
16687 - ˇa
16688 + A
16689 b
16690 - c
16691 "#
16692 .unindent(),
16693 );
16694
16695 let hunk_ranges = cx.update_editor(|editor, window, cx| {
16696 let snapshot = editor.snapshot(window, cx);
16697 let hunks = editor
16698 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
16699 .collect::<Vec<_>>();
16700 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
16701 let buffer_id = hunks[0].buffer_id;
16702 hunks
16703 .into_iter()
16704 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
16705 .collect::<Vec<_>>()
16706 });
16707 assert_eq!(hunk_ranges.len(), 2);
16708
16709 cx.update_editor(|editor, _, cx| {
16710 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
16711 });
16712 executor.run_until_parked();
16713
16714 cx.assert_state_with_diff(
16715 r#"
16716 - ˇa
16717 + A
16718 b
16719 "#
16720 .unindent(),
16721 );
16722}
16723
16724#[gpui::test]
16725async fn test_toggle_deletion_hunk_at_start_of_file(
16726 executor: BackgroundExecutor,
16727 cx: &mut TestAppContext,
16728) {
16729 init_test(cx, |_| {});
16730 let mut cx = EditorTestContext::new(cx).await;
16731
16732 let diff_base = r#"
16733 a
16734 b
16735 c
16736 "#
16737 .unindent();
16738
16739 cx.set_state(
16740 &r#"
16741 ˇb
16742 c
16743 "#
16744 .unindent(),
16745 );
16746 cx.set_head_text(&diff_base);
16747 cx.update_editor(|editor, window, cx| {
16748 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16749 });
16750 executor.run_until_parked();
16751
16752 let hunk_expanded = r#"
16753 - a
16754 ˇb
16755 c
16756 "#
16757 .unindent();
16758
16759 cx.assert_state_with_diff(hunk_expanded.clone());
16760
16761 let hunk_ranges = cx.update_editor(|editor, window, cx| {
16762 let snapshot = editor.snapshot(window, cx);
16763 let hunks = editor
16764 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
16765 .collect::<Vec<_>>();
16766 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
16767 let buffer_id = hunks[0].buffer_id;
16768 hunks
16769 .into_iter()
16770 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
16771 .collect::<Vec<_>>()
16772 });
16773 assert_eq!(hunk_ranges.len(), 1);
16774
16775 cx.update_editor(|editor, _, cx| {
16776 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
16777 });
16778 executor.run_until_parked();
16779
16780 let hunk_collapsed = r#"
16781 ˇb
16782 c
16783 "#
16784 .unindent();
16785
16786 cx.assert_state_with_diff(hunk_collapsed);
16787
16788 cx.update_editor(|editor, _, cx| {
16789 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
16790 });
16791 executor.run_until_parked();
16792
16793 cx.assert_state_with_diff(hunk_expanded.clone());
16794}
16795
16796#[gpui::test]
16797async fn test_display_diff_hunks(cx: &mut TestAppContext) {
16798 init_test(cx, |_| {});
16799
16800 let fs = FakeFs::new(cx.executor());
16801 fs.insert_tree(
16802 path!("/test"),
16803 json!({
16804 ".git": {},
16805 "file-1": "ONE\n",
16806 "file-2": "TWO\n",
16807 "file-3": "THREE\n",
16808 }),
16809 )
16810 .await;
16811
16812 fs.set_head_for_repo(
16813 path!("/test/.git").as_ref(),
16814 &[
16815 ("file-1".into(), "one\n".into()),
16816 ("file-2".into(), "two\n".into()),
16817 ("file-3".into(), "three\n".into()),
16818 ],
16819 );
16820
16821 let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
16822 let mut buffers = vec![];
16823 for i in 1..=3 {
16824 let buffer = project
16825 .update(cx, |project, cx| {
16826 let path = format!(path!("/test/file-{}"), i);
16827 project.open_local_buffer(path, cx)
16828 })
16829 .await
16830 .unwrap();
16831 buffers.push(buffer);
16832 }
16833
16834 let multibuffer = cx.new(|cx| {
16835 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
16836 multibuffer.set_all_diff_hunks_expanded(cx);
16837 for buffer in &buffers {
16838 let snapshot = buffer.read(cx).snapshot();
16839 multibuffer.set_excerpts_for_path(
16840 PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
16841 buffer.clone(),
16842 vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
16843 DEFAULT_MULTIBUFFER_CONTEXT,
16844 cx,
16845 );
16846 }
16847 multibuffer
16848 });
16849
16850 let editor = cx.add_window(|window, cx| {
16851 Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
16852 });
16853 cx.run_until_parked();
16854
16855 let snapshot = editor
16856 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
16857 .unwrap();
16858 let hunks = snapshot
16859 .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
16860 .map(|hunk| match hunk {
16861 DisplayDiffHunk::Unfolded {
16862 display_row_range, ..
16863 } => display_row_range,
16864 DisplayDiffHunk::Folded { .. } => unreachable!(),
16865 })
16866 .collect::<Vec<_>>();
16867 assert_eq!(
16868 hunks,
16869 [
16870 DisplayRow(2)..DisplayRow(4),
16871 DisplayRow(7)..DisplayRow(9),
16872 DisplayRow(12)..DisplayRow(14),
16873 ]
16874 );
16875}
16876
16877#[gpui::test]
16878async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
16879 init_test(cx, |_| {});
16880
16881 let mut cx = EditorTestContext::new(cx).await;
16882 cx.set_head_text(indoc! { "
16883 one
16884 two
16885 three
16886 four
16887 five
16888 "
16889 });
16890 cx.set_index_text(indoc! { "
16891 one
16892 two
16893 three
16894 four
16895 five
16896 "
16897 });
16898 cx.set_state(indoc! {"
16899 one
16900 TWO
16901 ˇTHREE
16902 FOUR
16903 five
16904 "});
16905 cx.run_until_parked();
16906 cx.update_editor(|editor, window, cx| {
16907 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
16908 });
16909 cx.run_until_parked();
16910 cx.assert_index_text(Some(indoc! {"
16911 one
16912 TWO
16913 THREE
16914 FOUR
16915 five
16916 "}));
16917 cx.set_state(indoc! { "
16918 one
16919 TWO
16920 ˇTHREE-HUNDRED
16921 FOUR
16922 five
16923 "});
16924 cx.run_until_parked();
16925 cx.update_editor(|editor, window, cx| {
16926 let snapshot = editor.snapshot(window, cx);
16927 let hunks = editor
16928 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
16929 .collect::<Vec<_>>();
16930 assert_eq!(hunks.len(), 1);
16931 assert_eq!(
16932 hunks[0].status(),
16933 DiffHunkStatus {
16934 kind: DiffHunkStatusKind::Modified,
16935 secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
16936 }
16937 );
16938
16939 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
16940 });
16941 cx.run_until_parked();
16942 cx.assert_index_text(Some(indoc! {"
16943 one
16944 TWO
16945 THREE-HUNDRED
16946 FOUR
16947 five
16948 "}));
16949}
16950
16951#[gpui::test]
16952fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
16953 init_test(cx, |_| {});
16954
16955 let editor = cx.add_window(|window, cx| {
16956 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
16957 build_editor(buffer, window, cx)
16958 });
16959
16960 let render_args = Arc::new(Mutex::new(None));
16961 let snapshot = editor
16962 .update(cx, |editor, window, cx| {
16963 let snapshot = editor.buffer().read(cx).snapshot(cx);
16964 let range =
16965 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
16966
16967 struct RenderArgs {
16968 row: MultiBufferRow,
16969 folded: bool,
16970 callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
16971 }
16972
16973 let crease = Crease::inline(
16974 range,
16975 FoldPlaceholder::test(),
16976 {
16977 let toggle_callback = render_args.clone();
16978 move |row, folded, callback, _window, _cx| {
16979 *toggle_callback.lock() = Some(RenderArgs {
16980 row,
16981 folded,
16982 callback,
16983 });
16984 div()
16985 }
16986 },
16987 |_row, _folded, _window, _cx| div(),
16988 );
16989
16990 editor.insert_creases(Some(crease), cx);
16991 let snapshot = editor.snapshot(window, cx);
16992 let _div = snapshot.render_crease_toggle(
16993 MultiBufferRow(1),
16994 false,
16995 cx.entity().clone(),
16996 window,
16997 cx,
16998 );
16999 snapshot
17000 })
17001 .unwrap();
17002
17003 let render_args = render_args.lock().take().unwrap();
17004 assert_eq!(render_args.row, MultiBufferRow(1));
17005 assert!(!render_args.folded);
17006 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
17007
17008 cx.update_window(*editor, |_, window, cx| {
17009 (render_args.callback)(true, window, cx)
17010 })
17011 .unwrap();
17012 let snapshot = editor
17013 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
17014 .unwrap();
17015 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
17016
17017 cx.update_window(*editor, |_, window, cx| {
17018 (render_args.callback)(false, window, cx)
17019 })
17020 .unwrap();
17021 let snapshot = editor
17022 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
17023 .unwrap();
17024 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
17025}
17026
17027#[gpui::test]
17028async fn test_input_text(cx: &mut TestAppContext) {
17029 init_test(cx, |_| {});
17030 let mut cx = EditorTestContext::new(cx).await;
17031
17032 cx.set_state(
17033 &r#"ˇone
17034 two
17035
17036 three
17037 fourˇ
17038 five
17039
17040 siˇx"#
17041 .unindent(),
17042 );
17043
17044 cx.dispatch_action(HandleInput(String::new()));
17045 cx.assert_editor_state(
17046 &r#"ˇone
17047 two
17048
17049 three
17050 fourˇ
17051 five
17052
17053 siˇx"#
17054 .unindent(),
17055 );
17056
17057 cx.dispatch_action(HandleInput("AAAA".to_string()));
17058 cx.assert_editor_state(
17059 &r#"AAAAˇone
17060 two
17061
17062 three
17063 fourAAAAˇ
17064 five
17065
17066 siAAAAˇx"#
17067 .unindent(),
17068 );
17069}
17070
17071#[gpui::test]
17072async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
17073 init_test(cx, |_| {});
17074
17075 let mut cx = EditorTestContext::new(cx).await;
17076 cx.set_state(
17077 r#"let foo = 1;
17078let foo = 2;
17079let foo = 3;
17080let fooˇ = 4;
17081let foo = 5;
17082let foo = 6;
17083let foo = 7;
17084let foo = 8;
17085let foo = 9;
17086let foo = 10;
17087let foo = 11;
17088let foo = 12;
17089let foo = 13;
17090let foo = 14;
17091let foo = 15;"#,
17092 );
17093
17094 cx.update_editor(|e, window, cx| {
17095 assert_eq!(
17096 e.next_scroll_position,
17097 NextScrollCursorCenterTopBottom::Center,
17098 "Default next scroll direction is center",
17099 );
17100
17101 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
17102 assert_eq!(
17103 e.next_scroll_position,
17104 NextScrollCursorCenterTopBottom::Top,
17105 "After center, next scroll direction should be top",
17106 );
17107
17108 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
17109 assert_eq!(
17110 e.next_scroll_position,
17111 NextScrollCursorCenterTopBottom::Bottom,
17112 "After top, next scroll direction should be bottom",
17113 );
17114
17115 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
17116 assert_eq!(
17117 e.next_scroll_position,
17118 NextScrollCursorCenterTopBottom::Center,
17119 "After bottom, scrolling should start over",
17120 );
17121
17122 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
17123 assert_eq!(
17124 e.next_scroll_position,
17125 NextScrollCursorCenterTopBottom::Top,
17126 "Scrolling continues if retriggered fast enough"
17127 );
17128 });
17129
17130 cx.executor()
17131 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
17132 cx.executor().run_until_parked();
17133 cx.update_editor(|e, _, _| {
17134 assert_eq!(
17135 e.next_scroll_position,
17136 NextScrollCursorCenterTopBottom::Center,
17137 "If scrolling is not triggered fast enough, it should reset"
17138 );
17139 });
17140}
17141
17142#[gpui::test]
17143async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
17144 init_test(cx, |_| {});
17145 let mut cx = EditorLspTestContext::new_rust(
17146 lsp::ServerCapabilities {
17147 definition_provider: Some(lsp::OneOf::Left(true)),
17148 references_provider: Some(lsp::OneOf::Left(true)),
17149 ..lsp::ServerCapabilities::default()
17150 },
17151 cx,
17152 )
17153 .await;
17154
17155 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
17156 let go_to_definition = cx
17157 .lsp
17158 .set_request_handler::<lsp::request::GotoDefinition, _, _>(
17159 move |params, _| async move {
17160 if empty_go_to_definition {
17161 Ok(None)
17162 } else {
17163 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
17164 uri: params.text_document_position_params.text_document.uri,
17165 range: lsp::Range::new(
17166 lsp::Position::new(4, 3),
17167 lsp::Position::new(4, 6),
17168 ),
17169 })))
17170 }
17171 },
17172 );
17173 let references = cx
17174 .lsp
17175 .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
17176 Ok(Some(vec![lsp::Location {
17177 uri: params.text_document_position.text_document.uri,
17178 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
17179 }]))
17180 });
17181 (go_to_definition, references)
17182 };
17183
17184 cx.set_state(
17185 &r#"fn one() {
17186 let mut a = ˇtwo();
17187 }
17188
17189 fn two() {}"#
17190 .unindent(),
17191 );
17192 set_up_lsp_handlers(false, &mut cx);
17193 let navigated = cx
17194 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
17195 .await
17196 .expect("Failed to navigate to definition");
17197 assert_eq!(
17198 navigated,
17199 Navigated::Yes,
17200 "Should have navigated to definition from the GetDefinition response"
17201 );
17202 cx.assert_editor_state(
17203 &r#"fn one() {
17204 let mut a = two();
17205 }
17206
17207 fn «twoˇ»() {}"#
17208 .unindent(),
17209 );
17210
17211 let editors = cx.update_workspace(|workspace, _, cx| {
17212 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
17213 });
17214 cx.update_editor(|_, _, test_editor_cx| {
17215 assert_eq!(
17216 editors.len(),
17217 1,
17218 "Initially, only one, test, editor should be open in the workspace"
17219 );
17220 assert_eq!(
17221 test_editor_cx.entity(),
17222 editors.last().expect("Asserted len is 1").clone()
17223 );
17224 });
17225
17226 set_up_lsp_handlers(true, &mut cx);
17227 let navigated = cx
17228 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
17229 .await
17230 .expect("Failed to navigate to lookup references");
17231 assert_eq!(
17232 navigated,
17233 Navigated::Yes,
17234 "Should have navigated to references as a fallback after empty GoToDefinition response"
17235 );
17236 // We should not change the selections in the existing file,
17237 // if opening another milti buffer with the references
17238 cx.assert_editor_state(
17239 &r#"fn one() {
17240 let mut a = two();
17241 }
17242
17243 fn «twoˇ»() {}"#
17244 .unindent(),
17245 );
17246 let editors = cx.update_workspace(|workspace, _, cx| {
17247 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
17248 });
17249 cx.update_editor(|_, _, test_editor_cx| {
17250 assert_eq!(
17251 editors.len(),
17252 2,
17253 "After falling back to references search, we open a new editor with the results"
17254 );
17255 let references_fallback_text = editors
17256 .into_iter()
17257 .find(|new_editor| *new_editor != test_editor_cx.entity())
17258 .expect("Should have one non-test editor now")
17259 .read(test_editor_cx)
17260 .text(test_editor_cx);
17261 assert_eq!(
17262 references_fallback_text, "fn one() {\n let mut a = two();\n}",
17263 "Should use the range from the references response and not the GoToDefinition one"
17264 );
17265 });
17266}
17267
17268#[gpui::test]
17269async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
17270 init_test(cx, |_| {});
17271 cx.update(|cx| {
17272 let mut editor_settings = EditorSettings::get_global(cx).clone();
17273 editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
17274 EditorSettings::override_global(editor_settings, cx);
17275 });
17276 let mut cx = EditorLspTestContext::new_rust(
17277 lsp::ServerCapabilities {
17278 definition_provider: Some(lsp::OneOf::Left(true)),
17279 references_provider: Some(lsp::OneOf::Left(true)),
17280 ..lsp::ServerCapabilities::default()
17281 },
17282 cx,
17283 )
17284 .await;
17285 let original_state = r#"fn one() {
17286 let mut a = ˇtwo();
17287 }
17288
17289 fn two() {}"#
17290 .unindent();
17291 cx.set_state(&original_state);
17292
17293 let mut go_to_definition = cx
17294 .lsp
17295 .set_request_handler::<lsp::request::GotoDefinition, _, _>(
17296 move |_, _| async move { Ok(None) },
17297 );
17298 let _references = cx
17299 .lsp
17300 .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
17301 panic!("Should not call for references with no go to definition fallback")
17302 });
17303
17304 let navigated = cx
17305 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
17306 .await
17307 .expect("Failed to navigate to lookup references");
17308 go_to_definition
17309 .next()
17310 .await
17311 .expect("Should have called the go_to_definition handler");
17312
17313 assert_eq!(
17314 navigated,
17315 Navigated::No,
17316 "Should have navigated to references as a fallback after empty GoToDefinition response"
17317 );
17318 cx.assert_editor_state(&original_state);
17319 let editors = cx.update_workspace(|workspace, _, cx| {
17320 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
17321 });
17322 cx.update_editor(|_, _, _| {
17323 assert_eq!(
17324 editors.len(),
17325 1,
17326 "After unsuccessful fallback, no other editor should have been opened"
17327 );
17328 });
17329}
17330
17331#[gpui::test]
17332async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
17333 init_test(cx, |_| {});
17334
17335 let language = Arc::new(Language::new(
17336 LanguageConfig::default(),
17337 Some(tree_sitter_rust::LANGUAGE.into()),
17338 ));
17339
17340 let text = r#"
17341 #[cfg(test)]
17342 mod tests() {
17343 #[test]
17344 fn runnable_1() {
17345 let a = 1;
17346 }
17347
17348 #[test]
17349 fn runnable_2() {
17350 let a = 1;
17351 let b = 2;
17352 }
17353 }
17354 "#
17355 .unindent();
17356
17357 let fs = FakeFs::new(cx.executor());
17358 fs.insert_file("/file.rs", Default::default()).await;
17359
17360 let project = Project::test(fs, ["/a".as_ref()], cx).await;
17361 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17362 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17363 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
17364 let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
17365
17366 let editor = cx.new_window_entity(|window, cx| {
17367 Editor::new(
17368 EditorMode::full(),
17369 multi_buffer,
17370 Some(project.clone()),
17371 window,
17372 cx,
17373 )
17374 });
17375
17376 editor.update_in(cx, |editor, window, cx| {
17377 let snapshot = editor.buffer().read(cx).snapshot(cx);
17378 editor.tasks.insert(
17379 (buffer.read(cx).remote_id(), 3),
17380 RunnableTasks {
17381 templates: vec![],
17382 offset: snapshot.anchor_before(43),
17383 column: 0,
17384 extra_variables: HashMap::default(),
17385 context_range: BufferOffset(43)..BufferOffset(85),
17386 },
17387 );
17388 editor.tasks.insert(
17389 (buffer.read(cx).remote_id(), 8),
17390 RunnableTasks {
17391 templates: vec![],
17392 offset: snapshot.anchor_before(86),
17393 column: 0,
17394 extra_variables: HashMap::default(),
17395 context_range: BufferOffset(86)..BufferOffset(191),
17396 },
17397 );
17398
17399 // Test finding task when cursor is inside function body
17400 editor.change_selections(None, window, cx, |s| {
17401 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
17402 });
17403 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
17404 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
17405
17406 // Test finding task when cursor is on function name
17407 editor.change_selections(None, window, cx, |s| {
17408 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
17409 });
17410 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
17411 assert_eq!(row, 8, "Should find task when cursor is on function name");
17412 });
17413}
17414
17415#[gpui::test]
17416async fn test_folding_buffers(cx: &mut TestAppContext) {
17417 init_test(cx, |_| {});
17418
17419 let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
17420 let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
17421 let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
17422
17423 let fs = FakeFs::new(cx.executor());
17424 fs.insert_tree(
17425 path!("/a"),
17426 json!({
17427 "first.rs": sample_text_1,
17428 "second.rs": sample_text_2,
17429 "third.rs": sample_text_3,
17430 }),
17431 )
17432 .await;
17433 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17434 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17435 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17436 let worktree = project.update(cx, |project, cx| {
17437 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
17438 assert_eq!(worktrees.len(), 1);
17439 worktrees.pop().unwrap()
17440 });
17441 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
17442
17443 let buffer_1 = project
17444 .update(cx, |project, cx| {
17445 project.open_buffer((worktree_id, "first.rs"), cx)
17446 })
17447 .await
17448 .unwrap();
17449 let buffer_2 = project
17450 .update(cx, |project, cx| {
17451 project.open_buffer((worktree_id, "second.rs"), cx)
17452 })
17453 .await
17454 .unwrap();
17455 let buffer_3 = project
17456 .update(cx, |project, cx| {
17457 project.open_buffer((worktree_id, "third.rs"), cx)
17458 })
17459 .await
17460 .unwrap();
17461
17462 let multi_buffer = cx.new(|cx| {
17463 let mut multi_buffer = MultiBuffer::new(ReadWrite);
17464 multi_buffer.push_excerpts(
17465 buffer_1.clone(),
17466 [
17467 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17468 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17469 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17470 ],
17471 cx,
17472 );
17473 multi_buffer.push_excerpts(
17474 buffer_2.clone(),
17475 [
17476 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17477 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17478 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17479 ],
17480 cx,
17481 );
17482 multi_buffer.push_excerpts(
17483 buffer_3.clone(),
17484 [
17485 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17486 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17487 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17488 ],
17489 cx,
17490 );
17491 multi_buffer
17492 });
17493 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
17494 Editor::new(
17495 EditorMode::full(),
17496 multi_buffer.clone(),
17497 Some(project.clone()),
17498 window,
17499 cx,
17500 )
17501 });
17502
17503 assert_eq!(
17504 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17505 "\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",
17506 );
17507
17508 multi_buffer_editor.update(cx, |editor, cx| {
17509 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
17510 });
17511 assert_eq!(
17512 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17513 "\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",
17514 "After folding the first buffer, its text should not be displayed"
17515 );
17516
17517 multi_buffer_editor.update(cx, |editor, cx| {
17518 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
17519 });
17520 assert_eq!(
17521 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17522 "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
17523 "After folding the second buffer, its text should not be displayed"
17524 );
17525
17526 multi_buffer_editor.update(cx, |editor, cx| {
17527 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
17528 });
17529 assert_eq!(
17530 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17531 "\n\n\n\n\n",
17532 "After folding the third buffer, its text should not be displayed"
17533 );
17534
17535 // Emulate selection inside the fold logic, that should work
17536 multi_buffer_editor.update_in(cx, |editor, window, cx| {
17537 editor
17538 .snapshot(window, cx)
17539 .next_line_boundary(Point::new(0, 4));
17540 });
17541
17542 multi_buffer_editor.update(cx, |editor, cx| {
17543 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
17544 });
17545 assert_eq!(
17546 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17547 "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
17548 "After unfolding the second buffer, its text should be displayed"
17549 );
17550
17551 // Typing inside of buffer 1 causes that buffer to be unfolded.
17552 multi_buffer_editor.update_in(cx, |editor, window, cx| {
17553 assert_eq!(
17554 multi_buffer
17555 .read(cx)
17556 .snapshot(cx)
17557 .text_for_range(Point::new(1, 0)..Point::new(1, 4))
17558 .collect::<String>(),
17559 "bbbb"
17560 );
17561 editor.change_selections(None, window, cx, |selections| {
17562 selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
17563 });
17564 editor.handle_input("B", window, cx);
17565 });
17566
17567 assert_eq!(
17568 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17569 "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
17570 "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
17571 );
17572
17573 multi_buffer_editor.update(cx, |editor, cx| {
17574 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
17575 });
17576 assert_eq!(
17577 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17578 "\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",
17579 "After unfolding the all buffers, all original text should be displayed"
17580 );
17581}
17582
17583#[gpui::test]
17584async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
17585 init_test(cx, |_| {});
17586
17587 let sample_text_1 = "1111\n2222\n3333".to_string();
17588 let sample_text_2 = "4444\n5555\n6666".to_string();
17589 let sample_text_3 = "7777\n8888\n9999".to_string();
17590
17591 let fs = FakeFs::new(cx.executor());
17592 fs.insert_tree(
17593 path!("/a"),
17594 json!({
17595 "first.rs": sample_text_1,
17596 "second.rs": sample_text_2,
17597 "third.rs": sample_text_3,
17598 }),
17599 )
17600 .await;
17601 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17602 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17603 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17604 let worktree = project.update(cx, |project, cx| {
17605 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
17606 assert_eq!(worktrees.len(), 1);
17607 worktrees.pop().unwrap()
17608 });
17609 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
17610
17611 let buffer_1 = project
17612 .update(cx, |project, cx| {
17613 project.open_buffer((worktree_id, "first.rs"), cx)
17614 })
17615 .await
17616 .unwrap();
17617 let buffer_2 = project
17618 .update(cx, |project, cx| {
17619 project.open_buffer((worktree_id, "second.rs"), cx)
17620 })
17621 .await
17622 .unwrap();
17623 let buffer_3 = project
17624 .update(cx, |project, cx| {
17625 project.open_buffer((worktree_id, "third.rs"), cx)
17626 })
17627 .await
17628 .unwrap();
17629
17630 let multi_buffer = cx.new(|cx| {
17631 let mut multi_buffer = MultiBuffer::new(ReadWrite);
17632 multi_buffer.push_excerpts(
17633 buffer_1.clone(),
17634 [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
17635 cx,
17636 );
17637 multi_buffer.push_excerpts(
17638 buffer_2.clone(),
17639 [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
17640 cx,
17641 );
17642 multi_buffer.push_excerpts(
17643 buffer_3.clone(),
17644 [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
17645 cx,
17646 );
17647 multi_buffer
17648 });
17649
17650 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
17651 Editor::new(
17652 EditorMode::full(),
17653 multi_buffer,
17654 Some(project.clone()),
17655 window,
17656 cx,
17657 )
17658 });
17659
17660 let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
17661 assert_eq!(
17662 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17663 full_text,
17664 );
17665
17666 multi_buffer_editor.update(cx, |editor, cx| {
17667 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
17668 });
17669 assert_eq!(
17670 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17671 "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
17672 "After folding the first buffer, its text should not be displayed"
17673 );
17674
17675 multi_buffer_editor.update(cx, |editor, cx| {
17676 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
17677 });
17678
17679 assert_eq!(
17680 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17681 "\n\n\n\n\n\n7777\n8888\n9999",
17682 "After folding the second buffer, its text should not be displayed"
17683 );
17684
17685 multi_buffer_editor.update(cx, |editor, cx| {
17686 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
17687 });
17688 assert_eq!(
17689 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17690 "\n\n\n\n\n",
17691 "After folding the third buffer, its text should not be displayed"
17692 );
17693
17694 multi_buffer_editor.update(cx, |editor, cx| {
17695 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
17696 });
17697 assert_eq!(
17698 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17699 "\n\n\n\n4444\n5555\n6666\n\n",
17700 "After unfolding the second buffer, its text should be displayed"
17701 );
17702
17703 multi_buffer_editor.update(cx, |editor, cx| {
17704 editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
17705 });
17706 assert_eq!(
17707 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17708 "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
17709 "After unfolding the first buffer, its text should be displayed"
17710 );
17711
17712 multi_buffer_editor.update(cx, |editor, cx| {
17713 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
17714 });
17715 assert_eq!(
17716 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17717 full_text,
17718 "After unfolding all buffers, all original text should be displayed"
17719 );
17720}
17721
17722#[gpui::test]
17723async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
17724 init_test(cx, |_| {});
17725
17726 let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
17727
17728 let fs = FakeFs::new(cx.executor());
17729 fs.insert_tree(
17730 path!("/a"),
17731 json!({
17732 "main.rs": sample_text,
17733 }),
17734 )
17735 .await;
17736 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17737 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17738 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17739 let worktree = project.update(cx, |project, cx| {
17740 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
17741 assert_eq!(worktrees.len(), 1);
17742 worktrees.pop().unwrap()
17743 });
17744 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
17745
17746 let buffer_1 = project
17747 .update(cx, |project, cx| {
17748 project.open_buffer((worktree_id, "main.rs"), cx)
17749 })
17750 .await
17751 .unwrap();
17752
17753 let multi_buffer = cx.new(|cx| {
17754 let mut multi_buffer = MultiBuffer::new(ReadWrite);
17755 multi_buffer.push_excerpts(
17756 buffer_1.clone(),
17757 [ExcerptRange::new(
17758 Point::new(0, 0)
17759 ..Point::new(
17760 sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
17761 0,
17762 ),
17763 )],
17764 cx,
17765 );
17766 multi_buffer
17767 });
17768 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
17769 Editor::new(
17770 EditorMode::full(),
17771 multi_buffer,
17772 Some(project.clone()),
17773 window,
17774 cx,
17775 )
17776 });
17777
17778 let selection_range = Point::new(1, 0)..Point::new(2, 0);
17779 multi_buffer_editor.update_in(cx, |editor, window, cx| {
17780 enum TestHighlight {}
17781 let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
17782 let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
17783 editor.highlight_text::<TestHighlight>(
17784 vec![highlight_range.clone()],
17785 HighlightStyle::color(Hsla::green()),
17786 cx,
17787 );
17788 editor.change_selections(None, window, cx, |s| s.select_ranges(Some(highlight_range)));
17789 });
17790
17791 let full_text = format!("\n\n{sample_text}");
17792 assert_eq!(
17793 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17794 full_text,
17795 );
17796}
17797
17798#[gpui::test]
17799async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
17800 init_test(cx, |_| {});
17801 cx.update(|cx| {
17802 let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
17803 "keymaps/default-linux.json",
17804 cx,
17805 )
17806 .unwrap();
17807 cx.bind_keys(default_key_bindings);
17808 });
17809
17810 let (editor, cx) = cx.add_window_view(|window, cx| {
17811 let multi_buffer = MultiBuffer::build_multi(
17812 [
17813 ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
17814 ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
17815 ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
17816 ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
17817 ],
17818 cx,
17819 );
17820 let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
17821
17822 let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
17823 // fold all but the second buffer, so that we test navigating between two
17824 // adjacent folded buffers, as well as folded buffers at the start and
17825 // end the multibuffer
17826 editor.fold_buffer(buffer_ids[0], cx);
17827 editor.fold_buffer(buffer_ids[2], cx);
17828 editor.fold_buffer(buffer_ids[3], cx);
17829
17830 editor
17831 });
17832 cx.simulate_resize(size(px(1000.), px(1000.)));
17833
17834 let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
17835 cx.assert_excerpts_with_selections(indoc! {"
17836 [EXCERPT]
17837 ˇ[FOLDED]
17838 [EXCERPT]
17839 a1
17840 b1
17841 [EXCERPT]
17842 [FOLDED]
17843 [EXCERPT]
17844 [FOLDED]
17845 "
17846 });
17847 cx.simulate_keystroke("down");
17848 cx.assert_excerpts_with_selections(indoc! {"
17849 [EXCERPT]
17850 [FOLDED]
17851 [EXCERPT]
17852 ˇa1
17853 b1
17854 [EXCERPT]
17855 [FOLDED]
17856 [EXCERPT]
17857 [FOLDED]
17858 "
17859 });
17860 cx.simulate_keystroke("down");
17861 cx.assert_excerpts_with_selections(indoc! {"
17862 [EXCERPT]
17863 [FOLDED]
17864 [EXCERPT]
17865 a1
17866 ˇb1
17867 [EXCERPT]
17868 [FOLDED]
17869 [EXCERPT]
17870 [FOLDED]
17871 "
17872 });
17873 cx.simulate_keystroke("down");
17874 cx.assert_excerpts_with_selections(indoc! {"
17875 [EXCERPT]
17876 [FOLDED]
17877 [EXCERPT]
17878 a1
17879 b1
17880 ˇ[EXCERPT]
17881 [FOLDED]
17882 [EXCERPT]
17883 [FOLDED]
17884 "
17885 });
17886 cx.simulate_keystroke("down");
17887 cx.assert_excerpts_with_selections(indoc! {"
17888 [EXCERPT]
17889 [FOLDED]
17890 [EXCERPT]
17891 a1
17892 b1
17893 [EXCERPT]
17894 ˇ[FOLDED]
17895 [EXCERPT]
17896 [FOLDED]
17897 "
17898 });
17899 for _ in 0..5 {
17900 cx.simulate_keystroke("down");
17901 cx.assert_excerpts_with_selections(indoc! {"
17902 [EXCERPT]
17903 [FOLDED]
17904 [EXCERPT]
17905 a1
17906 b1
17907 [EXCERPT]
17908 [FOLDED]
17909 [EXCERPT]
17910 ˇ[FOLDED]
17911 "
17912 });
17913 }
17914
17915 cx.simulate_keystroke("up");
17916 cx.assert_excerpts_with_selections(indoc! {"
17917 [EXCERPT]
17918 [FOLDED]
17919 [EXCERPT]
17920 a1
17921 b1
17922 [EXCERPT]
17923 ˇ[FOLDED]
17924 [EXCERPT]
17925 [FOLDED]
17926 "
17927 });
17928 cx.simulate_keystroke("up");
17929 cx.assert_excerpts_with_selections(indoc! {"
17930 [EXCERPT]
17931 [FOLDED]
17932 [EXCERPT]
17933 a1
17934 b1
17935 ˇ[EXCERPT]
17936 [FOLDED]
17937 [EXCERPT]
17938 [FOLDED]
17939 "
17940 });
17941 cx.simulate_keystroke("up");
17942 cx.assert_excerpts_with_selections(indoc! {"
17943 [EXCERPT]
17944 [FOLDED]
17945 [EXCERPT]
17946 a1
17947 ˇb1
17948 [EXCERPT]
17949 [FOLDED]
17950 [EXCERPT]
17951 [FOLDED]
17952 "
17953 });
17954 cx.simulate_keystroke("up");
17955 cx.assert_excerpts_with_selections(indoc! {"
17956 [EXCERPT]
17957 [FOLDED]
17958 [EXCERPT]
17959 ˇa1
17960 b1
17961 [EXCERPT]
17962 [FOLDED]
17963 [EXCERPT]
17964 [FOLDED]
17965 "
17966 });
17967 for _ in 0..5 {
17968 cx.simulate_keystroke("up");
17969 cx.assert_excerpts_with_selections(indoc! {"
17970 [EXCERPT]
17971 ˇ[FOLDED]
17972 [EXCERPT]
17973 a1
17974 b1
17975 [EXCERPT]
17976 [FOLDED]
17977 [EXCERPT]
17978 [FOLDED]
17979 "
17980 });
17981 }
17982}
17983
17984#[gpui::test]
17985async fn test_inline_completion_text(cx: &mut TestAppContext) {
17986 init_test(cx, |_| {});
17987
17988 // Simple insertion
17989 assert_highlighted_edits(
17990 "Hello, world!",
17991 vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
17992 true,
17993 cx,
17994 |highlighted_edits, cx| {
17995 assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
17996 assert_eq!(highlighted_edits.highlights.len(), 1);
17997 assert_eq!(highlighted_edits.highlights[0].0, 6..16);
17998 assert_eq!(
17999 highlighted_edits.highlights[0].1.background_color,
18000 Some(cx.theme().status().created_background)
18001 );
18002 },
18003 )
18004 .await;
18005
18006 // Replacement
18007 assert_highlighted_edits(
18008 "This is a test.",
18009 vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
18010 false,
18011 cx,
18012 |highlighted_edits, cx| {
18013 assert_eq!(highlighted_edits.text, "That is a test.");
18014 assert_eq!(highlighted_edits.highlights.len(), 1);
18015 assert_eq!(highlighted_edits.highlights[0].0, 0..4);
18016 assert_eq!(
18017 highlighted_edits.highlights[0].1.background_color,
18018 Some(cx.theme().status().created_background)
18019 );
18020 },
18021 )
18022 .await;
18023
18024 // Multiple edits
18025 assert_highlighted_edits(
18026 "Hello, world!",
18027 vec![
18028 (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
18029 (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
18030 ],
18031 false,
18032 cx,
18033 |highlighted_edits, cx| {
18034 assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
18035 assert_eq!(highlighted_edits.highlights.len(), 2);
18036 assert_eq!(highlighted_edits.highlights[0].0, 0..9);
18037 assert_eq!(highlighted_edits.highlights[1].0, 16..29);
18038 assert_eq!(
18039 highlighted_edits.highlights[0].1.background_color,
18040 Some(cx.theme().status().created_background)
18041 );
18042 assert_eq!(
18043 highlighted_edits.highlights[1].1.background_color,
18044 Some(cx.theme().status().created_background)
18045 );
18046 },
18047 )
18048 .await;
18049
18050 // Multiple lines with edits
18051 assert_highlighted_edits(
18052 "First line\nSecond line\nThird line\nFourth line",
18053 vec![
18054 (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
18055 (
18056 Point::new(2, 0)..Point::new(2, 10),
18057 "New third line".to_string(),
18058 ),
18059 (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
18060 ],
18061 false,
18062 cx,
18063 |highlighted_edits, cx| {
18064 assert_eq!(
18065 highlighted_edits.text,
18066 "Second modified\nNew third line\nFourth updated line"
18067 );
18068 assert_eq!(highlighted_edits.highlights.len(), 3);
18069 assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
18070 assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
18071 assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
18072 for highlight in &highlighted_edits.highlights {
18073 assert_eq!(
18074 highlight.1.background_color,
18075 Some(cx.theme().status().created_background)
18076 );
18077 }
18078 },
18079 )
18080 .await;
18081}
18082
18083#[gpui::test]
18084async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
18085 init_test(cx, |_| {});
18086
18087 // Deletion
18088 assert_highlighted_edits(
18089 "Hello, world!",
18090 vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
18091 true,
18092 cx,
18093 |highlighted_edits, cx| {
18094 assert_eq!(highlighted_edits.text, "Hello, world!");
18095 assert_eq!(highlighted_edits.highlights.len(), 1);
18096 assert_eq!(highlighted_edits.highlights[0].0, 5..11);
18097 assert_eq!(
18098 highlighted_edits.highlights[0].1.background_color,
18099 Some(cx.theme().status().deleted_background)
18100 );
18101 },
18102 )
18103 .await;
18104
18105 // Insertion
18106 assert_highlighted_edits(
18107 "Hello, world!",
18108 vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
18109 true,
18110 cx,
18111 |highlighted_edits, cx| {
18112 assert_eq!(highlighted_edits.highlights.len(), 1);
18113 assert_eq!(highlighted_edits.highlights[0].0, 6..14);
18114 assert_eq!(
18115 highlighted_edits.highlights[0].1.background_color,
18116 Some(cx.theme().status().created_background)
18117 );
18118 },
18119 )
18120 .await;
18121}
18122
18123async fn assert_highlighted_edits(
18124 text: &str,
18125 edits: Vec<(Range<Point>, String)>,
18126 include_deletions: bool,
18127 cx: &mut TestAppContext,
18128 assertion_fn: impl Fn(HighlightedText, &App),
18129) {
18130 let window = cx.add_window(|window, cx| {
18131 let buffer = MultiBuffer::build_simple(text, cx);
18132 Editor::new(EditorMode::full(), buffer, None, window, cx)
18133 });
18134 let cx = &mut VisualTestContext::from_window(*window, cx);
18135
18136 let (buffer, snapshot) = window
18137 .update(cx, |editor, _window, cx| {
18138 (
18139 editor.buffer().clone(),
18140 editor.buffer().read(cx).snapshot(cx),
18141 )
18142 })
18143 .unwrap();
18144
18145 let edits = edits
18146 .into_iter()
18147 .map(|(range, edit)| {
18148 (
18149 snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
18150 edit,
18151 )
18152 })
18153 .collect::<Vec<_>>();
18154
18155 let text_anchor_edits = edits
18156 .clone()
18157 .into_iter()
18158 .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
18159 .collect::<Vec<_>>();
18160
18161 let edit_preview = window
18162 .update(cx, |_, _window, cx| {
18163 buffer
18164 .read(cx)
18165 .as_singleton()
18166 .unwrap()
18167 .read(cx)
18168 .preview_edits(text_anchor_edits.into(), cx)
18169 })
18170 .unwrap()
18171 .await;
18172
18173 cx.update(|_window, cx| {
18174 let highlighted_edits = inline_completion_edit_text(
18175 &snapshot.as_singleton().unwrap().2,
18176 &edits,
18177 &edit_preview,
18178 include_deletions,
18179 cx,
18180 );
18181 assertion_fn(highlighted_edits, cx)
18182 });
18183}
18184
18185#[track_caller]
18186fn assert_breakpoint(
18187 breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
18188 path: &Arc<Path>,
18189 expected: Vec<(u32, Breakpoint)>,
18190) {
18191 if expected.len() == 0usize {
18192 assert!(!breakpoints.contains_key(path), "{}", path.display());
18193 } else {
18194 let mut breakpoint = breakpoints
18195 .get(path)
18196 .unwrap()
18197 .into_iter()
18198 .map(|breakpoint| {
18199 (
18200 breakpoint.row,
18201 Breakpoint {
18202 message: breakpoint.message.clone(),
18203 state: breakpoint.state,
18204 condition: breakpoint.condition.clone(),
18205 hit_condition: breakpoint.hit_condition.clone(),
18206 },
18207 )
18208 })
18209 .collect::<Vec<_>>();
18210
18211 breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
18212
18213 assert_eq!(expected, breakpoint);
18214 }
18215}
18216
18217fn add_log_breakpoint_at_cursor(
18218 editor: &mut Editor,
18219 log_message: &str,
18220 window: &mut Window,
18221 cx: &mut Context<Editor>,
18222) {
18223 let (anchor, bp) = editor
18224 .breakpoints_at_cursors(window, cx)
18225 .first()
18226 .and_then(|(anchor, bp)| {
18227 if let Some(bp) = bp {
18228 Some((*anchor, bp.clone()))
18229 } else {
18230 None
18231 }
18232 })
18233 .unwrap_or_else(|| {
18234 let cursor_position: Point = editor.selections.newest(cx).head();
18235
18236 let breakpoint_position = editor
18237 .snapshot(window, cx)
18238 .display_snapshot
18239 .buffer_snapshot
18240 .anchor_before(Point::new(cursor_position.row, 0));
18241
18242 (breakpoint_position, Breakpoint::new_log(&log_message))
18243 });
18244
18245 editor.edit_breakpoint_at_anchor(
18246 anchor,
18247 bp,
18248 BreakpointEditAction::EditLogMessage(log_message.into()),
18249 cx,
18250 );
18251}
18252
18253#[gpui::test]
18254async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
18255 init_test(cx, |_| {});
18256
18257 let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
18258 let fs = FakeFs::new(cx.executor());
18259 fs.insert_tree(
18260 path!("/a"),
18261 json!({
18262 "main.rs": sample_text,
18263 }),
18264 )
18265 .await;
18266 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18267 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18268 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18269
18270 let fs = FakeFs::new(cx.executor());
18271 fs.insert_tree(
18272 path!("/a"),
18273 json!({
18274 "main.rs": sample_text,
18275 }),
18276 )
18277 .await;
18278 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18279 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18280 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18281 let worktree_id = workspace
18282 .update(cx, |workspace, _window, cx| {
18283 workspace.project().update(cx, |project, cx| {
18284 project.worktrees(cx).next().unwrap().read(cx).id()
18285 })
18286 })
18287 .unwrap();
18288
18289 let buffer = project
18290 .update(cx, |project, cx| {
18291 project.open_buffer((worktree_id, "main.rs"), cx)
18292 })
18293 .await
18294 .unwrap();
18295
18296 let (editor, cx) = cx.add_window_view(|window, cx| {
18297 Editor::new(
18298 EditorMode::full(),
18299 MultiBuffer::build_from_buffer(buffer, cx),
18300 Some(project.clone()),
18301 window,
18302 cx,
18303 )
18304 });
18305
18306 let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
18307 let abs_path = project.read_with(cx, |project, cx| {
18308 project
18309 .absolute_path(&project_path, cx)
18310 .map(|path_buf| Arc::from(path_buf.to_owned()))
18311 .unwrap()
18312 });
18313
18314 // assert we can add breakpoint on the first line
18315 editor.update_in(cx, |editor, window, cx| {
18316 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18317 editor.move_to_end(&MoveToEnd, window, cx);
18318 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18319 });
18320
18321 let breakpoints = editor.update(cx, |editor, cx| {
18322 editor
18323 .breakpoint_store()
18324 .as_ref()
18325 .unwrap()
18326 .read(cx)
18327 .all_breakpoints(cx)
18328 .clone()
18329 });
18330
18331 assert_eq!(1, breakpoints.len());
18332 assert_breakpoint(
18333 &breakpoints,
18334 &abs_path,
18335 vec![
18336 (0, Breakpoint::new_standard()),
18337 (3, Breakpoint::new_standard()),
18338 ],
18339 );
18340
18341 editor.update_in(cx, |editor, window, cx| {
18342 editor.move_to_beginning(&MoveToBeginning, window, cx);
18343 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18344 });
18345
18346 let breakpoints = editor.update(cx, |editor, cx| {
18347 editor
18348 .breakpoint_store()
18349 .as_ref()
18350 .unwrap()
18351 .read(cx)
18352 .all_breakpoints(cx)
18353 .clone()
18354 });
18355
18356 assert_eq!(1, breakpoints.len());
18357 assert_breakpoint(
18358 &breakpoints,
18359 &abs_path,
18360 vec![(3, Breakpoint::new_standard())],
18361 );
18362
18363 editor.update_in(cx, |editor, window, cx| {
18364 editor.move_to_end(&MoveToEnd, window, cx);
18365 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18366 });
18367
18368 let breakpoints = editor.update(cx, |editor, cx| {
18369 editor
18370 .breakpoint_store()
18371 .as_ref()
18372 .unwrap()
18373 .read(cx)
18374 .all_breakpoints(cx)
18375 .clone()
18376 });
18377
18378 assert_eq!(0, breakpoints.len());
18379 assert_breakpoint(&breakpoints, &abs_path, vec![]);
18380}
18381
18382#[gpui::test]
18383async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
18384 init_test(cx, |_| {});
18385
18386 let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
18387
18388 let fs = FakeFs::new(cx.executor());
18389 fs.insert_tree(
18390 path!("/a"),
18391 json!({
18392 "main.rs": sample_text,
18393 }),
18394 )
18395 .await;
18396 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18397 let (workspace, cx) =
18398 cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
18399
18400 let worktree_id = workspace.update(cx, |workspace, cx| {
18401 workspace.project().update(cx, |project, cx| {
18402 project.worktrees(cx).next().unwrap().read(cx).id()
18403 })
18404 });
18405
18406 let buffer = project
18407 .update(cx, |project, cx| {
18408 project.open_buffer((worktree_id, "main.rs"), cx)
18409 })
18410 .await
18411 .unwrap();
18412
18413 let (editor, cx) = cx.add_window_view(|window, cx| {
18414 Editor::new(
18415 EditorMode::full(),
18416 MultiBuffer::build_from_buffer(buffer, cx),
18417 Some(project.clone()),
18418 window,
18419 cx,
18420 )
18421 });
18422
18423 let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
18424 let abs_path = project.read_with(cx, |project, cx| {
18425 project
18426 .absolute_path(&project_path, cx)
18427 .map(|path_buf| Arc::from(path_buf.to_owned()))
18428 .unwrap()
18429 });
18430
18431 editor.update_in(cx, |editor, window, cx| {
18432 add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
18433 });
18434
18435 let breakpoints = editor.update(cx, |editor, cx| {
18436 editor
18437 .breakpoint_store()
18438 .as_ref()
18439 .unwrap()
18440 .read(cx)
18441 .all_breakpoints(cx)
18442 .clone()
18443 });
18444
18445 assert_breakpoint(
18446 &breakpoints,
18447 &abs_path,
18448 vec![(0, Breakpoint::new_log("hello world"))],
18449 );
18450
18451 // Removing a log message from a log breakpoint should remove it
18452 editor.update_in(cx, |editor, window, cx| {
18453 add_log_breakpoint_at_cursor(editor, "", window, cx);
18454 });
18455
18456 let breakpoints = editor.update(cx, |editor, cx| {
18457 editor
18458 .breakpoint_store()
18459 .as_ref()
18460 .unwrap()
18461 .read(cx)
18462 .all_breakpoints(cx)
18463 .clone()
18464 });
18465
18466 assert_breakpoint(&breakpoints, &abs_path, vec![]);
18467
18468 editor.update_in(cx, |editor, window, cx| {
18469 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18470 editor.move_to_end(&MoveToEnd, window, cx);
18471 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18472 // Not adding a log message to a standard breakpoint shouldn't remove it
18473 add_log_breakpoint_at_cursor(editor, "", window, cx);
18474 });
18475
18476 let breakpoints = editor.update(cx, |editor, cx| {
18477 editor
18478 .breakpoint_store()
18479 .as_ref()
18480 .unwrap()
18481 .read(cx)
18482 .all_breakpoints(cx)
18483 .clone()
18484 });
18485
18486 assert_breakpoint(
18487 &breakpoints,
18488 &abs_path,
18489 vec![
18490 (0, Breakpoint::new_standard()),
18491 (3, Breakpoint::new_standard()),
18492 ],
18493 );
18494
18495 editor.update_in(cx, |editor, window, cx| {
18496 add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
18497 });
18498
18499 let breakpoints = editor.update(cx, |editor, cx| {
18500 editor
18501 .breakpoint_store()
18502 .as_ref()
18503 .unwrap()
18504 .read(cx)
18505 .all_breakpoints(cx)
18506 .clone()
18507 });
18508
18509 assert_breakpoint(
18510 &breakpoints,
18511 &abs_path,
18512 vec![
18513 (0, Breakpoint::new_standard()),
18514 (3, Breakpoint::new_log("hello world")),
18515 ],
18516 );
18517
18518 editor.update_in(cx, |editor, window, cx| {
18519 add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
18520 });
18521
18522 let breakpoints = editor.update(cx, |editor, cx| {
18523 editor
18524 .breakpoint_store()
18525 .as_ref()
18526 .unwrap()
18527 .read(cx)
18528 .all_breakpoints(cx)
18529 .clone()
18530 });
18531
18532 assert_breakpoint(
18533 &breakpoints,
18534 &abs_path,
18535 vec![
18536 (0, Breakpoint::new_standard()),
18537 (3, Breakpoint::new_log("hello Earth!!")),
18538 ],
18539 );
18540}
18541
18542/// This also tests that Editor::breakpoint_at_cursor_head is working properly
18543/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
18544/// or when breakpoints were placed out of order. This tests for a regression too
18545#[gpui::test]
18546async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
18547 init_test(cx, |_| {});
18548
18549 let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
18550 let fs = FakeFs::new(cx.executor());
18551 fs.insert_tree(
18552 path!("/a"),
18553 json!({
18554 "main.rs": sample_text,
18555 }),
18556 )
18557 .await;
18558 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18559 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18560 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18561
18562 let fs = FakeFs::new(cx.executor());
18563 fs.insert_tree(
18564 path!("/a"),
18565 json!({
18566 "main.rs": sample_text,
18567 }),
18568 )
18569 .await;
18570 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18571 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18572 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18573 let worktree_id = workspace
18574 .update(cx, |workspace, _window, cx| {
18575 workspace.project().update(cx, |project, cx| {
18576 project.worktrees(cx).next().unwrap().read(cx).id()
18577 })
18578 })
18579 .unwrap();
18580
18581 let buffer = project
18582 .update(cx, |project, cx| {
18583 project.open_buffer((worktree_id, "main.rs"), cx)
18584 })
18585 .await
18586 .unwrap();
18587
18588 let (editor, cx) = cx.add_window_view(|window, cx| {
18589 Editor::new(
18590 EditorMode::full(),
18591 MultiBuffer::build_from_buffer(buffer, cx),
18592 Some(project.clone()),
18593 window,
18594 cx,
18595 )
18596 });
18597
18598 let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
18599 let abs_path = project.read_with(cx, |project, cx| {
18600 project
18601 .absolute_path(&project_path, cx)
18602 .map(|path_buf| Arc::from(path_buf.to_owned()))
18603 .unwrap()
18604 });
18605
18606 // assert we can add breakpoint on the first line
18607 editor.update_in(cx, |editor, window, cx| {
18608 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18609 editor.move_to_end(&MoveToEnd, window, cx);
18610 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18611 editor.move_up(&MoveUp, window, cx);
18612 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18613 });
18614
18615 let breakpoints = editor.update(cx, |editor, cx| {
18616 editor
18617 .breakpoint_store()
18618 .as_ref()
18619 .unwrap()
18620 .read(cx)
18621 .all_breakpoints(cx)
18622 .clone()
18623 });
18624
18625 assert_eq!(1, breakpoints.len());
18626 assert_breakpoint(
18627 &breakpoints,
18628 &abs_path,
18629 vec![
18630 (0, Breakpoint::new_standard()),
18631 (2, Breakpoint::new_standard()),
18632 (3, Breakpoint::new_standard()),
18633 ],
18634 );
18635
18636 editor.update_in(cx, |editor, window, cx| {
18637 editor.move_to_beginning(&MoveToBeginning, window, cx);
18638 editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
18639 editor.move_to_end(&MoveToEnd, window, cx);
18640 editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
18641 // Disabling a breakpoint that doesn't exist should do nothing
18642 editor.move_up(&MoveUp, window, cx);
18643 editor.move_up(&MoveUp, window, cx);
18644 editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
18645 });
18646
18647 let breakpoints = editor.update(cx, |editor, cx| {
18648 editor
18649 .breakpoint_store()
18650 .as_ref()
18651 .unwrap()
18652 .read(cx)
18653 .all_breakpoints(cx)
18654 .clone()
18655 });
18656
18657 let disable_breakpoint = {
18658 let mut bp = Breakpoint::new_standard();
18659 bp.state = BreakpointState::Disabled;
18660 bp
18661 };
18662
18663 assert_eq!(1, breakpoints.len());
18664 assert_breakpoint(
18665 &breakpoints,
18666 &abs_path,
18667 vec![
18668 (0, disable_breakpoint.clone()),
18669 (2, Breakpoint::new_standard()),
18670 (3, disable_breakpoint.clone()),
18671 ],
18672 );
18673
18674 editor.update_in(cx, |editor, window, cx| {
18675 editor.move_to_beginning(&MoveToBeginning, window, cx);
18676 editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
18677 editor.move_to_end(&MoveToEnd, window, cx);
18678 editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
18679 editor.move_up(&MoveUp, window, cx);
18680 editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
18681 });
18682
18683 let breakpoints = editor.update(cx, |editor, cx| {
18684 editor
18685 .breakpoint_store()
18686 .as_ref()
18687 .unwrap()
18688 .read(cx)
18689 .all_breakpoints(cx)
18690 .clone()
18691 });
18692
18693 assert_eq!(1, breakpoints.len());
18694 assert_breakpoint(
18695 &breakpoints,
18696 &abs_path,
18697 vec![
18698 (0, Breakpoint::new_standard()),
18699 (2, disable_breakpoint),
18700 (3, Breakpoint::new_standard()),
18701 ],
18702 );
18703}
18704
18705#[gpui::test]
18706async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
18707 init_test(cx, |_| {});
18708 let capabilities = lsp::ServerCapabilities {
18709 rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
18710 prepare_provider: Some(true),
18711 work_done_progress_options: Default::default(),
18712 })),
18713 ..Default::default()
18714 };
18715 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
18716
18717 cx.set_state(indoc! {"
18718 struct Fˇoo {}
18719 "});
18720
18721 cx.update_editor(|editor, _, cx| {
18722 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
18723 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
18724 editor.highlight_background::<DocumentHighlightRead>(
18725 &[highlight_range],
18726 |c| c.editor_document_highlight_read_background,
18727 cx,
18728 );
18729 });
18730
18731 let mut prepare_rename_handler = cx
18732 .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
18733 move |_, _, _| async move {
18734 Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
18735 start: lsp::Position {
18736 line: 0,
18737 character: 7,
18738 },
18739 end: lsp::Position {
18740 line: 0,
18741 character: 10,
18742 },
18743 })))
18744 },
18745 );
18746 let prepare_rename_task = cx
18747 .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
18748 .expect("Prepare rename was not started");
18749 prepare_rename_handler.next().await.unwrap();
18750 prepare_rename_task.await.expect("Prepare rename failed");
18751
18752 let mut rename_handler =
18753 cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
18754 let edit = lsp::TextEdit {
18755 range: lsp::Range {
18756 start: lsp::Position {
18757 line: 0,
18758 character: 7,
18759 },
18760 end: lsp::Position {
18761 line: 0,
18762 character: 10,
18763 },
18764 },
18765 new_text: "FooRenamed".to_string(),
18766 };
18767 Ok(Some(lsp::WorkspaceEdit::new(
18768 // Specify the same edit twice
18769 std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
18770 )))
18771 });
18772 let rename_task = cx
18773 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
18774 .expect("Confirm rename was not started");
18775 rename_handler.next().await.unwrap();
18776 rename_task.await.expect("Confirm rename failed");
18777 cx.run_until_parked();
18778
18779 // Despite two edits, only one is actually applied as those are identical
18780 cx.assert_editor_state(indoc! {"
18781 struct FooRenamedˇ {}
18782 "});
18783}
18784
18785#[gpui::test]
18786async fn test_rename_without_prepare(cx: &mut TestAppContext) {
18787 init_test(cx, |_| {});
18788 // These capabilities indicate that the server does not support prepare rename.
18789 let capabilities = lsp::ServerCapabilities {
18790 rename_provider: Some(lsp::OneOf::Left(true)),
18791 ..Default::default()
18792 };
18793 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
18794
18795 cx.set_state(indoc! {"
18796 struct Fˇoo {}
18797 "});
18798
18799 cx.update_editor(|editor, _window, cx| {
18800 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
18801 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
18802 editor.highlight_background::<DocumentHighlightRead>(
18803 &[highlight_range],
18804 |c| c.editor_document_highlight_read_background,
18805 cx,
18806 );
18807 });
18808
18809 cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
18810 .expect("Prepare rename was not started")
18811 .await
18812 .expect("Prepare rename failed");
18813
18814 let mut rename_handler =
18815 cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
18816 let edit = lsp::TextEdit {
18817 range: lsp::Range {
18818 start: lsp::Position {
18819 line: 0,
18820 character: 7,
18821 },
18822 end: lsp::Position {
18823 line: 0,
18824 character: 10,
18825 },
18826 },
18827 new_text: "FooRenamed".to_string(),
18828 };
18829 Ok(Some(lsp::WorkspaceEdit::new(
18830 std::collections::HashMap::from_iter(Some((url, vec![edit]))),
18831 )))
18832 });
18833 let rename_task = cx
18834 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
18835 .expect("Confirm rename was not started");
18836 rename_handler.next().await.unwrap();
18837 rename_task.await.expect("Confirm rename failed");
18838 cx.run_until_parked();
18839
18840 // Correct range is renamed, as `surrounding_word` is used to find it.
18841 cx.assert_editor_state(indoc! {"
18842 struct FooRenamedˇ {}
18843 "});
18844}
18845
18846#[gpui::test]
18847async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
18848 init_test(cx, |_| {});
18849 let mut cx = EditorTestContext::new(cx).await;
18850
18851 let language = Arc::new(
18852 Language::new(
18853 LanguageConfig::default(),
18854 Some(tree_sitter_html::LANGUAGE.into()),
18855 )
18856 .with_brackets_query(
18857 r#"
18858 ("<" @open "/>" @close)
18859 ("</" @open ">" @close)
18860 ("<" @open ">" @close)
18861 ("\"" @open "\"" @close)
18862 ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
18863 "#,
18864 )
18865 .unwrap(),
18866 );
18867 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
18868
18869 cx.set_state(indoc! {"
18870 <span>ˇ</span>
18871 "});
18872 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
18873 cx.assert_editor_state(indoc! {"
18874 <span>
18875 ˇ
18876 </span>
18877 "});
18878
18879 cx.set_state(indoc! {"
18880 <span><span></span>ˇ</span>
18881 "});
18882 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
18883 cx.assert_editor_state(indoc! {"
18884 <span><span></span>
18885 ˇ</span>
18886 "});
18887
18888 cx.set_state(indoc! {"
18889 <span>ˇ
18890 </span>
18891 "});
18892 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
18893 cx.assert_editor_state(indoc! {"
18894 <span>
18895 ˇ
18896 </span>
18897 "});
18898}
18899
18900#[gpui::test(iterations = 10)]
18901async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
18902 init_test(cx, |_| {});
18903
18904 let fs = FakeFs::new(cx.executor());
18905 fs.insert_tree(
18906 path!("/dir"),
18907 json!({
18908 "a.ts": "a",
18909 }),
18910 )
18911 .await;
18912
18913 let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
18914 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18915 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18916
18917 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18918 language_registry.add(Arc::new(Language::new(
18919 LanguageConfig {
18920 name: "TypeScript".into(),
18921 matcher: LanguageMatcher {
18922 path_suffixes: vec!["ts".to_string()],
18923 ..Default::default()
18924 },
18925 ..Default::default()
18926 },
18927 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
18928 )));
18929 let mut fake_language_servers = language_registry.register_fake_lsp(
18930 "TypeScript",
18931 FakeLspAdapter {
18932 capabilities: lsp::ServerCapabilities {
18933 code_lens_provider: Some(lsp::CodeLensOptions {
18934 resolve_provider: Some(true),
18935 }),
18936 execute_command_provider: Some(lsp::ExecuteCommandOptions {
18937 commands: vec!["_the/command".to_string()],
18938 ..lsp::ExecuteCommandOptions::default()
18939 }),
18940 ..lsp::ServerCapabilities::default()
18941 },
18942 ..FakeLspAdapter::default()
18943 },
18944 );
18945
18946 let (buffer, _handle) = project
18947 .update(cx, |p, cx| {
18948 p.open_local_buffer_with_lsp(path!("/dir/a.ts"), cx)
18949 })
18950 .await
18951 .unwrap();
18952 cx.executor().run_until_parked();
18953
18954 let fake_server = fake_language_servers.next().await.unwrap();
18955
18956 let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
18957 let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
18958 drop(buffer_snapshot);
18959 let actions = cx
18960 .update_window(*workspace, |_, window, cx| {
18961 project.code_actions(&buffer, anchor..anchor, window, cx)
18962 })
18963 .unwrap();
18964
18965 fake_server
18966 .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
18967 Ok(Some(vec![
18968 lsp::CodeLens {
18969 range: lsp::Range::default(),
18970 command: Some(lsp::Command {
18971 title: "Code lens command".to_owned(),
18972 command: "_the/command".to_owned(),
18973 arguments: None,
18974 }),
18975 data: None,
18976 },
18977 lsp::CodeLens {
18978 range: lsp::Range::default(),
18979 command: Some(lsp::Command {
18980 title: "Command not in capabilities".to_owned(),
18981 command: "not in capabilities".to_owned(),
18982 arguments: None,
18983 }),
18984 data: None,
18985 },
18986 lsp::CodeLens {
18987 range: lsp::Range {
18988 start: lsp::Position {
18989 line: 1,
18990 character: 1,
18991 },
18992 end: lsp::Position {
18993 line: 1,
18994 character: 1,
18995 },
18996 },
18997 command: Some(lsp::Command {
18998 title: "Command not in range".to_owned(),
18999 command: "_the/command".to_owned(),
19000 arguments: None,
19001 }),
19002 data: None,
19003 },
19004 ]))
19005 })
19006 .next()
19007 .await;
19008
19009 let actions = actions.await.unwrap();
19010 assert_eq!(
19011 actions.len(),
19012 1,
19013 "Should have only one valid action for the 0..0 range"
19014 );
19015 let action = actions[0].clone();
19016 let apply = project.update(cx, |project, cx| {
19017 project.apply_code_action(buffer.clone(), action, true, cx)
19018 });
19019
19020 // Resolving the code action does not populate its edits. In absence of
19021 // edits, we must execute the given command.
19022 fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
19023 |mut lens, _| async move {
19024 let lens_command = lens.command.as_mut().expect("should have a command");
19025 assert_eq!(lens_command.title, "Code lens command");
19026 lens_command.arguments = Some(vec![json!("the-argument")]);
19027 Ok(lens)
19028 },
19029 );
19030
19031 // While executing the command, the language server sends the editor
19032 // a `workspaceEdit` request.
19033 fake_server
19034 .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
19035 let fake = fake_server.clone();
19036 move |params, _| {
19037 assert_eq!(params.command, "_the/command");
19038 let fake = fake.clone();
19039 async move {
19040 fake.server
19041 .request::<lsp::request::ApplyWorkspaceEdit>(
19042 lsp::ApplyWorkspaceEditParams {
19043 label: None,
19044 edit: lsp::WorkspaceEdit {
19045 changes: Some(
19046 [(
19047 lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
19048 vec![lsp::TextEdit {
19049 range: lsp::Range::new(
19050 lsp::Position::new(0, 0),
19051 lsp::Position::new(0, 0),
19052 ),
19053 new_text: "X".into(),
19054 }],
19055 )]
19056 .into_iter()
19057 .collect(),
19058 ),
19059 ..Default::default()
19060 },
19061 },
19062 )
19063 .await
19064 .unwrap();
19065 Ok(Some(json!(null)))
19066 }
19067 }
19068 })
19069 .next()
19070 .await;
19071
19072 // Applying the code lens command returns a project transaction containing the edits
19073 // sent by the language server in its `workspaceEdit` request.
19074 let transaction = apply.await.unwrap();
19075 assert!(transaction.0.contains_key(&buffer));
19076 buffer.update(cx, |buffer, cx| {
19077 assert_eq!(buffer.text(), "Xa");
19078 buffer.undo(cx);
19079 assert_eq!(buffer.text(), "a");
19080 });
19081}
19082
19083#[gpui::test]
19084async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
19085 init_test(cx, |_| {});
19086
19087 let fs = FakeFs::new(cx.executor());
19088 let main_text = r#"fn main() {
19089println!("1");
19090println!("2");
19091println!("3");
19092println!("4");
19093println!("5");
19094}"#;
19095 let lib_text = "mod foo {}";
19096 fs.insert_tree(
19097 path!("/a"),
19098 json!({
19099 "lib.rs": lib_text,
19100 "main.rs": main_text,
19101 }),
19102 )
19103 .await;
19104
19105 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19106 let (workspace, cx) =
19107 cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
19108 let worktree_id = workspace.update(cx, |workspace, cx| {
19109 workspace.project().update(cx, |project, cx| {
19110 project.worktrees(cx).next().unwrap().read(cx).id()
19111 })
19112 });
19113
19114 let expected_ranges = vec![
19115 Point::new(0, 0)..Point::new(0, 0),
19116 Point::new(1, 0)..Point::new(1, 1),
19117 Point::new(2, 0)..Point::new(2, 2),
19118 Point::new(3, 0)..Point::new(3, 3),
19119 ];
19120
19121 let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
19122 let editor_1 = workspace
19123 .update_in(cx, |workspace, window, cx| {
19124 workspace.open_path(
19125 (worktree_id, "main.rs"),
19126 Some(pane_1.downgrade()),
19127 true,
19128 window,
19129 cx,
19130 )
19131 })
19132 .unwrap()
19133 .await
19134 .downcast::<Editor>()
19135 .unwrap();
19136 pane_1.update(cx, |pane, cx| {
19137 let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19138 open_editor.update(cx, |editor, cx| {
19139 assert_eq!(
19140 editor.display_text(cx),
19141 main_text,
19142 "Original main.rs text on initial open",
19143 );
19144 assert_eq!(
19145 editor
19146 .selections
19147 .all::<Point>(cx)
19148 .into_iter()
19149 .map(|s| s.range())
19150 .collect::<Vec<_>>(),
19151 vec![Point::zero()..Point::zero()],
19152 "Default selections on initial open",
19153 );
19154 })
19155 });
19156 editor_1.update_in(cx, |editor, window, cx| {
19157 editor.change_selections(None, window, cx, |s| {
19158 s.select_ranges(expected_ranges.clone());
19159 });
19160 });
19161
19162 let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
19163 workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
19164 });
19165 let editor_2 = workspace
19166 .update_in(cx, |workspace, window, cx| {
19167 workspace.open_path(
19168 (worktree_id, "main.rs"),
19169 Some(pane_2.downgrade()),
19170 true,
19171 window,
19172 cx,
19173 )
19174 })
19175 .unwrap()
19176 .await
19177 .downcast::<Editor>()
19178 .unwrap();
19179 pane_2.update(cx, |pane, cx| {
19180 let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19181 open_editor.update(cx, |editor, cx| {
19182 assert_eq!(
19183 editor.display_text(cx),
19184 main_text,
19185 "Original main.rs text on initial open in another panel",
19186 );
19187 assert_eq!(
19188 editor
19189 .selections
19190 .all::<Point>(cx)
19191 .into_iter()
19192 .map(|s| s.range())
19193 .collect::<Vec<_>>(),
19194 vec![Point::zero()..Point::zero()],
19195 "Default selections on initial open in another panel",
19196 );
19197 })
19198 });
19199
19200 editor_2.update_in(cx, |editor, window, cx| {
19201 editor.fold_ranges(expected_ranges.clone(), false, window, cx);
19202 });
19203
19204 let _other_editor_1 = workspace
19205 .update_in(cx, |workspace, window, cx| {
19206 workspace.open_path(
19207 (worktree_id, "lib.rs"),
19208 Some(pane_1.downgrade()),
19209 true,
19210 window,
19211 cx,
19212 )
19213 })
19214 .unwrap()
19215 .await
19216 .downcast::<Editor>()
19217 .unwrap();
19218 pane_1
19219 .update_in(cx, |pane, window, cx| {
19220 pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
19221 .unwrap()
19222 })
19223 .await
19224 .unwrap();
19225 drop(editor_1);
19226 pane_1.update(cx, |pane, cx| {
19227 pane.active_item()
19228 .unwrap()
19229 .downcast::<Editor>()
19230 .unwrap()
19231 .update(cx, |editor, cx| {
19232 assert_eq!(
19233 editor.display_text(cx),
19234 lib_text,
19235 "Other file should be open and active",
19236 );
19237 });
19238 assert_eq!(pane.items().count(), 1, "No other editors should be open");
19239 });
19240
19241 let _other_editor_2 = workspace
19242 .update_in(cx, |workspace, window, cx| {
19243 workspace.open_path(
19244 (worktree_id, "lib.rs"),
19245 Some(pane_2.downgrade()),
19246 true,
19247 window,
19248 cx,
19249 )
19250 })
19251 .unwrap()
19252 .await
19253 .downcast::<Editor>()
19254 .unwrap();
19255 pane_2
19256 .update_in(cx, |pane, window, cx| {
19257 pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
19258 .unwrap()
19259 })
19260 .await
19261 .unwrap();
19262 drop(editor_2);
19263 pane_2.update(cx, |pane, cx| {
19264 let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19265 open_editor.update(cx, |editor, cx| {
19266 assert_eq!(
19267 editor.display_text(cx),
19268 lib_text,
19269 "Other file should be open and active in another panel too",
19270 );
19271 });
19272 assert_eq!(
19273 pane.items().count(),
19274 1,
19275 "No other editors should be open in another pane",
19276 );
19277 });
19278
19279 let _editor_1_reopened = workspace
19280 .update_in(cx, |workspace, window, cx| {
19281 workspace.open_path(
19282 (worktree_id, "main.rs"),
19283 Some(pane_1.downgrade()),
19284 true,
19285 window,
19286 cx,
19287 )
19288 })
19289 .unwrap()
19290 .await
19291 .downcast::<Editor>()
19292 .unwrap();
19293 let _editor_2_reopened = workspace
19294 .update_in(cx, |workspace, window, cx| {
19295 workspace.open_path(
19296 (worktree_id, "main.rs"),
19297 Some(pane_2.downgrade()),
19298 true,
19299 window,
19300 cx,
19301 )
19302 })
19303 .unwrap()
19304 .await
19305 .downcast::<Editor>()
19306 .unwrap();
19307 pane_1.update(cx, |pane, cx| {
19308 let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19309 open_editor.update(cx, |editor, cx| {
19310 assert_eq!(
19311 editor.display_text(cx),
19312 main_text,
19313 "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
19314 );
19315 assert_eq!(
19316 editor
19317 .selections
19318 .all::<Point>(cx)
19319 .into_iter()
19320 .map(|s| s.range())
19321 .collect::<Vec<_>>(),
19322 expected_ranges,
19323 "Previous editor in the 1st panel had selections and should get them restored on reopen",
19324 );
19325 })
19326 });
19327 pane_2.update(cx, |pane, cx| {
19328 let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19329 open_editor.update(cx, |editor, cx| {
19330 assert_eq!(
19331 editor.display_text(cx),
19332 r#"fn main() {
19333⋯rintln!("1");
19334⋯intln!("2");
19335⋯ntln!("3");
19336println!("4");
19337println!("5");
19338}"#,
19339 "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
19340 );
19341 assert_eq!(
19342 editor
19343 .selections
19344 .all::<Point>(cx)
19345 .into_iter()
19346 .map(|s| s.range())
19347 .collect::<Vec<_>>(),
19348 vec![Point::zero()..Point::zero()],
19349 "Previous editor in the 2nd pane had no selections changed hence should restore none",
19350 );
19351 })
19352 });
19353}
19354
19355#[gpui::test]
19356async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
19357 init_test(cx, |_| {});
19358
19359 let fs = FakeFs::new(cx.executor());
19360 let main_text = r#"fn main() {
19361println!("1");
19362println!("2");
19363println!("3");
19364println!("4");
19365println!("5");
19366}"#;
19367 let lib_text = "mod foo {}";
19368 fs.insert_tree(
19369 path!("/a"),
19370 json!({
19371 "lib.rs": lib_text,
19372 "main.rs": main_text,
19373 }),
19374 )
19375 .await;
19376
19377 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19378 let (workspace, cx) =
19379 cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
19380 let worktree_id = workspace.update(cx, |workspace, cx| {
19381 workspace.project().update(cx, |project, cx| {
19382 project.worktrees(cx).next().unwrap().read(cx).id()
19383 })
19384 });
19385
19386 let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
19387 let editor = workspace
19388 .update_in(cx, |workspace, window, cx| {
19389 workspace.open_path(
19390 (worktree_id, "main.rs"),
19391 Some(pane.downgrade()),
19392 true,
19393 window,
19394 cx,
19395 )
19396 })
19397 .unwrap()
19398 .await
19399 .downcast::<Editor>()
19400 .unwrap();
19401 pane.update(cx, |pane, cx| {
19402 let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19403 open_editor.update(cx, |editor, cx| {
19404 assert_eq!(
19405 editor.display_text(cx),
19406 main_text,
19407 "Original main.rs text on initial open",
19408 );
19409 })
19410 });
19411 editor.update_in(cx, |editor, window, cx| {
19412 editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
19413 });
19414
19415 cx.update_global(|store: &mut SettingsStore, cx| {
19416 store.update_user_settings::<WorkspaceSettings>(cx, |s| {
19417 s.restore_on_file_reopen = Some(false);
19418 });
19419 });
19420 editor.update_in(cx, |editor, window, cx| {
19421 editor.fold_ranges(
19422 vec![
19423 Point::new(1, 0)..Point::new(1, 1),
19424 Point::new(2, 0)..Point::new(2, 2),
19425 Point::new(3, 0)..Point::new(3, 3),
19426 ],
19427 false,
19428 window,
19429 cx,
19430 );
19431 });
19432 pane.update_in(cx, |pane, window, cx| {
19433 pane.close_all_items(&CloseAllItems::default(), window, cx)
19434 .unwrap()
19435 })
19436 .await
19437 .unwrap();
19438 pane.update(cx, |pane, _| {
19439 assert!(pane.active_item().is_none());
19440 });
19441 cx.update_global(|store: &mut SettingsStore, cx| {
19442 store.update_user_settings::<WorkspaceSettings>(cx, |s| {
19443 s.restore_on_file_reopen = Some(true);
19444 });
19445 });
19446
19447 let _editor_reopened = workspace
19448 .update_in(cx, |workspace, window, cx| {
19449 workspace.open_path(
19450 (worktree_id, "main.rs"),
19451 Some(pane.downgrade()),
19452 true,
19453 window,
19454 cx,
19455 )
19456 })
19457 .unwrap()
19458 .await
19459 .downcast::<Editor>()
19460 .unwrap();
19461 pane.update(cx, |pane, cx| {
19462 let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19463 open_editor.update(cx, |editor, cx| {
19464 assert_eq!(
19465 editor.display_text(cx),
19466 main_text,
19467 "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
19468 );
19469 })
19470 });
19471}
19472
19473#[gpui::test]
19474async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
19475 struct EmptyModalView {
19476 focus_handle: gpui::FocusHandle,
19477 }
19478 impl EventEmitter<DismissEvent> for EmptyModalView {}
19479 impl Render for EmptyModalView {
19480 fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
19481 div()
19482 }
19483 }
19484 impl Focusable for EmptyModalView {
19485 fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
19486 self.focus_handle.clone()
19487 }
19488 }
19489 impl workspace::ModalView for EmptyModalView {}
19490 fn new_empty_modal_view(cx: &App) -> EmptyModalView {
19491 EmptyModalView {
19492 focus_handle: cx.focus_handle(),
19493 }
19494 }
19495
19496 init_test(cx, |_| {});
19497
19498 let fs = FakeFs::new(cx.executor());
19499 let project = Project::test(fs, [], cx).await;
19500 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19501 let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
19502 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19503 let editor = cx.new_window_entity(|window, cx| {
19504 Editor::new(
19505 EditorMode::full(),
19506 buffer,
19507 Some(project.clone()),
19508 window,
19509 cx,
19510 )
19511 });
19512 workspace
19513 .update(cx, |workspace, window, cx| {
19514 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
19515 })
19516 .unwrap();
19517 editor.update_in(cx, |editor, window, cx| {
19518 editor.open_context_menu(&OpenContextMenu, window, cx);
19519 assert!(editor.mouse_context_menu.is_some());
19520 });
19521 workspace
19522 .update(cx, |workspace, window, cx| {
19523 workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
19524 })
19525 .unwrap();
19526 cx.read(|cx| {
19527 assert!(editor.read(cx).mouse_context_menu.is_none());
19528 });
19529}
19530
19531fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
19532 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
19533 point..point
19534}
19535
19536fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
19537 let (text, ranges) = marked_text_ranges(marked_text, true);
19538 assert_eq!(editor.text(cx), text);
19539 assert_eq!(
19540 editor.selections.ranges(cx),
19541 ranges,
19542 "Assert selections are {}",
19543 marked_text
19544 );
19545}
19546
19547pub fn handle_signature_help_request(
19548 cx: &mut EditorLspTestContext,
19549 mocked_response: lsp::SignatureHelp,
19550) -> impl Future<Output = ()> + use<> {
19551 let mut request =
19552 cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
19553 let mocked_response = mocked_response.clone();
19554 async move { Ok(Some(mocked_response)) }
19555 });
19556
19557 async move {
19558 request.next().await;
19559 }
19560}
19561
19562/// Handle completion request passing a marked string specifying where the completion
19563/// should be triggered from using '|' character, what range should be replaced, and what completions
19564/// should be returned using '<' and '>' to delimit the range.
19565///
19566/// Also see `handle_completion_request_with_insert_and_replace`.
19567#[track_caller]
19568pub fn handle_completion_request(
19569 cx: &mut EditorLspTestContext,
19570 marked_string: &str,
19571 completions: Vec<&'static str>,
19572 counter: Arc<AtomicUsize>,
19573) -> impl Future<Output = ()> {
19574 let complete_from_marker: TextRangeMarker = '|'.into();
19575 let replace_range_marker: TextRangeMarker = ('<', '>').into();
19576 let (_, mut marked_ranges) = marked_text_ranges_by(
19577 marked_string,
19578 vec![complete_from_marker.clone(), replace_range_marker.clone()],
19579 );
19580
19581 let complete_from_position =
19582 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
19583 let replace_range =
19584 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
19585
19586 let mut request =
19587 cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
19588 let completions = completions.clone();
19589 counter.fetch_add(1, atomic::Ordering::Release);
19590 async move {
19591 assert_eq!(params.text_document_position.text_document.uri, url.clone());
19592 assert_eq!(
19593 params.text_document_position.position,
19594 complete_from_position
19595 );
19596 Ok(Some(lsp::CompletionResponse::Array(
19597 completions
19598 .iter()
19599 .map(|completion_text| lsp::CompletionItem {
19600 label: completion_text.to_string(),
19601 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
19602 range: replace_range,
19603 new_text: completion_text.to_string(),
19604 })),
19605 ..Default::default()
19606 })
19607 .collect(),
19608 )))
19609 }
19610 });
19611
19612 async move {
19613 request.next().await;
19614 }
19615}
19616
19617/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
19618/// given instead, which also contains an `insert` range.
19619///
19620/// This function uses the cursor position to mimic what Rust-Analyzer provides as the `insert` range,
19621/// that is, `replace_range.start..cursor_pos`.
19622pub fn handle_completion_request_with_insert_and_replace(
19623 cx: &mut EditorLspTestContext,
19624 marked_string: &str,
19625 completions: Vec<&'static str>,
19626 counter: Arc<AtomicUsize>,
19627) -> impl Future<Output = ()> {
19628 let complete_from_marker: TextRangeMarker = '|'.into();
19629 let replace_range_marker: TextRangeMarker = ('<', '>').into();
19630 let (_, mut marked_ranges) = marked_text_ranges_by(
19631 marked_string,
19632 vec![complete_from_marker.clone(), replace_range_marker.clone()],
19633 );
19634
19635 let complete_from_position =
19636 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
19637 let replace_range =
19638 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
19639
19640 let mut request =
19641 cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
19642 let completions = completions.clone();
19643 counter.fetch_add(1, atomic::Ordering::Release);
19644 async move {
19645 assert_eq!(params.text_document_position.text_document.uri, url.clone());
19646 assert_eq!(
19647 params.text_document_position.position, complete_from_position,
19648 "marker `|` position doesn't match",
19649 );
19650 Ok(Some(lsp::CompletionResponse::Array(
19651 completions
19652 .iter()
19653 .map(|completion_text| lsp::CompletionItem {
19654 label: completion_text.to_string(),
19655 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
19656 lsp::InsertReplaceEdit {
19657 insert: lsp::Range {
19658 start: replace_range.start,
19659 end: complete_from_position,
19660 },
19661 replace: replace_range,
19662 new_text: completion_text.to_string(),
19663 },
19664 )),
19665 ..Default::default()
19666 })
19667 .collect(),
19668 )))
19669 }
19670 });
19671
19672 async move {
19673 request.next().await;
19674 }
19675}
19676
19677fn handle_resolve_completion_request(
19678 cx: &mut EditorLspTestContext,
19679 edits: Option<Vec<(&'static str, &'static str)>>,
19680) -> impl Future<Output = ()> {
19681 let edits = edits.map(|edits| {
19682 edits
19683 .iter()
19684 .map(|(marked_string, new_text)| {
19685 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
19686 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
19687 lsp::TextEdit::new(replace_range, new_text.to_string())
19688 })
19689 .collect::<Vec<_>>()
19690 });
19691
19692 let mut request =
19693 cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
19694 let edits = edits.clone();
19695 async move {
19696 Ok(lsp::CompletionItem {
19697 additional_text_edits: edits,
19698 ..Default::default()
19699 })
19700 }
19701 });
19702
19703 async move {
19704 request.next().await;
19705 }
19706}
19707
19708pub(crate) fn update_test_language_settings(
19709 cx: &mut TestAppContext,
19710 f: impl Fn(&mut AllLanguageSettingsContent),
19711) {
19712 cx.update(|cx| {
19713 SettingsStore::update_global(cx, |store, cx| {
19714 store.update_user_settings::<AllLanguageSettings>(cx, f);
19715 });
19716 });
19717}
19718
19719pub(crate) fn update_test_project_settings(
19720 cx: &mut TestAppContext,
19721 f: impl Fn(&mut ProjectSettings),
19722) {
19723 cx.update(|cx| {
19724 SettingsStore::update_global(cx, |store, cx| {
19725 store.update_user_settings::<ProjectSettings>(cx, f);
19726 });
19727 });
19728}
19729
19730pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
19731 cx.update(|cx| {
19732 assets::Assets.load_test_fonts(cx);
19733 let store = SettingsStore::test(cx);
19734 cx.set_global(store);
19735 theme::init(theme::LoadThemes::JustBase, cx);
19736 release_channel::init(SemanticVersion::default(), cx);
19737 client::init_settings(cx);
19738 language::init(cx);
19739 Project::init_settings(cx);
19740 workspace::init_settings(cx);
19741 crate::init(cx);
19742 });
19743
19744 update_test_language_settings(cx, f);
19745}
19746
19747#[track_caller]
19748fn assert_hunk_revert(
19749 not_reverted_text_with_selections: &str,
19750 expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
19751 expected_reverted_text_with_selections: &str,
19752 base_text: &str,
19753 cx: &mut EditorLspTestContext,
19754) {
19755 cx.set_state(not_reverted_text_with_selections);
19756 cx.set_head_text(base_text);
19757 cx.executor().run_until_parked();
19758
19759 let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
19760 let snapshot = editor.snapshot(window, cx);
19761 let reverted_hunk_statuses = snapshot
19762 .buffer_snapshot
19763 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
19764 .map(|hunk| hunk.status().kind)
19765 .collect::<Vec<_>>();
19766
19767 editor.git_restore(&Default::default(), window, cx);
19768 reverted_hunk_statuses
19769 });
19770 cx.executor().run_until_parked();
19771 cx.assert_editor_state(expected_reverted_text_with_selections);
19772 assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
19773}