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