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