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