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