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