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
5830#[gpui::test]
5831async fn test_select_all_matches(cx: &mut TestAppContext) {
5832 init_test(cx, |_| {});
5833
5834 let mut cx = EditorTestContext::new(cx).await;
5835
5836 // Test caret-only selections
5837 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5838 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5839 .unwrap();
5840 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5841
5842 // Test left-to-right selections
5843 cx.set_state("abc\n«abcˇ»\nabc");
5844 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5845 .unwrap();
5846 cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
5847
5848 // Test right-to-left selections
5849 cx.set_state("abc\n«ˇabc»\nabc");
5850 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5851 .unwrap();
5852 cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
5853
5854 // Test selecting whitespace with caret selection
5855 cx.set_state("abc\nˇ abc\nabc");
5856 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5857 .unwrap();
5858 cx.assert_editor_state("abc\n« ˇ»abc\nabc");
5859
5860 // Test selecting whitespace with left-to-right selection
5861 cx.set_state("abc\n«ˇ »abc\nabc");
5862 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5863 .unwrap();
5864 cx.assert_editor_state("abc\n«ˇ »abc\nabc");
5865
5866 // Test no matches with right-to-left selection
5867 cx.set_state("abc\n« ˇ»abc\nabc");
5868 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5869 .unwrap();
5870 cx.assert_editor_state("abc\n« ˇ»abc\nabc");
5871}
5872
5873#[gpui::test]
5874async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
5875 init_test(cx, |_| {});
5876
5877 let mut cx = EditorTestContext::new(cx).await;
5878
5879 let large_body_1 = "\nd".repeat(200);
5880 let large_body_2 = "\ne".repeat(200);
5881
5882 cx.set_state(&format!(
5883 "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
5884 ));
5885 let initial_scroll_position = cx.update_editor(|editor, _, cx| {
5886 let scroll_position = editor.scroll_position(cx);
5887 assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
5888 scroll_position
5889 });
5890
5891 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5892 .unwrap();
5893 cx.assert_editor_state(&format!(
5894 "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
5895 ));
5896 let scroll_position_after_selection =
5897 cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
5898 assert_eq!(
5899 initial_scroll_position, scroll_position_after_selection,
5900 "Scroll position should not change after selecting all matches"
5901 );
5902}
5903
5904#[gpui::test]
5905async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
5906 init_test(cx, |_| {});
5907
5908 let mut cx = EditorLspTestContext::new_rust(
5909 lsp::ServerCapabilities {
5910 document_formatting_provider: Some(lsp::OneOf::Left(true)),
5911 ..Default::default()
5912 },
5913 cx,
5914 )
5915 .await;
5916
5917 cx.set_state(indoc! {"
5918 line 1
5919 line 2
5920 linˇe 3
5921 line 4
5922 line 5
5923 "});
5924
5925 // Make an edit
5926 cx.update_editor(|editor, window, cx| {
5927 editor.handle_input("X", window, cx);
5928 });
5929
5930 // Move cursor to a different position
5931 cx.update_editor(|editor, window, cx| {
5932 editor.change_selections(None, window, cx, |s| {
5933 s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
5934 });
5935 });
5936
5937 cx.assert_editor_state(indoc! {"
5938 line 1
5939 line 2
5940 linXe 3
5941 line 4
5942 liˇne 5
5943 "});
5944
5945 cx.lsp
5946 .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
5947 Ok(Some(vec![lsp::TextEdit::new(
5948 lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
5949 "PREFIX ".to_string(),
5950 )]))
5951 });
5952
5953 cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
5954 .unwrap()
5955 .await
5956 .unwrap();
5957
5958 cx.assert_editor_state(indoc! {"
5959 PREFIX line 1
5960 line 2
5961 linXe 3
5962 line 4
5963 liˇne 5
5964 "});
5965
5966 // Undo formatting
5967 cx.update_editor(|editor, window, cx| {
5968 editor.undo(&Default::default(), window, cx);
5969 });
5970
5971 // Verify cursor moved back to position after edit
5972 cx.assert_editor_state(indoc! {"
5973 line 1
5974 line 2
5975 linXˇe 3
5976 line 4
5977 line 5
5978 "});
5979}
5980
5981#[gpui::test]
5982async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
5983 init_test(cx, |_| {});
5984
5985 let mut cx = EditorTestContext::new(cx).await;
5986 cx.set_state(
5987 r#"let foo = 2;
5988lˇet foo = 2;
5989let fooˇ = 2;
5990let foo = 2;
5991let foo = ˇ2;"#,
5992 );
5993
5994 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5995 .unwrap();
5996 cx.assert_editor_state(
5997 r#"let foo = 2;
5998«letˇ» foo = 2;
5999let «fooˇ» = 2;
6000let foo = 2;
6001let foo = «2ˇ»;"#,
6002 );
6003
6004 // noop for multiple selections with different contents
6005 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
6006 .unwrap();
6007 cx.assert_editor_state(
6008 r#"let foo = 2;
6009«letˇ» foo = 2;
6010let «fooˇ» = 2;
6011let foo = 2;
6012let foo = «2ˇ»;"#,
6013 );
6014}
6015
6016#[gpui::test]
6017async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
6018 init_test(cx, |_| {});
6019
6020 let mut cx =
6021 EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
6022
6023 cx.assert_editor_state(indoc! {"
6024 ˇbbb
6025 ccc
6026
6027 bbb
6028 ccc
6029 "});
6030 cx.dispatch_action(SelectPrevious::default());
6031 cx.assert_editor_state(indoc! {"
6032 «bbbˇ»
6033 ccc
6034
6035 bbb
6036 ccc
6037 "});
6038 cx.dispatch_action(SelectPrevious::default());
6039 cx.assert_editor_state(indoc! {"
6040 «bbbˇ»
6041 ccc
6042
6043 «bbbˇ»
6044 ccc
6045 "});
6046}
6047
6048#[gpui::test]
6049async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
6050 init_test(cx, |_| {});
6051
6052 let mut cx = EditorTestContext::new(cx).await;
6053 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
6054
6055 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6056 .unwrap();
6057 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
6058
6059 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6060 .unwrap();
6061 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
6062
6063 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
6064 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
6065
6066 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
6067 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
6068
6069 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6070 .unwrap();
6071 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
6072
6073 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6074 .unwrap();
6075 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
6076
6077 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6078 .unwrap();
6079 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
6080}
6081
6082#[gpui::test]
6083async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
6084 init_test(cx, |_| {});
6085
6086 let mut cx = EditorTestContext::new(cx).await;
6087 cx.set_state("aˇ");
6088
6089 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6090 .unwrap();
6091 cx.assert_editor_state("«aˇ»");
6092 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6093 .unwrap();
6094 cx.assert_editor_state("«aˇ»");
6095}
6096
6097#[gpui::test]
6098async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
6099 init_test(cx, |_| {});
6100
6101 let mut cx = EditorTestContext::new(cx).await;
6102 cx.set_state(
6103 r#"let foo = 2;
6104lˇet foo = 2;
6105let fooˇ = 2;
6106let foo = 2;
6107let foo = ˇ2;"#,
6108 );
6109
6110 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6111 .unwrap();
6112 cx.assert_editor_state(
6113 r#"let foo = 2;
6114«letˇ» foo = 2;
6115let «fooˇ» = 2;
6116let foo = 2;
6117let foo = «2ˇ»;"#,
6118 );
6119
6120 // noop for multiple selections with different contents
6121 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6122 .unwrap();
6123 cx.assert_editor_state(
6124 r#"let foo = 2;
6125«letˇ» foo = 2;
6126let «fooˇ» = 2;
6127let foo = 2;
6128let foo = «2ˇ»;"#,
6129 );
6130}
6131
6132#[gpui::test]
6133async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
6134 init_test(cx, |_| {});
6135
6136 let mut cx = EditorTestContext::new(cx).await;
6137 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
6138
6139 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6140 .unwrap();
6141 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
6142
6143 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6144 .unwrap();
6145 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
6146
6147 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
6148 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
6149
6150 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
6151 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
6152
6153 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6154 .unwrap();
6155 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
6156
6157 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
6158 .unwrap();
6159 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
6160}
6161
6162#[gpui::test]
6163async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
6164 init_test(cx, |_| {});
6165
6166 let language = Arc::new(Language::new(
6167 LanguageConfig::default(),
6168 Some(tree_sitter_rust::LANGUAGE.into()),
6169 ));
6170
6171 let text = r#"
6172 use mod1::mod2::{mod3, mod4};
6173
6174 fn fn_1(param1: bool, param2: &str) {
6175 let var1 = "text";
6176 }
6177 "#
6178 .unindent();
6179
6180 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6181 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6182 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6183
6184 editor
6185 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6186 .await;
6187
6188 editor.update_in(cx, |editor, window, cx| {
6189 editor.change_selections(None, window, cx, |s| {
6190 s.select_display_ranges([
6191 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
6192 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
6193 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
6194 ]);
6195 });
6196 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6197 });
6198 editor.update(cx, |editor, cx| {
6199 assert_text_with_selections(
6200 editor,
6201 indoc! {r#"
6202 use mod1::mod2::{mod3, «mod4ˇ»};
6203
6204 fn fn_1«ˇ(param1: bool, param2: &str)» {
6205 let var1 = "«ˇtext»";
6206 }
6207 "#},
6208 cx,
6209 );
6210 });
6211
6212 editor.update_in(cx, |editor, window, cx| {
6213 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6214 });
6215 editor.update(cx, |editor, cx| {
6216 assert_text_with_selections(
6217 editor,
6218 indoc! {r#"
6219 use mod1::mod2::«{mod3, mod4}ˇ»;
6220
6221 «ˇfn fn_1(param1: bool, param2: &str) {
6222 let var1 = "text";
6223 }»
6224 "#},
6225 cx,
6226 );
6227 });
6228
6229 editor.update_in(cx, |editor, window, cx| {
6230 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6231 });
6232 assert_eq!(
6233 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
6234 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
6235 );
6236
6237 // Trying to expand the selected syntax node one more time has no effect.
6238 editor.update_in(cx, |editor, window, cx| {
6239 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6240 });
6241 assert_eq!(
6242 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
6243 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
6244 );
6245
6246 editor.update_in(cx, |editor, window, cx| {
6247 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
6248 });
6249 editor.update(cx, |editor, cx| {
6250 assert_text_with_selections(
6251 editor,
6252 indoc! {r#"
6253 use mod1::mod2::«{mod3, mod4}ˇ»;
6254
6255 «ˇfn fn_1(param1: bool, param2: &str) {
6256 let var1 = "text";
6257 }»
6258 "#},
6259 cx,
6260 );
6261 });
6262
6263 editor.update_in(cx, |editor, window, cx| {
6264 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
6265 });
6266 editor.update(cx, |editor, cx| {
6267 assert_text_with_selections(
6268 editor,
6269 indoc! {r#"
6270 use mod1::mod2::{mod3, «mod4ˇ»};
6271
6272 fn fn_1«ˇ(param1: bool, param2: &str)» {
6273 let var1 = "«ˇtext»";
6274 }
6275 "#},
6276 cx,
6277 );
6278 });
6279
6280 editor.update_in(cx, |editor, window, cx| {
6281 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
6282 });
6283 editor.update(cx, |editor, cx| {
6284 assert_text_with_selections(
6285 editor,
6286 indoc! {r#"
6287 use mod1::mod2::{mod3, mo«ˇ»d4};
6288
6289 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
6290 let var1 = "te«ˇ»xt";
6291 }
6292 "#},
6293 cx,
6294 );
6295 });
6296
6297 // Trying to shrink the selected syntax node one more time has no effect.
6298 editor.update_in(cx, |editor, window, cx| {
6299 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
6300 });
6301 editor.update_in(cx, |editor, _, cx| {
6302 assert_text_with_selections(
6303 editor,
6304 indoc! {r#"
6305 use mod1::mod2::{mod3, mo«ˇ»d4};
6306
6307 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
6308 let var1 = "te«ˇ»xt";
6309 }
6310 "#},
6311 cx,
6312 );
6313 });
6314
6315 // Ensure that we keep expanding the selection if the larger selection starts or ends within
6316 // a fold.
6317 editor.update_in(cx, |editor, window, cx| {
6318 editor.fold_creases(
6319 vec![
6320 Crease::simple(
6321 Point::new(0, 21)..Point::new(0, 24),
6322 FoldPlaceholder::test(),
6323 ),
6324 Crease::simple(
6325 Point::new(3, 20)..Point::new(3, 22),
6326 FoldPlaceholder::test(),
6327 ),
6328 ],
6329 true,
6330 window,
6331 cx,
6332 );
6333 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6334 });
6335 editor.update(cx, |editor, cx| {
6336 assert_text_with_selections(
6337 editor,
6338 indoc! {r#"
6339 use mod1::mod2::«{mod3, mod4}ˇ»;
6340
6341 fn fn_1«ˇ(param1: bool, param2: &str)» {
6342 let var1 = "«ˇtext»";
6343 }
6344 "#},
6345 cx,
6346 );
6347 });
6348}
6349
6350#[gpui::test]
6351async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
6352 init_test(cx, |_| {});
6353
6354 let language = Arc::new(Language::new(
6355 LanguageConfig::default(),
6356 Some(tree_sitter_rust::LANGUAGE.into()),
6357 ));
6358
6359 let text = r#"
6360 use mod1::mod2::{mod3, mod4};
6361
6362 fn fn_1(param1: bool, param2: &str) {
6363 let var1 = "hello world";
6364 }
6365 "#
6366 .unindent();
6367
6368 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6369 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6370 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6371
6372 editor
6373 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6374 .await;
6375
6376 // Test 1: Cursor on a letter of a string word
6377 editor.update_in(cx, |editor, window, cx| {
6378 editor.change_selections(None, window, cx, |s| {
6379 s.select_display_ranges([
6380 DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
6381 ]);
6382 });
6383 });
6384 editor.update_in(cx, |editor, window, cx| {
6385 assert_text_with_selections(
6386 editor,
6387 indoc! {r#"
6388 use mod1::mod2::{mod3, mod4};
6389
6390 fn fn_1(param1: bool, param2: &str) {
6391 let var1 = "hˇello world";
6392 }
6393 "#},
6394 cx,
6395 );
6396 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6397 assert_text_with_selections(
6398 editor,
6399 indoc! {r#"
6400 use mod1::mod2::{mod3, mod4};
6401
6402 fn fn_1(param1: bool, param2: &str) {
6403 let var1 = "«ˇhello» world";
6404 }
6405 "#},
6406 cx,
6407 );
6408 });
6409
6410 // Test 2: Partial selection within a word
6411 editor.update_in(cx, |editor, window, cx| {
6412 editor.change_selections(None, window, cx, |s| {
6413 s.select_display_ranges([
6414 DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
6415 ]);
6416 });
6417 });
6418 editor.update_in(cx, |editor, window, cx| {
6419 assert_text_with_selections(
6420 editor,
6421 indoc! {r#"
6422 use mod1::mod2::{mod3, mod4};
6423
6424 fn fn_1(param1: bool, param2: &str) {
6425 let var1 = "h«elˇ»lo world";
6426 }
6427 "#},
6428 cx,
6429 );
6430 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6431 assert_text_with_selections(
6432 editor,
6433 indoc! {r#"
6434 use mod1::mod2::{mod3, mod4};
6435
6436 fn fn_1(param1: bool, param2: &str) {
6437 let var1 = "«ˇhello» world";
6438 }
6439 "#},
6440 cx,
6441 );
6442 });
6443
6444 // Test 3: Complete word already selected
6445 editor.update_in(cx, |editor, window, cx| {
6446 editor.change_selections(None, window, cx, |s| {
6447 s.select_display_ranges([
6448 DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
6449 ]);
6450 });
6451 });
6452 editor.update_in(cx, |editor, window, cx| {
6453 assert_text_with_selections(
6454 editor,
6455 indoc! {r#"
6456 use mod1::mod2::{mod3, mod4};
6457
6458 fn fn_1(param1: bool, param2: &str) {
6459 let var1 = "«helloˇ» world";
6460 }
6461 "#},
6462 cx,
6463 );
6464 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6465 assert_text_with_selections(
6466 editor,
6467 indoc! {r#"
6468 use mod1::mod2::{mod3, mod4};
6469
6470 fn fn_1(param1: bool, param2: &str) {
6471 let var1 = "«hello worldˇ»";
6472 }
6473 "#},
6474 cx,
6475 );
6476 });
6477
6478 // Test 4: Selection spanning across words
6479 editor.update_in(cx, |editor, window, cx| {
6480 editor.change_selections(None, window, cx, |s| {
6481 s.select_display_ranges([
6482 DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
6483 ]);
6484 });
6485 });
6486 editor.update_in(cx, |editor, window, cx| {
6487 assert_text_with_selections(
6488 editor,
6489 indoc! {r#"
6490 use mod1::mod2::{mod3, mod4};
6491
6492 fn fn_1(param1: bool, param2: &str) {
6493 let var1 = "hel«lo woˇ»rld";
6494 }
6495 "#},
6496 cx,
6497 );
6498 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6499 assert_text_with_selections(
6500 editor,
6501 indoc! {r#"
6502 use mod1::mod2::{mod3, mod4};
6503
6504 fn fn_1(param1: bool, param2: &str) {
6505 let var1 = "«ˇhello world»";
6506 }
6507 "#},
6508 cx,
6509 );
6510 });
6511
6512 // Test 5: Expansion beyond string
6513 editor.update_in(cx, |editor, window, cx| {
6514 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6515 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
6516 assert_text_with_selections(
6517 editor,
6518 indoc! {r#"
6519 use mod1::mod2::{mod3, mod4};
6520
6521 fn fn_1(param1: bool, param2: &str) {
6522 «ˇlet var1 = "hello world";»
6523 }
6524 "#},
6525 cx,
6526 );
6527 });
6528}
6529
6530#[gpui::test]
6531async fn test_fold_function_bodies(cx: &mut TestAppContext) {
6532 init_test(cx, |_| {});
6533
6534 let base_text = r#"
6535 impl A {
6536 // this is an uncommitted comment
6537
6538 fn b() {
6539 c();
6540 }
6541
6542 // this is another uncommitted comment
6543
6544 fn d() {
6545 // e
6546 // f
6547 }
6548 }
6549
6550 fn g() {
6551 // h
6552 }
6553 "#
6554 .unindent();
6555
6556 let text = r#"
6557 ˇimpl A {
6558
6559 fn b() {
6560 c();
6561 }
6562
6563 fn d() {
6564 // e
6565 // f
6566 }
6567 }
6568
6569 fn g() {
6570 // h
6571 }
6572 "#
6573 .unindent();
6574
6575 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
6576 cx.set_state(&text);
6577 cx.set_head_text(&base_text);
6578 cx.update_editor(|editor, window, cx| {
6579 editor.expand_all_diff_hunks(&Default::default(), window, cx);
6580 });
6581
6582 cx.assert_state_with_diff(
6583 "
6584 ˇimpl A {
6585 - // this is an uncommitted comment
6586
6587 fn b() {
6588 c();
6589 }
6590
6591 - // this is another uncommitted comment
6592 -
6593 fn d() {
6594 // e
6595 // f
6596 }
6597 }
6598
6599 fn g() {
6600 // h
6601 }
6602 "
6603 .unindent(),
6604 );
6605
6606 let expected_display_text = "
6607 impl A {
6608 // this is an uncommitted comment
6609
6610 fn b() {
6611 ⋯
6612 }
6613
6614 // this is another uncommitted comment
6615
6616 fn d() {
6617 ⋯
6618 }
6619 }
6620
6621 fn g() {
6622 ⋯
6623 }
6624 "
6625 .unindent();
6626
6627 cx.update_editor(|editor, window, cx| {
6628 editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
6629 assert_eq!(editor.display_text(cx), expected_display_text);
6630 });
6631}
6632
6633#[gpui::test]
6634async fn test_autoindent(cx: &mut TestAppContext) {
6635 init_test(cx, |_| {});
6636
6637 let language = Arc::new(
6638 Language::new(
6639 LanguageConfig {
6640 brackets: BracketPairConfig {
6641 pairs: vec![
6642 BracketPair {
6643 start: "{".to_string(),
6644 end: "}".to_string(),
6645 close: false,
6646 surround: false,
6647 newline: true,
6648 },
6649 BracketPair {
6650 start: "(".to_string(),
6651 end: ")".to_string(),
6652 close: false,
6653 surround: false,
6654 newline: true,
6655 },
6656 ],
6657 ..Default::default()
6658 },
6659 ..Default::default()
6660 },
6661 Some(tree_sitter_rust::LANGUAGE.into()),
6662 )
6663 .with_indents_query(
6664 r#"
6665 (_ "(" ")" @end) @indent
6666 (_ "{" "}" @end) @indent
6667 "#,
6668 )
6669 .unwrap(),
6670 );
6671
6672 let text = "fn a() {}";
6673
6674 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6675 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6676 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6677 editor
6678 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6679 .await;
6680
6681 editor.update_in(cx, |editor, window, cx| {
6682 editor.change_selections(None, window, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
6683 editor.newline(&Newline, window, cx);
6684 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
6685 assert_eq!(
6686 editor.selections.ranges(cx),
6687 &[
6688 Point::new(1, 4)..Point::new(1, 4),
6689 Point::new(3, 4)..Point::new(3, 4),
6690 Point::new(5, 0)..Point::new(5, 0)
6691 ]
6692 );
6693 });
6694}
6695
6696#[gpui::test]
6697async fn test_autoindent_selections(cx: &mut TestAppContext) {
6698 init_test(cx, |_| {});
6699
6700 {
6701 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
6702 cx.set_state(indoc! {"
6703 impl A {
6704
6705 fn b() {}
6706
6707 «fn c() {
6708
6709 }ˇ»
6710 }
6711 "});
6712
6713 cx.update_editor(|editor, window, cx| {
6714 editor.autoindent(&Default::default(), window, cx);
6715 });
6716
6717 cx.assert_editor_state(indoc! {"
6718 impl A {
6719
6720 fn b() {}
6721
6722 «fn c() {
6723
6724 }ˇ»
6725 }
6726 "});
6727 }
6728
6729 {
6730 let mut cx = EditorTestContext::new_multibuffer(
6731 cx,
6732 [indoc! { "
6733 impl A {
6734 «
6735 // a
6736 fn b(){}
6737 »
6738 «
6739 }
6740 fn c(){}
6741 »
6742 "}],
6743 );
6744
6745 let buffer = cx.update_editor(|editor, _, cx| {
6746 let buffer = editor.buffer().update(cx, |buffer, _| {
6747 buffer.all_buffers().iter().next().unwrap().clone()
6748 });
6749 buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
6750 buffer
6751 });
6752
6753 cx.run_until_parked();
6754 cx.update_editor(|editor, window, cx| {
6755 editor.select_all(&Default::default(), window, cx);
6756 editor.autoindent(&Default::default(), window, cx)
6757 });
6758 cx.run_until_parked();
6759
6760 cx.update(|_, cx| {
6761 assert_eq!(
6762 buffer.read(cx).text(),
6763 indoc! { "
6764 impl A {
6765
6766 // a
6767 fn b(){}
6768
6769
6770 }
6771 fn c(){}
6772
6773 " }
6774 )
6775 });
6776 }
6777}
6778
6779#[gpui::test]
6780async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
6781 init_test(cx, |_| {});
6782
6783 let mut cx = EditorTestContext::new(cx).await;
6784
6785 let language = Arc::new(Language::new(
6786 LanguageConfig {
6787 brackets: BracketPairConfig {
6788 pairs: vec![
6789 BracketPair {
6790 start: "{".to_string(),
6791 end: "}".to_string(),
6792 close: true,
6793 surround: true,
6794 newline: true,
6795 },
6796 BracketPair {
6797 start: "(".to_string(),
6798 end: ")".to_string(),
6799 close: true,
6800 surround: true,
6801 newline: true,
6802 },
6803 BracketPair {
6804 start: "/*".to_string(),
6805 end: " */".to_string(),
6806 close: true,
6807 surround: true,
6808 newline: true,
6809 },
6810 BracketPair {
6811 start: "[".to_string(),
6812 end: "]".to_string(),
6813 close: false,
6814 surround: false,
6815 newline: true,
6816 },
6817 BracketPair {
6818 start: "\"".to_string(),
6819 end: "\"".to_string(),
6820 close: true,
6821 surround: true,
6822 newline: false,
6823 },
6824 BracketPair {
6825 start: "<".to_string(),
6826 end: ">".to_string(),
6827 close: false,
6828 surround: true,
6829 newline: true,
6830 },
6831 ],
6832 ..Default::default()
6833 },
6834 autoclose_before: "})]".to_string(),
6835 ..Default::default()
6836 },
6837 Some(tree_sitter_rust::LANGUAGE.into()),
6838 ));
6839
6840 cx.language_registry().add(language.clone());
6841 cx.update_buffer(|buffer, cx| {
6842 buffer.set_language(Some(language), cx);
6843 });
6844
6845 cx.set_state(
6846 &r#"
6847 🏀ˇ
6848 εˇ
6849 ❤️ˇ
6850 "#
6851 .unindent(),
6852 );
6853
6854 // autoclose multiple nested brackets at multiple cursors
6855 cx.update_editor(|editor, window, cx| {
6856 editor.handle_input("{", window, cx);
6857 editor.handle_input("{", window, cx);
6858 editor.handle_input("{", window, cx);
6859 });
6860 cx.assert_editor_state(
6861 &"
6862 🏀{{{ˇ}}}
6863 ε{{{ˇ}}}
6864 ❤️{{{ˇ}}}
6865 "
6866 .unindent(),
6867 );
6868
6869 // insert a different closing bracket
6870 cx.update_editor(|editor, window, cx| {
6871 editor.handle_input(")", window, cx);
6872 });
6873 cx.assert_editor_state(
6874 &"
6875 🏀{{{)ˇ}}}
6876 ε{{{)ˇ}}}
6877 ❤️{{{)ˇ}}}
6878 "
6879 .unindent(),
6880 );
6881
6882 // skip over the auto-closed brackets when typing a closing bracket
6883 cx.update_editor(|editor, window, cx| {
6884 editor.move_right(&MoveRight, window, cx);
6885 editor.handle_input("}", window, cx);
6886 editor.handle_input("}", window, cx);
6887 editor.handle_input("}", window, cx);
6888 });
6889 cx.assert_editor_state(
6890 &"
6891 🏀{{{)}}}}ˇ
6892 ε{{{)}}}}ˇ
6893 ❤️{{{)}}}}ˇ
6894 "
6895 .unindent(),
6896 );
6897
6898 // autoclose multi-character pairs
6899 cx.set_state(
6900 &"
6901 ˇ
6902 ˇ
6903 "
6904 .unindent(),
6905 );
6906 cx.update_editor(|editor, window, cx| {
6907 editor.handle_input("/", window, cx);
6908 editor.handle_input("*", window, cx);
6909 });
6910 cx.assert_editor_state(
6911 &"
6912 /*ˇ */
6913 /*ˇ */
6914 "
6915 .unindent(),
6916 );
6917
6918 // one cursor autocloses a multi-character pair, one cursor
6919 // does not autoclose.
6920 cx.set_state(
6921 &"
6922 /ˇ
6923 ˇ
6924 "
6925 .unindent(),
6926 );
6927 cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
6928 cx.assert_editor_state(
6929 &"
6930 /*ˇ */
6931 *ˇ
6932 "
6933 .unindent(),
6934 );
6935
6936 // Don't autoclose if the next character isn't whitespace and isn't
6937 // listed in the language's "autoclose_before" section.
6938 cx.set_state("ˇa b");
6939 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
6940 cx.assert_editor_state("{ˇa b");
6941
6942 // Don't autoclose if `close` is false for the bracket pair
6943 cx.set_state("ˇ");
6944 cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
6945 cx.assert_editor_state("[ˇ");
6946
6947 // Surround with brackets if text is selected
6948 cx.set_state("«aˇ» b");
6949 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
6950 cx.assert_editor_state("{«aˇ»} b");
6951
6952 // Autoclose when not immediately after a word character
6953 cx.set_state("a ˇ");
6954 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6955 cx.assert_editor_state("a \"ˇ\"");
6956
6957 // Autoclose pair where the start and end characters are the same
6958 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6959 cx.assert_editor_state("a \"\"ˇ");
6960
6961 // Don't autoclose when immediately after a word character
6962 cx.set_state("aˇ");
6963 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6964 cx.assert_editor_state("a\"ˇ");
6965
6966 // Do autoclose when after a non-word character
6967 cx.set_state("{ˇ");
6968 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6969 cx.assert_editor_state("{\"ˇ\"");
6970
6971 // Non identical pairs autoclose regardless of preceding character
6972 cx.set_state("aˇ");
6973 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
6974 cx.assert_editor_state("a{ˇ}");
6975
6976 // Don't autoclose pair if autoclose is disabled
6977 cx.set_state("ˇ");
6978 cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
6979 cx.assert_editor_state("<ˇ");
6980
6981 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
6982 cx.set_state("«aˇ» b");
6983 cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
6984 cx.assert_editor_state("<«aˇ»> b");
6985}
6986
6987#[gpui::test]
6988async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
6989 init_test(cx, |settings| {
6990 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6991 });
6992
6993 let mut cx = EditorTestContext::new(cx).await;
6994
6995 let language = Arc::new(Language::new(
6996 LanguageConfig {
6997 brackets: BracketPairConfig {
6998 pairs: vec![
6999 BracketPair {
7000 start: "{".to_string(),
7001 end: "}".to_string(),
7002 close: true,
7003 surround: true,
7004 newline: true,
7005 },
7006 BracketPair {
7007 start: "(".to_string(),
7008 end: ")".to_string(),
7009 close: true,
7010 surround: true,
7011 newline: true,
7012 },
7013 BracketPair {
7014 start: "[".to_string(),
7015 end: "]".to_string(),
7016 close: false,
7017 surround: false,
7018 newline: true,
7019 },
7020 ],
7021 ..Default::default()
7022 },
7023 autoclose_before: "})]".to_string(),
7024 ..Default::default()
7025 },
7026 Some(tree_sitter_rust::LANGUAGE.into()),
7027 ));
7028
7029 cx.language_registry().add(language.clone());
7030 cx.update_buffer(|buffer, cx| {
7031 buffer.set_language(Some(language), cx);
7032 });
7033
7034 cx.set_state(
7035 &"
7036 ˇ
7037 ˇ
7038 ˇ
7039 "
7040 .unindent(),
7041 );
7042
7043 // ensure only matching closing brackets are skipped over
7044 cx.update_editor(|editor, window, cx| {
7045 editor.handle_input("}", window, cx);
7046 editor.move_left(&MoveLeft, window, cx);
7047 editor.handle_input(")", window, cx);
7048 editor.move_left(&MoveLeft, window, cx);
7049 });
7050 cx.assert_editor_state(
7051 &"
7052 ˇ)}
7053 ˇ)}
7054 ˇ)}
7055 "
7056 .unindent(),
7057 );
7058
7059 // skip-over closing brackets at multiple cursors
7060 cx.update_editor(|editor, window, cx| {
7061 editor.handle_input(")", window, cx);
7062 editor.handle_input("}", window, cx);
7063 });
7064 cx.assert_editor_state(
7065 &"
7066 )}ˇ
7067 )}ˇ
7068 )}ˇ
7069 "
7070 .unindent(),
7071 );
7072
7073 // ignore non-close brackets
7074 cx.update_editor(|editor, window, cx| {
7075 editor.handle_input("]", window, cx);
7076 editor.move_left(&MoveLeft, window, cx);
7077 editor.handle_input("]", window, cx);
7078 });
7079 cx.assert_editor_state(
7080 &"
7081 )}]ˇ]
7082 )}]ˇ]
7083 )}]ˇ]
7084 "
7085 .unindent(),
7086 );
7087}
7088
7089#[gpui::test]
7090async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
7091 init_test(cx, |_| {});
7092
7093 let mut cx = EditorTestContext::new(cx).await;
7094
7095 let html_language = Arc::new(
7096 Language::new(
7097 LanguageConfig {
7098 name: "HTML".into(),
7099 brackets: BracketPairConfig {
7100 pairs: vec![
7101 BracketPair {
7102 start: "<".into(),
7103 end: ">".into(),
7104 close: true,
7105 ..Default::default()
7106 },
7107 BracketPair {
7108 start: "{".into(),
7109 end: "}".into(),
7110 close: true,
7111 ..Default::default()
7112 },
7113 BracketPair {
7114 start: "(".into(),
7115 end: ")".into(),
7116 close: true,
7117 ..Default::default()
7118 },
7119 ],
7120 ..Default::default()
7121 },
7122 autoclose_before: "})]>".into(),
7123 ..Default::default()
7124 },
7125 Some(tree_sitter_html::LANGUAGE.into()),
7126 )
7127 .with_injection_query(
7128 r#"
7129 (script_element
7130 (raw_text) @injection.content
7131 (#set! injection.language "javascript"))
7132 "#,
7133 )
7134 .unwrap(),
7135 );
7136
7137 let javascript_language = Arc::new(Language::new(
7138 LanguageConfig {
7139 name: "JavaScript".into(),
7140 brackets: BracketPairConfig {
7141 pairs: vec![
7142 BracketPair {
7143 start: "/*".into(),
7144 end: " */".into(),
7145 close: true,
7146 ..Default::default()
7147 },
7148 BracketPair {
7149 start: "{".into(),
7150 end: "}".into(),
7151 close: true,
7152 ..Default::default()
7153 },
7154 BracketPair {
7155 start: "(".into(),
7156 end: ")".into(),
7157 close: true,
7158 ..Default::default()
7159 },
7160 ],
7161 ..Default::default()
7162 },
7163 autoclose_before: "})]>".into(),
7164 ..Default::default()
7165 },
7166 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
7167 ));
7168
7169 cx.language_registry().add(html_language.clone());
7170 cx.language_registry().add(javascript_language.clone());
7171
7172 cx.update_buffer(|buffer, cx| {
7173 buffer.set_language(Some(html_language), cx);
7174 });
7175
7176 cx.set_state(
7177 &r#"
7178 <body>ˇ
7179 <script>
7180 var x = 1;ˇ
7181 </script>
7182 </body>ˇ
7183 "#
7184 .unindent(),
7185 );
7186
7187 // Precondition: different languages are active at different locations.
7188 cx.update_editor(|editor, window, cx| {
7189 let snapshot = editor.snapshot(window, cx);
7190 let cursors = editor.selections.ranges::<usize>(cx);
7191 let languages = cursors
7192 .iter()
7193 .map(|c| snapshot.language_at(c.start).unwrap().name())
7194 .collect::<Vec<_>>();
7195 assert_eq!(
7196 languages,
7197 &["HTML".into(), "JavaScript".into(), "HTML".into()]
7198 );
7199 });
7200
7201 // Angle brackets autoclose in HTML, but not JavaScript.
7202 cx.update_editor(|editor, window, cx| {
7203 editor.handle_input("<", window, cx);
7204 editor.handle_input("a", window, cx);
7205 });
7206 cx.assert_editor_state(
7207 &r#"
7208 <body><aˇ>
7209 <script>
7210 var x = 1;<aˇ
7211 </script>
7212 </body><aˇ>
7213 "#
7214 .unindent(),
7215 );
7216
7217 // Curly braces and parens autoclose in both HTML and JavaScript.
7218 cx.update_editor(|editor, window, cx| {
7219 editor.handle_input(" b=", window, cx);
7220 editor.handle_input("{", window, cx);
7221 editor.handle_input("c", window, cx);
7222 editor.handle_input("(", window, cx);
7223 });
7224 cx.assert_editor_state(
7225 &r#"
7226 <body><a b={c(ˇ)}>
7227 <script>
7228 var x = 1;<a b={c(ˇ)}
7229 </script>
7230 </body><a b={c(ˇ)}>
7231 "#
7232 .unindent(),
7233 );
7234
7235 // Brackets that were already autoclosed are skipped.
7236 cx.update_editor(|editor, window, cx| {
7237 editor.handle_input(")", window, cx);
7238 editor.handle_input("d", window, cx);
7239 editor.handle_input("}", window, cx);
7240 });
7241 cx.assert_editor_state(
7242 &r#"
7243 <body><a b={c()d}ˇ>
7244 <script>
7245 var x = 1;<a b={c()d}ˇ
7246 </script>
7247 </body><a b={c()d}ˇ>
7248 "#
7249 .unindent(),
7250 );
7251 cx.update_editor(|editor, window, cx| {
7252 editor.handle_input(">", window, cx);
7253 });
7254 cx.assert_editor_state(
7255 &r#"
7256 <body><a b={c()d}>ˇ
7257 <script>
7258 var x = 1;<a b={c()d}>ˇ
7259 </script>
7260 </body><a b={c()d}>ˇ
7261 "#
7262 .unindent(),
7263 );
7264
7265 // Reset
7266 cx.set_state(
7267 &r#"
7268 <body>ˇ
7269 <script>
7270 var x = 1;ˇ
7271 </script>
7272 </body>ˇ
7273 "#
7274 .unindent(),
7275 );
7276
7277 cx.update_editor(|editor, window, cx| {
7278 editor.handle_input("<", window, cx);
7279 });
7280 cx.assert_editor_state(
7281 &r#"
7282 <body><ˇ>
7283 <script>
7284 var x = 1;<ˇ
7285 </script>
7286 </body><ˇ>
7287 "#
7288 .unindent(),
7289 );
7290
7291 // When backspacing, the closing angle brackets are removed.
7292 cx.update_editor(|editor, window, cx| {
7293 editor.backspace(&Backspace, window, cx);
7294 });
7295 cx.assert_editor_state(
7296 &r#"
7297 <body>ˇ
7298 <script>
7299 var x = 1;ˇ
7300 </script>
7301 </body>ˇ
7302 "#
7303 .unindent(),
7304 );
7305
7306 // Block comments autoclose in JavaScript, but not HTML.
7307 cx.update_editor(|editor, window, cx| {
7308 editor.handle_input("/", window, cx);
7309 editor.handle_input("*", window, cx);
7310 });
7311 cx.assert_editor_state(
7312 &r#"
7313 <body>/*ˇ
7314 <script>
7315 var x = 1;/*ˇ */
7316 </script>
7317 </body>/*ˇ
7318 "#
7319 .unindent(),
7320 );
7321}
7322
7323#[gpui::test]
7324async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
7325 init_test(cx, |_| {});
7326
7327 let mut cx = EditorTestContext::new(cx).await;
7328
7329 let rust_language = Arc::new(
7330 Language::new(
7331 LanguageConfig {
7332 name: "Rust".into(),
7333 brackets: serde_json::from_value(json!([
7334 { "start": "{", "end": "}", "close": true, "newline": true },
7335 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
7336 ]))
7337 .unwrap(),
7338 autoclose_before: "})]>".into(),
7339 ..Default::default()
7340 },
7341 Some(tree_sitter_rust::LANGUAGE.into()),
7342 )
7343 .with_override_query("(string_literal) @string")
7344 .unwrap(),
7345 );
7346
7347 cx.language_registry().add(rust_language.clone());
7348 cx.update_buffer(|buffer, cx| {
7349 buffer.set_language(Some(rust_language), cx);
7350 });
7351
7352 cx.set_state(
7353 &r#"
7354 let x = ˇ
7355 "#
7356 .unindent(),
7357 );
7358
7359 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
7360 cx.update_editor(|editor, window, cx| {
7361 editor.handle_input("\"", window, cx);
7362 });
7363 cx.assert_editor_state(
7364 &r#"
7365 let x = "ˇ"
7366 "#
7367 .unindent(),
7368 );
7369
7370 // Inserting another quotation mark. The cursor moves across the existing
7371 // automatically-inserted quotation mark.
7372 cx.update_editor(|editor, window, cx| {
7373 editor.handle_input("\"", window, cx);
7374 });
7375 cx.assert_editor_state(
7376 &r#"
7377 let x = ""ˇ
7378 "#
7379 .unindent(),
7380 );
7381
7382 // Reset
7383 cx.set_state(
7384 &r#"
7385 let x = ˇ
7386 "#
7387 .unindent(),
7388 );
7389
7390 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
7391 cx.update_editor(|editor, window, cx| {
7392 editor.handle_input("\"", window, cx);
7393 editor.handle_input(" ", window, cx);
7394 editor.move_left(&Default::default(), window, cx);
7395 editor.handle_input("\\", window, cx);
7396 editor.handle_input("\"", window, cx);
7397 });
7398 cx.assert_editor_state(
7399 &r#"
7400 let x = "\"ˇ "
7401 "#
7402 .unindent(),
7403 );
7404
7405 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
7406 // mark. Nothing is inserted.
7407 cx.update_editor(|editor, window, cx| {
7408 editor.move_right(&Default::default(), window, cx);
7409 editor.handle_input("\"", window, cx);
7410 });
7411 cx.assert_editor_state(
7412 &r#"
7413 let x = "\" "ˇ
7414 "#
7415 .unindent(),
7416 );
7417}
7418
7419#[gpui::test]
7420async fn test_surround_with_pair(cx: &mut TestAppContext) {
7421 init_test(cx, |_| {});
7422
7423 let language = Arc::new(Language::new(
7424 LanguageConfig {
7425 brackets: BracketPairConfig {
7426 pairs: vec![
7427 BracketPair {
7428 start: "{".to_string(),
7429 end: "}".to_string(),
7430 close: true,
7431 surround: true,
7432 newline: true,
7433 },
7434 BracketPair {
7435 start: "/* ".to_string(),
7436 end: "*/".to_string(),
7437 close: true,
7438 surround: true,
7439 ..Default::default()
7440 },
7441 ],
7442 ..Default::default()
7443 },
7444 ..Default::default()
7445 },
7446 Some(tree_sitter_rust::LANGUAGE.into()),
7447 ));
7448
7449 let text = r#"
7450 a
7451 b
7452 c
7453 "#
7454 .unindent();
7455
7456 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
7457 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7458 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7459 editor
7460 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
7461 .await;
7462
7463 editor.update_in(cx, |editor, window, cx| {
7464 editor.change_selections(None, window, cx, |s| {
7465 s.select_display_ranges([
7466 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
7467 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
7468 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
7469 ])
7470 });
7471
7472 editor.handle_input("{", window, cx);
7473 editor.handle_input("{", window, cx);
7474 editor.handle_input("{", window, cx);
7475 assert_eq!(
7476 editor.text(cx),
7477 "
7478 {{{a}}}
7479 {{{b}}}
7480 {{{c}}}
7481 "
7482 .unindent()
7483 );
7484 assert_eq!(
7485 editor.selections.display_ranges(cx),
7486 [
7487 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
7488 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
7489 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
7490 ]
7491 );
7492
7493 editor.undo(&Undo, window, cx);
7494 editor.undo(&Undo, window, cx);
7495 editor.undo(&Undo, window, cx);
7496 assert_eq!(
7497 editor.text(cx),
7498 "
7499 a
7500 b
7501 c
7502 "
7503 .unindent()
7504 );
7505 assert_eq!(
7506 editor.selections.display_ranges(cx),
7507 [
7508 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
7509 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
7510 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
7511 ]
7512 );
7513
7514 // Ensure inserting the first character of a multi-byte bracket pair
7515 // doesn't surround the selections with the bracket.
7516 editor.handle_input("/", window, cx);
7517 assert_eq!(
7518 editor.text(cx),
7519 "
7520 /
7521 /
7522 /
7523 "
7524 .unindent()
7525 );
7526 assert_eq!(
7527 editor.selections.display_ranges(cx),
7528 [
7529 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
7530 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
7531 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
7532 ]
7533 );
7534
7535 editor.undo(&Undo, window, cx);
7536 assert_eq!(
7537 editor.text(cx),
7538 "
7539 a
7540 b
7541 c
7542 "
7543 .unindent()
7544 );
7545 assert_eq!(
7546 editor.selections.display_ranges(cx),
7547 [
7548 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
7549 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
7550 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
7551 ]
7552 );
7553
7554 // Ensure inserting the last character of a multi-byte bracket pair
7555 // doesn't surround the selections with the bracket.
7556 editor.handle_input("*", window, cx);
7557 assert_eq!(
7558 editor.text(cx),
7559 "
7560 *
7561 *
7562 *
7563 "
7564 .unindent()
7565 );
7566 assert_eq!(
7567 editor.selections.display_ranges(cx),
7568 [
7569 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
7570 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
7571 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
7572 ]
7573 );
7574 });
7575}
7576
7577#[gpui::test]
7578async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
7579 init_test(cx, |_| {});
7580
7581 let language = Arc::new(Language::new(
7582 LanguageConfig {
7583 brackets: BracketPairConfig {
7584 pairs: vec![BracketPair {
7585 start: "{".to_string(),
7586 end: "}".to_string(),
7587 close: true,
7588 surround: true,
7589 newline: true,
7590 }],
7591 ..Default::default()
7592 },
7593 autoclose_before: "}".to_string(),
7594 ..Default::default()
7595 },
7596 Some(tree_sitter_rust::LANGUAGE.into()),
7597 ));
7598
7599 let text = r#"
7600 a
7601 b
7602 c
7603 "#
7604 .unindent();
7605
7606 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
7607 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7608 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7609 editor
7610 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
7611 .await;
7612
7613 editor.update_in(cx, |editor, window, cx| {
7614 editor.change_selections(None, window, cx, |s| {
7615 s.select_ranges([
7616 Point::new(0, 1)..Point::new(0, 1),
7617 Point::new(1, 1)..Point::new(1, 1),
7618 Point::new(2, 1)..Point::new(2, 1),
7619 ])
7620 });
7621
7622 editor.handle_input("{", window, cx);
7623 editor.handle_input("{", window, cx);
7624 editor.handle_input("_", window, cx);
7625 assert_eq!(
7626 editor.text(cx),
7627 "
7628 a{{_}}
7629 b{{_}}
7630 c{{_}}
7631 "
7632 .unindent()
7633 );
7634 assert_eq!(
7635 editor.selections.ranges::<Point>(cx),
7636 [
7637 Point::new(0, 4)..Point::new(0, 4),
7638 Point::new(1, 4)..Point::new(1, 4),
7639 Point::new(2, 4)..Point::new(2, 4)
7640 ]
7641 );
7642
7643 editor.backspace(&Default::default(), window, cx);
7644 editor.backspace(&Default::default(), window, cx);
7645 assert_eq!(
7646 editor.text(cx),
7647 "
7648 a{}
7649 b{}
7650 c{}
7651 "
7652 .unindent()
7653 );
7654 assert_eq!(
7655 editor.selections.ranges::<Point>(cx),
7656 [
7657 Point::new(0, 2)..Point::new(0, 2),
7658 Point::new(1, 2)..Point::new(1, 2),
7659 Point::new(2, 2)..Point::new(2, 2)
7660 ]
7661 );
7662
7663 editor.delete_to_previous_word_start(&Default::default(), window, cx);
7664 assert_eq!(
7665 editor.text(cx),
7666 "
7667 a
7668 b
7669 c
7670 "
7671 .unindent()
7672 );
7673 assert_eq!(
7674 editor.selections.ranges::<Point>(cx),
7675 [
7676 Point::new(0, 1)..Point::new(0, 1),
7677 Point::new(1, 1)..Point::new(1, 1),
7678 Point::new(2, 1)..Point::new(2, 1)
7679 ]
7680 );
7681 });
7682}
7683
7684#[gpui::test]
7685async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
7686 init_test(cx, |settings| {
7687 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
7688 });
7689
7690 let mut cx = EditorTestContext::new(cx).await;
7691
7692 let language = Arc::new(Language::new(
7693 LanguageConfig {
7694 brackets: BracketPairConfig {
7695 pairs: vec![
7696 BracketPair {
7697 start: "{".to_string(),
7698 end: "}".to_string(),
7699 close: true,
7700 surround: true,
7701 newline: true,
7702 },
7703 BracketPair {
7704 start: "(".to_string(),
7705 end: ")".to_string(),
7706 close: true,
7707 surround: true,
7708 newline: true,
7709 },
7710 BracketPair {
7711 start: "[".to_string(),
7712 end: "]".to_string(),
7713 close: false,
7714 surround: true,
7715 newline: true,
7716 },
7717 ],
7718 ..Default::default()
7719 },
7720 autoclose_before: "})]".to_string(),
7721 ..Default::default()
7722 },
7723 Some(tree_sitter_rust::LANGUAGE.into()),
7724 ));
7725
7726 cx.language_registry().add(language.clone());
7727 cx.update_buffer(|buffer, cx| {
7728 buffer.set_language(Some(language), cx);
7729 });
7730
7731 cx.set_state(
7732 &"
7733 {(ˇ)}
7734 [[ˇ]]
7735 {(ˇ)}
7736 "
7737 .unindent(),
7738 );
7739
7740 cx.update_editor(|editor, window, cx| {
7741 editor.backspace(&Default::default(), window, cx);
7742 editor.backspace(&Default::default(), window, cx);
7743 });
7744
7745 cx.assert_editor_state(
7746 &"
7747 ˇ
7748 ˇ]]
7749 ˇ
7750 "
7751 .unindent(),
7752 );
7753
7754 cx.update_editor(|editor, window, cx| {
7755 editor.handle_input("{", window, cx);
7756 editor.handle_input("{", window, cx);
7757 editor.move_right(&MoveRight, window, cx);
7758 editor.move_right(&MoveRight, window, cx);
7759 editor.move_left(&MoveLeft, window, cx);
7760 editor.move_left(&MoveLeft, window, cx);
7761 editor.backspace(&Default::default(), window, cx);
7762 });
7763
7764 cx.assert_editor_state(
7765 &"
7766 {ˇ}
7767 {ˇ}]]
7768 {ˇ}
7769 "
7770 .unindent(),
7771 );
7772
7773 cx.update_editor(|editor, window, cx| {
7774 editor.backspace(&Default::default(), window, cx);
7775 });
7776
7777 cx.assert_editor_state(
7778 &"
7779 ˇ
7780 ˇ]]
7781 ˇ
7782 "
7783 .unindent(),
7784 );
7785}
7786
7787#[gpui::test]
7788async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
7789 init_test(cx, |_| {});
7790
7791 let language = Arc::new(Language::new(
7792 LanguageConfig::default(),
7793 Some(tree_sitter_rust::LANGUAGE.into()),
7794 ));
7795
7796 let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
7797 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7798 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7799 editor
7800 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
7801 .await;
7802
7803 editor.update_in(cx, |editor, window, cx| {
7804 editor.set_auto_replace_emoji_shortcode(true);
7805
7806 editor.handle_input("Hello ", window, cx);
7807 editor.handle_input(":wave", window, cx);
7808 assert_eq!(editor.text(cx), "Hello :wave".unindent());
7809
7810 editor.handle_input(":", window, cx);
7811 assert_eq!(editor.text(cx), "Hello 👋".unindent());
7812
7813 editor.handle_input(" :smile", window, cx);
7814 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
7815
7816 editor.handle_input(":", window, cx);
7817 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
7818
7819 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
7820 editor.handle_input(":wave", window, cx);
7821 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
7822
7823 editor.handle_input(":", window, cx);
7824 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
7825
7826 editor.handle_input(":1", window, cx);
7827 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
7828
7829 editor.handle_input(":", window, cx);
7830 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
7831
7832 // Ensure shortcode does not get replaced when it is part of a word
7833 editor.handle_input(" Test:wave", window, cx);
7834 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
7835
7836 editor.handle_input(":", window, cx);
7837 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
7838
7839 editor.set_auto_replace_emoji_shortcode(false);
7840
7841 // Ensure shortcode does not get replaced when auto replace is off
7842 editor.handle_input(" :wave", window, cx);
7843 assert_eq!(
7844 editor.text(cx),
7845 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
7846 );
7847
7848 editor.handle_input(":", window, cx);
7849 assert_eq!(
7850 editor.text(cx),
7851 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
7852 );
7853 });
7854}
7855
7856#[gpui::test]
7857async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
7858 init_test(cx, |_| {});
7859
7860 let (text, insertion_ranges) = marked_text_ranges(
7861 indoc! {"
7862 ˇ
7863 "},
7864 false,
7865 );
7866
7867 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
7868 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7869
7870 _ = editor.update_in(cx, |editor, window, cx| {
7871 let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
7872
7873 editor
7874 .insert_snippet(&insertion_ranges, snippet, window, cx)
7875 .unwrap();
7876
7877 fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
7878 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
7879 assert_eq!(editor.text(cx), expected_text);
7880 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
7881 }
7882
7883 assert(
7884 editor,
7885 cx,
7886 indoc! {"
7887 type «» =•
7888 "},
7889 );
7890
7891 assert!(editor.context_menu_visible(), "There should be a matches");
7892 });
7893}
7894
7895#[gpui::test]
7896async fn test_snippets(cx: &mut TestAppContext) {
7897 init_test(cx, |_| {});
7898
7899 let (text, insertion_ranges) = marked_text_ranges(
7900 indoc! {"
7901 a.ˇ b
7902 a.ˇ b
7903 a.ˇ b
7904 "},
7905 false,
7906 );
7907
7908 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
7909 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7910
7911 editor.update_in(cx, |editor, window, cx| {
7912 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
7913
7914 editor
7915 .insert_snippet(&insertion_ranges, snippet, window, cx)
7916 .unwrap();
7917
7918 fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
7919 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
7920 assert_eq!(editor.text(cx), expected_text);
7921 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
7922 }
7923
7924 assert(
7925 editor,
7926 cx,
7927 indoc! {"
7928 a.f(«one», two, «three») b
7929 a.f(«one», two, «three») b
7930 a.f(«one», two, «three») b
7931 "},
7932 );
7933
7934 // Can't move earlier than the first tab stop
7935 assert!(!editor.move_to_prev_snippet_tabstop(window, cx));
7936 assert(
7937 editor,
7938 cx,
7939 indoc! {"
7940 a.f(«one», two, «three») b
7941 a.f(«one», two, «three») b
7942 a.f(«one», two, «three») b
7943 "},
7944 );
7945
7946 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7947 assert(
7948 editor,
7949 cx,
7950 indoc! {"
7951 a.f(one, «two», three) b
7952 a.f(one, «two», three) b
7953 a.f(one, «two», three) b
7954 "},
7955 );
7956
7957 editor.move_to_prev_snippet_tabstop(window, cx);
7958 assert(
7959 editor,
7960 cx,
7961 indoc! {"
7962 a.f(«one», two, «three») b
7963 a.f(«one», two, «three») b
7964 a.f(«one», two, «three») b
7965 "},
7966 );
7967
7968 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7969 assert(
7970 editor,
7971 cx,
7972 indoc! {"
7973 a.f(one, «two», three) b
7974 a.f(one, «two», three) b
7975 a.f(one, «two», three) b
7976 "},
7977 );
7978 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7979 assert(
7980 editor,
7981 cx,
7982 indoc! {"
7983 a.f(one, two, three)ˇ b
7984 a.f(one, two, three)ˇ b
7985 a.f(one, two, three)ˇ b
7986 "},
7987 );
7988
7989 // As soon as the last tab stop is reached, snippet state is gone
7990 editor.move_to_prev_snippet_tabstop(window, cx);
7991 assert(
7992 editor,
7993 cx,
7994 indoc! {"
7995 a.f(one, two, three)ˇ b
7996 a.f(one, two, three)ˇ b
7997 a.f(one, two, three)ˇ b
7998 "},
7999 );
8000 });
8001}
8002
8003#[gpui::test]
8004async fn test_document_format_during_save(cx: &mut TestAppContext) {
8005 init_test(cx, |_| {});
8006
8007 let fs = FakeFs::new(cx.executor());
8008 fs.insert_file(path!("/file.rs"), Default::default()).await;
8009
8010 let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
8011
8012 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8013 language_registry.add(rust_lang());
8014 let mut fake_servers = language_registry.register_fake_lsp(
8015 "Rust",
8016 FakeLspAdapter {
8017 capabilities: lsp::ServerCapabilities {
8018 document_formatting_provider: Some(lsp::OneOf::Left(true)),
8019 ..Default::default()
8020 },
8021 ..Default::default()
8022 },
8023 );
8024
8025 let buffer = project
8026 .update(cx, |project, cx| {
8027 project.open_local_buffer(path!("/file.rs"), cx)
8028 })
8029 .await
8030 .unwrap();
8031
8032 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
8033 let (editor, cx) = cx.add_window_view(|window, cx| {
8034 build_editor_with_project(project.clone(), buffer, window, cx)
8035 });
8036 editor.update_in(cx, |editor, window, cx| {
8037 editor.set_text("one\ntwo\nthree\n", window, cx)
8038 });
8039 assert!(cx.read(|cx| editor.is_dirty(cx)));
8040
8041 cx.executor().start_waiting();
8042 let fake_server = fake_servers.next().await.unwrap();
8043
8044 {
8045 fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
8046 move |params, _| async move {
8047 assert_eq!(
8048 params.text_document.uri,
8049 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
8050 );
8051 assert_eq!(params.options.tab_size, 4);
8052 Ok(Some(vec![lsp::TextEdit::new(
8053 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
8054 ", ".to_string(),
8055 )]))
8056 },
8057 );
8058 let save = editor
8059 .update_in(cx, |editor, window, cx| {
8060 editor.save(true, project.clone(), window, cx)
8061 })
8062 .unwrap();
8063 cx.executor().start_waiting();
8064 save.await;
8065
8066 assert_eq!(
8067 editor.update(cx, |editor, cx| editor.text(cx)),
8068 "one, two\nthree\n"
8069 );
8070 assert!(!cx.read(|cx| editor.is_dirty(cx)));
8071 }
8072
8073 {
8074 editor.update_in(cx, |editor, window, cx| {
8075 editor.set_text("one\ntwo\nthree\n", window, cx)
8076 });
8077 assert!(cx.read(|cx| editor.is_dirty(cx)));
8078
8079 // Ensure we can still save even if formatting hangs.
8080 fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
8081 move |params, _| async move {
8082 assert_eq!(
8083 params.text_document.uri,
8084 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
8085 );
8086 futures::future::pending::<()>().await;
8087 unreachable!()
8088 },
8089 );
8090 let save = editor
8091 .update_in(cx, |editor, window, cx| {
8092 editor.save(true, project.clone(), window, cx)
8093 })
8094 .unwrap();
8095 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
8096 cx.executor().start_waiting();
8097 save.await;
8098 assert_eq!(
8099 editor.update(cx, |editor, cx| editor.text(cx)),
8100 "one\ntwo\nthree\n"
8101 );
8102 }
8103
8104 // For non-dirty buffer, no formatting request should be sent
8105 {
8106 assert!(!cx.read(|cx| editor.is_dirty(cx)));
8107
8108 fake_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
8109 panic!("Should not be invoked on non-dirty buffer");
8110 });
8111 let save = editor
8112 .update_in(cx, |editor, window, cx| {
8113 editor.save(true, project.clone(), window, cx)
8114 })
8115 .unwrap();
8116 cx.executor().start_waiting();
8117 save.await;
8118 }
8119
8120 // Set rust language override and assert overridden tabsize is sent to language server
8121 update_test_language_settings(cx, |settings| {
8122 settings.languages.insert(
8123 "Rust".into(),
8124 LanguageSettingsContent {
8125 tab_size: NonZeroU32::new(8),
8126 ..Default::default()
8127 },
8128 );
8129 });
8130
8131 {
8132 editor.update_in(cx, |editor, window, cx| {
8133 editor.set_text("somehting_new\n", window, cx)
8134 });
8135 assert!(cx.read(|cx| editor.is_dirty(cx)));
8136 let _formatting_request_signal = fake_server
8137 .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
8138 assert_eq!(
8139 params.text_document.uri,
8140 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
8141 );
8142 assert_eq!(params.options.tab_size, 8);
8143 Ok(Some(vec![]))
8144 });
8145 let save = editor
8146 .update_in(cx, |editor, window, cx| {
8147 editor.save(true, project.clone(), window, cx)
8148 })
8149 .unwrap();
8150 cx.executor().start_waiting();
8151 save.await;
8152 }
8153}
8154
8155#[gpui::test]
8156async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
8157 init_test(cx, |_| {});
8158
8159 let cols = 4;
8160 let rows = 10;
8161 let sample_text_1 = sample_text(rows, cols, 'a');
8162 assert_eq!(
8163 sample_text_1,
8164 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
8165 );
8166 let sample_text_2 = sample_text(rows, cols, 'l');
8167 assert_eq!(
8168 sample_text_2,
8169 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
8170 );
8171 let sample_text_3 = sample_text(rows, cols, 'v');
8172 assert_eq!(
8173 sample_text_3,
8174 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
8175 );
8176
8177 let fs = FakeFs::new(cx.executor());
8178 fs.insert_tree(
8179 path!("/a"),
8180 json!({
8181 "main.rs": sample_text_1,
8182 "other.rs": sample_text_2,
8183 "lib.rs": sample_text_3,
8184 }),
8185 )
8186 .await;
8187
8188 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
8189 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
8190 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
8191
8192 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8193 language_registry.add(rust_lang());
8194 let mut fake_servers = language_registry.register_fake_lsp(
8195 "Rust",
8196 FakeLspAdapter {
8197 capabilities: lsp::ServerCapabilities {
8198 document_formatting_provider: Some(lsp::OneOf::Left(true)),
8199 ..Default::default()
8200 },
8201 ..Default::default()
8202 },
8203 );
8204
8205 let worktree = project.update(cx, |project, cx| {
8206 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
8207 assert_eq!(worktrees.len(), 1);
8208 worktrees.pop().unwrap()
8209 });
8210 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
8211
8212 let buffer_1 = project
8213 .update(cx, |project, cx| {
8214 project.open_buffer((worktree_id, "main.rs"), cx)
8215 })
8216 .await
8217 .unwrap();
8218 let buffer_2 = project
8219 .update(cx, |project, cx| {
8220 project.open_buffer((worktree_id, "other.rs"), cx)
8221 })
8222 .await
8223 .unwrap();
8224 let buffer_3 = project
8225 .update(cx, |project, cx| {
8226 project.open_buffer((worktree_id, "lib.rs"), cx)
8227 })
8228 .await
8229 .unwrap();
8230
8231 let multi_buffer = cx.new(|cx| {
8232 let mut multi_buffer = MultiBuffer::new(ReadWrite);
8233 multi_buffer.push_excerpts(
8234 buffer_1.clone(),
8235 [
8236 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
8237 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
8238 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
8239 ],
8240 cx,
8241 );
8242 multi_buffer.push_excerpts(
8243 buffer_2.clone(),
8244 [
8245 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
8246 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
8247 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
8248 ],
8249 cx,
8250 );
8251 multi_buffer.push_excerpts(
8252 buffer_3.clone(),
8253 [
8254 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
8255 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
8256 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
8257 ],
8258 cx,
8259 );
8260 multi_buffer
8261 });
8262 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
8263 Editor::new(
8264 EditorMode::full(),
8265 multi_buffer,
8266 Some(project.clone()),
8267 window,
8268 cx,
8269 )
8270 });
8271
8272 multi_buffer_editor.update_in(cx, |editor, window, cx| {
8273 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
8274 s.select_ranges(Some(1..2))
8275 });
8276 editor.insert("|one|two|three|", window, cx);
8277 });
8278 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
8279 multi_buffer_editor.update_in(cx, |editor, window, cx| {
8280 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
8281 s.select_ranges(Some(60..70))
8282 });
8283 editor.insert("|four|five|six|", window, cx);
8284 });
8285 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
8286
8287 // First two buffers should be edited, but not the third one.
8288 assert_eq!(
8289 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
8290 "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}",
8291 );
8292 buffer_1.update(cx, |buffer, _| {
8293 assert!(buffer.is_dirty());
8294 assert_eq!(
8295 buffer.text(),
8296 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
8297 )
8298 });
8299 buffer_2.update(cx, |buffer, _| {
8300 assert!(buffer.is_dirty());
8301 assert_eq!(
8302 buffer.text(),
8303 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
8304 )
8305 });
8306 buffer_3.update(cx, |buffer, _| {
8307 assert!(!buffer.is_dirty());
8308 assert_eq!(buffer.text(), sample_text_3,)
8309 });
8310 cx.executor().run_until_parked();
8311
8312 cx.executor().start_waiting();
8313 let save = multi_buffer_editor
8314 .update_in(cx, |editor, window, cx| {
8315 editor.save(true, project.clone(), window, cx)
8316 })
8317 .unwrap();
8318
8319 let fake_server = fake_servers.next().await.unwrap();
8320 fake_server
8321 .server
8322 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
8323 Ok(Some(vec![lsp::TextEdit::new(
8324 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
8325 format!("[{} formatted]", params.text_document.uri),
8326 )]))
8327 })
8328 .detach();
8329 save.await;
8330
8331 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
8332 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
8333 assert_eq!(
8334 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
8335 uri!(
8336 "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}"
8337 ),
8338 );
8339 buffer_1.update(cx, |buffer, _| {
8340 assert!(!buffer.is_dirty());
8341 assert_eq!(
8342 buffer.text(),
8343 uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
8344 )
8345 });
8346 buffer_2.update(cx, |buffer, _| {
8347 assert!(!buffer.is_dirty());
8348 assert_eq!(
8349 buffer.text(),
8350 uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
8351 )
8352 });
8353 buffer_3.update(cx, |buffer, _| {
8354 assert!(!buffer.is_dirty());
8355 assert_eq!(buffer.text(), sample_text_3,)
8356 });
8357}
8358
8359#[gpui::test]
8360async fn test_range_format_during_save(cx: &mut TestAppContext) {
8361 init_test(cx, |_| {});
8362
8363 let fs = FakeFs::new(cx.executor());
8364 fs.insert_file(path!("/file.rs"), Default::default()).await;
8365
8366 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
8367
8368 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8369 language_registry.add(rust_lang());
8370 let mut fake_servers = language_registry.register_fake_lsp(
8371 "Rust",
8372 FakeLspAdapter {
8373 capabilities: lsp::ServerCapabilities {
8374 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
8375 ..Default::default()
8376 },
8377 ..Default::default()
8378 },
8379 );
8380
8381 let buffer = project
8382 .update(cx, |project, cx| {
8383 project.open_local_buffer(path!("/file.rs"), cx)
8384 })
8385 .await
8386 .unwrap();
8387
8388 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
8389 let (editor, cx) = cx.add_window_view(|window, cx| {
8390 build_editor_with_project(project.clone(), buffer, window, cx)
8391 });
8392 editor.update_in(cx, |editor, window, cx| {
8393 editor.set_text("one\ntwo\nthree\n", window, cx)
8394 });
8395 assert!(cx.read(|cx| editor.is_dirty(cx)));
8396
8397 cx.executor().start_waiting();
8398 let fake_server = fake_servers.next().await.unwrap();
8399
8400 let save = editor
8401 .update_in(cx, |editor, window, cx| {
8402 editor.save(true, project.clone(), window, cx)
8403 })
8404 .unwrap();
8405 fake_server
8406 .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
8407 assert_eq!(
8408 params.text_document.uri,
8409 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
8410 );
8411 assert_eq!(params.options.tab_size, 4);
8412 Ok(Some(vec![lsp::TextEdit::new(
8413 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
8414 ", ".to_string(),
8415 )]))
8416 })
8417 .next()
8418 .await;
8419 cx.executor().start_waiting();
8420 save.await;
8421 assert_eq!(
8422 editor.update(cx, |editor, cx| editor.text(cx)),
8423 "one, two\nthree\n"
8424 );
8425 assert!(!cx.read(|cx| editor.is_dirty(cx)));
8426
8427 editor.update_in(cx, |editor, window, cx| {
8428 editor.set_text("one\ntwo\nthree\n", window, cx)
8429 });
8430 assert!(cx.read(|cx| editor.is_dirty(cx)));
8431
8432 // Ensure we can still save even if formatting hangs.
8433 fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
8434 move |params, _| async move {
8435 assert_eq!(
8436 params.text_document.uri,
8437 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
8438 );
8439 futures::future::pending::<()>().await;
8440 unreachable!()
8441 },
8442 );
8443 let save = editor
8444 .update_in(cx, |editor, window, cx| {
8445 editor.save(true, project.clone(), window, cx)
8446 })
8447 .unwrap();
8448 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
8449 cx.executor().start_waiting();
8450 save.await;
8451 assert_eq!(
8452 editor.update(cx, |editor, cx| editor.text(cx)),
8453 "one\ntwo\nthree\n"
8454 );
8455 assert!(!cx.read(|cx| editor.is_dirty(cx)));
8456
8457 // For non-dirty buffer, no formatting request should be sent
8458 let save = editor
8459 .update_in(cx, |editor, window, cx| {
8460 editor.save(true, project.clone(), window, cx)
8461 })
8462 .unwrap();
8463 let _pending_format_request = fake_server
8464 .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
8465 panic!("Should not be invoked on non-dirty buffer");
8466 })
8467 .next();
8468 cx.executor().start_waiting();
8469 save.await;
8470
8471 // Set Rust language override and assert overridden tabsize is sent to language server
8472 update_test_language_settings(cx, |settings| {
8473 settings.languages.insert(
8474 "Rust".into(),
8475 LanguageSettingsContent {
8476 tab_size: NonZeroU32::new(8),
8477 ..Default::default()
8478 },
8479 );
8480 });
8481
8482 editor.update_in(cx, |editor, window, cx| {
8483 editor.set_text("somehting_new\n", window, cx)
8484 });
8485 assert!(cx.read(|cx| editor.is_dirty(cx)));
8486 let save = editor
8487 .update_in(cx, |editor, window, cx| {
8488 editor.save(true, project.clone(), window, cx)
8489 })
8490 .unwrap();
8491 fake_server
8492 .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
8493 assert_eq!(
8494 params.text_document.uri,
8495 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
8496 );
8497 assert_eq!(params.options.tab_size, 8);
8498 Ok(Some(vec![]))
8499 })
8500 .next()
8501 .await;
8502 cx.executor().start_waiting();
8503 save.await;
8504}
8505
8506#[gpui::test]
8507async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
8508 init_test(cx, |settings| {
8509 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
8510 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
8511 ))
8512 });
8513
8514 let fs = FakeFs::new(cx.executor());
8515 fs.insert_file(path!("/file.rs"), Default::default()).await;
8516
8517 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
8518
8519 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8520 language_registry.add(Arc::new(Language::new(
8521 LanguageConfig {
8522 name: "Rust".into(),
8523 matcher: LanguageMatcher {
8524 path_suffixes: vec!["rs".to_string()],
8525 ..Default::default()
8526 },
8527 ..LanguageConfig::default()
8528 },
8529 Some(tree_sitter_rust::LANGUAGE.into()),
8530 )));
8531 update_test_language_settings(cx, |settings| {
8532 // Enable Prettier formatting for the same buffer, and ensure
8533 // LSP is called instead of Prettier.
8534 settings.defaults.prettier = Some(PrettierSettings {
8535 allowed: true,
8536 ..PrettierSettings::default()
8537 });
8538 });
8539 let mut fake_servers = language_registry.register_fake_lsp(
8540 "Rust",
8541 FakeLspAdapter {
8542 capabilities: lsp::ServerCapabilities {
8543 document_formatting_provider: Some(lsp::OneOf::Left(true)),
8544 ..Default::default()
8545 },
8546 ..Default::default()
8547 },
8548 );
8549
8550 let buffer = project
8551 .update(cx, |project, cx| {
8552 project.open_local_buffer(path!("/file.rs"), cx)
8553 })
8554 .await
8555 .unwrap();
8556
8557 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
8558 let (editor, cx) = cx.add_window_view(|window, cx| {
8559 build_editor_with_project(project.clone(), buffer, window, cx)
8560 });
8561 editor.update_in(cx, |editor, window, cx| {
8562 editor.set_text("one\ntwo\nthree\n", window, cx)
8563 });
8564
8565 cx.executor().start_waiting();
8566 let fake_server = fake_servers.next().await.unwrap();
8567
8568 let format = editor
8569 .update_in(cx, |editor, window, cx| {
8570 editor.perform_format(
8571 project.clone(),
8572 FormatTrigger::Manual,
8573 FormatTarget::Buffers,
8574 window,
8575 cx,
8576 )
8577 })
8578 .unwrap();
8579 fake_server
8580 .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
8581 assert_eq!(
8582 params.text_document.uri,
8583 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
8584 );
8585 assert_eq!(params.options.tab_size, 4);
8586 Ok(Some(vec![lsp::TextEdit::new(
8587 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
8588 ", ".to_string(),
8589 )]))
8590 })
8591 .next()
8592 .await;
8593 cx.executor().start_waiting();
8594 format.await;
8595 assert_eq!(
8596 editor.update(cx, |editor, cx| editor.text(cx)),
8597 "one, two\nthree\n"
8598 );
8599
8600 editor.update_in(cx, |editor, window, cx| {
8601 editor.set_text("one\ntwo\nthree\n", window, cx)
8602 });
8603 // Ensure we don't lock if formatting hangs.
8604 fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
8605 move |params, _| async move {
8606 assert_eq!(
8607 params.text_document.uri,
8608 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
8609 );
8610 futures::future::pending::<()>().await;
8611 unreachable!()
8612 },
8613 );
8614 let format = editor
8615 .update_in(cx, |editor, window, cx| {
8616 editor.perform_format(
8617 project,
8618 FormatTrigger::Manual,
8619 FormatTarget::Buffers,
8620 window,
8621 cx,
8622 )
8623 })
8624 .unwrap();
8625 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
8626 cx.executor().start_waiting();
8627 format.await;
8628 assert_eq!(
8629 editor.update(cx, |editor, cx| editor.text(cx)),
8630 "one\ntwo\nthree\n"
8631 );
8632}
8633
8634#[gpui::test]
8635async fn test_multiple_formatters(cx: &mut TestAppContext) {
8636 init_test(cx, |settings| {
8637 settings.defaults.remove_trailing_whitespace_on_save = Some(true);
8638 settings.defaults.formatter =
8639 Some(language_settings::SelectedFormatter::List(FormatterList(
8640 vec![
8641 Formatter::LanguageServer { name: None },
8642 Formatter::CodeActions(
8643 [
8644 ("code-action-1".into(), true),
8645 ("code-action-2".into(), true),
8646 ]
8647 .into_iter()
8648 .collect(),
8649 ),
8650 ]
8651 .into(),
8652 )))
8653 });
8654
8655 let fs = FakeFs::new(cx.executor());
8656 fs.insert_file(path!("/file.rs"), "one \ntwo \nthree".into())
8657 .await;
8658
8659 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
8660 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8661 language_registry.add(rust_lang());
8662
8663 let mut fake_servers = language_registry.register_fake_lsp(
8664 "Rust",
8665 FakeLspAdapter {
8666 capabilities: lsp::ServerCapabilities {
8667 document_formatting_provider: Some(lsp::OneOf::Left(true)),
8668 execute_command_provider: Some(lsp::ExecuteCommandOptions {
8669 commands: vec!["the-command-for-code-action-1".into()],
8670 ..Default::default()
8671 }),
8672 code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
8673 ..Default::default()
8674 },
8675 ..Default::default()
8676 },
8677 );
8678
8679 let buffer = project
8680 .update(cx, |project, cx| {
8681 project.open_local_buffer(path!("/file.rs"), cx)
8682 })
8683 .await
8684 .unwrap();
8685
8686 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
8687 let (editor, cx) = cx.add_window_view(|window, cx| {
8688 build_editor_with_project(project.clone(), buffer, window, cx)
8689 });
8690
8691 cx.executor().start_waiting();
8692
8693 let fake_server = fake_servers.next().await.unwrap();
8694 fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
8695 move |_params, _| async move {
8696 Ok(Some(vec![lsp::TextEdit::new(
8697 lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
8698 "applied-formatting\n".to_string(),
8699 )]))
8700 },
8701 );
8702 fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
8703 move |params, _| async move {
8704 assert_eq!(
8705 params.context.only,
8706 Some(vec!["code-action-1".into(), "code-action-2".into()])
8707 );
8708 let uri = lsp::Url::from_file_path(path!("/file.rs")).unwrap();
8709 Ok(Some(vec![
8710 lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
8711 kind: Some("code-action-1".into()),
8712 edit: Some(lsp::WorkspaceEdit::new(
8713 [(
8714 uri.clone(),
8715 vec![lsp::TextEdit::new(
8716 lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
8717 "applied-code-action-1-edit\n".to_string(),
8718 )],
8719 )]
8720 .into_iter()
8721 .collect(),
8722 )),
8723 command: Some(lsp::Command {
8724 command: "the-command-for-code-action-1".into(),
8725 ..Default::default()
8726 }),
8727 ..Default::default()
8728 }),
8729 lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
8730 kind: Some("code-action-2".into()),
8731 edit: Some(lsp::WorkspaceEdit::new(
8732 [(
8733 uri.clone(),
8734 vec![lsp::TextEdit::new(
8735 lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
8736 "applied-code-action-2-edit\n".to_string(),
8737 )],
8738 )]
8739 .into_iter()
8740 .collect(),
8741 )),
8742 ..Default::default()
8743 }),
8744 ]))
8745 },
8746 );
8747
8748 fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
8749 move |params, _| async move { Ok(params) }
8750 });
8751
8752 let command_lock = Arc::new(futures::lock::Mutex::new(()));
8753 fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
8754 let fake = fake_server.clone();
8755 let lock = command_lock.clone();
8756 move |params, _| {
8757 assert_eq!(params.command, "the-command-for-code-action-1");
8758 let fake = fake.clone();
8759 let lock = lock.clone();
8760 async move {
8761 lock.lock().await;
8762 fake.server
8763 .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
8764 label: None,
8765 edit: lsp::WorkspaceEdit {
8766 changes: Some(
8767 [(
8768 lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
8769 vec![lsp::TextEdit {
8770 range: lsp::Range::new(
8771 lsp::Position::new(0, 0),
8772 lsp::Position::new(0, 0),
8773 ),
8774 new_text: "applied-code-action-1-command\n".into(),
8775 }],
8776 )]
8777 .into_iter()
8778 .collect(),
8779 ),
8780 ..Default::default()
8781 },
8782 })
8783 .await
8784 .unwrap();
8785 Ok(Some(json!(null)))
8786 }
8787 }
8788 });
8789
8790 cx.executor().start_waiting();
8791 editor
8792 .update_in(cx, |editor, window, cx| {
8793 editor.perform_format(
8794 project.clone(),
8795 FormatTrigger::Manual,
8796 FormatTarget::Buffers,
8797 window,
8798 cx,
8799 )
8800 })
8801 .unwrap()
8802 .await;
8803 editor.update(cx, |editor, cx| {
8804 assert_eq!(
8805 editor.text(cx),
8806 r#"
8807 applied-code-action-2-edit
8808 applied-code-action-1-command
8809 applied-code-action-1-edit
8810 applied-formatting
8811 one
8812 two
8813 three
8814 "#
8815 .unindent()
8816 );
8817 });
8818
8819 editor.update_in(cx, |editor, window, cx| {
8820 editor.undo(&Default::default(), window, cx);
8821 assert_eq!(editor.text(cx), "one \ntwo \nthree");
8822 });
8823
8824 // Perform a manual edit while waiting for an LSP command
8825 // that's being run as part of a formatting code action.
8826 let lock_guard = command_lock.lock().await;
8827 let format = editor
8828 .update_in(cx, |editor, window, cx| {
8829 editor.perform_format(
8830 project.clone(),
8831 FormatTrigger::Manual,
8832 FormatTarget::Buffers,
8833 window,
8834 cx,
8835 )
8836 })
8837 .unwrap();
8838 cx.run_until_parked();
8839 editor.update(cx, |editor, cx| {
8840 assert_eq!(
8841 editor.text(cx),
8842 r#"
8843 applied-code-action-1-edit
8844 applied-formatting
8845 one
8846 two
8847 three
8848 "#
8849 .unindent()
8850 );
8851
8852 editor.buffer.update(cx, |buffer, cx| {
8853 let ix = buffer.len(cx);
8854 buffer.edit([(ix..ix, "edited\n")], None, cx);
8855 });
8856 });
8857
8858 // Allow the LSP command to proceed. Because the buffer was edited,
8859 // the second code action will not be run.
8860 drop(lock_guard);
8861 format.await;
8862 editor.update_in(cx, |editor, window, cx| {
8863 assert_eq!(
8864 editor.text(cx),
8865 r#"
8866 applied-code-action-1-command
8867 applied-code-action-1-edit
8868 applied-formatting
8869 one
8870 two
8871 three
8872 edited
8873 "#
8874 .unindent()
8875 );
8876
8877 // The manual edit is undone first, because it is the last thing the user did
8878 // (even though the command completed afterwards).
8879 editor.undo(&Default::default(), window, cx);
8880 assert_eq!(
8881 editor.text(cx),
8882 r#"
8883 applied-code-action-1-command
8884 applied-code-action-1-edit
8885 applied-formatting
8886 one
8887 two
8888 three
8889 "#
8890 .unindent()
8891 );
8892
8893 // All the formatting (including the command, which completed after the manual edit)
8894 // is undone together.
8895 editor.undo(&Default::default(), window, cx);
8896 assert_eq!(editor.text(cx), "one \ntwo \nthree");
8897 });
8898}
8899
8900#[gpui::test]
8901async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
8902 init_test(cx, |settings| {
8903 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
8904 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
8905 ))
8906 });
8907
8908 let fs = FakeFs::new(cx.executor());
8909 fs.insert_file(path!("/file.ts"), Default::default()).await;
8910
8911 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
8912
8913 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8914 language_registry.add(Arc::new(Language::new(
8915 LanguageConfig {
8916 name: "TypeScript".into(),
8917 matcher: LanguageMatcher {
8918 path_suffixes: vec!["ts".to_string()],
8919 ..Default::default()
8920 },
8921 ..LanguageConfig::default()
8922 },
8923 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
8924 )));
8925 update_test_language_settings(cx, |settings| {
8926 settings.defaults.prettier = Some(PrettierSettings {
8927 allowed: true,
8928 ..PrettierSettings::default()
8929 });
8930 });
8931 let mut fake_servers = language_registry.register_fake_lsp(
8932 "TypeScript",
8933 FakeLspAdapter {
8934 capabilities: lsp::ServerCapabilities {
8935 code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
8936 ..Default::default()
8937 },
8938 ..Default::default()
8939 },
8940 );
8941
8942 let buffer = project
8943 .update(cx, |project, cx| {
8944 project.open_local_buffer(path!("/file.ts"), cx)
8945 })
8946 .await
8947 .unwrap();
8948
8949 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
8950 let (editor, cx) = cx.add_window_view(|window, cx| {
8951 build_editor_with_project(project.clone(), buffer, window, cx)
8952 });
8953 editor.update_in(cx, |editor, window, cx| {
8954 editor.set_text(
8955 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
8956 window,
8957 cx,
8958 )
8959 });
8960
8961 cx.executor().start_waiting();
8962 let fake_server = fake_servers.next().await.unwrap();
8963
8964 let format = editor
8965 .update_in(cx, |editor, window, cx| {
8966 editor.perform_code_action_kind(
8967 project.clone(),
8968 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
8969 window,
8970 cx,
8971 )
8972 })
8973 .unwrap();
8974 fake_server
8975 .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
8976 assert_eq!(
8977 params.text_document.uri,
8978 lsp::Url::from_file_path(path!("/file.ts")).unwrap()
8979 );
8980 Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
8981 lsp::CodeAction {
8982 title: "Organize Imports".to_string(),
8983 kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
8984 edit: Some(lsp::WorkspaceEdit {
8985 changes: Some(
8986 [(
8987 params.text_document.uri.clone(),
8988 vec![lsp::TextEdit::new(
8989 lsp::Range::new(
8990 lsp::Position::new(1, 0),
8991 lsp::Position::new(2, 0),
8992 ),
8993 "".to_string(),
8994 )],
8995 )]
8996 .into_iter()
8997 .collect(),
8998 ),
8999 ..Default::default()
9000 }),
9001 ..Default::default()
9002 },
9003 )]))
9004 })
9005 .next()
9006 .await;
9007 cx.executor().start_waiting();
9008 format.await;
9009 assert_eq!(
9010 editor.update(cx, |editor, cx| editor.text(cx)),
9011 "import { a } from 'module';\n\nconst x = a;\n"
9012 );
9013
9014 editor.update_in(cx, |editor, window, cx| {
9015 editor.set_text(
9016 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
9017 window,
9018 cx,
9019 )
9020 });
9021 // Ensure we don't lock if code action hangs.
9022 fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
9023 move |params, _| async move {
9024 assert_eq!(
9025 params.text_document.uri,
9026 lsp::Url::from_file_path(path!("/file.ts")).unwrap()
9027 );
9028 futures::future::pending::<()>().await;
9029 unreachable!()
9030 },
9031 );
9032 let format = editor
9033 .update_in(cx, |editor, window, cx| {
9034 editor.perform_code_action_kind(
9035 project,
9036 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
9037 window,
9038 cx,
9039 )
9040 })
9041 .unwrap();
9042 cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
9043 cx.executor().start_waiting();
9044 format.await;
9045 assert_eq!(
9046 editor.update(cx, |editor, cx| editor.text(cx)),
9047 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
9048 );
9049}
9050
9051#[gpui::test]
9052async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
9053 init_test(cx, |_| {});
9054
9055 let mut cx = EditorLspTestContext::new_rust(
9056 lsp::ServerCapabilities {
9057 document_formatting_provider: Some(lsp::OneOf::Left(true)),
9058 ..Default::default()
9059 },
9060 cx,
9061 )
9062 .await;
9063
9064 cx.set_state(indoc! {"
9065 one.twoˇ
9066 "});
9067
9068 // The format request takes a long time. When it completes, it inserts
9069 // a newline and an indent before the `.`
9070 cx.lsp
9071 .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
9072 let executor = cx.background_executor().clone();
9073 async move {
9074 executor.timer(Duration::from_millis(100)).await;
9075 Ok(Some(vec![lsp::TextEdit {
9076 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
9077 new_text: "\n ".into(),
9078 }]))
9079 }
9080 });
9081
9082 // Submit a format request.
9083 let format_1 = cx
9084 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
9085 .unwrap();
9086 cx.executor().run_until_parked();
9087
9088 // Submit a second format request.
9089 let format_2 = cx
9090 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
9091 .unwrap();
9092 cx.executor().run_until_parked();
9093
9094 // Wait for both format requests to complete
9095 cx.executor().advance_clock(Duration::from_millis(200));
9096 cx.executor().start_waiting();
9097 format_1.await.unwrap();
9098 cx.executor().start_waiting();
9099 format_2.await.unwrap();
9100
9101 // The formatting edits only happens once.
9102 cx.assert_editor_state(indoc! {"
9103 one
9104 .twoˇ
9105 "});
9106}
9107
9108#[gpui::test]
9109async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
9110 init_test(cx, |settings| {
9111 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
9112 });
9113
9114 let mut cx = EditorLspTestContext::new_rust(
9115 lsp::ServerCapabilities {
9116 document_formatting_provider: Some(lsp::OneOf::Left(true)),
9117 ..Default::default()
9118 },
9119 cx,
9120 )
9121 .await;
9122
9123 // Set up a buffer white some trailing whitespace and no trailing newline.
9124 cx.set_state(
9125 &[
9126 "one ", //
9127 "twoˇ", //
9128 "three ", //
9129 "four", //
9130 ]
9131 .join("\n"),
9132 );
9133
9134 // Submit a format request.
9135 let format = cx
9136 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
9137 .unwrap();
9138
9139 // Record which buffer changes have been sent to the language server
9140 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
9141 cx.lsp
9142 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
9143 let buffer_changes = buffer_changes.clone();
9144 move |params, _| {
9145 buffer_changes.lock().extend(
9146 params
9147 .content_changes
9148 .into_iter()
9149 .map(|e| (e.range.unwrap(), e.text)),
9150 );
9151 }
9152 });
9153
9154 // Handle formatting requests to the language server.
9155 cx.lsp
9156 .set_request_handler::<lsp::request::Formatting, _, _>({
9157 let buffer_changes = buffer_changes.clone();
9158 move |_, _| {
9159 // When formatting is requested, trailing whitespace has already been stripped,
9160 // and the trailing newline has already been added.
9161 assert_eq!(
9162 &buffer_changes.lock()[1..],
9163 &[
9164 (
9165 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
9166 "".into()
9167 ),
9168 (
9169 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
9170 "".into()
9171 ),
9172 (
9173 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
9174 "\n".into()
9175 ),
9176 ]
9177 );
9178
9179 // Insert blank lines between each line of the buffer.
9180 async move {
9181 Ok(Some(vec![
9182 lsp::TextEdit {
9183 range: lsp::Range::new(
9184 lsp::Position::new(1, 0),
9185 lsp::Position::new(1, 0),
9186 ),
9187 new_text: "\n".into(),
9188 },
9189 lsp::TextEdit {
9190 range: lsp::Range::new(
9191 lsp::Position::new(2, 0),
9192 lsp::Position::new(2, 0),
9193 ),
9194 new_text: "\n".into(),
9195 },
9196 ]))
9197 }
9198 }
9199 });
9200
9201 // After formatting the buffer, the trailing whitespace is stripped,
9202 // a newline is appended, and the edits provided by the language server
9203 // have been applied.
9204 format.await.unwrap();
9205 cx.assert_editor_state(
9206 &[
9207 "one", //
9208 "", //
9209 "twoˇ", //
9210 "", //
9211 "three", //
9212 "four", //
9213 "", //
9214 ]
9215 .join("\n"),
9216 );
9217
9218 // Undoing the formatting undoes the trailing whitespace removal, the
9219 // trailing newline, and the LSP edits.
9220 cx.update_buffer(|buffer, cx| buffer.undo(cx));
9221 cx.assert_editor_state(
9222 &[
9223 "one ", //
9224 "twoˇ", //
9225 "three ", //
9226 "four", //
9227 ]
9228 .join("\n"),
9229 );
9230}
9231
9232#[gpui::test]
9233async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
9234 cx: &mut TestAppContext,
9235) {
9236 init_test(cx, |_| {});
9237
9238 cx.update(|cx| {
9239 cx.update_global::<SettingsStore, _>(|settings, cx| {
9240 settings.update_user_settings::<EditorSettings>(cx, |settings| {
9241 settings.auto_signature_help = Some(true);
9242 });
9243 });
9244 });
9245
9246 let mut cx = EditorLspTestContext::new_rust(
9247 lsp::ServerCapabilities {
9248 signature_help_provider: Some(lsp::SignatureHelpOptions {
9249 ..Default::default()
9250 }),
9251 ..Default::default()
9252 },
9253 cx,
9254 )
9255 .await;
9256
9257 let language = Language::new(
9258 LanguageConfig {
9259 name: "Rust".into(),
9260 brackets: BracketPairConfig {
9261 pairs: vec![
9262 BracketPair {
9263 start: "{".to_string(),
9264 end: "}".to_string(),
9265 close: true,
9266 surround: true,
9267 newline: true,
9268 },
9269 BracketPair {
9270 start: "(".to_string(),
9271 end: ")".to_string(),
9272 close: true,
9273 surround: true,
9274 newline: true,
9275 },
9276 BracketPair {
9277 start: "/*".to_string(),
9278 end: " */".to_string(),
9279 close: true,
9280 surround: true,
9281 newline: true,
9282 },
9283 BracketPair {
9284 start: "[".to_string(),
9285 end: "]".to_string(),
9286 close: false,
9287 surround: false,
9288 newline: true,
9289 },
9290 BracketPair {
9291 start: "\"".to_string(),
9292 end: "\"".to_string(),
9293 close: true,
9294 surround: true,
9295 newline: false,
9296 },
9297 BracketPair {
9298 start: "<".to_string(),
9299 end: ">".to_string(),
9300 close: false,
9301 surround: true,
9302 newline: true,
9303 },
9304 ],
9305 ..Default::default()
9306 },
9307 autoclose_before: "})]".to_string(),
9308 ..Default::default()
9309 },
9310 Some(tree_sitter_rust::LANGUAGE.into()),
9311 );
9312 let language = Arc::new(language);
9313
9314 cx.language_registry().add(language.clone());
9315 cx.update_buffer(|buffer, cx| {
9316 buffer.set_language(Some(language), cx);
9317 });
9318
9319 cx.set_state(
9320 &r#"
9321 fn main() {
9322 sampleˇ
9323 }
9324 "#
9325 .unindent(),
9326 );
9327
9328 cx.update_editor(|editor, window, cx| {
9329 editor.handle_input("(", window, cx);
9330 });
9331 cx.assert_editor_state(
9332 &"
9333 fn main() {
9334 sample(ˇ)
9335 }
9336 "
9337 .unindent(),
9338 );
9339
9340 let mocked_response = lsp::SignatureHelp {
9341 signatures: vec![lsp::SignatureInformation {
9342 label: "fn sample(param1: u8, param2: u8)".to_string(),
9343 documentation: None,
9344 parameters: Some(vec![
9345 lsp::ParameterInformation {
9346 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
9347 documentation: None,
9348 },
9349 lsp::ParameterInformation {
9350 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
9351 documentation: None,
9352 },
9353 ]),
9354 active_parameter: None,
9355 }],
9356 active_signature: Some(0),
9357 active_parameter: Some(0),
9358 };
9359 handle_signature_help_request(&mut cx, mocked_response).await;
9360
9361 cx.condition(|editor, _| editor.signature_help_state.is_shown())
9362 .await;
9363
9364 cx.editor(|editor, _, _| {
9365 let signature_help_state = editor.signature_help_state.popover().cloned();
9366 assert_eq!(
9367 signature_help_state.unwrap().label,
9368 "param1: u8, param2: u8"
9369 );
9370 });
9371}
9372
9373#[gpui::test]
9374async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
9375 init_test(cx, |_| {});
9376
9377 cx.update(|cx| {
9378 cx.update_global::<SettingsStore, _>(|settings, cx| {
9379 settings.update_user_settings::<EditorSettings>(cx, |settings| {
9380 settings.auto_signature_help = Some(false);
9381 settings.show_signature_help_after_edits = Some(false);
9382 });
9383 });
9384 });
9385
9386 let mut cx = EditorLspTestContext::new_rust(
9387 lsp::ServerCapabilities {
9388 signature_help_provider: Some(lsp::SignatureHelpOptions {
9389 ..Default::default()
9390 }),
9391 ..Default::default()
9392 },
9393 cx,
9394 )
9395 .await;
9396
9397 let language = Language::new(
9398 LanguageConfig {
9399 name: "Rust".into(),
9400 brackets: BracketPairConfig {
9401 pairs: vec![
9402 BracketPair {
9403 start: "{".to_string(),
9404 end: "}".to_string(),
9405 close: true,
9406 surround: true,
9407 newline: true,
9408 },
9409 BracketPair {
9410 start: "(".to_string(),
9411 end: ")".to_string(),
9412 close: true,
9413 surround: true,
9414 newline: true,
9415 },
9416 BracketPair {
9417 start: "/*".to_string(),
9418 end: " */".to_string(),
9419 close: true,
9420 surround: true,
9421 newline: true,
9422 },
9423 BracketPair {
9424 start: "[".to_string(),
9425 end: "]".to_string(),
9426 close: false,
9427 surround: false,
9428 newline: true,
9429 },
9430 BracketPair {
9431 start: "\"".to_string(),
9432 end: "\"".to_string(),
9433 close: true,
9434 surround: true,
9435 newline: false,
9436 },
9437 BracketPair {
9438 start: "<".to_string(),
9439 end: ">".to_string(),
9440 close: false,
9441 surround: true,
9442 newline: true,
9443 },
9444 ],
9445 ..Default::default()
9446 },
9447 autoclose_before: "})]".to_string(),
9448 ..Default::default()
9449 },
9450 Some(tree_sitter_rust::LANGUAGE.into()),
9451 );
9452 let language = Arc::new(language);
9453
9454 cx.language_registry().add(language.clone());
9455 cx.update_buffer(|buffer, cx| {
9456 buffer.set_language(Some(language), cx);
9457 });
9458
9459 // Ensure that signature_help is not called when no signature help is enabled.
9460 cx.set_state(
9461 &r#"
9462 fn main() {
9463 sampleˇ
9464 }
9465 "#
9466 .unindent(),
9467 );
9468 cx.update_editor(|editor, window, cx| {
9469 editor.handle_input("(", window, cx);
9470 });
9471 cx.assert_editor_state(
9472 &"
9473 fn main() {
9474 sample(ˇ)
9475 }
9476 "
9477 .unindent(),
9478 );
9479 cx.editor(|editor, _, _| {
9480 assert!(editor.signature_help_state.task().is_none());
9481 });
9482
9483 let mocked_response = lsp::SignatureHelp {
9484 signatures: vec![lsp::SignatureInformation {
9485 label: "fn sample(param1: u8, param2: u8)".to_string(),
9486 documentation: None,
9487 parameters: Some(vec![
9488 lsp::ParameterInformation {
9489 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
9490 documentation: None,
9491 },
9492 lsp::ParameterInformation {
9493 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
9494 documentation: None,
9495 },
9496 ]),
9497 active_parameter: None,
9498 }],
9499 active_signature: Some(0),
9500 active_parameter: Some(0),
9501 };
9502
9503 // Ensure that signature_help is called when enabled afte edits
9504 cx.update(|_, cx| {
9505 cx.update_global::<SettingsStore, _>(|settings, cx| {
9506 settings.update_user_settings::<EditorSettings>(cx, |settings| {
9507 settings.auto_signature_help = Some(false);
9508 settings.show_signature_help_after_edits = Some(true);
9509 });
9510 });
9511 });
9512 cx.set_state(
9513 &r#"
9514 fn main() {
9515 sampleˇ
9516 }
9517 "#
9518 .unindent(),
9519 );
9520 cx.update_editor(|editor, window, cx| {
9521 editor.handle_input("(", window, cx);
9522 });
9523 cx.assert_editor_state(
9524 &"
9525 fn main() {
9526 sample(ˇ)
9527 }
9528 "
9529 .unindent(),
9530 );
9531 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
9532 cx.condition(|editor, _| editor.signature_help_state.is_shown())
9533 .await;
9534 cx.update_editor(|editor, _, _| {
9535 let signature_help_state = editor.signature_help_state.popover().cloned();
9536 assert!(signature_help_state.is_some());
9537 assert_eq!(
9538 signature_help_state.unwrap().label,
9539 "param1: u8, param2: u8"
9540 );
9541 editor.signature_help_state = SignatureHelpState::default();
9542 });
9543
9544 // Ensure that signature_help is called when auto signature help override is enabled
9545 cx.update(|_, cx| {
9546 cx.update_global::<SettingsStore, _>(|settings, cx| {
9547 settings.update_user_settings::<EditorSettings>(cx, |settings| {
9548 settings.auto_signature_help = Some(true);
9549 settings.show_signature_help_after_edits = Some(false);
9550 });
9551 });
9552 });
9553 cx.set_state(
9554 &r#"
9555 fn main() {
9556 sampleˇ
9557 }
9558 "#
9559 .unindent(),
9560 );
9561 cx.update_editor(|editor, window, cx| {
9562 editor.handle_input("(", window, cx);
9563 });
9564 cx.assert_editor_state(
9565 &"
9566 fn main() {
9567 sample(ˇ)
9568 }
9569 "
9570 .unindent(),
9571 );
9572 handle_signature_help_request(&mut cx, mocked_response).await;
9573 cx.condition(|editor, _| editor.signature_help_state.is_shown())
9574 .await;
9575 cx.editor(|editor, _, _| {
9576 let signature_help_state = editor.signature_help_state.popover().cloned();
9577 assert!(signature_help_state.is_some());
9578 assert_eq!(
9579 signature_help_state.unwrap().label,
9580 "param1: u8, param2: u8"
9581 );
9582 });
9583}
9584
9585#[gpui::test]
9586async fn test_signature_help(cx: &mut TestAppContext) {
9587 init_test(cx, |_| {});
9588 cx.update(|cx| {
9589 cx.update_global::<SettingsStore, _>(|settings, cx| {
9590 settings.update_user_settings::<EditorSettings>(cx, |settings| {
9591 settings.auto_signature_help = Some(true);
9592 });
9593 });
9594 });
9595
9596 let mut cx = EditorLspTestContext::new_rust(
9597 lsp::ServerCapabilities {
9598 signature_help_provider: Some(lsp::SignatureHelpOptions {
9599 ..Default::default()
9600 }),
9601 ..Default::default()
9602 },
9603 cx,
9604 )
9605 .await;
9606
9607 // A test that directly calls `show_signature_help`
9608 cx.update_editor(|editor, window, cx| {
9609 editor.show_signature_help(&ShowSignatureHelp, window, cx);
9610 });
9611
9612 let mocked_response = lsp::SignatureHelp {
9613 signatures: vec![lsp::SignatureInformation {
9614 label: "fn sample(param1: u8, param2: u8)".to_string(),
9615 documentation: None,
9616 parameters: Some(vec![
9617 lsp::ParameterInformation {
9618 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
9619 documentation: None,
9620 },
9621 lsp::ParameterInformation {
9622 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
9623 documentation: None,
9624 },
9625 ]),
9626 active_parameter: None,
9627 }],
9628 active_signature: Some(0),
9629 active_parameter: Some(0),
9630 };
9631 handle_signature_help_request(&mut cx, mocked_response).await;
9632
9633 cx.condition(|editor, _| editor.signature_help_state.is_shown())
9634 .await;
9635
9636 cx.editor(|editor, _, _| {
9637 let signature_help_state = editor.signature_help_state.popover().cloned();
9638 assert!(signature_help_state.is_some());
9639 assert_eq!(
9640 signature_help_state.unwrap().label,
9641 "param1: u8, param2: u8"
9642 );
9643 });
9644
9645 // When exiting outside from inside the brackets, `signature_help` is closed.
9646 cx.set_state(indoc! {"
9647 fn main() {
9648 sample(ˇ);
9649 }
9650
9651 fn sample(param1: u8, param2: u8) {}
9652 "});
9653
9654 cx.update_editor(|editor, window, cx| {
9655 editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
9656 });
9657
9658 let mocked_response = lsp::SignatureHelp {
9659 signatures: Vec::new(),
9660 active_signature: None,
9661 active_parameter: None,
9662 };
9663 handle_signature_help_request(&mut cx, mocked_response).await;
9664
9665 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
9666 .await;
9667
9668 cx.editor(|editor, _, _| {
9669 assert!(!editor.signature_help_state.is_shown());
9670 });
9671
9672 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
9673 cx.set_state(indoc! {"
9674 fn main() {
9675 sample(ˇ);
9676 }
9677
9678 fn sample(param1: u8, param2: u8) {}
9679 "});
9680
9681 let mocked_response = lsp::SignatureHelp {
9682 signatures: vec![lsp::SignatureInformation {
9683 label: "fn sample(param1: u8, param2: u8)".to_string(),
9684 documentation: None,
9685 parameters: Some(vec![
9686 lsp::ParameterInformation {
9687 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
9688 documentation: None,
9689 },
9690 lsp::ParameterInformation {
9691 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
9692 documentation: None,
9693 },
9694 ]),
9695 active_parameter: None,
9696 }],
9697 active_signature: Some(0),
9698 active_parameter: Some(0),
9699 };
9700 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
9701 cx.condition(|editor, _| editor.signature_help_state.is_shown())
9702 .await;
9703 cx.editor(|editor, _, _| {
9704 assert!(editor.signature_help_state.is_shown());
9705 });
9706
9707 // Restore the popover with more parameter input
9708 cx.set_state(indoc! {"
9709 fn main() {
9710 sample(param1, param2ˇ);
9711 }
9712
9713 fn sample(param1: u8, param2: u8) {}
9714 "});
9715
9716 let mocked_response = lsp::SignatureHelp {
9717 signatures: vec![lsp::SignatureInformation {
9718 label: "fn sample(param1: u8, param2: u8)".to_string(),
9719 documentation: None,
9720 parameters: Some(vec![
9721 lsp::ParameterInformation {
9722 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
9723 documentation: None,
9724 },
9725 lsp::ParameterInformation {
9726 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
9727 documentation: None,
9728 },
9729 ]),
9730 active_parameter: None,
9731 }],
9732 active_signature: Some(0),
9733 active_parameter: Some(1),
9734 };
9735 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
9736 cx.condition(|editor, _| editor.signature_help_state.is_shown())
9737 .await;
9738
9739 // When selecting a range, the popover is gone.
9740 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
9741 cx.update_editor(|editor, window, cx| {
9742 editor.change_selections(None, window, cx, |s| {
9743 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
9744 })
9745 });
9746 cx.assert_editor_state(indoc! {"
9747 fn main() {
9748 sample(param1, «ˇparam2»);
9749 }
9750
9751 fn sample(param1: u8, param2: u8) {}
9752 "});
9753 cx.editor(|editor, _, _| {
9754 assert!(!editor.signature_help_state.is_shown());
9755 });
9756
9757 // When unselecting again, the popover is back if within the brackets.
9758 cx.update_editor(|editor, window, cx| {
9759 editor.change_selections(None, window, cx, |s| {
9760 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
9761 })
9762 });
9763 cx.assert_editor_state(indoc! {"
9764 fn main() {
9765 sample(param1, ˇparam2);
9766 }
9767
9768 fn sample(param1: u8, param2: u8) {}
9769 "});
9770 handle_signature_help_request(&mut cx, mocked_response).await;
9771 cx.condition(|editor, _| editor.signature_help_state.is_shown())
9772 .await;
9773 cx.editor(|editor, _, _| {
9774 assert!(editor.signature_help_state.is_shown());
9775 });
9776
9777 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
9778 cx.update_editor(|editor, window, cx| {
9779 editor.change_selections(None, window, cx, |s| {
9780 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
9781 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
9782 })
9783 });
9784 cx.assert_editor_state(indoc! {"
9785 fn main() {
9786 sample(param1, ˇparam2);
9787 }
9788
9789 fn sample(param1: u8, param2: u8) {}
9790 "});
9791
9792 let mocked_response = lsp::SignatureHelp {
9793 signatures: vec![lsp::SignatureInformation {
9794 label: "fn sample(param1: u8, param2: u8)".to_string(),
9795 documentation: None,
9796 parameters: Some(vec![
9797 lsp::ParameterInformation {
9798 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
9799 documentation: None,
9800 },
9801 lsp::ParameterInformation {
9802 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
9803 documentation: None,
9804 },
9805 ]),
9806 active_parameter: None,
9807 }],
9808 active_signature: Some(0),
9809 active_parameter: Some(1),
9810 };
9811 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
9812 cx.condition(|editor, _| editor.signature_help_state.is_shown())
9813 .await;
9814 cx.update_editor(|editor, _, cx| {
9815 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
9816 });
9817 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
9818 .await;
9819 cx.update_editor(|editor, window, cx| {
9820 editor.change_selections(None, window, cx, |s| {
9821 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
9822 })
9823 });
9824 cx.assert_editor_state(indoc! {"
9825 fn main() {
9826 sample(param1, «ˇparam2»);
9827 }
9828
9829 fn sample(param1: u8, param2: u8) {}
9830 "});
9831 cx.update_editor(|editor, window, cx| {
9832 editor.change_selections(None, window, cx, |s| {
9833 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
9834 })
9835 });
9836 cx.assert_editor_state(indoc! {"
9837 fn main() {
9838 sample(param1, ˇparam2);
9839 }
9840
9841 fn sample(param1: u8, param2: u8) {}
9842 "});
9843 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
9844 .await;
9845}
9846
9847#[gpui::test]
9848async fn test_completion_mode(cx: &mut TestAppContext) {
9849 init_test(cx, |_| {});
9850 let mut cx = EditorLspTestContext::new_rust(
9851 lsp::ServerCapabilities {
9852 completion_provider: Some(lsp::CompletionOptions {
9853 resolve_provider: Some(true),
9854 ..Default::default()
9855 }),
9856 ..Default::default()
9857 },
9858 cx,
9859 )
9860 .await;
9861
9862 struct Run {
9863 run_description: &'static str,
9864 initial_state: String,
9865 buffer_marked_text: String,
9866 completion_text: &'static str,
9867 expected_with_insert_mode: String,
9868 expected_with_replace_mode: String,
9869 expected_with_replace_subsequence_mode: String,
9870 expected_with_replace_suffix_mode: String,
9871 }
9872
9873 let runs = [
9874 Run {
9875 run_description: "Start of word matches completion text",
9876 initial_state: "before ediˇ after".into(),
9877 buffer_marked_text: "before <edi|> after".into(),
9878 completion_text: "editor",
9879 expected_with_insert_mode: "before editorˇ after".into(),
9880 expected_with_replace_mode: "before editorˇ after".into(),
9881 expected_with_replace_subsequence_mode: "before editorˇ after".into(),
9882 expected_with_replace_suffix_mode: "before editorˇ after".into(),
9883 },
9884 Run {
9885 run_description: "Accept same text at the middle of the word",
9886 initial_state: "before ediˇtor after".into(),
9887 buffer_marked_text: "before <edi|tor> after".into(),
9888 completion_text: "editor",
9889 expected_with_insert_mode: "before editorˇtor after".into(),
9890 expected_with_replace_mode: "before editorˇ after".into(),
9891 expected_with_replace_subsequence_mode: "before editorˇ after".into(),
9892 expected_with_replace_suffix_mode: "before editorˇ after".into(),
9893 },
9894 Run {
9895 run_description: "End of word matches completion text -- cursor at end",
9896 initial_state: "before torˇ after".into(),
9897 buffer_marked_text: "before <tor|> after".into(),
9898 completion_text: "editor",
9899 expected_with_insert_mode: "before editorˇ after".into(),
9900 expected_with_replace_mode: "before editorˇ after".into(),
9901 expected_with_replace_subsequence_mode: "before editorˇ after".into(),
9902 expected_with_replace_suffix_mode: "before editorˇ after".into(),
9903 },
9904 Run {
9905 run_description: "End of word matches completion text -- cursor at start",
9906 initial_state: "before ˇtor after".into(),
9907 buffer_marked_text: "before <|tor> after".into(),
9908 completion_text: "editor",
9909 expected_with_insert_mode: "before editorˇtor after".into(),
9910 expected_with_replace_mode: "before editorˇ after".into(),
9911 expected_with_replace_subsequence_mode: "before editorˇ after".into(),
9912 expected_with_replace_suffix_mode: "before editorˇ after".into(),
9913 },
9914 Run {
9915 run_description: "Prepend text containing whitespace",
9916 initial_state: "pˇfield: bool".into(),
9917 buffer_marked_text: "<p|field>: bool".into(),
9918 completion_text: "pub ",
9919 expected_with_insert_mode: "pub ˇfield: bool".into(),
9920 expected_with_replace_mode: "pub ˇ: bool".into(),
9921 expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
9922 expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
9923 },
9924 Run {
9925 run_description: "Add element to start of list",
9926 initial_state: "[element_ˇelement_2]".into(),
9927 buffer_marked_text: "[<element_|element_2>]".into(),
9928 completion_text: "element_1",
9929 expected_with_insert_mode: "[element_1ˇelement_2]".into(),
9930 expected_with_replace_mode: "[element_1ˇ]".into(),
9931 expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
9932 expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
9933 },
9934 Run {
9935 run_description: "Add element to start of list -- first and second elements are equal",
9936 initial_state: "[elˇelement]".into(),
9937 buffer_marked_text: "[<el|element>]".into(),
9938 completion_text: "element",
9939 expected_with_insert_mode: "[elementˇelement]".into(),
9940 expected_with_replace_mode: "[elementˇ]".into(),
9941 expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
9942 expected_with_replace_suffix_mode: "[elementˇ]".into(),
9943 },
9944 Run {
9945 run_description: "Ends with matching suffix",
9946 initial_state: "SubˇError".into(),
9947 buffer_marked_text: "<Sub|Error>".into(),
9948 completion_text: "SubscriptionError",
9949 expected_with_insert_mode: "SubscriptionErrorˇError".into(),
9950 expected_with_replace_mode: "SubscriptionErrorˇ".into(),
9951 expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
9952 expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
9953 },
9954 Run {
9955 run_description: "Suffix is a subsequence -- contiguous",
9956 initial_state: "SubˇErr".into(),
9957 buffer_marked_text: "<Sub|Err>".into(),
9958 completion_text: "SubscriptionError",
9959 expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
9960 expected_with_replace_mode: "SubscriptionErrorˇ".into(),
9961 expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
9962 expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
9963 },
9964 Run {
9965 run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
9966 initial_state: "Suˇscrirr".into(),
9967 buffer_marked_text: "<Su|scrirr>".into(),
9968 completion_text: "SubscriptionError",
9969 expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
9970 expected_with_replace_mode: "SubscriptionErrorˇ".into(),
9971 expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
9972 expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
9973 },
9974 Run {
9975 run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
9976 initial_state: "foo(indˇix)".into(),
9977 buffer_marked_text: "foo(<ind|ix>)".into(),
9978 completion_text: "node_index",
9979 expected_with_insert_mode: "foo(node_indexˇix)".into(),
9980 expected_with_replace_mode: "foo(node_indexˇ)".into(),
9981 expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
9982 expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
9983 },
9984 ];
9985
9986 for run in runs {
9987 let run_variations = [
9988 (LspInsertMode::Insert, run.expected_with_insert_mode),
9989 (LspInsertMode::Replace, run.expected_with_replace_mode),
9990 (
9991 LspInsertMode::ReplaceSubsequence,
9992 run.expected_with_replace_subsequence_mode,
9993 ),
9994 (
9995 LspInsertMode::ReplaceSuffix,
9996 run.expected_with_replace_suffix_mode,
9997 ),
9998 ];
9999
10000 for (lsp_insert_mode, expected_text) in run_variations {
10001 eprintln!(
10002 "run = {:?}, mode = {lsp_insert_mode:.?}",
10003 run.run_description,
10004 );
10005
10006 update_test_language_settings(&mut cx, |settings| {
10007 settings.defaults.completions = Some(CompletionSettings {
10008 lsp_insert_mode,
10009 words: WordsCompletionMode::Disabled,
10010 lsp: true,
10011 lsp_fetch_timeout_ms: 0,
10012 });
10013 });
10014
10015 cx.set_state(&run.initial_state);
10016 cx.update_editor(|editor, window, cx| {
10017 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10018 });
10019
10020 let counter = Arc::new(AtomicUsize::new(0));
10021 handle_completion_request_with_insert_and_replace(
10022 &mut cx,
10023 &run.buffer_marked_text,
10024 vec![run.completion_text],
10025 counter.clone(),
10026 )
10027 .await;
10028 cx.condition(|editor, _| editor.context_menu_visible())
10029 .await;
10030 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
10031
10032 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10033 editor
10034 .confirm_completion(&ConfirmCompletion::default(), window, cx)
10035 .unwrap()
10036 });
10037 cx.assert_editor_state(&expected_text);
10038 handle_resolve_completion_request(&mut cx, None).await;
10039 apply_additional_edits.await.unwrap();
10040 }
10041 }
10042}
10043
10044#[gpui::test]
10045async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
10046 init_test(cx, |_| {});
10047 let mut cx = EditorLspTestContext::new_rust(
10048 lsp::ServerCapabilities {
10049 completion_provider: Some(lsp::CompletionOptions {
10050 resolve_provider: Some(true),
10051 ..Default::default()
10052 }),
10053 ..Default::default()
10054 },
10055 cx,
10056 )
10057 .await;
10058
10059 let initial_state = "SubˇError";
10060 let buffer_marked_text = "<Sub|Error>";
10061 let completion_text = "SubscriptionError";
10062 let expected_with_insert_mode = "SubscriptionErrorˇError";
10063 let expected_with_replace_mode = "SubscriptionErrorˇ";
10064
10065 update_test_language_settings(&mut cx, |settings| {
10066 settings.defaults.completions = Some(CompletionSettings {
10067 words: WordsCompletionMode::Disabled,
10068 // set the opposite here to ensure that the action is overriding the default behavior
10069 lsp_insert_mode: LspInsertMode::Insert,
10070 lsp: true,
10071 lsp_fetch_timeout_ms: 0,
10072 });
10073 });
10074
10075 cx.set_state(initial_state);
10076 cx.update_editor(|editor, window, cx| {
10077 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10078 });
10079
10080 let counter = Arc::new(AtomicUsize::new(0));
10081 handle_completion_request_with_insert_and_replace(
10082 &mut cx,
10083 &buffer_marked_text,
10084 vec![completion_text],
10085 counter.clone(),
10086 )
10087 .await;
10088 cx.condition(|editor, _| editor.context_menu_visible())
10089 .await;
10090 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
10091
10092 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10093 editor
10094 .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10095 .unwrap()
10096 });
10097 cx.assert_editor_state(&expected_with_replace_mode);
10098 handle_resolve_completion_request(&mut cx, None).await;
10099 apply_additional_edits.await.unwrap();
10100
10101 update_test_language_settings(&mut cx, |settings| {
10102 settings.defaults.completions = Some(CompletionSettings {
10103 words: WordsCompletionMode::Disabled,
10104 // set the opposite here to ensure that the action is overriding the default behavior
10105 lsp_insert_mode: LspInsertMode::Replace,
10106 lsp: true,
10107 lsp_fetch_timeout_ms: 0,
10108 });
10109 });
10110
10111 cx.set_state(initial_state);
10112 cx.update_editor(|editor, window, cx| {
10113 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10114 });
10115 handle_completion_request_with_insert_and_replace(
10116 &mut cx,
10117 &buffer_marked_text,
10118 vec![completion_text],
10119 counter.clone(),
10120 )
10121 .await;
10122 cx.condition(|editor, _| editor.context_menu_visible())
10123 .await;
10124 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
10125
10126 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10127 editor
10128 .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
10129 .unwrap()
10130 });
10131 cx.assert_editor_state(&expected_with_insert_mode);
10132 handle_resolve_completion_request(&mut cx, None).await;
10133 apply_additional_edits.await.unwrap();
10134}
10135
10136#[gpui::test]
10137async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
10138 init_test(cx, |_| {});
10139 let mut cx = EditorLspTestContext::new_rust(
10140 lsp::ServerCapabilities {
10141 completion_provider: Some(lsp::CompletionOptions {
10142 resolve_provider: Some(true),
10143 ..Default::default()
10144 }),
10145 ..Default::default()
10146 },
10147 cx,
10148 )
10149 .await;
10150
10151 // scenario: surrounding text matches completion text
10152 let completion_text = "to_offset";
10153 let initial_state = indoc! {"
10154 1. buf.to_offˇsuffix
10155 2. buf.to_offˇsuf
10156 3. buf.to_offˇfix
10157 4. buf.to_offˇ
10158 5. into_offˇensive
10159 6. ˇsuffix
10160 7. let ˇ //
10161 8. aaˇzz
10162 9. buf.to_off«zzzzzˇ»suffix
10163 10. buf.«ˇzzzzz»suffix
10164 11. to_off«ˇzzzzz»
10165
10166 buf.to_offˇsuffix // newest cursor
10167 "};
10168 let completion_marked_buffer = indoc! {"
10169 1. buf.to_offsuffix
10170 2. buf.to_offsuf
10171 3. buf.to_offfix
10172 4. buf.to_off
10173 5. into_offensive
10174 6. suffix
10175 7. let //
10176 8. aazz
10177 9. buf.to_offzzzzzsuffix
10178 10. buf.zzzzzsuffix
10179 11. to_offzzzzz
10180
10181 buf.<to_off|suffix> // newest cursor
10182 "};
10183 let expected = indoc! {"
10184 1. buf.to_offsetˇ
10185 2. buf.to_offsetˇsuf
10186 3. buf.to_offsetˇfix
10187 4. buf.to_offsetˇ
10188 5. into_offsetˇensive
10189 6. to_offsetˇsuffix
10190 7. let to_offsetˇ //
10191 8. aato_offsetˇzz
10192 9. buf.to_offsetˇ
10193 10. buf.to_offsetˇsuffix
10194 11. to_offsetˇ
10195
10196 buf.to_offsetˇ // newest cursor
10197 "};
10198 cx.set_state(initial_state);
10199 cx.update_editor(|editor, window, cx| {
10200 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10201 });
10202 handle_completion_request_with_insert_and_replace(
10203 &mut cx,
10204 completion_marked_buffer,
10205 vec![completion_text],
10206 Arc::new(AtomicUsize::new(0)),
10207 )
10208 .await;
10209 cx.condition(|editor, _| editor.context_menu_visible())
10210 .await;
10211 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10212 editor
10213 .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10214 .unwrap()
10215 });
10216 cx.assert_editor_state(expected);
10217 handle_resolve_completion_request(&mut cx, None).await;
10218 apply_additional_edits.await.unwrap();
10219
10220 // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
10221 let completion_text = "foo_and_bar";
10222 let initial_state = indoc! {"
10223 1. ooanbˇ
10224 2. zooanbˇ
10225 3. ooanbˇz
10226 4. zooanbˇz
10227 5. ooanˇ
10228 6. oanbˇ
10229
10230 ooanbˇ
10231 "};
10232 let completion_marked_buffer = indoc! {"
10233 1. ooanb
10234 2. zooanb
10235 3. ooanbz
10236 4. zooanbz
10237 5. ooan
10238 6. oanb
10239
10240 <ooanb|>
10241 "};
10242 let expected = indoc! {"
10243 1. foo_and_barˇ
10244 2. zfoo_and_barˇ
10245 3. foo_and_barˇz
10246 4. zfoo_and_barˇz
10247 5. ooanfoo_and_barˇ
10248 6. oanbfoo_and_barˇ
10249
10250 foo_and_barˇ
10251 "};
10252 cx.set_state(initial_state);
10253 cx.update_editor(|editor, window, cx| {
10254 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10255 });
10256 handle_completion_request_with_insert_and_replace(
10257 &mut cx,
10258 completion_marked_buffer,
10259 vec![completion_text],
10260 Arc::new(AtomicUsize::new(0)),
10261 )
10262 .await;
10263 cx.condition(|editor, _| editor.context_menu_visible())
10264 .await;
10265 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10266 editor
10267 .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10268 .unwrap()
10269 });
10270 cx.assert_editor_state(expected);
10271 handle_resolve_completion_request(&mut cx, None).await;
10272 apply_additional_edits.await.unwrap();
10273
10274 // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
10275 // (expects the same as if it was inserted at the end)
10276 let completion_text = "foo_and_bar";
10277 let initial_state = indoc! {"
10278 1. ooˇanb
10279 2. zooˇanb
10280 3. ooˇanbz
10281 4. zooˇanbz
10282
10283 ooˇanb
10284 "};
10285 let completion_marked_buffer = indoc! {"
10286 1. ooanb
10287 2. zooanb
10288 3. ooanbz
10289 4. zooanbz
10290
10291 <oo|anb>
10292 "};
10293 let expected = indoc! {"
10294 1. foo_and_barˇ
10295 2. zfoo_and_barˇ
10296 3. foo_and_barˇz
10297 4. zfoo_and_barˇz
10298
10299 foo_and_barˇ
10300 "};
10301 cx.set_state(initial_state);
10302 cx.update_editor(|editor, window, cx| {
10303 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10304 });
10305 handle_completion_request_with_insert_and_replace(
10306 &mut cx,
10307 completion_marked_buffer,
10308 vec![completion_text],
10309 Arc::new(AtomicUsize::new(0)),
10310 )
10311 .await;
10312 cx.condition(|editor, _| editor.context_menu_visible())
10313 .await;
10314 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10315 editor
10316 .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10317 .unwrap()
10318 });
10319 cx.assert_editor_state(expected);
10320 handle_resolve_completion_request(&mut cx, None).await;
10321 apply_additional_edits.await.unwrap();
10322}
10323
10324// This used to crash
10325#[gpui::test]
10326async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
10327 init_test(cx, |_| {});
10328
10329 let buffer_text = indoc! {"
10330 fn main() {
10331 10.satu;
10332
10333 //
10334 // separate cursors so they open in different excerpts (manually reproducible)
10335 //
10336
10337 10.satu20;
10338 }
10339 "};
10340 let multibuffer_text_with_selections = indoc! {"
10341 fn main() {
10342 10.satuˇ;
10343
10344 //
10345
10346 //
10347
10348 10.satuˇ20;
10349 }
10350 "};
10351 let expected_multibuffer = indoc! {"
10352 fn main() {
10353 10.saturating_sub()ˇ;
10354
10355 //
10356
10357 //
10358
10359 10.saturating_sub()ˇ;
10360 }
10361 "};
10362
10363 let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
10364 let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
10365
10366 let fs = FakeFs::new(cx.executor());
10367 fs.insert_tree(
10368 path!("/a"),
10369 json!({
10370 "main.rs": buffer_text,
10371 }),
10372 )
10373 .await;
10374
10375 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
10376 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10377 language_registry.add(rust_lang());
10378 let mut fake_servers = language_registry.register_fake_lsp(
10379 "Rust",
10380 FakeLspAdapter {
10381 capabilities: lsp::ServerCapabilities {
10382 completion_provider: Some(lsp::CompletionOptions {
10383 resolve_provider: None,
10384 ..lsp::CompletionOptions::default()
10385 }),
10386 ..lsp::ServerCapabilities::default()
10387 },
10388 ..FakeLspAdapter::default()
10389 },
10390 );
10391 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
10392 let cx = &mut VisualTestContext::from_window(*workspace, cx);
10393 let buffer = project
10394 .update(cx, |project, cx| {
10395 project.open_local_buffer(path!("/a/main.rs"), cx)
10396 })
10397 .await
10398 .unwrap();
10399
10400 let multi_buffer = cx.new(|cx| {
10401 let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
10402 multi_buffer.push_excerpts(
10403 buffer.clone(),
10404 [ExcerptRange::new(0..first_excerpt_end)],
10405 cx,
10406 );
10407 multi_buffer.push_excerpts(
10408 buffer.clone(),
10409 [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
10410 cx,
10411 );
10412 multi_buffer
10413 });
10414
10415 let editor = workspace
10416 .update(cx, |_, window, cx| {
10417 cx.new(|cx| {
10418 Editor::new(
10419 EditorMode::Full {
10420 scale_ui_elements_with_buffer_font_size: false,
10421 show_active_line_background: false,
10422 sized_by_content: false,
10423 },
10424 multi_buffer.clone(),
10425 Some(project.clone()),
10426 window,
10427 cx,
10428 )
10429 })
10430 })
10431 .unwrap();
10432
10433 let pane = workspace
10434 .update(cx, |workspace, _, _| workspace.active_pane().clone())
10435 .unwrap();
10436 pane.update_in(cx, |pane, window, cx| {
10437 pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
10438 });
10439
10440 let fake_server = fake_servers.next().await.unwrap();
10441
10442 editor.update_in(cx, |editor, window, cx| {
10443 editor.change_selections(None, window, cx, |s| {
10444 s.select_ranges([
10445 Point::new(1, 11)..Point::new(1, 11),
10446 Point::new(7, 11)..Point::new(7, 11),
10447 ])
10448 });
10449
10450 assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
10451 });
10452
10453 editor.update_in(cx, |editor, window, cx| {
10454 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10455 });
10456
10457 fake_server
10458 .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
10459 let completion_item = lsp::CompletionItem {
10460 label: "saturating_sub()".into(),
10461 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
10462 lsp::InsertReplaceEdit {
10463 new_text: "saturating_sub()".to_owned(),
10464 insert: lsp::Range::new(
10465 lsp::Position::new(7, 7),
10466 lsp::Position::new(7, 11),
10467 ),
10468 replace: lsp::Range::new(
10469 lsp::Position::new(7, 7),
10470 lsp::Position::new(7, 13),
10471 ),
10472 },
10473 )),
10474 ..lsp::CompletionItem::default()
10475 };
10476
10477 Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
10478 })
10479 .next()
10480 .await
10481 .unwrap();
10482
10483 cx.condition(&editor, |editor, _| editor.context_menu_visible())
10484 .await;
10485
10486 editor
10487 .update_in(cx, |editor, window, cx| {
10488 editor
10489 .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10490 .unwrap()
10491 })
10492 .await
10493 .unwrap();
10494
10495 editor.update(cx, |editor, cx| {
10496 assert_text_with_selections(editor, expected_multibuffer, cx);
10497 })
10498}
10499
10500#[gpui::test]
10501async fn test_completion(cx: &mut TestAppContext) {
10502 init_test(cx, |_| {});
10503
10504 let mut cx = EditorLspTestContext::new_rust(
10505 lsp::ServerCapabilities {
10506 completion_provider: Some(lsp::CompletionOptions {
10507 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
10508 resolve_provider: Some(true),
10509 ..Default::default()
10510 }),
10511 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
10512 ..Default::default()
10513 },
10514 cx,
10515 )
10516 .await;
10517 let counter = Arc::new(AtomicUsize::new(0));
10518
10519 cx.set_state(indoc! {"
10520 oneˇ
10521 two
10522 three
10523 "});
10524 cx.simulate_keystroke(".");
10525 handle_completion_request(
10526 &mut cx,
10527 indoc! {"
10528 one.|<>
10529 two
10530 three
10531 "},
10532 vec!["first_completion", "second_completion"],
10533 counter.clone(),
10534 )
10535 .await;
10536 cx.condition(|editor, _| editor.context_menu_visible())
10537 .await;
10538 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
10539
10540 let _handler = handle_signature_help_request(
10541 &mut cx,
10542 lsp::SignatureHelp {
10543 signatures: vec![lsp::SignatureInformation {
10544 label: "test signature".to_string(),
10545 documentation: None,
10546 parameters: Some(vec![lsp::ParameterInformation {
10547 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
10548 documentation: None,
10549 }]),
10550 active_parameter: None,
10551 }],
10552 active_signature: None,
10553 active_parameter: None,
10554 },
10555 );
10556 cx.update_editor(|editor, window, cx| {
10557 assert!(
10558 !editor.signature_help_state.is_shown(),
10559 "No signature help was called for"
10560 );
10561 editor.show_signature_help(&ShowSignatureHelp, window, cx);
10562 });
10563 cx.run_until_parked();
10564 cx.update_editor(|editor, _, _| {
10565 assert!(
10566 !editor.signature_help_state.is_shown(),
10567 "No signature help should be shown when completions menu is open"
10568 );
10569 });
10570
10571 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10572 editor.context_menu_next(&Default::default(), window, cx);
10573 editor
10574 .confirm_completion(&ConfirmCompletion::default(), window, cx)
10575 .unwrap()
10576 });
10577 cx.assert_editor_state(indoc! {"
10578 one.second_completionˇ
10579 two
10580 three
10581 "});
10582
10583 handle_resolve_completion_request(
10584 &mut cx,
10585 Some(vec![
10586 (
10587 //This overlaps with the primary completion edit which is
10588 //misbehavior from the LSP spec, test that we filter it out
10589 indoc! {"
10590 one.second_ˇcompletion
10591 two
10592 threeˇ
10593 "},
10594 "overlapping additional edit",
10595 ),
10596 (
10597 indoc! {"
10598 one.second_completion
10599 two
10600 threeˇ
10601 "},
10602 "\nadditional edit",
10603 ),
10604 ]),
10605 )
10606 .await;
10607 apply_additional_edits.await.unwrap();
10608 cx.assert_editor_state(indoc! {"
10609 one.second_completionˇ
10610 two
10611 three
10612 additional edit
10613 "});
10614
10615 cx.set_state(indoc! {"
10616 one.second_completion
10617 twoˇ
10618 threeˇ
10619 additional edit
10620 "});
10621 cx.simulate_keystroke(" ");
10622 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
10623 cx.simulate_keystroke("s");
10624 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
10625
10626 cx.assert_editor_state(indoc! {"
10627 one.second_completion
10628 two sˇ
10629 three sˇ
10630 additional edit
10631 "});
10632 handle_completion_request(
10633 &mut cx,
10634 indoc! {"
10635 one.second_completion
10636 two s
10637 three <s|>
10638 additional edit
10639 "},
10640 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
10641 counter.clone(),
10642 )
10643 .await;
10644 cx.condition(|editor, _| editor.context_menu_visible())
10645 .await;
10646 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
10647
10648 cx.simulate_keystroke("i");
10649
10650 handle_completion_request(
10651 &mut cx,
10652 indoc! {"
10653 one.second_completion
10654 two si
10655 three <si|>
10656 additional edit
10657 "},
10658 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
10659 counter.clone(),
10660 )
10661 .await;
10662 cx.condition(|editor, _| editor.context_menu_visible())
10663 .await;
10664 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
10665
10666 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10667 editor
10668 .confirm_completion(&ConfirmCompletion::default(), window, cx)
10669 .unwrap()
10670 });
10671 cx.assert_editor_state(indoc! {"
10672 one.second_completion
10673 two sixth_completionˇ
10674 three sixth_completionˇ
10675 additional edit
10676 "});
10677
10678 apply_additional_edits.await.unwrap();
10679
10680 update_test_language_settings(&mut cx, |settings| {
10681 settings.defaults.show_completions_on_input = Some(false);
10682 });
10683 cx.set_state("editorˇ");
10684 cx.simulate_keystroke(".");
10685 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
10686 cx.simulate_keystrokes("c l o");
10687 cx.assert_editor_state("editor.cloˇ");
10688 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
10689 cx.update_editor(|editor, window, cx| {
10690 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10691 });
10692 handle_completion_request(
10693 &mut cx,
10694 "editor.<clo|>",
10695 vec!["close", "clobber"],
10696 counter.clone(),
10697 )
10698 .await;
10699 cx.condition(|editor, _| editor.context_menu_visible())
10700 .await;
10701 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
10702
10703 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10704 editor
10705 .confirm_completion(&ConfirmCompletion::default(), window, cx)
10706 .unwrap()
10707 });
10708 cx.assert_editor_state("editor.clobberˇ");
10709 handle_resolve_completion_request(&mut cx, None).await;
10710 apply_additional_edits.await.unwrap();
10711}
10712
10713#[gpui::test]
10714async fn test_word_completion(cx: &mut TestAppContext) {
10715 let lsp_fetch_timeout_ms = 10;
10716 init_test(cx, |language_settings| {
10717 language_settings.defaults.completions = Some(CompletionSettings {
10718 words: WordsCompletionMode::Fallback,
10719 lsp: true,
10720 lsp_fetch_timeout_ms: 10,
10721 lsp_insert_mode: LspInsertMode::Insert,
10722 });
10723 });
10724
10725 let mut cx = EditorLspTestContext::new_rust(
10726 lsp::ServerCapabilities {
10727 completion_provider: Some(lsp::CompletionOptions {
10728 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
10729 ..lsp::CompletionOptions::default()
10730 }),
10731 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
10732 ..lsp::ServerCapabilities::default()
10733 },
10734 cx,
10735 )
10736 .await;
10737
10738 let throttle_completions = Arc::new(AtomicBool::new(false));
10739
10740 let lsp_throttle_completions = throttle_completions.clone();
10741 let _completion_requests_handler =
10742 cx.lsp
10743 .server
10744 .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
10745 let lsp_throttle_completions = lsp_throttle_completions.clone();
10746 let cx = cx.clone();
10747 async move {
10748 if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
10749 cx.background_executor()
10750 .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
10751 .await;
10752 }
10753 Ok(Some(lsp::CompletionResponse::Array(vec![
10754 lsp::CompletionItem {
10755 label: "first".into(),
10756 ..lsp::CompletionItem::default()
10757 },
10758 lsp::CompletionItem {
10759 label: "last".into(),
10760 ..lsp::CompletionItem::default()
10761 },
10762 ])))
10763 }
10764 });
10765
10766 cx.set_state(indoc! {"
10767 oneˇ
10768 two
10769 three
10770 "});
10771 cx.simulate_keystroke(".");
10772 cx.executor().run_until_parked();
10773 cx.condition(|editor, _| editor.context_menu_visible())
10774 .await;
10775 cx.update_editor(|editor, window, cx| {
10776 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10777 {
10778 assert_eq!(
10779 completion_menu_entries(&menu),
10780 &["first", "last"],
10781 "When LSP server is fast to reply, no fallback word completions are used"
10782 );
10783 } else {
10784 panic!("expected completion menu to be open");
10785 }
10786 editor.cancel(&Cancel, window, cx);
10787 });
10788 cx.executor().run_until_parked();
10789 cx.condition(|editor, _| !editor.context_menu_visible())
10790 .await;
10791
10792 throttle_completions.store(true, atomic::Ordering::Release);
10793 cx.simulate_keystroke(".");
10794 cx.executor()
10795 .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
10796 cx.executor().run_until_parked();
10797 cx.condition(|editor, _| editor.context_menu_visible())
10798 .await;
10799 cx.update_editor(|editor, _, _| {
10800 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10801 {
10802 assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
10803 "When LSP server is slow, document words can be shown instead, if configured accordingly");
10804 } else {
10805 panic!("expected completion menu to be open");
10806 }
10807 });
10808}
10809
10810#[gpui::test]
10811async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
10812 init_test(cx, |language_settings| {
10813 language_settings.defaults.completions = Some(CompletionSettings {
10814 words: WordsCompletionMode::Enabled,
10815 lsp: true,
10816 lsp_fetch_timeout_ms: 0,
10817 lsp_insert_mode: LspInsertMode::Insert,
10818 });
10819 });
10820
10821 let mut cx = EditorLspTestContext::new_rust(
10822 lsp::ServerCapabilities {
10823 completion_provider: Some(lsp::CompletionOptions {
10824 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
10825 ..lsp::CompletionOptions::default()
10826 }),
10827 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
10828 ..lsp::ServerCapabilities::default()
10829 },
10830 cx,
10831 )
10832 .await;
10833
10834 let _completion_requests_handler =
10835 cx.lsp
10836 .server
10837 .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
10838 Ok(Some(lsp::CompletionResponse::Array(vec![
10839 lsp::CompletionItem {
10840 label: "first".into(),
10841 ..lsp::CompletionItem::default()
10842 },
10843 lsp::CompletionItem {
10844 label: "last".into(),
10845 ..lsp::CompletionItem::default()
10846 },
10847 ])))
10848 });
10849
10850 cx.set_state(indoc! {"ˇ
10851 first
10852 last
10853 second
10854 "});
10855 cx.simulate_keystroke(".");
10856 cx.executor().run_until_parked();
10857 cx.condition(|editor, _| editor.context_menu_visible())
10858 .await;
10859 cx.update_editor(|editor, _, _| {
10860 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10861 {
10862 assert_eq!(
10863 completion_menu_entries(&menu),
10864 &["first", "last", "second"],
10865 "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
10866 );
10867 } else {
10868 panic!("expected completion menu to be open");
10869 }
10870 });
10871}
10872
10873#[gpui::test]
10874async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
10875 init_test(cx, |language_settings| {
10876 language_settings.defaults.completions = Some(CompletionSettings {
10877 words: WordsCompletionMode::Disabled,
10878 lsp: true,
10879 lsp_fetch_timeout_ms: 0,
10880 lsp_insert_mode: LspInsertMode::Insert,
10881 });
10882 });
10883
10884 let mut cx = EditorLspTestContext::new_rust(
10885 lsp::ServerCapabilities {
10886 completion_provider: Some(lsp::CompletionOptions {
10887 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
10888 ..lsp::CompletionOptions::default()
10889 }),
10890 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
10891 ..lsp::ServerCapabilities::default()
10892 },
10893 cx,
10894 )
10895 .await;
10896
10897 let _completion_requests_handler =
10898 cx.lsp
10899 .server
10900 .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
10901 panic!("LSP completions should not be queried when dealing with word completions")
10902 });
10903
10904 cx.set_state(indoc! {"ˇ
10905 first
10906 last
10907 second
10908 "});
10909 cx.update_editor(|editor, window, cx| {
10910 editor.show_word_completions(&ShowWordCompletions, window, cx);
10911 });
10912 cx.executor().run_until_parked();
10913 cx.condition(|editor, _| editor.context_menu_visible())
10914 .await;
10915 cx.update_editor(|editor, _, _| {
10916 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10917 {
10918 assert_eq!(
10919 completion_menu_entries(&menu),
10920 &["first", "last", "second"],
10921 "`ShowWordCompletions` action should show word completions"
10922 );
10923 } else {
10924 panic!("expected completion menu to be open");
10925 }
10926 });
10927
10928 cx.simulate_keystroke("l");
10929 cx.executor().run_until_parked();
10930 cx.condition(|editor, _| editor.context_menu_visible())
10931 .await;
10932 cx.update_editor(|editor, _, _| {
10933 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10934 {
10935 assert_eq!(
10936 completion_menu_entries(&menu),
10937 &["last"],
10938 "After showing word completions, further editing should filter them and not query the LSP"
10939 );
10940 } else {
10941 panic!("expected completion menu to be open");
10942 }
10943 });
10944}
10945
10946#[gpui::test]
10947async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
10948 init_test(cx, |language_settings| {
10949 language_settings.defaults.completions = Some(CompletionSettings {
10950 words: WordsCompletionMode::Fallback,
10951 lsp: false,
10952 lsp_fetch_timeout_ms: 0,
10953 lsp_insert_mode: LspInsertMode::Insert,
10954 });
10955 });
10956
10957 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10958
10959 cx.set_state(indoc! {"ˇ
10960 0_usize
10961 let
10962 33
10963 4.5f32
10964 "});
10965 cx.update_editor(|editor, window, cx| {
10966 editor.show_completions(&ShowCompletions::default(), window, cx);
10967 });
10968 cx.executor().run_until_parked();
10969 cx.condition(|editor, _| editor.context_menu_visible())
10970 .await;
10971 cx.update_editor(|editor, window, cx| {
10972 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10973 {
10974 assert_eq!(
10975 completion_menu_entries(&menu),
10976 &["let"],
10977 "With no digits in the completion query, no digits should be in the word completions"
10978 );
10979 } else {
10980 panic!("expected completion menu to be open");
10981 }
10982 editor.cancel(&Cancel, window, cx);
10983 });
10984
10985 cx.set_state(indoc! {"3ˇ
10986 0_usize
10987 let
10988 3
10989 33.35f32
10990 "});
10991 cx.update_editor(|editor, window, cx| {
10992 editor.show_completions(&ShowCompletions::default(), window, cx);
10993 });
10994 cx.executor().run_until_parked();
10995 cx.condition(|editor, _| editor.context_menu_visible())
10996 .await;
10997 cx.update_editor(|editor, _, _| {
10998 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10999 {
11000 assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
11001 return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
11002 } else {
11003 panic!("expected completion menu to be open");
11004 }
11005 });
11006}
11007
11008fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
11009 let position = || lsp::Position {
11010 line: params.text_document_position.position.line,
11011 character: params.text_document_position.position.character,
11012 };
11013 Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11014 range: lsp::Range {
11015 start: position(),
11016 end: position(),
11017 },
11018 new_text: text.to_string(),
11019 }))
11020}
11021
11022#[gpui::test]
11023async fn test_multiline_completion(cx: &mut TestAppContext) {
11024 init_test(cx, |_| {});
11025
11026 let fs = FakeFs::new(cx.executor());
11027 fs.insert_tree(
11028 path!("/a"),
11029 json!({
11030 "main.ts": "a",
11031 }),
11032 )
11033 .await;
11034
11035 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11036 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11037 let typescript_language = Arc::new(Language::new(
11038 LanguageConfig {
11039 name: "TypeScript".into(),
11040 matcher: LanguageMatcher {
11041 path_suffixes: vec!["ts".to_string()],
11042 ..LanguageMatcher::default()
11043 },
11044 line_comments: vec!["// ".into()],
11045 ..LanguageConfig::default()
11046 },
11047 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
11048 ));
11049 language_registry.add(typescript_language.clone());
11050 let mut fake_servers = language_registry.register_fake_lsp(
11051 "TypeScript",
11052 FakeLspAdapter {
11053 capabilities: lsp::ServerCapabilities {
11054 completion_provider: Some(lsp::CompletionOptions {
11055 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11056 ..lsp::CompletionOptions::default()
11057 }),
11058 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11059 ..lsp::ServerCapabilities::default()
11060 },
11061 // Emulate vtsls label generation
11062 label_for_completion: Some(Box::new(|item, _| {
11063 let text = if let Some(description) = item
11064 .label_details
11065 .as_ref()
11066 .and_then(|label_details| label_details.description.as_ref())
11067 {
11068 format!("{} {}", item.label, description)
11069 } else if let Some(detail) = &item.detail {
11070 format!("{} {}", item.label, detail)
11071 } else {
11072 item.label.clone()
11073 };
11074 let len = text.len();
11075 Some(language::CodeLabel {
11076 text,
11077 runs: Vec::new(),
11078 filter_range: 0..len,
11079 })
11080 })),
11081 ..FakeLspAdapter::default()
11082 },
11083 );
11084 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11085 let cx = &mut VisualTestContext::from_window(*workspace, cx);
11086 let worktree_id = workspace
11087 .update(cx, |workspace, _window, cx| {
11088 workspace.project().update(cx, |project, cx| {
11089 project.worktrees(cx).next().unwrap().read(cx).id()
11090 })
11091 })
11092 .unwrap();
11093 let _buffer = project
11094 .update(cx, |project, cx| {
11095 project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
11096 })
11097 .await
11098 .unwrap();
11099 let editor = workspace
11100 .update(cx, |workspace, window, cx| {
11101 workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
11102 })
11103 .unwrap()
11104 .await
11105 .unwrap()
11106 .downcast::<Editor>()
11107 .unwrap();
11108 let fake_server = fake_servers.next().await.unwrap();
11109
11110 let multiline_label = "StickyHeaderExcerpt {\n excerpt,\n next_excerpt_controls_present,\n next_buffer_row,\n }: StickyHeaderExcerpt<'_>,";
11111 let multiline_label_2 = "a\nb\nc\n";
11112 let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
11113 let multiline_description = "d\ne\nf\n";
11114 let multiline_detail_2 = "g\nh\ni\n";
11115
11116 let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
11117 move |params, _| async move {
11118 Ok(Some(lsp::CompletionResponse::Array(vec![
11119 lsp::CompletionItem {
11120 label: multiline_label.to_string(),
11121 text_edit: gen_text_edit(¶ms, "new_text_1"),
11122 ..lsp::CompletionItem::default()
11123 },
11124 lsp::CompletionItem {
11125 label: "single line label 1".to_string(),
11126 detail: Some(multiline_detail.to_string()),
11127 text_edit: gen_text_edit(¶ms, "new_text_2"),
11128 ..lsp::CompletionItem::default()
11129 },
11130 lsp::CompletionItem {
11131 label: "single line label 2".to_string(),
11132 label_details: Some(lsp::CompletionItemLabelDetails {
11133 description: Some(multiline_description.to_string()),
11134 detail: None,
11135 }),
11136 text_edit: gen_text_edit(¶ms, "new_text_2"),
11137 ..lsp::CompletionItem::default()
11138 },
11139 lsp::CompletionItem {
11140 label: multiline_label_2.to_string(),
11141 detail: Some(multiline_detail_2.to_string()),
11142 text_edit: gen_text_edit(¶ms, "new_text_3"),
11143 ..lsp::CompletionItem::default()
11144 },
11145 lsp::CompletionItem {
11146 label: "Label with many spaces and \t but without newlines".to_string(),
11147 detail: Some(
11148 "Details with many spaces and \t but without newlines".to_string(),
11149 ),
11150 text_edit: gen_text_edit(¶ms, "new_text_4"),
11151 ..lsp::CompletionItem::default()
11152 },
11153 ])))
11154 },
11155 );
11156
11157 editor.update_in(cx, |editor, window, cx| {
11158 cx.focus_self(window);
11159 editor.move_to_end(&MoveToEnd, window, cx);
11160 editor.handle_input(".", window, cx);
11161 });
11162 cx.run_until_parked();
11163 completion_handle.next().await.unwrap();
11164
11165 editor.update(cx, |editor, _| {
11166 assert!(editor.context_menu_visible());
11167 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11168 {
11169 let completion_labels = menu
11170 .completions
11171 .borrow()
11172 .iter()
11173 .map(|c| c.label.text.clone())
11174 .collect::<Vec<_>>();
11175 assert_eq!(
11176 completion_labels,
11177 &[
11178 "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
11179 "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
11180 "single line label 2 d e f ",
11181 "a b c g h i ",
11182 "Label with many spaces and \t but without newlines Details with many spaces and \t but without newlines",
11183 ],
11184 "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
11185 );
11186
11187 for completion in menu
11188 .completions
11189 .borrow()
11190 .iter() {
11191 assert_eq!(
11192 completion.label.filter_range,
11193 0..completion.label.text.len(),
11194 "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
11195 );
11196 }
11197 } else {
11198 panic!("expected completion menu to be open");
11199 }
11200 });
11201}
11202
11203#[gpui::test]
11204async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
11205 init_test(cx, |_| {});
11206 let mut cx = EditorLspTestContext::new_rust(
11207 lsp::ServerCapabilities {
11208 completion_provider: Some(lsp::CompletionOptions {
11209 trigger_characters: Some(vec![".".to_string()]),
11210 ..Default::default()
11211 }),
11212 ..Default::default()
11213 },
11214 cx,
11215 )
11216 .await;
11217 cx.lsp
11218 .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
11219 Ok(Some(lsp::CompletionResponse::Array(vec![
11220 lsp::CompletionItem {
11221 label: "first".into(),
11222 ..Default::default()
11223 },
11224 lsp::CompletionItem {
11225 label: "last".into(),
11226 ..Default::default()
11227 },
11228 ])))
11229 });
11230 cx.set_state("variableˇ");
11231 cx.simulate_keystroke(".");
11232 cx.executor().run_until_parked();
11233
11234 cx.update_editor(|editor, _, _| {
11235 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11236 {
11237 assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
11238 } else {
11239 panic!("expected completion menu to be open");
11240 }
11241 });
11242
11243 cx.update_editor(|editor, window, cx| {
11244 editor.move_page_down(&MovePageDown::default(), window, cx);
11245 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11246 {
11247 assert!(
11248 menu.selected_item == 1,
11249 "expected PageDown to select the last item from the context menu"
11250 );
11251 } else {
11252 panic!("expected completion menu to stay open after PageDown");
11253 }
11254 });
11255
11256 cx.update_editor(|editor, window, cx| {
11257 editor.move_page_up(&MovePageUp::default(), window, cx);
11258 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11259 {
11260 assert!(
11261 menu.selected_item == 0,
11262 "expected PageUp to select the first item from the context menu"
11263 );
11264 } else {
11265 panic!("expected completion menu to stay open after PageUp");
11266 }
11267 });
11268}
11269
11270#[gpui::test]
11271async fn test_as_is_completions(cx: &mut TestAppContext) {
11272 init_test(cx, |_| {});
11273 let mut cx = EditorLspTestContext::new_rust(
11274 lsp::ServerCapabilities {
11275 completion_provider: Some(lsp::CompletionOptions {
11276 ..Default::default()
11277 }),
11278 ..Default::default()
11279 },
11280 cx,
11281 )
11282 .await;
11283 cx.lsp
11284 .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
11285 Ok(Some(lsp::CompletionResponse::Array(vec![
11286 lsp::CompletionItem {
11287 label: "unsafe".into(),
11288 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11289 range: lsp::Range {
11290 start: lsp::Position {
11291 line: 1,
11292 character: 2,
11293 },
11294 end: lsp::Position {
11295 line: 1,
11296 character: 3,
11297 },
11298 },
11299 new_text: "unsafe".to_string(),
11300 })),
11301 insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
11302 ..Default::default()
11303 },
11304 ])))
11305 });
11306 cx.set_state("fn a() {}\n nˇ");
11307 cx.executor().run_until_parked();
11308 cx.update_editor(|editor, window, cx| {
11309 editor.show_completions(
11310 &ShowCompletions {
11311 trigger: Some("\n".into()),
11312 },
11313 window,
11314 cx,
11315 );
11316 });
11317 cx.executor().run_until_parked();
11318
11319 cx.update_editor(|editor, window, cx| {
11320 editor.confirm_completion(&Default::default(), window, cx)
11321 });
11322 cx.executor().run_until_parked();
11323 cx.assert_editor_state("fn a() {}\n unsafeˇ");
11324}
11325
11326#[gpui::test]
11327async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
11328 init_test(cx, |_| {});
11329
11330 let mut cx = EditorLspTestContext::new_rust(
11331 lsp::ServerCapabilities {
11332 completion_provider: Some(lsp::CompletionOptions {
11333 trigger_characters: Some(vec![".".to_string()]),
11334 resolve_provider: Some(true),
11335 ..Default::default()
11336 }),
11337 ..Default::default()
11338 },
11339 cx,
11340 )
11341 .await;
11342
11343 cx.set_state("fn main() { let a = 2ˇ; }");
11344 cx.simulate_keystroke(".");
11345 let completion_item = lsp::CompletionItem {
11346 label: "Some".into(),
11347 kind: Some(lsp::CompletionItemKind::SNIPPET),
11348 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
11349 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
11350 kind: lsp::MarkupKind::Markdown,
11351 value: "```rust\nSome(2)\n```".to_string(),
11352 })),
11353 deprecated: Some(false),
11354 sort_text: Some("Some".to_string()),
11355 filter_text: Some("Some".to_string()),
11356 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
11357 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11358 range: lsp::Range {
11359 start: lsp::Position {
11360 line: 0,
11361 character: 22,
11362 },
11363 end: lsp::Position {
11364 line: 0,
11365 character: 22,
11366 },
11367 },
11368 new_text: "Some(2)".to_string(),
11369 })),
11370 additional_text_edits: Some(vec![lsp::TextEdit {
11371 range: lsp::Range {
11372 start: lsp::Position {
11373 line: 0,
11374 character: 20,
11375 },
11376 end: lsp::Position {
11377 line: 0,
11378 character: 22,
11379 },
11380 },
11381 new_text: "".to_string(),
11382 }]),
11383 ..Default::default()
11384 };
11385
11386 let closure_completion_item = completion_item.clone();
11387 let counter = Arc::new(AtomicUsize::new(0));
11388 let counter_clone = counter.clone();
11389 let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
11390 let task_completion_item = closure_completion_item.clone();
11391 counter_clone.fetch_add(1, atomic::Ordering::Release);
11392 async move {
11393 Ok(Some(lsp::CompletionResponse::Array(vec![
11394 task_completion_item,
11395 ])))
11396 }
11397 });
11398
11399 cx.condition(|editor, _| editor.context_menu_visible())
11400 .await;
11401 cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
11402 assert!(request.next().await.is_some());
11403 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11404
11405 cx.simulate_keystrokes("S o m");
11406 cx.condition(|editor, _| editor.context_menu_visible())
11407 .await;
11408 cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
11409 assert!(request.next().await.is_some());
11410 assert!(request.next().await.is_some());
11411 assert!(request.next().await.is_some());
11412 request.close();
11413 assert!(request.next().await.is_none());
11414 assert_eq!(
11415 counter.load(atomic::Ordering::Acquire),
11416 4,
11417 "With the completions menu open, only one LSP request should happen per input"
11418 );
11419}
11420
11421#[gpui::test]
11422async fn test_toggle_comment(cx: &mut TestAppContext) {
11423 init_test(cx, |_| {});
11424 let mut cx = EditorTestContext::new(cx).await;
11425 let language = Arc::new(Language::new(
11426 LanguageConfig {
11427 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
11428 ..Default::default()
11429 },
11430 Some(tree_sitter_rust::LANGUAGE.into()),
11431 ));
11432 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
11433
11434 // If multiple selections intersect a line, the line is only toggled once.
11435 cx.set_state(indoc! {"
11436 fn a() {
11437 «//b();
11438 ˇ»// «c();
11439 //ˇ» d();
11440 }
11441 "});
11442
11443 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
11444
11445 cx.assert_editor_state(indoc! {"
11446 fn a() {
11447 «b();
11448 c();
11449 ˇ» d();
11450 }
11451 "});
11452
11453 // The comment prefix is inserted at the same column for every line in a
11454 // selection.
11455 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
11456
11457 cx.assert_editor_state(indoc! {"
11458 fn a() {
11459 // «b();
11460 // c();
11461 ˇ»// d();
11462 }
11463 "});
11464
11465 // If a selection ends at the beginning of a line, that line is not toggled.
11466 cx.set_selections_state(indoc! {"
11467 fn a() {
11468 // b();
11469 «// c();
11470 ˇ» // d();
11471 }
11472 "});
11473
11474 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
11475
11476 cx.assert_editor_state(indoc! {"
11477 fn a() {
11478 // b();
11479 «c();
11480 ˇ» // d();
11481 }
11482 "});
11483
11484 // If a selection span a single line and is empty, the line is toggled.
11485 cx.set_state(indoc! {"
11486 fn a() {
11487 a();
11488 b();
11489 ˇ
11490 }
11491 "});
11492
11493 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
11494
11495 cx.assert_editor_state(indoc! {"
11496 fn a() {
11497 a();
11498 b();
11499 //•ˇ
11500 }
11501 "});
11502
11503 // If a selection span multiple lines, empty lines are not toggled.
11504 cx.set_state(indoc! {"
11505 fn a() {
11506 «a();
11507
11508 c();ˇ»
11509 }
11510 "});
11511
11512 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
11513
11514 cx.assert_editor_state(indoc! {"
11515 fn a() {
11516 // «a();
11517
11518 // c();ˇ»
11519 }
11520 "});
11521
11522 // If a selection includes multiple comment prefixes, all lines are uncommented.
11523 cx.set_state(indoc! {"
11524 fn a() {
11525 «// a();
11526 /// b();
11527 //! c();ˇ»
11528 }
11529 "});
11530
11531 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
11532
11533 cx.assert_editor_state(indoc! {"
11534 fn a() {
11535 «a();
11536 b();
11537 c();ˇ»
11538 }
11539 "});
11540}
11541
11542#[gpui::test]
11543async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
11544 init_test(cx, |_| {});
11545 let mut cx = EditorTestContext::new(cx).await;
11546 let language = Arc::new(Language::new(
11547 LanguageConfig {
11548 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
11549 ..Default::default()
11550 },
11551 Some(tree_sitter_rust::LANGUAGE.into()),
11552 ));
11553 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
11554
11555 let toggle_comments = &ToggleComments {
11556 advance_downwards: false,
11557 ignore_indent: true,
11558 };
11559
11560 // If multiple selections intersect a line, the line is only toggled once.
11561 cx.set_state(indoc! {"
11562 fn a() {
11563 // «b();
11564 // c();
11565 // ˇ» d();
11566 }
11567 "});
11568
11569 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
11570
11571 cx.assert_editor_state(indoc! {"
11572 fn a() {
11573 «b();
11574 c();
11575 ˇ» d();
11576 }
11577 "});
11578
11579 // The comment prefix is inserted at the beginning of each line
11580 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
11581
11582 cx.assert_editor_state(indoc! {"
11583 fn a() {
11584 // «b();
11585 // c();
11586 // ˇ» d();
11587 }
11588 "});
11589
11590 // If a selection ends at the beginning of a line, that line is not toggled.
11591 cx.set_selections_state(indoc! {"
11592 fn a() {
11593 // b();
11594 // «c();
11595 ˇ»// d();
11596 }
11597 "});
11598
11599 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
11600
11601 cx.assert_editor_state(indoc! {"
11602 fn a() {
11603 // b();
11604 «c();
11605 ˇ»// d();
11606 }
11607 "});
11608
11609 // If a selection span a single line and is empty, the line is toggled.
11610 cx.set_state(indoc! {"
11611 fn a() {
11612 a();
11613 b();
11614 ˇ
11615 }
11616 "});
11617
11618 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
11619
11620 cx.assert_editor_state(indoc! {"
11621 fn a() {
11622 a();
11623 b();
11624 //ˇ
11625 }
11626 "});
11627
11628 // If a selection span multiple lines, empty lines are not toggled.
11629 cx.set_state(indoc! {"
11630 fn a() {
11631 «a();
11632
11633 c();ˇ»
11634 }
11635 "});
11636
11637 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
11638
11639 cx.assert_editor_state(indoc! {"
11640 fn a() {
11641 // «a();
11642
11643 // c();ˇ»
11644 }
11645 "});
11646
11647 // If a selection includes multiple comment prefixes, all lines are uncommented.
11648 cx.set_state(indoc! {"
11649 fn a() {
11650 // «a();
11651 /// b();
11652 //! c();ˇ»
11653 }
11654 "});
11655
11656 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
11657
11658 cx.assert_editor_state(indoc! {"
11659 fn a() {
11660 «a();
11661 b();
11662 c();ˇ»
11663 }
11664 "});
11665}
11666
11667#[gpui::test]
11668async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
11669 init_test(cx, |_| {});
11670
11671 let language = Arc::new(Language::new(
11672 LanguageConfig {
11673 line_comments: vec!["// ".into()],
11674 ..Default::default()
11675 },
11676 Some(tree_sitter_rust::LANGUAGE.into()),
11677 ));
11678
11679 let mut cx = EditorTestContext::new(cx).await;
11680
11681 cx.language_registry().add(language.clone());
11682 cx.update_buffer(|buffer, cx| {
11683 buffer.set_language(Some(language), cx);
11684 });
11685
11686 let toggle_comments = &ToggleComments {
11687 advance_downwards: true,
11688 ignore_indent: false,
11689 };
11690
11691 // Single cursor on one line -> advance
11692 // Cursor moves horizontally 3 characters as well on non-blank line
11693 cx.set_state(indoc!(
11694 "fn a() {
11695 ˇdog();
11696 cat();
11697 }"
11698 ));
11699 cx.update_editor(|editor, window, cx| {
11700 editor.toggle_comments(toggle_comments, window, cx);
11701 });
11702 cx.assert_editor_state(indoc!(
11703 "fn a() {
11704 // dog();
11705 catˇ();
11706 }"
11707 ));
11708
11709 // Single selection on one line -> don't advance
11710 cx.set_state(indoc!(
11711 "fn a() {
11712 «dog()ˇ»;
11713 cat();
11714 }"
11715 ));
11716 cx.update_editor(|editor, window, cx| {
11717 editor.toggle_comments(toggle_comments, window, cx);
11718 });
11719 cx.assert_editor_state(indoc!(
11720 "fn a() {
11721 // «dog()ˇ»;
11722 cat();
11723 }"
11724 ));
11725
11726 // Multiple cursors on one line -> advance
11727 cx.set_state(indoc!(
11728 "fn a() {
11729 ˇdˇog();
11730 cat();
11731 }"
11732 ));
11733 cx.update_editor(|editor, window, cx| {
11734 editor.toggle_comments(toggle_comments, window, cx);
11735 });
11736 cx.assert_editor_state(indoc!(
11737 "fn a() {
11738 // dog();
11739 catˇ(ˇ);
11740 }"
11741 ));
11742
11743 // Multiple cursors on one line, with selection -> don't advance
11744 cx.set_state(indoc!(
11745 "fn a() {
11746 ˇdˇog«()ˇ»;
11747 cat();
11748 }"
11749 ));
11750 cx.update_editor(|editor, window, cx| {
11751 editor.toggle_comments(toggle_comments, window, cx);
11752 });
11753 cx.assert_editor_state(indoc!(
11754 "fn a() {
11755 // ˇdˇog«()ˇ»;
11756 cat();
11757 }"
11758 ));
11759
11760 // Single cursor on one line -> advance
11761 // Cursor moves to column 0 on blank line
11762 cx.set_state(indoc!(
11763 "fn a() {
11764 ˇdog();
11765
11766 cat();
11767 }"
11768 ));
11769 cx.update_editor(|editor, window, cx| {
11770 editor.toggle_comments(toggle_comments, window, cx);
11771 });
11772 cx.assert_editor_state(indoc!(
11773 "fn a() {
11774 // dog();
11775 ˇ
11776 cat();
11777 }"
11778 ));
11779
11780 // Single cursor on one line -> advance
11781 // Cursor starts and ends at column 0
11782 cx.set_state(indoc!(
11783 "fn a() {
11784 ˇ dog();
11785 cat();
11786 }"
11787 ));
11788 cx.update_editor(|editor, window, cx| {
11789 editor.toggle_comments(toggle_comments, window, cx);
11790 });
11791 cx.assert_editor_state(indoc!(
11792 "fn a() {
11793 // dog();
11794 ˇ cat();
11795 }"
11796 ));
11797}
11798
11799#[gpui::test]
11800async fn test_toggle_block_comment(cx: &mut TestAppContext) {
11801 init_test(cx, |_| {});
11802
11803 let mut cx = EditorTestContext::new(cx).await;
11804
11805 let html_language = Arc::new(
11806 Language::new(
11807 LanguageConfig {
11808 name: "HTML".into(),
11809 block_comment: Some(("<!-- ".into(), " -->".into())),
11810 ..Default::default()
11811 },
11812 Some(tree_sitter_html::LANGUAGE.into()),
11813 )
11814 .with_injection_query(
11815 r#"
11816 (script_element
11817 (raw_text) @injection.content
11818 (#set! injection.language "javascript"))
11819 "#,
11820 )
11821 .unwrap(),
11822 );
11823
11824 let javascript_language = Arc::new(Language::new(
11825 LanguageConfig {
11826 name: "JavaScript".into(),
11827 line_comments: vec!["// ".into()],
11828 ..Default::default()
11829 },
11830 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
11831 ));
11832
11833 cx.language_registry().add(html_language.clone());
11834 cx.language_registry().add(javascript_language.clone());
11835 cx.update_buffer(|buffer, cx| {
11836 buffer.set_language(Some(html_language), cx);
11837 });
11838
11839 // Toggle comments for empty selections
11840 cx.set_state(
11841 &r#"
11842 <p>A</p>ˇ
11843 <p>B</p>ˇ
11844 <p>C</p>ˇ
11845 "#
11846 .unindent(),
11847 );
11848 cx.update_editor(|editor, window, cx| {
11849 editor.toggle_comments(&ToggleComments::default(), window, cx)
11850 });
11851 cx.assert_editor_state(
11852 &r#"
11853 <!-- <p>A</p>ˇ -->
11854 <!-- <p>B</p>ˇ -->
11855 <!-- <p>C</p>ˇ -->
11856 "#
11857 .unindent(),
11858 );
11859 cx.update_editor(|editor, window, cx| {
11860 editor.toggle_comments(&ToggleComments::default(), window, cx)
11861 });
11862 cx.assert_editor_state(
11863 &r#"
11864 <p>A</p>ˇ
11865 <p>B</p>ˇ
11866 <p>C</p>ˇ
11867 "#
11868 .unindent(),
11869 );
11870
11871 // Toggle comments for mixture of empty and non-empty selections, where
11872 // multiple selections occupy a given line.
11873 cx.set_state(
11874 &r#"
11875 <p>A«</p>
11876 <p>ˇ»B</p>ˇ
11877 <p>C«</p>
11878 <p>ˇ»D</p>ˇ
11879 "#
11880 .unindent(),
11881 );
11882
11883 cx.update_editor(|editor, window, cx| {
11884 editor.toggle_comments(&ToggleComments::default(), window, cx)
11885 });
11886 cx.assert_editor_state(
11887 &r#"
11888 <!-- <p>A«</p>
11889 <p>ˇ»B</p>ˇ -->
11890 <!-- <p>C«</p>
11891 <p>ˇ»D</p>ˇ -->
11892 "#
11893 .unindent(),
11894 );
11895 cx.update_editor(|editor, window, cx| {
11896 editor.toggle_comments(&ToggleComments::default(), window, cx)
11897 });
11898 cx.assert_editor_state(
11899 &r#"
11900 <p>A«</p>
11901 <p>ˇ»B</p>ˇ
11902 <p>C«</p>
11903 <p>ˇ»D</p>ˇ
11904 "#
11905 .unindent(),
11906 );
11907
11908 // Toggle comments when different languages are active for different
11909 // selections.
11910 cx.set_state(
11911 &r#"
11912 ˇ<script>
11913 ˇvar x = new Y();
11914 ˇ</script>
11915 "#
11916 .unindent(),
11917 );
11918 cx.executor().run_until_parked();
11919 cx.update_editor(|editor, window, cx| {
11920 editor.toggle_comments(&ToggleComments::default(), window, cx)
11921 });
11922 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
11923 // Uncommenting and commenting from this position brings in even more wrong artifacts.
11924 cx.assert_editor_state(
11925 &r#"
11926 <!-- ˇ<script> -->
11927 // ˇvar x = new Y();
11928 <!-- ˇ</script> -->
11929 "#
11930 .unindent(),
11931 );
11932}
11933
11934#[gpui::test]
11935fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
11936 init_test(cx, |_| {});
11937
11938 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
11939 let multibuffer = cx.new(|cx| {
11940 let mut multibuffer = MultiBuffer::new(ReadWrite);
11941 multibuffer.push_excerpts(
11942 buffer.clone(),
11943 [
11944 ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
11945 ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
11946 ],
11947 cx,
11948 );
11949 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
11950 multibuffer
11951 });
11952
11953 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
11954 editor.update_in(cx, |editor, window, cx| {
11955 assert_eq!(editor.text(cx), "aaaa\nbbbb");
11956 editor.change_selections(None, window, cx, |s| {
11957 s.select_ranges([
11958 Point::new(0, 0)..Point::new(0, 0),
11959 Point::new(1, 0)..Point::new(1, 0),
11960 ])
11961 });
11962
11963 editor.handle_input("X", window, cx);
11964 assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
11965 assert_eq!(
11966 editor.selections.ranges(cx),
11967 [
11968 Point::new(0, 1)..Point::new(0, 1),
11969 Point::new(1, 1)..Point::new(1, 1),
11970 ]
11971 );
11972
11973 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
11974 editor.change_selections(None, window, cx, |s| {
11975 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
11976 });
11977 editor.backspace(&Default::default(), window, cx);
11978 assert_eq!(editor.text(cx), "Xa\nbbb");
11979 assert_eq!(
11980 editor.selections.ranges(cx),
11981 [Point::new(1, 0)..Point::new(1, 0)]
11982 );
11983
11984 editor.change_selections(None, window, cx, |s| {
11985 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
11986 });
11987 editor.backspace(&Default::default(), window, cx);
11988 assert_eq!(editor.text(cx), "X\nbb");
11989 assert_eq!(
11990 editor.selections.ranges(cx),
11991 [Point::new(0, 1)..Point::new(0, 1)]
11992 );
11993 });
11994}
11995
11996#[gpui::test]
11997fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
11998 init_test(cx, |_| {});
11999
12000 let markers = vec![('[', ']').into(), ('(', ')').into()];
12001 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
12002 indoc! {"
12003 [aaaa
12004 (bbbb]
12005 cccc)",
12006 },
12007 markers.clone(),
12008 );
12009 let excerpt_ranges = markers.into_iter().map(|marker| {
12010 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
12011 ExcerptRange::new(context.clone())
12012 });
12013 let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
12014 let multibuffer = cx.new(|cx| {
12015 let mut multibuffer = MultiBuffer::new(ReadWrite);
12016 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
12017 multibuffer
12018 });
12019
12020 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
12021 editor.update_in(cx, |editor, window, cx| {
12022 let (expected_text, selection_ranges) = marked_text_ranges(
12023 indoc! {"
12024 aaaa
12025 bˇbbb
12026 bˇbbˇb
12027 cccc"
12028 },
12029 true,
12030 );
12031 assert_eq!(editor.text(cx), expected_text);
12032 editor.change_selections(None, window, cx, |s| s.select_ranges(selection_ranges));
12033
12034 editor.handle_input("X", window, cx);
12035
12036 let (expected_text, expected_selections) = marked_text_ranges(
12037 indoc! {"
12038 aaaa
12039 bXˇbbXb
12040 bXˇbbXˇb
12041 cccc"
12042 },
12043 false,
12044 );
12045 assert_eq!(editor.text(cx), expected_text);
12046 assert_eq!(editor.selections.ranges(cx), expected_selections);
12047
12048 editor.newline(&Newline, window, cx);
12049 let (expected_text, expected_selections) = marked_text_ranges(
12050 indoc! {"
12051 aaaa
12052 bX
12053 ˇbbX
12054 b
12055 bX
12056 ˇbbX
12057 ˇb
12058 cccc"
12059 },
12060 false,
12061 );
12062 assert_eq!(editor.text(cx), expected_text);
12063 assert_eq!(editor.selections.ranges(cx), expected_selections);
12064 });
12065}
12066
12067#[gpui::test]
12068fn test_refresh_selections(cx: &mut TestAppContext) {
12069 init_test(cx, |_| {});
12070
12071 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
12072 let mut excerpt1_id = None;
12073 let multibuffer = cx.new(|cx| {
12074 let mut multibuffer = MultiBuffer::new(ReadWrite);
12075 excerpt1_id = multibuffer
12076 .push_excerpts(
12077 buffer.clone(),
12078 [
12079 ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
12080 ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
12081 ],
12082 cx,
12083 )
12084 .into_iter()
12085 .next();
12086 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
12087 multibuffer
12088 });
12089
12090 let editor = cx.add_window(|window, cx| {
12091 let mut editor = build_editor(multibuffer.clone(), window, cx);
12092 let snapshot = editor.snapshot(window, cx);
12093 editor.change_selections(None, window, cx, |s| {
12094 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
12095 });
12096 editor.begin_selection(
12097 Point::new(2, 1).to_display_point(&snapshot),
12098 true,
12099 1,
12100 window,
12101 cx,
12102 );
12103 assert_eq!(
12104 editor.selections.ranges(cx),
12105 [
12106 Point::new(1, 3)..Point::new(1, 3),
12107 Point::new(2, 1)..Point::new(2, 1),
12108 ]
12109 );
12110 editor
12111 });
12112
12113 // Refreshing selections is a no-op when excerpts haven't changed.
12114 _ = editor.update(cx, |editor, window, cx| {
12115 editor.change_selections(None, window, cx, |s| s.refresh());
12116 assert_eq!(
12117 editor.selections.ranges(cx),
12118 [
12119 Point::new(1, 3)..Point::new(1, 3),
12120 Point::new(2, 1)..Point::new(2, 1),
12121 ]
12122 );
12123 });
12124
12125 multibuffer.update(cx, |multibuffer, cx| {
12126 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
12127 });
12128 _ = editor.update(cx, |editor, window, cx| {
12129 // Removing an excerpt causes the first selection to become degenerate.
12130 assert_eq!(
12131 editor.selections.ranges(cx),
12132 [
12133 Point::new(0, 0)..Point::new(0, 0),
12134 Point::new(0, 1)..Point::new(0, 1)
12135 ]
12136 );
12137
12138 // Refreshing selections will relocate the first selection to the original buffer
12139 // location.
12140 editor.change_selections(None, window, cx, |s| s.refresh());
12141 assert_eq!(
12142 editor.selections.ranges(cx),
12143 [
12144 Point::new(0, 1)..Point::new(0, 1),
12145 Point::new(0, 3)..Point::new(0, 3)
12146 ]
12147 );
12148 assert!(editor.selections.pending_anchor().is_some());
12149 });
12150}
12151
12152#[gpui::test]
12153fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
12154 init_test(cx, |_| {});
12155
12156 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
12157 let mut excerpt1_id = None;
12158 let multibuffer = cx.new(|cx| {
12159 let mut multibuffer = MultiBuffer::new(ReadWrite);
12160 excerpt1_id = multibuffer
12161 .push_excerpts(
12162 buffer.clone(),
12163 [
12164 ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
12165 ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
12166 ],
12167 cx,
12168 )
12169 .into_iter()
12170 .next();
12171 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
12172 multibuffer
12173 });
12174
12175 let editor = cx.add_window(|window, cx| {
12176 let mut editor = build_editor(multibuffer.clone(), window, cx);
12177 let snapshot = editor.snapshot(window, cx);
12178 editor.begin_selection(
12179 Point::new(1, 3).to_display_point(&snapshot),
12180 false,
12181 1,
12182 window,
12183 cx,
12184 );
12185 assert_eq!(
12186 editor.selections.ranges(cx),
12187 [Point::new(1, 3)..Point::new(1, 3)]
12188 );
12189 editor
12190 });
12191
12192 multibuffer.update(cx, |multibuffer, cx| {
12193 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
12194 });
12195 _ = editor.update(cx, |editor, window, cx| {
12196 assert_eq!(
12197 editor.selections.ranges(cx),
12198 [Point::new(0, 0)..Point::new(0, 0)]
12199 );
12200
12201 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
12202 editor.change_selections(None, window, cx, |s| s.refresh());
12203 assert_eq!(
12204 editor.selections.ranges(cx),
12205 [Point::new(0, 3)..Point::new(0, 3)]
12206 );
12207 assert!(editor.selections.pending_anchor().is_some());
12208 });
12209}
12210
12211#[gpui::test]
12212async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
12213 init_test(cx, |_| {});
12214
12215 let language = Arc::new(
12216 Language::new(
12217 LanguageConfig {
12218 brackets: BracketPairConfig {
12219 pairs: vec![
12220 BracketPair {
12221 start: "{".to_string(),
12222 end: "}".to_string(),
12223 close: true,
12224 surround: true,
12225 newline: true,
12226 },
12227 BracketPair {
12228 start: "/* ".to_string(),
12229 end: " */".to_string(),
12230 close: true,
12231 surround: true,
12232 newline: true,
12233 },
12234 ],
12235 ..Default::default()
12236 },
12237 ..Default::default()
12238 },
12239 Some(tree_sitter_rust::LANGUAGE.into()),
12240 )
12241 .with_indents_query("")
12242 .unwrap(),
12243 );
12244
12245 let text = concat!(
12246 "{ }\n", //
12247 " x\n", //
12248 " /* */\n", //
12249 "x\n", //
12250 "{{} }\n", //
12251 );
12252
12253 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
12254 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12255 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
12256 editor
12257 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
12258 .await;
12259
12260 editor.update_in(cx, |editor, window, cx| {
12261 editor.change_selections(None, window, cx, |s| {
12262 s.select_display_ranges([
12263 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
12264 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
12265 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
12266 ])
12267 });
12268 editor.newline(&Newline, window, cx);
12269
12270 assert_eq!(
12271 editor.buffer().read(cx).read(cx).text(),
12272 concat!(
12273 "{ \n", // Suppress rustfmt
12274 "\n", //
12275 "}\n", //
12276 " x\n", //
12277 " /* \n", //
12278 " \n", //
12279 " */\n", //
12280 "x\n", //
12281 "{{} \n", //
12282 "}\n", //
12283 )
12284 );
12285 });
12286}
12287
12288#[gpui::test]
12289fn test_highlighted_ranges(cx: &mut TestAppContext) {
12290 init_test(cx, |_| {});
12291
12292 let editor = cx.add_window(|window, cx| {
12293 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
12294 build_editor(buffer.clone(), window, cx)
12295 });
12296
12297 _ = editor.update(cx, |editor, window, cx| {
12298 struct Type1;
12299 struct Type2;
12300
12301 let buffer = editor.buffer.read(cx).snapshot(cx);
12302
12303 let anchor_range =
12304 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
12305
12306 editor.highlight_background::<Type1>(
12307 &[
12308 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
12309 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
12310 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
12311 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
12312 ],
12313 |_| Hsla::red(),
12314 cx,
12315 );
12316 editor.highlight_background::<Type2>(
12317 &[
12318 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
12319 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
12320 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
12321 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
12322 ],
12323 |_| Hsla::green(),
12324 cx,
12325 );
12326
12327 let snapshot = editor.snapshot(window, cx);
12328 let mut highlighted_ranges = editor.background_highlights_in_range(
12329 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
12330 &snapshot,
12331 cx.theme().colors(),
12332 );
12333 // Enforce a consistent ordering based on color without relying on the ordering of the
12334 // highlight's `TypeId` which is non-executor.
12335 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
12336 assert_eq!(
12337 highlighted_ranges,
12338 &[
12339 (
12340 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
12341 Hsla::red(),
12342 ),
12343 (
12344 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
12345 Hsla::red(),
12346 ),
12347 (
12348 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
12349 Hsla::green(),
12350 ),
12351 (
12352 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
12353 Hsla::green(),
12354 ),
12355 ]
12356 );
12357 assert_eq!(
12358 editor.background_highlights_in_range(
12359 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
12360 &snapshot,
12361 cx.theme().colors(),
12362 ),
12363 &[(
12364 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
12365 Hsla::red(),
12366 )]
12367 );
12368 });
12369}
12370
12371#[gpui::test]
12372async fn test_following(cx: &mut TestAppContext) {
12373 init_test(cx, |_| {});
12374
12375 let fs = FakeFs::new(cx.executor());
12376 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
12377
12378 let buffer = project.update(cx, |project, cx| {
12379 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
12380 cx.new(|cx| MultiBuffer::singleton(buffer, cx))
12381 });
12382 let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
12383 let follower = cx.update(|cx| {
12384 cx.open_window(
12385 WindowOptions {
12386 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
12387 gpui::Point::new(px(0.), px(0.)),
12388 gpui::Point::new(px(10.), px(80.)),
12389 ))),
12390 ..Default::default()
12391 },
12392 |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
12393 )
12394 .unwrap()
12395 });
12396
12397 let is_still_following = Rc::new(RefCell::new(true));
12398 let follower_edit_event_count = Rc::new(RefCell::new(0));
12399 let pending_update = Rc::new(RefCell::new(None));
12400 let leader_entity = leader.root(cx).unwrap();
12401 let follower_entity = follower.root(cx).unwrap();
12402 _ = follower.update(cx, {
12403 let update = pending_update.clone();
12404 let is_still_following = is_still_following.clone();
12405 let follower_edit_event_count = follower_edit_event_count.clone();
12406 |_, window, cx| {
12407 cx.subscribe_in(
12408 &leader_entity,
12409 window,
12410 move |_, leader, event, window, cx| {
12411 leader.read(cx).add_event_to_update_proto(
12412 event,
12413 &mut update.borrow_mut(),
12414 window,
12415 cx,
12416 );
12417 },
12418 )
12419 .detach();
12420
12421 cx.subscribe_in(
12422 &follower_entity,
12423 window,
12424 move |_, _, event: &EditorEvent, _window, _cx| {
12425 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
12426 *is_still_following.borrow_mut() = false;
12427 }
12428
12429 if let EditorEvent::BufferEdited = event {
12430 *follower_edit_event_count.borrow_mut() += 1;
12431 }
12432 },
12433 )
12434 .detach();
12435 }
12436 });
12437
12438 // Update the selections only
12439 _ = leader.update(cx, |leader, window, cx| {
12440 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
12441 });
12442 follower
12443 .update(cx, |follower, window, cx| {
12444 follower.apply_update_proto(
12445 &project,
12446 pending_update.borrow_mut().take().unwrap(),
12447 window,
12448 cx,
12449 )
12450 })
12451 .unwrap()
12452 .await
12453 .unwrap();
12454 _ = follower.update(cx, |follower, _, cx| {
12455 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
12456 });
12457 assert!(*is_still_following.borrow());
12458 assert_eq!(*follower_edit_event_count.borrow(), 0);
12459
12460 // Update the scroll position only
12461 _ = leader.update(cx, |leader, window, cx| {
12462 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
12463 });
12464 follower
12465 .update(cx, |follower, window, cx| {
12466 follower.apply_update_proto(
12467 &project,
12468 pending_update.borrow_mut().take().unwrap(),
12469 window,
12470 cx,
12471 )
12472 })
12473 .unwrap()
12474 .await
12475 .unwrap();
12476 assert_eq!(
12477 follower
12478 .update(cx, |follower, _, cx| follower.scroll_position(cx))
12479 .unwrap(),
12480 gpui::Point::new(1.5, 3.5)
12481 );
12482 assert!(*is_still_following.borrow());
12483 assert_eq!(*follower_edit_event_count.borrow(), 0);
12484
12485 // Update the selections and scroll position. The follower's scroll position is updated
12486 // via autoscroll, not via the leader's exact scroll position.
12487 _ = leader.update(cx, |leader, window, cx| {
12488 leader.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
12489 leader.request_autoscroll(Autoscroll::newest(), cx);
12490 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
12491 });
12492 follower
12493 .update(cx, |follower, window, cx| {
12494 follower.apply_update_proto(
12495 &project,
12496 pending_update.borrow_mut().take().unwrap(),
12497 window,
12498 cx,
12499 )
12500 })
12501 .unwrap()
12502 .await
12503 .unwrap();
12504 _ = follower.update(cx, |follower, _, cx| {
12505 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
12506 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
12507 });
12508 assert!(*is_still_following.borrow());
12509
12510 // Creating a pending selection that precedes another selection
12511 _ = leader.update(cx, |leader, window, cx| {
12512 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
12513 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
12514 });
12515 follower
12516 .update(cx, |follower, window, cx| {
12517 follower.apply_update_proto(
12518 &project,
12519 pending_update.borrow_mut().take().unwrap(),
12520 window,
12521 cx,
12522 )
12523 })
12524 .unwrap()
12525 .await
12526 .unwrap();
12527 _ = follower.update(cx, |follower, _, cx| {
12528 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
12529 });
12530 assert!(*is_still_following.borrow());
12531
12532 // Extend the pending selection so that it surrounds another selection
12533 _ = leader.update(cx, |leader, window, cx| {
12534 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
12535 });
12536 follower
12537 .update(cx, |follower, window, cx| {
12538 follower.apply_update_proto(
12539 &project,
12540 pending_update.borrow_mut().take().unwrap(),
12541 window,
12542 cx,
12543 )
12544 })
12545 .unwrap()
12546 .await
12547 .unwrap();
12548 _ = follower.update(cx, |follower, _, cx| {
12549 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
12550 });
12551
12552 // Scrolling locally breaks the follow
12553 _ = follower.update(cx, |follower, window, cx| {
12554 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
12555 follower.set_scroll_anchor(
12556 ScrollAnchor {
12557 anchor: top_anchor,
12558 offset: gpui::Point::new(0.0, 0.5),
12559 },
12560 window,
12561 cx,
12562 );
12563 });
12564 assert!(!(*is_still_following.borrow()));
12565}
12566
12567#[gpui::test]
12568async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
12569 init_test(cx, |_| {});
12570
12571 let fs = FakeFs::new(cx.executor());
12572 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
12573 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12574 let pane = workspace
12575 .update(cx, |workspace, _, _| workspace.active_pane().clone())
12576 .unwrap();
12577
12578 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
12579
12580 let leader = pane.update_in(cx, |_, window, cx| {
12581 let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
12582 cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
12583 });
12584
12585 // Start following the editor when it has no excerpts.
12586 let mut state_message =
12587 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
12588 let workspace_entity = workspace.root(cx).unwrap();
12589 let follower_1 = cx
12590 .update_window(*workspace.deref(), |_, window, cx| {
12591 Editor::from_state_proto(
12592 workspace_entity,
12593 ViewId {
12594 creator: Default::default(),
12595 id: 0,
12596 },
12597 &mut state_message,
12598 window,
12599 cx,
12600 )
12601 })
12602 .unwrap()
12603 .unwrap()
12604 .await
12605 .unwrap();
12606
12607 let update_message = Rc::new(RefCell::new(None));
12608 follower_1.update_in(cx, {
12609 let update = update_message.clone();
12610 |_, window, cx| {
12611 cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
12612 leader.read(cx).add_event_to_update_proto(
12613 event,
12614 &mut update.borrow_mut(),
12615 window,
12616 cx,
12617 );
12618 })
12619 .detach();
12620 }
12621 });
12622
12623 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
12624 (
12625 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
12626 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
12627 )
12628 });
12629
12630 // Insert some excerpts.
12631 leader.update(cx, |leader, cx| {
12632 leader.buffer.update(cx, |multibuffer, cx| {
12633 multibuffer.set_excerpts_for_path(
12634 PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
12635 buffer_1.clone(),
12636 vec![
12637 Point::row_range(0..3),
12638 Point::row_range(1..6),
12639 Point::row_range(12..15),
12640 ],
12641 0,
12642 cx,
12643 );
12644 multibuffer.set_excerpts_for_path(
12645 PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
12646 buffer_2.clone(),
12647 vec![Point::row_range(0..6), Point::row_range(8..12)],
12648 0,
12649 cx,
12650 );
12651 });
12652 });
12653
12654 // Apply the update of adding the excerpts.
12655 follower_1
12656 .update_in(cx, |follower, window, cx| {
12657 follower.apply_update_proto(
12658 &project,
12659 update_message.borrow().clone().unwrap(),
12660 window,
12661 cx,
12662 )
12663 })
12664 .await
12665 .unwrap();
12666 assert_eq!(
12667 follower_1.update(cx, |editor, cx| editor.text(cx)),
12668 leader.update(cx, |editor, cx| editor.text(cx))
12669 );
12670 update_message.borrow_mut().take();
12671
12672 // Start following separately after it already has excerpts.
12673 let mut state_message =
12674 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
12675 let workspace_entity = workspace.root(cx).unwrap();
12676 let follower_2 = cx
12677 .update_window(*workspace.deref(), |_, window, cx| {
12678 Editor::from_state_proto(
12679 workspace_entity,
12680 ViewId {
12681 creator: Default::default(),
12682 id: 0,
12683 },
12684 &mut state_message,
12685 window,
12686 cx,
12687 )
12688 })
12689 .unwrap()
12690 .unwrap()
12691 .await
12692 .unwrap();
12693 assert_eq!(
12694 follower_2.update(cx, |editor, cx| editor.text(cx)),
12695 leader.update(cx, |editor, cx| editor.text(cx))
12696 );
12697
12698 // Remove some excerpts.
12699 leader.update(cx, |leader, cx| {
12700 leader.buffer.update(cx, |multibuffer, cx| {
12701 let excerpt_ids = multibuffer.excerpt_ids();
12702 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
12703 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
12704 });
12705 });
12706
12707 // Apply the update of removing the excerpts.
12708 follower_1
12709 .update_in(cx, |follower, window, cx| {
12710 follower.apply_update_proto(
12711 &project,
12712 update_message.borrow().clone().unwrap(),
12713 window,
12714 cx,
12715 )
12716 })
12717 .await
12718 .unwrap();
12719 follower_2
12720 .update_in(cx, |follower, window, cx| {
12721 follower.apply_update_proto(
12722 &project,
12723 update_message.borrow().clone().unwrap(),
12724 window,
12725 cx,
12726 )
12727 })
12728 .await
12729 .unwrap();
12730 update_message.borrow_mut().take();
12731 assert_eq!(
12732 follower_1.update(cx, |editor, cx| editor.text(cx)),
12733 leader.update(cx, |editor, cx| editor.text(cx))
12734 );
12735}
12736
12737#[gpui::test]
12738async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
12739 init_test(cx, |_| {});
12740
12741 let mut cx = EditorTestContext::new(cx).await;
12742 let lsp_store =
12743 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
12744
12745 cx.set_state(indoc! {"
12746 ˇfn func(abc def: i32) -> u32 {
12747 }
12748 "});
12749
12750 cx.update(|_, cx| {
12751 lsp_store.update(cx, |lsp_store, cx| {
12752 lsp_store
12753 .update_diagnostics(
12754 LanguageServerId(0),
12755 lsp::PublishDiagnosticsParams {
12756 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
12757 version: None,
12758 diagnostics: vec![
12759 lsp::Diagnostic {
12760 range: lsp::Range::new(
12761 lsp::Position::new(0, 11),
12762 lsp::Position::new(0, 12),
12763 ),
12764 severity: Some(lsp::DiagnosticSeverity::ERROR),
12765 ..Default::default()
12766 },
12767 lsp::Diagnostic {
12768 range: lsp::Range::new(
12769 lsp::Position::new(0, 12),
12770 lsp::Position::new(0, 15),
12771 ),
12772 severity: Some(lsp::DiagnosticSeverity::ERROR),
12773 ..Default::default()
12774 },
12775 lsp::Diagnostic {
12776 range: lsp::Range::new(
12777 lsp::Position::new(0, 25),
12778 lsp::Position::new(0, 28),
12779 ),
12780 severity: Some(lsp::DiagnosticSeverity::ERROR),
12781 ..Default::default()
12782 },
12783 ],
12784 },
12785 &[],
12786 cx,
12787 )
12788 .unwrap()
12789 });
12790 });
12791
12792 executor.run_until_parked();
12793
12794 cx.update_editor(|editor, window, cx| {
12795 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
12796 });
12797
12798 cx.assert_editor_state(indoc! {"
12799 fn func(abc def: i32) -> ˇu32 {
12800 }
12801 "});
12802
12803 cx.update_editor(|editor, window, cx| {
12804 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
12805 });
12806
12807 cx.assert_editor_state(indoc! {"
12808 fn func(abc ˇdef: i32) -> u32 {
12809 }
12810 "});
12811
12812 cx.update_editor(|editor, window, cx| {
12813 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
12814 });
12815
12816 cx.assert_editor_state(indoc! {"
12817 fn func(abcˇ def: i32) -> u32 {
12818 }
12819 "});
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
12831#[gpui::test]
12832async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
12833 init_test(cx, |_| {});
12834
12835 let mut cx = EditorTestContext::new(cx).await;
12836
12837 cx.set_state(indoc! {"
12838 fn func(abˇc def: i32) -> u32 {
12839 }
12840 "});
12841 let lsp_store =
12842 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
12843
12844 cx.update(|_, cx| {
12845 lsp_store.update(cx, |lsp_store, cx| {
12846 lsp_store.update_diagnostics(
12847 LanguageServerId(0),
12848 lsp::PublishDiagnosticsParams {
12849 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
12850 version: None,
12851 diagnostics: vec![lsp::Diagnostic {
12852 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
12853 severity: Some(lsp::DiagnosticSeverity::ERROR),
12854 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
12855 ..Default::default()
12856 }],
12857 },
12858 &[],
12859 cx,
12860 )
12861 })
12862 }).unwrap();
12863 cx.run_until_parked();
12864 cx.update_editor(|editor, window, cx| {
12865 hover_popover::hover(editor, &Default::default(), window, cx)
12866 });
12867 cx.run_until_parked();
12868 cx.update_editor(|editor, _, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
12869}
12870
12871#[gpui::test]
12872async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
12873 init_test(cx, |_| {});
12874
12875 let mut cx = EditorTestContext::new(cx).await;
12876
12877 let diff_base = r#"
12878 use some::mod;
12879
12880 const A: u32 = 42;
12881
12882 fn main() {
12883 println!("hello");
12884
12885 println!("world");
12886 }
12887 "#
12888 .unindent();
12889
12890 // Edits are modified, removed, modified, added
12891 cx.set_state(
12892 &r#"
12893 use some::modified;
12894
12895 ˇ
12896 fn main() {
12897 println!("hello there");
12898
12899 println!("around the");
12900 println!("world");
12901 }
12902 "#
12903 .unindent(),
12904 );
12905
12906 cx.set_head_text(&diff_base);
12907 executor.run_until_parked();
12908
12909 cx.update_editor(|editor, window, cx| {
12910 //Wrap around the bottom of the buffer
12911 for _ in 0..3 {
12912 editor.go_to_next_hunk(&GoToHunk, window, cx);
12913 }
12914 });
12915
12916 cx.assert_editor_state(
12917 &r#"
12918 ˇuse some::modified;
12919
12920
12921 fn main() {
12922 println!("hello there");
12923
12924 println!("around the");
12925 println!("world");
12926 }
12927 "#
12928 .unindent(),
12929 );
12930
12931 cx.update_editor(|editor, window, cx| {
12932 //Wrap around the top of the buffer
12933 for _ in 0..2 {
12934 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
12935 }
12936 });
12937
12938 cx.assert_editor_state(
12939 &r#"
12940 use some::modified;
12941
12942
12943 fn main() {
12944 ˇ println!("hello there");
12945
12946 println!("around the");
12947 println!("world");
12948 }
12949 "#
12950 .unindent(),
12951 );
12952
12953 cx.update_editor(|editor, window, cx| {
12954 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
12955 });
12956
12957 cx.assert_editor_state(
12958 &r#"
12959 use some::modified;
12960
12961 ˇ
12962 fn main() {
12963 println!("hello there");
12964
12965 println!("around the");
12966 println!("world");
12967 }
12968 "#
12969 .unindent(),
12970 );
12971
12972 cx.update_editor(|editor, window, cx| {
12973 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
12974 });
12975
12976 cx.assert_editor_state(
12977 &r#"
12978 ˇuse some::modified;
12979
12980
12981 fn main() {
12982 println!("hello there");
12983
12984 println!("around the");
12985 println!("world");
12986 }
12987 "#
12988 .unindent(),
12989 );
12990
12991 cx.update_editor(|editor, window, cx| {
12992 for _ in 0..2 {
12993 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
12994 }
12995 });
12996
12997 cx.assert_editor_state(
12998 &r#"
12999 use some::modified;
13000
13001
13002 fn main() {
13003 ˇ println!("hello there");
13004
13005 println!("around the");
13006 println!("world");
13007 }
13008 "#
13009 .unindent(),
13010 );
13011
13012 cx.update_editor(|editor, window, cx| {
13013 editor.fold(&Fold, window, cx);
13014 });
13015
13016 cx.update_editor(|editor, window, cx| {
13017 editor.go_to_next_hunk(&GoToHunk, window, cx);
13018 });
13019
13020 cx.assert_editor_state(
13021 &r#"
13022 ˇuse some::modified;
13023
13024
13025 fn main() {
13026 println!("hello there");
13027
13028 println!("around the");
13029 println!("world");
13030 }
13031 "#
13032 .unindent(),
13033 );
13034}
13035
13036#[test]
13037fn test_split_words() {
13038 fn split(text: &str) -> Vec<&str> {
13039 split_words(text).collect()
13040 }
13041
13042 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
13043 assert_eq!(split("hello_world"), &["hello_", "world"]);
13044 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
13045 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
13046 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
13047 assert_eq!(split("helloworld"), &["helloworld"]);
13048
13049 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
13050}
13051
13052#[gpui::test]
13053async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
13054 init_test(cx, |_| {});
13055
13056 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
13057 let mut assert = |before, after| {
13058 let _state_context = cx.set_state(before);
13059 cx.run_until_parked();
13060 cx.update_editor(|editor, window, cx| {
13061 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
13062 });
13063 cx.run_until_parked();
13064 cx.assert_editor_state(after);
13065 };
13066
13067 // Outside bracket jumps to outside of matching bracket
13068 assert("console.logˇ(var);", "console.log(var)ˇ;");
13069 assert("console.log(var)ˇ;", "console.logˇ(var);");
13070
13071 // Inside bracket jumps to inside of matching bracket
13072 assert("console.log(ˇvar);", "console.log(varˇ);");
13073 assert("console.log(varˇ);", "console.log(ˇvar);");
13074
13075 // When outside a bracket and inside, favor jumping to the inside bracket
13076 assert(
13077 "console.log('foo', [1, 2, 3]ˇ);",
13078 "console.log(ˇ'foo', [1, 2, 3]);",
13079 );
13080 assert(
13081 "console.log(ˇ'foo', [1, 2, 3]);",
13082 "console.log('foo', [1, 2, 3]ˇ);",
13083 );
13084
13085 // Bias forward if two options are equally likely
13086 assert(
13087 "let result = curried_fun()ˇ();",
13088 "let result = curried_fun()()ˇ;",
13089 );
13090
13091 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
13092 assert(
13093 indoc! {"
13094 function test() {
13095 console.log('test')ˇ
13096 }"},
13097 indoc! {"
13098 function test() {
13099 console.logˇ('test')
13100 }"},
13101 );
13102}
13103
13104#[gpui::test]
13105async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
13106 init_test(cx, |_| {});
13107
13108 let fs = FakeFs::new(cx.executor());
13109 fs.insert_tree(
13110 path!("/a"),
13111 json!({
13112 "main.rs": "fn main() { let a = 5; }",
13113 "other.rs": "// Test file",
13114 }),
13115 )
13116 .await;
13117 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13118
13119 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13120 language_registry.add(Arc::new(Language::new(
13121 LanguageConfig {
13122 name: "Rust".into(),
13123 matcher: LanguageMatcher {
13124 path_suffixes: vec!["rs".to_string()],
13125 ..Default::default()
13126 },
13127 brackets: BracketPairConfig {
13128 pairs: vec![BracketPair {
13129 start: "{".to_string(),
13130 end: "}".to_string(),
13131 close: true,
13132 surround: true,
13133 newline: true,
13134 }],
13135 disabled_scopes_by_bracket_ix: Vec::new(),
13136 },
13137 ..Default::default()
13138 },
13139 Some(tree_sitter_rust::LANGUAGE.into()),
13140 )));
13141 let mut fake_servers = language_registry.register_fake_lsp(
13142 "Rust",
13143 FakeLspAdapter {
13144 capabilities: lsp::ServerCapabilities {
13145 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
13146 first_trigger_character: "{".to_string(),
13147 more_trigger_character: None,
13148 }),
13149 ..Default::default()
13150 },
13151 ..Default::default()
13152 },
13153 );
13154
13155 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13156
13157 let cx = &mut VisualTestContext::from_window(*workspace, cx);
13158
13159 let worktree_id = workspace
13160 .update(cx, |workspace, _, cx| {
13161 workspace.project().update(cx, |project, cx| {
13162 project.worktrees(cx).next().unwrap().read(cx).id()
13163 })
13164 })
13165 .unwrap();
13166
13167 let buffer = project
13168 .update(cx, |project, cx| {
13169 project.open_local_buffer(path!("/a/main.rs"), cx)
13170 })
13171 .await
13172 .unwrap();
13173 let editor_handle = workspace
13174 .update(cx, |workspace, window, cx| {
13175 workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
13176 })
13177 .unwrap()
13178 .await
13179 .unwrap()
13180 .downcast::<Editor>()
13181 .unwrap();
13182
13183 cx.executor().start_waiting();
13184 let fake_server = fake_servers.next().await.unwrap();
13185
13186 fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
13187 |params, _| async move {
13188 assert_eq!(
13189 params.text_document_position.text_document.uri,
13190 lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
13191 );
13192 assert_eq!(
13193 params.text_document_position.position,
13194 lsp::Position::new(0, 21),
13195 );
13196
13197 Ok(Some(vec![lsp::TextEdit {
13198 new_text: "]".to_string(),
13199 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
13200 }]))
13201 },
13202 );
13203
13204 editor_handle.update_in(cx, |editor, window, cx| {
13205 window.focus(&editor.focus_handle(cx));
13206 editor.change_selections(None, window, cx, |s| {
13207 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
13208 });
13209 editor.handle_input("{", window, cx);
13210 });
13211
13212 cx.executor().run_until_parked();
13213
13214 buffer.update(cx, |buffer, _| {
13215 assert_eq!(
13216 buffer.text(),
13217 "fn main() { let a = {5}; }",
13218 "No extra braces from on type formatting should appear in the buffer"
13219 )
13220 });
13221}
13222
13223#[gpui::test]
13224async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
13225 init_test(cx, |_| {});
13226
13227 let fs = FakeFs::new(cx.executor());
13228 fs.insert_tree(
13229 path!("/a"),
13230 json!({
13231 "main.rs": "fn main() { let a = 5; }",
13232 "other.rs": "// Test file",
13233 }),
13234 )
13235 .await;
13236
13237 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13238
13239 let server_restarts = Arc::new(AtomicUsize::new(0));
13240 let closure_restarts = Arc::clone(&server_restarts);
13241 let language_server_name = "test language server";
13242 let language_name: LanguageName = "Rust".into();
13243
13244 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13245 language_registry.add(Arc::new(Language::new(
13246 LanguageConfig {
13247 name: language_name.clone(),
13248 matcher: LanguageMatcher {
13249 path_suffixes: vec!["rs".to_string()],
13250 ..Default::default()
13251 },
13252 ..Default::default()
13253 },
13254 Some(tree_sitter_rust::LANGUAGE.into()),
13255 )));
13256 let mut fake_servers = language_registry.register_fake_lsp(
13257 "Rust",
13258 FakeLspAdapter {
13259 name: language_server_name,
13260 initialization_options: Some(json!({
13261 "testOptionValue": true
13262 })),
13263 initializer: Some(Box::new(move |fake_server| {
13264 let task_restarts = Arc::clone(&closure_restarts);
13265 fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
13266 task_restarts.fetch_add(1, atomic::Ordering::Release);
13267 futures::future::ready(Ok(()))
13268 });
13269 })),
13270 ..Default::default()
13271 },
13272 );
13273
13274 let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13275 let _buffer = project
13276 .update(cx, |project, cx| {
13277 project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
13278 })
13279 .await
13280 .unwrap();
13281 let _fake_server = fake_servers.next().await.unwrap();
13282 update_test_language_settings(cx, |language_settings| {
13283 language_settings.languages.insert(
13284 language_name.clone(),
13285 LanguageSettingsContent {
13286 tab_size: NonZeroU32::new(8),
13287 ..Default::default()
13288 },
13289 );
13290 });
13291 cx.executor().run_until_parked();
13292 assert_eq!(
13293 server_restarts.load(atomic::Ordering::Acquire),
13294 0,
13295 "Should not restart LSP server on an unrelated change"
13296 );
13297
13298 update_test_project_settings(cx, |project_settings| {
13299 project_settings.lsp.insert(
13300 "Some other server name".into(),
13301 LspSettings {
13302 binary: None,
13303 settings: None,
13304 initialization_options: Some(json!({
13305 "some other init value": false
13306 })),
13307 enable_lsp_tasks: false,
13308 },
13309 );
13310 });
13311 cx.executor().run_until_parked();
13312 assert_eq!(
13313 server_restarts.load(atomic::Ordering::Acquire),
13314 0,
13315 "Should not restart LSP server on an unrelated LSP settings change"
13316 );
13317
13318 update_test_project_settings(cx, |project_settings| {
13319 project_settings.lsp.insert(
13320 language_server_name.into(),
13321 LspSettings {
13322 binary: None,
13323 settings: None,
13324 initialization_options: Some(json!({
13325 "anotherInitValue": false
13326 })),
13327 enable_lsp_tasks: false,
13328 },
13329 );
13330 });
13331 cx.executor().run_until_parked();
13332 assert_eq!(
13333 server_restarts.load(atomic::Ordering::Acquire),
13334 1,
13335 "Should restart LSP server on a related LSP settings change"
13336 );
13337
13338 update_test_project_settings(cx, |project_settings| {
13339 project_settings.lsp.insert(
13340 language_server_name.into(),
13341 LspSettings {
13342 binary: None,
13343 settings: None,
13344 initialization_options: Some(json!({
13345 "anotherInitValue": false
13346 })),
13347 enable_lsp_tasks: false,
13348 },
13349 );
13350 });
13351 cx.executor().run_until_parked();
13352 assert_eq!(
13353 server_restarts.load(atomic::Ordering::Acquire),
13354 1,
13355 "Should not restart LSP server on a related LSP settings change that is the same"
13356 );
13357
13358 update_test_project_settings(cx, |project_settings| {
13359 project_settings.lsp.insert(
13360 language_server_name.into(),
13361 LspSettings {
13362 binary: None,
13363 settings: None,
13364 initialization_options: None,
13365 enable_lsp_tasks: false,
13366 },
13367 );
13368 });
13369 cx.executor().run_until_parked();
13370 assert_eq!(
13371 server_restarts.load(atomic::Ordering::Acquire),
13372 2,
13373 "Should restart LSP server on another related LSP settings change"
13374 );
13375}
13376
13377#[gpui::test]
13378async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
13379 init_test(cx, |_| {});
13380
13381 let mut cx = EditorLspTestContext::new_rust(
13382 lsp::ServerCapabilities {
13383 completion_provider: Some(lsp::CompletionOptions {
13384 trigger_characters: Some(vec![".".to_string()]),
13385 resolve_provider: Some(true),
13386 ..Default::default()
13387 }),
13388 ..Default::default()
13389 },
13390 cx,
13391 )
13392 .await;
13393
13394 cx.set_state("fn main() { let a = 2ˇ; }");
13395 cx.simulate_keystroke(".");
13396 let completion_item = lsp::CompletionItem {
13397 label: "some".into(),
13398 kind: Some(lsp::CompletionItemKind::SNIPPET),
13399 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
13400 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
13401 kind: lsp::MarkupKind::Markdown,
13402 value: "```rust\nSome(2)\n```".to_string(),
13403 })),
13404 deprecated: Some(false),
13405 sort_text: Some("fffffff2".to_string()),
13406 filter_text: Some("some".to_string()),
13407 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
13408 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13409 range: lsp::Range {
13410 start: lsp::Position {
13411 line: 0,
13412 character: 22,
13413 },
13414 end: lsp::Position {
13415 line: 0,
13416 character: 22,
13417 },
13418 },
13419 new_text: "Some(2)".to_string(),
13420 })),
13421 additional_text_edits: Some(vec![lsp::TextEdit {
13422 range: lsp::Range {
13423 start: lsp::Position {
13424 line: 0,
13425 character: 20,
13426 },
13427 end: lsp::Position {
13428 line: 0,
13429 character: 22,
13430 },
13431 },
13432 new_text: "".to_string(),
13433 }]),
13434 ..Default::default()
13435 };
13436
13437 let closure_completion_item = completion_item.clone();
13438 let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
13439 let task_completion_item = closure_completion_item.clone();
13440 async move {
13441 Ok(Some(lsp::CompletionResponse::Array(vec![
13442 task_completion_item,
13443 ])))
13444 }
13445 });
13446
13447 request.next().await;
13448
13449 cx.condition(|editor, _| editor.context_menu_visible())
13450 .await;
13451 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13452 editor
13453 .confirm_completion(&ConfirmCompletion::default(), window, cx)
13454 .unwrap()
13455 });
13456 cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
13457
13458 cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
13459 let task_completion_item = completion_item.clone();
13460 async move { Ok(task_completion_item) }
13461 })
13462 .next()
13463 .await
13464 .unwrap();
13465 apply_additional_edits.await.unwrap();
13466 cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
13467}
13468
13469#[gpui::test]
13470async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
13471 init_test(cx, |_| {});
13472
13473 let mut cx = EditorLspTestContext::new_rust(
13474 lsp::ServerCapabilities {
13475 completion_provider: Some(lsp::CompletionOptions {
13476 trigger_characters: Some(vec![".".to_string()]),
13477 resolve_provider: Some(true),
13478 ..Default::default()
13479 }),
13480 ..Default::default()
13481 },
13482 cx,
13483 )
13484 .await;
13485
13486 cx.set_state("fn main() { let a = 2ˇ; }");
13487 cx.simulate_keystroke(".");
13488
13489 let item1 = lsp::CompletionItem {
13490 label: "method id()".to_string(),
13491 filter_text: Some("id".to_string()),
13492 detail: None,
13493 documentation: None,
13494 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13495 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
13496 new_text: ".id".to_string(),
13497 })),
13498 ..lsp::CompletionItem::default()
13499 };
13500
13501 let item2 = lsp::CompletionItem {
13502 label: "other".to_string(),
13503 filter_text: Some("other".to_string()),
13504 detail: None,
13505 documentation: None,
13506 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13507 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
13508 new_text: ".other".to_string(),
13509 })),
13510 ..lsp::CompletionItem::default()
13511 };
13512
13513 let item1 = item1.clone();
13514 cx.set_request_handler::<lsp::request::Completion, _, _>({
13515 let item1 = item1.clone();
13516 move |_, _, _| {
13517 let item1 = item1.clone();
13518 let item2 = item2.clone();
13519 async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
13520 }
13521 })
13522 .next()
13523 .await;
13524
13525 cx.condition(|editor, _| editor.context_menu_visible())
13526 .await;
13527 cx.update_editor(|editor, _, _| {
13528 let context_menu = editor.context_menu.borrow_mut();
13529 let context_menu = context_menu
13530 .as_ref()
13531 .expect("Should have the context menu deployed");
13532 match context_menu {
13533 CodeContextMenu::Completions(completions_menu) => {
13534 let completions = completions_menu.completions.borrow_mut();
13535 assert_eq!(
13536 completions
13537 .iter()
13538 .map(|completion| &completion.label.text)
13539 .collect::<Vec<_>>(),
13540 vec!["method id()", "other"]
13541 )
13542 }
13543 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
13544 }
13545 });
13546
13547 cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
13548 let item1 = item1.clone();
13549 move |_, item_to_resolve, _| {
13550 let item1 = item1.clone();
13551 async move {
13552 if item1 == item_to_resolve {
13553 Ok(lsp::CompletionItem {
13554 label: "method id()".to_string(),
13555 filter_text: Some("id".to_string()),
13556 detail: Some("Now resolved!".to_string()),
13557 documentation: Some(lsp::Documentation::String("Docs".to_string())),
13558 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13559 range: lsp::Range::new(
13560 lsp::Position::new(0, 22),
13561 lsp::Position::new(0, 22),
13562 ),
13563 new_text: ".id".to_string(),
13564 })),
13565 ..lsp::CompletionItem::default()
13566 })
13567 } else {
13568 Ok(item_to_resolve)
13569 }
13570 }
13571 }
13572 })
13573 .next()
13574 .await
13575 .unwrap();
13576 cx.run_until_parked();
13577
13578 cx.update_editor(|editor, window, cx| {
13579 editor.context_menu_next(&Default::default(), window, cx);
13580 });
13581
13582 cx.update_editor(|editor, _, _| {
13583 let context_menu = editor.context_menu.borrow_mut();
13584 let context_menu = context_menu
13585 .as_ref()
13586 .expect("Should have the context menu deployed");
13587 match context_menu {
13588 CodeContextMenu::Completions(completions_menu) => {
13589 let completions = completions_menu.completions.borrow_mut();
13590 assert_eq!(
13591 completions
13592 .iter()
13593 .map(|completion| &completion.label.text)
13594 .collect::<Vec<_>>(),
13595 vec!["method id() Now resolved!", "other"],
13596 "Should update first completion label, but not second as the filter text did not match."
13597 );
13598 }
13599 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
13600 }
13601 });
13602}
13603
13604#[gpui::test]
13605async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
13606 init_test(cx, |_| {});
13607
13608 let mut cx = EditorLspTestContext::new_rust(
13609 lsp::ServerCapabilities {
13610 completion_provider: Some(lsp::CompletionOptions {
13611 trigger_characters: Some(vec![".".to_string()]),
13612 resolve_provider: Some(true),
13613 ..Default::default()
13614 }),
13615 ..Default::default()
13616 },
13617 cx,
13618 )
13619 .await;
13620
13621 cx.set_state("fn main() { let a = 2ˇ; }");
13622 cx.simulate_keystroke(".");
13623
13624 let unresolved_item_1 = lsp::CompletionItem {
13625 label: "id".to_string(),
13626 filter_text: Some("id".to_string()),
13627 detail: None,
13628 documentation: None,
13629 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13630 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
13631 new_text: ".id".to_string(),
13632 })),
13633 ..lsp::CompletionItem::default()
13634 };
13635 let resolved_item_1 = lsp::CompletionItem {
13636 additional_text_edits: Some(vec![lsp::TextEdit {
13637 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
13638 new_text: "!!".to_string(),
13639 }]),
13640 ..unresolved_item_1.clone()
13641 };
13642 let unresolved_item_2 = lsp::CompletionItem {
13643 label: "other".to_string(),
13644 filter_text: Some("other".to_string()),
13645 detail: None,
13646 documentation: None,
13647 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13648 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
13649 new_text: ".other".to_string(),
13650 })),
13651 ..lsp::CompletionItem::default()
13652 };
13653 let resolved_item_2 = lsp::CompletionItem {
13654 additional_text_edits: Some(vec![lsp::TextEdit {
13655 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
13656 new_text: "??".to_string(),
13657 }]),
13658 ..unresolved_item_2.clone()
13659 };
13660
13661 let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
13662 let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
13663 cx.lsp
13664 .server
13665 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
13666 let unresolved_item_1 = unresolved_item_1.clone();
13667 let resolved_item_1 = resolved_item_1.clone();
13668 let unresolved_item_2 = unresolved_item_2.clone();
13669 let resolved_item_2 = resolved_item_2.clone();
13670 let resolve_requests_1 = resolve_requests_1.clone();
13671 let resolve_requests_2 = resolve_requests_2.clone();
13672 move |unresolved_request, _| {
13673 let unresolved_item_1 = unresolved_item_1.clone();
13674 let resolved_item_1 = resolved_item_1.clone();
13675 let unresolved_item_2 = unresolved_item_2.clone();
13676 let resolved_item_2 = resolved_item_2.clone();
13677 let resolve_requests_1 = resolve_requests_1.clone();
13678 let resolve_requests_2 = resolve_requests_2.clone();
13679 async move {
13680 if unresolved_request == unresolved_item_1 {
13681 resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
13682 Ok(resolved_item_1.clone())
13683 } else if unresolved_request == unresolved_item_2 {
13684 resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
13685 Ok(resolved_item_2.clone())
13686 } else {
13687 panic!("Unexpected completion item {unresolved_request:?}")
13688 }
13689 }
13690 }
13691 })
13692 .detach();
13693
13694 cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
13695 let unresolved_item_1 = unresolved_item_1.clone();
13696 let unresolved_item_2 = unresolved_item_2.clone();
13697 async move {
13698 Ok(Some(lsp::CompletionResponse::Array(vec![
13699 unresolved_item_1,
13700 unresolved_item_2,
13701 ])))
13702 }
13703 })
13704 .next()
13705 .await;
13706
13707 cx.condition(|editor, _| editor.context_menu_visible())
13708 .await;
13709 cx.update_editor(|editor, _, _| {
13710 let context_menu = editor.context_menu.borrow_mut();
13711 let context_menu = context_menu
13712 .as_ref()
13713 .expect("Should have the context menu deployed");
13714 match context_menu {
13715 CodeContextMenu::Completions(completions_menu) => {
13716 let completions = completions_menu.completions.borrow_mut();
13717 assert_eq!(
13718 completions
13719 .iter()
13720 .map(|completion| &completion.label.text)
13721 .collect::<Vec<_>>(),
13722 vec!["id", "other"]
13723 )
13724 }
13725 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
13726 }
13727 });
13728 cx.run_until_parked();
13729
13730 cx.update_editor(|editor, window, cx| {
13731 editor.context_menu_next(&ContextMenuNext, window, cx);
13732 });
13733 cx.run_until_parked();
13734 cx.update_editor(|editor, window, cx| {
13735 editor.context_menu_prev(&ContextMenuPrevious, window, cx);
13736 });
13737 cx.run_until_parked();
13738 cx.update_editor(|editor, window, cx| {
13739 editor.context_menu_next(&ContextMenuNext, window, cx);
13740 });
13741 cx.run_until_parked();
13742 cx.update_editor(|editor, window, cx| {
13743 editor
13744 .compose_completion(&ComposeCompletion::default(), window, cx)
13745 .expect("No task returned")
13746 })
13747 .await
13748 .expect("Completion failed");
13749 cx.run_until_parked();
13750
13751 cx.update_editor(|editor, _, cx| {
13752 assert_eq!(
13753 resolve_requests_1.load(atomic::Ordering::Acquire),
13754 1,
13755 "Should always resolve once despite multiple selections"
13756 );
13757 assert_eq!(
13758 resolve_requests_2.load(atomic::Ordering::Acquire),
13759 1,
13760 "Should always resolve once after multiple selections and applying the completion"
13761 );
13762 assert_eq!(
13763 editor.text(cx),
13764 "fn main() { let a = ??.other; }",
13765 "Should use resolved data when applying the completion"
13766 );
13767 });
13768}
13769
13770#[gpui::test]
13771async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
13772 init_test(cx, |_| {});
13773
13774 let item_0 = lsp::CompletionItem {
13775 label: "abs".into(),
13776 insert_text: Some("abs".into()),
13777 data: Some(json!({ "very": "special"})),
13778 insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
13779 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
13780 lsp::InsertReplaceEdit {
13781 new_text: "abs".to_string(),
13782 insert: lsp::Range::default(),
13783 replace: lsp::Range::default(),
13784 },
13785 )),
13786 ..lsp::CompletionItem::default()
13787 };
13788 let items = iter::once(item_0.clone())
13789 .chain((11..51).map(|i| lsp::CompletionItem {
13790 label: format!("item_{}", i),
13791 insert_text: Some(format!("item_{}", i)),
13792 insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
13793 ..lsp::CompletionItem::default()
13794 }))
13795 .collect::<Vec<_>>();
13796
13797 let default_commit_characters = vec!["?".to_string()];
13798 let default_data = json!({ "default": "data"});
13799 let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
13800 let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
13801 let default_edit_range = lsp::Range {
13802 start: lsp::Position {
13803 line: 0,
13804 character: 5,
13805 },
13806 end: lsp::Position {
13807 line: 0,
13808 character: 5,
13809 },
13810 };
13811
13812 let mut cx = EditorLspTestContext::new_rust(
13813 lsp::ServerCapabilities {
13814 completion_provider: Some(lsp::CompletionOptions {
13815 trigger_characters: Some(vec![".".to_string()]),
13816 resolve_provider: Some(true),
13817 ..Default::default()
13818 }),
13819 ..Default::default()
13820 },
13821 cx,
13822 )
13823 .await;
13824
13825 cx.set_state("fn main() { let a = 2ˇ; }");
13826 cx.simulate_keystroke(".");
13827
13828 let completion_data = default_data.clone();
13829 let completion_characters = default_commit_characters.clone();
13830 let completion_items = items.clone();
13831 cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
13832 let default_data = completion_data.clone();
13833 let default_commit_characters = completion_characters.clone();
13834 let items = completion_items.clone();
13835 async move {
13836 Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13837 items,
13838 item_defaults: Some(lsp::CompletionListItemDefaults {
13839 data: Some(default_data.clone()),
13840 commit_characters: Some(default_commit_characters.clone()),
13841 edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
13842 default_edit_range,
13843 )),
13844 insert_text_format: Some(default_insert_text_format),
13845 insert_text_mode: Some(default_insert_text_mode),
13846 }),
13847 ..lsp::CompletionList::default()
13848 })))
13849 }
13850 })
13851 .next()
13852 .await;
13853
13854 let resolved_items = Arc::new(Mutex::new(Vec::new()));
13855 cx.lsp
13856 .server
13857 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
13858 let closure_resolved_items = resolved_items.clone();
13859 move |item_to_resolve, _| {
13860 let closure_resolved_items = closure_resolved_items.clone();
13861 async move {
13862 closure_resolved_items.lock().push(item_to_resolve.clone());
13863 Ok(item_to_resolve)
13864 }
13865 }
13866 })
13867 .detach();
13868
13869 cx.condition(|editor, _| editor.context_menu_visible())
13870 .await;
13871 cx.run_until_parked();
13872 cx.update_editor(|editor, _, _| {
13873 let menu = editor.context_menu.borrow_mut();
13874 match menu.as_ref().expect("should have the completions menu") {
13875 CodeContextMenu::Completions(completions_menu) => {
13876 assert_eq!(
13877 completions_menu
13878 .entries
13879 .borrow()
13880 .iter()
13881 .map(|mat| mat.string.clone())
13882 .collect::<Vec<String>>(),
13883 items
13884 .iter()
13885 .map(|completion| completion.label.clone())
13886 .collect::<Vec<String>>()
13887 );
13888 }
13889 CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
13890 }
13891 });
13892 // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
13893 // with 4 from the end.
13894 assert_eq!(
13895 *resolved_items.lock(),
13896 [&items[0..16], &items[items.len() - 4..items.len()]]
13897 .concat()
13898 .iter()
13899 .cloned()
13900 .map(|mut item| {
13901 if item.data.is_none() {
13902 item.data = Some(default_data.clone());
13903 }
13904 item
13905 })
13906 .collect::<Vec<lsp::CompletionItem>>(),
13907 "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
13908 );
13909 resolved_items.lock().clear();
13910
13911 cx.update_editor(|editor, window, cx| {
13912 editor.context_menu_prev(&ContextMenuPrevious, window, cx);
13913 });
13914 cx.run_until_parked();
13915 // Completions that have already been resolved are skipped.
13916 assert_eq!(
13917 *resolved_items.lock(),
13918 items[items.len() - 16..items.len() - 4]
13919 .iter()
13920 .cloned()
13921 .map(|mut item| {
13922 if item.data.is_none() {
13923 item.data = Some(default_data.clone());
13924 }
13925 item
13926 })
13927 .collect::<Vec<lsp::CompletionItem>>()
13928 );
13929 resolved_items.lock().clear();
13930}
13931
13932#[gpui::test]
13933async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
13934 init_test(cx, |_| {});
13935
13936 let mut cx = EditorLspTestContext::new(
13937 Language::new(
13938 LanguageConfig {
13939 matcher: LanguageMatcher {
13940 path_suffixes: vec!["jsx".into()],
13941 ..Default::default()
13942 },
13943 overrides: [(
13944 "element".into(),
13945 LanguageConfigOverride {
13946 completion_query_characters: Override::Set(['-'].into_iter().collect()),
13947 ..Default::default()
13948 },
13949 )]
13950 .into_iter()
13951 .collect(),
13952 ..Default::default()
13953 },
13954 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
13955 )
13956 .with_override_query("(jsx_self_closing_element) @element")
13957 .unwrap(),
13958 lsp::ServerCapabilities {
13959 completion_provider: Some(lsp::CompletionOptions {
13960 trigger_characters: Some(vec![":".to_string()]),
13961 ..Default::default()
13962 }),
13963 ..Default::default()
13964 },
13965 cx,
13966 )
13967 .await;
13968
13969 cx.lsp
13970 .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13971 Ok(Some(lsp::CompletionResponse::Array(vec![
13972 lsp::CompletionItem {
13973 label: "bg-blue".into(),
13974 ..Default::default()
13975 },
13976 lsp::CompletionItem {
13977 label: "bg-red".into(),
13978 ..Default::default()
13979 },
13980 lsp::CompletionItem {
13981 label: "bg-yellow".into(),
13982 ..Default::default()
13983 },
13984 ])))
13985 });
13986
13987 cx.set_state(r#"<p class="bgˇ" />"#);
13988
13989 // Trigger completion when typing a dash, because the dash is an extra
13990 // word character in the 'element' scope, which contains the cursor.
13991 cx.simulate_keystroke("-");
13992 cx.executor().run_until_parked();
13993 cx.update_editor(|editor, _, _| {
13994 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13995 {
13996 assert_eq!(
13997 completion_menu_entries(&menu),
13998 &["bg-blue", "bg-red", "bg-yellow"]
13999 );
14000 } else {
14001 panic!("expected completion menu to be open");
14002 }
14003 });
14004
14005 cx.simulate_keystroke("l");
14006 cx.executor().run_until_parked();
14007 cx.update_editor(|editor, _, _| {
14008 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14009 {
14010 assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
14011 } else {
14012 panic!("expected completion menu to be open");
14013 }
14014 });
14015
14016 // When filtering completions, consider the character after the '-' to
14017 // be the start of a subword.
14018 cx.set_state(r#"<p class="yelˇ" />"#);
14019 cx.simulate_keystroke("l");
14020 cx.executor().run_until_parked();
14021 cx.update_editor(|editor, _, _| {
14022 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14023 {
14024 assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
14025 } else {
14026 panic!("expected completion menu to be open");
14027 }
14028 });
14029}
14030
14031fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
14032 let entries = menu.entries.borrow();
14033 entries.iter().map(|mat| mat.string.clone()).collect()
14034}
14035
14036#[gpui::test]
14037async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
14038 init_test(cx, |settings| {
14039 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
14040 FormatterList(vec![Formatter::Prettier].into()),
14041 ))
14042 });
14043
14044 let fs = FakeFs::new(cx.executor());
14045 fs.insert_file(path!("/file.ts"), Default::default()).await;
14046
14047 let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
14048 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14049
14050 language_registry.add(Arc::new(Language::new(
14051 LanguageConfig {
14052 name: "TypeScript".into(),
14053 matcher: LanguageMatcher {
14054 path_suffixes: vec!["ts".to_string()],
14055 ..Default::default()
14056 },
14057 ..Default::default()
14058 },
14059 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
14060 )));
14061 update_test_language_settings(cx, |settings| {
14062 settings.defaults.prettier = Some(PrettierSettings {
14063 allowed: true,
14064 ..PrettierSettings::default()
14065 });
14066 });
14067
14068 let test_plugin = "test_plugin";
14069 let _ = language_registry.register_fake_lsp(
14070 "TypeScript",
14071 FakeLspAdapter {
14072 prettier_plugins: vec![test_plugin],
14073 ..Default::default()
14074 },
14075 );
14076
14077 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
14078 let buffer = project
14079 .update(cx, |project, cx| {
14080 project.open_local_buffer(path!("/file.ts"), cx)
14081 })
14082 .await
14083 .unwrap();
14084
14085 let buffer_text = "one\ntwo\nthree\n";
14086 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
14087 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
14088 editor.update_in(cx, |editor, window, cx| {
14089 editor.set_text(buffer_text, window, cx)
14090 });
14091
14092 editor
14093 .update_in(cx, |editor, window, cx| {
14094 editor.perform_format(
14095 project.clone(),
14096 FormatTrigger::Manual,
14097 FormatTarget::Buffers,
14098 window,
14099 cx,
14100 )
14101 })
14102 .unwrap()
14103 .await;
14104 assert_eq!(
14105 editor.update(cx, |editor, cx| editor.text(cx)),
14106 buffer_text.to_string() + prettier_format_suffix,
14107 "Test prettier formatting was not applied to the original buffer text",
14108 );
14109
14110 update_test_language_settings(cx, |settings| {
14111 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
14112 });
14113 let format = editor.update_in(cx, |editor, window, cx| {
14114 editor.perform_format(
14115 project.clone(),
14116 FormatTrigger::Manual,
14117 FormatTarget::Buffers,
14118 window,
14119 cx,
14120 )
14121 });
14122 format.await.unwrap();
14123 assert_eq!(
14124 editor.update(cx, |editor, cx| editor.text(cx)),
14125 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
14126 "Autoformatting (via test prettier) was not applied to the original buffer text",
14127 );
14128}
14129
14130#[gpui::test]
14131async fn test_addition_reverts(cx: &mut TestAppContext) {
14132 init_test(cx, |_| {});
14133 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14134 let base_text = indoc! {r#"
14135 struct Row;
14136 struct Row1;
14137 struct Row2;
14138
14139 struct Row4;
14140 struct Row5;
14141 struct Row6;
14142
14143 struct Row8;
14144 struct Row9;
14145 struct Row10;"#};
14146
14147 // When addition hunks are not adjacent to carets, no hunk revert is performed
14148 assert_hunk_revert(
14149 indoc! {r#"struct Row;
14150 struct Row1;
14151 struct Row1.1;
14152 struct Row1.2;
14153 struct Row2;ˇ
14154
14155 struct Row4;
14156 struct Row5;
14157 struct Row6;
14158
14159 struct Row8;
14160 ˇstruct Row9;
14161 struct Row9.1;
14162 struct Row9.2;
14163 struct Row9.3;
14164 struct Row10;"#},
14165 vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
14166 indoc! {r#"struct Row;
14167 struct Row1;
14168 struct Row1.1;
14169 struct Row1.2;
14170 struct Row2;ˇ
14171
14172 struct Row4;
14173 struct Row5;
14174 struct Row6;
14175
14176 struct Row8;
14177 ˇstruct Row9;
14178 struct Row9.1;
14179 struct Row9.2;
14180 struct Row9.3;
14181 struct Row10;"#},
14182 base_text,
14183 &mut cx,
14184 );
14185 // Same for selections
14186 assert_hunk_revert(
14187 indoc! {r#"struct Row;
14188 struct Row1;
14189 struct Row2;
14190 struct Row2.1;
14191 struct Row2.2;
14192 «ˇ
14193 struct Row4;
14194 struct» Row5;
14195 «struct Row6;
14196 ˇ»
14197 struct Row9.1;
14198 struct Row9.2;
14199 struct Row9.3;
14200 struct Row8;
14201 struct Row9;
14202 struct Row10;"#},
14203 vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
14204 indoc! {r#"struct Row;
14205 struct Row1;
14206 struct Row2;
14207 struct Row2.1;
14208 struct Row2.2;
14209 «ˇ
14210 struct Row4;
14211 struct» Row5;
14212 «struct Row6;
14213 ˇ»
14214 struct Row9.1;
14215 struct Row9.2;
14216 struct Row9.3;
14217 struct Row8;
14218 struct Row9;
14219 struct Row10;"#},
14220 base_text,
14221 &mut cx,
14222 );
14223
14224 // When carets and selections intersect the addition hunks, those are reverted.
14225 // Adjacent carets got merged.
14226 assert_hunk_revert(
14227 indoc! {r#"struct Row;
14228 ˇ// something on the top
14229 struct Row1;
14230 struct Row2;
14231 struct Roˇw3.1;
14232 struct Row2.2;
14233 struct Row2.3;ˇ
14234
14235 struct Row4;
14236 struct ˇRow5.1;
14237 struct Row5.2;
14238 struct «Rowˇ»5.3;
14239 struct Row5;
14240 struct Row6;
14241 ˇ
14242 struct Row9.1;
14243 struct «Rowˇ»9.2;
14244 struct «ˇRow»9.3;
14245 struct Row8;
14246 struct Row9;
14247 «ˇ// something on bottom»
14248 struct Row10;"#},
14249 vec![
14250 DiffHunkStatusKind::Added,
14251 DiffHunkStatusKind::Added,
14252 DiffHunkStatusKind::Added,
14253 DiffHunkStatusKind::Added,
14254 DiffHunkStatusKind::Added,
14255 ],
14256 indoc! {r#"struct Row;
14257 ˇstruct Row1;
14258 struct Row2;
14259 ˇ
14260 struct Row4;
14261 ˇstruct Row5;
14262 struct Row6;
14263 ˇ
14264 ˇstruct Row8;
14265 struct Row9;
14266 ˇstruct Row10;"#},
14267 base_text,
14268 &mut cx,
14269 );
14270}
14271
14272#[gpui::test]
14273async fn test_modification_reverts(cx: &mut TestAppContext) {
14274 init_test(cx, |_| {});
14275 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14276 let base_text = indoc! {r#"
14277 struct Row;
14278 struct Row1;
14279 struct Row2;
14280
14281 struct Row4;
14282 struct Row5;
14283 struct Row6;
14284
14285 struct Row8;
14286 struct Row9;
14287 struct Row10;"#};
14288
14289 // Modification hunks behave the same as the addition ones.
14290 assert_hunk_revert(
14291 indoc! {r#"struct Row;
14292 struct Row1;
14293 struct Row33;
14294 ˇ
14295 struct Row4;
14296 struct Row5;
14297 struct Row6;
14298 ˇ
14299 struct Row99;
14300 struct Row9;
14301 struct Row10;"#},
14302 vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
14303 indoc! {r#"struct Row;
14304 struct Row1;
14305 struct Row33;
14306 ˇ
14307 struct Row4;
14308 struct Row5;
14309 struct Row6;
14310 ˇ
14311 struct Row99;
14312 struct Row9;
14313 struct Row10;"#},
14314 base_text,
14315 &mut cx,
14316 );
14317 assert_hunk_revert(
14318 indoc! {r#"struct Row;
14319 struct Row1;
14320 struct Row33;
14321 «ˇ
14322 struct Row4;
14323 struct» Row5;
14324 «struct Row6;
14325 ˇ»
14326 struct Row99;
14327 struct Row9;
14328 struct Row10;"#},
14329 vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
14330 indoc! {r#"struct Row;
14331 struct Row1;
14332 struct Row33;
14333 «ˇ
14334 struct Row4;
14335 struct» Row5;
14336 «struct Row6;
14337 ˇ»
14338 struct Row99;
14339 struct Row9;
14340 struct Row10;"#},
14341 base_text,
14342 &mut cx,
14343 );
14344
14345 assert_hunk_revert(
14346 indoc! {r#"ˇstruct Row1.1;
14347 struct Row1;
14348 «ˇstr»uct Row22;
14349
14350 struct ˇRow44;
14351 struct Row5;
14352 struct «Rˇ»ow66;ˇ
14353
14354 «struˇ»ct Row88;
14355 struct Row9;
14356 struct Row1011;ˇ"#},
14357 vec![
14358 DiffHunkStatusKind::Modified,
14359 DiffHunkStatusKind::Modified,
14360 DiffHunkStatusKind::Modified,
14361 DiffHunkStatusKind::Modified,
14362 DiffHunkStatusKind::Modified,
14363 DiffHunkStatusKind::Modified,
14364 ],
14365 indoc! {r#"struct Row;
14366 ˇstruct Row1;
14367 struct Row2;
14368 ˇ
14369 struct Row4;
14370 ˇstruct Row5;
14371 struct Row6;
14372 ˇ
14373 struct Row8;
14374 ˇstruct Row9;
14375 struct Row10;ˇ"#},
14376 base_text,
14377 &mut cx,
14378 );
14379}
14380
14381#[gpui::test]
14382async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
14383 init_test(cx, |_| {});
14384 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14385 let base_text = indoc! {r#"
14386 one
14387
14388 two
14389 three
14390 "#};
14391
14392 cx.set_head_text(base_text);
14393 cx.set_state("\nˇ\n");
14394 cx.executor().run_until_parked();
14395 cx.update_editor(|editor, _window, cx| {
14396 editor.expand_selected_diff_hunks(cx);
14397 });
14398 cx.executor().run_until_parked();
14399 cx.update_editor(|editor, window, cx| {
14400 editor.backspace(&Default::default(), window, cx);
14401 });
14402 cx.run_until_parked();
14403 cx.assert_state_with_diff(
14404 indoc! {r#"
14405
14406 - two
14407 - threeˇ
14408 +
14409 "#}
14410 .to_string(),
14411 );
14412}
14413
14414#[gpui::test]
14415async fn test_deletion_reverts(cx: &mut TestAppContext) {
14416 init_test(cx, |_| {});
14417 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14418 let base_text = indoc! {r#"struct Row;
14419struct Row1;
14420struct Row2;
14421
14422struct Row4;
14423struct Row5;
14424struct Row6;
14425
14426struct Row8;
14427struct Row9;
14428struct Row10;"#};
14429
14430 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
14431 assert_hunk_revert(
14432 indoc! {r#"struct Row;
14433 struct Row2;
14434
14435 ˇstruct Row4;
14436 struct Row5;
14437 struct Row6;
14438 ˇ
14439 struct Row8;
14440 struct Row10;"#},
14441 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
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 base_text,
14452 &mut cx,
14453 );
14454 assert_hunk_revert(
14455 indoc! {r#"struct Row;
14456 struct Row2;
14457
14458 «ˇstruct Row4;
14459 struct» Row5;
14460 «struct Row6;
14461 ˇ»
14462 struct Row8;
14463 struct Row10;"#},
14464 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
14465 indoc! {r#"struct Row;
14466 struct Row2;
14467
14468 «ˇstruct Row4;
14469 struct» Row5;
14470 «struct Row6;
14471 ˇ»
14472 struct Row8;
14473 struct Row10;"#},
14474 base_text,
14475 &mut cx,
14476 );
14477
14478 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
14479 assert_hunk_revert(
14480 indoc! {r#"struct Row;
14481 ˇstruct Row2;
14482
14483 struct Row4;
14484 struct Row5;
14485 struct Row6;
14486
14487 struct Row8;ˇ
14488 struct Row10;"#},
14489 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
14490 indoc! {r#"struct Row;
14491 struct Row1;
14492 ˇstruct Row2;
14493
14494 struct Row4;
14495 struct Row5;
14496 struct Row6;
14497
14498 struct Row8;ˇ
14499 struct Row9;
14500 struct Row10;"#},
14501 base_text,
14502 &mut cx,
14503 );
14504 assert_hunk_revert(
14505 indoc! {r#"struct Row;
14506 struct Row2«ˇ;
14507 struct Row4;
14508 struct» Row5;
14509 «struct Row6;
14510
14511 struct Row8;ˇ»
14512 struct Row10;"#},
14513 vec![
14514 DiffHunkStatusKind::Deleted,
14515 DiffHunkStatusKind::Deleted,
14516 DiffHunkStatusKind::Deleted,
14517 ],
14518 indoc! {r#"struct Row;
14519 struct Row1;
14520 struct Row2«ˇ;
14521
14522 struct Row4;
14523 struct» Row5;
14524 «struct Row6;
14525
14526 struct Row8;ˇ»
14527 struct Row9;
14528 struct Row10;"#},
14529 base_text,
14530 &mut cx,
14531 );
14532}
14533
14534#[gpui::test]
14535async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
14536 init_test(cx, |_| {});
14537
14538 let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
14539 let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
14540 let base_text_3 =
14541 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
14542
14543 let text_1 = edit_first_char_of_every_line(base_text_1);
14544 let text_2 = edit_first_char_of_every_line(base_text_2);
14545 let text_3 = edit_first_char_of_every_line(base_text_3);
14546
14547 let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
14548 let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
14549 let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
14550
14551 let multibuffer = cx.new(|cx| {
14552 let mut multibuffer = MultiBuffer::new(ReadWrite);
14553 multibuffer.push_excerpts(
14554 buffer_1.clone(),
14555 [
14556 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14557 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14558 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
14559 ],
14560 cx,
14561 );
14562 multibuffer.push_excerpts(
14563 buffer_2.clone(),
14564 [
14565 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14566 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14567 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
14568 ],
14569 cx,
14570 );
14571 multibuffer.push_excerpts(
14572 buffer_3.clone(),
14573 [
14574 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14575 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14576 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
14577 ],
14578 cx,
14579 );
14580 multibuffer
14581 });
14582
14583 let fs = FakeFs::new(cx.executor());
14584 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
14585 let (editor, cx) = cx
14586 .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
14587 editor.update_in(cx, |editor, _window, cx| {
14588 for (buffer, diff_base) in [
14589 (buffer_1.clone(), base_text_1),
14590 (buffer_2.clone(), base_text_2),
14591 (buffer_3.clone(), base_text_3),
14592 ] {
14593 let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
14594 editor
14595 .buffer
14596 .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
14597 }
14598 });
14599 cx.executor().run_until_parked();
14600
14601 editor.update_in(cx, |editor, window, cx| {
14602 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}");
14603 editor.select_all(&SelectAll, window, cx);
14604 editor.git_restore(&Default::default(), window, cx);
14605 });
14606 cx.executor().run_until_parked();
14607
14608 // When all ranges are selected, all buffer hunks are reverted.
14609 editor.update(cx, |editor, cx| {
14610 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");
14611 });
14612 buffer_1.update(cx, |buffer, _| {
14613 assert_eq!(buffer.text(), base_text_1);
14614 });
14615 buffer_2.update(cx, |buffer, _| {
14616 assert_eq!(buffer.text(), base_text_2);
14617 });
14618 buffer_3.update(cx, |buffer, _| {
14619 assert_eq!(buffer.text(), base_text_3);
14620 });
14621
14622 editor.update_in(cx, |editor, window, cx| {
14623 editor.undo(&Default::default(), window, cx);
14624 });
14625
14626 editor.update_in(cx, |editor, window, cx| {
14627 editor.change_selections(None, window, cx, |s| {
14628 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
14629 });
14630 editor.git_restore(&Default::default(), window, cx);
14631 });
14632
14633 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
14634 // but not affect buffer_2 and its related excerpts.
14635 editor.update(cx, |editor, cx| {
14636 assert_eq!(
14637 editor.text(cx),
14638 "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}"
14639 );
14640 });
14641 buffer_1.update(cx, |buffer, _| {
14642 assert_eq!(buffer.text(), base_text_1);
14643 });
14644 buffer_2.update(cx, |buffer, _| {
14645 assert_eq!(
14646 buffer.text(),
14647 "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
14648 );
14649 });
14650 buffer_3.update(cx, |buffer, _| {
14651 assert_eq!(
14652 buffer.text(),
14653 "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
14654 );
14655 });
14656
14657 fn edit_first_char_of_every_line(text: &str) -> String {
14658 text.split('\n')
14659 .map(|line| format!("X{}", &line[1..]))
14660 .collect::<Vec<_>>()
14661 .join("\n")
14662 }
14663}
14664
14665#[gpui::test]
14666async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
14667 init_test(cx, |_| {});
14668
14669 let cols = 4;
14670 let rows = 10;
14671 let sample_text_1 = sample_text(rows, cols, 'a');
14672 assert_eq!(
14673 sample_text_1,
14674 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
14675 );
14676 let sample_text_2 = sample_text(rows, cols, 'l');
14677 assert_eq!(
14678 sample_text_2,
14679 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
14680 );
14681 let sample_text_3 = sample_text(rows, cols, 'v');
14682 assert_eq!(
14683 sample_text_3,
14684 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
14685 );
14686
14687 let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
14688 let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
14689 let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
14690
14691 let multi_buffer = cx.new(|cx| {
14692 let mut multibuffer = MultiBuffer::new(ReadWrite);
14693 multibuffer.push_excerpts(
14694 buffer_1.clone(),
14695 [
14696 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14697 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14698 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
14699 ],
14700 cx,
14701 );
14702 multibuffer.push_excerpts(
14703 buffer_2.clone(),
14704 [
14705 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14706 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14707 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
14708 ],
14709 cx,
14710 );
14711 multibuffer.push_excerpts(
14712 buffer_3.clone(),
14713 [
14714 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14715 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14716 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
14717 ],
14718 cx,
14719 );
14720 multibuffer
14721 });
14722
14723 let fs = FakeFs::new(cx.executor());
14724 fs.insert_tree(
14725 "/a",
14726 json!({
14727 "main.rs": sample_text_1,
14728 "other.rs": sample_text_2,
14729 "lib.rs": sample_text_3,
14730 }),
14731 )
14732 .await;
14733 let project = Project::test(fs, ["/a".as_ref()], cx).await;
14734 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14735 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14736 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
14737 Editor::new(
14738 EditorMode::full(),
14739 multi_buffer,
14740 Some(project.clone()),
14741 window,
14742 cx,
14743 )
14744 });
14745 let multibuffer_item_id = workspace
14746 .update(cx, |workspace, window, cx| {
14747 assert!(
14748 workspace.active_item(cx).is_none(),
14749 "active item should be None before the first item is added"
14750 );
14751 workspace.add_item_to_active_pane(
14752 Box::new(multi_buffer_editor.clone()),
14753 None,
14754 true,
14755 window,
14756 cx,
14757 );
14758 let active_item = workspace
14759 .active_item(cx)
14760 .expect("should have an active item after adding the multi buffer");
14761 assert!(
14762 !active_item.is_singleton(cx),
14763 "A multi buffer was expected to active after adding"
14764 );
14765 active_item.item_id()
14766 })
14767 .unwrap();
14768 cx.executor().run_until_parked();
14769
14770 multi_buffer_editor.update_in(cx, |editor, window, cx| {
14771 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
14772 s.select_ranges(Some(1..2))
14773 });
14774 editor.open_excerpts(&OpenExcerpts, window, cx);
14775 });
14776 cx.executor().run_until_parked();
14777 let first_item_id = workspace
14778 .update(cx, |workspace, window, cx| {
14779 let active_item = workspace
14780 .active_item(cx)
14781 .expect("should have an active item after navigating into the 1st buffer");
14782 let first_item_id = active_item.item_id();
14783 assert_ne!(
14784 first_item_id, multibuffer_item_id,
14785 "Should navigate into the 1st buffer and activate it"
14786 );
14787 assert!(
14788 active_item.is_singleton(cx),
14789 "New active item should be a singleton buffer"
14790 );
14791 assert_eq!(
14792 active_item
14793 .act_as::<Editor>(cx)
14794 .expect("should have navigated into an editor for the 1st buffer")
14795 .read(cx)
14796 .text(cx),
14797 sample_text_1
14798 );
14799
14800 workspace
14801 .go_back(workspace.active_pane().downgrade(), window, cx)
14802 .detach_and_log_err(cx);
14803
14804 first_item_id
14805 })
14806 .unwrap();
14807 cx.executor().run_until_parked();
14808 workspace
14809 .update(cx, |workspace, _, cx| {
14810 let active_item = workspace
14811 .active_item(cx)
14812 .expect("should have an active item after navigating back");
14813 assert_eq!(
14814 active_item.item_id(),
14815 multibuffer_item_id,
14816 "Should navigate back to the multi buffer"
14817 );
14818 assert!(!active_item.is_singleton(cx));
14819 })
14820 .unwrap();
14821
14822 multi_buffer_editor.update_in(cx, |editor, window, cx| {
14823 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
14824 s.select_ranges(Some(39..40))
14825 });
14826 editor.open_excerpts(&OpenExcerpts, window, cx);
14827 });
14828 cx.executor().run_until_parked();
14829 let second_item_id = workspace
14830 .update(cx, |workspace, window, cx| {
14831 let active_item = workspace
14832 .active_item(cx)
14833 .expect("should have an active item after navigating into the 2nd buffer");
14834 let second_item_id = active_item.item_id();
14835 assert_ne!(
14836 second_item_id, multibuffer_item_id,
14837 "Should navigate away from the multibuffer"
14838 );
14839 assert_ne!(
14840 second_item_id, first_item_id,
14841 "Should navigate into the 2nd buffer and activate it"
14842 );
14843 assert!(
14844 active_item.is_singleton(cx),
14845 "New active item should be a singleton buffer"
14846 );
14847 assert_eq!(
14848 active_item
14849 .act_as::<Editor>(cx)
14850 .expect("should have navigated into an editor")
14851 .read(cx)
14852 .text(cx),
14853 sample_text_2
14854 );
14855
14856 workspace
14857 .go_back(workspace.active_pane().downgrade(), window, cx)
14858 .detach_and_log_err(cx);
14859
14860 second_item_id
14861 })
14862 .unwrap();
14863 cx.executor().run_until_parked();
14864 workspace
14865 .update(cx, |workspace, _, cx| {
14866 let active_item = workspace
14867 .active_item(cx)
14868 .expect("should have an active item after navigating back from the 2nd buffer");
14869 assert_eq!(
14870 active_item.item_id(),
14871 multibuffer_item_id,
14872 "Should navigate back from the 2nd buffer to the multi buffer"
14873 );
14874 assert!(!active_item.is_singleton(cx));
14875 })
14876 .unwrap();
14877
14878 multi_buffer_editor.update_in(cx, |editor, window, cx| {
14879 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
14880 s.select_ranges(Some(70..70))
14881 });
14882 editor.open_excerpts(&OpenExcerpts, window, cx);
14883 });
14884 cx.executor().run_until_parked();
14885 workspace
14886 .update(cx, |workspace, window, cx| {
14887 let active_item = workspace
14888 .active_item(cx)
14889 .expect("should have an active item after navigating into the 3rd buffer");
14890 let third_item_id = active_item.item_id();
14891 assert_ne!(
14892 third_item_id, multibuffer_item_id,
14893 "Should navigate into the 3rd buffer and activate it"
14894 );
14895 assert_ne!(third_item_id, first_item_id);
14896 assert_ne!(third_item_id, second_item_id);
14897 assert!(
14898 active_item.is_singleton(cx),
14899 "New active item should be a singleton buffer"
14900 );
14901 assert_eq!(
14902 active_item
14903 .act_as::<Editor>(cx)
14904 .expect("should have navigated into an editor")
14905 .read(cx)
14906 .text(cx),
14907 sample_text_3
14908 );
14909
14910 workspace
14911 .go_back(workspace.active_pane().downgrade(), window, cx)
14912 .detach_and_log_err(cx);
14913 })
14914 .unwrap();
14915 cx.executor().run_until_parked();
14916 workspace
14917 .update(cx, |workspace, _, cx| {
14918 let active_item = workspace
14919 .active_item(cx)
14920 .expect("should have an active item after navigating back from the 3rd buffer");
14921 assert_eq!(
14922 active_item.item_id(),
14923 multibuffer_item_id,
14924 "Should navigate back from the 3rd buffer to the multi buffer"
14925 );
14926 assert!(!active_item.is_singleton(cx));
14927 })
14928 .unwrap();
14929}
14930
14931#[gpui::test]
14932async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14933 init_test(cx, |_| {});
14934
14935 let mut cx = EditorTestContext::new(cx).await;
14936
14937 let diff_base = r#"
14938 use some::mod;
14939
14940 const A: u32 = 42;
14941
14942 fn main() {
14943 println!("hello");
14944
14945 println!("world");
14946 }
14947 "#
14948 .unindent();
14949
14950 cx.set_state(
14951 &r#"
14952 use some::modified;
14953
14954 ˇ
14955 fn main() {
14956 println!("hello there");
14957
14958 println!("around the");
14959 println!("world");
14960 }
14961 "#
14962 .unindent(),
14963 );
14964
14965 cx.set_head_text(&diff_base);
14966 executor.run_until_parked();
14967
14968 cx.update_editor(|editor, window, cx| {
14969 editor.go_to_next_hunk(&GoToHunk, window, cx);
14970 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
14971 });
14972 executor.run_until_parked();
14973 cx.assert_state_with_diff(
14974 r#"
14975 use some::modified;
14976
14977
14978 fn main() {
14979 - println!("hello");
14980 + ˇ println!("hello there");
14981
14982 println!("around the");
14983 println!("world");
14984 }
14985 "#
14986 .unindent(),
14987 );
14988
14989 cx.update_editor(|editor, window, cx| {
14990 for _ in 0..2 {
14991 editor.go_to_next_hunk(&GoToHunk, window, cx);
14992 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
14993 }
14994 });
14995 executor.run_until_parked();
14996 cx.assert_state_with_diff(
14997 r#"
14998 - use some::mod;
14999 + ˇuse some::modified;
15000
15001
15002 fn main() {
15003 - println!("hello");
15004 + println!("hello there");
15005
15006 + println!("around the");
15007 println!("world");
15008 }
15009 "#
15010 .unindent(),
15011 );
15012
15013 cx.update_editor(|editor, window, cx| {
15014 editor.go_to_next_hunk(&GoToHunk, window, cx);
15015 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
15016 });
15017 executor.run_until_parked();
15018 cx.assert_state_with_diff(
15019 r#"
15020 - use some::mod;
15021 + use some::modified;
15022
15023 - const A: u32 = 42;
15024 ˇ
15025 fn main() {
15026 - println!("hello");
15027 + println!("hello there");
15028
15029 + println!("around the");
15030 println!("world");
15031 }
15032 "#
15033 .unindent(),
15034 );
15035
15036 cx.update_editor(|editor, window, cx| {
15037 editor.cancel(&Cancel, window, cx);
15038 });
15039
15040 cx.assert_state_with_diff(
15041 r#"
15042 use some::modified;
15043
15044 ˇ
15045 fn main() {
15046 println!("hello there");
15047
15048 println!("around the");
15049 println!("world");
15050 }
15051 "#
15052 .unindent(),
15053 );
15054}
15055
15056#[gpui::test]
15057async fn test_diff_base_change_with_expanded_diff_hunks(
15058 executor: BackgroundExecutor,
15059 cx: &mut TestAppContext,
15060) {
15061 init_test(cx, |_| {});
15062
15063 let mut cx = EditorTestContext::new(cx).await;
15064
15065 let diff_base = r#"
15066 use some::mod1;
15067 use some::mod2;
15068
15069 const A: u32 = 42;
15070 const B: u32 = 42;
15071 const C: u32 = 42;
15072
15073 fn main() {
15074 println!("hello");
15075
15076 println!("world");
15077 }
15078 "#
15079 .unindent();
15080
15081 cx.set_state(
15082 &r#"
15083 use some::mod2;
15084
15085 const A: u32 = 42;
15086 const C: u32 = 42;
15087
15088 fn main(ˇ) {
15089 //println!("hello");
15090
15091 println!("world");
15092 //
15093 //
15094 }
15095 "#
15096 .unindent(),
15097 );
15098
15099 cx.set_head_text(&diff_base);
15100 executor.run_until_parked();
15101
15102 cx.update_editor(|editor, window, cx| {
15103 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15104 });
15105 executor.run_until_parked();
15106 cx.assert_state_with_diff(
15107 r#"
15108 - use some::mod1;
15109 use some::mod2;
15110
15111 const A: u32 = 42;
15112 - const B: u32 = 42;
15113 const C: u32 = 42;
15114
15115 fn main(ˇ) {
15116 - println!("hello");
15117 + //println!("hello");
15118
15119 println!("world");
15120 + //
15121 + //
15122 }
15123 "#
15124 .unindent(),
15125 );
15126
15127 cx.set_head_text("new diff base!");
15128 executor.run_until_parked();
15129 cx.assert_state_with_diff(
15130 r#"
15131 - new diff base!
15132 + use some::mod2;
15133 +
15134 + const A: u32 = 42;
15135 + const C: u32 = 42;
15136 +
15137 + fn main(ˇ) {
15138 + //println!("hello");
15139 +
15140 + println!("world");
15141 + //
15142 + //
15143 + }
15144 "#
15145 .unindent(),
15146 );
15147}
15148
15149#[gpui::test]
15150async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
15151 init_test(cx, |_| {});
15152
15153 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
15154 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
15155 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
15156 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
15157 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
15158 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
15159
15160 let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
15161 let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
15162 let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
15163
15164 let multi_buffer = cx.new(|cx| {
15165 let mut multibuffer = MultiBuffer::new(ReadWrite);
15166 multibuffer.push_excerpts(
15167 buffer_1.clone(),
15168 [
15169 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15170 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15171 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
15172 ],
15173 cx,
15174 );
15175 multibuffer.push_excerpts(
15176 buffer_2.clone(),
15177 [
15178 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15179 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15180 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
15181 ],
15182 cx,
15183 );
15184 multibuffer.push_excerpts(
15185 buffer_3.clone(),
15186 [
15187 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15188 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15189 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
15190 ],
15191 cx,
15192 );
15193 multibuffer
15194 });
15195
15196 let editor =
15197 cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
15198 editor
15199 .update(cx, |editor, _window, cx| {
15200 for (buffer, diff_base) in [
15201 (buffer_1.clone(), file_1_old),
15202 (buffer_2.clone(), file_2_old),
15203 (buffer_3.clone(), file_3_old),
15204 ] {
15205 let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
15206 editor
15207 .buffer
15208 .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
15209 }
15210 })
15211 .unwrap();
15212
15213 let mut cx = EditorTestContext::for_editor(editor, cx).await;
15214 cx.run_until_parked();
15215
15216 cx.assert_editor_state(
15217 &"
15218 ˇaaa
15219 ccc
15220 ddd
15221
15222 ggg
15223 hhh
15224
15225
15226 lll
15227 mmm
15228 NNN
15229
15230 qqq
15231 rrr
15232
15233 uuu
15234 111
15235 222
15236 333
15237
15238 666
15239 777
15240
15241 000
15242 !!!"
15243 .unindent(),
15244 );
15245
15246 cx.update_editor(|editor, window, cx| {
15247 editor.select_all(&SelectAll, window, cx);
15248 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
15249 });
15250 cx.executor().run_until_parked();
15251
15252 cx.assert_state_with_diff(
15253 "
15254 «aaa
15255 - bbb
15256 ccc
15257 ddd
15258
15259 ggg
15260 hhh
15261
15262
15263 lll
15264 mmm
15265 - nnn
15266 + NNN
15267
15268 qqq
15269 rrr
15270
15271 uuu
15272 111
15273 222
15274 333
15275
15276 + 666
15277 777
15278
15279 000
15280 !!!ˇ»"
15281 .unindent(),
15282 );
15283}
15284
15285#[gpui::test]
15286async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
15287 init_test(cx, |_| {});
15288
15289 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
15290 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
15291
15292 let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
15293 let multi_buffer = cx.new(|cx| {
15294 let mut multibuffer = MultiBuffer::new(ReadWrite);
15295 multibuffer.push_excerpts(
15296 buffer.clone(),
15297 [
15298 ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
15299 ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
15300 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
15301 ],
15302 cx,
15303 );
15304 multibuffer
15305 });
15306
15307 let editor =
15308 cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
15309 editor
15310 .update(cx, |editor, _window, cx| {
15311 let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
15312 editor
15313 .buffer
15314 .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
15315 })
15316 .unwrap();
15317
15318 let mut cx = EditorTestContext::for_editor(editor, cx).await;
15319 cx.run_until_parked();
15320
15321 cx.update_editor(|editor, window, cx| {
15322 editor.expand_all_diff_hunks(&Default::default(), window, cx)
15323 });
15324 cx.executor().run_until_parked();
15325
15326 // When the start of a hunk coincides with the start of its excerpt,
15327 // the hunk is expanded. When the start of a a hunk is earlier than
15328 // the start of its excerpt, the hunk is not expanded.
15329 cx.assert_state_with_diff(
15330 "
15331 ˇaaa
15332 - bbb
15333 + BBB
15334
15335 - ddd
15336 - eee
15337 + DDD
15338 + EEE
15339 fff
15340
15341 iii
15342 "
15343 .unindent(),
15344 );
15345}
15346
15347#[gpui::test]
15348async fn test_edits_around_expanded_insertion_hunks(
15349 executor: BackgroundExecutor,
15350 cx: &mut TestAppContext,
15351) {
15352 init_test(cx, |_| {});
15353
15354 let mut cx = EditorTestContext::new(cx).await;
15355
15356 let diff_base = r#"
15357 use some::mod1;
15358 use some::mod2;
15359
15360 const A: u32 = 42;
15361
15362 fn main() {
15363 println!("hello");
15364
15365 println!("world");
15366 }
15367 "#
15368 .unindent();
15369 executor.run_until_parked();
15370 cx.set_state(
15371 &r#"
15372 use some::mod1;
15373 use some::mod2;
15374
15375 const A: u32 = 42;
15376 const B: u32 = 42;
15377 const C: u32 = 42;
15378 ˇ
15379
15380 fn main() {
15381 println!("hello");
15382
15383 println!("world");
15384 }
15385 "#
15386 .unindent(),
15387 );
15388
15389 cx.set_head_text(&diff_base);
15390 executor.run_until_parked();
15391
15392 cx.update_editor(|editor, window, cx| {
15393 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15394 });
15395 executor.run_until_parked();
15396
15397 cx.assert_state_with_diff(
15398 r#"
15399 use some::mod1;
15400 use some::mod2;
15401
15402 const A: u32 = 42;
15403 + const B: u32 = 42;
15404 + const C: u32 = 42;
15405 + ˇ
15406
15407 fn main() {
15408 println!("hello");
15409
15410 println!("world");
15411 }
15412 "#
15413 .unindent(),
15414 );
15415
15416 cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
15417 executor.run_until_parked();
15418
15419 cx.assert_state_with_diff(
15420 r#"
15421 use some::mod1;
15422 use some::mod2;
15423
15424 const A: u32 = 42;
15425 + const B: u32 = 42;
15426 + const C: u32 = 42;
15427 + const D: u32 = 42;
15428 + ˇ
15429
15430 fn main() {
15431 println!("hello");
15432
15433 println!("world");
15434 }
15435 "#
15436 .unindent(),
15437 );
15438
15439 cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
15440 executor.run_until_parked();
15441
15442 cx.assert_state_with_diff(
15443 r#"
15444 use some::mod1;
15445 use some::mod2;
15446
15447 const A: u32 = 42;
15448 + const B: u32 = 42;
15449 + const C: u32 = 42;
15450 + const D: u32 = 42;
15451 + const E: u32 = 42;
15452 + ˇ
15453
15454 fn main() {
15455 println!("hello");
15456
15457 println!("world");
15458 }
15459 "#
15460 .unindent(),
15461 );
15462
15463 cx.update_editor(|editor, window, cx| {
15464 editor.delete_line(&DeleteLine, window, cx);
15465 });
15466 executor.run_until_parked();
15467
15468 cx.assert_state_with_diff(
15469 r#"
15470 use some::mod1;
15471 use some::mod2;
15472
15473 const A: u32 = 42;
15474 + const B: u32 = 42;
15475 + const C: u32 = 42;
15476 + const D: u32 = 42;
15477 + const E: u32 = 42;
15478 ˇ
15479 fn main() {
15480 println!("hello");
15481
15482 println!("world");
15483 }
15484 "#
15485 .unindent(),
15486 );
15487
15488 cx.update_editor(|editor, window, cx| {
15489 editor.move_up(&MoveUp, window, cx);
15490 editor.delete_line(&DeleteLine, window, cx);
15491 editor.move_up(&MoveUp, window, cx);
15492 editor.delete_line(&DeleteLine, window, cx);
15493 editor.move_up(&MoveUp, window, cx);
15494 editor.delete_line(&DeleteLine, window, cx);
15495 });
15496 executor.run_until_parked();
15497 cx.assert_state_with_diff(
15498 r#"
15499 use some::mod1;
15500 use some::mod2;
15501
15502 const A: u32 = 42;
15503 + const B: u32 = 42;
15504 ˇ
15505 fn main() {
15506 println!("hello");
15507
15508 println!("world");
15509 }
15510 "#
15511 .unindent(),
15512 );
15513
15514 cx.update_editor(|editor, window, cx| {
15515 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
15516 editor.delete_line(&DeleteLine, window, cx);
15517 });
15518 executor.run_until_parked();
15519 cx.assert_state_with_diff(
15520 r#"
15521 ˇ
15522 fn main() {
15523 println!("hello");
15524
15525 println!("world");
15526 }
15527 "#
15528 .unindent(),
15529 );
15530}
15531
15532#[gpui::test]
15533async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
15534 init_test(cx, |_| {});
15535
15536 let mut cx = EditorTestContext::new(cx).await;
15537 cx.set_head_text(indoc! { "
15538 one
15539 two
15540 three
15541 four
15542 five
15543 "
15544 });
15545 cx.set_state(indoc! { "
15546 one
15547 ˇthree
15548 five
15549 "});
15550 cx.run_until_parked();
15551 cx.update_editor(|editor, window, cx| {
15552 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
15553 });
15554 cx.assert_state_with_diff(
15555 indoc! { "
15556 one
15557 - two
15558 ˇthree
15559 - four
15560 five
15561 "}
15562 .to_string(),
15563 );
15564 cx.update_editor(|editor, window, cx| {
15565 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
15566 });
15567
15568 cx.assert_state_with_diff(
15569 indoc! { "
15570 one
15571 ˇthree
15572 five
15573 "}
15574 .to_string(),
15575 );
15576
15577 cx.set_state(indoc! { "
15578 one
15579 ˇTWO
15580 three
15581 four
15582 five
15583 "});
15584 cx.run_until_parked();
15585 cx.update_editor(|editor, window, cx| {
15586 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
15587 });
15588
15589 cx.assert_state_with_diff(
15590 indoc! { "
15591 one
15592 - two
15593 + ˇTWO
15594 three
15595 four
15596 five
15597 "}
15598 .to_string(),
15599 );
15600 cx.update_editor(|editor, window, cx| {
15601 editor.move_up(&Default::default(), window, cx);
15602 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
15603 });
15604 cx.assert_state_with_diff(
15605 indoc! { "
15606 one
15607 ˇTWO
15608 three
15609 four
15610 five
15611 "}
15612 .to_string(),
15613 );
15614}
15615
15616#[gpui::test]
15617async fn test_edits_around_expanded_deletion_hunks(
15618 executor: BackgroundExecutor,
15619 cx: &mut TestAppContext,
15620) {
15621 init_test(cx, |_| {});
15622
15623 let mut cx = EditorTestContext::new(cx).await;
15624
15625 let diff_base = r#"
15626 use some::mod1;
15627 use some::mod2;
15628
15629 const A: u32 = 42;
15630 const B: u32 = 42;
15631 const C: u32 = 42;
15632
15633
15634 fn main() {
15635 println!("hello");
15636
15637 println!("world");
15638 }
15639 "#
15640 .unindent();
15641 executor.run_until_parked();
15642 cx.set_state(
15643 &r#"
15644 use some::mod1;
15645 use some::mod2;
15646
15647 ˇconst B: u32 = 42;
15648 const C: u32 = 42;
15649
15650
15651 fn main() {
15652 println!("hello");
15653
15654 println!("world");
15655 }
15656 "#
15657 .unindent(),
15658 );
15659
15660 cx.set_head_text(&diff_base);
15661 executor.run_until_parked();
15662
15663 cx.update_editor(|editor, window, cx| {
15664 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15665 });
15666 executor.run_until_parked();
15667
15668 cx.assert_state_with_diff(
15669 r#"
15670 use some::mod1;
15671 use some::mod2;
15672
15673 - const A: u32 = 42;
15674 ˇconst B: u32 = 42;
15675 const C: u32 = 42;
15676
15677
15678 fn main() {
15679 println!("hello");
15680
15681 println!("world");
15682 }
15683 "#
15684 .unindent(),
15685 );
15686
15687 cx.update_editor(|editor, window, cx| {
15688 editor.delete_line(&DeleteLine, window, cx);
15689 });
15690 executor.run_until_parked();
15691 cx.assert_state_with_diff(
15692 r#"
15693 use some::mod1;
15694 use some::mod2;
15695
15696 - const A: u32 = 42;
15697 - const B: u32 = 42;
15698 ˇconst C: u32 = 42;
15699
15700
15701 fn main() {
15702 println!("hello");
15703
15704 println!("world");
15705 }
15706 "#
15707 .unindent(),
15708 );
15709
15710 cx.update_editor(|editor, window, cx| {
15711 editor.delete_line(&DeleteLine, window, cx);
15712 });
15713 executor.run_until_parked();
15714 cx.assert_state_with_diff(
15715 r#"
15716 use some::mod1;
15717 use some::mod2;
15718
15719 - const A: u32 = 42;
15720 - const B: u32 = 42;
15721 - const C: u32 = 42;
15722 ˇ
15723
15724 fn main() {
15725 println!("hello");
15726
15727 println!("world");
15728 }
15729 "#
15730 .unindent(),
15731 );
15732
15733 cx.update_editor(|editor, window, cx| {
15734 editor.handle_input("replacement", window, cx);
15735 });
15736 executor.run_until_parked();
15737 cx.assert_state_with_diff(
15738 r#"
15739 use some::mod1;
15740 use some::mod2;
15741
15742 - const A: u32 = 42;
15743 - const B: u32 = 42;
15744 - const C: u32 = 42;
15745 -
15746 + replacementˇ
15747
15748 fn main() {
15749 println!("hello");
15750
15751 println!("world");
15752 }
15753 "#
15754 .unindent(),
15755 );
15756}
15757
15758#[gpui::test]
15759async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15760 init_test(cx, |_| {});
15761
15762 let mut cx = EditorTestContext::new(cx).await;
15763
15764 let base_text = r#"
15765 one
15766 two
15767 three
15768 four
15769 five
15770 "#
15771 .unindent();
15772 executor.run_until_parked();
15773 cx.set_state(
15774 &r#"
15775 one
15776 two
15777 fˇour
15778 five
15779 "#
15780 .unindent(),
15781 );
15782
15783 cx.set_head_text(&base_text);
15784 executor.run_until_parked();
15785
15786 cx.update_editor(|editor, window, cx| {
15787 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15788 });
15789 executor.run_until_parked();
15790
15791 cx.assert_state_with_diff(
15792 r#"
15793 one
15794 two
15795 - three
15796 fˇour
15797 five
15798 "#
15799 .unindent(),
15800 );
15801
15802 cx.update_editor(|editor, window, cx| {
15803 editor.backspace(&Backspace, window, cx);
15804 editor.backspace(&Backspace, window, cx);
15805 });
15806 executor.run_until_parked();
15807 cx.assert_state_with_diff(
15808 r#"
15809 one
15810 two
15811 - threeˇ
15812 - four
15813 + our
15814 five
15815 "#
15816 .unindent(),
15817 );
15818}
15819
15820#[gpui::test]
15821async fn test_edit_after_expanded_modification_hunk(
15822 executor: BackgroundExecutor,
15823 cx: &mut TestAppContext,
15824) {
15825 init_test(cx, |_| {});
15826
15827 let mut cx = EditorTestContext::new(cx).await;
15828
15829 let diff_base = r#"
15830 use some::mod1;
15831 use some::mod2;
15832
15833 const A: u32 = 42;
15834 const B: u32 = 42;
15835 const C: u32 = 42;
15836 const D: u32 = 42;
15837
15838
15839 fn main() {
15840 println!("hello");
15841
15842 println!("world");
15843 }"#
15844 .unindent();
15845
15846 cx.set_state(
15847 &r#"
15848 use some::mod1;
15849 use some::mod2;
15850
15851 const A: u32 = 42;
15852 const B: u32 = 42;
15853 const C: u32 = 43ˇ
15854 const D: u32 = 42;
15855
15856
15857 fn main() {
15858 println!("hello");
15859
15860 println!("world");
15861 }"#
15862 .unindent(),
15863 );
15864
15865 cx.set_head_text(&diff_base);
15866 executor.run_until_parked();
15867 cx.update_editor(|editor, window, cx| {
15868 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15869 });
15870 executor.run_until_parked();
15871
15872 cx.assert_state_with_diff(
15873 r#"
15874 use some::mod1;
15875 use some::mod2;
15876
15877 const A: u32 = 42;
15878 const B: u32 = 42;
15879 - const C: u32 = 42;
15880 + const C: u32 = 43ˇ
15881 const D: u32 = 42;
15882
15883
15884 fn main() {
15885 println!("hello");
15886
15887 println!("world");
15888 }"#
15889 .unindent(),
15890 );
15891
15892 cx.update_editor(|editor, window, cx| {
15893 editor.handle_input("\nnew_line\n", window, cx);
15894 });
15895 executor.run_until_parked();
15896
15897 cx.assert_state_with_diff(
15898 r#"
15899 use some::mod1;
15900 use some::mod2;
15901
15902 const A: u32 = 42;
15903 const B: u32 = 42;
15904 - const C: u32 = 42;
15905 + const C: u32 = 43
15906 + new_line
15907 + ˇ
15908 const D: u32 = 42;
15909
15910
15911 fn main() {
15912 println!("hello");
15913
15914 println!("world");
15915 }"#
15916 .unindent(),
15917 );
15918}
15919
15920#[gpui::test]
15921async fn test_stage_and_unstage_added_file_hunk(
15922 executor: BackgroundExecutor,
15923 cx: &mut TestAppContext,
15924) {
15925 init_test(cx, |_| {});
15926
15927 let mut cx = EditorTestContext::new(cx).await;
15928 cx.update_editor(|editor, _, cx| {
15929 editor.set_expand_all_diff_hunks(cx);
15930 });
15931
15932 let working_copy = r#"
15933 ˇfn main() {
15934 println!("hello, world!");
15935 }
15936 "#
15937 .unindent();
15938
15939 cx.set_state(&working_copy);
15940 executor.run_until_parked();
15941
15942 cx.assert_state_with_diff(
15943 r#"
15944 + ˇfn main() {
15945 + println!("hello, world!");
15946 + }
15947 "#
15948 .unindent(),
15949 );
15950 cx.assert_index_text(None);
15951
15952 cx.update_editor(|editor, window, cx| {
15953 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
15954 });
15955 executor.run_until_parked();
15956 cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
15957 cx.assert_state_with_diff(
15958 r#"
15959 + ˇfn main() {
15960 + println!("hello, world!");
15961 + }
15962 "#
15963 .unindent(),
15964 );
15965
15966 cx.update_editor(|editor, window, cx| {
15967 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
15968 });
15969 executor.run_until_parked();
15970 cx.assert_index_text(None);
15971}
15972
15973async fn setup_indent_guides_editor(
15974 text: &str,
15975 cx: &mut TestAppContext,
15976) -> (BufferId, EditorTestContext) {
15977 init_test(cx, |_| {});
15978
15979 let mut cx = EditorTestContext::new(cx).await;
15980
15981 let buffer_id = cx.update_editor(|editor, window, cx| {
15982 editor.set_text(text, window, cx);
15983 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
15984
15985 buffer_ids[0]
15986 });
15987
15988 (buffer_id, cx)
15989}
15990
15991fn assert_indent_guides(
15992 range: Range<u32>,
15993 expected: Vec<IndentGuide>,
15994 active_indices: Option<Vec<usize>>,
15995 cx: &mut EditorTestContext,
15996) {
15997 let indent_guides = cx.update_editor(|editor, window, cx| {
15998 let snapshot = editor.snapshot(window, cx).display_snapshot;
15999 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
16000 editor,
16001 MultiBufferRow(range.start)..MultiBufferRow(range.end),
16002 true,
16003 &snapshot,
16004 cx,
16005 );
16006
16007 indent_guides.sort_by(|a, b| {
16008 a.depth.cmp(&b.depth).then(
16009 a.start_row
16010 .cmp(&b.start_row)
16011 .then(a.end_row.cmp(&b.end_row)),
16012 )
16013 });
16014 indent_guides
16015 });
16016
16017 if let Some(expected) = active_indices {
16018 let active_indices = cx.update_editor(|editor, window, cx| {
16019 let snapshot = editor.snapshot(window, cx).display_snapshot;
16020 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
16021 });
16022
16023 assert_eq!(
16024 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
16025 expected,
16026 "Active indent guide indices do not match"
16027 );
16028 }
16029
16030 assert_eq!(indent_guides, expected, "Indent guides do not match");
16031}
16032
16033fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
16034 IndentGuide {
16035 buffer_id,
16036 start_row: MultiBufferRow(start_row),
16037 end_row: MultiBufferRow(end_row),
16038 depth,
16039 tab_size: 4,
16040 settings: IndentGuideSettings {
16041 enabled: true,
16042 line_width: 1,
16043 active_line_width: 1,
16044 ..Default::default()
16045 },
16046 }
16047}
16048
16049#[gpui::test]
16050async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
16051 let (buffer_id, mut cx) = setup_indent_guides_editor(
16052 &"
16053 fn main() {
16054 let a = 1;
16055 }"
16056 .unindent(),
16057 cx,
16058 )
16059 .await;
16060
16061 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
16062}
16063
16064#[gpui::test]
16065async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
16066 let (buffer_id, mut cx) = setup_indent_guides_editor(
16067 &"
16068 fn main() {
16069 let a = 1;
16070 let b = 2;
16071 }"
16072 .unindent(),
16073 cx,
16074 )
16075 .await;
16076
16077 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
16078}
16079
16080#[gpui::test]
16081async fn test_indent_guide_nested(cx: &mut TestAppContext) {
16082 let (buffer_id, mut cx) = setup_indent_guides_editor(
16083 &"
16084 fn main() {
16085 let a = 1;
16086 if a == 3 {
16087 let b = 2;
16088 } else {
16089 let c = 3;
16090 }
16091 }"
16092 .unindent(),
16093 cx,
16094 )
16095 .await;
16096
16097 assert_indent_guides(
16098 0..8,
16099 vec![
16100 indent_guide(buffer_id, 1, 6, 0),
16101 indent_guide(buffer_id, 3, 3, 1),
16102 indent_guide(buffer_id, 5, 5, 1),
16103 ],
16104 None,
16105 &mut cx,
16106 );
16107}
16108
16109#[gpui::test]
16110async fn test_indent_guide_tab(cx: &mut TestAppContext) {
16111 let (buffer_id, mut cx) = setup_indent_guides_editor(
16112 &"
16113 fn main() {
16114 let a = 1;
16115 let b = 2;
16116 let c = 3;
16117 }"
16118 .unindent(),
16119 cx,
16120 )
16121 .await;
16122
16123 assert_indent_guides(
16124 0..5,
16125 vec![
16126 indent_guide(buffer_id, 1, 3, 0),
16127 indent_guide(buffer_id, 2, 2, 1),
16128 ],
16129 None,
16130 &mut cx,
16131 );
16132}
16133
16134#[gpui::test]
16135async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
16136 let (buffer_id, mut cx) = setup_indent_guides_editor(
16137 &"
16138 fn main() {
16139 let a = 1;
16140
16141 let c = 3;
16142 }"
16143 .unindent(),
16144 cx,
16145 )
16146 .await;
16147
16148 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
16149}
16150
16151#[gpui::test]
16152async fn test_indent_guide_complex(cx: &mut TestAppContext) {
16153 let (buffer_id, mut cx) = setup_indent_guides_editor(
16154 &"
16155 fn main() {
16156 let a = 1;
16157
16158 let c = 3;
16159
16160 if a == 3 {
16161 let b = 2;
16162 } else {
16163 let c = 3;
16164 }
16165 }"
16166 .unindent(),
16167 cx,
16168 )
16169 .await;
16170
16171 assert_indent_guides(
16172 0..11,
16173 vec![
16174 indent_guide(buffer_id, 1, 9, 0),
16175 indent_guide(buffer_id, 6, 6, 1),
16176 indent_guide(buffer_id, 8, 8, 1),
16177 ],
16178 None,
16179 &mut cx,
16180 );
16181}
16182
16183#[gpui::test]
16184async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
16185 let (buffer_id, mut cx) = setup_indent_guides_editor(
16186 &"
16187 fn main() {
16188 let a = 1;
16189
16190 let c = 3;
16191
16192 if a == 3 {
16193 let b = 2;
16194 } else {
16195 let c = 3;
16196 }
16197 }"
16198 .unindent(),
16199 cx,
16200 )
16201 .await;
16202
16203 assert_indent_guides(
16204 1..11,
16205 vec![
16206 indent_guide(buffer_id, 1, 9, 0),
16207 indent_guide(buffer_id, 6, 6, 1),
16208 indent_guide(buffer_id, 8, 8, 1),
16209 ],
16210 None,
16211 &mut cx,
16212 );
16213}
16214
16215#[gpui::test]
16216async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
16217 let (buffer_id, mut cx) = setup_indent_guides_editor(
16218 &"
16219 fn main() {
16220 let a = 1;
16221
16222 let c = 3;
16223
16224 if a == 3 {
16225 let b = 2;
16226 } else {
16227 let c = 3;
16228 }
16229 }"
16230 .unindent(),
16231 cx,
16232 )
16233 .await;
16234
16235 assert_indent_guides(
16236 1..10,
16237 vec![
16238 indent_guide(buffer_id, 1, 9, 0),
16239 indent_guide(buffer_id, 6, 6, 1),
16240 indent_guide(buffer_id, 8, 8, 1),
16241 ],
16242 None,
16243 &mut cx,
16244 );
16245}
16246
16247#[gpui::test]
16248async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
16249 let (buffer_id, mut cx) = setup_indent_guides_editor(
16250 &"
16251 block1
16252 block2
16253 block3
16254 block4
16255 block2
16256 block1
16257 block1"
16258 .unindent(),
16259 cx,
16260 )
16261 .await;
16262
16263 assert_indent_guides(
16264 1..10,
16265 vec![
16266 indent_guide(buffer_id, 1, 4, 0),
16267 indent_guide(buffer_id, 2, 3, 1),
16268 indent_guide(buffer_id, 3, 3, 2),
16269 ],
16270 None,
16271 &mut cx,
16272 );
16273}
16274
16275#[gpui::test]
16276async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
16277 let (buffer_id, mut cx) = setup_indent_guides_editor(
16278 &"
16279 block1
16280 block2
16281 block3
16282
16283 block1
16284 block1"
16285 .unindent(),
16286 cx,
16287 )
16288 .await;
16289
16290 assert_indent_guides(
16291 0..6,
16292 vec![
16293 indent_guide(buffer_id, 1, 2, 0),
16294 indent_guide(buffer_id, 2, 2, 1),
16295 ],
16296 None,
16297 &mut cx,
16298 );
16299}
16300
16301#[gpui::test]
16302async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
16303 let (buffer_id, mut cx) = setup_indent_guides_editor(
16304 &"
16305 block1
16306
16307
16308
16309 block2
16310 "
16311 .unindent(),
16312 cx,
16313 )
16314 .await;
16315
16316 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
16317}
16318
16319#[gpui::test]
16320async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
16321 let (buffer_id, mut cx) = setup_indent_guides_editor(
16322 &"
16323 def a:
16324 \tb = 3
16325 \tif True:
16326 \t\tc = 4
16327 \t\td = 5
16328 \tprint(b)
16329 "
16330 .unindent(),
16331 cx,
16332 )
16333 .await;
16334
16335 assert_indent_guides(
16336 0..6,
16337 vec![
16338 indent_guide(buffer_id, 1, 6, 0),
16339 indent_guide(buffer_id, 3, 4, 1),
16340 ],
16341 None,
16342 &mut cx,
16343 );
16344}
16345
16346#[gpui::test]
16347async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
16348 let (buffer_id, mut cx) = setup_indent_guides_editor(
16349 &"
16350 fn main() {
16351 let a = 1;
16352 }"
16353 .unindent(),
16354 cx,
16355 )
16356 .await;
16357
16358 cx.update_editor(|editor, window, cx| {
16359 editor.change_selections(None, window, cx, |s| {
16360 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
16361 });
16362 });
16363
16364 assert_indent_guides(
16365 0..3,
16366 vec![indent_guide(buffer_id, 1, 1, 0)],
16367 Some(vec![0]),
16368 &mut cx,
16369 );
16370}
16371
16372#[gpui::test]
16373async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
16374 let (buffer_id, mut cx) = setup_indent_guides_editor(
16375 &"
16376 fn main() {
16377 if 1 == 2 {
16378 let a = 1;
16379 }
16380 }"
16381 .unindent(),
16382 cx,
16383 )
16384 .await;
16385
16386 cx.update_editor(|editor, window, cx| {
16387 editor.change_selections(None, window, cx, |s| {
16388 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
16389 });
16390 });
16391
16392 assert_indent_guides(
16393 0..4,
16394 vec![
16395 indent_guide(buffer_id, 1, 3, 0),
16396 indent_guide(buffer_id, 2, 2, 1),
16397 ],
16398 Some(vec![1]),
16399 &mut cx,
16400 );
16401
16402 cx.update_editor(|editor, window, cx| {
16403 editor.change_selections(None, window, cx, |s| {
16404 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
16405 });
16406 });
16407
16408 assert_indent_guides(
16409 0..4,
16410 vec![
16411 indent_guide(buffer_id, 1, 3, 0),
16412 indent_guide(buffer_id, 2, 2, 1),
16413 ],
16414 Some(vec![1]),
16415 &mut cx,
16416 );
16417
16418 cx.update_editor(|editor, window, cx| {
16419 editor.change_selections(None, window, cx, |s| {
16420 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
16421 });
16422 });
16423
16424 assert_indent_guides(
16425 0..4,
16426 vec![
16427 indent_guide(buffer_id, 1, 3, 0),
16428 indent_guide(buffer_id, 2, 2, 1),
16429 ],
16430 Some(vec![0]),
16431 &mut cx,
16432 );
16433}
16434
16435#[gpui::test]
16436async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
16437 let (buffer_id, mut cx) = setup_indent_guides_editor(
16438 &"
16439 fn main() {
16440 let a = 1;
16441
16442 let b = 2;
16443 }"
16444 .unindent(),
16445 cx,
16446 )
16447 .await;
16448
16449 cx.update_editor(|editor, window, cx| {
16450 editor.change_selections(None, window, cx, |s| {
16451 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
16452 });
16453 });
16454
16455 assert_indent_guides(
16456 0..5,
16457 vec![indent_guide(buffer_id, 1, 3, 0)],
16458 Some(vec![0]),
16459 &mut cx,
16460 );
16461}
16462
16463#[gpui::test]
16464async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
16465 let (buffer_id, mut cx) = setup_indent_guides_editor(
16466 &"
16467 def m:
16468 a = 1
16469 pass"
16470 .unindent(),
16471 cx,
16472 )
16473 .await;
16474
16475 cx.update_editor(|editor, window, cx| {
16476 editor.change_selections(None, window, cx, |s| {
16477 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
16478 });
16479 });
16480
16481 assert_indent_guides(
16482 0..3,
16483 vec![indent_guide(buffer_id, 1, 2, 0)],
16484 Some(vec![0]),
16485 &mut cx,
16486 );
16487}
16488
16489#[gpui::test]
16490async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
16491 init_test(cx, |_| {});
16492 let mut cx = EditorTestContext::new(cx).await;
16493 let text = indoc! {
16494 "
16495 impl A {
16496 fn b() {
16497 0;
16498 3;
16499 5;
16500 6;
16501 7;
16502 }
16503 }
16504 "
16505 };
16506 let base_text = indoc! {
16507 "
16508 impl A {
16509 fn b() {
16510 0;
16511 1;
16512 2;
16513 3;
16514 4;
16515 }
16516 fn c() {
16517 5;
16518 6;
16519 7;
16520 }
16521 }
16522 "
16523 };
16524
16525 cx.update_editor(|editor, window, cx| {
16526 editor.set_text(text, window, cx);
16527
16528 editor.buffer().update(cx, |multibuffer, cx| {
16529 let buffer = multibuffer.as_singleton().unwrap();
16530 let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
16531
16532 multibuffer.set_all_diff_hunks_expanded(cx);
16533 multibuffer.add_diff(diff, cx);
16534
16535 buffer.read(cx).remote_id()
16536 })
16537 });
16538 cx.run_until_parked();
16539
16540 cx.assert_state_with_diff(
16541 indoc! { "
16542 impl A {
16543 fn b() {
16544 0;
16545 - 1;
16546 - 2;
16547 3;
16548 - 4;
16549 - }
16550 - fn c() {
16551 5;
16552 6;
16553 7;
16554 }
16555 }
16556 ˇ"
16557 }
16558 .to_string(),
16559 );
16560
16561 let mut actual_guides = cx.update_editor(|editor, window, cx| {
16562 editor
16563 .snapshot(window, cx)
16564 .buffer_snapshot
16565 .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
16566 .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
16567 .collect::<Vec<_>>()
16568 });
16569 actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
16570 assert_eq!(
16571 actual_guides,
16572 vec![
16573 (MultiBufferRow(1)..=MultiBufferRow(12), 0),
16574 (MultiBufferRow(2)..=MultiBufferRow(6), 1),
16575 (MultiBufferRow(9)..=MultiBufferRow(11), 1),
16576 ]
16577 );
16578}
16579
16580#[gpui::test]
16581async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16582 init_test(cx, |_| {});
16583 let mut cx = EditorTestContext::new(cx).await;
16584
16585 let diff_base = r#"
16586 a
16587 b
16588 c
16589 "#
16590 .unindent();
16591
16592 cx.set_state(
16593 &r#"
16594 ˇA
16595 b
16596 C
16597 "#
16598 .unindent(),
16599 );
16600 cx.set_head_text(&diff_base);
16601 cx.update_editor(|editor, window, cx| {
16602 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16603 });
16604 executor.run_until_parked();
16605
16606 let both_hunks_expanded = r#"
16607 - a
16608 + ˇA
16609 b
16610 - c
16611 + C
16612 "#
16613 .unindent();
16614
16615 cx.assert_state_with_diff(both_hunks_expanded.clone());
16616
16617 let hunk_ranges = cx.update_editor(|editor, window, cx| {
16618 let snapshot = editor.snapshot(window, cx);
16619 let hunks = editor
16620 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
16621 .collect::<Vec<_>>();
16622 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
16623 let buffer_id = hunks[0].buffer_id;
16624 hunks
16625 .into_iter()
16626 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
16627 .collect::<Vec<_>>()
16628 });
16629 assert_eq!(hunk_ranges.len(), 2);
16630
16631 cx.update_editor(|editor, _, cx| {
16632 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
16633 });
16634 executor.run_until_parked();
16635
16636 let second_hunk_expanded = r#"
16637 ˇA
16638 b
16639 - c
16640 + C
16641 "#
16642 .unindent();
16643
16644 cx.assert_state_with_diff(second_hunk_expanded);
16645
16646 cx.update_editor(|editor, _, cx| {
16647 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
16648 });
16649 executor.run_until_parked();
16650
16651 cx.assert_state_with_diff(both_hunks_expanded.clone());
16652
16653 cx.update_editor(|editor, _, cx| {
16654 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
16655 });
16656 executor.run_until_parked();
16657
16658 let first_hunk_expanded = r#"
16659 - a
16660 + ˇA
16661 b
16662 C
16663 "#
16664 .unindent();
16665
16666 cx.assert_state_with_diff(first_hunk_expanded);
16667
16668 cx.update_editor(|editor, _, cx| {
16669 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
16670 });
16671 executor.run_until_parked();
16672
16673 cx.assert_state_with_diff(both_hunks_expanded);
16674
16675 cx.set_state(
16676 &r#"
16677 ˇA
16678 b
16679 "#
16680 .unindent(),
16681 );
16682 cx.run_until_parked();
16683
16684 // TODO this cursor position seems bad
16685 cx.assert_state_with_diff(
16686 r#"
16687 - ˇa
16688 + A
16689 b
16690 "#
16691 .unindent(),
16692 );
16693
16694 cx.update_editor(|editor, window, cx| {
16695 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16696 });
16697
16698 cx.assert_state_with_diff(
16699 r#"
16700 - ˇa
16701 + A
16702 b
16703 - c
16704 "#
16705 .unindent(),
16706 );
16707
16708 let hunk_ranges = cx.update_editor(|editor, window, cx| {
16709 let snapshot = editor.snapshot(window, cx);
16710 let hunks = editor
16711 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
16712 .collect::<Vec<_>>();
16713 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
16714 let buffer_id = hunks[0].buffer_id;
16715 hunks
16716 .into_iter()
16717 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
16718 .collect::<Vec<_>>()
16719 });
16720 assert_eq!(hunk_ranges.len(), 2);
16721
16722 cx.update_editor(|editor, _, cx| {
16723 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
16724 });
16725 executor.run_until_parked();
16726
16727 cx.assert_state_with_diff(
16728 r#"
16729 - ˇa
16730 + A
16731 b
16732 "#
16733 .unindent(),
16734 );
16735}
16736
16737#[gpui::test]
16738async fn test_toggle_deletion_hunk_at_start_of_file(
16739 executor: BackgroundExecutor,
16740 cx: &mut TestAppContext,
16741) {
16742 init_test(cx, |_| {});
16743 let mut cx = EditorTestContext::new(cx).await;
16744
16745 let diff_base = r#"
16746 a
16747 b
16748 c
16749 "#
16750 .unindent();
16751
16752 cx.set_state(
16753 &r#"
16754 ˇb
16755 c
16756 "#
16757 .unindent(),
16758 );
16759 cx.set_head_text(&diff_base);
16760 cx.update_editor(|editor, window, cx| {
16761 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16762 });
16763 executor.run_until_parked();
16764
16765 let hunk_expanded = r#"
16766 - a
16767 ˇb
16768 c
16769 "#
16770 .unindent();
16771
16772 cx.assert_state_with_diff(hunk_expanded.clone());
16773
16774 let hunk_ranges = cx.update_editor(|editor, window, cx| {
16775 let snapshot = editor.snapshot(window, cx);
16776 let hunks = editor
16777 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
16778 .collect::<Vec<_>>();
16779 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
16780 let buffer_id = hunks[0].buffer_id;
16781 hunks
16782 .into_iter()
16783 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
16784 .collect::<Vec<_>>()
16785 });
16786 assert_eq!(hunk_ranges.len(), 1);
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 let hunk_collapsed = r#"
16794 ˇb
16795 c
16796 "#
16797 .unindent();
16798
16799 cx.assert_state_with_diff(hunk_collapsed);
16800
16801 cx.update_editor(|editor, _, cx| {
16802 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
16803 });
16804 executor.run_until_parked();
16805
16806 cx.assert_state_with_diff(hunk_expanded.clone());
16807}
16808
16809#[gpui::test]
16810async fn test_display_diff_hunks(cx: &mut TestAppContext) {
16811 init_test(cx, |_| {});
16812
16813 let fs = FakeFs::new(cx.executor());
16814 fs.insert_tree(
16815 path!("/test"),
16816 json!({
16817 ".git": {},
16818 "file-1": "ONE\n",
16819 "file-2": "TWO\n",
16820 "file-3": "THREE\n",
16821 }),
16822 )
16823 .await;
16824
16825 fs.set_head_for_repo(
16826 path!("/test/.git").as_ref(),
16827 &[
16828 ("file-1".into(), "one\n".into()),
16829 ("file-2".into(), "two\n".into()),
16830 ("file-3".into(), "three\n".into()),
16831 ],
16832 );
16833
16834 let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
16835 let mut buffers = vec![];
16836 for i in 1..=3 {
16837 let buffer = project
16838 .update(cx, |project, cx| {
16839 let path = format!(path!("/test/file-{}"), i);
16840 project.open_local_buffer(path, cx)
16841 })
16842 .await
16843 .unwrap();
16844 buffers.push(buffer);
16845 }
16846
16847 let multibuffer = cx.new(|cx| {
16848 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
16849 multibuffer.set_all_diff_hunks_expanded(cx);
16850 for buffer in &buffers {
16851 let snapshot = buffer.read(cx).snapshot();
16852 multibuffer.set_excerpts_for_path(
16853 PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
16854 buffer.clone(),
16855 vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
16856 DEFAULT_MULTIBUFFER_CONTEXT,
16857 cx,
16858 );
16859 }
16860 multibuffer
16861 });
16862
16863 let editor = cx.add_window(|window, cx| {
16864 Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
16865 });
16866 cx.run_until_parked();
16867
16868 let snapshot = editor
16869 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
16870 .unwrap();
16871 let hunks = snapshot
16872 .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
16873 .map(|hunk| match hunk {
16874 DisplayDiffHunk::Unfolded {
16875 display_row_range, ..
16876 } => display_row_range,
16877 DisplayDiffHunk::Folded { .. } => unreachable!(),
16878 })
16879 .collect::<Vec<_>>();
16880 assert_eq!(
16881 hunks,
16882 [
16883 DisplayRow(2)..DisplayRow(4),
16884 DisplayRow(7)..DisplayRow(9),
16885 DisplayRow(12)..DisplayRow(14),
16886 ]
16887 );
16888}
16889
16890#[gpui::test]
16891async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
16892 init_test(cx, |_| {});
16893
16894 let mut cx = EditorTestContext::new(cx).await;
16895 cx.set_head_text(indoc! { "
16896 one
16897 two
16898 three
16899 four
16900 five
16901 "
16902 });
16903 cx.set_index_text(indoc! { "
16904 one
16905 two
16906 three
16907 four
16908 five
16909 "
16910 });
16911 cx.set_state(indoc! {"
16912 one
16913 TWO
16914 ˇTHREE
16915 FOUR
16916 five
16917 "});
16918 cx.run_until_parked();
16919 cx.update_editor(|editor, window, cx| {
16920 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
16921 });
16922 cx.run_until_parked();
16923 cx.assert_index_text(Some(indoc! {"
16924 one
16925 TWO
16926 THREE
16927 FOUR
16928 five
16929 "}));
16930 cx.set_state(indoc! { "
16931 one
16932 TWO
16933 ˇTHREE-HUNDRED
16934 FOUR
16935 five
16936 "});
16937 cx.run_until_parked();
16938 cx.update_editor(|editor, window, cx| {
16939 let snapshot = editor.snapshot(window, cx);
16940 let hunks = editor
16941 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
16942 .collect::<Vec<_>>();
16943 assert_eq!(hunks.len(), 1);
16944 assert_eq!(
16945 hunks[0].status(),
16946 DiffHunkStatus {
16947 kind: DiffHunkStatusKind::Modified,
16948 secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
16949 }
16950 );
16951
16952 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
16953 });
16954 cx.run_until_parked();
16955 cx.assert_index_text(Some(indoc! {"
16956 one
16957 TWO
16958 THREE-HUNDRED
16959 FOUR
16960 five
16961 "}));
16962}
16963
16964#[gpui::test]
16965fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
16966 init_test(cx, |_| {});
16967
16968 let editor = cx.add_window(|window, cx| {
16969 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
16970 build_editor(buffer, window, cx)
16971 });
16972
16973 let render_args = Arc::new(Mutex::new(None));
16974 let snapshot = editor
16975 .update(cx, |editor, window, cx| {
16976 let snapshot = editor.buffer().read(cx).snapshot(cx);
16977 let range =
16978 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
16979
16980 struct RenderArgs {
16981 row: MultiBufferRow,
16982 folded: bool,
16983 callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
16984 }
16985
16986 let crease = Crease::inline(
16987 range,
16988 FoldPlaceholder::test(),
16989 {
16990 let toggle_callback = render_args.clone();
16991 move |row, folded, callback, _window, _cx| {
16992 *toggle_callback.lock() = Some(RenderArgs {
16993 row,
16994 folded,
16995 callback,
16996 });
16997 div()
16998 }
16999 },
17000 |_row, _folded, _window, _cx| div(),
17001 );
17002
17003 editor.insert_creases(Some(crease), cx);
17004 let snapshot = editor.snapshot(window, cx);
17005 let _div = snapshot.render_crease_toggle(
17006 MultiBufferRow(1),
17007 false,
17008 cx.entity().clone(),
17009 window,
17010 cx,
17011 );
17012 snapshot
17013 })
17014 .unwrap();
17015
17016 let render_args = render_args.lock().take().unwrap();
17017 assert_eq!(render_args.row, MultiBufferRow(1));
17018 assert!(!render_args.folded);
17019 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
17020
17021 cx.update_window(*editor, |_, window, cx| {
17022 (render_args.callback)(true, window, cx)
17023 })
17024 .unwrap();
17025 let snapshot = editor
17026 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
17027 .unwrap();
17028 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
17029
17030 cx.update_window(*editor, |_, window, cx| {
17031 (render_args.callback)(false, window, cx)
17032 })
17033 .unwrap();
17034 let snapshot = editor
17035 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
17036 .unwrap();
17037 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
17038}
17039
17040#[gpui::test]
17041async fn test_input_text(cx: &mut TestAppContext) {
17042 init_test(cx, |_| {});
17043 let mut cx = EditorTestContext::new(cx).await;
17044
17045 cx.set_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(String::new()));
17058 cx.assert_editor_state(
17059 &r#"ˇone
17060 two
17061
17062 three
17063 fourˇ
17064 five
17065
17066 siˇx"#
17067 .unindent(),
17068 );
17069
17070 cx.dispatch_action(HandleInput("AAAA".to_string()));
17071 cx.assert_editor_state(
17072 &r#"AAAAˇone
17073 two
17074
17075 three
17076 fourAAAAˇ
17077 five
17078
17079 siAAAAˇx"#
17080 .unindent(),
17081 );
17082}
17083
17084#[gpui::test]
17085async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
17086 init_test(cx, |_| {});
17087
17088 let mut cx = EditorTestContext::new(cx).await;
17089 cx.set_state(
17090 r#"let foo = 1;
17091let foo = 2;
17092let foo = 3;
17093let fooˇ = 4;
17094let foo = 5;
17095let foo = 6;
17096let foo = 7;
17097let foo = 8;
17098let foo = 9;
17099let foo = 10;
17100let foo = 11;
17101let foo = 12;
17102let foo = 13;
17103let foo = 14;
17104let foo = 15;"#,
17105 );
17106
17107 cx.update_editor(|e, window, cx| {
17108 assert_eq!(
17109 e.next_scroll_position,
17110 NextScrollCursorCenterTopBottom::Center,
17111 "Default next scroll direction is center",
17112 );
17113
17114 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
17115 assert_eq!(
17116 e.next_scroll_position,
17117 NextScrollCursorCenterTopBottom::Top,
17118 "After center, next scroll direction should be top",
17119 );
17120
17121 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
17122 assert_eq!(
17123 e.next_scroll_position,
17124 NextScrollCursorCenterTopBottom::Bottom,
17125 "After top, next scroll direction should be bottom",
17126 );
17127
17128 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
17129 assert_eq!(
17130 e.next_scroll_position,
17131 NextScrollCursorCenterTopBottom::Center,
17132 "After bottom, scrolling should start over",
17133 );
17134
17135 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
17136 assert_eq!(
17137 e.next_scroll_position,
17138 NextScrollCursorCenterTopBottom::Top,
17139 "Scrolling continues if retriggered fast enough"
17140 );
17141 });
17142
17143 cx.executor()
17144 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
17145 cx.executor().run_until_parked();
17146 cx.update_editor(|e, _, _| {
17147 assert_eq!(
17148 e.next_scroll_position,
17149 NextScrollCursorCenterTopBottom::Center,
17150 "If scrolling is not triggered fast enough, it should reset"
17151 );
17152 });
17153}
17154
17155#[gpui::test]
17156async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
17157 init_test(cx, |_| {});
17158 let mut cx = EditorLspTestContext::new_rust(
17159 lsp::ServerCapabilities {
17160 definition_provider: Some(lsp::OneOf::Left(true)),
17161 references_provider: Some(lsp::OneOf::Left(true)),
17162 ..lsp::ServerCapabilities::default()
17163 },
17164 cx,
17165 )
17166 .await;
17167
17168 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
17169 let go_to_definition = cx
17170 .lsp
17171 .set_request_handler::<lsp::request::GotoDefinition, _, _>(
17172 move |params, _| async move {
17173 if empty_go_to_definition {
17174 Ok(None)
17175 } else {
17176 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
17177 uri: params.text_document_position_params.text_document.uri,
17178 range: lsp::Range::new(
17179 lsp::Position::new(4, 3),
17180 lsp::Position::new(4, 6),
17181 ),
17182 })))
17183 }
17184 },
17185 );
17186 let references = cx
17187 .lsp
17188 .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
17189 Ok(Some(vec![lsp::Location {
17190 uri: params.text_document_position.text_document.uri,
17191 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
17192 }]))
17193 });
17194 (go_to_definition, references)
17195 };
17196
17197 cx.set_state(
17198 &r#"fn one() {
17199 let mut a = ˇtwo();
17200 }
17201
17202 fn two() {}"#
17203 .unindent(),
17204 );
17205 set_up_lsp_handlers(false, &mut cx);
17206 let navigated = cx
17207 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
17208 .await
17209 .expect("Failed to navigate to definition");
17210 assert_eq!(
17211 navigated,
17212 Navigated::Yes,
17213 "Should have navigated to definition from the GetDefinition response"
17214 );
17215 cx.assert_editor_state(
17216 &r#"fn one() {
17217 let mut a = two();
17218 }
17219
17220 fn «twoˇ»() {}"#
17221 .unindent(),
17222 );
17223
17224 let editors = cx.update_workspace(|workspace, _, cx| {
17225 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
17226 });
17227 cx.update_editor(|_, _, test_editor_cx| {
17228 assert_eq!(
17229 editors.len(),
17230 1,
17231 "Initially, only one, test, editor should be open in the workspace"
17232 );
17233 assert_eq!(
17234 test_editor_cx.entity(),
17235 editors.last().expect("Asserted len is 1").clone()
17236 );
17237 });
17238
17239 set_up_lsp_handlers(true, &mut cx);
17240 let navigated = cx
17241 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
17242 .await
17243 .expect("Failed to navigate to lookup references");
17244 assert_eq!(
17245 navigated,
17246 Navigated::Yes,
17247 "Should have navigated to references as a fallback after empty GoToDefinition response"
17248 );
17249 // We should not change the selections in the existing file,
17250 // if opening another milti buffer with the references
17251 cx.assert_editor_state(
17252 &r#"fn one() {
17253 let mut a = two();
17254 }
17255
17256 fn «twoˇ»() {}"#
17257 .unindent(),
17258 );
17259 let editors = cx.update_workspace(|workspace, _, cx| {
17260 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
17261 });
17262 cx.update_editor(|_, _, test_editor_cx| {
17263 assert_eq!(
17264 editors.len(),
17265 2,
17266 "After falling back to references search, we open a new editor with the results"
17267 );
17268 let references_fallback_text = editors
17269 .into_iter()
17270 .find(|new_editor| *new_editor != test_editor_cx.entity())
17271 .expect("Should have one non-test editor now")
17272 .read(test_editor_cx)
17273 .text(test_editor_cx);
17274 assert_eq!(
17275 references_fallback_text, "fn one() {\n let mut a = two();\n}",
17276 "Should use the range from the references response and not the GoToDefinition one"
17277 );
17278 });
17279}
17280
17281#[gpui::test]
17282async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
17283 init_test(cx, |_| {});
17284 cx.update(|cx| {
17285 let mut editor_settings = EditorSettings::get_global(cx).clone();
17286 editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
17287 EditorSettings::override_global(editor_settings, cx);
17288 });
17289 let mut cx = EditorLspTestContext::new_rust(
17290 lsp::ServerCapabilities {
17291 definition_provider: Some(lsp::OneOf::Left(true)),
17292 references_provider: Some(lsp::OneOf::Left(true)),
17293 ..lsp::ServerCapabilities::default()
17294 },
17295 cx,
17296 )
17297 .await;
17298 let original_state = r#"fn one() {
17299 let mut a = ˇtwo();
17300 }
17301
17302 fn two() {}"#
17303 .unindent();
17304 cx.set_state(&original_state);
17305
17306 let mut go_to_definition = cx
17307 .lsp
17308 .set_request_handler::<lsp::request::GotoDefinition, _, _>(
17309 move |_, _| async move { Ok(None) },
17310 );
17311 let _references = cx
17312 .lsp
17313 .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
17314 panic!("Should not call for references with no go to definition fallback")
17315 });
17316
17317 let navigated = cx
17318 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
17319 .await
17320 .expect("Failed to navigate to lookup references");
17321 go_to_definition
17322 .next()
17323 .await
17324 .expect("Should have called the go_to_definition handler");
17325
17326 assert_eq!(
17327 navigated,
17328 Navigated::No,
17329 "Should have navigated to references as a fallback after empty GoToDefinition response"
17330 );
17331 cx.assert_editor_state(&original_state);
17332 let editors = cx.update_workspace(|workspace, _, cx| {
17333 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
17334 });
17335 cx.update_editor(|_, _, _| {
17336 assert_eq!(
17337 editors.len(),
17338 1,
17339 "After unsuccessful fallback, no other editor should have been opened"
17340 );
17341 });
17342}
17343
17344#[gpui::test]
17345async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
17346 init_test(cx, |_| {});
17347
17348 let language = Arc::new(Language::new(
17349 LanguageConfig::default(),
17350 Some(tree_sitter_rust::LANGUAGE.into()),
17351 ));
17352
17353 let text = r#"
17354 #[cfg(test)]
17355 mod tests() {
17356 #[test]
17357 fn runnable_1() {
17358 let a = 1;
17359 }
17360
17361 #[test]
17362 fn runnable_2() {
17363 let a = 1;
17364 let b = 2;
17365 }
17366 }
17367 "#
17368 .unindent();
17369
17370 let fs = FakeFs::new(cx.executor());
17371 fs.insert_file("/file.rs", Default::default()).await;
17372
17373 let project = Project::test(fs, ["/a".as_ref()], cx).await;
17374 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17375 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17376 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
17377 let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
17378
17379 let editor = cx.new_window_entity(|window, cx| {
17380 Editor::new(
17381 EditorMode::full(),
17382 multi_buffer,
17383 Some(project.clone()),
17384 window,
17385 cx,
17386 )
17387 });
17388
17389 editor.update_in(cx, |editor, window, cx| {
17390 let snapshot = editor.buffer().read(cx).snapshot(cx);
17391 editor.tasks.insert(
17392 (buffer.read(cx).remote_id(), 3),
17393 RunnableTasks {
17394 templates: vec![],
17395 offset: snapshot.anchor_before(43),
17396 column: 0,
17397 extra_variables: HashMap::default(),
17398 context_range: BufferOffset(43)..BufferOffset(85),
17399 },
17400 );
17401 editor.tasks.insert(
17402 (buffer.read(cx).remote_id(), 8),
17403 RunnableTasks {
17404 templates: vec![],
17405 offset: snapshot.anchor_before(86),
17406 column: 0,
17407 extra_variables: HashMap::default(),
17408 context_range: BufferOffset(86)..BufferOffset(191),
17409 },
17410 );
17411
17412 // Test finding task when cursor is inside function body
17413 editor.change_selections(None, window, cx, |s| {
17414 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
17415 });
17416 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
17417 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
17418
17419 // Test finding task when cursor is on function name
17420 editor.change_selections(None, window, cx, |s| {
17421 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
17422 });
17423 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
17424 assert_eq!(row, 8, "Should find task when cursor is on function name");
17425 });
17426}
17427
17428#[gpui::test]
17429async fn test_folding_buffers(cx: &mut TestAppContext) {
17430 init_test(cx, |_| {});
17431
17432 let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
17433 let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
17434 let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
17435
17436 let fs = FakeFs::new(cx.executor());
17437 fs.insert_tree(
17438 path!("/a"),
17439 json!({
17440 "first.rs": sample_text_1,
17441 "second.rs": sample_text_2,
17442 "third.rs": sample_text_3,
17443 }),
17444 )
17445 .await;
17446 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17447 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17448 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17449 let worktree = project.update(cx, |project, cx| {
17450 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
17451 assert_eq!(worktrees.len(), 1);
17452 worktrees.pop().unwrap()
17453 });
17454 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
17455
17456 let buffer_1 = project
17457 .update(cx, |project, cx| {
17458 project.open_buffer((worktree_id, "first.rs"), cx)
17459 })
17460 .await
17461 .unwrap();
17462 let buffer_2 = project
17463 .update(cx, |project, cx| {
17464 project.open_buffer((worktree_id, "second.rs"), cx)
17465 })
17466 .await
17467 .unwrap();
17468 let buffer_3 = project
17469 .update(cx, |project, cx| {
17470 project.open_buffer((worktree_id, "third.rs"), cx)
17471 })
17472 .await
17473 .unwrap();
17474
17475 let multi_buffer = cx.new(|cx| {
17476 let mut multi_buffer = MultiBuffer::new(ReadWrite);
17477 multi_buffer.push_excerpts(
17478 buffer_1.clone(),
17479 [
17480 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17481 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17482 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17483 ],
17484 cx,
17485 );
17486 multi_buffer.push_excerpts(
17487 buffer_2.clone(),
17488 [
17489 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17490 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17491 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17492 ],
17493 cx,
17494 );
17495 multi_buffer.push_excerpts(
17496 buffer_3.clone(),
17497 [
17498 ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17499 ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17500 ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17501 ],
17502 cx,
17503 );
17504 multi_buffer
17505 });
17506 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
17507 Editor::new(
17508 EditorMode::full(),
17509 multi_buffer.clone(),
17510 Some(project.clone()),
17511 window,
17512 cx,
17513 )
17514 });
17515
17516 assert_eq!(
17517 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17518 "\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",
17519 );
17520
17521 multi_buffer_editor.update(cx, |editor, cx| {
17522 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
17523 });
17524 assert_eq!(
17525 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17526 "\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",
17527 "After folding the first buffer, its text should not be displayed"
17528 );
17529
17530 multi_buffer_editor.update(cx, |editor, cx| {
17531 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
17532 });
17533 assert_eq!(
17534 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17535 "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
17536 "After folding the second buffer, its text should not be displayed"
17537 );
17538
17539 multi_buffer_editor.update(cx, |editor, cx| {
17540 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
17541 });
17542 assert_eq!(
17543 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17544 "\n\n\n\n\n",
17545 "After folding the third buffer, its text should not be displayed"
17546 );
17547
17548 // Emulate selection inside the fold logic, that should work
17549 multi_buffer_editor.update_in(cx, |editor, window, cx| {
17550 editor
17551 .snapshot(window, cx)
17552 .next_line_boundary(Point::new(0, 4));
17553 });
17554
17555 multi_buffer_editor.update(cx, |editor, cx| {
17556 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
17557 });
17558 assert_eq!(
17559 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17560 "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
17561 "After unfolding the second buffer, its text should be displayed"
17562 );
17563
17564 // Typing inside of buffer 1 causes that buffer to be unfolded.
17565 multi_buffer_editor.update_in(cx, |editor, window, cx| {
17566 assert_eq!(
17567 multi_buffer
17568 .read(cx)
17569 .snapshot(cx)
17570 .text_for_range(Point::new(1, 0)..Point::new(1, 4))
17571 .collect::<String>(),
17572 "bbbb"
17573 );
17574 editor.change_selections(None, window, cx, |selections| {
17575 selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
17576 });
17577 editor.handle_input("B", window, cx);
17578 });
17579
17580 assert_eq!(
17581 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17582 "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
17583 "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
17584 );
17585
17586 multi_buffer_editor.update(cx, |editor, cx| {
17587 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
17588 });
17589 assert_eq!(
17590 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17591 "\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",
17592 "After unfolding the all buffers, all original text should be displayed"
17593 );
17594}
17595
17596#[gpui::test]
17597async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
17598 init_test(cx, |_| {});
17599
17600 let sample_text_1 = "1111\n2222\n3333".to_string();
17601 let sample_text_2 = "4444\n5555\n6666".to_string();
17602 let sample_text_3 = "7777\n8888\n9999".to_string();
17603
17604 let fs = FakeFs::new(cx.executor());
17605 fs.insert_tree(
17606 path!("/a"),
17607 json!({
17608 "first.rs": sample_text_1,
17609 "second.rs": sample_text_2,
17610 "third.rs": sample_text_3,
17611 }),
17612 )
17613 .await;
17614 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17615 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17616 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17617 let worktree = project.update(cx, |project, cx| {
17618 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
17619 assert_eq!(worktrees.len(), 1);
17620 worktrees.pop().unwrap()
17621 });
17622 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
17623
17624 let buffer_1 = project
17625 .update(cx, |project, cx| {
17626 project.open_buffer((worktree_id, "first.rs"), cx)
17627 })
17628 .await
17629 .unwrap();
17630 let buffer_2 = project
17631 .update(cx, |project, cx| {
17632 project.open_buffer((worktree_id, "second.rs"), cx)
17633 })
17634 .await
17635 .unwrap();
17636 let buffer_3 = project
17637 .update(cx, |project, cx| {
17638 project.open_buffer((worktree_id, "third.rs"), cx)
17639 })
17640 .await
17641 .unwrap();
17642
17643 let multi_buffer = cx.new(|cx| {
17644 let mut multi_buffer = MultiBuffer::new(ReadWrite);
17645 multi_buffer.push_excerpts(
17646 buffer_1.clone(),
17647 [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
17648 cx,
17649 );
17650 multi_buffer.push_excerpts(
17651 buffer_2.clone(),
17652 [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
17653 cx,
17654 );
17655 multi_buffer.push_excerpts(
17656 buffer_3.clone(),
17657 [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
17658 cx,
17659 );
17660 multi_buffer
17661 });
17662
17663 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
17664 Editor::new(
17665 EditorMode::full(),
17666 multi_buffer,
17667 Some(project.clone()),
17668 window,
17669 cx,
17670 )
17671 });
17672
17673 let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
17674 assert_eq!(
17675 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17676 full_text,
17677 );
17678
17679 multi_buffer_editor.update(cx, |editor, cx| {
17680 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
17681 });
17682 assert_eq!(
17683 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17684 "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
17685 "After folding the first buffer, its text should not be displayed"
17686 );
17687
17688 multi_buffer_editor.update(cx, |editor, cx| {
17689 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
17690 });
17691
17692 assert_eq!(
17693 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17694 "\n\n\n\n\n\n7777\n8888\n9999",
17695 "After folding the second buffer, its text should not be displayed"
17696 );
17697
17698 multi_buffer_editor.update(cx, |editor, cx| {
17699 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
17700 });
17701 assert_eq!(
17702 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17703 "\n\n\n\n\n",
17704 "After folding the third buffer, its text should not be displayed"
17705 );
17706
17707 multi_buffer_editor.update(cx, |editor, cx| {
17708 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
17709 });
17710 assert_eq!(
17711 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17712 "\n\n\n\n4444\n5555\n6666\n\n",
17713 "After unfolding the second buffer, its text should be displayed"
17714 );
17715
17716 multi_buffer_editor.update(cx, |editor, cx| {
17717 editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
17718 });
17719 assert_eq!(
17720 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17721 "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
17722 "After unfolding the first buffer, its text should be displayed"
17723 );
17724
17725 multi_buffer_editor.update(cx, |editor, cx| {
17726 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
17727 });
17728 assert_eq!(
17729 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17730 full_text,
17731 "After unfolding all buffers, all original text should be displayed"
17732 );
17733}
17734
17735#[gpui::test]
17736async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
17737 init_test(cx, |_| {});
17738
17739 let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
17740
17741 let fs = FakeFs::new(cx.executor());
17742 fs.insert_tree(
17743 path!("/a"),
17744 json!({
17745 "main.rs": sample_text,
17746 }),
17747 )
17748 .await;
17749 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17750 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17751 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17752 let worktree = project.update(cx, |project, cx| {
17753 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
17754 assert_eq!(worktrees.len(), 1);
17755 worktrees.pop().unwrap()
17756 });
17757 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
17758
17759 let buffer_1 = project
17760 .update(cx, |project, cx| {
17761 project.open_buffer((worktree_id, "main.rs"), cx)
17762 })
17763 .await
17764 .unwrap();
17765
17766 let multi_buffer = cx.new(|cx| {
17767 let mut multi_buffer = MultiBuffer::new(ReadWrite);
17768 multi_buffer.push_excerpts(
17769 buffer_1.clone(),
17770 [ExcerptRange::new(
17771 Point::new(0, 0)
17772 ..Point::new(
17773 sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
17774 0,
17775 ),
17776 )],
17777 cx,
17778 );
17779 multi_buffer
17780 });
17781 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
17782 Editor::new(
17783 EditorMode::full(),
17784 multi_buffer,
17785 Some(project.clone()),
17786 window,
17787 cx,
17788 )
17789 });
17790
17791 let selection_range = Point::new(1, 0)..Point::new(2, 0);
17792 multi_buffer_editor.update_in(cx, |editor, window, cx| {
17793 enum TestHighlight {}
17794 let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
17795 let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
17796 editor.highlight_text::<TestHighlight>(
17797 vec![highlight_range.clone()],
17798 HighlightStyle::color(Hsla::green()),
17799 cx,
17800 );
17801 editor.change_selections(None, window, cx, |s| s.select_ranges(Some(highlight_range)));
17802 });
17803
17804 let full_text = format!("\n\n{sample_text}");
17805 assert_eq!(
17806 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17807 full_text,
17808 );
17809}
17810
17811#[gpui::test]
17812async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
17813 init_test(cx, |_| {});
17814 cx.update(|cx| {
17815 let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
17816 "keymaps/default-linux.json",
17817 cx,
17818 )
17819 .unwrap();
17820 cx.bind_keys(default_key_bindings);
17821 });
17822
17823 let (editor, cx) = cx.add_window_view(|window, cx| {
17824 let multi_buffer = MultiBuffer::build_multi(
17825 [
17826 ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
17827 ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
17828 ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
17829 ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
17830 ],
17831 cx,
17832 );
17833 let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
17834
17835 let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
17836 // fold all but the second buffer, so that we test navigating between two
17837 // adjacent folded buffers, as well as folded buffers at the start and
17838 // end the multibuffer
17839 editor.fold_buffer(buffer_ids[0], cx);
17840 editor.fold_buffer(buffer_ids[2], cx);
17841 editor.fold_buffer(buffer_ids[3], cx);
17842
17843 editor
17844 });
17845 cx.simulate_resize(size(px(1000.), px(1000.)));
17846
17847 let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
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 cx.simulate_keystroke("down");
17900 cx.assert_excerpts_with_selections(indoc! {"
17901 [EXCERPT]
17902 [FOLDED]
17903 [EXCERPT]
17904 a1
17905 b1
17906 [EXCERPT]
17907 ˇ[FOLDED]
17908 [EXCERPT]
17909 [FOLDED]
17910 "
17911 });
17912 for _ in 0..5 {
17913 cx.simulate_keystroke("down");
17914 cx.assert_excerpts_with_selections(indoc! {"
17915 [EXCERPT]
17916 [FOLDED]
17917 [EXCERPT]
17918 a1
17919 b1
17920 [EXCERPT]
17921 [FOLDED]
17922 [EXCERPT]
17923 ˇ[FOLDED]
17924 "
17925 });
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 cx.simulate_keystroke("up");
17968 cx.assert_excerpts_with_selections(indoc! {"
17969 [EXCERPT]
17970 [FOLDED]
17971 [EXCERPT]
17972 ˇa1
17973 b1
17974 [EXCERPT]
17975 [FOLDED]
17976 [EXCERPT]
17977 [FOLDED]
17978 "
17979 });
17980 for _ in 0..5 {
17981 cx.simulate_keystroke("up");
17982 cx.assert_excerpts_with_selections(indoc! {"
17983 [EXCERPT]
17984 ˇ[FOLDED]
17985 [EXCERPT]
17986 a1
17987 b1
17988 [EXCERPT]
17989 [FOLDED]
17990 [EXCERPT]
17991 [FOLDED]
17992 "
17993 });
17994 }
17995}
17996
17997#[gpui::test]
17998async fn test_inline_completion_text(cx: &mut TestAppContext) {
17999 init_test(cx, |_| {});
18000
18001 // Simple insertion
18002 assert_highlighted_edits(
18003 "Hello, world!",
18004 vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
18005 true,
18006 cx,
18007 |highlighted_edits, cx| {
18008 assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
18009 assert_eq!(highlighted_edits.highlights.len(), 1);
18010 assert_eq!(highlighted_edits.highlights[0].0, 6..16);
18011 assert_eq!(
18012 highlighted_edits.highlights[0].1.background_color,
18013 Some(cx.theme().status().created_background)
18014 );
18015 },
18016 )
18017 .await;
18018
18019 // Replacement
18020 assert_highlighted_edits(
18021 "This is a test.",
18022 vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
18023 false,
18024 cx,
18025 |highlighted_edits, cx| {
18026 assert_eq!(highlighted_edits.text, "That is a test.");
18027 assert_eq!(highlighted_edits.highlights.len(), 1);
18028 assert_eq!(highlighted_edits.highlights[0].0, 0..4);
18029 assert_eq!(
18030 highlighted_edits.highlights[0].1.background_color,
18031 Some(cx.theme().status().created_background)
18032 );
18033 },
18034 )
18035 .await;
18036
18037 // Multiple edits
18038 assert_highlighted_edits(
18039 "Hello, world!",
18040 vec![
18041 (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
18042 (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
18043 ],
18044 false,
18045 cx,
18046 |highlighted_edits, cx| {
18047 assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
18048 assert_eq!(highlighted_edits.highlights.len(), 2);
18049 assert_eq!(highlighted_edits.highlights[0].0, 0..9);
18050 assert_eq!(highlighted_edits.highlights[1].0, 16..29);
18051 assert_eq!(
18052 highlighted_edits.highlights[0].1.background_color,
18053 Some(cx.theme().status().created_background)
18054 );
18055 assert_eq!(
18056 highlighted_edits.highlights[1].1.background_color,
18057 Some(cx.theme().status().created_background)
18058 );
18059 },
18060 )
18061 .await;
18062
18063 // Multiple lines with edits
18064 assert_highlighted_edits(
18065 "First line\nSecond line\nThird line\nFourth line",
18066 vec![
18067 (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
18068 (
18069 Point::new(2, 0)..Point::new(2, 10),
18070 "New third line".to_string(),
18071 ),
18072 (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
18073 ],
18074 false,
18075 cx,
18076 |highlighted_edits, cx| {
18077 assert_eq!(
18078 highlighted_edits.text,
18079 "Second modified\nNew third line\nFourth updated line"
18080 );
18081 assert_eq!(highlighted_edits.highlights.len(), 3);
18082 assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
18083 assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
18084 assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
18085 for highlight in &highlighted_edits.highlights {
18086 assert_eq!(
18087 highlight.1.background_color,
18088 Some(cx.theme().status().created_background)
18089 );
18090 }
18091 },
18092 )
18093 .await;
18094}
18095
18096#[gpui::test]
18097async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
18098 init_test(cx, |_| {});
18099
18100 // Deletion
18101 assert_highlighted_edits(
18102 "Hello, world!",
18103 vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
18104 true,
18105 cx,
18106 |highlighted_edits, cx| {
18107 assert_eq!(highlighted_edits.text, "Hello, world!");
18108 assert_eq!(highlighted_edits.highlights.len(), 1);
18109 assert_eq!(highlighted_edits.highlights[0].0, 5..11);
18110 assert_eq!(
18111 highlighted_edits.highlights[0].1.background_color,
18112 Some(cx.theme().status().deleted_background)
18113 );
18114 },
18115 )
18116 .await;
18117
18118 // Insertion
18119 assert_highlighted_edits(
18120 "Hello, world!",
18121 vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
18122 true,
18123 cx,
18124 |highlighted_edits, cx| {
18125 assert_eq!(highlighted_edits.highlights.len(), 1);
18126 assert_eq!(highlighted_edits.highlights[0].0, 6..14);
18127 assert_eq!(
18128 highlighted_edits.highlights[0].1.background_color,
18129 Some(cx.theme().status().created_background)
18130 );
18131 },
18132 )
18133 .await;
18134}
18135
18136async fn assert_highlighted_edits(
18137 text: &str,
18138 edits: Vec<(Range<Point>, String)>,
18139 include_deletions: bool,
18140 cx: &mut TestAppContext,
18141 assertion_fn: impl Fn(HighlightedText, &App),
18142) {
18143 let window = cx.add_window(|window, cx| {
18144 let buffer = MultiBuffer::build_simple(text, cx);
18145 Editor::new(EditorMode::full(), buffer, None, window, cx)
18146 });
18147 let cx = &mut VisualTestContext::from_window(*window, cx);
18148
18149 let (buffer, snapshot) = window
18150 .update(cx, |editor, _window, cx| {
18151 (
18152 editor.buffer().clone(),
18153 editor.buffer().read(cx).snapshot(cx),
18154 )
18155 })
18156 .unwrap();
18157
18158 let edits = edits
18159 .into_iter()
18160 .map(|(range, edit)| {
18161 (
18162 snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
18163 edit,
18164 )
18165 })
18166 .collect::<Vec<_>>();
18167
18168 let text_anchor_edits = edits
18169 .clone()
18170 .into_iter()
18171 .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
18172 .collect::<Vec<_>>();
18173
18174 let edit_preview = window
18175 .update(cx, |_, _window, cx| {
18176 buffer
18177 .read(cx)
18178 .as_singleton()
18179 .unwrap()
18180 .read(cx)
18181 .preview_edits(text_anchor_edits.into(), cx)
18182 })
18183 .unwrap()
18184 .await;
18185
18186 cx.update(|_window, cx| {
18187 let highlighted_edits = inline_completion_edit_text(
18188 &snapshot.as_singleton().unwrap().2,
18189 &edits,
18190 &edit_preview,
18191 include_deletions,
18192 cx,
18193 );
18194 assertion_fn(highlighted_edits, cx)
18195 });
18196}
18197
18198#[track_caller]
18199fn assert_breakpoint(
18200 breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
18201 path: &Arc<Path>,
18202 expected: Vec<(u32, Breakpoint)>,
18203) {
18204 if expected.len() == 0usize {
18205 assert!(!breakpoints.contains_key(path), "{}", path.display());
18206 } else {
18207 let mut breakpoint = breakpoints
18208 .get(path)
18209 .unwrap()
18210 .into_iter()
18211 .map(|breakpoint| {
18212 (
18213 breakpoint.row,
18214 Breakpoint {
18215 message: breakpoint.message.clone(),
18216 state: breakpoint.state,
18217 condition: breakpoint.condition.clone(),
18218 hit_condition: breakpoint.hit_condition.clone(),
18219 },
18220 )
18221 })
18222 .collect::<Vec<_>>();
18223
18224 breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
18225
18226 assert_eq!(expected, breakpoint);
18227 }
18228}
18229
18230fn add_log_breakpoint_at_cursor(
18231 editor: &mut Editor,
18232 log_message: &str,
18233 window: &mut Window,
18234 cx: &mut Context<Editor>,
18235) {
18236 let (anchor, bp) = editor
18237 .breakpoints_at_cursors(window, cx)
18238 .first()
18239 .and_then(|(anchor, bp)| {
18240 if let Some(bp) = bp {
18241 Some((*anchor, bp.clone()))
18242 } else {
18243 None
18244 }
18245 })
18246 .unwrap_or_else(|| {
18247 let cursor_position: Point = editor.selections.newest(cx).head();
18248
18249 let breakpoint_position = editor
18250 .snapshot(window, cx)
18251 .display_snapshot
18252 .buffer_snapshot
18253 .anchor_before(Point::new(cursor_position.row, 0));
18254
18255 (breakpoint_position, Breakpoint::new_log(&log_message))
18256 });
18257
18258 editor.edit_breakpoint_at_anchor(
18259 anchor,
18260 bp,
18261 BreakpointEditAction::EditLogMessage(log_message.into()),
18262 cx,
18263 );
18264}
18265
18266#[gpui::test]
18267async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
18268 init_test(cx, |_| {});
18269
18270 let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
18271 let fs = FakeFs::new(cx.executor());
18272 fs.insert_tree(
18273 path!("/a"),
18274 json!({
18275 "main.rs": sample_text,
18276 }),
18277 )
18278 .await;
18279 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18280 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18281 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18282
18283 let fs = FakeFs::new(cx.executor());
18284 fs.insert_tree(
18285 path!("/a"),
18286 json!({
18287 "main.rs": sample_text,
18288 }),
18289 )
18290 .await;
18291 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18292 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18293 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18294 let worktree_id = workspace
18295 .update(cx, |workspace, _window, cx| {
18296 workspace.project().update(cx, |project, cx| {
18297 project.worktrees(cx).next().unwrap().read(cx).id()
18298 })
18299 })
18300 .unwrap();
18301
18302 let buffer = project
18303 .update(cx, |project, cx| {
18304 project.open_buffer((worktree_id, "main.rs"), cx)
18305 })
18306 .await
18307 .unwrap();
18308
18309 let (editor, cx) = cx.add_window_view(|window, cx| {
18310 Editor::new(
18311 EditorMode::full(),
18312 MultiBuffer::build_from_buffer(buffer, cx),
18313 Some(project.clone()),
18314 window,
18315 cx,
18316 )
18317 });
18318
18319 let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
18320 let abs_path = project.read_with(cx, |project, cx| {
18321 project
18322 .absolute_path(&project_path, cx)
18323 .map(|path_buf| Arc::from(path_buf.to_owned()))
18324 .unwrap()
18325 });
18326
18327 // assert we can add breakpoint on the first line
18328 editor.update_in(cx, |editor, window, cx| {
18329 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18330 editor.move_to_end(&MoveToEnd, window, cx);
18331 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18332 });
18333
18334 let breakpoints = editor.update(cx, |editor, cx| {
18335 editor
18336 .breakpoint_store()
18337 .as_ref()
18338 .unwrap()
18339 .read(cx)
18340 .all_breakpoints(cx)
18341 .clone()
18342 });
18343
18344 assert_eq!(1, breakpoints.len());
18345 assert_breakpoint(
18346 &breakpoints,
18347 &abs_path,
18348 vec![
18349 (0, Breakpoint::new_standard()),
18350 (3, Breakpoint::new_standard()),
18351 ],
18352 );
18353
18354 editor.update_in(cx, |editor, window, cx| {
18355 editor.move_to_beginning(&MoveToBeginning, window, cx);
18356 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18357 });
18358
18359 let breakpoints = editor.update(cx, |editor, cx| {
18360 editor
18361 .breakpoint_store()
18362 .as_ref()
18363 .unwrap()
18364 .read(cx)
18365 .all_breakpoints(cx)
18366 .clone()
18367 });
18368
18369 assert_eq!(1, breakpoints.len());
18370 assert_breakpoint(
18371 &breakpoints,
18372 &abs_path,
18373 vec![(3, Breakpoint::new_standard())],
18374 );
18375
18376 editor.update_in(cx, |editor, window, cx| {
18377 editor.move_to_end(&MoveToEnd, window, cx);
18378 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18379 });
18380
18381 let breakpoints = editor.update(cx, |editor, cx| {
18382 editor
18383 .breakpoint_store()
18384 .as_ref()
18385 .unwrap()
18386 .read(cx)
18387 .all_breakpoints(cx)
18388 .clone()
18389 });
18390
18391 assert_eq!(0, breakpoints.len());
18392 assert_breakpoint(&breakpoints, &abs_path, vec![]);
18393}
18394
18395#[gpui::test]
18396async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
18397 init_test(cx, |_| {});
18398
18399 let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
18400
18401 let fs = FakeFs::new(cx.executor());
18402 fs.insert_tree(
18403 path!("/a"),
18404 json!({
18405 "main.rs": sample_text,
18406 }),
18407 )
18408 .await;
18409 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18410 let (workspace, cx) =
18411 cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
18412
18413 let worktree_id = workspace.update(cx, |workspace, cx| {
18414 workspace.project().update(cx, |project, cx| {
18415 project.worktrees(cx).next().unwrap().read(cx).id()
18416 })
18417 });
18418
18419 let buffer = project
18420 .update(cx, |project, cx| {
18421 project.open_buffer((worktree_id, "main.rs"), cx)
18422 })
18423 .await
18424 .unwrap();
18425
18426 let (editor, cx) = cx.add_window_view(|window, cx| {
18427 Editor::new(
18428 EditorMode::full(),
18429 MultiBuffer::build_from_buffer(buffer, cx),
18430 Some(project.clone()),
18431 window,
18432 cx,
18433 )
18434 });
18435
18436 let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
18437 let abs_path = project.read_with(cx, |project, cx| {
18438 project
18439 .absolute_path(&project_path, cx)
18440 .map(|path_buf| Arc::from(path_buf.to_owned()))
18441 .unwrap()
18442 });
18443
18444 editor.update_in(cx, |editor, window, cx| {
18445 add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
18446 });
18447
18448 let breakpoints = editor.update(cx, |editor, cx| {
18449 editor
18450 .breakpoint_store()
18451 .as_ref()
18452 .unwrap()
18453 .read(cx)
18454 .all_breakpoints(cx)
18455 .clone()
18456 });
18457
18458 assert_breakpoint(
18459 &breakpoints,
18460 &abs_path,
18461 vec![(0, Breakpoint::new_log("hello world"))],
18462 );
18463
18464 // Removing a log message from a log breakpoint should remove it
18465 editor.update_in(cx, |editor, window, cx| {
18466 add_log_breakpoint_at_cursor(editor, "", window, cx);
18467 });
18468
18469 let breakpoints = editor.update(cx, |editor, cx| {
18470 editor
18471 .breakpoint_store()
18472 .as_ref()
18473 .unwrap()
18474 .read(cx)
18475 .all_breakpoints(cx)
18476 .clone()
18477 });
18478
18479 assert_breakpoint(&breakpoints, &abs_path, vec![]);
18480
18481 editor.update_in(cx, |editor, window, cx| {
18482 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18483 editor.move_to_end(&MoveToEnd, window, cx);
18484 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18485 // Not adding a log message to a standard breakpoint shouldn't remove it
18486 add_log_breakpoint_at_cursor(editor, "", window, cx);
18487 });
18488
18489 let breakpoints = editor.update(cx, |editor, cx| {
18490 editor
18491 .breakpoint_store()
18492 .as_ref()
18493 .unwrap()
18494 .read(cx)
18495 .all_breakpoints(cx)
18496 .clone()
18497 });
18498
18499 assert_breakpoint(
18500 &breakpoints,
18501 &abs_path,
18502 vec![
18503 (0, Breakpoint::new_standard()),
18504 (3, Breakpoint::new_standard()),
18505 ],
18506 );
18507
18508 editor.update_in(cx, |editor, window, cx| {
18509 add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
18510 });
18511
18512 let breakpoints = editor.update(cx, |editor, cx| {
18513 editor
18514 .breakpoint_store()
18515 .as_ref()
18516 .unwrap()
18517 .read(cx)
18518 .all_breakpoints(cx)
18519 .clone()
18520 });
18521
18522 assert_breakpoint(
18523 &breakpoints,
18524 &abs_path,
18525 vec![
18526 (0, Breakpoint::new_standard()),
18527 (3, Breakpoint::new_log("hello world")),
18528 ],
18529 );
18530
18531 editor.update_in(cx, |editor, window, cx| {
18532 add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
18533 });
18534
18535 let breakpoints = editor.update(cx, |editor, cx| {
18536 editor
18537 .breakpoint_store()
18538 .as_ref()
18539 .unwrap()
18540 .read(cx)
18541 .all_breakpoints(cx)
18542 .clone()
18543 });
18544
18545 assert_breakpoint(
18546 &breakpoints,
18547 &abs_path,
18548 vec![
18549 (0, Breakpoint::new_standard()),
18550 (3, Breakpoint::new_log("hello Earth!!")),
18551 ],
18552 );
18553}
18554
18555/// This also tests that Editor::breakpoint_at_cursor_head is working properly
18556/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
18557/// or when breakpoints were placed out of order. This tests for a regression too
18558#[gpui::test]
18559async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
18560 init_test(cx, |_| {});
18561
18562 let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
18563 let fs = FakeFs::new(cx.executor());
18564 fs.insert_tree(
18565 path!("/a"),
18566 json!({
18567 "main.rs": sample_text,
18568 }),
18569 )
18570 .await;
18571 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18572 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18573 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18574
18575 let fs = FakeFs::new(cx.executor());
18576 fs.insert_tree(
18577 path!("/a"),
18578 json!({
18579 "main.rs": sample_text,
18580 }),
18581 )
18582 .await;
18583 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18584 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18585 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18586 let worktree_id = workspace
18587 .update(cx, |workspace, _window, cx| {
18588 workspace.project().update(cx, |project, cx| {
18589 project.worktrees(cx).next().unwrap().read(cx).id()
18590 })
18591 })
18592 .unwrap();
18593
18594 let buffer = project
18595 .update(cx, |project, cx| {
18596 project.open_buffer((worktree_id, "main.rs"), cx)
18597 })
18598 .await
18599 .unwrap();
18600
18601 let (editor, cx) = cx.add_window_view(|window, cx| {
18602 Editor::new(
18603 EditorMode::full(),
18604 MultiBuffer::build_from_buffer(buffer, cx),
18605 Some(project.clone()),
18606 window,
18607 cx,
18608 )
18609 });
18610
18611 let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
18612 let abs_path = project.read_with(cx, |project, cx| {
18613 project
18614 .absolute_path(&project_path, cx)
18615 .map(|path_buf| Arc::from(path_buf.to_owned()))
18616 .unwrap()
18617 });
18618
18619 // assert we can add breakpoint on the first line
18620 editor.update_in(cx, |editor, window, cx| {
18621 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18622 editor.move_to_end(&MoveToEnd, window, cx);
18623 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18624 editor.move_up(&MoveUp, window, cx);
18625 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18626 });
18627
18628 let breakpoints = editor.update(cx, |editor, cx| {
18629 editor
18630 .breakpoint_store()
18631 .as_ref()
18632 .unwrap()
18633 .read(cx)
18634 .all_breakpoints(cx)
18635 .clone()
18636 });
18637
18638 assert_eq!(1, breakpoints.len());
18639 assert_breakpoint(
18640 &breakpoints,
18641 &abs_path,
18642 vec![
18643 (0, Breakpoint::new_standard()),
18644 (2, Breakpoint::new_standard()),
18645 (3, Breakpoint::new_standard()),
18646 ],
18647 );
18648
18649 editor.update_in(cx, |editor, window, cx| {
18650 editor.move_to_beginning(&MoveToBeginning, window, cx);
18651 editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
18652 editor.move_to_end(&MoveToEnd, window, cx);
18653 editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
18654 // Disabling a breakpoint that doesn't exist should do nothing
18655 editor.move_up(&MoveUp, window, cx);
18656 editor.move_up(&MoveUp, window, cx);
18657 editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
18658 });
18659
18660 let breakpoints = editor.update(cx, |editor, cx| {
18661 editor
18662 .breakpoint_store()
18663 .as_ref()
18664 .unwrap()
18665 .read(cx)
18666 .all_breakpoints(cx)
18667 .clone()
18668 });
18669
18670 let disable_breakpoint = {
18671 let mut bp = Breakpoint::new_standard();
18672 bp.state = BreakpointState::Disabled;
18673 bp
18674 };
18675
18676 assert_eq!(1, breakpoints.len());
18677 assert_breakpoint(
18678 &breakpoints,
18679 &abs_path,
18680 vec![
18681 (0, disable_breakpoint.clone()),
18682 (2, Breakpoint::new_standard()),
18683 (3, disable_breakpoint.clone()),
18684 ],
18685 );
18686
18687 editor.update_in(cx, |editor, window, cx| {
18688 editor.move_to_beginning(&MoveToBeginning, window, cx);
18689 editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
18690 editor.move_to_end(&MoveToEnd, window, cx);
18691 editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
18692 editor.move_up(&MoveUp, window, cx);
18693 editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
18694 });
18695
18696 let breakpoints = editor.update(cx, |editor, cx| {
18697 editor
18698 .breakpoint_store()
18699 .as_ref()
18700 .unwrap()
18701 .read(cx)
18702 .all_breakpoints(cx)
18703 .clone()
18704 });
18705
18706 assert_eq!(1, breakpoints.len());
18707 assert_breakpoint(
18708 &breakpoints,
18709 &abs_path,
18710 vec![
18711 (0, Breakpoint::new_standard()),
18712 (2, disable_breakpoint),
18713 (3, Breakpoint::new_standard()),
18714 ],
18715 );
18716}
18717
18718#[gpui::test]
18719async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
18720 init_test(cx, |_| {});
18721 let capabilities = lsp::ServerCapabilities {
18722 rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
18723 prepare_provider: Some(true),
18724 work_done_progress_options: Default::default(),
18725 })),
18726 ..Default::default()
18727 };
18728 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
18729
18730 cx.set_state(indoc! {"
18731 struct Fˇoo {}
18732 "});
18733
18734 cx.update_editor(|editor, _, cx| {
18735 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
18736 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
18737 editor.highlight_background::<DocumentHighlightRead>(
18738 &[highlight_range],
18739 |c| c.editor_document_highlight_read_background,
18740 cx,
18741 );
18742 });
18743
18744 let mut prepare_rename_handler = cx
18745 .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
18746 move |_, _, _| async move {
18747 Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
18748 start: lsp::Position {
18749 line: 0,
18750 character: 7,
18751 },
18752 end: lsp::Position {
18753 line: 0,
18754 character: 10,
18755 },
18756 })))
18757 },
18758 );
18759 let prepare_rename_task = cx
18760 .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
18761 .expect("Prepare rename was not started");
18762 prepare_rename_handler.next().await.unwrap();
18763 prepare_rename_task.await.expect("Prepare rename failed");
18764
18765 let mut rename_handler =
18766 cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
18767 let edit = lsp::TextEdit {
18768 range: lsp::Range {
18769 start: lsp::Position {
18770 line: 0,
18771 character: 7,
18772 },
18773 end: lsp::Position {
18774 line: 0,
18775 character: 10,
18776 },
18777 },
18778 new_text: "FooRenamed".to_string(),
18779 };
18780 Ok(Some(lsp::WorkspaceEdit::new(
18781 // Specify the same edit twice
18782 std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
18783 )))
18784 });
18785 let rename_task = cx
18786 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
18787 .expect("Confirm rename was not started");
18788 rename_handler.next().await.unwrap();
18789 rename_task.await.expect("Confirm rename failed");
18790 cx.run_until_parked();
18791
18792 // Despite two edits, only one is actually applied as those are identical
18793 cx.assert_editor_state(indoc! {"
18794 struct FooRenamedˇ {}
18795 "});
18796}
18797
18798#[gpui::test]
18799async fn test_rename_without_prepare(cx: &mut TestAppContext) {
18800 init_test(cx, |_| {});
18801 // These capabilities indicate that the server does not support prepare rename.
18802 let capabilities = lsp::ServerCapabilities {
18803 rename_provider: Some(lsp::OneOf::Left(true)),
18804 ..Default::default()
18805 };
18806 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
18807
18808 cx.set_state(indoc! {"
18809 struct Fˇoo {}
18810 "});
18811
18812 cx.update_editor(|editor, _window, cx| {
18813 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
18814 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
18815 editor.highlight_background::<DocumentHighlightRead>(
18816 &[highlight_range],
18817 |c| c.editor_document_highlight_read_background,
18818 cx,
18819 );
18820 });
18821
18822 cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
18823 .expect("Prepare rename was not started")
18824 .await
18825 .expect("Prepare rename failed");
18826
18827 let mut rename_handler =
18828 cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
18829 let edit = lsp::TextEdit {
18830 range: lsp::Range {
18831 start: lsp::Position {
18832 line: 0,
18833 character: 7,
18834 },
18835 end: lsp::Position {
18836 line: 0,
18837 character: 10,
18838 },
18839 },
18840 new_text: "FooRenamed".to_string(),
18841 };
18842 Ok(Some(lsp::WorkspaceEdit::new(
18843 std::collections::HashMap::from_iter(Some((url, vec![edit]))),
18844 )))
18845 });
18846 let rename_task = cx
18847 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
18848 .expect("Confirm rename was not started");
18849 rename_handler.next().await.unwrap();
18850 rename_task.await.expect("Confirm rename failed");
18851 cx.run_until_parked();
18852
18853 // Correct range is renamed, as `surrounding_word` is used to find it.
18854 cx.assert_editor_state(indoc! {"
18855 struct FooRenamedˇ {}
18856 "});
18857}
18858
18859#[gpui::test]
18860async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
18861 init_test(cx, |_| {});
18862 let mut cx = EditorTestContext::new(cx).await;
18863
18864 let language = Arc::new(
18865 Language::new(
18866 LanguageConfig::default(),
18867 Some(tree_sitter_html::LANGUAGE.into()),
18868 )
18869 .with_brackets_query(
18870 r#"
18871 ("<" @open "/>" @close)
18872 ("</" @open ">" @close)
18873 ("<" @open ">" @close)
18874 ("\"" @open "\"" @close)
18875 ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
18876 "#,
18877 )
18878 .unwrap(),
18879 );
18880 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
18881
18882 cx.set_state(indoc! {"
18883 <span>ˇ</span>
18884 "});
18885 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
18886 cx.assert_editor_state(indoc! {"
18887 <span>
18888 ˇ
18889 </span>
18890 "});
18891
18892 cx.set_state(indoc! {"
18893 <span><span></span>ˇ</span>
18894 "});
18895 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
18896 cx.assert_editor_state(indoc! {"
18897 <span><span></span>
18898 ˇ</span>
18899 "});
18900
18901 cx.set_state(indoc! {"
18902 <span>ˇ
18903 </span>
18904 "});
18905 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
18906 cx.assert_editor_state(indoc! {"
18907 <span>
18908 ˇ
18909 </span>
18910 "});
18911}
18912
18913#[gpui::test(iterations = 10)]
18914async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
18915 init_test(cx, |_| {});
18916
18917 let fs = FakeFs::new(cx.executor());
18918 fs.insert_tree(
18919 path!("/dir"),
18920 json!({
18921 "a.ts": "a",
18922 }),
18923 )
18924 .await;
18925
18926 let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
18927 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18928 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18929
18930 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18931 language_registry.add(Arc::new(Language::new(
18932 LanguageConfig {
18933 name: "TypeScript".into(),
18934 matcher: LanguageMatcher {
18935 path_suffixes: vec!["ts".to_string()],
18936 ..Default::default()
18937 },
18938 ..Default::default()
18939 },
18940 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
18941 )));
18942 let mut fake_language_servers = language_registry.register_fake_lsp(
18943 "TypeScript",
18944 FakeLspAdapter {
18945 capabilities: lsp::ServerCapabilities {
18946 code_lens_provider: Some(lsp::CodeLensOptions {
18947 resolve_provider: Some(true),
18948 }),
18949 execute_command_provider: Some(lsp::ExecuteCommandOptions {
18950 commands: vec!["_the/command".to_string()],
18951 ..lsp::ExecuteCommandOptions::default()
18952 }),
18953 ..lsp::ServerCapabilities::default()
18954 },
18955 ..FakeLspAdapter::default()
18956 },
18957 );
18958
18959 let (buffer, _handle) = project
18960 .update(cx, |p, cx| {
18961 p.open_local_buffer_with_lsp(path!("/dir/a.ts"), cx)
18962 })
18963 .await
18964 .unwrap();
18965 cx.executor().run_until_parked();
18966
18967 let fake_server = fake_language_servers.next().await.unwrap();
18968
18969 let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
18970 let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
18971 drop(buffer_snapshot);
18972 let actions = cx
18973 .update_window(*workspace, |_, window, cx| {
18974 project.code_actions(&buffer, anchor..anchor, window, cx)
18975 })
18976 .unwrap();
18977
18978 fake_server
18979 .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
18980 Ok(Some(vec![
18981 lsp::CodeLens {
18982 range: lsp::Range::default(),
18983 command: Some(lsp::Command {
18984 title: "Code lens command".to_owned(),
18985 command: "_the/command".to_owned(),
18986 arguments: None,
18987 }),
18988 data: None,
18989 },
18990 lsp::CodeLens {
18991 range: lsp::Range::default(),
18992 command: Some(lsp::Command {
18993 title: "Command not in capabilities".to_owned(),
18994 command: "not in capabilities".to_owned(),
18995 arguments: None,
18996 }),
18997 data: None,
18998 },
18999 lsp::CodeLens {
19000 range: lsp::Range {
19001 start: lsp::Position {
19002 line: 1,
19003 character: 1,
19004 },
19005 end: lsp::Position {
19006 line: 1,
19007 character: 1,
19008 },
19009 },
19010 command: Some(lsp::Command {
19011 title: "Command not in range".to_owned(),
19012 command: "_the/command".to_owned(),
19013 arguments: None,
19014 }),
19015 data: None,
19016 },
19017 ]))
19018 })
19019 .next()
19020 .await;
19021
19022 let actions = actions.await.unwrap();
19023 assert_eq!(
19024 actions.len(),
19025 1,
19026 "Should have only one valid action for the 0..0 range"
19027 );
19028 let action = actions[0].clone();
19029 let apply = project.update(cx, |project, cx| {
19030 project.apply_code_action(buffer.clone(), action, true, cx)
19031 });
19032
19033 // Resolving the code action does not populate its edits. In absence of
19034 // edits, we must execute the given command.
19035 fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
19036 |mut lens, _| async move {
19037 let lens_command = lens.command.as_mut().expect("should have a command");
19038 assert_eq!(lens_command.title, "Code lens command");
19039 lens_command.arguments = Some(vec![json!("the-argument")]);
19040 Ok(lens)
19041 },
19042 );
19043
19044 // While executing the command, the language server sends the editor
19045 // a `workspaceEdit` request.
19046 fake_server
19047 .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
19048 let fake = fake_server.clone();
19049 move |params, _| {
19050 assert_eq!(params.command, "_the/command");
19051 let fake = fake.clone();
19052 async move {
19053 fake.server
19054 .request::<lsp::request::ApplyWorkspaceEdit>(
19055 lsp::ApplyWorkspaceEditParams {
19056 label: None,
19057 edit: lsp::WorkspaceEdit {
19058 changes: Some(
19059 [(
19060 lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
19061 vec![lsp::TextEdit {
19062 range: lsp::Range::new(
19063 lsp::Position::new(0, 0),
19064 lsp::Position::new(0, 0),
19065 ),
19066 new_text: "X".into(),
19067 }],
19068 )]
19069 .into_iter()
19070 .collect(),
19071 ),
19072 ..Default::default()
19073 },
19074 },
19075 )
19076 .await
19077 .unwrap();
19078 Ok(Some(json!(null)))
19079 }
19080 }
19081 })
19082 .next()
19083 .await;
19084
19085 // Applying the code lens command returns a project transaction containing the edits
19086 // sent by the language server in its `workspaceEdit` request.
19087 let transaction = apply.await.unwrap();
19088 assert!(transaction.0.contains_key(&buffer));
19089 buffer.update(cx, |buffer, cx| {
19090 assert_eq!(buffer.text(), "Xa");
19091 buffer.undo(cx);
19092 assert_eq!(buffer.text(), "a");
19093 });
19094}
19095
19096#[gpui::test]
19097async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
19098 init_test(cx, |_| {});
19099
19100 let fs = FakeFs::new(cx.executor());
19101 let main_text = r#"fn main() {
19102println!("1");
19103println!("2");
19104println!("3");
19105println!("4");
19106println!("5");
19107}"#;
19108 let lib_text = "mod foo {}";
19109 fs.insert_tree(
19110 path!("/a"),
19111 json!({
19112 "lib.rs": lib_text,
19113 "main.rs": main_text,
19114 }),
19115 )
19116 .await;
19117
19118 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19119 let (workspace, cx) =
19120 cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
19121 let worktree_id = workspace.update(cx, |workspace, cx| {
19122 workspace.project().update(cx, |project, cx| {
19123 project.worktrees(cx).next().unwrap().read(cx).id()
19124 })
19125 });
19126
19127 let expected_ranges = vec![
19128 Point::new(0, 0)..Point::new(0, 0),
19129 Point::new(1, 0)..Point::new(1, 1),
19130 Point::new(2, 0)..Point::new(2, 2),
19131 Point::new(3, 0)..Point::new(3, 3),
19132 ];
19133
19134 let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
19135 let editor_1 = workspace
19136 .update_in(cx, |workspace, window, cx| {
19137 workspace.open_path(
19138 (worktree_id, "main.rs"),
19139 Some(pane_1.downgrade()),
19140 true,
19141 window,
19142 cx,
19143 )
19144 })
19145 .unwrap()
19146 .await
19147 .downcast::<Editor>()
19148 .unwrap();
19149 pane_1.update(cx, |pane, cx| {
19150 let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19151 open_editor.update(cx, |editor, cx| {
19152 assert_eq!(
19153 editor.display_text(cx),
19154 main_text,
19155 "Original main.rs text on initial open",
19156 );
19157 assert_eq!(
19158 editor
19159 .selections
19160 .all::<Point>(cx)
19161 .into_iter()
19162 .map(|s| s.range())
19163 .collect::<Vec<_>>(),
19164 vec![Point::zero()..Point::zero()],
19165 "Default selections on initial open",
19166 );
19167 })
19168 });
19169 editor_1.update_in(cx, |editor, window, cx| {
19170 editor.change_selections(None, window, cx, |s| {
19171 s.select_ranges(expected_ranges.clone());
19172 });
19173 });
19174
19175 let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
19176 workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
19177 });
19178 let editor_2 = workspace
19179 .update_in(cx, |workspace, window, cx| {
19180 workspace.open_path(
19181 (worktree_id, "main.rs"),
19182 Some(pane_2.downgrade()),
19183 true,
19184 window,
19185 cx,
19186 )
19187 })
19188 .unwrap()
19189 .await
19190 .downcast::<Editor>()
19191 .unwrap();
19192 pane_2.update(cx, |pane, cx| {
19193 let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19194 open_editor.update(cx, |editor, cx| {
19195 assert_eq!(
19196 editor.display_text(cx),
19197 main_text,
19198 "Original main.rs text on initial open in another panel",
19199 );
19200 assert_eq!(
19201 editor
19202 .selections
19203 .all::<Point>(cx)
19204 .into_iter()
19205 .map(|s| s.range())
19206 .collect::<Vec<_>>(),
19207 vec![Point::zero()..Point::zero()],
19208 "Default selections on initial open in another panel",
19209 );
19210 })
19211 });
19212
19213 editor_2.update_in(cx, |editor, window, cx| {
19214 editor.fold_ranges(expected_ranges.clone(), false, window, cx);
19215 });
19216
19217 let _other_editor_1 = workspace
19218 .update_in(cx, |workspace, window, cx| {
19219 workspace.open_path(
19220 (worktree_id, "lib.rs"),
19221 Some(pane_1.downgrade()),
19222 true,
19223 window,
19224 cx,
19225 )
19226 })
19227 .unwrap()
19228 .await
19229 .downcast::<Editor>()
19230 .unwrap();
19231 pane_1
19232 .update_in(cx, |pane, window, cx| {
19233 pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
19234 .unwrap()
19235 })
19236 .await
19237 .unwrap();
19238 drop(editor_1);
19239 pane_1.update(cx, |pane, cx| {
19240 pane.active_item()
19241 .unwrap()
19242 .downcast::<Editor>()
19243 .unwrap()
19244 .update(cx, |editor, cx| {
19245 assert_eq!(
19246 editor.display_text(cx),
19247 lib_text,
19248 "Other file should be open and active",
19249 );
19250 });
19251 assert_eq!(pane.items().count(), 1, "No other editors should be open");
19252 });
19253
19254 let _other_editor_2 = workspace
19255 .update_in(cx, |workspace, window, cx| {
19256 workspace.open_path(
19257 (worktree_id, "lib.rs"),
19258 Some(pane_2.downgrade()),
19259 true,
19260 window,
19261 cx,
19262 )
19263 })
19264 .unwrap()
19265 .await
19266 .downcast::<Editor>()
19267 .unwrap();
19268 pane_2
19269 .update_in(cx, |pane, window, cx| {
19270 pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
19271 .unwrap()
19272 })
19273 .await
19274 .unwrap();
19275 drop(editor_2);
19276 pane_2.update(cx, |pane, cx| {
19277 let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19278 open_editor.update(cx, |editor, cx| {
19279 assert_eq!(
19280 editor.display_text(cx),
19281 lib_text,
19282 "Other file should be open and active in another panel too",
19283 );
19284 });
19285 assert_eq!(
19286 pane.items().count(),
19287 1,
19288 "No other editors should be open in another pane",
19289 );
19290 });
19291
19292 let _editor_1_reopened = workspace
19293 .update_in(cx, |workspace, window, cx| {
19294 workspace.open_path(
19295 (worktree_id, "main.rs"),
19296 Some(pane_1.downgrade()),
19297 true,
19298 window,
19299 cx,
19300 )
19301 })
19302 .unwrap()
19303 .await
19304 .downcast::<Editor>()
19305 .unwrap();
19306 let _editor_2_reopened = workspace
19307 .update_in(cx, |workspace, window, cx| {
19308 workspace.open_path(
19309 (worktree_id, "main.rs"),
19310 Some(pane_2.downgrade()),
19311 true,
19312 window,
19313 cx,
19314 )
19315 })
19316 .unwrap()
19317 .await
19318 .downcast::<Editor>()
19319 .unwrap();
19320 pane_1.update(cx, |pane, cx| {
19321 let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19322 open_editor.update(cx, |editor, cx| {
19323 assert_eq!(
19324 editor.display_text(cx),
19325 main_text,
19326 "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
19327 );
19328 assert_eq!(
19329 editor
19330 .selections
19331 .all::<Point>(cx)
19332 .into_iter()
19333 .map(|s| s.range())
19334 .collect::<Vec<_>>(),
19335 expected_ranges,
19336 "Previous editor in the 1st panel had selections and should get them restored on reopen",
19337 );
19338 })
19339 });
19340 pane_2.update(cx, |pane, cx| {
19341 let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19342 open_editor.update(cx, |editor, cx| {
19343 assert_eq!(
19344 editor.display_text(cx),
19345 r#"fn main() {
19346⋯rintln!("1");
19347⋯intln!("2");
19348⋯ntln!("3");
19349println!("4");
19350println!("5");
19351}"#,
19352 "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
19353 );
19354 assert_eq!(
19355 editor
19356 .selections
19357 .all::<Point>(cx)
19358 .into_iter()
19359 .map(|s| s.range())
19360 .collect::<Vec<_>>(),
19361 vec![Point::zero()..Point::zero()],
19362 "Previous editor in the 2nd pane had no selections changed hence should restore none",
19363 );
19364 })
19365 });
19366}
19367
19368#[gpui::test]
19369async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
19370 init_test(cx, |_| {});
19371
19372 let fs = FakeFs::new(cx.executor());
19373 let main_text = r#"fn main() {
19374println!("1");
19375println!("2");
19376println!("3");
19377println!("4");
19378println!("5");
19379}"#;
19380 let lib_text = "mod foo {}";
19381 fs.insert_tree(
19382 path!("/a"),
19383 json!({
19384 "lib.rs": lib_text,
19385 "main.rs": main_text,
19386 }),
19387 )
19388 .await;
19389
19390 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19391 let (workspace, cx) =
19392 cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
19393 let worktree_id = workspace.update(cx, |workspace, cx| {
19394 workspace.project().update(cx, |project, cx| {
19395 project.worktrees(cx).next().unwrap().read(cx).id()
19396 })
19397 });
19398
19399 let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
19400 let editor = workspace
19401 .update_in(cx, |workspace, window, cx| {
19402 workspace.open_path(
19403 (worktree_id, "main.rs"),
19404 Some(pane.downgrade()),
19405 true,
19406 window,
19407 cx,
19408 )
19409 })
19410 .unwrap()
19411 .await
19412 .downcast::<Editor>()
19413 .unwrap();
19414 pane.update(cx, |pane, cx| {
19415 let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19416 open_editor.update(cx, |editor, cx| {
19417 assert_eq!(
19418 editor.display_text(cx),
19419 main_text,
19420 "Original main.rs text on initial open",
19421 );
19422 })
19423 });
19424 editor.update_in(cx, |editor, window, cx| {
19425 editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
19426 });
19427
19428 cx.update_global(|store: &mut SettingsStore, cx| {
19429 store.update_user_settings::<WorkspaceSettings>(cx, |s| {
19430 s.restore_on_file_reopen = Some(false);
19431 });
19432 });
19433 editor.update_in(cx, |editor, window, cx| {
19434 editor.fold_ranges(
19435 vec![
19436 Point::new(1, 0)..Point::new(1, 1),
19437 Point::new(2, 0)..Point::new(2, 2),
19438 Point::new(3, 0)..Point::new(3, 3),
19439 ],
19440 false,
19441 window,
19442 cx,
19443 );
19444 });
19445 pane.update_in(cx, |pane, window, cx| {
19446 pane.close_all_items(&CloseAllItems::default(), window, cx)
19447 .unwrap()
19448 })
19449 .await
19450 .unwrap();
19451 pane.update(cx, |pane, _| {
19452 assert!(pane.active_item().is_none());
19453 });
19454 cx.update_global(|store: &mut SettingsStore, cx| {
19455 store.update_user_settings::<WorkspaceSettings>(cx, |s| {
19456 s.restore_on_file_reopen = Some(true);
19457 });
19458 });
19459
19460 let _editor_reopened = workspace
19461 .update_in(cx, |workspace, window, cx| {
19462 workspace.open_path(
19463 (worktree_id, "main.rs"),
19464 Some(pane.downgrade()),
19465 true,
19466 window,
19467 cx,
19468 )
19469 })
19470 .unwrap()
19471 .await
19472 .downcast::<Editor>()
19473 .unwrap();
19474 pane.update(cx, |pane, cx| {
19475 let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19476 open_editor.update(cx, |editor, cx| {
19477 assert_eq!(
19478 editor.display_text(cx),
19479 main_text,
19480 "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
19481 );
19482 })
19483 });
19484}
19485
19486#[gpui::test]
19487async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
19488 struct EmptyModalView {
19489 focus_handle: gpui::FocusHandle,
19490 }
19491 impl EventEmitter<DismissEvent> for EmptyModalView {}
19492 impl Render for EmptyModalView {
19493 fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
19494 div()
19495 }
19496 }
19497 impl Focusable for EmptyModalView {
19498 fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
19499 self.focus_handle.clone()
19500 }
19501 }
19502 impl workspace::ModalView for EmptyModalView {}
19503 fn new_empty_modal_view(cx: &App) -> EmptyModalView {
19504 EmptyModalView {
19505 focus_handle: cx.focus_handle(),
19506 }
19507 }
19508
19509 init_test(cx, |_| {});
19510
19511 let fs = FakeFs::new(cx.executor());
19512 let project = Project::test(fs, [], cx).await;
19513 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19514 let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
19515 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19516 let editor = cx.new_window_entity(|window, cx| {
19517 Editor::new(
19518 EditorMode::full(),
19519 buffer,
19520 Some(project.clone()),
19521 window,
19522 cx,
19523 )
19524 });
19525 workspace
19526 .update(cx, |workspace, window, cx| {
19527 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
19528 })
19529 .unwrap();
19530 editor.update_in(cx, |editor, window, cx| {
19531 editor.open_context_menu(&OpenContextMenu, window, cx);
19532 assert!(editor.mouse_context_menu.is_some());
19533 });
19534 workspace
19535 .update(cx, |workspace, window, cx| {
19536 workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
19537 })
19538 .unwrap();
19539 cx.read(|cx| {
19540 assert!(editor.read(cx).mouse_context_menu.is_none());
19541 });
19542}
19543
19544fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
19545 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
19546 point..point
19547}
19548
19549fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
19550 let (text, ranges) = marked_text_ranges(marked_text, true);
19551 assert_eq!(editor.text(cx), text);
19552 assert_eq!(
19553 editor.selections.ranges(cx),
19554 ranges,
19555 "Assert selections are {}",
19556 marked_text
19557 );
19558}
19559
19560pub fn handle_signature_help_request(
19561 cx: &mut EditorLspTestContext,
19562 mocked_response: lsp::SignatureHelp,
19563) -> impl Future<Output = ()> + use<> {
19564 let mut request =
19565 cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
19566 let mocked_response = mocked_response.clone();
19567 async move { Ok(Some(mocked_response)) }
19568 });
19569
19570 async move {
19571 request.next().await;
19572 }
19573}
19574
19575/// Handle completion request passing a marked string specifying where the completion
19576/// should be triggered from using '|' character, what range should be replaced, and what completions
19577/// should be returned using '<' and '>' to delimit the range.
19578///
19579/// Also see `handle_completion_request_with_insert_and_replace`.
19580#[track_caller]
19581pub fn handle_completion_request(
19582 cx: &mut EditorLspTestContext,
19583 marked_string: &str,
19584 completions: Vec<&'static str>,
19585 counter: Arc<AtomicUsize>,
19586) -> impl Future<Output = ()> {
19587 let complete_from_marker: TextRangeMarker = '|'.into();
19588 let replace_range_marker: TextRangeMarker = ('<', '>').into();
19589 let (_, mut marked_ranges) = marked_text_ranges_by(
19590 marked_string,
19591 vec![complete_from_marker.clone(), replace_range_marker.clone()],
19592 );
19593
19594 let complete_from_position =
19595 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
19596 let replace_range =
19597 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
19598
19599 let mut request =
19600 cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
19601 let completions = completions.clone();
19602 counter.fetch_add(1, atomic::Ordering::Release);
19603 async move {
19604 assert_eq!(params.text_document_position.text_document.uri, url.clone());
19605 assert_eq!(
19606 params.text_document_position.position,
19607 complete_from_position
19608 );
19609 Ok(Some(lsp::CompletionResponse::Array(
19610 completions
19611 .iter()
19612 .map(|completion_text| lsp::CompletionItem {
19613 label: completion_text.to_string(),
19614 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
19615 range: replace_range,
19616 new_text: completion_text.to_string(),
19617 })),
19618 ..Default::default()
19619 })
19620 .collect(),
19621 )))
19622 }
19623 });
19624
19625 async move {
19626 request.next().await;
19627 }
19628}
19629
19630/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
19631/// given instead, which also contains an `insert` range.
19632///
19633/// This function uses the cursor position to mimic what Rust-Analyzer provides as the `insert` range,
19634/// that is, `replace_range.start..cursor_pos`.
19635pub fn handle_completion_request_with_insert_and_replace(
19636 cx: &mut EditorLspTestContext,
19637 marked_string: &str,
19638 completions: Vec<&'static str>,
19639 counter: Arc<AtomicUsize>,
19640) -> impl Future<Output = ()> {
19641 let complete_from_marker: TextRangeMarker = '|'.into();
19642 let replace_range_marker: TextRangeMarker = ('<', '>').into();
19643 let (_, mut marked_ranges) = marked_text_ranges_by(
19644 marked_string,
19645 vec![complete_from_marker.clone(), replace_range_marker.clone()],
19646 );
19647
19648 let complete_from_position =
19649 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
19650 let replace_range =
19651 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
19652
19653 let mut request =
19654 cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
19655 let completions = completions.clone();
19656 counter.fetch_add(1, atomic::Ordering::Release);
19657 async move {
19658 assert_eq!(params.text_document_position.text_document.uri, url.clone());
19659 assert_eq!(
19660 params.text_document_position.position, complete_from_position,
19661 "marker `|` position doesn't match",
19662 );
19663 Ok(Some(lsp::CompletionResponse::Array(
19664 completions
19665 .iter()
19666 .map(|completion_text| lsp::CompletionItem {
19667 label: completion_text.to_string(),
19668 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
19669 lsp::InsertReplaceEdit {
19670 insert: lsp::Range {
19671 start: replace_range.start,
19672 end: complete_from_position,
19673 },
19674 replace: replace_range,
19675 new_text: completion_text.to_string(),
19676 },
19677 )),
19678 ..Default::default()
19679 })
19680 .collect(),
19681 )))
19682 }
19683 });
19684
19685 async move {
19686 request.next().await;
19687 }
19688}
19689
19690fn handle_resolve_completion_request(
19691 cx: &mut EditorLspTestContext,
19692 edits: Option<Vec<(&'static str, &'static str)>>,
19693) -> impl Future<Output = ()> {
19694 let edits = edits.map(|edits| {
19695 edits
19696 .iter()
19697 .map(|(marked_string, new_text)| {
19698 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
19699 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
19700 lsp::TextEdit::new(replace_range, new_text.to_string())
19701 })
19702 .collect::<Vec<_>>()
19703 });
19704
19705 let mut request =
19706 cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
19707 let edits = edits.clone();
19708 async move {
19709 Ok(lsp::CompletionItem {
19710 additional_text_edits: edits,
19711 ..Default::default()
19712 })
19713 }
19714 });
19715
19716 async move {
19717 request.next().await;
19718 }
19719}
19720
19721pub(crate) fn update_test_language_settings(
19722 cx: &mut TestAppContext,
19723 f: impl Fn(&mut AllLanguageSettingsContent),
19724) {
19725 cx.update(|cx| {
19726 SettingsStore::update_global(cx, |store, cx| {
19727 store.update_user_settings::<AllLanguageSettings>(cx, f);
19728 });
19729 });
19730}
19731
19732pub(crate) fn update_test_project_settings(
19733 cx: &mut TestAppContext,
19734 f: impl Fn(&mut ProjectSettings),
19735) {
19736 cx.update(|cx| {
19737 SettingsStore::update_global(cx, |store, cx| {
19738 store.update_user_settings::<ProjectSettings>(cx, f);
19739 });
19740 });
19741}
19742
19743pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
19744 cx.update(|cx| {
19745 assets::Assets.load_test_fonts(cx);
19746 let store = SettingsStore::test(cx);
19747 cx.set_global(store);
19748 theme::init(theme::LoadThemes::JustBase, cx);
19749 release_channel::init(SemanticVersion::default(), cx);
19750 client::init_settings(cx);
19751 language::init(cx);
19752 Project::init_settings(cx);
19753 workspace::init_settings(cx);
19754 crate::init(cx);
19755 });
19756
19757 update_test_language_settings(cx, f);
19758}
19759
19760#[track_caller]
19761fn assert_hunk_revert(
19762 not_reverted_text_with_selections: &str,
19763 expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
19764 expected_reverted_text_with_selections: &str,
19765 base_text: &str,
19766 cx: &mut EditorLspTestContext,
19767) {
19768 cx.set_state(not_reverted_text_with_selections);
19769 cx.set_head_text(base_text);
19770 cx.executor().run_until_parked();
19771
19772 let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
19773 let snapshot = editor.snapshot(window, cx);
19774 let reverted_hunk_statuses = snapshot
19775 .buffer_snapshot
19776 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
19777 .map(|hunk| hunk.status().kind)
19778 .collect::<Vec<_>>();
19779
19780 editor.git_restore(&Default::default(), window, cx);
19781 reverted_hunk_statuses
19782 });
19783 cx.executor().run_until_parked();
19784 cx.assert_editor_state(expected_reverted_text_with_selections);
19785 assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
19786}