1use super::*;
2use crate::{
3 scroll::scroll_amount::ScrollAmount,
4 test::{
5 assert_text_with_selections, build_editor, editor_lsp_test_context::EditorLspTestContext,
6 editor_test_context::EditorTestContext, select_ranges,
7 },
8 JoinLines,
9};
10
11use futures::StreamExt;
12use gpui::{div, TestAppContext, VisualTestContext, WindowBounds, WindowOptions};
13use indoc::indoc;
14use language::{
15 language_settings::{AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent},
16 BracketPairConfig,
17 Capability::ReadWrite,
18 FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageRegistry, Override, Point,
19};
20use parking_lot::Mutex;
21use project::project_settings::{LspSettings, ProjectSettings};
22use project::FakeFs;
23use serde_json::{self, json};
24use std::sync::atomic;
25use std::sync::atomic::AtomicUsize;
26use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
27use unindent::Unindent;
28use util::{
29 assert_set_eq,
30 test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
31};
32use workspace::{
33 item::{FollowEvent, FollowableItem, Item, ItemHandle},
34 NavigationEntry, ViewId,
35};
36
37#[gpui::test]
38fn test_edit_events(cx: &mut TestAppContext) {
39 init_test(cx, |_| {});
40
41 let buffer = cx.new_model(|cx| {
42 let mut buffer = language::Buffer::new(0, cx.entity_id().as_u64(), "123456");
43 buffer.set_group_interval(Duration::from_secs(1));
44 buffer
45 });
46
47 let events = Rc::new(RefCell::new(Vec::new()));
48 let editor1 = cx.add_window({
49 let events = events.clone();
50 |cx| {
51 let view = cx.view().clone();
52 cx.subscribe(&view, move |_, _, event: &EditorEvent, _| {
53 if matches!(event, EditorEvent::Edited | EditorEvent::BufferEdited) {
54 events.borrow_mut().push(("editor1", event.clone()));
55 }
56 })
57 .detach();
58 Editor::for_buffer(buffer.clone(), None, cx)
59 }
60 });
61
62 let editor2 = cx.add_window({
63 let events = events.clone();
64 |cx| {
65 cx.subscribe(&cx.view().clone(), move |_, _, event: &EditorEvent, _| {
66 if matches!(event, EditorEvent::Edited | EditorEvent::BufferEdited) {
67 events.borrow_mut().push(("editor2", event.clone()));
68 }
69 })
70 .detach();
71 Editor::for_buffer(buffer.clone(), None, cx)
72 }
73 });
74
75 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
76
77 // Mutating editor 1 will emit an `Edited` event only for that editor.
78 _ = editor1.update(cx, |editor, cx| editor.insert("X", cx));
79 assert_eq!(
80 mem::take(&mut *events.borrow_mut()),
81 [
82 ("editor1", EditorEvent::Edited),
83 ("editor1", EditorEvent::BufferEdited),
84 ("editor2", EditorEvent::BufferEdited),
85 ]
86 );
87
88 // Mutating editor 2 will emit an `Edited` event only for that editor.
89 _ = editor2.update(cx, |editor, cx| editor.delete(&Delete, cx));
90 assert_eq!(
91 mem::take(&mut *events.borrow_mut()),
92 [
93 ("editor2", EditorEvent::Edited),
94 ("editor1", EditorEvent::BufferEdited),
95 ("editor2", EditorEvent::BufferEdited),
96 ]
97 );
98
99 // Undoing on editor 1 will emit an `Edited` event only for that editor.
100 _ = editor1.update(cx, |editor, cx| editor.undo(&Undo, cx));
101 assert_eq!(
102 mem::take(&mut *events.borrow_mut()),
103 [
104 ("editor1", EditorEvent::Edited),
105 ("editor1", EditorEvent::BufferEdited),
106 ("editor2", EditorEvent::BufferEdited),
107 ]
108 );
109
110 // Redoing on editor 1 will emit an `Edited` event only for that editor.
111 _ = editor1.update(cx, |editor, cx| editor.redo(&Redo, cx));
112 assert_eq!(
113 mem::take(&mut *events.borrow_mut()),
114 [
115 ("editor1", EditorEvent::Edited),
116 ("editor1", EditorEvent::BufferEdited),
117 ("editor2", EditorEvent::BufferEdited),
118 ]
119 );
120
121 // Undoing on editor 2 will emit an `Edited` event only for that editor.
122 _ = editor2.update(cx, |editor, cx| editor.undo(&Undo, cx));
123 assert_eq!(
124 mem::take(&mut *events.borrow_mut()),
125 [
126 ("editor2", EditorEvent::Edited),
127 ("editor1", EditorEvent::BufferEdited),
128 ("editor2", EditorEvent::BufferEdited),
129 ]
130 );
131
132 // Redoing on editor 2 will emit an `Edited` event only for that editor.
133 _ = editor2.update(cx, |editor, cx| editor.redo(&Redo, cx));
134 assert_eq!(
135 mem::take(&mut *events.borrow_mut()),
136 [
137 ("editor2", EditorEvent::Edited),
138 ("editor1", EditorEvent::BufferEdited),
139 ("editor2", EditorEvent::BufferEdited),
140 ]
141 );
142
143 // No event is emitted when the mutation is a no-op.
144 _ = editor2.update(cx, |editor, cx| {
145 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
146
147 editor.backspace(&Backspace, cx);
148 });
149 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
150}
151
152#[gpui::test]
153fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
154 init_test(cx, |_| {});
155
156 let mut now = Instant::now();
157 let buffer = cx.new_model(|cx| language::Buffer::new(0, cx.entity_id().as_u64(), "123456"));
158 let group_interval = buffer.update(cx, |buffer, _| buffer.transaction_group_interval());
159 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
160 let editor = cx.add_window(|cx| build_editor(buffer.clone(), cx));
161
162 _ = editor.update(cx, |editor, cx| {
163 editor.start_transaction_at(now, cx);
164 editor.change_selections(None, cx, |s| s.select_ranges([2..4]));
165
166 editor.insert("cd", cx);
167 editor.end_transaction_at(now, cx);
168 assert_eq!(editor.text(cx), "12cd56");
169 assert_eq!(editor.selections.ranges(cx), vec![4..4]);
170
171 editor.start_transaction_at(now, cx);
172 editor.change_selections(None, cx, |s| s.select_ranges([4..5]));
173 editor.insert("e", cx);
174 editor.end_transaction_at(now, cx);
175 assert_eq!(editor.text(cx), "12cde6");
176 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
177
178 now += group_interval + Duration::from_millis(1);
179 editor.change_selections(None, cx, |s| s.select_ranges([2..2]));
180
181 // Simulate an edit in another editor
182 _ = buffer.update(cx, |buffer, cx| {
183 buffer.start_transaction_at(now, cx);
184 buffer.edit([(0..1, "a")], None, cx);
185 buffer.edit([(1..1, "b")], None, cx);
186 buffer.end_transaction_at(now, cx);
187 });
188
189 assert_eq!(editor.text(cx), "ab2cde6");
190 assert_eq!(editor.selections.ranges(cx), vec![3..3]);
191
192 // Last transaction happened past the group interval in a different editor.
193 // Undo it individually and don't restore selections.
194 editor.undo(&Undo, cx);
195 assert_eq!(editor.text(cx), "12cde6");
196 assert_eq!(editor.selections.ranges(cx), vec![2..2]);
197
198 // First two transactions happened within the group interval in this editor.
199 // Undo them together and restore selections.
200 editor.undo(&Undo, cx);
201 editor.undo(&Undo, cx); // Undo stack is empty here, so this is a no-op.
202 assert_eq!(editor.text(cx), "123456");
203 assert_eq!(editor.selections.ranges(cx), vec![0..0]);
204
205 // Redo the first two transactions together.
206 editor.redo(&Redo, cx);
207 assert_eq!(editor.text(cx), "12cde6");
208 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
209
210 // Redo the last transaction on its own.
211 editor.redo(&Redo, cx);
212 assert_eq!(editor.text(cx), "ab2cde6");
213 assert_eq!(editor.selections.ranges(cx), vec![6..6]);
214
215 // Test empty transactions.
216 editor.start_transaction_at(now, cx);
217 editor.end_transaction_at(now, cx);
218 editor.undo(&Undo, cx);
219 assert_eq!(editor.text(cx), "12cde6");
220 });
221}
222
223#[gpui::test]
224fn test_ime_composition(cx: &mut TestAppContext) {
225 init_test(cx, |_| {});
226
227 let buffer = cx.new_model(|cx| {
228 let mut buffer = language::Buffer::new(0, cx.entity_id().as_u64(), "abcde");
229 // Ensure automatic grouping doesn't occur.
230 buffer.set_group_interval(Duration::ZERO);
231 buffer
232 });
233
234 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
235 cx.add_window(|cx| {
236 let mut editor = build_editor(buffer.clone(), cx);
237
238 // Start a new IME composition.
239 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
240 editor.replace_and_mark_text_in_range(Some(0..1), "á", None, cx);
241 editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, cx);
242 assert_eq!(editor.text(cx), "äbcde");
243 assert_eq!(
244 editor.marked_text_ranges(cx),
245 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
246 );
247
248 // Finalize IME composition.
249 editor.replace_text_in_range(None, "ā", cx);
250 assert_eq!(editor.text(cx), "ābcde");
251 assert_eq!(editor.marked_text_ranges(cx), None);
252
253 // IME composition edits are grouped and are undone/redone at once.
254 editor.undo(&Default::default(), cx);
255 assert_eq!(editor.text(cx), "abcde");
256 assert_eq!(editor.marked_text_ranges(cx), None);
257 editor.redo(&Default::default(), cx);
258 assert_eq!(editor.text(cx), "ābcde");
259 assert_eq!(editor.marked_text_ranges(cx), None);
260
261 // Start a new IME composition.
262 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
263 assert_eq!(
264 editor.marked_text_ranges(cx),
265 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
266 );
267
268 // Undoing during an IME composition cancels it.
269 editor.undo(&Default::default(), cx);
270 assert_eq!(editor.text(cx), "ābcde");
271 assert_eq!(editor.marked_text_ranges(cx), None);
272
273 // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
274 editor.replace_and_mark_text_in_range(Some(4..999), "è", None, cx);
275 assert_eq!(editor.text(cx), "ābcdè");
276 assert_eq!(
277 editor.marked_text_ranges(cx),
278 Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
279 );
280
281 // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
282 editor.replace_text_in_range(Some(4..999), "ę", cx);
283 assert_eq!(editor.text(cx), "ābcdę");
284 assert_eq!(editor.marked_text_ranges(cx), None);
285
286 // Start a new IME composition with multiple cursors.
287 editor.change_selections(None, cx, |s| {
288 s.select_ranges([
289 OffsetUtf16(1)..OffsetUtf16(1),
290 OffsetUtf16(3)..OffsetUtf16(3),
291 OffsetUtf16(5)..OffsetUtf16(5),
292 ])
293 });
294 editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, cx);
295 assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
296 assert_eq!(
297 editor.marked_text_ranges(cx),
298 Some(vec![
299 OffsetUtf16(0)..OffsetUtf16(3),
300 OffsetUtf16(4)..OffsetUtf16(7),
301 OffsetUtf16(8)..OffsetUtf16(11)
302 ])
303 );
304
305 // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
306 editor.replace_and_mark_text_in_range(Some(1..2), "1", None, cx);
307 assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
308 assert_eq!(
309 editor.marked_text_ranges(cx),
310 Some(vec![
311 OffsetUtf16(1)..OffsetUtf16(2),
312 OffsetUtf16(5)..OffsetUtf16(6),
313 OffsetUtf16(9)..OffsetUtf16(10)
314 ])
315 );
316
317 // Finalize IME composition with multiple cursors.
318 editor.replace_text_in_range(Some(9..10), "2", cx);
319 assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
320 assert_eq!(editor.marked_text_ranges(cx), None);
321
322 editor
323 });
324}
325
326#[gpui::test]
327fn test_selection_with_mouse(cx: &mut TestAppContext) {
328 init_test(cx, |_| {});
329
330 let editor = cx.add_window(|cx| {
331 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
332 build_editor(buffer, cx)
333 });
334
335 _ = editor.update(cx, |view, cx| {
336 view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx);
337 });
338 assert_eq!(
339 editor
340 .update(cx, |view, cx| view.selections.display_ranges(cx))
341 .unwrap(),
342 [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)]
343 );
344
345 _ = editor.update(cx, |view, cx| {
346 view.update_selection(
347 DisplayPoint::new(3, 3),
348 0,
349 gpui::Point::<f32>::default(),
350 cx,
351 );
352 });
353
354 assert_eq!(
355 editor
356 .update(cx, |view, cx| view.selections.display_ranges(cx))
357 .unwrap(),
358 [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
359 );
360
361 _ = editor.update(cx, |view, cx| {
362 view.update_selection(
363 DisplayPoint::new(1, 1),
364 0,
365 gpui::Point::<f32>::default(),
366 cx,
367 );
368 });
369
370 assert_eq!(
371 editor
372 .update(cx, |view, cx| view.selections.display_ranges(cx))
373 .unwrap(),
374 [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
375 );
376
377 _ = editor.update(cx, |view, cx| {
378 view.end_selection(cx);
379 view.update_selection(
380 DisplayPoint::new(3, 3),
381 0,
382 gpui::Point::<f32>::default(),
383 cx,
384 );
385 });
386
387 assert_eq!(
388 editor
389 .update(cx, |view, cx| view.selections.display_ranges(cx))
390 .unwrap(),
391 [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
392 );
393
394 _ = editor.update(cx, |view, cx| {
395 view.begin_selection(DisplayPoint::new(3, 3), true, 1, cx);
396 view.update_selection(
397 DisplayPoint::new(0, 0),
398 0,
399 gpui::Point::<f32>::default(),
400 cx,
401 );
402 });
403
404 assert_eq!(
405 editor
406 .update(cx, |view, cx| view.selections.display_ranges(cx))
407 .unwrap(),
408 [
409 DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1),
410 DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)
411 ]
412 );
413
414 _ = editor.update(cx, |view, cx| {
415 view.end_selection(cx);
416 });
417
418 assert_eq!(
419 editor
420 .update(cx, |view, cx| view.selections.display_ranges(cx))
421 .unwrap(),
422 [DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)]
423 );
424}
425
426#[gpui::test]
427fn test_canceling_pending_selection(cx: &mut TestAppContext) {
428 init_test(cx, |_| {});
429
430 let view = cx.add_window(|cx| {
431 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
432 build_editor(buffer, cx)
433 });
434
435 _ = view.update(cx, |view, cx| {
436 view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx);
437 assert_eq!(
438 view.selections.display_ranges(cx),
439 [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)]
440 );
441 });
442
443 _ = view.update(cx, |view, cx| {
444 view.update_selection(
445 DisplayPoint::new(3, 3),
446 0,
447 gpui::Point::<f32>::default(),
448 cx,
449 );
450 assert_eq!(
451 view.selections.display_ranges(cx),
452 [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
453 );
454 });
455
456 _ = view.update(cx, |view, cx| {
457 view.cancel(&Cancel, cx);
458 view.update_selection(
459 DisplayPoint::new(1, 1),
460 0,
461 gpui::Point::<f32>::default(),
462 cx,
463 );
464 assert_eq!(
465 view.selections.display_ranges(cx),
466 [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
467 );
468 });
469}
470
471#[gpui::test]
472fn test_clone(cx: &mut TestAppContext) {
473 init_test(cx, |_| {});
474
475 let (text, selection_ranges) = marked_text_ranges(
476 indoc! {"
477 one
478 two
479 threeˇ
480 four
481 fiveˇ
482 "},
483 true,
484 );
485
486 let editor = cx.add_window(|cx| {
487 let buffer = MultiBuffer::build_simple(&text, cx);
488 build_editor(buffer, cx)
489 });
490
491 _ = editor.update(cx, |editor, cx| {
492 editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
493 editor.fold_ranges(
494 [
495 Point::new(1, 0)..Point::new(2, 0),
496 Point::new(3, 0)..Point::new(4, 0),
497 ],
498 true,
499 cx,
500 );
501 });
502
503 let cloned_editor = editor
504 .update(cx, |editor, cx| {
505 cx.open_window(Default::default(), |cx| cx.new_view(|cx| editor.clone(cx)))
506 })
507 .unwrap();
508
509 let snapshot = editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
510 let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
511
512 assert_eq!(
513 cloned_editor
514 .update(cx, |e, cx| e.display_text(cx))
515 .unwrap(),
516 editor.update(cx, |e, cx| e.display_text(cx)).unwrap()
517 );
518 assert_eq!(
519 cloned_snapshot
520 .folds_in_range(0..text.len())
521 .collect::<Vec<_>>(),
522 snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
523 );
524 assert_set_eq!(
525 cloned_editor
526 .update(cx, |editor, cx| editor.selections.ranges::<Point>(cx))
527 .unwrap(),
528 editor
529 .update(cx, |editor, cx| editor.selections.ranges(cx))
530 .unwrap()
531 );
532 assert_set_eq!(
533 cloned_editor
534 .update(cx, |e, cx| e.selections.display_ranges(cx))
535 .unwrap(),
536 editor
537 .update(cx, |e, cx| e.selections.display_ranges(cx))
538 .unwrap()
539 );
540}
541
542//todo!(editor navigate)
543#[gpui::test]
544async fn test_navigation_history(cx: &mut TestAppContext) {
545 init_test(cx, |_| {});
546
547 use workspace::item::Item;
548
549 let fs = FakeFs::new(cx.executor());
550 let project = Project::test(fs, [], cx).await;
551 let workspace = cx.add_window(|cx| Workspace::test_new(project, cx));
552 let pane = workspace
553 .update(cx, |workspace, _| workspace.active_pane().clone())
554 .unwrap();
555
556 _ = workspace.update(cx, |_v, cx| {
557 cx.new_view(|cx| {
558 let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
559 let mut editor = build_editor(buffer.clone(), cx);
560 let handle = cx.view();
561 editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
562
563 fn pop_history(editor: &mut Editor, cx: &mut WindowContext) -> Option<NavigationEntry> {
564 editor.nav_history.as_mut().unwrap().pop_backward(cx)
565 }
566
567 // Move the cursor a small distance.
568 // Nothing is added to the navigation history.
569 editor.change_selections(None, cx, |s| {
570 s.select_display_ranges([DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)])
571 });
572 editor.change_selections(None, cx, |s| {
573 s.select_display_ranges([DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)])
574 });
575 assert!(pop_history(&mut editor, cx).is_none());
576
577 // Move the cursor a large distance.
578 // The history can jump back to the previous position.
579 editor.change_selections(None, cx, |s| {
580 s.select_display_ranges([DisplayPoint::new(13, 0)..DisplayPoint::new(13, 3)])
581 });
582 let nav_entry = pop_history(&mut editor, cx).unwrap();
583 editor.navigate(nav_entry.data.unwrap(), cx);
584 assert_eq!(nav_entry.item.id(), cx.entity_id());
585 assert_eq!(
586 editor.selections.display_ranges(cx),
587 &[DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)]
588 );
589 assert!(pop_history(&mut editor, cx).is_none());
590
591 // Move the cursor a small distance via the mouse.
592 // Nothing is added to the navigation history.
593 editor.begin_selection(DisplayPoint::new(5, 0), false, 1, cx);
594 editor.end_selection(cx);
595 assert_eq!(
596 editor.selections.display_ranges(cx),
597 &[DisplayPoint::new(5, 0)..DisplayPoint::new(5, 0)]
598 );
599 assert!(pop_history(&mut editor, cx).is_none());
600
601 // Move the cursor a large distance via the mouse.
602 // The history can jump back to the previous position.
603 editor.begin_selection(DisplayPoint::new(15, 0), false, 1, cx);
604 editor.end_selection(cx);
605 assert_eq!(
606 editor.selections.display_ranges(cx),
607 &[DisplayPoint::new(15, 0)..DisplayPoint::new(15, 0)]
608 );
609 let nav_entry = pop_history(&mut editor, cx).unwrap();
610 editor.navigate(nav_entry.data.unwrap(), cx);
611 assert_eq!(nav_entry.item.id(), cx.entity_id());
612 assert_eq!(
613 editor.selections.display_ranges(cx),
614 &[DisplayPoint::new(5, 0)..DisplayPoint::new(5, 0)]
615 );
616 assert!(pop_history(&mut editor, cx).is_none());
617
618 // Set scroll position to check later
619 editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), cx);
620 let original_scroll_position = editor.scroll_manager.anchor();
621
622 // Jump to the end of the document and adjust scroll
623 editor.move_to_end(&MoveToEnd, cx);
624 editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), cx);
625 assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
626
627 let nav_entry = pop_history(&mut editor, cx).unwrap();
628 editor.navigate(nav_entry.data.unwrap(), cx);
629 assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
630
631 // Ensure we don't panic when navigation data contains invalid anchors *and* points.
632 let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
633 invalid_anchor.text_anchor.buffer_id = Some(999);
634 let invalid_point = Point::new(9999, 0);
635 editor.navigate(
636 Box::new(NavigationData {
637 cursor_anchor: invalid_anchor,
638 cursor_position: invalid_point,
639 scroll_anchor: ScrollAnchor {
640 anchor: invalid_anchor,
641 offset: Default::default(),
642 },
643 scroll_top_row: invalid_point.row,
644 }),
645 cx,
646 );
647 assert_eq!(
648 editor.selections.display_ranges(cx),
649 &[editor.max_point(cx)..editor.max_point(cx)]
650 );
651 assert_eq!(
652 editor.scroll_position(cx),
653 gpui::Point::new(0., editor.max_point(cx).row() as f32)
654 );
655
656 editor
657 })
658 });
659}
660
661#[gpui::test]
662fn test_cancel(cx: &mut TestAppContext) {
663 init_test(cx, |_| {});
664
665 let view = cx.add_window(|cx| {
666 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
667 build_editor(buffer, cx)
668 });
669
670 _ = view.update(cx, |view, cx| {
671 view.begin_selection(DisplayPoint::new(3, 4), false, 1, cx);
672 view.update_selection(
673 DisplayPoint::new(1, 1),
674 0,
675 gpui::Point::<f32>::default(),
676 cx,
677 );
678 view.end_selection(cx);
679
680 view.begin_selection(DisplayPoint::new(0, 1), true, 1, cx);
681 view.update_selection(
682 DisplayPoint::new(0, 3),
683 0,
684 gpui::Point::<f32>::default(),
685 cx,
686 );
687 view.end_selection(cx);
688 assert_eq!(
689 view.selections.display_ranges(cx),
690 [
691 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
692 DisplayPoint::new(3, 4)..DisplayPoint::new(1, 1),
693 ]
694 );
695 });
696
697 _ = view.update(cx, |view, cx| {
698 view.cancel(&Cancel, cx);
699 assert_eq!(
700 view.selections.display_ranges(cx),
701 [DisplayPoint::new(3, 4)..DisplayPoint::new(1, 1)]
702 );
703 });
704
705 _ = view.update(cx, |view, cx| {
706 view.cancel(&Cancel, cx);
707 assert_eq!(
708 view.selections.display_ranges(cx),
709 [DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1)]
710 );
711 });
712}
713
714#[gpui::test]
715fn test_fold_action(cx: &mut TestAppContext) {
716 init_test(cx, |_| {});
717
718 let view = cx.add_window(|cx| {
719 let buffer = MultiBuffer::build_simple(
720 &"
721 impl Foo {
722 // Hello!
723
724 fn a() {
725 1
726 }
727
728 fn b() {
729 2
730 }
731
732 fn c() {
733 3
734 }
735 }
736 "
737 .unindent(),
738 cx,
739 );
740 build_editor(buffer.clone(), cx)
741 });
742
743 _ = view.update(cx, |view, cx| {
744 view.change_selections(None, cx, |s| {
745 s.select_display_ranges([DisplayPoint::new(8, 0)..DisplayPoint::new(12, 0)]);
746 });
747 view.fold(&Fold, cx);
748 assert_eq!(
749 view.display_text(cx),
750 "
751 impl Foo {
752 // Hello!
753
754 fn a() {
755 1
756 }
757
758 fn b() {⋯
759 }
760
761 fn c() {⋯
762 }
763 }
764 "
765 .unindent(),
766 );
767
768 view.fold(&Fold, cx);
769 assert_eq!(
770 view.display_text(cx),
771 "
772 impl Foo {⋯
773 }
774 "
775 .unindent(),
776 );
777
778 view.unfold_lines(&UnfoldLines, cx);
779 assert_eq!(
780 view.display_text(cx),
781 "
782 impl Foo {
783 // Hello!
784
785 fn a() {
786 1
787 }
788
789 fn b() {⋯
790 }
791
792 fn c() {⋯
793 }
794 }
795 "
796 .unindent(),
797 );
798
799 view.unfold_lines(&UnfoldLines, cx);
800 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
801 });
802}
803
804#[gpui::test]
805fn test_move_cursor(cx: &mut TestAppContext) {
806 init_test(cx, |_| {});
807
808 let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
809 let view = cx.add_window(|cx| build_editor(buffer.clone(), cx));
810
811 _ = buffer.update(cx, |buffer, cx| {
812 buffer.edit(
813 vec![
814 (Point::new(1, 0)..Point::new(1, 0), "\t"),
815 (Point::new(1, 1)..Point::new(1, 1), "\t"),
816 ],
817 None,
818 cx,
819 );
820 });
821 _ = view.update(cx, |view, cx| {
822 assert_eq!(
823 view.selections.display_ranges(cx),
824 &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
825 );
826
827 view.move_down(&MoveDown, cx);
828 assert_eq!(
829 view.selections.display_ranges(cx),
830 &[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)]
831 );
832
833 view.move_right(&MoveRight, cx);
834 assert_eq!(
835 view.selections.display_ranges(cx),
836 &[DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4)]
837 );
838
839 view.move_left(&MoveLeft, cx);
840 assert_eq!(
841 view.selections.display_ranges(cx),
842 &[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)]
843 );
844
845 view.move_up(&MoveUp, cx);
846 assert_eq!(
847 view.selections.display_ranges(cx),
848 &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
849 );
850
851 view.move_to_end(&MoveToEnd, cx);
852 assert_eq!(
853 view.selections.display_ranges(cx),
854 &[DisplayPoint::new(5, 6)..DisplayPoint::new(5, 6)]
855 );
856
857 view.move_to_beginning(&MoveToBeginning, cx);
858 assert_eq!(
859 view.selections.display_ranges(cx),
860 &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
861 );
862
863 view.change_selections(None, cx, |s| {
864 s.select_display_ranges([DisplayPoint::new(0, 1)..DisplayPoint::new(0, 2)]);
865 });
866 view.select_to_beginning(&SelectToBeginning, cx);
867 assert_eq!(
868 view.selections.display_ranges(cx),
869 &[DisplayPoint::new(0, 1)..DisplayPoint::new(0, 0)]
870 );
871
872 view.select_to_end(&SelectToEnd, cx);
873 assert_eq!(
874 view.selections.display_ranges(cx),
875 &[DisplayPoint::new(0, 1)..DisplayPoint::new(5, 6)]
876 );
877 });
878}
879
880#[gpui::test]
881fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
882 init_test(cx, |_| {});
883
884 let view = cx.add_window(|cx| {
885 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε", cx);
886 build_editor(buffer.clone(), cx)
887 });
888
889 assert_eq!('ⓐ'.len_utf8(), 3);
890 assert_eq!('α'.len_utf8(), 2);
891
892 _ = view.update(cx, |view, cx| {
893 view.fold_ranges(
894 vec![
895 Point::new(0, 6)..Point::new(0, 12),
896 Point::new(1, 2)..Point::new(1, 4),
897 Point::new(2, 4)..Point::new(2, 8),
898 ],
899 true,
900 cx,
901 );
902 assert_eq!(view.display_text(cx), "ⓐⓑ⋯ⓔ\nab⋯e\nαβ⋯ε");
903
904 view.move_right(&MoveRight, cx);
905 assert_eq!(
906 view.selections.display_ranges(cx),
907 &[empty_range(0, "ⓐ".len())]
908 );
909 view.move_right(&MoveRight, cx);
910 assert_eq!(
911 view.selections.display_ranges(cx),
912 &[empty_range(0, "ⓐⓑ".len())]
913 );
914 view.move_right(&MoveRight, cx);
915 assert_eq!(
916 view.selections.display_ranges(cx),
917 &[empty_range(0, "ⓐⓑ⋯".len())]
918 );
919
920 view.move_down(&MoveDown, cx);
921 assert_eq!(
922 view.selections.display_ranges(cx),
923 &[empty_range(1, "ab⋯e".len())]
924 );
925 view.move_left(&MoveLeft, cx);
926 assert_eq!(
927 view.selections.display_ranges(cx),
928 &[empty_range(1, "ab⋯".len())]
929 );
930 view.move_left(&MoveLeft, cx);
931 assert_eq!(
932 view.selections.display_ranges(cx),
933 &[empty_range(1, "ab".len())]
934 );
935 view.move_left(&MoveLeft, cx);
936 assert_eq!(
937 view.selections.display_ranges(cx),
938 &[empty_range(1, "a".len())]
939 );
940
941 view.move_down(&MoveDown, cx);
942 assert_eq!(
943 view.selections.display_ranges(cx),
944 &[empty_range(2, "α".len())]
945 );
946 view.move_right(&MoveRight, cx);
947 assert_eq!(
948 view.selections.display_ranges(cx),
949 &[empty_range(2, "αβ".len())]
950 );
951 view.move_right(&MoveRight, cx);
952 assert_eq!(
953 view.selections.display_ranges(cx),
954 &[empty_range(2, "αβ⋯".len())]
955 );
956 view.move_right(&MoveRight, cx);
957 assert_eq!(
958 view.selections.display_ranges(cx),
959 &[empty_range(2, "αβ⋯ε".len())]
960 );
961
962 view.move_up(&MoveUp, cx);
963 assert_eq!(
964 view.selections.display_ranges(cx),
965 &[empty_range(1, "ab⋯e".len())]
966 );
967 view.move_down(&MoveDown, cx);
968 assert_eq!(
969 view.selections.display_ranges(cx),
970 &[empty_range(2, "αβ⋯ε".len())]
971 );
972 view.move_up(&MoveUp, cx);
973 assert_eq!(
974 view.selections.display_ranges(cx),
975 &[empty_range(1, "ab⋯e".len())]
976 );
977
978 view.move_up(&MoveUp, cx);
979 assert_eq!(
980 view.selections.display_ranges(cx),
981 &[empty_range(0, "ⓐⓑ".len())]
982 );
983 view.move_left(&MoveLeft, cx);
984 assert_eq!(
985 view.selections.display_ranges(cx),
986 &[empty_range(0, "ⓐ".len())]
987 );
988 view.move_left(&MoveLeft, cx);
989 assert_eq!(
990 view.selections.display_ranges(cx),
991 &[empty_range(0, "".len())]
992 );
993 });
994}
995
996//todo!(finish editor tests)
997#[gpui::test]
998fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
999 init_test(cx, |_| {});
1000
1001 let view = cx.add_window(|cx| {
1002 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
1003 build_editor(buffer.clone(), cx)
1004 });
1005 _ = view.update(cx, |view, cx| {
1006 view.change_selections(None, cx, |s| {
1007 s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
1008 });
1009 view.move_down(&MoveDown, cx);
1010 assert_eq!(
1011 view.selections.display_ranges(cx),
1012 &[empty_range(1, "abcd".len())]
1013 );
1014
1015 view.move_down(&MoveDown, cx);
1016 assert_eq!(
1017 view.selections.display_ranges(cx),
1018 &[empty_range(2, "αβγ".len())]
1019 );
1020
1021 view.move_down(&MoveDown, cx);
1022 assert_eq!(
1023 view.selections.display_ranges(cx),
1024 &[empty_range(3, "abcd".len())]
1025 );
1026
1027 view.move_down(&MoveDown, cx);
1028 assert_eq!(
1029 view.selections.display_ranges(cx),
1030 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1031 );
1032
1033 view.move_up(&MoveUp, cx);
1034 assert_eq!(
1035 view.selections.display_ranges(cx),
1036 &[empty_range(3, "abcd".len())]
1037 );
1038
1039 view.move_up(&MoveUp, cx);
1040 assert_eq!(
1041 view.selections.display_ranges(cx),
1042 &[empty_range(2, "αβγ".len())]
1043 );
1044 });
1045}
1046
1047#[gpui::test]
1048fn test_beginning_end_of_line(cx: &mut TestAppContext) {
1049 init_test(cx, |_| {});
1050
1051 let view = cx.add_window(|cx| {
1052 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1053 build_editor(buffer, cx)
1054 });
1055 _ = view.update(cx, |view, cx| {
1056 view.change_selections(None, cx, |s| {
1057 s.select_display_ranges([
1058 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
1059 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4),
1060 ]);
1061 });
1062 });
1063
1064 _ = view.update(cx, |view, cx| {
1065 view.move_to_beginning_of_line(&MoveToBeginningOfLine, cx);
1066 assert_eq!(
1067 view.selections.display_ranges(cx),
1068 &[
1069 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
1070 DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
1071 ]
1072 );
1073 });
1074
1075 _ = view.update(cx, |view, cx| {
1076 view.move_to_beginning_of_line(&MoveToBeginningOfLine, cx);
1077 assert_eq!(
1078 view.selections.display_ranges(cx),
1079 &[
1080 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
1081 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
1082 ]
1083 );
1084 });
1085
1086 _ = view.update(cx, |view, cx| {
1087 view.move_to_beginning_of_line(&MoveToBeginningOfLine, cx);
1088 assert_eq!(
1089 view.selections.display_ranges(cx),
1090 &[
1091 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
1092 DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
1093 ]
1094 );
1095 });
1096
1097 _ = view.update(cx, |view, cx| {
1098 view.move_to_end_of_line(&MoveToEndOfLine, cx);
1099 assert_eq!(
1100 view.selections.display_ranges(cx),
1101 &[
1102 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
1103 DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
1104 ]
1105 );
1106 });
1107
1108 // Moving to the end of line again is a no-op.
1109 _ = view.update(cx, |view, cx| {
1110 view.move_to_end_of_line(&MoveToEndOfLine, cx);
1111 assert_eq!(
1112 view.selections.display_ranges(cx),
1113 &[
1114 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
1115 DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
1116 ]
1117 );
1118 });
1119
1120 _ = view.update(cx, |view, cx| {
1121 view.move_left(&MoveLeft, cx);
1122 view.select_to_beginning_of_line(
1123 &SelectToBeginningOfLine {
1124 stop_at_soft_wraps: true,
1125 },
1126 cx,
1127 );
1128 assert_eq!(
1129 view.selections.display_ranges(cx),
1130 &[
1131 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0),
1132 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 2),
1133 ]
1134 );
1135 });
1136
1137 _ = view.update(cx, |view, cx| {
1138 view.select_to_beginning_of_line(
1139 &SelectToBeginningOfLine {
1140 stop_at_soft_wraps: true,
1141 },
1142 cx,
1143 );
1144 assert_eq!(
1145 view.selections.display_ranges(cx),
1146 &[
1147 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0),
1148 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 0),
1149 ]
1150 );
1151 });
1152
1153 _ = view.update(cx, |view, cx| {
1154 view.select_to_beginning_of_line(
1155 &SelectToBeginningOfLine {
1156 stop_at_soft_wraps: true,
1157 },
1158 cx,
1159 );
1160 assert_eq!(
1161 view.selections.display_ranges(cx),
1162 &[
1163 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0),
1164 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 2),
1165 ]
1166 );
1167 });
1168
1169 _ = view.update(cx, |view, cx| {
1170 view.select_to_end_of_line(
1171 &SelectToEndOfLine {
1172 stop_at_soft_wraps: true,
1173 },
1174 cx,
1175 );
1176 assert_eq!(
1177 view.selections.display_ranges(cx),
1178 &[
1179 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3),
1180 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 5),
1181 ]
1182 );
1183 });
1184
1185 _ = view.update(cx, |view, cx| {
1186 view.delete_to_end_of_line(&DeleteToEndOfLine, cx);
1187 assert_eq!(view.display_text(cx), "ab\n de");
1188 assert_eq!(
1189 view.selections.display_ranges(cx),
1190 &[
1191 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
1192 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4),
1193 ]
1194 );
1195 });
1196
1197 _ = view.update(cx, |view, cx| {
1198 view.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
1199 assert_eq!(view.display_text(cx), "\n");
1200 assert_eq!(
1201 view.selections.display_ranges(cx),
1202 &[
1203 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
1204 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
1205 ]
1206 );
1207 });
1208}
1209
1210#[gpui::test]
1211fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1212 init_test(cx, |_| {});
1213
1214 let view = cx.add_window(|cx| {
1215 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1216 build_editor(buffer, cx)
1217 });
1218 _ = view.update(cx, |view, cx| {
1219 view.change_selections(None, cx, |s| {
1220 s.select_display_ranges([
1221 DisplayPoint::new(0, 11)..DisplayPoint::new(0, 11),
1222 DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4),
1223 ])
1224 });
1225
1226 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1227 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1228
1229 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1230 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", view, cx);
1231
1232 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1233 assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", view, cx);
1234
1235 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1236 assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1237
1238 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1239 assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", view, cx);
1240
1241 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1242 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", view, cx);
1243
1244 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1245 assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1246
1247 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1248 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1249
1250 view.move_right(&MoveRight, cx);
1251 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1252 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1253
1254 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1255 assert_selection_ranges("use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}", view, cx);
1256
1257 view.select_to_next_word_end(&SelectToNextWordEnd, cx);
1258 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1259 });
1260}
1261
1262//todo!(finish editor tests)
1263#[gpui::test]
1264fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1265 init_test(cx, |_| {});
1266
1267 let view = cx.add_window(|cx| {
1268 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1269 build_editor(buffer, cx)
1270 });
1271
1272 _ = view.update(cx, |view, cx| {
1273 view.set_wrap_width(Some(140.0.into()), cx);
1274 assert_eq!(
1275 view.display_text(cx),
1276 "use one::{\n two::three::\n four::five\n};"
1277 );
1278
1279 view.change_selections(None, cx, |s| {
1280 s.select_display_ranges([DisplayPoint::new(1, 7)..DisplayPoint::new(1, 7)]);
1281 });
1282
1283 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1284 assert_eq!(
1285 view.selections.display_ranges(cx),
1286 &[DisplayPoint::new(1, 9)..DisplayPoint::new(1, 9)]
1287 );
1288
1289 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1290 assert_eq!(
1291 view.selections.display_ranges(cx),
1292 &[DisplayPoint::new(1, 14)..DisplayPoint::new(1, 14)]
1293 );
1294
1295 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1296 assert_eq!(
1297 view.selections.display_ranges(cx),
1298 &[DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4)]
1299 );
1300
1301 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1302 assert_eq!(
1303 view.selections.display_ranges(cx),
1304 &[DisplayPoint::new(2, 8)..DisplayPoint::new(2, 8)]
1305 );
1306
1307 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1308 assert_eq!(
1309 view.selections.display_ranges(cx),
1310 &[DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4)]
1311 );
1312
1313 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1314 assert_eq!(
1315 view.selections.display_ranges(cx),
1316 &[DisplayPoint::new(1, 14)..DisplayPoint::new(1, 14)]
1317 );
1318 });
1319}
1320
1321//todo!(simulate_resize)
1322#[gpui::test]
1323async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) {
1324 init_test(cx, |_| {});
1325 let mut cx = EditorTestContext::new(cx).await;
1326
1327 let line_height = cx.editor(|editor, cx| {
1328 editor
1329 .style()
1330 .unwrap()
1331 .text
1332 .line_height_in_pixels(cx.rem_size())
1333 });
1334 cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
1335
1336 cx.set_state(
1337 &r#"ˇone
1338 two
1339
1340 three
1341 fourˇ
1342 five
1343
1344 six"#
1345 .unindent(),
1346 );
1347
1348 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1349 cx.assert_editor_state(
1350 &r#"one
1351 two
1352 ˇ
1353 three
1354 four
1355 five
1356 ˇ
1357 six"#
1358 .unindent(),
1359 );
1360
1361 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1362 cx.assert_editor_state(
1363 &r#"one
1364 two
1365
1366 three
1367 four
1368 five
1369 ˇ
1370 sixˇ"#
1371 .unindent(),
1372 );
1373
1374 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1375 cx.assert_editor_state(
1376 &r#"one
1377 two
1378
1379 three
1380 four
1381 five
1382
1383 sixˇ"#
1384 .unindent(),
1385 );
1386
1387 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1388 cx.assert_editor_state(
1389 &r#"one
1390 two
1391
1392 three
1393 four
1394 five
1395 ˇ
1396 six"#
1397 .unindent(),
1398 );
1399
1400 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1401 cx.assert_editor_state(
1402 &r#"one
1403 two
1404 ˇ
1405 three
1406 four
1407 five
1408
1409 six"#
1410 .unindent(),
1411 );
1412
1413 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1414 cx.assert_editor_state(
1415 &r#"ˇone
1416 two
1417
1418 three
1419 four
1420 five
1421
1422 six"#
1423 .unindent(),
1424 );
1425}
1426
1427#[gpui::test]
1428async fn test_scroll_page_up_page_down(cx: &mut gpui::TestAppContext) {
1429 init_test(cx, |_| {});
1430 let mut cx = EditorTestContext::new(cx).await;
1431 let line_height = cx.editor(|editor, cx| {
1432 editor
1433 .style()
1434 .unwrap()
1435 .text
1436 .line_height_in_pixels(cx.rem_size())
1437 });
1438 let window = cx.window;
1439 cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
1440
1441 cx.set_state(
1442 &r#"ˇone
1443 two
1444 three
1445 four
1446 five
1447 six
1448 seven
1449 eight
1450 nine
1451 ten
1452 "#,
1453 );
1454
1455 cx.update_editor(|editor, cx| {
1456 assert_eq!(
1457 editor.snapshot(cx).scroll_position(),
1458 gpui::Point::new(0., 0.)
1459 );
1460 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1461 assert_eq!(
1462 editor.snapshot(cx).scroll_position(),
1463 gpui::Point::new(0., 3.)
1464 );
1465 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1466 assert_eq!(
1467 editor.snapshot(cx).scroll_position(),
1468 gpui::Point::new(0., 6.)
1469 );
1470 editor.scroll_screen(&ScrollAmount::Page(-1.), cx);
1471 assert_eq!(
1472 editor.snapshot(cx).scroll_position(),
1473 gpui::Point::new(0., 3.)
1474 );
1475
1476 editor.scroll_screen(&ScrollAmount::Page(-0.5), cx);
1477 assert_eq!(
1478 editor.snapshot(cx).scroll_position(),
1479 gpui::Point::new(0., 1.)
1480 );
1481 editor.scroll_screen(&ScrollAmount::Page(0.5), cx);
1482 assert_eq!(
1483 editor.snapshot(cx).scroll_position(),
1484 gpui::Point::new(0., 3.)
1485 );
1486 });
1487}
1488
1489#[gpui::test]
1490async fn test_autoscroll(cx: &mut gpui::TestAppContext) {
1491 init_test(cx, |_| {});
1492 let mut cx = EditorTestContext::new(cx).await;
1493
1494 let line_height = cx.update_editor(|editor, cx| {
1495 editor.set_vertical_scroll_margin(2, cx);
1496 editor
1497 .style()
1498 .unwrap()
1499 .text
1500 .line_height_in_pixels(cx.rem_size())
1501 });
1502 let window = cx.window;
1503 cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
1504
1505 cx.set_state(
1506 &r#"ˇone
1507 two
1508 three
1509 four
1510 five
1511 six
1512 seven
1513 eight
1514 nine
1515 ten
1516 "#,
1517 );
1518 cx.update_editor(|editor, cx| {
1519 assert_eq!(
1520 editor.snapshot(cx).scroll_position(),
1521 gpui::Point::new(0., 0.0)
1522 );
1523 });
1524
1525 // Add a cursor below the visible area. Since both cursors cannot fit
1526 // on screen, the editor autoscrolls to reveal the newest cursor, and
1527 // allows the vertical scroll margin below that cursor.
1528 cx.update_editor(|editor, cx| {
1529 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
1530 selections.select_ranges([
1531 Point::new(0, 0)..Point::new(0, 0),
1532 Point::new(6, 0)..Point::new(6, 0),
1533 ]);
1534 })
1535 });
1536 cx.update_editor(|editor, cx| {
1537 assert_eq!(
1538 editor.snapshot(cx).scroll_position(),
1539 gpui::Point::new(0., 3.0)
1540 );
1541 });
1542
1543 // Move down. The editor cursor scrolls down to track the newest cursor.
1544 cx.update_editor(|editor, cx| {
1545 editor.move_down(&Default::default(), cx);
1546 });
1547 cx.update_editor(|editor, cx| {
1548 assert_eq!(
1549 editor.snapshot(cx).scroll_position(),
1550 gpui::Point::new(0., 4.0)
1551 );
1552 });
1553
1554 // Add a cursor above the visible area. Since both cursors fit on screen,
1555 // the editor scrolls to show both.
1556 cx.update_editor(|editor, cx| {
1557 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
1558 selections.select_ranges([
1559 Point::new(1, 0)..Point::new(1, 0),
1560 Point::new(6, 0)..Point::new(6, 0),
1561 ]);
1562 })
1563 });
1564 cx.update_editor(|editor, cx| {
1565 assert_eq!(
1566 editor.snapshot(cx).scroll_position(),
1567 gpui::Point::new(0., 1.0)
1568 );
1569 });
1570}
1571
1572#[gpui::test]
1573async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
1574 init_test(cx, |_| {});
1575 let mut cx = EditorTestContext::new(cx).await;
1576
1577 let line_height = cx.editor(|editor, cx| {
1578 editor
1579 .style()
1580 .unwrap()
1581 .text
1582 .line_height_in_pixels(cx.rem_size())
1583 });
1584 let window = cx.window;
1585 cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
1586 cx.set_state(
1587 &r#"
1588 ˇone
1589 two
1590 threeˇ
1591 four
1592 five
1593 six
1594 seven
1595 eight
1596 nine
1597 ten
1598 "#
1599 .unindent(),
1600 );
1601
1602 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
1603 cx.assert_editor_state(
1604 &r#"
1605 one
1606 two
1607 three
1608 ˇfour
1609 five
1610 sixˇ
1611 seven
1612 eight
1613 nine
1614 ten
1615 "#
1616 .unindent(),
1617 );
1618
1619 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
1620 cx.assert_editor_state(
1621 &r#"
1622 one
1623 two
1624 three
1625 four
1626 five
1627 six
1628 ˇseven
1629 eight
1630 nineˇ
1631 ten
1632 "#
1633 .unindent(),
1634 );
1635
1636 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
1637 cx.assert_editor_state(
1638 &r#"
1639 one
1640 two
1641 three
1642 ˇfour
1643 five
1644 sixˇ
1645 seven
1646 eight
1647 nine
1648 ten
1649 "#
1650 .unindent(),
1651 );
1652
1653 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
1654 cx.assert_editor_state(
1655 &r#"
1656 ˇone
1657 two
1658 threeˇ
1659 four
1660 five
1661 six
1662 seven
1663 eight
1664 nine
1665 ten
1666 "#
1667 .unindent(),
1668 );
1669
1670 // Test select collapsing
1671 cx.update_editor(|editor, cx| {
1672 editor.move_page_down(&MovePageDown::default(), cx);
1673 editor.move_page_down(&MovePageDown::default(), cx);
1674 editor.move_page_down(&MovePageDown::default(), cx);
1675 });
1676 cx.assert_editor_state(
1677 &r#"
1678 one
1679 two
1680 three
1681 four
1682 five
1683 six
1684 seven
1685 eight
1686 nine
1687 ˇten
1688 ˇ"#
1689 .unindent(),
1690 );
1691}
1692
1693#[gpui::test]
1694async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
1695 init_test(cx, |_| {});
1696 let mut cx = EditorTestContext::new(cx).await;
1697 cx.set_state("one «two threeˇ» four");
1698 cx.update_editor(|editor, cx| {
1699 editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
1700 assert_eq!(editor.text(cx), " four");
1701 });
1702}
1703
1704#[gpui::test]
1705fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
1706 init_test(cx, |_| {});
1707
1708 let view = cx.add_window(|cx| {
1709 let buffer = MultiBuffer::build_simple("one two three four", cx);
1710 build_editor(buffer.clone(), cx)
1711 });
1712
1713 _ = view.update(cx, |view, cx| {
1714 view.change_selections(None, cx, |s| {
1715 s.select_display_ranges([
1716 // an empty selection - the preceding word fragment is deleted
1717 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
1718 // characters selected - they are deleted
1719 DisplayPoint::new(0, 9)..DisplayPoint::new(0, 12),
1720 ])
1721 });
1722 view.delete_to_previous_word_start(&DeleteToPreviousWordStart, cx);
1723 assert_eq!(view.buffer.read(cx).read(cx).text(), "e two te four");
1724 });
1725
1726 _ = view.update(cx, |view, cx| {
1727 view.change_selections(None, cx, |s| {
1728 s.select_display_ranges([
1729 // an empty selection - the following word fragment is deleted
1730 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
1731 // characters selected - they are deleted
1732 DisplayPoint::new(0, 9)..DisplayPoint::new(0, 10),
1733 ])
1734 });
1735 view.delete_to_next_word_end(&DeleteToNextWordEnd, cx);
1736 assert_eq!(view.buffer.read(cx).read(cx).text(), "e t te our");
1737 });
1738}
1739
1740#[gpui::test]
1741fn test_newline(cx: &mut TestAppContext) {
1742 init_test(cx, |_| {});
1743
1744 let view = cx.add_window(|cx| {
1745 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
1746 build_editor(buffer.clone(), cx)
1747 });
1748
1749 _ = view.update(cx, |view, cx| {
1750 view.change_selections(None, cx, |s| {
1751 s.select_display_ranges([
1752 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
1753 DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
1754 DisplayPoint::new(1, 6)..DisplayPoint::new(1, 6),
1755 ])
1756 });
1757
1758 view.newline(&Newline, cx);
1759 assert_eq!(view.text(cx), "aa\naa\n \n bb\n bb\n");
1760 });
1761}
1762
1763#[gpui::test]
1764fn test_newline_with_old_selections(cx: &mut TestAppContext) {
1765 init_test(cx, |_| {});
1766
1767 let editor = cx.add_window(|cx| {
1768 let buffer = MultiBuffer::build_simple(
1769 "
1770 a
1771 b(
1772 X
1773 )
1774 c(
1775 X
1776 )
1777 "
1778 .unindent()
1779 .as_str(),
1780 cx,
1781 );
1782 let mut editor = build_editor(buffer.clone(), cx);
1783 editor.change_selections(None, cx, |s| {
1784 s.select_ranges([
1785 Point::new(2, 4)..Point::new(2, 5),
1786 Point::new(5, 4)..Point::new(5, 5),
1787 ])
1788 });
1789 editor
1790 });
1791
1792 _ = editor.update(cx, |editor, cx| {
1793 // Edit the buffer directly, deleting ranges surrounding the editor's selections
1794 editor.buffer.update(cx, |buffer, cx| {
1795 buffer.edit(
1796 [
1797 (Point::new(1, 2)..Point::new(3, 0), ""),
1798 (Point::new(4, 2)..Point::new(6, 0), ""),
1799 ],
1800 None,
1801 cx,
1802 );
1803 assert_eq!(
1804 buffer.read(cx).text(),
1805 "
1806 a
1807 b()
1808 c()
1809 "
1810 .unindent()
1811 );
1812 });
1813 assert_eq!(
1814 editor.selections.ranges(cx),
1815 &[
1816 Point::new(1, 2)..Point::new(1, 2),
1817 Point::new(2, 2)..Point::new(2, 2),
1818 ],
1819 );
1820
1821 editor.newline(&Newline, cx);
1822 assert_eq!(
1823 editor.text(cx),
1824 "
1825 a
1826 b(
1827 )
1828 c(
1829 )
1830 "
1831 .unindent()
1832 );
1833
1834 // The selections are moved after the inserted newlines
1835 assert_eq!(
1836 editor.selections.ranges(cx),
1837 &[
1838 Point::new(2, 0)..Point::new(2, 0),
1839 Point::new(4, 0)..Point::new(4, 0),
1840 ],
1841 );
1842 });
1843}
1844
1845#[gpui::test]
1846async fn test_newline_above(cx: &mut gpui::TestAppContext) {
1847 init_test(cx, |settings| {
1848 settings.defaults.tab_size = NonZeroU32::new(4)
1849 });
1850
1851 let language = Arc::new(
1852 Language::new(
1853 LanguageConfig::default(),
1854 Some(tree_sitter_rust::language()),
1855 )
1856 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
1857 .unwrap(),
1858 );
1859
1860 let mut cx = EditorTestContext::new(cx).await;
1861 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
1862 cx.set_state(indoc! {"
1863 const a: ˇA = (
1864 (ˇ
1865 «const_functionˇ»(ˇ),
1866 so«mˇ»et«hˇ»ing_ˇelse,ˇ
1867 )ˇ
1868 ˇ);ˇ
1869 "});
1870
1871 cx.update_editor(|e, cx| e.newline_above(&NewlineAbove, cx));
1872 cx.assert_editor_state(indoc! {"
1873 ˇ
1874 const a: A = (
1875 ˇ
1876 (
1877 ˇ
1878 ˇ
1879 const_function(),
1880 ˇ
1881 ˇ
1882 ˇ
1883 ˇ
1884 something_else,
1885 ˇ
1886 )
1887 ˇ
1888 ˇ
1889 );
1890 "});
1891}
1892
1893#[gpui::test]
1894async fn test_newline_below(cx: &mut gpui::TestAppContext) {
1895 init_test(cx, |settings| {
1896 settings.defaults.tab_size = NonZeroU32::new(4)
1897 });
1898
1899 let language = Arc::new(
1900 Language::new(
1901 LanguageConfig::default(),
1902 Some(tree_sitter_rust::language()),
1903 )
1904 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
1905 .unwrap(),
1906 );
1907
1908 let mut cx = EditorTestContext::new(cx).await;
1909 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
1910 cx.set_state(indoc! {"
1911 const a: ˇA = (
1912 (ˇ
1913 «const_functionˇ»(ˇ),
1914 so«mˇ»et«hˇ»ing_ˇelse,ˇ
1915 )ˇ
1916 ˇ);ˇ
1917 "});
1918
1919 cx.update_editor(|e, cx| e.newline_below(&NewlineBelow, cx));
1920 cx.assert_editor_state(indoc! {"
1921 const a: A = (
1922 ˇ
1923 (
1924 ˇ
1925 const_function(),
1926 ˇ
1927 ˇ
1928 something_else,
1929 ˇ
1930 ˇ
1931 ˇ
1932 ˇ
1933 )
1934 ˇ
1935 );
1936 ˇ
1937 ˇ
1938 "});
1939}
1940
1941#[gpui::test]
1942async fn test_newline_comments(cx: &mut gpui::TestAppContext) {
1943 init_test(cx, |settings| {
1944 settings.defaults.tab_size = NonZeroU32::new(4)
1945 });
1946
1947 let language = Arc::new(Language::new(
1948 LanguageConfig {
1949 line_comment: Some("//".into()),
1950 ..LanguageConfig::default()
1951 },
1952 None,
1953 ));
1954 {
1955 let mut cx = EditorTestContext::new(cx).await;
1956 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
1957 cx.set_state(indoc! {"
1958 // Fooˇ
1959 "});
1960
1961 cx.update_editor(|e, cx| e.newline(&Newline, cx));
1962 cx.assert_editor_state(indoc! {"
1963 // Foo
1964 //ˇ
1965 "});
1966 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
1967 cx.set_state(indoc! {"
1968 ˇ// Foo
1969 "});
1970 cx.update_editor(|e, cx| e.newline(&Newline, cx));
1971 cx.assert_editor_state(indoc! {"
1972
1973 ˇ// Foo
1974 "});
1975 }
1976 // Ensure that comment continuations can be disabled.
1977 update_test_language_settings(cx, |settings| {
1978 settings.defaults.extend_comment_on_newline = Some(false);
1979 });
1980 let mut cx = EditorTestContext::new(cx).await;
1981 cx.set_state(indoc! {"
1982 // Fooˇ
1983 "});
1984 cx.update_editor(|e, cx| e.newline(&Newline, cx));
1985 cx.assert_editor_state(indoc! {"
1986 // Foo
1987 ˇ
1988 "});
1989}
1990
1991#[gpui::test]
1992fn test_insert_with_old_selections(cx: &mut TestAppContext) {
1993 init_test(cx, |_| {});
1994
1995 let editor = cx.add_window(|cx| {
1996 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
1997 let mut editor = build_editor(buffer.clone(), cx);
1998 editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
1999 editor
2000 });
2001
2002 _ = editor.update(cx, |editor, cx| {
2003 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2004 editor.buffer.update(cx, |buffer, cx| {
2005 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
2006 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
2007 });
2008 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
2009
2010 editor.insert("Z", cx);
2011 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
2012
2013 // The selections are moved after the inserted characters
2014 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
2015 });
2016}
2017
2018#[gpui::test]
2019async fn test_tab(cx: &mut gpui::TestAppContext) {
2020 init_test(cx, |settings| {
2021 settings.defaults.tab_size = NonZeroU32::new(3)
2022 });
2023
2024 let mut cx = EditorTestContext::new(cx).await;
2025 cx.set_state(indoc! {"
2026 ˇabˇc
2027 ˇ🏀ˇ🏀ˇefg
2028 dˇ
2029 "});
2030 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2031 cx.assert_editor_state(indoc! {"
2032 ˇab ˇc
2033 ˇ🏀 ˇ🏀 ˇefg
2034 d ˇ
2035 "});
2036
2037 cx.set_state(indoc! {"
2038 a
2039 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2040 "});
2041 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2042 cx.assert_editor_state(indoc! {"
2043 a
2044 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2045 "});
2046}
2047
2048#[gpui::test]
2049async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) {
2050 init_test(cx, |_| {});
2051
2052 let mut cx = EditorTestContext::new(cx).await;
2053 let language = Arc::new(
2054 Language::new(
2055 LanguageConfig::default(),
2056 Some(tree_sitter_rust::language()),
2057 )
2058 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2059 .unwrap(),
2060 );
2061 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2062
2063 // cursors that are already at the suggested indent level insert
2064 // a soft tab. cursors that are to the left of the suggested indent
2065 // auto-indent their line.
2066 cx.set_state(indoc! {"
2067 ˇ
2068 const a: B = (
2069 c(
2070 d(
2071 ˇ
2072 )
2073 ˇ
2074 ˇ )
2075 );
2076 "});
2077 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2078 cx.assert_editor_state(indoc! {"
2079 ˇ
2080 const a: B = (
2081 c(
2082 d(
2083 ˇ
2084 )
2085 ˇ
2086 ˇ)
2087 );
2088 "});
2089
2090 // handle auto-indent when there are multiple cursors on the same line
2091 cx.set_state(indoc! {"
2092 const a: B = (
2093 c(
2094 ˇ ˇ
2095 ˇ )
2096 );
2097 "});
2098 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2099 cx.assert_editor_state(indoc! {"
2100 const a: B = (
2101 c(
2102 ˇ
2103 ˇ)
2104 );
2105 "});
2106}
2107
2108#[gpui::test]
2109async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
2110 init_test(cx, |settings| {
2111 settings.defaults.tab_size = NonZeroU32::new(4)
2112 });
2113
2114 let language = Arc::new(
2115 Language::new(
2116 LanguageConfig::default(),
2117 Some(tree_sitter_rust::language()),
2118 )
2119 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2120 .unwrap(),
2121 );
2122
2123 let mut cx = EditorTestContext::new(cx).await;
2124 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2125 cx.set_state(indoc! {"
2126 fn a() {
2127 if b {
2128 \t ˇc
2129 }
2130 }
2131 "});
2132
2133 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2134 cx.assert_editor_state(indoc! {"
2135 fn a() {
2136 if b {
2137 ˇc
2138 }
2139 }
2140 "});
2141}
2142
2143#[gpui::test]
2144async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
2145 init_test(cx, |settings| {
2146 settings.defaults.tab_size = NonZeroU32::new(4);
2147 });
2148
2149 let mut cx = EditorTestContext::new(cx).await;
2150
2151 cx.set_state(indoc! {"
2152 «oneˇ» «twoˇ»
2153 three
2154 four
2155 "});
2156 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2157 cx.assert_editor_state(indoc! {"
2158 «oneˇ» «twoˇ»
2159 three
2160 four
2161 "});
2162
2163 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2164 cx.assert_editor_state(indoc! {"
2165 «oneˇ» «twoˇ»
2166 three
2167 four
2168 "});
2169
2170 // select across line ending
2171 cx.set_state(indoc! {"
2172 one two
2173 t«hree
2174 ˇ» four
2175 "});
2176 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2177 cx.assert_editor_state(indoc! {"
2178 one two
2179 t«hree
2180 ˇ» four
2181 "});
2182
2183 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2184 cx.assert_editor_state(indoc! {"
2185 one two
2186 t«hree
2187 ˇ» four
2188 "});
2189
2190 // Ensure that indenting/outdenting works when the cursor is at column 0.
2191 cx.set_state(indoc! {"
2192 one two
2193 ˇthree
2194 four
2195 "});
2196 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2197 cx.assert_editor_state(indoc! {"
2198 one two
2199 ˇthree
2200 four
2201 "});
2202
2203 cx.set_state(indoc! {"
2204 one two
2205 ˇ three
2206 four
2207 "});
2208 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2209 cx.assert_editor_state(indoc! {"
2210 one two
2211 ˇthree
2212 four
2213 "});
2214}
2215
2216#[gpui::test]
2217async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
2218 init_test(cx, |settings| {
2219 settings.defaults.hard_tabs = Some(true);
2220 });
2221
2222 let mut cx = EditorTestContext::new(cx).await;
2223
2224 // select two ranges on one line
2225 cx.set_state(indoc! {"
2226 «oneˇ» «twoˇ»
2227 three
2228 four
2229 "});
2230 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2231 cx.assert_editor_state(indoc! {"
2232 \t«oneˇ» «twoˇ»
2233 three
2234 four
2235 "});
2236 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2237 cx.assert_editor_state(indoc! {"
2238 \t\t«oneˇ» «twoˇ»
2239 three
2240 four
2241 "});
2242 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2243 cx.assert_editor_state(indoc! {"
2244 \t«oneˇ» «twoˇ»
2245 three
2246 four
2247 "});
2248 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2249 cx.assert_editor_state(indoc! {"
2250 «oneˇ» «twoˇ»
2251 three
2252 four
2253 "});
2254
2255 // select across a line ending
2256 cx.set_state(indoc! {"
2257 one two
2258 t«hree
2259 ˇ»four
2260 "});
2261 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2262 cx.assert_editor_state(indoc! {"
2263 one two
2264 \tt«hree
2265 ˇ»four
2266 "});
2267 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2268 cx.assert_editor_state(indoc! {"
2269 one two
2270 \t\tt«hree
2271 ˇ»four
2272 "});
2273 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2274 cx.assert_editor_state(indoc! {"
2275 one two
2276 \tt«hree
2277 ˇ»four
2278 "});
2279 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2280 cx.assert_editor_state(indoc! {"
2281 one two
2282 t«hree
2283 ˇ»four
2284 "});
2285
2286 // Ensure that indenting/outdenting works when the cursor is at column 0.
2287 cx.set_state(indoc! {"
2288 one two
2289 ˇthree
2290 four
2291 "});
2292 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2293 cx.assert_editor_state(indoc! {"
2294 one two
2295 ˇthree
2296 four
2297 "});
2298 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2299 cx.assert_editor_state(indoc! {"
2300 one two
2301 \tˇthree
2302 four
2303 "});
2304 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2305 cx.assert_editor_state(indoc! {"
2306 one two
2307 ˇthree
2308 four
2309 "});
2310}
2311
2312#[gpui::test]
2313fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
2314 init_test(cx, |settings| {
2315 settings.languages.extend([
2316 (
2317 "TOML".into(),
2318 LanguageSettingsContent {
2319 tab_size: NonZeroU32::new(2),
2320 ..Default::default()
2321 },
2322 ),
2323 (
2324 "Rust".into(),
2325 LanguageSettingsContent {
2326 tab_size: NonZeroU32::new(4),
2327 ..Default::default()
2328 },
2329 ),
2330 ]);
2331 });
2332
2333 let toml_language = Arc::new(Language::new(
2334 LanguageConfig {
2335 name: "TOML".into(),
2336 ..Default::default()
2337 },
2338 None,
2339 ));
2340 let rust_language = Arc::new(Language::new(
2341 LanguageConfig {
2342 name: "Rust".into(),
2343 ..Default::default()
2344 },
2345 None,
2346 ));
2347
2348 let toml_buffer = cx.new_model(|cx| {
2349 Buffer::new(0, cx.entity_id().as_u64(), "a = 1\nb = 2\n").with_language(toml_language, cx)
2350 });
2351 let rust_buffer = cx.new_model(|cx| {
2352 Buffer::new(0, cx.entity_id().as_u64(), "const c: usize = 3;\n")
2353 .with_language(rust_language, cx)
2354 });
2355 let multibuffer = cx.new_model(|cx| {
2356 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
2357 multibuffer.push_excerpts(
2358 toml_buffer.clone(),
2359 [ExcerptRange {
2360 context: Point::new(0, 0)..Point::new(2, 0),
2361 primary: None,
2362 }],
2363 cx,
2364 );
2365 multibuffer.push_excerpts(
2366 rust_buffer.clone(),
2367 [ExcerptRange {
2368 context: Point::new(0, 0)..Point::new(1, 0),
2369 primary: None,
2370 }],
2371 cx,
2372 );
2373 multibuffer
2374 });
2375
2376 cx.add_window(|cx| {
2377 let mut editor = build_editor(multibuffer, cx);
2378
2379 assert_eq!(
2380 editor.text(cx),
2381 indoc! {"
2382 a = 1
2383 b = 2
2384
2385 const c: usize = 3;
2386 "}
2387 );
2388
2389 select_ranges(
2390 &mut editor,
2391 indoc! {"
2392 «aˇ» = 1
2393 b = 2
2394
2395 «const c:ˇ» usize = 3;
2396 "},
2397 cx,
2398 );
2399
2400 editor.tab(&Tab, cx);
2401 assert_text_with_selections(
2402 &mut editor,
2403 indoc! {"
2404 «aˇ» = 1
2405 b = 2
2406
2407 «const c:ˇ» usize = 3;
2408 "},
2409 cx,
2410 );
2411 editor.tab_prev(&TabPrev, cx);
2412 assert_text_with_selections(
2413 &mut editor,
2414 indoc! {"
2415 «aˇ» = 1
2416 b = 2
2417
2418 «const c:ˇ» usize = 3;
2419 "},
2420 cx,
2421 );
2422
2423 editor
2424 });
2425}
2426
2427#[gpui::test]
2428async fn test_backspace(cx: &mut gpui::TestAppContext) {
2429 init_test(cx, |_| {});
2430
2431 let mut cx = EditorTestContext::new(cx).await;
2432
2433 // Basic backspace
2434 cx.set_state(indoc! {"
2435 onˇe two three
2436 fou«rˇ» five six
2437 seven «ˇeight nine
2438 »ten
2439 "});
2440 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2441 cx.assert_editor_state(indoc! {"
2442 oˇe two three
2443 fouˇ five six
2444 seven ˇten
2445 "});
2446
2447 // Test backspace inside and around indents
2448 cx.set_state(indoc! {"
2449 zero
2450 ˇone
2451 ˇtwo
2452 ˇ ˇ ˇ three
2453 ˇ ˇ four
2454 "});
2455 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2456 cx.assert_editor_state(indoc! {"
2457 zero
2458 ˇone
2459 ˇtwo
2460 ˇ threeˇ four
2461 "});
2462
2463 // Test backspace with line_mode set to true
2464 cx.update_editor(|e, _| e.selections.line_mode = true);
2465 cx.set_state(indoc! {"
2466 The ˇquick ˇbrown
2467 fox jumps over
2468 the lazy dog
2469 ˇThe qu«ick bˇ»rown"});
2470 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2471 cx.assert_editor_state(indoc! {"
2472 ˇfox jumps over
2473 the lazy dogˇ"});
2474}
2475
2476#[gpui::test]
2477async fn test_delete(cx: &mut gpui::TestAppContext) {
2478 init_test(cx, |_| {});
2479
2480 let mut cx = EditorTestContext::new(cx).await;
2481 cx.set_state(indoc! {"
2482 onˇe two three
2483 fou«rˇ» five six
2484 seven «ˇeight nine
2485 »ten
2486 "});
2487 cx.update_editor(|e, cx| e.delete(&Delete, cx));
2488 cx.assert_editor_state(indoc! {"
2489 onˇ two three
2490 fouˇ five six
2491 seven ˇten
2492 "});
2493
2494 // Test backspace with line_mode set to true
2495 cx.update_editor(|e, _| e.selections.line_mode = true);
2496 cx.set_state(indoc! {"
2497 The ˇquick ˇbrown
2498 fox «ˇjum»ps over
2499 the lazy dog
2500 ˇThe qu«ick bˇ»rown"});
2501 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2502 cx.assert_editor_state("ˇthe lazy dogˇ");
2503}
2504
2505#[gpui::test]
2506fn test_delete_line(cx: &mut TestAppContext) {
2507 init_test(cx, |_| {});
2508
2509 let view = cx.add_window(|cx| {
2510 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2511 build_editor(buffer, cx)
2512 });
2513 _ = view.update(cx, |view, cx| {
2514 view.change_selections(None, cx, |s| {
2515 s.select_display_ranges([
2516 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
2517 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
2518 DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
2519 ])
2520 });
2521 view.delete_line(&DeleteLine, cx);
2522 assert_eq!(view.display_text(cx), "ghi");
2523 assert_eq!(
2524 view.selections.display_ranges(cx),
2525 vec![
2526 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
2527 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)
2528 ]
2529 );
2530 });
2531
2532 let view = cx.add_window(|cx| {
2533 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2534 build_editor(buffer, cx)
2535 });
2536 _ = view.update(cx, |view, cx| {
2537 view.change_selections(None, cx, |s| {
2538 s.select_display_ranges([DisplayPoint::new(2, 0)..DisplayPoint::new(0, 1)])
2539 });
2540 view.delete_line(&DeleteLine, cx);
2541 assert_eq!(view.display_text(cx), "ghi\n");
2542 assert_eq!(
2543 view.selections.display_ranges(cx),
2544 vec![DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)]
2545 );
2546 });
2547}
2548
2549//todo!(select_anchor_ranges)
2550#[gpui::test]
2551fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
2552 init_test(cx, |_| {});
2553
2554 cx.add_window(|cx| {
2555 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
2556 let mut editor = build_editor(buffer.clone(), cx);
2557 let buffer = buffer.read(cx).as_singleton().unwrap();
2558
2559 assert_eq!(
2560 editor.selections.ranges::<Point>(cx),
2561 &[Point::new(0, 0)..Point::new(0, 0)]
2562 );
2563
2564 // When on single line, replace newline at end by space
2565 editor.join_lines(&JoinLines, cx);
2566 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
2567 assert_eq!(
2568 editor.selections.ranges::<Point>(cx),
2569 &[Point::new(0, 3)..Point::new(0, 3)]
2570 );
2571
2572 // When multiple lines are selected, remove newlines that are spanned by the selection
2573 editor.change_selections(None, cx, |s| {
2574 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
2575 });
2576 editor.join_lines(&JoinLines, cx);
2577 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
2578 assert_eq!(
2579 editor.selections.ranges::<Point>(cx),
2580 &[Point::new(0, 11)..Point::new(0, 11)]
2581 );
2582
2583 // Undo should be transactional
2584 editor.undo(&Undo, cx);
2585 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
2586 assert_eq!(
2587 editor.selections.ranges::<Point>(cx),
2588 &[Point::new(0, 5)..Point::new(2, 2)]
2589 );
2590
2591 // When joining an empty line don't insert a space
2592 editor.change_selections(None, cx, |s| {
2593 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
2594 });
2595 editor.join_lines(&JoinLines, cx);
2596 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
2597 assert_eq!(
2598 editor.selections.ranges::<Point>(cx),
2599 [Point::new(2, 3)..Point::new(2, 3)]
2600 );
2601
2602 // We can remove trailing newlines
2603 editor.join_lines(&JoinLines, cx);
2604 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
2605 assert_eq!(
2606 editor.selections.ranges::<Point>(cx),
2607 [Point::new(2, 3)..Point::new(2, 3)]
2608 );
2609
2610 // We don't blow up on the last line
2611 editor.join_lines(&JoinLines, cx);
2612 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
2613 assert_eq!(
2614 editor.selections.ranges::<Point>(cx),
2615 [Point::new(2, 3)..Point::new(2, 3)]
2616 );
2617
2618 // reset to test indentation
2619 editor.buffer.update(cx, |buffer, cx| {
2620 buffer.edit(
2621 [
2622 (Point::new(1, 0)..Point::new(1, 2), " "),
2623 (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
2624 ],
2625 None,
2626 cx,
2627 )
2628 });
2629
2630 // We remove any leading spaces
2631 assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
2632 editor.change_selections(None, cx, |s| {
2633 s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
2634 });
2635 editor.join_lines(&JoinLines, cx);
2636 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
2637
2638 // We don't insert a space for a line containing only spaces
2639 editor.join_lines(&JoinLines, cx);
2640 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
2641
2642 // We ignore any leading tabs
2643 editor.join_lines(&JoinLines, cx);
2644 assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
2645
2646 editor
2647 });
2648}
2649
2650#[gpui::test]
2651fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
2652 init_test(cx, |_| {});
2653
2654 cx.add_window(|cx| {
2655 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
2656 let mut editor = build_editor(buffer.clone(), cx);
2657 let buffer = buffer.read(cx).as_singleton().unwrap();
2658
2659 editor.change_selections(None, cx, |s| {
2660 s.select_ranges([
2661 Point::new(0, 2)..Point::new(1, 1),
2662 Point::new(1, 2)..Point::new(1, 2),
2663 Point::new(3, 1)..Point::new(3, 2),
2664 ])
2665 });
2666
2667 editor.join_lines(&JoinLines, cx);
2668 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
2669
2670 assert_eq!(
2671 editor.selections.ranges::<Point>(cx),
2672 [
2673 Point::new(0, 7)..Point::new(0, 7),
2674 Point::new(1, 3)..Point::new(1, 3)
2675 ]
2676 );
2677 editor
2678 });
2679}
2680
2681#[gpui::test]
2682async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
2683 init_test(cx, |_| {});
2684
2685 let mut cx = EditorTestContext::new(cx).await;
2686
2687 // Test sort_lines_case_insensitive()
2688 cx.set_state(indoc! {"
2689 «z
2690 y
2691 x
2692 Z
2693 Y
2694 Xˇ»
2695 "});
2696 cx.update_editor(|e, cx| e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, cx));
2697 cx.assert_editor_state(indoc! {"
2698 «x
2699 X
2700 y
2701 Y
2702 z
2703 Zˇ»
2704 "});
2705
2706 // Test reverse_lines()
2707 cx.set_state(indoc! {"
2708 «5
2709 4
2710 3
2711 2
2712 1ˇ»
2713 "});
2714 cx.update_editor(|e, cx| e.reverse_lines(&ReverseLines, cx));
2715 cx.assert_editor_state(indoc! {"
2716 «1
2717 2
2718 3
2719 4
2720 5ˇ»
2721 "});
2722
2723 // Skip testing shuffle_line()
2724
2725 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
2726 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
2727
2728 // Don't manipulate when cursor is on single line, but expand the selection
2729 cx.set_state(indoc! {"
2730 ddˇdd
2731 ccc
2732 bb
2733 a
2734 "});
2735 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
2736 cx.assert_editor_state(indoc! {"
2737 «ddddˇ»
2738 ccc
2739 bb
2740 a
2741 "});
2742
2743 // Basic manipulate case
2744 // Start selection moves to column 0
2745 // End of selection shrinks to fit shorter line
2746 cx.set_state(indoc! {"
2747 dd«d
2748 ccc
2749 bb
2750 aaaaaˇ»
2751 "});
2752 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
2753 cx.assert_editor_state(indoc! {"
2754 «aaaaa
2755 bb
2756 ccc
2757 dddˇ»
2758 "});
2759
2760 // Manipulate case with newlines
2761 cx.set_state(indoc! {"
2762 dd«d
2763 ccc
2764
2765 bb
2766 aaaaa
2767
2768 ˇ»
2769 "});
2770 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
2771 cx.assert_editor_state(indoc! {"
2772 «
2773
2774 aaaaa
2775 bb
2776 ccc
2777 dddˇ»
2778
2779 "});
2780}
2781
2782#[gpui::test]
2783async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
2784 init_test(cx, |_| {});
2785
2786 let mut cx = EditorTestContext::new(cx).await;
2787
2788 // Manipulate with multiple selections on a single line
2789 cx.set_state(indoc! {"
2790 dd«dd
2791 cˇ»c«c
2792 bb
2793 aaaˇ»aa
2794 "});
2795 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
2796 cx.assert_editor_state(indoc! {"
2797 «aaaaa
2798 bb
2799 ccc
2800 ddddˇ»
2801 "});
2802
2803 // Manipulate with multiple disjoin selections
2804 cx.set_state(indoc! {"
2805 5«
2806 4
2807 3
2808 2
2809 1ˇ»
2810
2811 dd«dd
2812 ccc
2813 bb
2814 aaaˇ»aa
2815 "});
2816 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
2817 cx.assert_editor_state(indoc! {"
2818 «1
2819 2
2820 3
2821 4
2822 5ˇ»
2823
2824 «aaaaa
2825 bb
2826 ccc
2827 ddddˇ»
2828 "});
2829}
2830
2831#[gpui::test]
2832async fn test_manipulate_text(cx: &mut TestAppContext) {
2833 init_test(cx, |_| {});
2834
2835 let mut cx = EditorTestContext::new(cx).await;
2836
2837 // Test convert_to_upper_case()
2838 cx.set_state(indoc! {"
2839 «hello worldˇ»
2840 "});
2841 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
2842 cx.assert_editor_state(indoc! {"
2843 «HELLO WORLDˇ»
2844 "});
2845
2846 // Test convert_to_lower_case()
2847 cx.set_state(indoc! {"
2848 «HELLO WORLDˇ»
2849 "});
2850 cx.update_editor(|e, cx| e.convert_to_lower_case(&ConvertToLowerCase, cx));
2851 cx.assert_editor_state(indoc! {"
2852 «hello worldˇ»
2853 "});
2854
2855 // Test multiple line, single selection case
2856 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
2857 cx.set_state(indoc! {"
2858 «The quick brown
2859 fox jumps over
2860 the lazy dogˇ»
2861 "});
2862 cx.update_editor(|e, cx| e.convert_to_title_case(&ConvertToTitleCase, cx));
2863 cx.assert_editor_state(indoc! {"
2864 «The Quick Brown
2865 Fox Jumps Over
2866 The Lazy Dogˇ»
2867 "});
2868
2869 // Test multiple line, single selection case
2870 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
2871 cx.set_state(indoc! {"
2872 «The quick brown
2873 fox jumps over
2874 the lazy dogˇ»
2875 "});
2876 cx.update_editor(|e, cx| e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, cx));
2877 cx.assert_editor_state(indoc! {"
2878 «TheQuickBrown
2879 FoxJumpsOver
2880 TheLazyDogˇ»
2881 "});
2882
2883 // From here on out, test more complex cases of manipulate_text()
2884
2885 // Test no selection case - should affect words cursors are in
2886 // Cursor at beginning, middle, and end of word
2887 cx.set_state(indoc! {"
2888 ˇhello big beauˇtiful worldˇ
2889 "});
2890 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
2891 cx.assert_editor_state(indoc! {"
2892 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
2893 "});
2894
2895 // Test multiple selections on a single line and across multiple lines
2896 cx.set_state(indoc! {"
2897 «Theˇ» quick «brown
2898 foxˇ» jumps «overˇ»
2899 the «lazyˇ» dog
2900 "});
2901 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
2902 cx.assert_editor_state(indoc! {"
2903 «THEˇ» quick «BROWN
2904 FOXˇ» jumps «OVERˇ»
2905 the «LAZYˇ» dog
2906 "});
2907
2908 // Test case where text length grows
2909 cx.set_state(indoc! {"
2910 «tschüߡ»
2911 "});
2912 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
2913 cx.assert_editor_state(indoc! {"
2914 «TSCHÜSSˇ»
2915 "});
2916
2917 // Test to make sure we don't crash when text shrinks
2918 cx.set_state(indoc! {"
2919 aaa_bbbˇ
2920 "});
2921 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
2922 cx.assert_editor_state(indoc! {"
2923 «aaaBbbˇ»
2924 "});
2925
2926 // Test to make sure we all aware of the fact that each word can grow and shrink
2927 // Final selections should be aware of this fact
2928 cx.set_state(indoc! {"
2929 aaa_bˇbb bbˇb_ccc ˇccc_ddd
2930 "});
2931 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
2932 cx.assert_editor_state(indoc! {"
2933 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
2934 "});
2935}
2936
2937#[gpui::test]
2938fn test_duplicate_line(cx: &mut TestAppContext) {
2939 init_test(cx, |_| {});
2940
2941 let view = cx.add_window(|cx| {
2942 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2943 build_editor(buffer, cx)
2944 });
2945 _ = view.update(cx, |view, cx| {
2946 view.change_selections(None, cx, |s| {
2947 s.select_display_ranges([
2948 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
2949 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
2950 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
2951 DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
2952 ])
2953 });
2954 view.duplicate_line(&DuplicateLine, cx);
2955 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
2956 assert_eq!(
2957 view.selections.display_ranges(cx),
2958 vec![
2959 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
2960 DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
2961 DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
2962 DisplayPoint::new(6, 0)..DisplayPoint::new(6, 0),
2963 ]
2964 );
2965 });
2966
2967 let view = cx.add_window(|cx| {
2968 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2969 build_editor(buffer, cx)
2970 });
2971 _ = view.update(cx, |view, cx| {
2972 view.change_selections(None, cx, |s| {
2973 s.select_display_ranges([
2974 DisplayPoint::new(0, 1)..DisplayPoint::new(1, 1),
2975 DisplayPoint::new(1, 2)..DisplayPoint::new(2, 1),
2976 ])
2977 });
2978 view.duplicate_line(&DuplicateLine, cx);
2979 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
2980 assert_eq!(
2981 view.selections.display_ranges(cx),
2982 vec![
2983 DisplayPoint::new(3, 1)..DisplayPoint::new(4, 1),
2984 DisplayPoint::new(4, 2)..DisplayPoint::new(5, 1),
2985 ]
2986 );
2987 });
2988}
2989
2990#[gpui::test]
2991fn test_move_line_up_down(cx: &mut TestAppContext) {
2992 init_test(cx, |_| {});
2993
2994 let view = cx.add_window(|cx| {
2995 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
2996 build_editor(buffer, cx)
2997 });
2998 _ = view.update(cx, |view, cx| {
2999 view.fold_ranges(
3000 vec![
3001 Point::new(0, 2)..Point::new(1, 2),
3002 Point::new(2, 3)..Point::new(4, 1),
3003 Point::new(7, 0)..Point::new(8, 4),
3004 ],
3005 true,
3006 cx,
3007 );
3008 view.change_selections(None, cx, |s| {
3009 s.select_display_ranges([
3010 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
3011 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
3012 DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
3013 DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2),
3014 ])
3015 });
3016 assert_eq!(
3017 view.display_text(cx),
3018 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
3019 );
3020
3021 view.move_line_up(&MoveLineUp, cx);
3022 assert_eq!(
3023 view.display_text(cx),
3024 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
3025 );
3026 assert_eq!(
3027 view.selections.display_ranges(cx),
3028 vec![
3029 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
3030 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
3031 DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3),
3032 DisplayPoint::new(4, 0)..DisplayPoint::new(4, 2)
3033 ]
3034 );
3035 });
3036
3037 _ = view.update(cx, |view, cx| {
3038 view.move_line_down(&MoveLineDown, cx);
3039 assert_eq!(
3040 view.display_text(cx),
3041 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
3042 );
3043 assert_eq!(
3044 view.selections.display_ranges(cx),
3045 vec![
3046 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
3047 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
3048 DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
3049 DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2)
3050 ]
3051 );
3052 });
3053
3054 _ = view.update(cx, |view, cx| {
3055 view.move_line_down(&MoveLineDown, cx);
3056 assert_eq!(
3057 view.display_text(cx),
3058 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
3059 );
3060 assert_eq!(
3061 view.selections.display_ranges(cx),
3062 vec![
3063 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
3064 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
3065 DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
3066 DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2)
3067 ]
3068 );
3069 });
3070
3071 _ = view.update(cx, |view, cx| {
3072 view.move_line_up(&MoveLineUp, cx);
3073 assert_eq!(
3074 view.display_text(cx),
3075 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
3076 );
3077 assert_eq!(
3078 view.selections.display_ranges(cx),
3079 vec![
3080 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
3081 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
3082 DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3),
3083 DisplayPoint::new(4, 0)..DisplayPoint::new(4, 2)
3084 ]
3085 );
3086 });
3087}
3088
3089#[gpui::test]
3090fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
3091 init_test(cx, |_| {});
3092
3093 let editor = cx.add_window(|cx| {
3094 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3095 build_editor(buffer, cx)
3096 });
3097 _ = editor.update(cx, |editor, cx| {
3098 let snapshot = editor.buffer.read(cx).snapshot(cx);
3099 editor.insert_blocks(
3100 [BlockProperties {
3101 style: BlockStyle::Fixed,
3102 position: snapshot.anchor_after(Point::new(2, 0)),
3103 disposition: BlockDisposition::Below,
3104 height: 1,
3105 render: Arc::new(|_| div().into_any()),
3106 }],
3107 Some(Autoscroll::fit()),
3108 cx,
3109 );
3110 editor.change_selections(None, cx, |s| {
3111 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
3112 });
3113 editor.move_line_down(&MoveLineDown, cx);
3114 });
3115}
3116
3117//todo!(test_transpose)
3118#[gpui::test]
3119fn test_transpose(cx: &mut TestAppContext) {
3120 init_test(cx, |_| {});
3121
3122 _ = cx.add_window(|cx| {
3123 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
3124 editor.set_style(EditorStyle::default(), cx);
3125 editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
3126 editor.transpose(&Default::default(), cx);
3127 assert_eq!(editor.text(cx), "bac");
3128 assert_eq!(editor.selections.ranges(cx), [2..2]);
3129
3130 editor.transpose(&Default::default(), cx);
3131 assert_eq!(editor.text(cx), "bca");
3132 assert_eq!(editor.selections.ranges(cx), [3..3]);
3133
3134 editor.transpose(&Default::default(), cx);
3135 assert_eq!(editor.text(cx), "bac");
3136 assert_eq!(editor.selections.ranges(cx), [3..3]);
3137
3138 editor
3139 });
3140
3141 _ = cx.add_window(|cx| {
3142 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
3143 editor.set_style(EditorStyle::default(), cx);
3144 editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
3145 editor.transpose(&Default::default(), cx);
3146 assert_eq!(editor.text(cx), "acb\nde");
3147 assert_eq!(editor.selections.ranges(cx), [3..3]);
3148
3149 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
3150 editor.transpose(&Default::default(), cx);
3151 assert_eq!(editor.text(cx), "acbd\ne");
3152 assert_eq!(editor.selections.ranges(cx), [5..5]);
3153
3154 editor.transpose(&Default::default(), cx);
3155 assert_eq!(editor.text(cx), "acbde\n");
3156 assert_eq!(editor.selections.ranges(cx), [6..6]);
3157
3158 editor.transpose(&Default::default(), cx);
3159 assert_eq!(editor.text(cx), "acbd\ne");
3160 assert_eq!(editor.selections.ranges(cx), [6..6]);
3161
3162 editor
3163 });
3164
3165 _ = cx.add_window(|cx| {
3166 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
3167 editor.set_style(EditorStyle::default(), cx);
3168 editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
3169 editor.transpose(&Default::default(), cx);
3170 assert_eq!(editor.text(cx), "bacd\ne");
3171 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
3172
3173 editor.transpose(&Default::default(), cx);
3174 assert_eq!(editor.text(cx), "bcade\n");
3175 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
3176
3177 editor.transpose(&Default::default(), cx);
3178 assert_eq!(editor.text(cx), "bcda\ne");
3179 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
3180
3181 editor.transpose(&Default::default(), cx);
3182 assert_eq!(editor.text(cx), "bcade\n");
3183 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
3184
3185 editor.transpose(&Default::default(), cx);
3186 assert_eq!(editor.text(cx), "bcaed\n");
3187 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
3188
3189 editor
3190 });
3191
3192 _ = cx.add_window(|cx| {
3193 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
3194 editor.set_style(EditorStyle::default(), cx);
3195 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
3196 editor.transpose(&Default::default(), cx);
3197 assert_eq!(editor.text(cx), "🏀🍐✋");
3198 assert_eq!(editor.selections.ranges(cx), [8..8]);
3199
3200 editor.transpose(&Default::default(), cx);
3201 assert_eq!(editor.text(cx), "🏀✋🍐");
3202 assert_eq!(editor.selections.ranges(cx), [11..11]);
3203
3204 editor.transpose(&Default::default(), cx);
3205 assert_eq!(editor.text(cx), "🏀🍐✋");
3206 assert_eq!(editor.selections.ranges(cx), [11..11]);
3207
3208 editor
3209 });
3210}
3211
3212#[gpui::test]
3213async fn test_clipboard(cx: &mut gpui::TestAppContext) {
3214 init_test(cx, |_| {});
3215
3216 let mut cx = EditorTestContext::new(cx).await;
3217
3218 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
3219 cx.update_editor(|e, cx| e.cut(&Cut, cx));
3220 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
3221
3222 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
3223 cx.set_state("two ˇfour ˇsix ˇ");
3224 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3225 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
3226
3227 // Paste again but with only two cursors. Since the number of cursors doesn't
3228 // match the number of slices in the clipboard, the entire clipboard text
3229 // is pasted at each cursor.
3230 cx.set_state("ˇtwo one✅ four three six five ˇ");
3231 cx.update_editor(|e, cx| {
3232 e.handle_input("( ", cx);
3233 e.paste(&Paste, cx);
3234 e.handle_input(") ", cx);
3235 });
3236 cx.assert_editor_state(
3237 &([
3238 "( one✅ ",
3239 "three ",
3240 "five ) ˇtwo one✅ four three six five ( one✅ ",
3241 "three ",
3242 "five ) ˇ",
3243 ]
3244 .join("\n")),
3245 );
3246
3247 // Cut with three selections, one of which is full-line.
3248 cx.set_state(indoc! {"
3249 1«2ˇ»3
3250 4ˇ567
3251 «8ˇ»9"});
3252 cx.update_editor(|e, cx| e.cut(&Cut, cx));
3253 cx.assert_editor_state(indoc! {"
3254 1ˇ3
3255 ˇ9"});
3256
3257 // Paste with three selections, noticing how the copied selection that was full-line
3258 // gets inserted before the second cursor.
3259 cx.set_state(indoc! {"
3260 1ˇ3
3261 9ˇ
3262 «oˇ»ne"});
3263 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3264 cx.assert_editor_state(indoc! {"
3265 12ˇ3
3266 4567
3267 9ˇ
3268 8ˇne"});
3269
3270 // Copy with a single cursor only, which writes the whole line into the clipboard.
3271 cx.set_state(indoc! {"
3272 The quick brown
3273 fox juˇmps over
3274 the lazy dog"});
3275 cx.update_editor(|e, cx| e.copy(&Copy, cx));
3276 assert_eq!(
3277 cx.read_from_clipboard().map(|item| item.text().to_owned()),
3278 Some("fox jumps over\n".to_owned())
3279 );
3280
3281 // Paste with three selections, noticing how the copied full-line selection is inserted
3282 // before the empty selections but replaces the selection that is non-empty.
3283 cx.set_state(indoc! {"
3284 Tˇhe quick brown
3285 «foˇ»x jumps over
3286 tˇhe lazy dog"});
3287 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3288 cx.assert_editor_state(indoc! {"
3289 fox jumps over
3290 Tˇhe quick brown
3291 fox jumps over
3292 ˇx jumps over
3293 fox jumps over
3294 tˇhe lazy dog"});
3295}
3296
3297#[gpui::test]
3298async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
3299 init_test(cx, |_| {});
3300
3301 let mut cx = EditorTestContext::new(cx).await;
3302 let language = Arc::new(Language::new(
3303 LanguageConfig::default(),
3304 Some(tree_sitter_rust::language()),
3305 ));
3306 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
3307
3308 // Cut an indented block, without the leading whitespace.
3309 cx.set_state(indoc! {"
3310 const a: B = (
3311 c(),
3312 «d(
3313 e,
3314 f
3315 )ˇ»
3316 );
3317 "});
3318 cx.update_editor(|e, cx| e.cut(&Cut, cx));
3319 cx.assert_editor_state(indoc! {"
3320 const a: B = (
3321 c(),
3322 ˇ
3323 );
3324 "});
3325
3326 // Paste it at the same position.
3327 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3328 cx.assert_editor_state(indoc! {"
3329 const a: B = (
3330 c(),
3331 d(
3332 e,
3333 f
3334 )ˇ
3335 );
3336 "});
3337
3338 // Paste it at a line with a lower indent level.
3339 cx.set_state(indoc! {"
3340 ˇ
3341 const a: B = (
3342 c(),
3343 );
3344 "});
3345 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3346 cx.assert_editor_state(indoc! {"
3347 d(
3348 e,
3349 f
3350 )ˇ
3351 const a: B = (
3352 c(),
3353 );
3354 "});
3355
3356 // Cut an indented block, with the leading whitespace.
3357 cx.set_state(indoc! {"
3358 const a: B = (
3359 c(),
3360 « d(
3361 e,
3362 f
3363 )
3364 ˇ»);
3365 "});
3366 cx.update_editor(|e, cx| e.cut(&Cut, cx));
3367 cx.assert_editor_state(indoc! {"
3368 const a: B = (
3369 c(),
3370 ˇ);
3371 "});
3372
3373 // Paste it at the same position.
3374 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3375 cx.assert_editor_state(indoc! {"
3376 const a: B = (
3377 c(),
3378 d(
3379 e,
3380 f
3381 )
3382 ˇ);
3383 "});
3384
3385 // Paste it at a line with a higher indent level.
3386 cx.set_state(indoc! {"
3387 const a: B = (
3388 c(),
3389 d(
3390 e,
3391 fˇ
3392 )
3393 );
3394 "});
3395 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3396 cx.assert_editor_state(indoc! {"
3397 const a: B = (
3398 c(),
3399 d(
3400 e,
3401 f d(
3402 e,
3403 f
3404 )
3405 ˇ
3406 )
3407 );
3408 "});
3409}
3410
3411#[gpui::test]
3412fn test_select_all(cx: &mut TestAppContext) {
3413 init_test(cx, |_| {});
3414
3415 let view = cx.add_window(|cx| {
3416 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
3417 build_editor(buffer, cx)
3418 });
3419 _ = view.update(cx, |view, cx| {
3420 view.select_all(&SelectAll, cx);
3421 assert_eq!(
3422 view.selections.display_ranges(cx),
3423 &[DisplayPoint::new(0, 0)..DisplayPoint::new(2, 3)]
3424 );
3425 });
3426}
3427
3428#[gpui::test]
3429fn test_select_line(cx: &mut TestAppContext) {
3430 init_test(cx, |_| {});
3431
3432 let view = cx.add_window(|cx| {
3433 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
3434 build_editor(buffer, cx)
3435 });
3436 _ = view.update(cx, |view, cx| {
3437 view.change_selections(None, cx, |s| {
3438 s.select_display_ranges([
3439 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
3440 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
3441 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
3442 DisplayPoint::new(4, 2)..DisplayPoint::new(4, 2),
3443 ])
3444 });
3445 view.select_line(&SelectLine, cx);
3446 assert_eq!(
3447 view.selections.display_ranges(cx),
3448 vec![
3449 DisplayPoint::new(0, 0)..DisplayPoint::new(2, 0),
3450 DisplayPoint::new(4, 0)..DisplayPoint::new(5, 0),
3451 ]
3452 );
3453 });
3454
3455 _ = view.update(cx, |view, cx| {
3456 view.select_line(&SelectLine, cx);
3457 assert_eq!(
3458 view.selections.display_ranges(cx),
3459 vec![
3460 DisplayPoint::new(0, 0)..DisplayPoint::new(3, 0),
3461 DisplayPoint::new(4, 0)..DisplayPoint::new(5, 5),
3462 ]
3463 );
3464 });
3465
3466 _ = view.update(cx, |view, cx| {
3467 view.select_line(&SelectLine, cx);
3468 assert_eq!(
3469 view.selections.display_ranges(cx),
3470 vec![DisplayPoint::new(0, 0)..DisplayPoint::new(5, 5)]
3471 );
3472 });
3473}
3474
3475#[gpui::test]
3476fn test_split_selection_into_lines(cx: &mut TestAppContext) {
3477 init_test(cx, |_| {});
3478
3479 let view = cx.add_window(|cx| {
3480 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
3481 build_editor(buffer, cx)
3482 });
3483 _ = view.update(cx, |view, cx| {
3484 view.fold_ranges(
3485 vec![
3486 Point::new(0, 2)..Point::new(1, 2),
3487 Point::new(2, 3)..Point::new(4, 1),
3488 Point::new(7, 0)..Point::new(8, 4),
3489 ],
3490 true,
3491 cx,
3492 );
3493 view.change_selections(None, cx, |s| {
3494 s.select_display_ranges([
3495 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
3496 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
3497 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
3498 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
3499 ])
3500 });
3501 assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i");
3502 });
3503
3504 _ = view.update(cx, |view, cx| {
3505 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
3506 assert_eq!(
3507 view.display_text(cx),
3508 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
3509 );
3510 assert_eq!(
3511 view.selections.display_ranges(cx),
3512 [
3513 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
3514 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
3515 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 0),
3516 DisplayPoint::new(5, 4)..DisplayPoint::new(5, 4)
3517 ]
3518 );
3519 });
3520
3521 _ = view.update(cx, |view, cx| {
3522 view.change_selections(None, cx, |s| {
3523 s.select_display_ranges([DisplayPoint::new(5, 0)..DisplayPoint::new(0, 1)])
3524 });
3525 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
3526 assert_eq!(
3527 view.display_text(cx),
3528 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
3529 );
3530 assert_eq!(
3531 view.selections.display_ranges(cx),
3532 [
3533 DisplayPoint::new(0, 5)..DisplayPoint::new(0, 5),
3534 DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
3535 DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
3536 DisplayPoint::new(3, 5)..DisplayPoint::new(3, 5),
3537 DisplayPoint::new(4, 5)..DisplayPoint::new(4, 5),
3538 DisplayPoint::new(5, 5)..DisplayPoint::new(5, 5),
3539 DisplayPoint::new(6, 5)..DisplayPoint::new(6, 5),
3540 DisplayPoint::new(7, 0)..DisplayPoint::new(7, 0)
3541 ]
3542 );
3543 });
3544}
3545
3546#[gpui::test]
3547async fn test_add_selection_above_below(cx: &mut TestAppContext) {
3548 init_test(cx, |_| {});
3549
3550 let mut cx = EditorTestContext::new(cx).await;
3551
3552 // let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
3553 cx.set_state(indoc!(
3554 r#"abc
3555 defˇghi
3556
3557 jk
3558 nlmo
3559 "#
3560 ));
3561
3562 cx.update_editor(|editor, cx| {
3563 editor.add_selection_above(&Default::default(), cx);
3564 });
3565
3566 cx.assert_editor_state(indoc!(
3567 r#"abcˇ
3568 defˇghi
3569
3570 jk
3571 nlmo
3572 "#
3573 ));
3574
3575 cx.update_editor(|editor, cx| {
3576 editor.add_selection_above(&Default::default(), cx);
3577 });
3578
3579 cx.assert_editor_state(indoc!(
3580 r#"abcˇ
3581 defˇghi
3582
3583 jk
3584 nlmo
3585 "#
3586 ));
3587
3588 cx.update_editor(|view, cx| {
3589 view.add_selection_below(&Default::default(), cx);
3590 });
3591
3592 cx.assert_editor_state(indoc!(
3593 r#"abc
3594 defˇghi
3595
3596 jk
3597 nlmo
3598 "#
3599 ));
3600
3601 cx.update_editor(|view, cx| {
3602 view.undo_selection(&Default::default(), cx);
3603 });
3604
3605 cx.assert_editor_state(indoc!(
3606 r#"abcˇ
3607 defˇghi
3608
3609 jk
3610 nlmo
3611 "#
3612 ));
3613
3614 cx.update_editor(|view, cx| {
3615 view.redo_selection(&Default::default(), cx);
3616 });
3617
3618 cx.assert_editor_state(indoc!(
3619 r#"abc
3620 defˇghi
3621
3622 jk
3623 nlmo
3624 "#
3625 ));
3626
3627 cx.update_editor(|view, cx| {
3628 view.add_selection_below(&Default::default(), cx);
3629 });
3630
3631 cx.assert_editor_state(indoc!(
3632 r#"abc
3633 defˇghi
3634
3635 jk
3636 nlmˇo
3637 "#
3638 ));
3639
3640 cx.update_editor(|view, cx| {
3641 view.add_selection_below(&Default::default(), cx);
3642 });
3643
3644 cx.assert_editor_state(indoc!(
3645 r#"abc
3646 defˇghi
3647
3648 jk
3649 nlmˇo
3650 "#
3651 ));
3652
3653 // change selections
3654 cx.set_state(indoc!(
3655 r#"abc
3656 def«ˇg»hi
3657
3658 jk
3659 nlmo
3660 "#
3661 ));
3662
3663 cx.update_editor(|view, cx| {
3664 view.add_selection_below(&Default::default(), cx);
3665 });
3666
3667 cx.assert_editor_state(indoc!(
3668 r#"abc
3669 def«ˇg»hi
3670
3671 jk
3672 nlm«ˇo»
3673 "#
3674 ));
3675
3676 cx.update_editor(|view, cx| {
3677 view.add_selection_below(&Default::default(), cx);
3678 });
3679
3680 cx.assert_editor_state(indoc!(
3681 r#"abc
3682 def«ˇg»hi
3683
3684 jk
3685 nlm«ˇo»
3686 "#
3687 ));
3688
3689 cx.update_editor(|view, cx| {
3690 view.add_selection_above(&Default::default(), cx);
3691 });
3692
3693 cx.assert_editor_state(indoc!(
3694 r#"abc
3695 def«ˇg»hi
3696
3697 jk
3698 nlmo
3699 "#
3700 ));
3701
3702 cx.update_editor(|view, cx| {
3703 view.add_selection_above(&Default::default(), cx);
3704 });
3705
3706 cx.assert_editor_state(indoc!(
3707 r#"abc
3708 def«ˇg»hi
3709
3710 jk
3711 nlmo
3712 "#
3713 ));
3714
3715 // Change selections again
3716 cx.set_state(indoc!(
3717 r#"a«bc
3718 defgˇ»hi
3719
3720 jk
3721 nlmo
3722 "#
3723 ));
3724
3725 cx.update_editor(|view, cx| {
3726 view.add_selection_below(&Default::default(), cx);
3727 });
3728
3729 cx.assert_editor_state(indoc!(
3730 r#"a«bcˇ»
3731 d«efgˇ»hi
3732
3733 j«kˇ»
3734 nlmo
3735 "#
3736 ));
3737
3738 cx.update_editor(|view, cx| {
3739 view.add_selection_below(&Default::default(), cx);
3740 });
3741 cx.assert_editor_state(indoc!(
3742 r#"a«bcˇ»
3743 d«efgˇ»hi
3744
3745 j«kˇ»
3746 n«lmoˇ»
3747 "#
3748 ));
3749 cx.update_editor(|view, cx| {
3750 view.add_selection_above(&Default::default(), cx);
3751 });
3752
3753 cx.assert_editor_state(indoc!(
3754 r#"a«bcˇ»
3755 d«efgˇ»hi
3756
3757 j«kˇ»
3758 nlmo
3759 "#
3760 ));
3761
3762 // Change selections again
3763 cx.set_state(indoc!(
3764 r#"abc
3765 d«ˇefghi
3766
3767 jk
3768 nlm»o
3769 "#
3770 ));
3771
3772 cx.update_editor(|view, cx| {
3773 view.add_selection_above(&Default::default(), cx);
3774 });
3775
3776 cx.assert_editor_state(indoc!(
3777 r#"a«ˇbc»
3778 d«ˇef»ghi
3779
3780 j«ˇk»
3781 n«ˇlm»o
3782 "#
3783 ));
3784
3785 cx.update_editor(|view, cx| {
3786 view.add_selection_below(&Default::default(), cx);
3787 });
3788
3789 cx.assert_editor_state(indoc!(
3790 r#"abc
3791 d«ˇef»ghi
3792
3793 j«ˇk»
3794 n«ˇlm»o
3795 "#
3796 ));
3797}
3798
3799#[gpui::test]
3800async fn test_select_next(cx: &mut gpui::TestAppContext) {
3801 init_test(cx, |_| {});
3802
3803 let mut cx = EditorTestContext::new(cx).await;
3804 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
3805
3806 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
3807 .unwrap();
3808 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
3809
3810 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
3811 .unwrap();
3812 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
3813
3814 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
3815 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
3816
3817 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
3818 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
3819
3820 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
3821 .unwrap();
3822 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
3823
3824 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
3825 .unwrap();
3826 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
3827}
3828
3829#[gpui::test]
3830async fn test_select_previous(cx: &mut gpui::TestAppContext) {
3831 init_test(cx, |_| {});
3832 {
3833 // `Select previous` without a selection (selects wordwise)
3834 let mut cx = EditorTestContext::new(cx).await;
3835 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
3836
3837 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
3838 .unwrap();
3839 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
3840
3841 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
3842 .unwrap();
3843 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
3844
3845 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
3846 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
3847
3848 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
3849 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
3850
3851 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
3852 .unwrap();
3853 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
3854
3855 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
3856 .unwrap();
3857 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
3858 }
3859 {
3860 // `Select previous` with a selection
3861 let mut cx = EditorTestContext::new(cx).await;
3862 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
3863
3864 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
3865 .unwrap();
3866 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
3867
3868 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
3869 .unwrap();
3870 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
3871
3872 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
3873 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
3874
3875 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
3876 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
3877
3878 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
3879 .unwrap();
3880 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
3881
3882 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
3883 .unwrap();
3884 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
3885 }
3886}
3887
3888#[gpui::test]
3889async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
3890 init_test(cx, |_| {});
3891
3892 let language = Arc::new(Language::new(
3893 LanguageConfig::default(),
3894 Some(tree_sitter_rust::language()),
3895 ));
3896
3897 let text = r#"
3898 use mod1::mod2::{mod3, mod4};
3899
3900 fn fn_1(param1: bool, param2: &str) {
3901 let var1 = "text";
3902 }
3903 "#
3904 .unindent();
3905
3906 let buffer = cx
3907 .new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx));
3908 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
3909 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
3910
3911 view.condition::<crate::EditorEvent>(&cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
3912 .await;
3913
3914 _ = view.update(cx, |view, cx| {
3915 view.change_selections(None, cx, |s| {
3916 s.select_display_ranges([
3917 DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
3918 DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
3919 DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
3920 ]);
3921 });
3922 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
3923 });
3924 assert_eq!(
3925 view.update(cx, |view, cx| { view.selections.display_ranges(cx) }),
3926 &[
3927 DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27),
3928 DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
3929 DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21),
3930 ]
3931 );
3932
3933 _ = view.update(cx, |view, cx| {
3934 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
3935 });
3936 assert_eq!(
3937 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3938 &[
3939 DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
3940 DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0),
3941 ]
3942 );
3943
3944 _ = view.update(cx, |view, cx| {
3945 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
3946 });
3947 assert_eq!(
3948 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3949 &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)]
3950 );
3951
3952 // Trying to expand the selected syntax node one more time has no effect.
3953 _ = view.update(cx, |view, cx| {
3954 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
3955 });
3956 assert_eq!(
3957 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3958 &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)]
3959 );
3960
3961 _ = view.update(cx, |view, cx| {
3962 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
3963 });
3964 assert_eq!(
3965 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3966 &[
3967 DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
3968 DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0),
3969 ]
3970 );
3971
3972 _ = view.update(cx, |view, cx| {
3973 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
3974 });
3975 assert_eq!(
3976 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3977 &[
3978 DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27),
3979 DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
3980 DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21),
3981 ]
3982 );
3983
3984 _ = view.update(cx, |view, cx| {
3985 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
3986 });
3987 assert_eq!(
3988 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3989 &[
3990 DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
3991 DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
3992 DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
3993 ]
3994 );
3995
3996 // Trying to shrink the selected syntax node one more time has no effect.
3997 _ = view.update(cx, |view, cx| {
3998 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
3999 });
4000 assert_eq!(
4001 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4002 &[
4003 DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
4004 DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
4005 DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
4006 ]
4007 );
4008
4009 // Ensure that we keep expanding the selection if the larger selection starts or ends within
4010 // a fold.
4011 _ = view.update(cx, |view, cx| {
4012 view.fold_ranges(
4013 vec![
4014 Point::new(0, 21)..Point::new(0, 24),
4015 Point::new(3, 20)..Point::new(3, 22),
4016 ],
4017 true,
4018 cx,
4019 );
4020 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4021 });
4022 assert_eq!(
4023 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4024 &[
4025 DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
4026 DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
4027 DisplayPoint::new(3, 4)..DisplayPoint::new(3, 23),
4028 ]
4029 );
4030}
4031
4032#[gpui::test]
4033async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
4034 init_test(cx, |_| {});
4035
4036 let language = Arc::new(
4037 Language::new(
4038 LanguageConfig {
4039 brackets: BracketPairConfig {
4040 pairs: vec![
4041 BracketPair {
4042 start: "{".to_string(),
4043 end: "}".to_string(),
4044 close: false,
4045 newline: true,
4046 },
4047 BracketPair {
4048 start: "(".to_string(),
4049 end: ")".to_string(),
4050 close: false,
4051 newline: true,
4052 },
4053 ],
4054 ..Default::default()
4055 },
4056 ..Default::default()
4057 },
4058 Some(tree_sitter_rust::language()),
4059 )
4060 .with_indents_query(
4061 r#"
4062 (_ "(" ")" @end) @indent
4063 (_ "{" "}" @end) @indent
4064 "#,
4065 )
4066 .unwrap(),
4067 );
4068
4069 let text = "fn a() {}";
4070
4071 let buffer = cx
4072 .new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx));
4073 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
4074 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
4075 editor
4076 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
4077 .await;
4078
4079 _ = editor.update(cx, |editor, cx| {
4080 editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
4081 editor.newline(&Newline, cx);
4082 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
4083 assert_eq!(
4084 editor.selections.ranges(cx),
4085 &[
4086 Point::new(1, 4)..Point::new(1, 4),
4087 Point::new(3, 4)..Point::new(3, 4),
4088 Point::new(5, 0)..Point::new(5, 0)
4089 ]
4090 );
4091 });
4092}
4093
4094#[gpui::test]
4095async fn test_autoclose_pairs(cx: &mut gpui::TestAppContext) {
4096 init_test(cx, |_| {});
4097
4098 let mut cx = EditorTestContext::new(cx).await;
4099
4100 let language = Arc::new(Language::new(
4101 LanguageConfig {
4102 brackets: BracketPairConfig {
4103 pairs: vec![
4104 BracketPair {
4105 start: "{".to_string(),
4106 end: "}".to_string(),
4107 close: true,
4108 newline: true,
4109 },
4110 BracketPair {
4111 start: "(".to_string(),
4112 end: ")".to_string(),
4113 close: true,
4114 newline: true,
4115 },
4116 BracketPair {
4117 start: "/*".to_string(),
4118 end: " */".to_string(),
4119 close: true,
4120 newline: true,
4121 },
4122 BracketPair {
4123 start: "[".to_string(),
4124 end: "]".to_string(),
4125 close: false,
4126 newline: true,
4127 },
4128 BracketPair {
4129 start: "\"".to_string(),
4130 end: "\"".to_string(),
4131 close: true,
4132 newline: false,
4133 },
4134 ],
4135 ..Default::default()
4136 },
4137 autoclose_before: "})]".to_string(),
4138 ..Default::default()
4139 },
4140 Some(tree_sitter_rust::language()),
4141 ));
4142
4143 let registry = Arc::new(LanguageRegistry::test());
4144 registry.add(language.clone());
4145 cx.update_buffer(|buffer, cx| {
4146 buffer.set_language_registry(registry);
4147 buffer.set_language(Some(language), cx);
4148 });
4149
4150 cx.set_state(
4151 &r#"
4152 🏀ˇ
4153 εˇ
4154 ❤️ˇ
4155 "#
4156 .unindent(),
4157 );
4158
4159 // autoclose multiple nested brackets at multiple cursors
4160 cx.update_editor(|view, cx| {
4161 view.handle_input("{", cx);
4162 view.handle_input("{", cx);
4163 view.handle_input("{", cx);
4164 });
4165 cx.assert_editor_state(
4166 &"
4167 🏀{{{ˇ}}}
4168 ε{{{ˇ}}}
4169 ❤️{{{ˇ}}}
4170 "
4171 .unindent(),
4172 );
4173
4174 // insert a different closing bracket
4175 cx.update_editor(|view, cx| {
4176 view.handle_input(")", cx);
4177 });
4178 cx.assert_editor_state(
4179 &"
4180 🏀{{{)ˇ}}}
4181 ε{{{)ˇ}}}
4182 ❤️{{{)ˇ}}}
4183 "
4184 .unindent(),
4185 );
4186
4187 // skip over the auto-closed brackets when typing a closing bracket
4188 cx.update_editor(|view, cx| {
4189 view.move_right(&MoveRight, cx);
4190 view.handle_input("}", cx);
4191 view.handle_input("}", cx);
4192 view.handle_input("}", cx);
4193 });
4194 cx.assert_editor_state(
4195 &"
4196 🏀{{{)}}}}ˇ
4197 ε{{{)}}}}ˇ
4198 ❤️{{{)}}}}ˇ
4199 "
4200 .unindent(),
4201 );
4202
4203 // autoclose multi-character pairs
4204 cx.set_state(
4205 &"
4206 ˇ
4207 ˇ
4208 "
4209 .unindent(),
4210 );
4211 cx.update_editor(|view, cx| {
4212 view.handle_input("/", cx);
4213 view.handle_input("*", cx);
4214 });
4215 cx.assert_editor_state(
4216 &"
4217 /*ˇ */
4218 /*ˇ */
4219 "
4220 .unindent(),
4221 );
4222
4223 // one cursor autocloses a multi-character pair, one cursor
4224 // does not autoclose.
4225 cx.set_state(
4226 &"
4227 /ˇ
4228 ˇ
4229 "
4230 .unindent(),
4231 );
4232 cx.update_editor(|view, cx| view.handle_input("*", cx));
4233 cx.assert_editor_state(
4234 &"
4235 /*ˇ */
4236 *ˇ
4237 "
4238 .unindent(),
4239 );
4240
4241 // Don't autoclose if the next character isn't whitespace and isn't
4242 // listed in the language's "autoclose_before" section.
4243 cx.set_state("ˇa b");
4244 cx.update_editor(|view, cx| view.handle_input("{", cx));
4245 cx.assert_editor_state("{ˇa b");
4246
4247 // Don't autoclose if `close` is false for the bracket pair
4248 cx.set_state("ˇ");
4249 cx.update_editor(|view, cx| view.handle_input("[", cx));
4250 cx.assert_editor_state("[ˇ");
4251
4252 // Surround with brackets if text is selected
4253 cx.set_state("«aˇ» b");
4254 cx.update_editor(|view, cx| view.handle_input("{", cx));
4255 cx.assert_editor_state("{«aˇ»} b");
4256
4257 // Autclose pair where the start and end characters are the same
4258 cx.set_state("aˇ");
4259 cx.update_editor(|view, cx| view.handle_input("\"", cx));
4260 cx.assert_editor_state("a\"ˇ\"");
4261 cx.update_editor(|view, cx| view.handle_input("\"", cx));
4262 cx.assert_editor_state("a\"\"ˇ");
4263}
4264
4265#[gpui::test]
4266async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
4267 init_test(cx, |_| {});
4268
4269 let mut cx = EditorTestContext::new(cx).await;
4270
4271 let html_language = Arc::new(
4272 Language::new(
4273 LanguageConfig {
4274 name: "HTML".into(),
4275 brackets: BracketPairConfig {
4276 pairs: vec![
4277 BracketPair {
4278 start: "<".into(),
4279 end: ">".into(),
4280 close: true,
4281 ..Default::default()
4282 },
4283 BracketPair {
4284 start: "{".into(),
4285 end: "}".into(),
4286 close: true,
4287 ..Default::default()
4288 },
4289 BracketPair {
4290 start: "(".into(),
4291 end: ")".into(),
4292 close: true,
4293 ..Default::default()
4294 },
4295 ],
4296 ..Default::default()
4297 },
4298 autoclose_before: "})]>".into(),
4299 ..Default::default()
4300 },
4301 Some(tree_sitter_html::language()),
4302 )
4303 .with_injection_query(
4304 r#"
4305 (script_element
4306 (raw_text) @content
4307 (#set! "language" "javascript"))
4308 "#,
4309 )
4310 .unwrap(),
4311 );
4312
4313 let javascript_language = Arc::new(Language::new(
4314 LanguageConfig {
4315 name: "JavaScript".into(),
4316 brackets: BracketPairConfig {
4317 pairs: vec![
4318 BracketPair {
4319 start: "/*".into(),
4320 end: " */".into(),
4321 close: true,
4322 ..Default::default()
4323 },
4324 BracketPair {
4325 start: "{".into(),
4326 end: "}".into(),
4327 close: true,
4328 ..Default::default()
4329 },
4330 BracketPair {
4331 start: "(".into(),
4332 end: ")".into(),
4333 close: true,
4334 ..Default::default()
4335 },
4336 ],
4337 ..Default::default()
4338 },
4339 autoclose_before: "})]>".into(),
4340 ..Default::default()
4341 },
4342 Some(tree_sitter_typescript::language_tsx()),
4343 ));
4344
4345 let registry = Arc::new(LanguageRegistry::test());
4346 registry.add(html_language.clone());
4347 registry.add(javascript_language.clone());
4348
4349 cx.update_buffer(|buffer, cx| {
4350 buffer.set_language_registry(registry);
4351 buffer.set_language(Some(html_language), cx);
4352 });
4353
4354 cx.set_state(
4355 &r#"
4356 <body>ˇ
4357 <script>
4358 var x = 1;ˇ
4359 </script>
4360 </body>ˇ
4361 "#
4362 .unindent(),
4363 );
4364
4365 // Precondition: different languages are active at different locations.
4366 cx.update_editor(|editor, cx| {
4367 let snapshot = editor.snapshot(cx);
4368 let cursors = editor.selections.ranges::<usize>(cx);
4369 let languages = cursors
4370 .iter()
4371 .map(|c| snapshot.language_at(c.start).unwrap().name())
4372 .collect::<Vec<_>>();
4373 assert_eq!(
4374 languages,
4375 &["HTML".into(), "JavaScript".into(), "HTML".into()]
4376 );
4377 });
4378
4379 // Angle brackets autoclose in HTML, but not JavaScript.
4380 cx.update_editor(|editor, cx| {
4381 editor.handle_input("<", cx);
4382 editor.handle_input("a", cx);
4383 });
4384 cx.assert_editor_state(
4385 &r#"
4386 <body><aˇ>
4387 <script>
4388 var x = 1;<aˇ
4389 </script>
4390 </body><aˇ>
4391 "#
4392 .unindent(),
4393 );
4394
4395 // Curly braces and parens autoclose in both HTML and JavaScript.
4396 cx.update_editor(|editor, cx| {
4397 editor.handle_input(" b=", cx);
4398 editor.handle_input("{", cx);
4399 editor.handle_input("c", cx);
4400 editor.handle_input("(", cx);
4401 });
4402 cx.assert_editor_state(
4403 &r#"
4404 <body><a b={c(ˇ)}>
4405 <script>
4406 var x = 1;<a b={c(ˇ)}
4407 </script>
4408 </body><a b={c(ˇ)}>
4409 "#
4410 .unindent(),
4411 );
4412
4413 // Brackets that were already autoclosed are skipped.
4414 cx.update_editor(|editor, cx| {
4415 editor.handle_input(")", cx);
4416 editor.handle_input("d", cx);
4417 editor.handle_input("}", cx);
4418 });
4419 cx.assert_editor_state(
4420 &r#"
4421 <body><a b={c()d}ˇ>
4422 <script>
4423 var x = 1;<a b={c()d}ˇ
4424 </script>
4425 </body><a b={c()d}ˇ>
4426 "#
4427 .unindent(),
4428 );
4429 cx.update_editor(|editor, cx| {
4430 editor.handle_input(">", cx);
4431 });
4432 cx.assert_editor_state(
4433 &r#"
4434 <body><a b={c()d}>ˇ
4435 <script>
4436 var x = 1;<a b={c()d}>ˇ
4437 </script>
4438 </body><a b={c()d}>ˇ
4439 "#
4440 .unindent(),
4441 );
4442
4443 // Reset
4444 cx.set_state(
4445 &r#"
4446 <body>ˇ
4447 <script>
4448 var x = 1;ˇ
4449 </script>
4450 </body>ˇ
4451 "#
4452 .unindent(),
4453 );
4454
4455 cx.update_editor(|editor, cx| {
4456 editor.handle_input("<", cx);
4457 });
4458 cx.assert_editor_state(
4459 &r#"
4460 <body><ˇ>
4461 <script>
4462 var x = 1;<ˇ
4463 </script>
4464 </body><ˇ>
4465 "#
4466 .unindent(),
4467 );
4468
4469 // When backspacing, the closing angle brackets are removed.
4470 cx.update_editor(|editor, cx| {
4471 editor.backspace(&Backspace, cx);
4472 });
4473 cx.assert_editor_state(
4474 &r#"
4475 <body>ˇ
4476 <script>
4477 var x = 1;ˇ
4478 </script>
4479 </body>ˇ
4480 "#
4481 .unindent(),
4482 );
4483
4484 // Block comments autoclose in JavaScript, but not HTML.
4485 cx.update_editor(|editor, cx| {
4486 editor.handle_input("/", cx);
4487 editor.handle_input("*", cx);
4488 });
4489 cx.assert_editor_state(
4490 &r#"
4491 <body>/*ˇ
4492 <script>
4493 var x = 1;/*ˇ */
4494 </script>
4495 </body>/*ˇ
4496 "#
4497 .unindent(),
4498 );
4499}
4500
4501#[gpui::test]
4502async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
4503 init_test(cx, |_| {});
4504
4505 let mut cx = EditorTestContext::new(cx).await;
4506
4507 let rust_language = Arc::new(
4508 Language::new(
4509 LanguageConfig {
4510 name: "Rust".into(),
4511 brackets: serde_json::from_value(json!([
4512 { "start": "{", "end": "}", "close": true, "newline": true },
4513 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
4514 ]))
4515 .unwrap(),
4516 autoclose_before: "})]>".into(),
4517 ..Default::default()
4518 },
4519 Some(tree_sitter_rust::language()),
4520 )
4521 .with_override_query("(string_literal) @string")
4522 .unwrap(),
4523 );
4524
4525 let registry = Arc::new(LanguageRegistry::test());
4526 registry.add(rust_language.clone());
4527
4528 cx.update_buffer(|buffer, cx| {
4529 buffer.set_language_registry(registry);
4530 buffer.set_language(Some(rust_language), cx);
4531 });
4532
4533 cx.set_state(
4534 &r#"
4535 let x = ˇ
4536 "#
4537 .unindent(),
4538 );
4539
4540 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
4541 cx.update_editor(|editor, cx| {
4542 editor.handle_input("\"", cx);
4543 });
4544 cx.assert_editor_state(
4545 &r#"
4546 let x = "ˇ"
4547 "#
4548 .unindent(),
4549 );
4550
4551 // Inserting another quotation mark. The cursor moves across the existing
4552 // automatically-inserted quotation mark.
4553 cx.update_editor(|editor, cx| {
4554 editor.handle_input("\"", cx);
4555 });
4556 cx.assert_editor_state(
4557 &r#"
4558 let x = ""ˇ
4559 "#
4560 .unindent(),
4561 );
4562
4563 // Reset
4564 cx.set_state(
4565 &r#"
4566 let x = ˇ
4567 "#
4568 .unindent(),
4569 );
4570
4571 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
4572 cx.update_editor(|editor, cx| {
4573 editor.handle_input("\"", cx);
4574 editor.handle_input(" ", cx);
4575 editor.move_left(&Default::default(), cx);
4576 editor.handle_input("\\", cx);
4577 editor.handle_input("\"", cx);
4578 });
4579 cx.assert_editor_state(
4580 &r#"
4581 let x = "\"ˇ "
4582 "#
4583 .unindent(),
4584 );
4585
4586 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
4587 // mark. Nothing is inserted.
4588 cx.update_editor(|editor, cx| {
4589 editor.move_right(&Default::default(), cx);
4590 editor.handle_input("\"", cx);
4591 });
4592 cx.assert_editor_state(
4593 &r#"
4594 let x = "\" "ˇ
4595 "#
4596 .unindent(),
4597 );
4598}
4599
4600#[gpui::test]
4601async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
4602 init_test(cx, |_| {});
4603
4604 let language = Arc::new(Language::new(
4605 LanguageConfig {
4606 brackets: BracketPairConfig {
4607 pairs: vec![
4608 BracketPair {
4609 start: "{".to_string(),
4610 end: "}".to_string(),
4611 close: true,
4612 newline: true,
4613 },
4614 BracketPair {
4615 start: "/* ".to_string(),
4616 end: "*/".to_string(),
4617 close: true,
4618 ..Default::default()
4619 },
4620 ],
4621 ..Default::default()
4622 },
4623 ..Default::default()
4624 },
4625 Some(tree_sitter_rust::language()),
4626 ));
4627
4628 let text = r#"
4629 a
4630 b
4631 c
4632 "#
4633 .unindent();
4634
4635 let buffer = cx
4636 .new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx));
4637 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
4638 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
4639 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
4640 .await;
4641
4642 _ = view.update(cx, |view, cx| {
4643 view.change_selections(None, cx, |s| {
4644 s.select_display_ranges([
4645 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
4646 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
4647 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1),
4648 ])
4649 });
4650
4651 view.handle_input("{", cx);
4652 view.handle_input("{", cx);
4653 view.handle_input("{", cx);
4654 assert_eq!(
4655 view.text(cx),
4656 "
4657 {{{a}}}
4658 {{{b}}}
4659 {{{c}}}
4660 "
4661 .unindent()
4662 );
4663 assert_eq!(
4664 view.selections.display_ranges(cx),
4665 [
4666 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 4),
4667 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 4),
4668 DisplayPoint::new(2, 3)..DisplayPoint::new(2, 4)
4669 ]
4670 );
4671
4672 view.undo(&Undo, cx);
4673 view.undo(&Undo, cx);
4674 view.undo(&Undo, cx);
4675 assert_eq!(
4676 view.text(cx),
4677 "
4678 a
4679 b
4680 c
4681 "
4682 .unindent()
4683 );
4684 assert_eq!(
4685 view.selections.display_ranges(cx),
4686 [
4687 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
4688 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
4689 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1)
4690 ]
4691 );
4692
4693 // Ensure inserting the first character of a multi-byte bracket pair
4694 // doesn't surround the selections with the bracket.
4695 view.handle_input("/", cx);
4696 assert_eq!(
4697 view.text(cx),
4698 "
4699 /
4700 /
4701 /
4702 "
4703 .unindent()
4704 );
4705 assert_eq!(
4706 view.selections.display_ranges(cx),
4707 [
4708 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
4709 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
4710 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1)
4711 ]
4712 );
4713
4714 view.undo(&Undo, cx);
4715 assert_eq!(
4716 view.text(cx),
4717 "
4718 a
4719 b
4720 c
4721 "
4722 .unindent()
4723 );
4724 assert_eq!(
4725 view.selections.display_ranges(cx),
4726 [
4727 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
4728 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
4729 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1)
4730 ]
4731 );
4732
4733 // Ensure inserting the last character of a multi-byte bracket pair
4734 // doesn't surround the selections with the bracket.
4735 view.handle_input("*", cx);
4736 assert_eq!(
4737 view.text(cx),
4738 "
4739 *
4740 *
4741 *
4742 "
4743 .unindent()
4744 );
4745 assert_eq!(
4746 view.selections.display_ranges(cx),
4747 [
4748 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
4749 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
4750 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1)
4751 ]
4752 );
4753 });
4754}
4755
4756#[gpui::test]
4757async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
4758 init_test(cx, |_| {});
4759
4760 let language = Arc::new(Language::new(
4761 LanguageConfig {
4762 brackets: BracketPairConfig {
4763 pairs: vec![BracketPair {
4764 start: "{".to_string(),
4765 end: "}".to_string(),
4766 close: true,
4767 newline: true,
4768 }],
4769 ..Default::default()
4770 },
4771 autoclose_before: "}".to_string(),
4772 ..Default::default()
4773 },
4774 Some(tree_sitter_rust::language()),
4775 ));
4776
4777 let text = r#"
4778 a
4779 b
4780 c
4781 "#
4782 .unindent();
4783
4784 let buffer = cx
4785 .new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx));
4786 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
4787 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
4788 editor
4789 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
4790 .await;
4791
4792 _ = editor.update(cx, |editor, cx| {
4793 editor.change_selections(None, cx, |s| {
4794 s.select_ranges([
4795 Point::new(0, 1)..Point::new(0, 1),
4796 Point::new(1, 1)..Point::new(1, 1),
4797 Point::new(2, 1)..Point::new(2, 1),
4798 ])
4799 });
4800
4801 editor.handle_input("{", cx);
4802 editor.handle_input("{", cx);
4803 editor.handle_input("_", cx);
4804 assert_eq!(
4805 editor.text(cx),
4806 "
4807 a{{_}}
4808 b{{_}}
4809 c{{_}}
4810 "
4811 .unindent()
4812 );
4813 assert_eq!(
4814 editor.selections.ranges::<Point>(cx),
4815 [
4816 Point::new(0, 4)..Point::new(0, 4),
4817 Point::new(1, 4)..Point::new(1, 4),
4818 Point::new(2, 4)..Point::new(2, 4)
4819 ]
4820 );
4821
4822 editor.backspace(&Default::default(), cx);
4823 editor.backspace(&Default::default(), cx);
4824 assert_eq!(
4825 editor.text(cx),
4826 "
4827 a{}
4828 b{}
4829 c{}
4830 "
4831 .unindent()
4832 );
4833 assert_eq!(
4834 editor.selections.ranges::<Point>(cx),
4835 [
4836 Point::new(0, 2)..Point::new(0, 2),
4837 Point::new(1, 2)..Point::new(1, 2),
4838 Point::new(2, 2)..Point::new(2, 2)
4839 ]
4840 );
4841
4842 editor.delete_to_previous_word_start(&Default::default(), cx);
4843 assert_eq!(
4844 editor.text(cx),
4845 "
4846 a
4847 b
4848 c
4849 "
4850 .unindent()
4851 );
4852 assert_eq!(
4853 editor.selections.ranges::<Point>(cx),
4854 [
4855 Point::new(0, 1)..Point::new(0, 1),
4856 Point::new(1, 1)..Point::new(1, 1),
4857 Point::new(2, 1)..Point::new(2, 1)
4858 ]
4859 );
4860 });
4861}
4862
4863// todo!(select_anchor_ranges)
4864#[gpui::test]
4865async fn test_snippets(cx: &mut gpui::TestAppContext) {
4866 init_test(cx, |_| {});
4867
4868 let (text, insertion_ranges) = marked_text_ranges(
4869 indoc! {"
4870 a.ˇ b
4871 a.ˇ b
4872 a.ˇ b
4873 "},
4874 false,
4875 );
4876
4877 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
4878 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
4879
4880 _ = editor.update(cx, |editor, cx| {
4881 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
4882
4883 editor
4884 .insert_snippet(&insertion_ranges, snippet, cx)
4885 .unwrap();
4886
4887 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
4888 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
4889 assert_eq!(editor.text(cx), expected_text);
4890 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
4891 }
4892
4893 assert(
4894 editor,
4895 cx,
4896 indoc! {"
4897 a.f(«one», two, «three») b
4898 a.f(«one», two, «three») b
4899 a.f(«one», two, «three») b
4900 "},
4901 );
4902
4903 // Can't move earlier than the first tab stop
4904 assert!(!editor.move_to_prev_snippet_tabstop(cx));
4905 assert(
4906 editor,
4907 cx,
4908 indoc! {"
4909 a.f(«one», two, «three») b
4910 a.f(«one», two, «three») b
4911 a.f(«one», two, «three») b
4912 "},
4913 );
4914
4915 assert!(editor.move_to_next_snippet_tabstop(cx));
4916 assert(
4917 editor,
4918 cx,
4919 indoc! {"
4920 a.f(one, «two», three) b
4921 a.f(one, «two», three) b
4922 a.f(one, «two», three) b
4923 "},
4924 );
4925
4926 editor.move_to_prev_snippet_tabstop(cx);
4927 assert(
4928 editor,
4929 cx,
4930 indoc! {"
4931 a.f(«one», two, «three») b
4932 a.f(«one», two, «three») b
4933 a.f(«one», two, «three») b
4934 "},
4935 );
4936
4937 assert!(editor.move_to_next_snippet_tabstop(cx));
4938 assert(
4939 editor,
4940 cx,
4941 indoc! {"
4942 a.f(one, «two», three) b
4943 a.f(one, «two», three) b
4944 a.f(one, «two», three) b
4945 "},
4946 );
4947 assert!(editor.move_to_next_snippet_tabstop(cx));
4948 assert(
4949 editor,
4950 cx,
4951 indoc! {"
4952 a.f(one, two, three)ˇ b
4953 a.f(one, two, three)ˇ b
4954 a.f(one, two, three)ˇ b
4955 "},
4956 );
4957
4958 // As soon as the last tab stop is reached, snippet state is gone
4959 editor.move_to_prev_snippet_tabstop(cx);
4960 assert(
4961 editor,
4962 cx,
4963 indoc! {"
4964 a.f(one, two, three)ˇ b
4965 a.f(one, two, three)ˇ b
4966 a.f(one, two, three)ˇ b
4967 "},
4968 );
4969 });
4970}
4971
4972#[gpui::test]
4973async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
4974 init_test(cx, |_| {});
4975
4976 let mut language = Language::new(
4977 LanguageConfig {
4978 name: "Rust".into(),
4979 path_suffixes: vec!["rs".to_string()],
4980 ..Default::default()
4981 },
4982 Some(tree_sitter_rust::language()),
4983 );
4984 let mut fake_servers = language
4985 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
4986 capabilities: lsp::ServerCapabilities {
4987 document_formatting_provider: Some(lsp::OneOf::Left(true)),
4988 ..Default::default()
4989 },
4990 ..Default::default()
4991 }))
4992 .await;
4993
4994 let fs = FakeFs::new(cx.executor());
4995 fs.insert_file("/file.rs", Default::default()).await;
4996
4997 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
4998 _ = project.update(cx, |project, _| project.languages().add(Arc::new(language)));
4999 let buffer = project
5000 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
5001 .await
5002 .unwrap();
5003
5004 cx.executor().start_waiting();
5005 let fake_server = fake_servers.next().await.unwrap();
5006
5007 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5008 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5009 _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
5010 assert!(cx.read(|cx| editor.is_dirty(cx)));
5011
5012 let save = editor
5013 .update(cx, |editor, cx| editor.save(project.clone(), cx))
5014 .unwrap();
5015 fake_server
5016 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
5017 assert_eq!(
5018 params.text_document.uri,
5019 lsp::Url::from_file_path("/file.rs").unwrap()
5020 );
5021 assert_eq!(params.options.tab_size, 4);
5022 Ok(Some(vec![lsp::TextEdit::new(
5023 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
5024 ", ".to_string(),
5025 )]))
5026 })
5027 .next()
5028 .await;
5029 cx.executor().start_waiting();
5030 let _x = save.await;
5031
5032 assert_eq!(
5033 editor.update(cx, |editor, cx| editor.text(cx)),
5034 "one, two\nthree\n"
5035 );
5036 assert!(!cx.read(|cx| editor.is_dirty(cx)));
5037
5038 _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
5039 assert!(cx.read(|cx| editor.is_dirty(cx)));
5040
5041 // Ensure we can still save even if formatting hangs.
5042 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
5043 assert_eq!(
5044 params.text_document.uri,
5045 lsp::Url::from_file_path("/file.rs").unwrap()
5046 );
5047 futures::future::pending::<()>().await;
5048 unreachable!()
5049 });
5050 let save = editor
5051 .update(cx, |editor, cx| editor.save(project.clone(), cx))
5052 .unwrap();
5053 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
5054 cx.executor().start_waiting();
5055 save.await;
5056 assert_eq!(
5057 editor.update(cx, |editor, cx| editor.text(cx)),
5058 "one\ntwo\nthree\n"
5059 );
5060 assert!(!cx.read(|cx| editor.is_dirty(cx)));
5061
5062 // Set rust language override and assert overridden tabsize is sent to language server
5063 update_test_language_settings(cx, |settings| {
5064 settings.languages.insert(
5065 "Rust".into(),
5066 LanguageSettingsContent {
5067 tab_size: NonZeroU32::new(8),
5068 ..Default::default()
5069 },
5070 );
5071 });
5072
5073 let save = editor
5074 .update(cx, |editor, cx| editor.save(project.clone(), cx))
5075 .unwrap();
5076 fake_server
5077 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
5078 assert_eq!(
5079 params.text_document.uri,
5080 lsp::Url::from_file_path("/file.rs").unwrap()
5081 );
5082 assert_eq!(params.options.tab_size, 8);
5083 Ok(Some(vec![]))
5084 })
5085 .next()
5086 .await;
5087 cx.executor().start_waiting();
5088 save.await;
5089}
5090
5091#[gpui::test]
5092async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
5093 init_test(cx, |_| {});
5094
5095 let mut language = Language::new(
5096 LanguageConfig {
5097 name: "Rust".into(),
5098 path_suffixes: vec!["rs".to_string()],
5099 ..Default::default()
5100 },
5101 Some(tree_sitter_rust::language()),
5102 );
5103 let mut fake_servers = language
5104 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
5105 capabilities: lsp::ServerCapabilities {
5106 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
5107 ..Default::default()
5108 },
5109 ..Default::default()
5110 }))
5111 .await;
5112
5113 let fs = FakeFs::new(cx.executor());
5114 fs.insert_file("/file.rs", Default::default()).await;
5115
5116 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
5117 _ = project.update(cx, |project, _| project.languages().add(Arc::new(language)));
5118 let buffer = project
5119 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
5120 .await
5121 .unwrap();
5122
5123 cx.executor().start_waiting();
5124 let fake_server = fake_servers.next().await.unwrap();
5125
5126 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5127 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5128 _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
5129 assert!(cx.read(|cx| editor.is_dirty(cx)));
5130
5131 let save = editor
5132 .update(cx, |editor, cx| editor.save(project.clone(), cx))
5133 .unwrap();
5134 fake_server
5135 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
5136 assert_eq!(
5137 params.text_document.uri,
5138 lsp::Url::from_file_path("/file.rs").unwrap()
5139 );
5140 assert_eq!(params.options.tab_size, 4);
5141 Ok(Some(vec![lsp::TextEdit::new(
5142 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
5143 ", ".to_string(),
5144 )]))
5145 })
5146 .next()
5147 .await;
5148 cx.executor().start_waiting();
5149 save.await;
5150 assert_eq!(
5151 editor.update(cx, |editor, cx| editor.text(cx)),
5152 "one, two\nthree\n"
5153 );
5154 assert!(!cx.read(|cx| editor.is_dirty(cx)));
5155
5156 _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
5157 assert!(cx.read(|cx| editor.is_dirty(cx)));
5158
5159 // Ensure we can still save even if formatting hangs.
5160 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
5161 move |params, _| async move {
5162 assert_eq!(
5163 params.text_document.uri,
5164 lsp::Url::from_file_path("/file.rs").unwrap()
5165 );
5166 futures::future::pending::<()>().await;
5167 unreachable!()
5168 },
5169 );
5170 let save = editor
5171 .update(cx, |editor, cx| editor.save(project.clone(), cx))
5172 .unwrap();
5173 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
5174 cx.executor().start_waiting();
5175 save.await;
5176 assert_eq!(
5177 editor.update(cx, |editor, cx| editor.text(cx)),
5178 "one\ntwo\nthree\n"
5179 );
5180 assert!(!cx.read(|cx| editor.is_dirty(cx)));
5181
5182 // Set rust language override and assert overridden tabsize is sent to language server
5183 update_test_language_settings(cx, |settings| {
5184 settings.languages.insert(
5185 "Rust".into(),
5186 LanguageSettingsContent {
5187 tab_size: NonZeroU32::new(8),
5188 ..Default::default()
5189 },
5190 );
5191 });
5192
5193 let save = editor
5194 .update(cx, |editor, cx| editor.save(project.clone(), cx))
5195 .unwrap();
5196 fake_server
5197 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
5198 assert_eq!(
5199 params.text_document.uri,
5200 lsp::Url::from_file_path("/file.rs").unwrap()
5201 );
5202 assert_eq!(params.options.tab_size, 8);
5203 Ok(Some(vec![]))
5204 })
5205 .next()
5206 .await;
5207 cx.executor().start_waiting();
5208 save.await;
5209}
5210
5211#[gpui::test]
5212async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
5213 init_test(cx, |settings| {
5214 settings.defaults.formatter = Some(language_settings::Formatter::LanguageServer)
5215 });
5216
5217 let mut language = Language::new(
5218 LanguageConfig {
5219 name: "Rust".into(),
5220 path_suffixes: vec!["rs".to_string()],
5221 // Enable Prettier formatting for the same buffer, and ensure
5222 // LSP is called instead of Prettier.
5223 prettier_parser_name: Some("test_parser".to_string()),
5224 ..Default::default()
5225 },
5226 Some(tree_sitter_rust::language()),
5227 );
5228 let mut fake_servers = language
5229 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
5230 capabilities: lsp::ServerCapabilities {
5231 document_formatting_provider: Some(lsp::OneOf::Left(true)),
5232 ..Default::default()
5233 },
5234 ..Default::default()
5235 }))
5236 .await;
5237
5238 let fs = FakeFs::new(cx.executor());
5239 fs.insert_file("/file.rs", Default::default()).await;
5240
5241 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
5242 _ = project.update(cx, |project, _| {
5243 project.languages().add(Arc::new(language));
5244 });
5245 let buffer = project
5246 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
5247 .await
5248 .unwrap();
5249
5250 cx.executor().start_waiting();
5251 let fake_server = fake_servers.next().await.unwrap();
5252
5253 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5254 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5255 _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
5256
5257 let format = editor
5258 .update(cx, |editor, cx| {
5259 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
5260 })
5261 .unwrap();
5262 fake_server
5263 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
5264 assert_eq!(
5265 params.text_document.uri,
5266 lsp::Url::from_file_path("/file.rs").unwrap()
5267 );
5268 assert_eq!(params.options.tab_size, 4);
5269 Ok(Some(vec![lsp::TextEdit::new(
5270 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
5271 ", ".to_string(),
5272 )]))
5273 })
5274 .next()
5275 .await;
5276 cx.executor().start_waiting();
5277 format.await;
5278 assert_eq!(
5279 editor.update(cx, |editor, cx| editor.text(cx)),
5280 "one, two\nthree\n"
5281 );
5282
5283 _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
5284 // Ensure we don't lock if formatting hangs.
5285 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
5286 assert_eq!(
5287 params.text_document.uri,
5288 lsp::Url::from_file_path("/file.rs").unwrap()
5289 );
5290 futures::future::pending::<()>().await;
5291 unreachable!()
5292 });
5293 let format = editor
5294 .update(cx, |editor, cx| {
5295 editor.perform_format(project, FormatTrigger::Manual, cx)
5296 })
5297 .unwrap();
5298 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
5299 cx.executor().start_waiting();
5300 format.await;
5301 assert_eq!(
5302 editor.update(cx, |editor, cx| editor.text(cx)),
5303 "one\ntwo\nthree\n"
5304 );
5305}
5306
5307#[gpui::test]
5308async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
5309 init_test(cx, |_| {});
5310
5311 let mut cx = EditorLspTestContext::new_rust(
5312 lsp::ServerCapabilities {
5313 document_formatting_provider: Some(lsp::OneOf::Left(true)),
5314 ..Default::default()
5315 },
5316 cx,
5317 )
5318 .await;
5319
5320 cx.set_state(indoc! {"
5321 one.twoˇ
5322 "});
5323
5324 // The format request takes a long time. When it completes, it inserts
5325 // a newline and an indent before the `.`
5326 cx.lsp
5327 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
5328 let executor = cx.background_executor().clone();
5329 async move {
5330 executor.timer(Duration::from_millis(100)).await;
5331 Ok(Some(vec![lsp::TextEdit {
5332 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
5333 new_text: "\n ".into(),
5334 }]))
5335 }
5336 });
5337
5338 // Submit a format request.
5339 let format_1 = cx
5340 .update_editor(|editor, cx| editor.format(&Format, cx))
5341 .unwrap();
5342 cx.executor().run_until_parked();
5343
5344 // Submit a second format request.
5345 let format_2 = cx
5346 .update_editor(|editor, cx| editor.format(&Format, cx))
5347 .unwrap();
5348 cx.executor().run_until_parked();
5349
5350 // Wait for both format requests to complete
5351 cx.executor().advance_clock(Duration::from_millis(200));
5352 cx.executor().start_waiting();
5353 format_1.await.unwrap();
5354 cx.executor().start_waiting();
5355 format_2.await.unwrap();
5356
5357 // The formatting edits only happens once.
5358 cx.assert_editor_state(indoc! {"
5359 one
5360 .twoˇ
5361 "});
5362}
5363
5364#[gpui::test]
5365async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
5366 init_test(cx, |settings| {
5367 settings.defaults.formatter = Some(language_settings::Formatter::Auto)
5368 });
5369
5370 let mut cx = EditorLspTestContext::new_rust(
5371 lsp::ServerCapabilities {
5372 document_formatting_provider: Some(lsp::OneOf::Left(true)),
5373 ..Default::default()
5374 },
5375 cx,
5376 )
5377 .await;
5378
5379 // Set up a buffer white some trailing whitespace and no trailing newline.
5380 cx.set_state(
5381 &[
5382 "one ", //
5383 "twoˇ", //
5384 "three ", //
5385 "four", //
5386 ]
5387 .join("\n"),
5388 );
5389
5390 // Submit a format request.
5391 let format = cx
5392 .update_editor(|editor, cx| editor.format(&Format, cx))
5393 .unwrap();
5394
5395 // Record which buffer changes have been sent to the language server
5396 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
5397 cx.lsp
5398 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
5399 let buffer_changes = buffer_changes.clone();
5400 move |params, _| {
5401 buffer_changes.lock().extend(
5402 params
5403 .content_changes
5404 .into_iter()
5405 .map(|e| (e.range.unwrap(), e.text)),
5406 );
5407 }
5408 });
5409
5410 // Handle formatting requests to the language server.
5411 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
5412 let buffer_changes = buffer_changes.clone();
5413 move |_, _| {
5414 // When formatting is requested, trailing whitespace has already been stripped,
5415 // and the trailing newline has already been added.
5416 assert_eq!(
5417 &buffer_changes.lock()[1..],
5418 &[
5419 (
5420 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
5421 "".into()
5422 ),
5423 (
5424 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
5425 "".into()
5426 ),
5427 (
5428 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
5429 "\n".into()
5430 ),
5431 ]
5432 );
5433
5434 // Insert blank lines between each line of the buffer.
5435 async move {
5436 Ok(Some(vec![
5437 lsp::TextEdit {
5438 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
5439 new_text: "\n".into(),
5440 },
5441 lsp::TextEdit {
5442 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
5443 new_text: "\n".into(),
5444 },
5445 ]))
5446 }
5447 }
5448 });
5449
5450 // After formatting the buffer, the trailing whitespace is stripped,
5451 // a newline is appended, and the edits provided by the language server
5452 // have been applied.
5453 format.await.unwrap();
5454 cx.assert_editor_state(
5455 &[
5456 "one", //
5457 "", //
5458 "twoˇ", //
5459 "", //
5460 "three", //
5461 "four", //
5462 "", //
5463 ]
5464 .join("\n"),
5465 );
5466
5467 // Undoing the formatting undoes the trailing whitespace removal, the
5468 // trailing newline, and the LSP edits.
5469 cx.update_buffer(|buffer, cx| buffer.undo(cx));
5470 cx.assert_editor_state(
5471 &[
5472 "one ", //
5473 "twoˇ", //
5474 "three ", //
5475 "four", //
5476 ]
5477 .join("\n"),
5478 );
5479}
5480
5481#[gpui::test]
5482async fn test_completion(cx: &mut gpui::TestAppContext) {
5483 init_test(cx, |_| {});
5484
5485 let mut cx = EditorLspTestContext::new_rust(
5486 lsp::ServerCapabilities {
5487 completion_provider: Some(lsp::CompletionOptions {
5488 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
5489 resolve_provider: Some(true),
5490 ..Default::default()
5491 }),
5492 ..Default::default()
5493 },
5494 cx,
5495 )
5496 .await;
5497
5498 cx.set_state(indoc! {"
5499 oneˇ
5500 two
5501 three
5502 "});
5503 cx.simulate_keystroke(".");
5504 handle_completion_request(
5505 &mut cx,
5506 indoc! {"
5507 one.|<>
5508 two
5509 three
5510 "},
5511 vec!["first_completion", "second_completion"],
5512 )
5513 .await;
5514 cx.condition(|editor, _| editor.context_menu_visible())
5515 .await;
5516 let apply_additional_edits = cx.update_editor(|editor, cx| {
5517 editor.context_menu_next(&Default::default(), cx);
5518 editor
5519 .confirm_completion(&ConfirmCompletion::default(), cx)
5520 .unwrap()
5521 });
5522 cx.assert_editor_state(indoc! {"
5523 one.second_completionˇ
5524 two
5525 three
5526 "});
5527
5528 handle_resolve_completion_request(
5529 &mut cx,
5530 Some(vec![
5531 (
5532 //This overlaps with the primary completion edit which is
5533 //misbehavior from the LSP spec, test that we filter it out
5534 indoc! {"
5535 one.second_ˇcompletion
5536 two
5537 threeˇ
5538 "},
5539 "overlapping additional edit",
5540 ),
5541 (
5542 indoc! {"
5543 one.second_completion
5544 two
5545 threeˇ
5546 "},
5547 "\nadditional edit",
5548 ),
5549 ]),
5550 )
5551 .await;
5552 apply_additional_edits.await.unwrap();
5553 cx.assert_editor_state(indoc! {"
5554 one.second_completionˇ
5555 two
5556 three
5557 additional edit
5558 "});
5559
5560 cx.set_state(indoc! {"
5561 one.second_completion
5562 twoˇ
5563 threeˇ
5564 additional edit
5565 "});
5566 cx.simulate_keystroke(" ");
5567 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
5568 cx.simulate_keystroke("s");
5569 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
5570
5571 cx.assert_editor_state(indoc! {"
5572 one.second_completion
5573 two sˇ
5574 three sˇ
5575 additional edit
5576 "});
5577 handle_completion_request(
5578 &mut cx,
5579 indoc! {"
5580 one.second_completion
5581 two s
5582 three <s|>
5583 additional edit
5584 "},
5585 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
5586 )
5587 .await;
5588 cx.condition(|editor, _| editor.context_menu_visible())
5589 .await;
5590
5591 cx.simulate_keystroke("i");
5592
5593 handle_completion_request(
5594 &mut cx,
5595 indoc! {"
5596 one.second_completion
5597 two si
5598 three <si|>
5599 additional edit
5600 "},
5601 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
5602 )
5603 .await;
5604 cx.condition(|editor, _| editor.context_menu_visible())
5605 .await;
5606
5607 let apply_additional_edits = cx.update_editor(|editor, cx| {
5608 editor
5609 .confirm_completion(&ConfirmCompletion::default(), cx)
5610 .unwrap()
5611 });
5612 cx.assert_editor_state(indoc! {"
5613 one.second_completion
5614 two sixth_completionˇ
5615 three sixth_completionˇ
5616 additional edit
5617 "});
5618
5619 handle_resolve_completion_request(&mut cx, None).await;
5620 apply_additional_edits.await.unwrap();
5621
5622 _ = cx.update(|cx| {
5623 cx.update_global::<SettingsStore, _>(|settings, cx| {
5624 settings.update_user_settings::<EditorSettings>(cx, |settings| {
5625 settings.show_completions_on_input = Some(false);
5626 });
5627 })
5628 });
5629 cx.set_state("editorˇ");
5630 cx.simulate_keystroke(".");
5631 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
5632 cx.simulate_keystroke("c");
5633 cx.simulate_keystroke("l");
5634 cx.simulate_keystroke("o");
5635 cx.assert_editor_state("editor.cloˇ");
5636 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
5637 cx.update_editor(|editor, cx| {
5638 editor.show_completions(&ShowCompletions, cx);
5639 });
5640 handle_completion_request(&mut cx, "editor.<clo|>", vec!["close", "clobber"]).await;
5641 cx.condition(|editor, _| editor.context_menu_visible())
5642 .await;
5643 let apply_additional_edits = cx.update_editor(|editor, cx| {
5644 editor
5645 .confirm_completion(&ConfirmCompletion::default(), cx)
5646 .unwrap()
5647 });
5648 cx.assert_editor_state("editor.closeˇ");
5649 handle_resolve_completion_request(&mut cx, None).await;
5650 apply_additional_edits.await.unwrap();
5651}
5652
5653#[gpui::test]
5654async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
5655 init_test(cx, |_| {});
5656 let mut cx = EditorTestContext::new(cx).await;
5657 let language = Arc::new(Language::new(
5658 LanguageConfig {
5659 line_comment: Some("// ".into()),
5660 ..Default::default()
5661 },
5662 Some(tree_sitter_rust::language()),
5663 ));
5664 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
5665
5666 // If multiple selections intersect a line, the line is only toggled once.
5667 cx.set_state(indoc! {"
5668 fn a() {
5669 «//b();
5670 ˇ»// «c();
5671 //ˇ» d();
5672 }
5673 "});
5674
5675 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5676
5677 cx.assert_editor_state(indoc! {"
5678 fn a() {
5679 «b();
5680 c();
5681 ˇ» d();
5682 }
5683 "});
5684
5685 // The comment prefix is inserted at the same column for every line in a
5686 // selection.
5687 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5688
5689 cx.assert_editor_state(indoc! {"
5690 fn a() {
5691 // «b();
5692 // c();
5693 ˇ»// d();
5694 }
5695 "});
5696
5697 // If a selection ends at the beginning of a line, that line is not toggled.
5698 cx.set_selections_state(indoc! {"
5699 fn a() {
5700 // b();
5701 «// c();
5702 ˇ» // d();
5703 }
5704 "});
5705
5706 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5707
5708 cx.assert_editor_state(indoc! {"
5709 fn a() {
5710 // b();
5711 «c();
5712 ˇ» // d();
5713 }
5714 "});
5715
5716 // If a selection span a single line and is empty, the line is toggled.
5717 cx.set_state(indoc! {"
5718 fn a() {
5719 a();
5720 b();
5721 ˇ
5722 }
5723 "});
5724
5725 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5726
5727 cx.assert_editor_state(indoc! {"
5728 fn a() {
5729 a();
5730 b();
5731 //•ˇ
5732 }
5733 "});
5734
5735 // If a selection span multiple lines, empty lines are not toggled.
5736 cx.set_state(indoc! {"
5737 fn a() {
5738 «a();
5739
5740 c();ˇ»
5741 }
5742 "});
5743
5744 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5745
5746 cx.assert_editor_state(indoc! {"
5747 fn a() {
5748 // «a();
5749
5750 // c();ˇ»
5751 }
5752 "});
5753}
5754
5755#[gpui::test]
5756async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
5757 init_test(cx, |_| {});
5758
5759 let language = Arc::new(Language::new(
5760 LanguageConfig {
5761 line_comment: Some("// ".into()),
5762 ..Default::default()
5763 },
5764 Some(tree_sitter_rust::language()),
5765 ));
5766
5767 let registry = Arc::new(LanguageRegistry::test());
5768 registry.add(language.clone());
5769
5770 let mut cx = EditorTestContext::new(cx).await;
5771 cx.update_buffer(|buffer, cx| {
5772 buffer.set_language_registry(registry);
5773 buffer.set_language(Some(language), cx);
5774 });
5775
5776 let toggle_comments = &ToggleComments {
5777 advance_downwards: true,
5778 };
5779
5780 // Single cursor on one line -> advance
5781 // Cursor moves horizontally 3 characters as well on non-blank line
5782 cx.set_state(indoc!(
5783 "fn a() {
5784 ˇdog();
5785 cat();
5786 }"
5787 ));
5788 cx.update_editor(|editor, cx| {
5789 editor.toggle_comments(toggle_comments, cx);
5790 });
5791 cx.assert_editor_state(indoc!(
5792 "fn a() {
5793 // dog();
5794 catˇ();
5795 }"
5796 ));
5797
5798 // Single selection on one line -> don't advance
5799 cx.set_state(indoc!(
5800 "fn a() {
5801 «dog()ˇ»;
5802 cat();
5803 }"
5804 ));
5805 cx.update_editor(|editor, cx| {
5806 editor.toggle_comments(toggle_comments, cx);
5807 });
5808 cx.assert_editor_state(indoc!(
5809 "fn a() {
5810 // «dog()ˇ»;
5811 cat();
5812 }"
5813 ));
5814
5815 // Multiple cursors on one line -> advance
5816 cx.set_state(indoc!(
5817 "fn a() {
5818 ˇdˇog();
5819 cat();
5820 }"
5821 ));
5822 cx.update_editor(|editor, cx| {
5823 editor.toggle_comments(toggle_comments, cx);
5824 });
5825 cx.assert_editor_state(indoc!(
5826 "fn a() {
5827 // dog();
5828 catˇ(ˇ);
5829 }"
5830 ));
5831
5832 // Multiple cursors on one line, with selection -> don't advance
5833 cx.set_state(indoc!(
5834 "fn a() {
5835 ˇdˇog«()ˇ»;
5836 cat();
5837 }"
5838 ));
5839 cx.update_editor(|editor, cx| {
5840 editor.toggle_comments(toggle_comments, cx);
5841 });
5842 cx.assert_editor_state(indoc!(
5843 "fn a() {
5844 // ˇdˇog«()ˇ»;
5845 cat();
5846 }"
5847 ));
5848
5849 // Single cursor on one line -> advance
5850 // Cursor moves to column 0 on blank line
5851 cx.set_state(indoc!(
5852 "fn a() {
5853 ˇdog();
5854
5855 cat();
5856 }"
5857 ));
5858 cx.update_editor(|editor, cx| {
5859 editor.toggle_comments(toggle_comments, cx);
5860 });
5861 cx.assert_editor_state(indoc!(
5862 "fn a() {
5863 // dog();
5864 ˇ
5865 cat();
5866 }"
5867 ));
5868
5869 // Single cursor on one line -> advance
5870 // Cursor starts and ends at column 0
5871 cx.set_state(indoc!(
5872 "fn a() {
5873 ˇ dog();
5874 cat();
5875 }"
5876 ));
5877 cx.update_editor(|editor, cx| {
5878 editor.toggle_comments(toggle_comments, cx);
5879 });
5880 cx.assert_editor_state(indoc!(
5881 "fn a() {
5882 // dog();
5883 ˇ cat();
5884 }"
5885 ));
5886}
5887
5888#[gpui::test]
5889async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
5890 init_test(cx, |_| {});
5891
5892 let mut cx = EditorTestContext::new(cx).await;
5893
5894 let html_language = Arc::new(
5895 Language::new(
5896 LanguageConfig {
5897 name: "HTML".into(),
5898 block_comment: Some(("<!-- ".into(), " -->".into())),
5899 ..Default::default()
5900 },
5901 Some(tree_sitter_html::language()),
5902 )
5903 .with_injection_query(
5904 r#"
5905 (script_element
5906 (raw_text) @content
5907 (#set! "language" "javascript"))
5908 "#,
5909 )
5910 .unwrap(),
5911 );
5912
5913 let javascript_language = Arc::new(Language::new(
5914 LanguageConfig {
5915 name: "JavaScript".into(),
5916 line_comment: Some("// ".into()),
5917 ..Default::default()
5918 },
5919 Some(tree_sitter_typescript::language_tsx()),
5920 ));
5921
5922 let registry = Arc::new(LanguageRegistry::test());
5923 registry.add(html_language.clone());
5924 registry.add(javascript_language.clone());
5925
5926 cx.update_buffer(|buffer, cx| {
5927 buffer.set_language_registry(registry);
5928 buffer.set_language(Some(html_language), cx);
5929 });
5930
5931 // Toggle comments for empty selections
5932 cx.set_state(
5933 &r#"
5934 <p>A</p>ˇ
5935 <p>B</p>ˇ
5936 <p>C</p>ˇ
5937 "#
5938 .unindent(),
5939 );
5940 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5941 cx.assert_editor_state(
5942 &r#"
5943 <!-- <p>A</p>ˇ -->
5944 <!-- <p>B</p>ˇ -->
5945 <!-- <p>C</p>ˇ -->
5946 "#
5947 .unindent(),
5948 );
5949 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5950 cx.assert_editor_state(
5951 &r#"
5952 <p>A</p>ˇ
5953 <p>B</p>ˇ
5954 <p>C</p>ˇ
5955 "#
5956 .unindent(),
5957 );
5958
5959 // Toggle comments for mixture of empty and non-empty selections, where
5960 // multiple selections occupy a given line.
5961 cx.set_state(
5962 &r#"
5963 <p>A«</p>
5964 <p>ˇ»B</p>ˇ
5965 <p>C«</p>
5966 <p>ˇ»D</p>ˇ
5967 "#
5968 .unindent(),
5969 );
5970
5971 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5972 cx.assert_editor_state(
5973 &r#"
5974 <!-- <p>A«</p>
5975 <p>ˇ»B</p>ˇ -->
5976 <!-- <p>C«</p>
5977 <p>ˇ»D</p>ˇ -->
5978 "#
5979 .unindent(),
5980 );
5981 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5982 cx.assert_editor_state(
5983 &r#"
5984 <p>A«</p>
5985 <p>ˇ»B</p>ˇ
5986 <p>C«</p>
5987 <p>ˇ»D</p>ˇ
5988 "#
5989 .unindent(),
5990 );
5991
5992 // Toggle comments when different languages are active for different
5993 // selections.
5994 cx.set_state(
5995 &r#"
5996 ˇ<script>
5997 ˇvar x = new Y();
5998 ˇ</script>
5999 "#
6000 .unindent(),
6001 );
6002 cx.executor().run_until_parked();
6003 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
6004 cx.assert_editor_state(
6005 &r#"
6006 <!-- ˇ<script> -->
6007 // ˇvar x = new Y();
6008 <!-- ˇ</script> -->
6009 "#
6010 .unindent(),
6011 );
6012}
6013
6014#[gpui::test]
6015fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
6016 init_test(cx, |_| {});
6017
6018 let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(3, 4, 'a')));
6019 let multibuffer = cx.new_model(|cx| {
6020 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
6021 multibuffer.push_excerpts(
6022 buffer.clone(),
6023 [
6024 ExcerptRange {
6025 context: Point::new(0, 0)..Point::new(0, 4),
6026 primary: None,
6027 },
6028 ExcerptRange {
6029 context: Point::new(1, 0)..Point::new(1, 4),
6030 primary: None,
6031 },
6032 ],
6033 cx,
6034 );
6035 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
6036 multibuffer
6037 });
6038
6039 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
6040 _ = view.update(cx, |view, cx| {
6041 assert_eq!(view.text(cx), "aaaa\nbbbb");
6042 view.change_selections(None, cx, |s| {
6043 s.select_ranges([
6044 Point::new(0, 0)..Point::new(0, 0),
6045 Point::new(1, 0)..Point::new(1, 0),
6046 ])
6047 });
6048
6049 view.handle_input("X", cx);
6050 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
6051 assert_eq!(
6052 view.selections.ranges(cx),
6053 [
6054 Point::new(0, 1)..Point::new(0, 1),
6055 Point::new(1, 1)..Point::new(1, 1),
6056 ]
6057 );
6058
6059 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
6060 view.change_selections(None, cx, |s| {
6061 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
6062 });
6063 view.backspace(&Default::default(), cx);
6064 assert_eq!(view.text(cx), "Xa\nbbb");
6065 assert_eq!(
6066 view.selections.ranges(cx),
6067 [Point::new(1, 0)..Point::new(1, 0)]
6068 );
6069
6070 view.change_selections(None, cx, |s| {
6071 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
6072 });
6073 view.backspace(&Default::default(), cx);
6074 assert_eq!(view.text(cx), "X\nbb");
6075 assert_eq!(
6076 view.selections.ranges(cx),
6077 [Point::new(0, 1)..Point::new(0, 1)]
6078 );
6079 });
6080}
6081
6082#[gpui::test]
6083fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
6084 init_test(cx, |_| {});
6085
6086 let markers = vec![('[', ']').into(), ('(', ')').into()];
6087 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
6088 indoc! {"
6089 [aaaa
6090 (bbbb]
6091 cccc)",
6092 },
6093 markers.clone(),
6094 );
6095 let excerpt_ranges = markers.into_iter().map(|marker| {
6096 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
6097 ExcerptRange {
6098 context,
6099 primary: None,
6100 }
6101 });
6102 let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), initial_text));
6103 let multibuffer = cx.new_model(|cx| {
6104 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
6105 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
6106 multibuffer
6107 });
6108
6109 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
6110 _ = view.update(cx, |view, cx| {
6111 let (expected_text, selection_ranges) = marked_text_ranges(
6112 indoc! {"
6113 aaaa
6114 bˇbbb
6115 bˇbbˇb
6116 cccc"
6117 },
6118 true,
6119 );
6120 assert_eq!(view.text(cx), expected_text);
6121 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
6122
6123 view.handle_input("X", cx);
6124
6125 let (expected_text, expected_selections) = marked_text_ranges(
6126 indoc! {"
6127 aaaa
6128 bXˇbbXb
6129 bXˇbbXˇb
6130 cccc"
6131 },
6132 false,
6133 );
6134 assert_eq!(view.text(cx), expected_text);
6135 assert_eq!(view.selections.ranges(cx), expected_selections);
6136
6137 view.newline(&Newline, cx);
6138 let (expected_text, expected_selections) = marked_text_ranges(
6139 indoc! {"
6140 aaaa
6141 bX
6142 ˇbbX
6143 b
6144 bX
6145 ˇbbX
6146 ˇb
6147 cccc"
6148 },
6149 false,
6150 );
6151 assert_eq!(view.text(cx), expected_text);
6152 assert_eq!(view.selections.ranges(cx), expected_selections);
6153 });
6154}
6155
6156#[gpui::test]
6157fn test_refresh_selections(cx: &mut TestAppContext) {
6158 init_test(cx, |_| {});
6159
6160 let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(3, 4, 'a')));
6161 let mut excerpt1_id = None;
6162 let multibuffer = cx.new_model(|cx| {
6163 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
6164 excerpt1_id = multibuffer
6165 .push_excerpts(
6166 buffer.clone(),
6167 [
6168 ExcerptRange {
6169 context: Point::new(0, 0)..Point::new(1, 4),
6170 primary: None,
6171 },
6172 ExcerptRange {
6173 context: Point::new(1, 0)..Point::new(2, 4),
6174 primary: None,
6175 },
6176 ],
6177 cx,
6178 )
6179 .into_iter()
6180 .next();
6181 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
6182 multibuffer
6183 });
6184
6185 let editor = cx.add_window(|cx| {
6186 let mut editor = build_editor(multibuffer.clone(), cx);
6187 let snapshot = editor.snapshot(cx);
6188 editor.change_selections(None, cx, |s| {
6189 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
6190 });
6191 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
6192 assert_eq!(
6193 editor.selections.ranges(cx),
6194 [
6195 Point::new(1, 3)..Point::new(1, 3),
6196 Point::new(2, 1)..Point::new(2, 1),
6197 ]
6198 );
6199 editor
6200 });
6201
6202 // Refreshing selections is a no-op when excerpts haven't changed.
6203 _ = editor.update(cx, |editor, cx| {
6204 editor.change_selections(None, cx, |s| s.refresh());
6205 assert_eq!(
6206 editor.selections.ranges(cx),
6207 [
6208 Point::new(1, 3)..Point::new(1, 3),
6209 Point::new(2, 1)..Point::new(2, 1),
6210 ]
6211 );
6212 });
6213
6214 _ = multibuffer.update(cx, |multibuffer, cx| {
6215 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
6216 });
6217 _ = editor.update(cx, |editor, cx| {
6218 // Removing an excerpt causes the first selection to become degenerate.
6219 assert_eq!(
6220 editor.selections.ranges(cx),
6221 [
6222 Point::new(0, 0)..Point::new(0, 0),
6223 Point::new(0, 1)..Point::new(0, 1)
6224 ]
6225 );
6226
6227 // Refreshing selections will relocate the first selection to the original buffer
6228 // location.
6229 editor.change_selections(None, cx, |s| s.refresh());
6230 assert_eq!(
6231 editor.selections.ranges(cx),
6232 [
6233 Point::new(0, 1)..Point::new(0, 1),
6234 Point::new(0, 3)..Point::new(0, 3)
6235 ]
6236 );
6237 assert!(editor.selections.pending_anchor().is_some());
6238 });
6239}
6240
6241#[gpui::test]
6242fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
6243 init_test(cx, |_| {});
6244
6245 let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(3, 4, 'a')));
6246 let mut excerpt1_id = None;
6247 let multibuffer = cx.new_model(|cx| {
6248 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
6249 excerpt1_id = multibuffer
6250 .push_excerpts(
6251 buffer.clone(),
6252 [
6253 ExcerptRange {
6254 context: Point::new(0, 0)..Point::new(1, 4),
6255 primary: None,
6256 },
6257 ExcerptRange {
6258 context: Point::new(1, 0)..Point::new(2, 4),
6259 primary: None,
6260 },
6261 ],
6262 cx,
6263 )
6264 .into_iter()
6265 .next();
6266 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
6267 multibuffer
6268 });
6269
6270 let editor = cx.add_window(|cx| {
6271 let mut editor = build_editor(multibuffer.clone(), cx);
6272 let snapshot = editor.snapshot(cx);
6273 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
6274 assert_eq!(
6275 editor.selections.ranges(cx),
6276 [Point::new(1, 3)..Point::new(1, 3)]
6277 );
6278 editor
6279 });
6280
6281 _ = multibuffer.update(cx, |multibuffer, cx| {
6282 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
6283 });
6284 _ = editor.update(cx, |editor, cx| {
6285 assert_eq!(
6286 editor.selections.ranges(cx),
6287 [Point::new(0, 0)..Point::new(0, 0)]
6288 );
6289
6290 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
6291 editor.change_selections(None, cx, |s| s.refresh());
6292 assert_eq!(
6293 editor.selections.ranges(cx),
6294 [Point::new(0, 3)..Point::new(0, 3)]
6295 );
6296 assert!(editor.selections.pending_anchor().is_some());
6297 });
6298}
6299
6300#[gpui::test]
6301async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
6302 init_test(cx, |_| {});
6303
6304 let language = Arc::new(
6305 Language::new(
6306 LanguageConfig {
6307 brackets: BracketPairConfig {
6308 pairs: vec![
6309 BracketPair {
6310 start: "{".to_string(),
6311 end: "}".to_string(),
6312 close: true,
6313 newline: true,
6314 },
6315 BracketPair {
6316 start: "/* ".to_string(),
6317 end: " */".to_string(),
6318 close: true,
6319 newline: true,
6320 },
6321 ],
6322 ..Default::default()
6323 },
6324 ..Default::default()
6325 },
6326 Some(tree_sitter_rust::language()),
6327 )
6328 .with_indents_query("")
6329 .unwrap(),
6330 );
6331
6332 let text = concat!(
6333 "{ }\n", //
6334 " x\n", //
6335 " /* */\n", //
6336 "x\n", //
6337 "{{} }\n", //
6338 );
6339
6340 let buffer = cx
6341 .new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx));
6342 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6343 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6344 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6345 .await;
6346
6347 _ = view.update(cx, |view, cx| {
6348 view.change_selections(None, cx, |s| {
6349 s.select_display_ranges([
6350 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3),
6351 DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
6352 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
6353 ])
6354 });
6355 view.newline(&Newline, cx);
6356
6357 assert_eq!(
6358 view.buffer().read(cx).read(cx).text(),
6359 concat!(
6360 "{ \n", // Suppress rustfmt
6361 "\n", //
6362 "}\n", //
6363 " x\n", //
6364 " /* \n", //
6365 " \n", //
6366 " */\n", //
6367 "x\n", //
6368 "{{} \n", //
6369 "}\n", //
6370 )
6371 );
6372 });
6373}
6374
6375#[gpui::test]
6376fn test_highlighted_ranges(cx: &mut TestAppContext) {
6377 init_test(cx, |_| {});
6378
6379 let editor = cx.add_window(|cx| {
6380 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
6381 build_editor(buffer.clone(), cx)
6382 });
6383
6384 _ = editor.update(cx, |editor, cx| {
6385 struct Type1;
6386 struct Type2;
6387
6388 let buffer = editor.buffer.read(cx).snapshot(cx);
6389
6390 let anchor_range =
6391 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
6392
6393 editor.highlight_background::<Type1>(
6394 vec![
6395 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
6396 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
6397 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
6398 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
6399 ],
6400 |_| Hsla::red(),
6401 cx,
6402 );
6403 editor.highlight_background::<Type2>(
6404 vec![
6405 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
6406 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
6407 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
6408 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
6409 ],
6410 |_| Hsla::green(),
6411 cx,
6412 );
6413
6414 let snapshot = editor.snapshot(cx);
6415 let mut highlighted_ranges = editor.background_highlights_in_range(
6416 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
6417 &snapshot,
6418 cx.theme().colors(),
6419 );
6420 // Enforce a consistent ordering based on color without relying on the ordering of the
6421 // highlight's `TypeId` which is non-executor.
6422 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
6423 assert_eq!(
6424 highlighted_ranges,
6425 &[
6426 (
6427 DisplayPoint::new(4, 2)..DisplayPoint::new(4, 4),
6428 Hsla::red(),
6429 ),
6430 (
6431 DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
6432 Hsla::red(),
6433 ),
6434 (
6435 DisplayPoint::new(3, 2)..DisplayPoint::new(3, 5),
6436 Hsla::green(),
6437 ),
6438 (
6439 DisplayPoint::new(5, 3)..DisplayPoint::new(5, 6),
6440 Hsla::green(),
6441 ),
6442 ]
6443 );
6444 assert_eq!(
6445 editor.background_highlights_in_range(
6446 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
6447 &snapshot,
6448 cx.theme().colors(),
6449 ),
6450 &[(
6451 DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
6452 Hsla::red(),
6453 )]
6454 );
6455 });
6456}
6457
6458// todo!(following)
6459#[gpui::test]
6460async fn test_following(cx: &mut gpui::TestAppContext) {
6461 init_test(cx, |_| {});
6462
6463 let fs = FakeFs::new(cx.executor());
6464 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6465
6466 let buffer = project.update(cx, |project, cx| {
6467 let buffer = project
6468 .create_buffer(&sample_text(16, 8, 'a'), None, cx)
6469 .unwrap();
6470 cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
6471 });
6472 let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
6473 let follower = cx.update(|cx| {
6474 cx.open_window(
6475 WindowOptions {
6476 bounds: WindowBounds::Fixed(Bounds::from_corners(
6477 gpui::Point::new((0. as f64).into(), (0. as f64).into()),
6478 gpui::Point::new((10. as f64).into(), (80. as f64).into()),
6479 )),
6480 ..Default::default()
6481 },
6482 |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
6483 )
6484 });
6485
6486 let is_still_following = Rc::new(RefCell::new(true));
6487 let follower_edit_event_count = Rc::new(RefCell::new(0));
6488 let pending_update = Rc::new(RefCell::new(None));
6489 _ = follower.update(cx, {
6490 let update = pending_update.clone();
6491 let is_still_following = is_still_following.clone();
6492 let follower_edit_event_count = follower_edit_event_count.clone();
6493 |_, cx| {
6494 cx.subscribe(
6495 &leader.root_view(cx).unwrap(),
6496 move |_, leader, event, cx| {
6497 leader
6498 .read(cx)
6499 .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx);
6500 },
6501 )
6502 .detach();
6503
6504 cx.subscribe(
6505 &follower.root_view(cx).unwrap(),
6506 move |_, _, event: &EditorEvent, _cx| {
6507 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
6508 *is_still_following.borrow_mut() = false;
6509 }
6510
6511 if let EditorEvent::BufferEdited = event {
6512 *follower_edit_event_count.borrow_mut() += 1;
6513 }
6514 },
6515 )
6516 .detach();
6517 }
6518 });
6519
6520 // Update the selections only
6521 _ = leader.update(cx, |leader, cx| {
6522 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
6523 });
6524 follower
6525 .update(cx, |follower, cx| {
6526 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6527 })
6528 .unwrap()
6529 .await
6530 .unwrap();
6531 _ = follower.update(cx, |follower, cx| {
6532 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
6533 });
6534 assert_eq!(*is_still_following.borrow(), true);
6535 assert_eq!(*follower_edit_event_count.borrow(), 0);
6536
6537 // Update the scroll position only
6538 _ = leader.update(cx, |leader, cx| {
6539 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
6540 });
6541 follower
6542 .update(cx, |follower, cx| {
6543 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6544 })
6545 .unwrap()
6546 .await
6547 .unwrap();
6548 assert_eq!(
6549 follower
6550 .update(cx, |follower, cx| follower.scroll_position(cx))
6551 .unwrap(),
6552 gpui::Point::new(1.5, 3.5)
6553 );
6554 assert_eq!(*is_still_following.borrow(), true);
6555 assert_eq!(*follower_edit_event_count.borrow(), 0);
6556
6557 // Update the selections and scroll position. The follower's scroll position is updated
6558 // via autoscroll, not via the leader's exact scroll position.
6559 _ = leader.update(cx, |leader, cx| {
6560 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
6561 leader.request_autoscroll(Autoscroll::newest(), cx);
6562 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
6563 });
6564 follower
6565 .update(cx, |follower, cx| {
6566 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6567 })
6568 .unwrap()
6569 .await
6570 .unwrap();
6571 _ = follower.update(cx, |follower, cx| {
6572 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
6573 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
6574 });
6575 assert_eq!(*is_still_following.borrow(), true);
6576
6577 // Creating a pending selection that precedes another selection
6578 _ = leader.update(cx, |leader, cx| {
6579 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
6580 leader.begin_selection(DisplayPoint::new(0, 0), true, 1, cx);
6581 });
6582 follower
6583 .update(cx, |follower, cx| {
6584 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6585 })
6586 .unwrap()
6587 .await
6588 .unwrap();
6589 _ = follower.update(cx, |follower, cx| {
6590 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
6591 });
6592 assert_eq!(*is_still_following.borrow(), true);
6593
6594 // Extend the pending selection so that it surrounds another selection
6595 _ = leader.update(cx, |leader, cx| {
6596 leader.extend_selection(DisplayPoint::new(0, 2), 1, cx);
6597 });
6598 follower
6599 .update(cx, |follower, cx| {
6600 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6601 })
6602 .unwrap()
6603 .await
6604 .unwrap();
6605 _ = follower.update(cx, |follower, cx| {
6606 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
6607 });
6608
6609 // Scrolling locally breaks the follow
6610 _ = follower.update(cx, |follower, cx| {
6611 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
6612 follower.set_scroll_anchor(
6613 ScrollAnchor {
6614 anchor: top_anchor,
6615 offset: gpui::Point::new(0.0, 0.5),
6616 },
6617 cx,
6618 );
6619 });
6620 assert_eq!(*is_still_following.borrow(), false);
6621}
6622
6623#[gpui::test]
6624async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
6625 init_test(cx, |_| {});
6626
6627 let fs = FakeFs::new(cx.executor());
6628 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6629 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
6630 let pane = workspace
6631 .update(cx, |workspace, _| workspace.active_pane().clone())
6632 .unwrap();
6633
6634 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
6635
6636 let leader = pane.update(cx, |_, cx| {
6637 let multibuffer = cx.new_model(|_| MultiBuffer::new(0, ReadWrite));
6638 cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
6639 });
6640
6641 // Start following the editor when it has no excerpts.
6642 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
6643 let follower_1 = cx
6644 .update_window(*workspace.deref(), |_, cx| {
6645 Editor::from_state_proto(
6646 pane.clone(),
6647 workspace.root_view(cx).unwrap(),
6648 ViewId {
6649 creator: Default::default(),
6650 id: 0,
6651 },
6652 &mut state_message,
6653 cx,
6654 )
6655 })
6656 .unwrap()
6657 .unwrap()
6658 .await
6659 .unwrap();
6660
6661 let update_message = Rc::new(RefCell::new(None));
6662 follower_1.update(cx, {
6663 let update = update_message.clone();
6664 |_, cx| {
6665 cx.subscribe(&leader, move |_, leader, event, cx| {
6666 leader
6667 .read(cx)
6668 .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx);
6669 })
6670 .detach();
6671 }
6672 });
6673
6674 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
6675 (
6676 project
6677 .create_buffer("abc\ndef\nghi\njkl\n", None, cx)
6678 .unwrap(),
6679 project
6680 .create_buffer("mno\npqr\nstu\nvwx\n", None, cx)
6681 .unwrap(),
6682 )
6683 });
6684
6685 // Insert some excerpts.
6686 _ = leader.update(cx, |leader, cx| {
6687 leader.buffer.update(cx, |multibuffer, cx| {
6688 let excerpt_ids = multibuffer.push_excerpts(
6689 buffer_1.clone(),
6690 [
6691 ExcerptRange {
6692 context: 1..6,
6693 primary: None,
6694 },
6695 ExcerptRange {
6696 context: 12..15,
6697 primary: None,
6698 },
6699 ExcerptRange {
6700 context: 0..3,
6701 primary: None,
6702 },
6703 ],
6704 cx,
6705 );
6706 multibuffer.insert_excerpts_after(
6707 excerpt_ids[0],
6708 buffer_2.clone(),
6709 [
6710 ExcerptRange {
6711 context: 8..12,
6712 primary: None,
6713 },
6714 ExcerptRange {
6715 context: 0..6,
6716 primary: None,
6717 },
6718 ],
6719 cx,
6720 );
6721 });
6722 });
6723
6724 // Apply the update of adding the excerpts.
6725 follower_1
6726 .update(cx, |follower, cx| {
6727 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
6728 })
6729 .await
6730 .unwrap();
6731 assert_eq!(
6732 follower_1.update(cx, |editor, cx| editor.text(cx)),
6733 leader.update(cx, |editor, cx| editor.text(cx))
6734 );
6735 update_message.borrow_mut().take();
6736
6737 // Start following separately after it already has excerpts.
6738 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
6739 let follower_2 = cx
6740 .update_window(*workspace.deref(), |_, cx| {
6741 Editor::from_state_proto(
6742 pane.clone(),
6743 workspace.root_view(cx).unwrap().clone(),
6744 ViewId {
6745 creator: Default::default(),
6746 id: 0,
6747 },
6748 &mut state_message,
6749 cx,
6750 )
6751 })
6752 .unwrap()
6753 .unwrap()
6754 .await
6755 .unwrap();
6756 assert_eq!(
6757 follower_2.update(cx, |editor, cx| editor.text(cx)),
6758 leader.update(cx, |editor, cx| editor.text(cx))
6759 );
6760
6761 // Remove some excerpts.
6762 _ = leader.update(cx, |leader, cx| {
6763 leader.buffer.update(cx, |multibuffer, cx| {
6764 let excerpt_ids = multibuffer.excerpt_ids();
6765 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
6766 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
6767 });
6768 });
6769
6770 // Apply the update of removing the excerpts.
6771 follower_1
6772 .update(cx, |follower, cx| {
6773 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
6774 })
6775 .await
6776 .unwrap();
6777 follower_2
6778 .update(cx, |follower, cx| {
6779 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
6780 })
6781 .await
6782 .unwrap();
6783 update_message.borrow_mut().take();
6784 assert_eq!(
6785 follower_1.update(cx, |editor, cx| editor.text(cx)),
6786 leader.update(cx, |editor, cx| editor.text(cx))
6787 );
6788}
6789
6790#[gpui::test]
6791async fn go_to_prev_overlapping_diagnostic(
6792 executor: BackgroundExecutor,
6793 cx: &mut gpui::TestAppContext,
6794) {
6795 init_test(cx, |_| {});
6796
6797 let mut cx = EditorTestContext::new(cx).await;
6798 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
6799
6800 cx.set_state(indoc! {"
6801 ˇfn func(abc def: i32) -> u32 {
6802 }
6803 "});
6804
6805 _ = cx.update(|cx| {
6806 _ = project.update(cx, |project, cx| {
6807 project
6808 .update_diagnostics(
6809 LanguageServerId(0),
6810 lsp::PublishDiagnosticsParams {
6811 uri: lsp::Url::from_file_path("/root/file").unwrap(),
6812 version: None,
6813 diagnostics: vec![
6814 lsp::Diagnostic {
6815 range: lsp::Range::new(
6816 lsp::Position::new(0, 11),
6817 lsp::Position::new(0, 12),
6818 ),
6819 severity: Some(lsp::DiagnosticSeverity::ERROR),
6820 ..Default::default()
6821 },
6822 lsp::Diagnostic {
6823 range: lsp::Range::new(
6824 lsp::Position::new(0, 12),
6825 lsp::Position::new(0, 15),
6826 ),
6827 severity: Some(lsp::DiagnosticSeverity::ERROR),
6828 ..Default::default()
6829 },
6830 lsp::Diagnostic {
6831 range: lsp::Range::new(
6832 lsp::Position::new(0, 25),
6833 lsp::Position::new(0, 28),
6834 ),
6835 severity: Some(lsp::DiagnosticSeverity::ERROR),
6836 ..Default::default()
6837 },
6838 ],
6839 },
6840 &[],
6841 cx,
6842 )
6843 .unwrap()
6844 });
6845 });
6846
6847 executor.run_until_parked();
6848
6849 cx.update_editor(|editor, cx| {
6850 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
6851 });
6852
6853 cx.assert_editor_state(indoc! {"
6854 fn func(abc def: i32) -> ˇu32 {
6855 }
6856 "});
6857
6858 cx.update_editor(|editor, cx| {
6859 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
6860 });
6861
6862 cx.assert_editor_state(indoc! {"
6863 fn func(abc ˇdef: i32) -> u32 {
6864 }
6865 "});
6866
6867 cx.update_editor(|editor, cx| {
6868 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
6869 });
6870
6871 cx.assert_editor_state(indoc! {"
6872 fn func(abcˇ def: i32) -> u32 {
6873 }
6874 "});
6875
6876 cx.update_editor(|editor, cx| {
6877 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
6878 });
6879
6880 cx.assert_editor_state(indoc! {"
6881 fn func(abc def: i32) -> ˇu32 {
6882 }
6883 "});
6884}
6885
6886#[gpui::test]
6887async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
6888 init_test(cx, |_| {});
6889
6890 let mut cx = EditorTestContext::new(cx).await;
6891
6892 let diff_base = r#"
6893 use some::mod;
6894
6895 const A: u32 = 42;
6896
6897 fn main() {
6898 println!("hello");
6899
6900 println!("world");
6901 }
6902 "#
6903 .unindent();
6904
6905 // Edits are modified, removed, modified, added
6906 cx.set_state(
6907 &r#"
6908 use some::modified;
6909
6910 ˇ
6911 fn main() {
6912 println!("hello there");
6913
6914 println!("around the");
6915 println!("world");
6916 }
6917 "#
6918 .unindent(),
6919 );
6920
6921 cx.set_diff_base(Some(&diff_base));
6922 executor.run_until_parked();
6923
6924 cx.update_editor(|editor, cx| {
6925 //Wrap around the bottom of the buffer
6926 for _ in 0..3 {
6927 editor.go_to_hunk(&GoToHunk, cx);
6928 }
6929 });
6930
6931 cx.assert_editor_state(
6932 &r#"
6933 ˇuse some::modified;
6934
6935
6936 fn main() {
6937 println!("hello there");
6938
6939 println!("around the");
6940 println!("world");
6941 }
6942 "#
6943 .unindent(),
6944 );
6945
6946 cx.update_editor(|editor, cx| {
6947 //Wrap around the top of the buffer
6948 for _ in 0..2 {
6949 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
6950 }
6951 });
6952
6953 cx.assert_editor_state(
6954 &r#"
6955 use some::modified;
6956
6957
6958 fn main() {
6959 ˇ println!("hello there");
6960
6961 println!("around the");
6962 println!("world");
6963 }
6964 "#
6965 .unindent(),
6966 );
6967
6968 cx.update_editor(|editor, cx| {
6969 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
6970 });
6971
6972 cx.assert_editor_state(
6973 &r#"
6974 use some::modified;
6975
6976 ˇ
6977 fn main() {
6978 println!("hello there");
6979
6980 println!("around the");
6981 println!("world");
6982 }
6983 "#
6984 .unindent(),
6985 );
6986
6987 cx.update_editor(|editor, cx| {
6988 for _ in 0..3 {
6989 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
6990 }
6991 });
6992
6993 cx.assert_editor_state(
6994 &r#"
6995 use some::modified;
6996
6997
6998 fn main() {
6999 ˇ println!("hello there");
7000
7001 println!("around the");
7002 println!("world");
7003 }
7004 "#
7005 .unindent(),
7006 );
7007
7008 cx.update_editor(|editor, cx| {
7009 editor.fold(&Fold, cx);
7010
7011 //Make sure that the fold only gets one hunk
7012 for _ in 0..4 {
7013 editor.go_to_hunk(&GoToHunk, cx);
7014 }
7015 });
7016
7017 cx.assert_editor_state(
7018 &r#"
7019 ˇuse some::modified;
7020
7021
7022 fn main() {
7023 println!("hello there");
7024
7025 println!("around the");
7026 println!("world");
7027 }
7028 "#
7029 .unindent(),
7030 );
7031}
7032
7033#[test]
7034fn test_split_words() {
7035 fn split<'a>(text: &'a str) -> Vec<&'a str> {
7036 split_words(text).collect()
7037 }
7038
7039 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
7040 assert_eq!(split("hello_world"), &["hello_", "world"]);
7041 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
7042 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
7043 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
7044 assert_eq!(split("helloworld"), &["helloworld"]);
7045}
7046
7047#[gpui::test]
7048async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
7049 init_test(cx, |_| {});
7050
7051 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
7052 let mut assert = |before, after| {
7053 let _state_context = cx.set_state(before);
7054 cx.update_editor(|editor, cx| {
7055 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
7056 });
7057 cx.assert_editor_state(after);
7058 };
7059
7060 // Outside bracket jumps to outside of matching bracket
7061 assert("console.logˇ(var);", "console.log(var)ˇ;");
7062 assert("console.log(var)ˇ;", "console.logˇ(var);");
7063
7064 // Inside bracket jumps to inside of matching bracket
7065 assert("console.log(ˇvar);", "console.log(varˇ);");
7066 assert("console.log(varˇ);", "console.log(ˇvar);");
7067
7068 // When outside a bracket and inside, favor jumping to the inside bracket
7069 assert(
7070 "console.log('foo', [1, 2, 3]ˇ);",
7071 "console.log(ˇ'foo', [1, 2, 3]);",
7072 );
7073 assert(
7074 "console.log(ˇ'foo', [1, 2, 3]);",
7075 "console.log('foo', [1, 2, 3]ˇ);",
7076 );
7077
7078 // Bias forward if two options are equally likely
7079 assert(
7080 "let result = curried_fun()ˇ();",
7081 "let result = curried_fun()()ˇ;",
7082 );
7083
7084 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
7085 assert(
7086 indoc! {"
7087 function test() {
7088 console.log('test')ˇ
7089 }"},
7090 indoc! {"
7091 function test() {
7092 console.logˇ('test')
7093 }"},
7094 );
7095}
7096
7097// todo!(completions)
7098#[gpui::test(iterations = 10)]
7099async fn test_copilot(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
7100 // flaky
7101 init_test(cx, |_| {});
7102
7103 let (copilot, copilot_lsp) = Copilot::fake(cx);
7104 _ = cx.update(|cx| cx.set_global(copilot));
7105 let mut cx = EditorLspTestContext::new_rust(
7106 lsp::ServerCapabilities {
7107 completion_provider: Some(lsp::CompletionOptions {
7108 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
7109 ..Default::default()
7110 }),
7111 ..Default::default()
7112 },
7113 cx,
7114 )
7115 .await;
7116
7117 // When inserting, ensure autocompletion is favored over Copilot suggestions.
7118 cx.set_state(indoc! {"
7119 oneˇ
7120 two
7121 three
7122 "});
7123 cx.simulate_keystroke(".");
7124 let _ = handle_completion_request(
7125 &mut cx,
7126 indoc! {"
7127 one.|<>
7128 two
7129 three
7130 "},
7131 vec!["completion_a", "completion_b"],
7132 );
7133 handle_copilot_completion_request(
7134 &copilot_lsp,
7135 vec![copilot::request::Completion {
7136 text: "one.copilot1".into(),
7137 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
7138 ..Default::default()
7139 }],
7140 vec![],
7141 );
7142 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7143 cx.update_editor(|editor, cx| {
7144 assert!(editor.context_menu_visible());
7145 assert!(!editor.has_active_copilot_suggestion(cx));
7146
7147 // Confirming a completion inserts it and hides the context menu, without showing
7148 // the copilot suggestion afterwards.
7149 editor
7150 .confirm_completion(&Default::default(), cx)
7151 .unwrap()
7152 .detach();
7153 assert!(!editor.context_menu_visible());
7154 assert!(!editor.has_active_copilot_suggestion(cx));
7155 assert_eq!(editor.text(cx), "one.completion_a\ntwo\nthree\n");
7156 assert_eq!(editor.display_text(cx), "one.completion_a\ntwo\nthree\n");
7157 });
7158
7159 // Ensure Copilot suggestions are shown right away if no autocompletion is available.
7160 cx.set_state(indoc! {"
7161 oneˇ
7162 two
7163 three
7164 "});
7165 cx.simulate_keystroke(".");
7166 let _ = handle_completion_request(
7167 &mut cx,
7168 indoc! {"
7169 one.|<>
7170 two
7171 three
7172 "},
7173 vec![],
7174 );
7175 handle_copilot_completion_request(
7176 &copilot_lsp,
7177 vec![copilot::request::Completion {
7178 text: "one.copilot1".into(),
7179 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
7180 ..Default::default()
7181 }],
7182 vec![],
7183 );
7184 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7185 cx.update_editor(|editor, cx| {
7186 assert!(!editor.context_menu_visible());
7187 assert!(editor.has_active_copilot_suggestion(cx));
7188 assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
7189 assert_eq!(editor.text(cx), "one.\ntwo\nthree\n");
7190 });
7191
7192 // Reset editor, and ensure autocompletion is still favored over Copilot suggestions.
7193 cx.set_state(indoc! {"
7194 oneˇ
7195 two
7196 three
7197 "});
7198 cx.simulate_keystroke(".");
7199 let _ = handle_completion_request(
7200 &mut cx,
7201 indoc! {"
7202 one.|<>
7203 two
7204 three
7205 "},
7206 vec!["completion_a", "completion_b"],
7207 );
7208 handle_copilot_completion_request(
7209 &copilot_lsp,
7210 vec![copilot::request::Completion {
7211 text: "one.copilot1".into(),
7212 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
7213 ..Default::default()
7214 }],
7215 vec![],
7216 );
7217 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7218 cx.update_editor(|editor, cx| {
7219 assert!(editor.context_menu_visible());
7220 assert!(!editor.has_active_copilot_suggestion(cx));
7221
7222 // When hiding the context menu, the Copilot suggestion becomes visible.
7223 editor.hide_context_menu(cx);
7224 assert!(!editor.context_menu_visible());
7225 assert!(editor.has_active_copilot_suggestion(cx));
7226 assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
7227 assert_eq!(editor.text(cx), "one.\ntwo\nthree\n");
7228 });
7229
7230 // Ensure existing completion is interpolated when inserting again.
7231 cx.simulate_keystroke("c");
7232 executor.run_until_parked();
7233 cx.update_editor(|editor, cx| {
7234 assert!(!editor.context_menu_visible());
7235 assert!(editor.has_active_copilot_suggestion(cx));
7236 assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
7237 assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
7238 });
7239
7240 // After debouncing, new Copilot completions should be requested.
7241 handle_copilot_completion_request(
7242 &copilot_lsp,
7243 vec![copilot::request::Completion {
7244 text: "one.copilot2".into(),
7245 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 5)),
7246 ..Default::default()
7247 }],
7248 vec![],
7249 );
7250 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7251 cx.update_editor(|editor, cx| {
7252 assert!(!editor.context_menu_visible());
7253 assert!(editor.has_active_copilot_suggestion(cx));
7254 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
7255 assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
7256
7257 // Canceling should remove the active Copilot suggestion.
7258 editor.cancel(&Default::default(), cx);
7259 assert!(!editor.has_active_copilot_suggestion(cx));
7260 assert_eq!(editor.display_text(cx), "one.c\ntwo\nthree\n");
7261 assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
7262
7263 // After canceling, tabbing shouldn't insert the previously shown suggestion.
7264 editor.tab(&Default::default(), cx);
7265 assert!(!editor.has_active_copilot_suggestion(cx));
7266 assert_eq!(editor.display_text(cx), "one.c \ntwo\nthree\n");
7267 assert_eq!(editor.text(cx), "one.c \ntwo\nthree\n");
7268
7269 // When undoing the previously active suggestion is shown again.
7270 editor.undo(&Default::default(), cx);
7271 assert!(editor.has_active_copilot_suggestion(cx));
7272 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
7273 assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
7274 });
7275
7276 // If an edit occurs outside of this editor, the suggestion is still correctly interpolated.
7277 cx.update_buffer(|buffer, cx| buffer.edit([(5..5, "o")], None, cx));
7278 cx.update_editor(|editor, cx| {
7279 assert!(editor.has_active_copilot_suggestion(cx));
7280 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
7281 assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
7282
7283 // Tabbing when there is an active suggestion inserts it.
7284 editor.tab(&Default::default(), cx);
7285 assert!(!editor.has_active_copilot_suggestion(cx));
7286 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
7287 assert_eq!(editor.text(cx), "one.copilot2\ntwo\nthree\n");
7288
7289 // When undoing the previously active suggestion is shown again.
7290 editor.undo(&Default::default(), cx);
7291 assert!(editor.has_active_copilot_suggestion(cx));
7292 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
7293 assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
7294
7295 // Hide suggestion.
7296 editor.cancel(&Default::default(), cx);
7297 assert!(!editor.has_active_copilot_suggestion(cx));
7298 assert_eq!(editor.display_text(cx), "one.co\ntwo\nthree\n");
7299 assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
7300 });
7301
7302 // If an edit occurs outside of this editor but no suggestion is being shown,
7303 // we won't make it visible.
7304 cx.update_buffer(|buffer, cx| buffer.edit([(6..6, "p")], None, cx));
7305 cx.update_editor(|editor, cx| {
7306 assert!(!editor.has_active_copilot_suggestion(cx));
7307 assert_eq!(editor.display_text(cx), "one.cop\ntwo\nthree\n");
7308 assert_eq!(editor.text(cx), "one.cop\ntwo\nthree\n");
7309 });
7310
7311 // Reset the editor to verify how suggestions behave when tabbing on leading indentation.
7312 cx.update_editor(|editor, cx| {
7313 editor.set_text("fn foo() {\n \n}", cx);
7314 editor.change_selections(None, cx, |s| {
7315 s.select_ranges([Point::new(1, 2)..Point::new(1, 2)])
7316 });
7317 });
7318 handle_copilot_completion_request(
7319 &copilot_lsp,
7320 vec![copilot::request::Completion {
7321 text: " let x = 4;".into(),
7322 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 2)),
7323 ..Default::default()
7324 }],
7325 vec![],
7326 );
7327
7328 cx.update_editor(|editor, cx| editor.next_copilot_suggestion(&Default::default(), cx));
7329 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7330 cx.update_editor(|editor, cx| {
7331 assert!(editor.has_active_copilot_suggestion(cx));
7332 assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
7333 assert_eq!(editor.text(cx), "fn foo() {\n \n}");
7334
7335 // Tabbing inside of leading whitespace inserts indentation without accepting the suggestion.
7336 editor.tab(&Default::default(), cx);
7337 assert!(editor.has_active_copilot_suggestion(cx));
7338 assert_eq!(editor.text(cx), "fn foo() {\n \n}");
7339 assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
7340
7341 // Tabbing again accepts the suggestion.
7342 editor.tab(&Default::default(), cx);
7343 assert!(!editor.has_active_copilot_suggestion(cx));
7344 assert_eq!(editor.text(cx), "fn foo() {\n let x = 4;\n}");
7345 assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
7346 });
7347}
7348
7349#[gpui::test]
7350async fn test_copilot_completion_invalidation(
7351 executor: BackgroundExecutor,
7352 cx: &mut gpui::TestAppContext,
7353) {
7354 init_test(cx, |_| {});
7355
7356 let (copilot, copilot_lsp) = Copilot::fake(cx);
7357 _ = cx.update(|cx| cx.set_global(copilot));
7358 let mut cx = EditorLspTestContext::new_rust(
7359 lsp::ServerCapabilities {
7360 completion_provider: Some(lsp::CompletionOptions {
7361 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
7362 ..Default::default()
7363 }),
7364 ..Default::default()
7365 },
7366 cx,
7367 )
7368 .await;
7369
7370 cx.set_state(indoc! {"
7371 one
7372 twˇ
7373 three
7374 "});
7375
7376 handle_copilot_completion_request(
7377 &copilot_lsp,
7378 vec![copilot::request::Completion {
7379 text: "two.foo()".into(),
7380 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 2)),
7381 ..Default::default()
7382 }],
7383 vec![],
7384 );
7385 cx.update_editor(|editor, cx| editor.next_copilot_suggestion(&Default::default(), cx));
7386 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7387 cx.update_editor(|editor, cx| {
7388 assert!(editor.has_active_copilot_suggestion(cx));
7389 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
7390 assert_eq!(editor.text(cx), "one\ntw\nthree\n");
7391
7392 editor.backspace(&Default::default(), cx);
7393 assert!(editor.has_active_copilot_suggestion(cx));
7394 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
7395 assert_eq!(editor.text(cx), "one\nt\nthree\n");
7396
7397 editor.backspace(&Default::default(), cx);
7398 assert!(editor.has_active_copilot_suggestion(cx));
7399 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
7400 assert_eq!(editor.text(cx), "one\n\nthree\n");
7401
7402 // Deleting across the original suggestion range invalidates it.
7403 editor.backspace(&Default::default(), cx);
7404 assert!(!editor.has_active_copilot_suggestion(cx));
7405 assert_eq!(editor.display_text(cx), "one\nthree\n");
7406 assert_eq!(editor.text(cx), "one\nthree\n");
7407
7408 // Undoing the deletion restores the suggestion.
7409 editor.undo(&Default::default(), cx);
7410 assert!(editor.has_active_copilot_suggestion(cx));
7411 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
7412 assert_eq!(editor.text(cx), "one\n\nthree\n");
7413 });
7414}
7415
7416#[gpui::test]
7417async fn test_copilot_multibuffer(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
7418 init_test(cx, |_| {});
7419
7420 let (copilot, copilot_lsp) = Copilot::fake(cx);
7421 _ = cx.update(|cx| cx.set_global(copilot));
7422
7423 let buffer_1 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "a = 1\nb = 2\n"));
7424 let buffer_2 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "c = 3\nd = 4\n"));
7425 let multibuffer = cx.new_model(|cx| {
7426 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
7427 multibuffer.push_excerpts(
7428 buffer_1.clone(),
7429 [ExcerptRange {
7430 context: Point::new(0, 0)..Point::new(2, 0),
7431 primary: None,
7432 }],
7433 cx,
7434 );
7435 multibuffer.push_excerpts(
7436 buffer_2.clone(),
7437 [ExcerptRange {
7438 context: Point::new(0, 0)..Point::new(2, 0),
7439 primary: None,
7440 }],
7441 cx,
7442 );
7443 multibuffer
7444 });
7445 let editor = cx.add_window(|cx| build_editor(multibuffer, cx));
7446
7447 handle_copilot_completion_request(
7448 &copilot_lsp,
7449 vec![copilot::request::Completion {
7450 text: "b = 2 + a".into(),
7451 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 5)),
7452 ..Default::default()
7453 }],
7454 vec![],
7455 );
7456 _ = editor.update(cx, |editor, cx| {
7457 // Ensure copilot suggestions are shown for the first excerpt.
7458 editor.change_selections(None, cx, |s| {
7459 s.select_ranges([Point::new(1, 5)..Point::new(1, 5)])
7460 });
7461 editor.next_copilot_suggestion(&Default::default(), cx);
7462 });
7463 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7464 _ = editor.update(cx, |editor, cx| {
7465 assert!(editor.has_active_copilot_suggestion(cx));
7466 assert_eq!(
7467 editor.display_text(cx),
7468 "\n\na = 1\nb = 2 + a\n\n\n\nc = 3\nd = 4\n"
7469 );
7470 assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4\n");
7471 });
7472
7473 handle_copilot_completion_request(
7474 &copilot_lsp,
7475 vec![copilot::request::Completion {
7476 text: "d = 4 + c".into(),
7477 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 6)),
7478 ..Default::default()
7479 }],
7480 vec![],
7481 );
7482 _ = editor.update(cx, |editor, cx| {
7483 // Move to another excerpt, ensuring the suggestion gets cleared.
7484 editor.change_selections(None, cx, |s| {
7485 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
7486 });
7487 assert!(!editor.has_active_copilot_suggestion(cx));
7488 assert_eq!(
7489 editor.display_text(cx),
7490 "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4\n"
7491 );
7492 assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4\n");
7493
7494 // Type a character, ensuring we don't even try to interpolate the previous suggestion.
7495 editor.handle_input(" ", cx);
7496 assert!(!editor.has_active_copilot_suggestion(cx));
7497 assert_eq!(
7498 editor.display_text(cx),
7499 "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4 \n"
7500 );
7501 assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4 \n");
7502 });
7503
7504 // Ensure the new suggestion is displayed when the debounce timeout expires.
7505 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7506 _ = editor.update(cx, |editor, cx| {
7507 assert!(editor.has_active_copilot_suggestion(cx));
7508 assert_eq!(
7509 editor.display_text(cx),
7510 "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4 + c\n"
7511 );
7512 assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4 \n");
7513 });
7514}
7515
7516#[gpui::test]
7517async fn test_copilot_disabled_globs(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
7518 init_test(cx, |settings| {
7519 settings
7520 .copilot
7521 .get_or_insert(Default::default())
7522 .disabled_globs = Some(vec![".env*".to_string()]);
7523 });
7524
7525 let (copilot, copilot_lsp) = Copilot::fake(cx);
7526 _ = cx.update(|cx| cx.set_global(copilot));
7527
7528 let fs = FakeFs::new(cx.executor());
7529 fs.insert_tree(
7530 "/test",
7531 json!({
7532 ".env": "SECRET=something\n",
7533 "README.md": "hello\n"
7534 }),
7535 )
7536 .await;
7537 let project = Project::test(fs, ["/test".as_ref()], cx).await;
7538
7539 let private_buffer = project
7540 .update(cx, |project, cx| {
7541 project.open_local_buffer("/test/.env", cx)
7542 })
7543 .await
7544 .unwrap();
7545 let public_buffer = project
7546 .update(cx, |project, cx| {
7547 project.open_local_buffer("/test/README.md", cx)
7548 })
7549 .await
7550 .unwrap();
7551
7552 let multibuffer = cx.new_model(|cx| {
7553 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
7554 multibuffer.push_excerpts(
7555 private_buffer.clone(),
7556 [ExcerptRange {
7557 context: Point::new(0, 0)..Point::new(1, 0),
7558 primary: None,
7559 }],
7560 cx,
7561 );
7562 multibuffer.push_excerpts(
7563 public_buffer.clone(),
7564 [ExcerptRange {
7565 context: Point::new(0, 0)..Point::new(1, 0),
7566 primary: None,
7567 }],
7568 cx,
7569 );
7570 multibuffer
7571 });
7572 let editor = cx.add_window(|cx| build_editor(multibuffer, cx));
7573
7574 let mut copilot_requests = copilot_lsp
7575 .handle_request::<copilot::request::GetCompletions, _, _>(move |_params, _cx| async move {
7576 Ok(copilot::request::GetCompletionsResult {
7577 completions: vec![copilot::request::Completion {
7578 text: "next line".into(),
7579 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
7580 ..Default::default()
7581 }],
7582 })
7583 });
7584
7585 _ = editor.update(cx, |editor, cx| {
7586 editor.change_selections(None, cx, |selections| {
7587 selections.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
7588 });
7589 editor.next_copilot_suggestion(&Default::default(), cx);
7590 });
7591
7592 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7593 assert!(copilot_requests.try_next().is_err());
7594
7595 _ = editor.update(cx, |editor, cx| {
7596 editor.change_selections(None, cx, |s| {
7597 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
7598 });
7599 editor.next_copilot_suggestion(&Default::default(), cx);
7600 });
7601
7602 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7603 assert!(copilot_requests.try_next().is_ok());
7604}
7605
7606#[gpui::test]
7607async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
7608 init_test(cx, |_| {});
7609
7610 let mut language = Language::new(
7611 LanguageConfig {
7612 name: "Rust".into(),
7613 path_suffixes: vec!["rs".to_string()],
7614 brackets: BracketPairConfig {
7615 pairs: vec![BracketPair {
7616 start: "{".to_string(),
7617 end: "}".to_string(),
7618 close: true,
7619 newline: true,
7620 }],
7621 disabled_scopes_by_bracket_ix: Vec::new(),
7622 },
7623 ..Default::default()
7624 },
7625 Some(tree_sitter_rust::language()),
7626 );
7627 let mut fake_servers = language
7628 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
7629 capabilities: lsp::ServerCapabilities {
7630 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
7631 first_trigger_character: "{".to_string(),
7632 more_trigger_character: None,
7633 }),
7634 ..Default::default()
7635 },
7636 ..Default::default()
7637 }))
7638 .await;
7639
7640 let fs = FakeFs::new(cx.executor());
7641 fs.insert_tree(
7642 "/a",
7643 json!({
7644 "main.rs": "fn main() { let a = 5; }",
7645 "other.rs": "// Test file",
7646 }),
7647 )
7648 .await;
7649 let project = Project::test(fs, ["/a".as_ref()], cx).await;
7650 _ = project.update(cx, |project, _| project.languages().add(Arc::new(language)));
7651 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
7652
7653 let cx = &mut VisualTestContext::from_window(*workspace, cx);
7654
7655 let worktree_id = workspace
7656 .update(cx, |workspace, cx| {
7657 workspace.project().update(cx, |project, cx| {
7658 project.worktrees().next().unwrap().read(cx).id()
7659 })
7660 })
7661 .unwrap();
7662
7663 let buffer = project
7664 .update(cx, |project, cx| {
7665 project.open_local_buffer("/a/main.rs", cx)
7666 })
7667 .await
7668 .unwrap();
7669 cx.executor().run_until_parked();
7670 cx.executor().start_waiting();
7671 let fake_server = fake_servers.next().await.unwrap();
7672 let editor_handle = workspace
7673 .update(cx, |workspace, cx| {
7674 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
7675 })
7676 .unwrap()
7677 .await
7678 .unwrap()
7679 .downcast::<Editor>()
7680 .unwrap();
7681
7682 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
7683 assert_eq!(
7684 params.text_document_position.text_document.uri,
7685 lsp::Url::from_file_path("/a/main.rs").unwrap(),
7686 );
7687 assert_eq!(
7688 params.text_document_position.position,
7689 lsp::Position::new(0, 21),
7690 );
7691
7692 Ok(Some(vec![lsp::TextEdit {
7693 new_text: "]".to_string(),
7694 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
7695 }]))
7696 });
7697
7698 editor_handle.update(cx, |editor, cx| {
7699 editor.focus(cx);
7700 editor.change_selections(None, cx, |s| {
7701 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
7702 });
7703 editor.handle_input("{", cx);
7704 });
7705
7706 cx.executor().run_until_parked();
7707
7708 _ = buffer.update(cx, |buffer, _| {
7709 assert_eq!(
7710 buffer.text(),
7711 "fn main() { let a = {5}; }",
7712 "No extra braces from on type formatting should appear in the buffer"
7713 )
7714 });
7715}
7716
7717#[gpui::test]
7718async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
7719 init_test(cx, |_| {});
7720
7721 let language_name: Arc<str> = "Rust".into();
7722 let mut language = Language::new(
7723 LanguageConfig {
7724 name: Arc::clone(&language_name),
7725 path_suffixes: vec!["rs".to_string()],
7726 ..Default::default()
7727 },
7728 Some(tree_sitter_rust::language()),
7729 );
7730
7731 let server_restarts = Arc::new(AtomicUsize::new(0));
7732 let closure_restarts = Arc::clone(&server_restarts);
7733 let language_server_name = "test language server";
7734 let mut fake_servers = language
7735 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
7736 name: language_server_name,
7737 initialization_options: Some(json!({
7738 "testOptionValue": true
7739 })),
7740 initializer: Some(Box::new(move |fake_server| {
7741 let task_restarts = Arc::clone(&closure_restarts);
7742 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
7743 task_restarts.fetch_add(1, atomic::Ordering::Release);
7744 futures::future::ready(Ok(()))
7745 });
7746 })),
7747 ..Default::default()
7748 }))
7749 .await;
7750
7751 let fs = FakeFs::new(cx.executor());
7752 fs.insert_tree(
7753 "/a",
7754 json!({
7755 "main.rs": "fn main() { let a = 5; }",
7756 "other.rs": "// Test file",
7757 }),
7758 )
7759 .await;
7760 let project = Project::test(fs, ["/a".as_ref()], cx).await;
7761 _ = project.update(cx, |project, _| project.languages().add(Arc::new(language)));
7762 let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
7763 let _buffer = project
7764 .update(cx, |project, cx| {
7765 project.open_local_buffer("/a/main.rs", cx)
7766 })
7767 .await
7768 .unwrap();
7769 let _fake_server = fake_servers.next().await.unwrap();
7770 update_test_language_settings(cx, |language_settings| {
7771 language_settings.languages.insert(
7772 Arc::clone(&language_name),
7773 LanguageSettingsContent {
7774 tab_size: NonZeroU32::new(8),
7775 ..Default::default()
7776 },
7777 );
7778 });
7779 cx.executor().run_until_parked();
7780 assert_eq!(
7781 server_restarts.load(atomic::Ordering::Acquire),
7782 0,
7783 "Should not restart LSP server on an unrelated change"
7784 );
7785
7786 update_test_project_settings(cx, |project_settings| {
7787 project_settings.lsp.insert(
7788 "Some other server name".into(),
7789 LspSettings {
7790 initialization_options: Some(json!({
7791 "some other init value": false
7792 })),
7793 },
7794 );
7795 });
7796 cx.executor().run_until_parked();
7797 assert_eq!(
7798 server_restarts.load(atomic::Ordering::Acquire),
7799 0,
7800 "Should not restart LSP server on an unrelated LSP settings change"
7801 );
7802
7803 update_test_project_settings(cx, |project_settings| {
7804 project_settings.lsp.insert(
7805 language_server_name.into(),
7806 LspSettings {
7807 initialization_options: Some(json!({
7808 "anotherInitValue": false
7809 })),
7810 },
7811 );
7812 });
7813 cx.executor().run_until_parked();
7814 assert_eq!(
7815 server_restarts.load(atomic::Ordering::Acquire),
7816 1,
7817 "Should restart LSP server on a related LSP settings change"
7818 );
7819
7820 update_test_project_settings(cx, |project_settings| {
7821 project_settings.lsp.insert(
7822 language_server_name.into(),
7823 LspSettings {
7824 initialization_options: Some(json!({
7825 "anotherInitValue": false
7826 })),
7827 },
7828 );
7829 });
7830 cx.executor().run_until_parked();
7831 assert_eq!(
7832 server_restarts.load(atomic::Ordering::Acquire),
7833 1,
7834 "Should not restart LSP server on a related LSP settings change that is the same"
7835 );
7836
7837 update_test_project_settings(cx, |project_settings| {
7838 project_settings.lsp.insert(
7839 language_server_name.into(),
7840 LspSettings {
7841 initialization_options: None,
7842 },
7843 );
7844 });
7845 cx.executor().run_until_parked();
7846 assert_eq!(
7847 server_restarts.load(atomic::Ordering::Acquire),
7848 2,
7849 "Should restart LSP server on another related LSP settings change"
7850 );
7851}
7852
7853#[gpui::test]
7854async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
7855 init_test(cx, |_| {});
7856
7857 let mut cx = EditorLspTestContext::new_rust(
7858 lsp::ServerCapabilities {
7859 completion_provider: Some(lsp::CompletionOptions {
7860 trigger_characters: Some(vec![".".to_string()]),
7861 resolve_provider: Some(true),
7862 ..Default::default()
7863 }),
7864 ..Default::default()
7865 },
7866 cx,
7867 )
7868 .await;
7869
7870 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
7871 cx.simulate_keystroke(".");
7872 let completion_item = lsp::CompletionItem {
7873 label: "some".into(),
7874 kind: Some(lsp::CompletionItemKind::SNIPPET),
7875 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
7876 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
7877 kind: lsp::MarkupKind::Markdown,
7878 value: "```rust\nSome(2)\n```".to_string(),
7879 })),
7880 deprecated: Some(false),
7881 sort_text: Some("fffffff2".to_string()),
7882 filter_text: Some("some".to_string()),
7883 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
7884 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
7885 range: lsp::Range {
7886 start: lsp::Position {
7887 line: 0,
7888 character: 22,
7889 },
7890 end: lsp::Position {
7891 line: 0,
7892 character: 22,
7893 },
7894 },
7895 new_text: "Some(2)".to_string(),
7896 })),
7897 additional_text_edits: Some(vec![lsp::TextEdit {
7898 range: lsp::Range {
7899 start: lsp::Position {
7900 line: 0,
7901 character: 20,
7902 },
7903 end: lsp::Position {
7904 line: 0,
7905 character: 22,
7906 },
7907 },
7908 new_text: "".to_string(),
7909 }]),
7910 ..Default::default()
7911 };
7912
7913 let closure_completion_item = completion_item.clone();
7914 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
7915 let task_completion_item = closure_completion_item.clone();
7916 async move {
7917 Ok(Some(lsp::CompletionResponse::Array(vec![
7918 task_completion_item,
7919 ])))
7920 }
7921 });
7922
7923 request.next().await;
7924
7925 cx.condition(|editor, _| editor.context_menu_visible())
7926 .await;
7927 let apply_additional_edits = cx.update_editor(|editor, cx| {
7928 editor
7929 .confirm_completion(&ConfirmCompletion::default(), cx)
7930 .unwrap()
7931 });
7932 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
7933
7934 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
7935 let task_completion_item = completion_item.clone();
7936 async move { Ok(task_completion_item) }
7937 })
7938 .next()
7939 .await
7940 .unwrap();
7941 apply_additional_edits.await.unwrap();
7942 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
7943}
7944
7945#[gpui::test]
7946async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
7947 init_test(cx, |_| {});
7948
7949 let mut cx = EditorLspTestContext::new(
7950 Language::new(
7951 LanguageConfig {
7952 path_suffixes: vec!["jsx".into()],
7953 overrides: [(
7954 "element".into(),
7955 LanguageConfigOverride {
7956 word_characters: Override::Set(['-'].into_iter().collect()),
7957 ..Default::default()
7958 },
7959 )]
7960 .into_iter()
7961 .collect(),
7962 ..Default::default()
7963 },
7964 Some(tree_sitter_typescript::language_tsx()),
7965 )
7966 .with_override_query("(jsx_self_closing_element) @element")
7967 .unwrap(),
7968 lsp::ServerCapabilities {
7969 completion_provider: Some(lsp::CompletionOptions {
7970 trigger_characters: Some(vec![":".to_string()]),
7971 ..Default::default()
7972 }),
7973 ..Default::default()
7974 },
7975 cx,
7976 )
7977 .await;
7978
7979 cx.lsp
7980 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
7981 Ok(Some(lsp::CompletionResponse::Array(vec![
7982 lsp::CompletionItem {
7983 label: "bg-blue".into(),
7984 ..Default::default()
7985 },
7986 lsp::CompletionItem {
7987 label: "bg-red".into(),
7988 ..Default::default()
7989 },
7990 lsp::CompletionItem {
7991 label: "bg-yellow".into(),
7992 ..Default::default()
7993 },
7994 ])))
7995 });
7996
7997 cx.set_state(r#"<p class="bgˇ" />"#);
7998
7999 // Trigger completion when typing a dash, because the dash is an extra
8000 // word character in the 'element' scope, which contains the cursor.
8001 cx.simulate_keystroke("-");
8002 cx.executor().run_until_parked();
8003 cx.update_editor(|editor, _| {
8004 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8005 assert_eq!(
8006 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8007 &["bg-red", "bg-blue", "bg-yellow"]
8008 );
8009 } else {
8010 panic!("expected completion menu to be open");
8011 }
8012 });
8013
8014 cx.simulate_keystroke("l");
8015 cx.executor().run_until_parked();
8016 cx.update_editor(|editor, _| {
8017 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8018 assert_eq!(
8019 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8020 &["bg-blue", "bg-yellow"]
8021 );
8022 } else {
8023 panic!("expected completion menu to be open");
8024 }
8025 });
8026
8027 // When filtering completions, consider the character after the '-' to
8028 // be the start of a subword.
8029 cx.set_state(r#"<p class="yelˇ" />"#);
8030 cx.simulate_keystroke("l");
8031 cx.executor().run_until_parked();
8032 cx.update_editor(|editor, _| {
8033 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8034 assert_eq!(
8035 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8036 &["bg-yellow"]
8037 );
8038 } else {
8039 panic!("expected completion menu to be open");
8040 }
8041 });
8042}
8043
8044#[gpui::test]
8045async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
8046 init_test(cx, |settings| {
8047 settings.defaults.formatter = Some(language_settings::Formatter::Prettier)
8048 });
8049
8050 let mut language = Language::new(
8051 LanguageConfig {
8052 name: "Rust".into(),
8053 path_suffixes: vec!["rs".to_string()],
8054 prettier_parser_name: Some("test_parser".to_string()),
8055 ..Default::default()
8056 },
8057 Some(tree_sitter_rust::language()),
8058 );
8059
8060 let test_plugin = "test_plugin";
8061 let _ = language
8062 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
8063 prettier_plugins: vec![test_plugin],
8064 ..Default::default()
8065 }))
8066 .await;
8067
8068 let fs = FakeFs::new(cx.executor());
8069 fs.insert_file("/file.rs", Default::default()).await;
8070
8071 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
8072 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
8073 _ = project.update(cx, |project, _| {
8074 project.languages().add(Arc::new(language));
8075 });
8076 let buffer = project
8077 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
8078 .await
8079 .unwrap();
8080
8081 let buffer_text = "one\ntwo\nthree\n";
8082 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
8083 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
8084 _ = editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
8085
8086 editor
8087 .update(cx, |editor, cx| {
8088 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
8089 })
8090 .unwrap()
8091 .await;
8092 assert_eq!(
8093 editor.update(cx, |editor, cx| editor.text(cx)),
8094 buffer_text.to_string() + prettier_format_suffix,
8095 "Test prettier formatting was not applied to the original buffer text",
8096 );
8097
8098 update_test_language_settings(cx, |settings| {
8099 settings.defaults.formatter = Some(language_settings::Formatter::Auto)
8100 });
8101 let format = editor.update(cx, |editor, cx| {
8102 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
8103 });
8104 format.await.unwrap();
8105 assert_eq!(
8106 editor.update(cx, |editor, cx| editor.text(cx)),
8107 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
8108 "Autoformatting (via test prettier) was not applied to the original buffer text",
8109 );
8110}
8111
8112fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
8113 let point = DisplayPoint::new(row as u32, column as u32);
8114 point..point
8115}
8116
8117fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
8118 let (text, ranges) = marked_text_ranges(marked_text, true);
8119 assert_eq!(view.text(cx), text);
8120 assert_eq!(
8121 view.selections.ranges(cx),
8122 ranges,
8123 "Assert selections are {}",
8124 marked_text
8125 );
8126}
8127
8128/// Handle completion request passing a marked string specifying where the completion
8129/// should be triggered from using '|' character, what range should be replaced, and what completions
8130/// should be returned using '<' and '>' to delimit the range
8131pub fn handle_completion_request(
8132 cx: &mut EditorLspTestContext,
8133 marked_string: &str,
8134 completions: Vec<&'static str>,
8135) -> impl Future<Output = ()> {
8136 let complete_from_marker: TextRangeMarker = '|'.into();
8137 let replace_range_marker: TextRangeMarker = ('<', '>').into();
8138 let (_, mut marked_ranges) = marked_text_ranges_by(
8139 marked_string,
8140 vec![complete_from_marker.clone(), replace_range_marker.clone()],
8141 );
8142
8143 let complete_from_position =
8144 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
8145 let replace_range =
8146 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
8147
8148 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
8149 let completions = completions.clone();
8150 async move {
8151 assert_eq!(params.text_document_position.text_document.uri, url.clone());
8152 assert_eq!(
8153 params.text_document_position.position,
8154 complete_from_position
8155 );
8156 Ok(Some(lsp::CompletionResponse::Array(
8157 completions
8158 .iter()
8159 .map(|completion_text| lsp::CompletionItem {
8160 label: completion_text.to_string(),
8161 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8162 range: replace_range,
8163 new_text: completion_text.to_string(),
8164 })),
8165 ..Default::default()
8166 })
8167 .collect(),
8168 )))
8169 }
8170 });
8171
8172 async move {
8173 request.next().await;
8174 }
8175}
8176
8177fn handle_resolve_completion_request(
8178 cx: &mut EditorLspTestContext,
8179 edits: Option<Vec<(&'static str, &'static str)>>,
8180) -> impl Future<Output = ()> {
8181 let edits = edits.map(|edits| {
8182 edits
8183 .iter()
8184 .map(|(marked_string, new_text)| {
8185 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
8186 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
8187 lsp::TextEdit::new(replace_range, new_text.to_string())
8188 })
8189 .collect::<Vec<_>>()
8190 });
8191
8192 let mut request =
8193 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
8194 let edits = edits.clone();
8195 async move {
8196 Ok(lsp::CompletionItem {
8197 additional_text_edits: edits,
8198 ..Default::default()
8199 })
8200 }
8201 });
8202
8203 async move {
8204 request.next().await;
8205 }
8206}
8207
8208fn handle_copilot_completion_request(
8209 lsp: &lsp::FakeLanguageServer,
8210 completions: Vec<copilot::request::Completion>,
8211 completions_cycling: Vec<copilot::request::Completion>,
8212) {
8213 lsp.handle_request::<copilot::request::GetCompletions, _, _>(move |_params, _cx| {
8214 let completions = completions.clone();
8215 async move {
8216 Ok(copilot::request::GetCompletionsResult {
8217 completions: completions.clone(),
8218 })
8219 }
8220 });
8221 lsp.handle_request::<copilot::request::GetCompletionsCycling, _, _>(move |_params, _cx| {
8222 let completions_cycling = completions_cycling.clone();
8223 async move {
8224 Ok(copilot::request::GetCompletionsResult {
8225 completions: completions_cycling.clone(),
8226 })
8227 }
8228 });
8229}
8230
8231pub(crate) fn update_test_language_settings(
8232 cx: &mut TestAppContext,
8233 f: impl Fn(&mut AllLanguageSettingsContent),
8234) {
8235 _ = cx.update(|cx| {
8236 cx.update_global(|store: &mut SettingsStore, cx| {
8237 store.update_user_settings::<AllLanguageSettings>(cx, f);
8238 });
8239 });
8240}
8241
8242pub(crate) fn update_test_project_settings(
8243 cx: &mut TestAppContext,
8244 f: impl Fn(&mut ProjectSettings),
8245) {
8246 _ = cx.update(|cx| {
8247 cx.update_global(|store: &mut SettingsStore, cx| {
8248 store.update_user_settings::<ProjectSettings>(cx, f);
8249 });
8250 });
8251}
8252
8253pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
8254 _ = cx.update(|cx| {
8255 let store = SettingsStore::test(cx);
8256 cx.set_global(store);
8257 theme::init(theme::LoadThemes::JustBase, cx);
8258 client::init_settings(cx);
8259 language::init(cx);
8260 Project::init_settings(cx);
8261 workspace::init_settings(cx);
8262 crate::init(cx);
8263 });
8264
8265 update_test_language_settings(cx, f);
8266}