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