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