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_duplicate_line(cx: &mut TestAppContext) {
2505 init_test(cx, |_| {});
2506
2507 let (_, view) = cx.add_window(|cx| {
2508 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2509 build_editor(buffer, cx)
2510 });
2511 view.update(cx, |view, cx| {
2512 view.change_selections(None, cx, |s| {
2513 s.select_display_ranges([
2514 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
2515 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
2516 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
2517 DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
2518 ])
2519 });
2520 view.duplicate_line(&DuplicateLine, cx);
2521 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
2522 assert_eq!(
2523 view.selections.display_ranges(cx),
2524 vec![
2525 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
2526 DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
2527 DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
2528 DisplayPoint::new(6, 0)..DisplayPoint::new(6, 0),
2529 ]
2530 );
2531 });
2532
2533 let (_, view) = cx.add_window(|cx| {
2534 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2535 build_editor(buffer, cx)
2536 });
2537 view.update(cx, |view, cx| {
2538 view.change_selections(None, cx, |s| {
2539 s.select_display_ranges([
2540 DisplayPoint::new(0, 1)..DisplayPoint::new(1, 1),
2541 DisplayPoint::new(1, 2)..DisplayPoint::new(2, 1),
2542 ])
2543 });
2544 view.duplicate_line(&DuplicateLine, cx);
2545 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
2546 assert_eq!(
2547 view.selections.display_ranges(cx),
2548 vec![
2549 DisplayPoint::new(3, 1)..DisplayPoint::new(4, 1),
2550 DisplayPoint::new(4, 2)..DisplayPoint::new(5, 1),
2551 ]
2552 );
2553 });
2554}
2555
2556#[gpui::test]
2557fn test_move_line_up_down(cx: &mut TestAppContext) {
2558 init_test(cx, |_| {});
2559
2560 let (_, view) = cx.add_window(|cx| {
2561 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
2562 build_editor(buffer, cx)
2563 });
2564 view.update(cx, |view, cx| {
2565 view.fold_ranges(
2566 vec![
2567 Point::new(0, 2)..Point::new(1, 2),
2568 Point::new(2, 3)..Point::new(4, 1),
2569 Point::new(7, 0)..Point::new(8, 4),
2570 ],
2571 true,
2572 cx,
2573 );
2574 view.change_selections(None, cx, |s| {
2575 s.select_display_ranges([
2576 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
2577 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
2578 DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
2579 DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2),
2580 ])
2581 });
2582 assert_eq!(
2583 view.display_text(cx),
2584 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
2585 );
2586
2587 view.move_line_up(&MoveLineUp, cx);
2588 assert_eq!(
2589 view.display_text(cx),
2590 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
2591 );
2592 assert_eq!(
2593 view.selections.display_ranges(cx),
2594 vec![
2595 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
2596 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
2597 DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3),
2598 DisplayPoint::new(4, 0)..DisplayPoint::new(4, 2)
2599 ]
2600 );
2601 });
2602
2603 view.update(cx, |view, cx| {
2604 view.move_line_down(&MoveLineDown, cx);
2605 assert_eq!(
2606 view.display_text(cx),
2607 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
2608 );
2609 assert_eq!(
2610 view.selections.display_ranges(cx),
2611 vec![
2612 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
2613 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
2614 DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
2615 DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2)
2616 ]
2617 );
2618 });
2619
2620 view.update(cx, |view, cx| {
2621 view.move_line_down(&MoveLineDown, cx);
2622 assert_eq!(
2623 view.display_text(cx),
2624 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
2625 );
2626 assert_eq!(
2627 view.selections.display_ranges(cx),
2628 vec![
2629 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
2630 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
2631 DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
2632 DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2)
2633 ]
2634 );
2635 });
2636
2637 view.update(cx, |view, cx| {
2638 view.move_line_up(&MoveLineUp, cx);
2639 assert_eq!(
2640 view.display_text(cx),
2641 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
2642 );
2643 assert_eq!(
2644 view.selections.display_ranges(cx),
2645 vec![
2646 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
2647 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
2648 DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3),
2649 DisplayPoint::new(4, 0)..DisplayPoint::new(4, 2)
2650 ]
2651 );
2652 });
2653}
2654
2655#[gpui::test]
2656fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
2657 init_test(cx, |_| {});
2658
2659 let (_, editor) = cx.add_window(|cx| {
2660 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
2661 build_editor(buffer, cx)
2662 });
2663 editor.update(cx, |editor, cx| {
2664 let snapshot = editor.buffer.read(cx).snapshot(cx);
2665 editor.insert_blocks(
2666 [BlockProperties {
2667 style: BlockStyle::Fixed,
2668 position: snapshot.anchor_after(Point::new(2, 0)),
2669 disposition: BlockDisposition::Below,
2670 height: 1,
2671 render: Arc::new(|_| Empty::new().into_any()),
2672 }],
2673 Some(Autoscroll::fit()),
2674 cx,
2675 );
2676 editor.change_selections(None, cx, |s| {
2677 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
2678 });
2679 editor.move_line_down(&MoveLineDown, cx);
2680 });
2681}
2682
2683#[gpui::test]
2684fn test_transpose(cx: &mut TestAppContext) {
2685 init_test(cx, |_| {});
2686
2687 _ = cx
2688 .add_window(|cx| {
2689 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
2690
2691 editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
2692 editor.transpose(&Default::default(), cx);
2693 assert_eq!(editor.text(cx), "bac");
2694 assert_eq!(editor.selections.ranges(cx), [2..2]);
2695
2696 editor.transpose(&Default::default(), cx);
2697 assert_eq!(editor.text(cx), "bca");
2698 assert_eq!(editor.selections.ranges(cx), [3..3]);
2699
2700 editor.transpose(&Default::default(), cx);
2701 assert_eq!(editor.text(cx), "bac");
2702 assert_eq!(editor.selections.ranges(cx), [3..3]);
2703
2704 editor
2705 })
2706 .1;
2707
2708 _ = cx
2709 .add_window(|cx| {
2710 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
2711
2712 editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
2713 editor.transpose(&Default::default(), cx);
2714 assert_eq!(editor.text(cx), "acb\nde");
2715 assert_eq!(editor.selections.ranges(cx), [3..3]);
2716
2717 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
2718 editor.transpose(&Default::default(), cx);
2719 assert_eq!(editor.text(cx), "acbd\ne");
2720 assert_eq!(editor.selections.ranges(cx), [5..5]);
2721
2722 editor.transpose(&Default::default(), cx);
2723 assert_eq!(editor.text(cx), "acbde\n");
2724 assert_eq!(editor.selections.ranges(cx), [6..6]);
2725
2726 editor.transpose(&Default::default(), cx);
2727 assert_eq!(editor.text(cx), "acbd\ne");
2728 assert_eq!(editor.selections.ranges(cx), [6..6]);
2729
2730 editor
2731 })
2732 .1;
2733
2734 _ = cx
2735 .add_window(|cx| {
2736 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
2737
2738 editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
2739 editor.transpose(&Default::default(), cx);
2740 assert_eq!(editor.text(cx), "bacd\ne");
2741 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
2742
2743 editor.transpose(&Default::default(), cx);
2744 assert_eq!(editor.text(cx), "bcade\n");
2745 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
2746
2747 editor.transpose(&Default::default(), cx);
2748 assert_eq!(editor.text(cx), "bcda\ne");
2749 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
2750
2751 editor.transpose(&Default::default(), cx);
2752 assert_eq!(editor.text(cx), "bcade\n");
2753 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
2754
2755 editor.transpose(&Default::default(), cx);
2756 assert_eq!(editor.text(cx), "bcaed\n");
2757 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
2758
2759 editor
2760 })
2761 .1;
2762
2763 _ = cx
2764 .add_window(|cx| {
2765 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
2766
2767 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
2768 editor.transpose(&Default::default(), cx);
2769 assert_eq!(editor.text(cx), "🏀🍐✋");
2770 assert_eq!(editor.selections.ranges(cx), [8..8]);
2771
2772 editor.transpose(&Default::default(), cx);
2773 assert_eq!(editor.text(cx), "🏀✋🍐");
2774 assert_eq!(editor.selections.ranges(cx), [11..11]);
2775
2776 editor.transpose(&Default::default(), cx);
2777 assert_eq!(editor.text(cx), "🏀🍐✋");
2778 assert_eq!(editor.selections.ranges(cx), [11..11]);
2779
2780 editor
2781 })
2782 .1;
2783}
2784
2785#[gpui::test]
2786async fn test_clipboard(cx: &mut gpui::TestAppContext) {
2787 init_test(cx, |_| {});
2788
2789 let mut cx = EditorTestContext::new(cx).await;
2790
2791 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
2792 cx.update_editor(|e, cx| e.cut(&Cut, cx));
2793 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
2794
2795 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
2796 cx.set_state("two ˇfour ˇsix ˇ");
2797 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2798 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
2799
2800 // Paste again but with only two cursors. Since the number of cursors doesn't
2801 // match the number of slices in the clipboard, the entire clipboard text
2802 // is pasted at each cursor.
2803 cx.set_state("ˇtwo one✅ four three six five ˇ");
2804 cx.update_editor(|e, cx| {
2805 e.handle_input("( ", cx);
2806 e.paste(&Paste, cx);
2807 e.handle_input(") ", cx);
2808 });
2809 cx.assert_editor_state(
2810 &([
2811 "( one✅ ",
2812 "three ",
2813 "five ) ˇtwo one✅ four three six five ( one✅ ",
2814 "three ",
2815 "five ) ˇ",
2816 ]
2817 .join("\n")),
2818 );
2819
2820 // Cut with three selections, one of which is full-line.
2821 cx.set_state(indoc! {"
2822 1«2ˇ»3
2823 4ˇ567
2824 «8ˇ»9"});
2825 cx.update_editor(|e, cx| e.cut(&Cut, cx));
2826 cx.assert_editor_state(indoc! {"
2827 1ˇ3
2828 ˇ9"});
2829
2830 // Paste with three selections, noticing how the copied selection that was full-line
2831 // gets inserted before the second cursor.
2832 cx.set_state(indoc! {"
2833 1ˇ3
2834 9ˇ
2835 «oˇ»ne"});
2836 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2837 cx.assert_editor_state(indoc! {"
2838 12ˇ3
2839 4567
2840 9ˇ
2841 8ˇne"});
2842
2843 // Copy with a single cursor only, which writes the whole line into the clipboard.
2844 cx.set_state(indoc! {"
2845 The quick brown
2846 fox juˇmps over
2847 the lazy dog"});
2848 cx.update_editor(|e, cx| e.copy(&Copy, cx));
2849 cx.cx.assert_clipboard_content(Some("fox jumps over\n"));
2850
2851 // Paste with three selections, noticing how the copied full-line selection is inserted
2852 // before the empty selections but replaces the selection that is non-empty.
2853 cx.set_state(indoc! {"
2854 Tˇhe quick brown
2855 «foˇ»x jumps over
2856 tˇhe lazy dog"});
2857 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2858 cx.assert_editor_state(indoc! {"
2859 fox jumps over
2860 Tˇhe quick brown
2861 fox jumps over
2862 ˇx jumps over
2863 fox jumps over
2864 tˇhe lazy dog"});
2865}
2866
2867#[gpui::test]
2868async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
2869 init_test(cx, |_| {});
2870
2871 let mut cx = EditorTestContext::new(cx).await;
2872 let language = Arc::new(Language::new(
2873 LanguageConfig::default(),
2874 Some(tree_sitter_rust::language()),
2875 ));
2876 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2877
2878 // Cut an indented block, without the leading whitespace.
2879 cx.set_state(indoc! {"
2880 const a: B = (
2881 c(),
2882 «d(
2883 e,
2884 f
2885 )ˇ»
2886 );
2887 "});
2888 cx.update_editor(|e, cx| e.cut(&Cut, cx));
2889 cx.assert_editor_state(indoc! {"
2890 const a: B = (
2891 c(),
2892 ˇ
2893 );
2894 "});
2895
2896 // Paste it at the same position.
2897 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2898 cx.assert_editor_state(indoc! {"
2899 const a: B = (
2900 c(),
2901 d(
2902 e,
2903 f
2904 )ˇ
2905 );
2906 "});
2907
2908 // Paste it at a line with a lower indent level.
2909 cx.set_state(indoc! {"
2910 ˇ
2911 const a: B = (
2912 c(),
2913 );
2914 "});
2915 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2916 cx.assert_editor_state(indoc! {"
2917 d(
2918 e,
2919 f
2920 )ˇ
2921 const a: B = (
2922 c(),
2923 );
2924 "});
2925
2926 // Cut an indented block, with the leading whitespace.
2927 cx.set_state(indoc! {"
2928 const a: B = (
2929 c(),
2930 « d(
2931 e,
2932 f
2933 )
2934 ˇ»);
2935 "});
2936 cx.update_editor(|e, cx| e.cut(&Cut, cx));
2937 cx.assert_editor_state(indoc! {"
2938 const a: B = (
2939 c(),
2940 ˇ);
2941 "});
2942
2943 // Paste it at the same position.
2944 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2945 cx.assert_editor_state(indoc! {"
2946 const a: B = (
2947 c(),
2948 d(
2949 e,
2950 f
2951 )
2952 ˇ);
2953 "});
2954
2955 // Paste it at a line with a higher indent level.
2956 cx.set_state(indoc! {"
2957 const a: B = (
2958 c(),
2959 d(
2960 e,
2961 fˇ
2962 )
2963 );
2964 "});
2965 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2966 cx.assert_editor_state(indoc! {"
2967 const a: B = (
2968 c(),
2969 d(
2970 e,
2971 f d(
2972 e,
2973 f
2974 )
2975 ˇ
2976 )
2977 );
2978 "});
2979}
2980
2981#[gpui::test]
2982fn test_select_all(cx: &mut TestAppContext) {
2983 init_test(cx, |_| {});
2984
2985 let (_, view) = cx.add_window(|cx| {
2986 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
2987 build_editor(buffer, cx)
2988 });
2989 view.update(cx, |view, cx| {
2990 view.select_all(&SelectAll, cx);
2991 assert_eq!(
2992 view.selections.display_ranges(cx),
2993 &[DisplayPoint::new(0, 0)..DisplayPoint::new(2, 3)]
2994 );
2995 });
2996}
2997
2998#[gpui::test]
2999fn test_select_line(cx: &mut TestAppContext) {
3000 init_test(cx, |_| {});
3001
3002 let (_, view) = cx.add_window(|cx| {
3003 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
3004 build_editor(buffer, cx)
3005 });
3006 view.update(cx, |view, cx| {
3007 view.change_selections(None, cx, |s| {
3008 s.select_display_ranges([
3009 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
3010 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
3011 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
3012 DisplayPoint::new(4, 2)..DisplayPoint::new(4, 2),
3013 ])
3014 });
3015 view.select_line(&SelectLine, cx);
3016 assert_eq!(
3017 view.selections.display_ranges(cx),
3018 vec![
3019 DisplayPoint::new(0, 0)..DisplayPoint::new(2, 0),
3020 DisplayPoint::new(4, 0)..DisplayPoint::new(5, 0),
3021 ]
3022 );
3023 });
3024
3025 view.update(cx, |view, cx| {
3026 view.select_line(&SelectLine, cx);
3027 assert_eq!(
3028 view.selections.display_ranges(cx),
3029 vec![
3030 DisplayPoint::new(0, 0)..DisplayPoint::new(3, 0),
3031 DisplayPoint::new(4, 0)..DisplayPoint::new(5, 5),
3032 ]
3033 );
3034 });
3035
3036 view.update(cx, |view, cx| {
3037 view.select_line(&SelectLine, cx);
3038 assert_eq!(
3039 view.selections.display_ranges(cx),
3040 vec![DisplayPoint::new(0, 0)..DisplayPoint::new(5, 5)]
3041 );
3042 });
3043}
3044
3045#[gpui::test]
3046fn test_split_selection_into_lines(cx: &mut TestAppContext) {
3047 init_test(cx, |_| {});
3048
3049 let (_, view) = cx.add_window(|cx| {
3050 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
3051 build_editor(buffer, cx)
3052 });
3053 view.update(cx, |view, cx| {
3054 view.fold_ranges(
3055 vec![
3056 Point::new(0, 2)..Point::new(1, 2),
3057 Point::new(2, 3)..Point::new(4, 1),
3058 Point::new(7, 0)..Point::new(8, 4),
3059 ],
3060 true,
3061 cx,
3062 );
3063 view.change_selections(None, cx, |s| {
3064 s.select_display_ranges([
3065 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
3066 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
3067 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
3068 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
3069 ])
3070 });
3071 assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i");
3072 });
3073
3074 view.update(cx, |view, cx| {
3075 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
3076 assert_eq!(
3077 view.display_text(cx),
3078 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
3079 );
3080 assert_eq!(
3081 view.selections.display_ranges(cx),
3082 [
3083 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
3084 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
3085 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 0),
3086 DisplayPoint::new(5, 4)..DisplayPoint::new(5, 4)
3087 ]
3088 );
3089 });
3090
3091 view.update(cx, |view, cx| {
3092 view.change_selections(None, cx, |s| {
3093 s.select_display_ranges([DisplayPoint::new(5, 0)..DisplayPoint::new(0, 1)])
3094 });
3095 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
3096 assert_eq!(
3097 view.display_text(cx),
3098 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
3099 );
3100 assert_eq!(
3101 view.selections.display_ranges(cx),
3102 [
3103 DisplayPoint::new(0, 5)..DisplayPoint::new(0, 5),
3104 DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
3105 DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
3106 DisplayPoint::new(3, 5)..DisplayPoint::new(3, 5),
3107 DisplayPoint::new(4, 5)..DisplayPoint::new(4, 5),
3108 DisplayPoint::new(5, 5)..DisplayPoint::new(5, 5),
3109 DisplayPoint::new(6, 5)..DisplayPoint::new(6, 5),
3110 DisplayPoint::new(7, 0)..DisplayPoint::new(7, 0)
3111 ]
3112 );
3113 });
3114}
3115
3116#[gpui::test]
3117fn test_add_selection_above_below(cx: &mut TestAppContext) {
3118 init_test(cx, |_| {});
3119
3120 let (_, view) = cx.add_window(|cx| {
3121 let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
3122 build_editor(buffer, cx)
3123 });
3124
3125 view.update(cx, |view, cx| {
3126 view.change_selections(None, cx, |s| {
3127 s.select_display_ranges([DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)])
3128 });
3129 });
3130 view.update(cx, |view, cx| {
3131 view.add_selection_above(&AddSelectionAbove, cx);
3132 assert_eq!(
3133 view.selections.display_ranges(cx),
3134 vec![
3135 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
3136 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)
3137 ]
3138 );
3139 });
3140
3141 view.update(cx, |view, cx| {
3142 view.add_selection_above(&AddSelectionAbove, cx);
3143 assert_eq!(
3144 view.selections.display_ranges(cx),
3145 vec![
3146 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
3147 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)
3148 ]
3149 );
3150 });
3151
3152 view.update(cx, |view, cx| {
3153 view.add_selection_below(&AddSelectionBelow, cx);
3154 assert_eq!(
3155 view.selections.display_ranges(cx),
3156 vec![DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)]
3157 );
3158
3159 view.undo_selection(&UndoSelection, cx);
3160 assert_eq!(
3161 view.selections.display_ranges(cx),
3162 vec![
3163 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
3164 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)
3165 ]
3166 );
3167
3168 view.redo_selection(&RedoSelection, cx);
3169 assert_eq!(
3170 view.selections.display_ranges(cx),
3171 vec![DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)]
3172 );
3173 });
3174
3175 view.update(cx, |view, cx| {
3176 view.add_selection_below(&AddSelectionBelow, cx);
3177 assert_eq!(
3178 view.selections.display_ranges(cx),
3179 vec![
3180 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3),
3181 DisplayPoint::new(4, 3)..DisplayPoint::new(4, 3)
3182 ]
3183 );
3184 });
3185
3186 view.update(cx, |view, cx| {
3187 view.add_selection_below(&AddSelectionBelow, cx);
3188 assert_eq!(
3189 view.selections.display_ranges(cx),
3190 vec![
3191 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3),
3192 DisplayPoint::new(4, 3)..DisplayPoint::new(4, 3)
3193 ]
3194 );
3195 });
3196
3197 view.update(cx, |view, cx| {
3198 view.change_selections(None, cx, |s| {
3199 s.select_display_ranges([DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)])
3200 });
3201 });
3202 view.update(cx, |view, cx| {
3203 view.add_selection_below(&AddSelectionBelow, cx);
3204 assert_eq!(
3205 view.selections.display_ranges(cx),
3206 vec![
3207 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3),
3208 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 3)
3209 ]
3210 );
3211 });
3212
3213 view.update(cx, |view, cx| {
3214 view.add_selection_below(&AddSelectionBelow, cx);
3215 assert_eq!(
3216 view.selections.display_ranges(cx),
3217 vec![
3218 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3),
3219 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 3)
3220 ]
3221 );
3222 });
3223
3224 view.update(cx, |view, cx| {
3225 view.add_selection_above(&AddSelectionAbove, cx);
3226 assert_eq!(
3227 view.selections.display_ranges(cx),
3228 vec![DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)]
3229 );
3230 });
3231
3232 view.update(cx, |view, cx| {
3233 view.add_selection_above(&AddSelectionAbove, cx);
3234 assert_eq!(
3235 view.selections.display_ranges(cx),
3236 vec![DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)]
3237 );
3238 });
3239
3240 view.update(cx, |view, cx| {
3241 view.change_selections(None, cx, |s| {
3242 s.select_display_ranges([DisplayPoint::new(0, 1)..DisplayPoint::new(1, 4)])
3243 });
3244 view.add_selection_below(&AddSelectionBelow, cx);
3245 assert_eq!(
3246 view.selections.display_ranges(cx),
3247 vec![
3248 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
3249 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4),
3250 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2),
3251 ]
3252 );
3253 });
3254
3255 view.update(cx, |view, cx| {
3256 view.add_selection_below(&AddSelectionBelow, cx);
3257 assert_eq!(
3258 view.selections.display_ranges(cx),
3259 vec![
3260 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
3261 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4),
3262 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2),
3263 DisplayPoint::new(4, 1)..DisplayPoint::new(4, 4),
3264 ]
3265 );
3266 });
3267
3268 view.update(cx, |view, cx| {
3269 view.add_selection_above(&AddSelectionAbove, cx);
3270 assert_eq!(
3271 view.selections.display_ranges(cx),
3272 vec![
3273 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
3274 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4),
3275 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2),
3276 ]
3277 );
3278 });
3279
3280 view.update(cx, |view, cx| {
3281 view.change_selections(None, cx, |s| {
3282 s.select_display_ranges([DisplayPoint::new(4, 3)..DisplayPoint::new(1, 1)])
3283 });
3284 });
3285 view.update(cx, |view, cx| {
3286 view.add_selection_above(&AddSelectionAbove, cx);
3287 assert_eq!(
3288 view.selections.display_ranges(cx),
3289 vec![
3290 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 1),
3291 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 1),
3292 DisplayPoint::new(3, 2)..DisplayPoint::new(3, 1),
3293 DisplayPoint::new(4, 3)..DisplayPoint::new(4, 1),
3294 ]
3295 );
3296 });
3297
3298 view.update(cx, |view, cx| {
3299 view.add_selection_below(&AddSelectionBelow, cx);
3300 assert_eq!(
3301 view.selections.display_ranges(cx),
3302 vec![
3303 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 1),
3304 DisplayPoint::new(3, 2)..DisplayPoint::new(3, 1),
3305 DisplayPoint::new(4, 3)..DisplayPoint::new(4, 1),
3306 ]
3307 );
3308 });
3309}
3310
3311#[gpui::test]
3312async fn test_select_next(cx: &mut gpui::TestAppContext) {
3313 init_test(cx, |_| {});
3314
3315 let mut cx = EditorTestContext::new(cx).await;
3316 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
3317
3318 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx));
3319 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
3320
3321 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx));
3322 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
3323
3324 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
3325 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
3326
3327 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
3328 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
3329
3330 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx));
3331 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
3332
3333 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx));
3334 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
3335}
3336
3337#[gpui::test]
3338async fn test_select_previous(cx: &mut gpui::TestAppContext) {
3339 init_test(cx, |_| {});
3340 {
3341 // `Select previous` without a selection (selects wordwise)
3342 let mut cx = EditorTestContext::new(cx).await;
3343 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
3344
3345 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
3346 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
3347
3348 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
3349 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
3350
3351 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
3352 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
3353
3354 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
3355 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
3356
3357 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
3358 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
3359
3360 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
3361 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
3362 }
3363 {
3364 // `Select previous` with a selection
3365 let mut cx = EditorTestContext::new(cx).await;
3366 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
3367
3368 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
3369 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
3370
3371 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
3372 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
3373
3374 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
3375 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
3376
3377 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
3378 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
3379
3380 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
3381 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
3382
3383 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
3384 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
3385 }
3386}
3387
3388#[gpui::test]
3389async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
3390 init_test(cx, |_| {});
3391
3392 let language = Arc::new(Language::new(
3393 LanguageConfig::default(),
3394 Some(tree_sitter_rust::language()),
3395 ));
3396
3397 let text = r#"
3398 use mod1::mod2::{mod3, mod4};
3399
3400 fn fn_1(param1: bool, param2: &str) {
3401 let var1 = "text";
3402 }
3403 "#
3404 .unindent();
3405
3406 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
3407 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
3408 let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
3409 view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
3410 .await;
3411
3412 view.update(cx, |view, cx| {
3413 view.change_selections(None, cx, |s| {
3414 s.select_display_ranges([
3415 DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
3416 DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
3417 DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
3418 ]);
3419 });
3420 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
3421 });
3422 assert_eq!(
3423 view.update(cx, |view, cx| { view.selections.display_ranges(cx) }),
3424 &[
3425 DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27),
3426 DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
3427 DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21),
3428 ]
3429 );
3430
3431 view.update(cx, |view, cx| {
3432 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
3433 });
3434 assert_eq!(
3435 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3436 &[
3437 DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
3438 DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0),
3439 ]
3440 );
3441
3442 view.update(cx, |view, cx| {
3443 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
3444 });
3445 assert_eq!(
3446 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3447 &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)]
3448 );
3449
3450 // Trying to expand the selected syntax node one more time has no effect.
3451 view.update(cx, |view, cx| {
3452 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
3453 });
3454 assert_eq!(
3455 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3456 &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)]
3457 );
3458
3459 view.update(cx, |view, cx| {
3460 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
3461 });
3462 assert_eq!(
3463 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3464 &[
3465 DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
3466 DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0),
3467 ]
3468 );
3469
3470 view.update(cx, |view, cx| {
3471 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
3472 });
3473 assert_eq!(
3474 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3475 &[
3476 DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27),
3477 DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
3478 DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21),
3479 ]
3480 );
3481
3482 view.update(cx, |view, cx| {
3483 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
3484 });
3485 assert_eq!(
3486 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3487 &[
3488 DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
3489 DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
3490 DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
3491 ]
3492 );
3493
3494 // Trying to shrink the selected syntax node one more time has no effect.
3495 view.update(cx, |view, cx| {
3496 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
3497 });
3498 assert_eq!(
3499 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3500 &[
3501 DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
3502 DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
3503 DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
3504 ]
3505 );
3506
3507 // Ensure that we keep expanding the selection if the larger selection starts or ends within
3508 // a fold.
3509 view.update(cx, |view, cx| {
3510 view.fold_ranges(
3511 vec![
3512 Point::new(0, 21)..Point::new(0, 24),
3513 Point::new(3, 20)..Point::new(3, 22),
3514 ],
3515 true,
3516 cx,
3517 );
3518 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
3519 });
3520 assert_eq!(
3521 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3522 &[
3523 DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
3524 DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
3525 DisplayPoint::new(3, 4)..DisplayPoint::new(3, 23),
3526 ]
3527 );
3528}
3529
3530#[gpui::test]
3531async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
3532 init_test(cx, |_| {});
3533
3534 let language = Arc::new(
3535 Language::new(
3536 LanguageConfig {
3537 brackets: BracketPairConfig {
3538 pairs: vec![
3539 BracketPair {
3540 start: "{".to_string(),
3541 end: "}".to_string(),
3542 close: false,
3543 newline: true,
3544 },
3545 BracketPair {
3546 start: "(".to_string(),
3547 end: ")".to_string(),
3548 close: false,
3549 newline: true,
3550 },
3551 ],
3552 ..Default::default()
3553 },
3554 ..Default::default()
3555 },
3556 Some(tree_sitter_rust::language()),
3557 )
3558 .with_indents_query(
3559 r#"
3560 (_ "(" ")" @end) @indent
3561 (_ "{" "}" @end) @indent
3562 "#,
3563 )
3564 .unwrap(),
3565 );
3566
3567 let text = "fn a() {}";
3568
3569 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
3570 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
3571 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
3572 editor
3573 .condition(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
3574 .await;
3575
3576 editor.update(cx, |editor, cx| {
3577 editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
3578 editor.newline(&Newline, cx);
3579 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
3580 assert_eq!(
3581 editor.selections.ranges(cx),
3582 &[
3583 Point::new(1, 4)..Point::new(1, 4),
3584 Point::new(3, 4)..Point::new(3, 4),
3585 Point::new(5, 0)..Point::new(5, 0)
3586 ]
3587 );
3588 });
3589}
3590
3591#[gpui::test]
3592async fn test_autoclose_pairs(cx: &mut gpui::TestAppContext) {
3593 init_test(cx, |_| {});
3594
3595 let mut cx = EditorTestContext::new(cx).await;
3596
3597 let language = Arc::new(Language::new(
3598 LanguageConfig {
3599 brackets: BracketPairConfig {
3600 pairs: vec![
3601 BracketPair {
3602 start: "{".to_string(),
3603 end: "}".to_string(),
3604 close: true,
3605 newline: true,
3606 },
3607 BracketPair {
3608 start: "(".to_string(),
3609 end: ")".to_string(),
3610 close: true,
3611 newline: true,
3612 },
3613 BracketPair {
3614 start: "/*".to_string(),
3615 end: " */".to_string(),
3616 close: true,
3617 newline: true,
3618 },
3619 BracketPair {
3620 start: "[".to_string(),
3621 end: "]".to_string(),
3622 close: false,
3623 newline: true,
3624 },
3625 BracketPair {
3626 start: "\"".to_string(),
3627 end: "\"".to_string(),
3628 close: true,
3629 newline: false,
3630 },
3631 ],
3632 ..Default::default()
3633 },
3634 autoclose_before: "})]".to_string(),
3635 ..Default::default()
3636 },
3637 Some(tree_sitter_rust::language()),
3638 ));
3639
3640 let registry = Arc::new(LanguageRegistry::test());
3641 registry.add(language.clone());
3642 cx.update_buffer(|buffer, cx| {
3643 buffer.set_language_registry(registry);
3644 buffer.set_language(Some(language), cx);
3645 });
3646
3647 cx.set_state(
3648 &r#"
3649 🏀ˇ
3650 εˇ
3651 ❤️ˇ
3652 "#
3653 .unindent(),
3654 );
3655
3656 // autoclose multiple nested brackets at multiple cursors
3657 cx.update_editor(|view, cx| {
3658 view.handle_input("{", cx);
3659 view.handle_input("{", cx);
3660 view.handle_input("{", cx);
3661 });
3662 cx.assert_editor_state(
3663 &"
3664 🏀{{{ˇ}}}
3665 ε{{{ˇ}}}
3666 ❤️{{{ˇ}}}
3667 "
3668 .unindent(),
3669 );
3670
3671 // insert a different closing bracket
3672 cx.update_editor(|view, cx| {
3673 view.handle_input(")", cx);
3674 });
3675 cx.assert_editor_state(
3676 &"
3677 🏀{{{)ˇ}}}
3678 ε{{{)ˇ}}}
3679 ❤️{{{)ˇ}}}
3680 "
3681 .unindent(),
3682 );
3683
3684 // skip over the auto-closed brackets when typing a closing bracket
3685 cx.update_editor(|view, cx| {
3686 view.move_right(&MoveRight, cx);
3687 view.handle_input("}", cx);
3688 view.handle_input("}", cx);
3689 view.handle_input("}", cx);
3690 });
3691 cx.assert_editor_state(
3692 &"
3693 🏀{{{)}}}}ˇ
3694 ε{{{)}}}}ˇ
3695 ❤️{{{)}}}}ˇ
3696 "
3697 .unindent(),
3698 );
3699
3700 // autoclose multi-character pairs
3701 cx.set_state(
3702 &"
3703 ˇ
3704 ˇ
3705 "
3706 .unindent(),
3707 );
3708 cx.update_editor(|view, cx| {
3709 view.handle_input("/", cx);
3710 view.handle_input("*", cx);
3711 });
3712 cx.assert_editor_state(
3713 &"
3714 /*ˇ */
3715 /*ˇ */
3716 "
3717 .unindent(),
3718 );
3719
3720 // one cursor autocloses a multi-character pair, one cursor
3721 // does not autoclose.
3722 cx.set_state(
3723 &"
3724 /ˇ
3725 ˇ
3726 "
3727 .unindent(),
3728 );
3729 cx.update_editor(|view, cx| view.handle_input("*", cx));
3730 cx.assert_editor_state(
3731 &"
3732 /*ˇ */
3733 *ˇ
3734 "
3735 .unindent(),
3736 );
3737
3738 // Don't autoclose if the next character isn't whitespace and isn't
3739 // listed in the language's "autoclose_before" section.
3740 cx.set_state("ˇa b");
3741 cx.update_editor(|view, cx| view.handle_input("{", cx));
3742 cx.assert_editor_state("{ˇa b");
3743
3744 // Don't autoclose if `close` is false for the bracket pair
3745 cx.set_state("ˇ");
3746 cx.update_editor(|view, cx| view.handle_input("[", cx));
3747 cx.assert_editor_state("[ˇ");
3748
3749 // Surround with brackets if text is selected
3750 cx.set_state("«aˇ» b");
3751 cx.update_editor(|view, cx| view.handle_input("{", cx));
3752 cx.assert_editor_state("{«aˇ»} b");
3753
3754 // Autclose pair where the start and end characters are the same
3755 cx.set_state("aˇ");
3756 cx.update_editor(|view, cx| view.handle_input("\"", cx));
3757 cx.assert_editor_state("a\"ˇ\"");
3758 cx.update_editor(|view, cx| view.handle_input("\"", cx));
3759 cx.assert_editor_state("a\"\"ˇ");
3760}
3761
3762#[gpui::test]
3763async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
3764 init_test(cx, |_| {});
3765
3766 let mut cx = EditorTestContext::new(cx).await;
3767
3768 let html_language = Arc::new(
3769 Language::new(
3770 LanguageConfig {
3771 name: "HTML".into(),
3772 brackets: BracketPairConfig {
3773 pairs: vec![
3774 BracketPair {
3775 start: "<".into(),
3776 end: ">".into(),
3777 close: true,
3778 ..Default::default()
3779 },
3780 BracketPair {
3781 start: "{".into(),
3782 end: "}".into(),
3783 close: true,
3784 ..Default::default()
3785 },
3786 BracketPair {
3787 start: "(".into(),
3788 end: ")".into(),
3789 close: true,
3790 ..Default::default()
3791 },
3792 ],
3793 ..Default::default()
3794 },
3795 autoclose_before: "})]>".into(),
3796 ..Default::default()
3797 },
3798 Some(tree_sitter_html::language()),
3799 )
3800 .with_injection_query(
3801 r#"
3802 (script_element
3803 (raw_text) @content
3804 (#set! "language" "javascript"))
3805 "#,
3806 )
3807 .unwrap(),
3808 );
3809
3810 let javascript_language = Arc::new(Language::new(
3811 LanguageConfig {
3812 name: "JavaScript".into(),
3813 brackets: BracketPairConfig {
3814 pairs: vec![
3815 BracketPair {
3816 start: "/*".into(),
3817 end: " */".into(),
3818 close: true,
3819 ..Default::default()
3820 },
3821 BracketPair {
3822 start: "{".into(),
3823 end: "}".into(),
3824 close: true,
3825 ..Default::default()
3826 },
3827 BracketPair {
3828 start: "(".into(),
3829 end: ")".into(),
3830 close: true,
3831 ..Default::default()
3832 },
3833 ],
3834 ..Default::default()
3835 },
3836 autoclose_before: "})]>".into(),
3837 ..Default::default()
3838 },
3839 Some(tree_sitter_typescript::language_tsx()),
3840 ));
3841
3842 let registry = Arc::new(LanguageRegistry::test());
3843 registry.add(html_language.clone());
3844 registry.add(javascript_language.clone());
3845
3846 cx.update_buffer(|buffer, cx| {
3847 buffer.set_language_registry(registry);
3848 buffer.set_language(Some(html_language), cx);
3849 });
3850
3851 cx.set_state(
3852 &r#"
3853 <body>ˇ
3854 <script>
3855 var x = 1;ˇ
3856 </script>
3857 </body>ˇ
3858 "#
3859 .unindent(),
3860 );
3861
3862 // Precondition: different languages are active at different locations.
3863 cx.update_editor(|editor, cx| {
3864 let snapshot = editor.snapshot(cx);
3865 let cursors = editor.selections.ranges::<usize>(cx);
3866 let languages = cursors
3867 .iter()
3868 .map(|c| snapshot.language_at(c.start).unwrap().name())
3869 .collect::<Vec<_>>();
3870 assert_eq!(
3871 languages,
3872 &["HTML".into(), "JavaScript".into(), "HTML".into()]
3873 );
3874 });
3875
3876 // Angle brackets autoclose in HTML, but not JavaScript.
3877 cx.update_editor(|editor, cx| {
3878 editor.handle_input("<", cx);
3879 editor.handle_input("a", cx);
3880 });
3881 cx.assert_editor_state(
3882 &r#"
3883 <body><aˇ>
3884 <script>
3885 var x = 1;<aˇ
3886 </script>
3887 </body><aˇ>
3888 "#
3889 .unindent(),
3890 );
3891
3892 // Curly braces and parens autoclose in both HTML and JavaScript.
3893 cx.update_editor(|editor, cx| {
3894 editor.handle_input(" b=", cx);
3895 editor.handle_input("{", cx);
3896 editor.handle_input("c", cx);
3897 editor.handle_input("(", cx);
3898 });
3899 cx.assert_editor_state(
3900 &r#"
3901 <body><a b={c(ˇ)}>
3902 <script>
3903 var x = 1;<a b={c(ˇ)}
3904 </script>
3905 </body><a b={c(ˇ)}>
3906 "#
3907 .unindent(),
3908 );
3909
3910 // Brackets that were already autoclosed are skipped.
3911 cx.update_editor(|editor, cx| {
3912 editor.handle_input(")", cx);
3913 editor.handle_input("d", cx);
3914 editor.handle_input("}", cx);
3915 });
3916 cx.assert_editor_state(
3917 &r#"
3918 <body><a b={c()d}ˇ>
3919 <script>
3920 var x = 1;<a b={c()d}ˇ
3921 </script>
3922 </body><a b={c()d}ˇ>
3923 "#
3924 .unindent(),
3925 );
3926 cx.update_editor(|editor, cx| {
3927 editor.handle_input(">", cx);
3928 });
3929 cx.assert_editor_state(
3930 &r#"
3931 <body><a b={c()d}>ˇ
3932 <script>
3933 var x = 1;<a b={c()d}>ˇ
3934 </script>
3935 </body><a b={c()d}>ˇ
3936 "#
3937 .unindent(),
3938 );
3939
3940 // Reset
3941 cx.set_state(
3942 &r#"
3943 <body>ˇ
3944 <script>
3945 var x = 1;ˇ
3946 </script>
3947 </body>ˇ
3948 "#
3949 .unindent(),
3950 );
3951
3952 cx.update_editor(|editor, cx| {
3953 editor.handle_input("<", cx);
3954 });
3955 cx.assert_editor_state(
3956 &r#"
3957 <body><ˇ>
3958 <script>
3959 var x = 1;<ˇ
3960 </script>
3961 </body><ˇ>
3962 "#
3963 .unindent(),
3964 );
3965
3966 // When backspacing, the closing angle brackets are removed.
3967 cx.update_editor(|editor, cx| {
3968 editor.backspace(&Backspace, cx);
3969 });
3970 cx.assert_editor_state(
3971 &r#"
3972 <body>ˇ
3973 <script>
3974 var x = 1;ˇ
3975 </script>
3976 </body>ˇ
3977 "#
3978 .unindent(),
3979 );
3980
3981 // Block comments autoclose in JavaScript, but not HTML.
3982 cx.update_editor(|editor, cx| {
3983 editor.handle_input("/", cx);
3984 editor.handle_input("*", cx);
3985 });
3986 cx.assert_editor_state(
3987 &r#"
3988 <body>/*ˇ
3989 <script>
3990 var x = 1;/*ˇ */
3991 </script>
3992 </body>/*ˇ
3993 "#
3994 .unindent(),
3995 );
3996}
3997
3998#[gpui::test]
3999async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
4000 init_test(cx, |_| {});
4001
4002 let mut cx = EditorTestContext::new(cx).await;
4003
4004 let rust_language = Arc::new(
4005 Language::new(
4006 LanguageConfig {
4007 name: "Rust".into(),
4008 brackets: serde_json::from_value(json!([
4009 { "start": "{", "end": "}", "close": true, "newline": true },
4010 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
4011 ]))
4012 .unwrap(),
4013 autoclose_before: "})]>".into(),
4014 ..Default::default()
4015 },
4016 Some(tree_sitter_rust::language()),
4017 )
4018 .with_override_query("(string_literal) @string")
4019 .unwrap(),
4020 );
4021
4022 let registry = Arc::new(LanguageRegistry::test());
4023 registry.add(rust_language.clone());
4024
4025 cx.update_buffer(|buffer, cx| {
4026 buffer.set_language_registry(registry);
4027 buffer.set_language(Some(rust_language), cx);
4028 });
4029
4030 cx.set_state(
4031 &r#"
4032 let x = ˇ
4033 "#
4034 .unindent(),
4035 );
4036
4037 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
4038 cx.update_editor(|editor, cx| {
4039 editor.handle_input("\"", cx);
4040 });
4041 cx.assert_editor_state(
4042 &r#"
4043 let x = "ˇ"
4044 "#
4045 .unindent(),
4046 );
4047
4048 // Inserting another quotation mark. The cursor moves across the existing
4049 // automatically-inserted quotation mark.
4050 cx.update_editor(|editor, cx| {
4051 editor.handle_input("\"", cx);
4052 });
4053 cx.assert_editor_state(
4054 &r#"
4055 let x = ""ˇ
4056 "#
4057 .unindent(),
4058 );
4059
4060 // Reset
4061 cx.set_state(
4062 &r#"
4063 let x = ˇ
4064 "#
4065 .unindent(),
4066 );
4067
4068 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
4069 cx.update_editor(|editor, cx| {
4070 editor.handle_input("\"", cx);
4071 editor.handle_input(" ", cx);
4072 editor.move_left(&Default::default(), cx);
4073 editor.handle_input("\\", cx);
4074 editor.handle_input("\"", cx);
4075 });
4076 cx.assert_editor_state(
4077 &r#"
4078 let x = "\"ˇ "
4079 "#
4080 .unindent(),
4081 );
4082
4083 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
4084 // mark. Nothing is inserted.
4085 cx.update_editor(|editor, cx| {
4086 editor.move_right(&Default::default(), cx);
4087 editor.handle_input("\"", cx);
4088 });
4089 cx.assert_editor_state(
4090 &r#"
4091 let x = "\" "ˇ
4092 "#
4093 .unindent(),
4094 );
4095}
4096
4097#[gpui::test]
4098async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
4099 init_test(cx, |_| {});
4100
4101 let language = Arc::new(Language::new(
4102 LanguageConfig {
4103 brackets: BracketPairConfig {
4104 pairs: vec![
4105 BracketPair {
4106 start: "{".to_string(),
4107 end: "}".to_string(),
4108 close: true,
4109 newline: true,
4110 },
4111 BracketPair {
4112 start: "/* ".to_string(),
4113 end: "*/".to_string(),
4114 close: true,
4115 ..Default::default()
4116 },
4117 ],
4118 ..Default::default()
4119 },
4120 ..Default::default()
4121 },
4122 Some(tree_sitter_rust::language()),
4123 ));
4124
4125 let text = r#"
4126 a
4127 b
4128 c
4129 "#
4130 .unindent();
4131
4132 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
4133 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
4134 let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
4135 view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
4136 .await;
4137
4138 view.update(cx, |view, cx| {
4139 view.change_selections(None, cx, |s| {
4140 s.select_display_ranges([
4141 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
4142 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
4143 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1),
4144 ])
4145 });
4146
4147 view.handle_input("{", cx);
4148 view.handle_input("{", cx);
4149 view.handle_input("{", cx);
4150 assert_eq!(
4151 view.text(cx),
4152 "
4153 {{{a}}}
4154 {{{b}}}
4155 {{{c}}}
4156 "
4157 .unindent()
4158 );
4159 assert_eq!(
4160 view.selections.display_ranges(cx),
4161 [
4162 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 4),
4163 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 4),
4164 DisplayPoint::new(2, 3)..DisplayPoint::new(2, 4)
4165 ]
4166 );
4167
4168 view.undo(&Undo, cx);
4169 view.undo(&Undo, cx);
4170 view.undo(&Undo, cx);
4171 assert_eq!(
4172 view.text(cx),
4173 "
4174 a
4175 b
4176 c
4177 "
4178 .unindent()
4179 );
4180 assert_eq!(
4181 view.selections.display_ranges(cx),
4182 [
4183 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
4184 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
4185 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1)
4186 ]
4187 );
4188
4189 // Ensure inserting the first character of a multi-byte bracket pair
4190 // doesn't surround the selections with the bracket.
4191 view.handle_input("/", cx);
4192 assert_eq!(
4193 view.text(cx),
4194 "
4195 /
4196 /
4197 /
4198 "
4199 .unindent()
4200 );
4201 assert_eq!(
4202 view.selections.display_ranges(cx),
4203 [
4204 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
4205 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
4206 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1)
4207 ]
4208 );
4209
4210 view.undo(&Undo, cx);
4211 assert_eq!(
4212 view.text(cx),
4213 "
4214 a
4215 b
4216 c
4217 "
4218 .unindent()
4219 );
4220 assert_eq!(
4221 view.selections.display_ranges(cx),
4222 [
4223 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
4224 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
4225 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1)
4226 ]
4227 );
4228
4229 // Ensure inserting the last character of a multi-byte bracket pair
4230 // doesn't surround the selections with the bracket.
4231 view.handle_input("*", cx);
4232 assert_eq!(
4233 view.text(cx),
4234 "
4235 *
4236 *
4237 *
4238 "
4239 .unindent()
4240 );
4241 assert_eq!(
4242 view.selections.display_ranges(cx),
4243 [
4244 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
4245 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
4246 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1)
4247 ]
4248 );
4249 });
4250}
4251
4252#[gpui::test]
4253async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
4254 init_test(cx, |_| {});
4255
4256 let language = Arc::new(Language::new(
4257 LanguageConfig {
4258 brackets: BracketPairConfig {
4259 pairs: vec![BracketPair {
4260 start: "{".to_string(),
4261 end: "}".to_string(),
4262 close: true,
4263 newline: true,
4264 }],
4265 ..Default::default()
4266 },
4267 autoclose_before: "}".to_string(),
4268 ..Default::default()
4269 },
4270 Some(tree_sitter_rust::language()),
4271 ));
4272
4273 let text = r#"
4274 a
4275 b
4276 c
4277 "#
4278 .unindent();
4279
4280 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
4281 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
4282 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
4283 editor
4284 .condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
4285 .await;
4286
4287 editor.update(cx, |editor, cx| {
4288 editor.change_selections(None, cx, |s| {
4289 s.select_ranges([
4290 Point::new(0, 1)..Point::new(0, 1),
4291 Point::new(1, 1)..Point::new(1, 1),
4292 Point::new(2, 1)..Point::new(2, 1),
4293 ])
4294 });
4295
4296 editor.handle_input("{", cx);
4297 editor.handle_input("{", cx);
4298 editor.handle_input("_", cx);
4299 assert_eq!(
4300 editor.text(cx),
4301 "
4302 a{{_}}
4303 b{{_}}
4304 c{{_}}
4305 "
4306 .unindent()
4307 );
4308 assert_eq!(
4309 editor.selections.ranges::<Point>(cx),
4310 [
4311 Point::new(0, 4)..Point::new(0, 4),
4312 Point::new(1, 4)..Point::new(1, 4),
4313 Point::new(2, 4)..Point::new(2, 4)
4314 ]
4315 );
4316
4317 editor.backspace(&Default::default(), cx);
4318 editor.backspace(&Default::default(), cx);
4319 assert_eq!(
4320 editor.text(cx),
4321 "
4322 a{}
4323 b{}
4324 c{}
4325 "
4326 .unindent()
4327 );
4328 assert_eq!(
4329 editor.selections.ranges::<Point>(cx),
4330 [
4331 Point::new(0, 2)..Point::new(0, 2),
4332 Point::new(1, 2)..Point::new(1, 2),
4333 Point::new(2, 2)..Point::new(2, 2)
4334 ]
4335 );
4336
4337 editor.delete_to_previous_word_start(&Default::default(), cx);
4338 assert_eq!(
4339 editor.text(cx),
4340 "
4341 a
4342 b
4343 c
4344 "
4345 .unindent()
4346 );
4347 assert_eq!(
4348 editor.selections.ranges::<Point>(cx),
4349 [
4350 Point::new(0, 1)..Point::new(0, 1),
4351 Point::new(1, 1)..Point::new(1, 1),
4352 Point::new(2, 1)..Point::new(2, 1)
4353 ]
4354 );
4355 });
4356}
4357
4358#[gpui::test]
4359async fn test_snippets(cx: &mut gpui::TestAppContext) {
4360 init_test(cx, |_| {});
4361
4362 let (text, insertion_ranges) = marked_text_ranges(
4363 indoc! {"
4364 a.ˇ b
4365 a.ˇ b
4366 a.ˇ b
4367 "},
4368 false,
4369 );
4370
4371 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
4372 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
4373
4374 editor.update(cx, |editor, cx| {
4375 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
4376
4377 editor
4378 .insert_snippet(&insertion_ranges, snippet, cx)
4379 .unwrap();
4380
4381 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
4382 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
4383 assert_eq!(editor.text(cx), expected_text);
4384 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
4385 }
4386
4387 assert(
4388 editor,
4389 cx,
4390 indoc! {"
4391 a.f(«one», two, «three») b
4392 a.f(«one», two, «three») b
4393 a.f(«one», two, «three») b
4394 "},
4395 );
4396
4397 // Can't move earlier than the first tab stop
4398 assert!(!editor.move_to_prev_snippet_tabstop(cx));
4399 assert(
4400 editor,
4401 cx,
4402 indoc! {"
4403 a.f(«one», two, «three») b
4404 a.f(«one», two, «three») b
4405 a.f(«one», two, «three») b
4406 "},
4407 );
4408
4409 assert!(editor.move_to_next_snippet_tabstop(cx));
4410 assert(
4411 editor,
4412 cx,
4413 indoc! {"
4414 a.f(one, «two», three) b
4415 a.f(one, «two», three) b
4416 a.f(one, «two», three) b
4417 "},
4418 );
4419
4420 editor.move_to_prev_snippet_tabstop(cx);
4421 assert(
4422 editor,
4423 cx,
4424 indoc! {"
4425 a.f(«one», two, «three») b
4426 a.f(«one», two, «three») b
4427 a.f(«one», two, «three») b
4428 "},
4429 );
4430
4431 assert!(editor.move_to_next_snippet_tabstop(cx));
4432 assert(
4433 editor,
4434 cx,
4435 indoc! {"
4436 a.f(one, «two», three) b
4437 a.f(one, «two», three) b
4438 a.f(one, «two», three) b
4439 "},
4440 );
4441 assert!(editor.move_to_next_snippet_tabstop(cx));
4442 assert(
4443 editor,
4444 cx,
4445 indoc! {"
4446 a.f(one, two, three)ˇ b
4447 a.f(one, two, three)ˇ b
4448 a.f(one, two, three)ˇ b
4449 "},
4450 );
4451
4452 // As soon as the last tab stop is reached, snippet state is gone
4453 editor.move_to_prev_snippet_tabstop(cx);
4454 assert(
4455 editor,
4456 cx,
4457 indoc! {"
4458 a.f(one, two, three)ˇ b
4459 a.f(one, two, three)ˇ b
4460 a.f(one, two, three)ˇ b
4461 "},
4462 );
4463 });
4464}
4465
4466#[gpui::test]
4467async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
4468 init_test(cx, |_| {});
4469
4470 let mut language = Language::new(
4471 LanguageConfig {
4472 name: "Rust".into(),
4473 path_suffixes: vec!["rs".to_string()],
4474 ..Default::default()
4475 },
4476 Some(tree_sitter_rust::language()),
4477 );
4478 let mut fake_servers = language
4479 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
4480 capabilities: lsp::ServerCapabilities {
4481 document_formatting_provider: Some(lsp::OneOf::Left(true)),
4482 ..Default::default()
4483 },
4484 ..Default::default()
4485 }))
4486 .await;
4487
4488 let fs = FakeFs::new(cx.background());
4489 fs.insert_file("/file.rs", Default::default()).await;
4490
4491 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
4492 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
4493 let buffer = project
4494 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
4495 .await
4496 .unwrap();
4497
4498 cx.foreground().start_waiting();
4499 let fake_server = fake_servers.next().await.unwrap();
4500
4501 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
4502 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
4503 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
4504 assert!(cx.read(|cx| editor.is_dirty(cx)));
4505
4506 let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
4507 fake_server
4508 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
4509 assert_eq!(
4510 params.text_document.uri,
4511 lsp::Url::from_file_path("/file.rs").unwrap()
4512 );
4513 assert_eq!(params.options.tab_size, 4);
4514 Ok(Some(vec![lsp::TextEdit::new(
4515 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
4516 ", ".to_string(),
4517 )]))
4518 })
4519 .next()
4520 .await;
4521 cx.foreground().start_waiting();
4522 save.await.unwrap();
4523 assert_eq!(
4524 editor.read_with(cx, |editor, cx| editor.text(cx)),
4525 "one, two\nthree\n"
4526 );
4527 assert!(!cx.read(|cx| editor.is_dirty(cx)));
4528
4529 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
4530 assert!(cx.read(|cx| editor.is_dirty(cx)));
4531
4532 // Ensure we can still save even if formatting hangs.
4533 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
4534 assert_eq!(
4535 params.text_document.uri,
4536 lsp::Url::from_file_path("/file.rs").unwrap()
4537 );
4538 futures::future::pending::<()>().await;
4539 unreachable!()
4540 });
4541 let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
4542 cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
4543 cx.foreground().start_waiting();
4544 save.await.unwrap();
4545 assert_eq!(
4546 editor.read_with(cx, |editor, cx| editor.text(cx)),
4547 "one\ntwo\nthree\n"
4548 );
4549 assert!(!cx.read(|cx| editor.is_dirty(cx)));
4550
4551 // Set rust language override and assert overridden tabsize is sent to language server
4552 update_test_language_settings(cx, |settings| {
4553 settings.languages.insert(
4554 "Rust".into(),
4555 LanguageSettingsContent {
4556 tab_size: NonZeroU32::new(8),
4557 ..Default::default()
4558 },
4559 );
4560 });
4561
4562 let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
4563 fake_server
4564 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
4565 assert_eq!(
4566 params.text_document.uri,
4567 lsp::Url::from_file_path("/file.rs").unwrap()
4568 );
4569 assert_eq!(params.options.tab_size, 8);
4570 Ok(Some(vec![]))
4571 })
4572 .next()
4573 .await;
4574 cx.foreground().start_waiting();
4575 save.await.unwrap();
4576}
4577
4578#[gpui::test]
4579async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
4580 init_test(cx, |_| {});
4581
4582 let mut language = Language::new(
4583 LanguageConfig {
4584 name: "Rust".into(),
4585 path_suffixes: vec!["rs".to_string()],
4586 ..Default::default()
4587 },
4588 Some(tree_sitter_rust::language()),
4589 );
4590 let mut fake_servers = language
4591 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
4592 capabilities: lsp::ServerCapabilities {
4593 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
4594 ..Default::default()
4595 },
4596 ..Default::default()
4597 }))
4598 .await;
4599
4600 let fs = FakeFs::new(cx.background());
4601 fs.insert_file("/file.rs", Default::default()).await;
4602
4603 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
4604 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
4605 let buffer = project
4606 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
4607 .await
4608 .unwrap();
4609
4610 cx.foreground().start_waiting();
4611 let fake_server = fake_servers.next().await.unwrap();
4612
4613 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
4614 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
4615 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
4616 assert!(cx.read(|cx| editor.is_dirty(cx)));
4617
4618 let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
4619 fake_server
4620 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
4621 assert_eq!(
4622 params.text_document.uri,
4623 lsp::Url::from_file_path("/file.rs").unwrap()
4624 );
4625 assert_eq!(params.options.tab_size, 4);
4626 Ok(Some(vec![lsp::TextEdit::new(
4627 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
4628 ", ".to_string(),
4629 )]))
4630 })
4631 .next()
4632 .await;
4633 cx.foreground().start_waiting();
4634 save.await.unwrap();
4635 assert_eq!(
4636 editor.read_with(cx, |editor, cx| editor.text(cx)),
4637 "one, two\nthree\n"
4638 );
4639 assert!(!cx.read(|cx| editor.is_dirty(cx)));
4640
4641 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
4642 assert!(cx.read(|cx| editor.is_dirty(cx)));
4643
4644 // Ensure we can still save even if formatting hangs.
4645 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
4646 move |params, _| async move {
4647 assert_eq!(
4648 params.text_document.uri,
4649 lsp::Url::from_file_path("/file.rs").unwrap()
4650 );
4651 futures::future::pending::<()>().await;
4652 unreachable!()
4653 },
4654 );
4655 let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
4656 cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
4657 cx.foreground().start_waiting();
4658 save.await.unwrap();
4659 assert_eq!(
4660 editor.read_with(cx, |editor, cx| editor.text(cx)),
4661 "one\ntwo\nthree\n"
4662 );
4663 assert!(!cx.read(|cx| editor.is_dirty(cx)));
4664
4665 // Set rust language override and assert overridden tabsize is sent to language server
4666 update_test_language_settings(cx, |settings| {
4667 settings.languages.insert(
4668 "Rust".into(),
4669 LanguageSettingsContent {
4670 tab_size: NonZeroU32::new(8),
4671 ..Default::default()
4672 },
4673 );
4674 });
4675
4676 let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
4677 fake_server
4678 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
4679 assert_eq!(
4680 params.text_document.uri,
4681 lsp::Url::from_file_path("/file.rs").unwrap()
4682 );
4683 assert_eq!(params.options.tab_size, 8);
4684 Ok(Some(vec![]))
4685 })
4686 .next()
4687 .await;
4688 cx.foreground().start_waiting();
4689 save.await.unwrap();
4690}
4691
4692#[gpui::test]
4693async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
4694 init_test(cx, |_| {});
4695
4696 let mut language = Language::new(
4697 LanguageConfig {
4698 name: "Rust".into(),
4699 path_suffixes: vec!["rs".to_string()],
4700 ..Default::default()
4701 },
4702 Some(tree_sitter_rust::language()),
4703 );
4704 let mut fake_servers = language
4705 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
4706 capabilities: lsp::ServerCapabilities {
4707 document_formatting_provider: Some(lsp::OneOf::Left(true)),
4708 ..Default::default()
4709 },
4710 ..Default::default()
4711 }))
4712 .await;
4713
4714 let fs = FakeFs::new(cx.background());
4715 fs.insert_file("/file.rs", Default::default()).await;
4716
4717 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
4718 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
4719 let buffer = project
4720 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
4721 .await
4722 .unwrap();
4723
4724 cx.foreground().start_waiting();
4725 let fake_server = fake_servers.next().await.unwrap();
4726
4727 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
4728 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
4729 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
4730
4731 let format = editor.update(cx, |editor, cx| {
4732 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
4733 });
4734 fake_server
4735 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
4736 assert_eq!(
4737 params.text_document.uri,
4738 lsp::Url::from_file_path("/file.rs").unwrap()
4739 );
4740 assert_eq!(params.options.tab_size, 4);
4741 Ok(Some(vec![lsp::TextEdit::new(
4742 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
4743 ", ".to_string(),
4744 )]))
4745 })
4746 .next()
4747 .await;
4748 cx.foreground().start_waiting();
4749 format.await.unwrap();
4750 assert_eq!(
4751 editor.read_with(cx, |editor, cx| editor.text(cx)),
4752 "one, two\nthree\n"
4753 );
4754
4755 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
4756 // Ensure we don't lock if formatting hangs.
4757 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
4758 assert_eq!(
4759 params.text_document.uri,
4760 lsp::Url::from_file_path("/file.rs").unwrap()
4761 );
4762 futures::future::pending::<()>().await;
4763 unreachable!()
4764 });
4765 let format = editor.update(cx, |editor, cx| {
4766 editor.perform_format(project, FormatTrigger::Manual, cx)
4767 });
4768 cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
4769 cx.foreground().start_waiting();
4770 format.await.unwrap();
4771 assert_eq!(
4772 editor.read_with(cx, |editor, cx| editor.text(cx)),
4773 "one\ntwo\nthree\n"
4774 );
4775}
4776
4777#[gpui::test]
4778async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
4779 init_test(cx, |_| {});
4780
4781 let mut cx = EditorLspTestContext::new_rust(
4782 lsp::ServerCapabilities {
4783 document_formatting_provider: Some(lsp::OneOf::Left(true)),
4784 ..Default::default()
4785 },
4786 cx,
4787 )
4788 .await;
4789
4790 cx.set_state(indoc! {"
4791 one.twoˇ
4792 "});
4793
4794 // The format request takes a long time. When it completes, it inserts
4795 // a newline and an indent before the `.`
4796 cx.lsp
4797 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
4798 let executor = cx.background();
4799 async move {
4800 executor.timer(Duration::from_millis(100)).await;
4801 Ok(Some(vec![lsp::TextEdit {
4802 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
4803 new_text: "\n ".into(),
4804 }]))
4805 }
4806 });
4807
4808 // Submit a format request.
4809 let format_1 = cx
4810 .update_editor(|editor, cx| editor.format(&Format, cx))
4811 .unwrap();
4812 cx.foreground().run_until_parked();
4813
4814 // Submit a second format request.
4815 let format_2 = cx
4816 .update_editor(|editor, cx| editor.format(&Format, cx))
4817 .unwrap();
4818 cx.foreground().run_until_parked();
4819
4820 // Wait for both format requests to complete
4821 cx.foreground().advance_clock(Duration::from_millis(200));
4822 cx.foreground().start_waiting();
4823 format_1.await.unwrap();
4824 cx.foreground().start_waiting();
4825 format_2.await.unwrap();
4826
4827 // The formatting edits only happens once.
4828 cx.assert_editor_state(indoc! {"
4829 one
4830 .twoˇ
4831 "});
4832}
4833
4834#[gpui::test]
4835async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
4836 init_test(cx, |_| {});
4837
4838 let mut cx = EditorLspTestContext::new_rust(
4839 lsp::ServerCapabilities {
4840 document_formatting_provider: Some(lsp::OneOf::Left(true)),
4841 ..Default::default()
4842 },
4843 cx,
4844 )
4845 .await;
4846
4847 // Set up a buffer white some trailing whitespace and no trailing newline.
4848 cx.set_state(
4849 &[
4850 "one ", //
4851 "twoˇ", //
4852 "three ", //
4853 "four", //
4854 ]
4855 .join("\n"),
4856 );
4857
4858 // Submit a format request.
4859 let format = cx
4860 .update_editor(|editor, cx| editor.format(&Format, cx))
4861 .unwrap();
4862
4863 // Record which buffer changes have been sent to the language server
4864 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
4865 cx.lsp
4866 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
4867 let buffer_changes = buffer_changes.clone();
4868 move |params, _| {
4869 buffer_changes.lock().extend(
4870 params
4871 .content_changes
4872 .into_iter()
4873 .map(|e| (e.range.unwrap(), e.text)),
4874 );
4875 }
4876 });
4877
4878 // Handle formatting requests to the language server.
4879 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
4880 let buffer_changes = buffer_changes.clone();
4881 move |_, _| {
4882 // When formatting is requested, trailing whitespace has already been stripped,
4883 // and the trailing newline has already been added.
4884 assert_eq!(
4885 &buffer_changes.lock()[1..],
4886 &[
4887 (
4888 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
4889 "".into()
4890 ),
4891 (
4892 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
4893 "".into()
4894 ),
4895 (
4896 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
4897 "\n".into()
4898 ),
4899 ]
4900 );
4901
4902 // Insert blank lines between each line of the buffer.
4903 async move {
4904 Ok(Some(vec![
4905 lsp::TextEdit {
4906 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
4907 new_text: "\n".into(),
4908 },
4909 lsp::TextEdit {
4910 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
4911 new_text: "\n".into(),
4912 },
4913 ]))
4914 }
4915 }
4916 });
4917
4918 // After formatting the buffer, the trailing whitespace is stripped,
4919 // a newline is appended, and the edits provided by the language server
4920 // have been applied.
4921 format.await.unwrap();
4922 cx.assert_editor_state(
4923 &[
4924 "one", //
4925 "", //
4926 "twoˇ", //
4927 "", //
4928 "three", //
4929 "four", //
4930 "", //
4931 ]
4932 .join("\n"),
4933 );
4934
4935 // Undoing the formatting undoes the trailing whitespace removal, the
4936 // trailing newline, and the LSP edits.
4937 cx.update_buffer(|buffer, cx| buffer.undo(cx));
4938 cx.assert_editor_state(
4939 &[
4940 "one ", //
4941 "twoˇ", //
4942 "three ", //
4943 "four", //
4944 ]
4945 .join("\n"),
4946 );
4947}
4948
4949#[gpui::test]
4950async fn test_completion(cx: &mut gpui::TestAppContext) {
4951 init_test(cx, |_| {});
4952
4953 let mut cx = EditorLspTestContext::new_rust(
4954 lsp::ServerCapabilities {
4955 completion_provider: Some(lsp::CompletionOptions {
4956 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
4957 ..Default::default()
4958 }),
4959 ..Default::default()
4960 },
4961 cx,
4962 )
4963 .await;
4964
4965 cx.set_state(indoc! {"
4966 oneˇ
4967 two
4968 three
4969 "});
4970 cx.simulate_keystroke(".");
4971 handle_completion_request(
4972 &mut cx,
4973 indoc! {"
4974 one.|<>
4975 two
4976 three
4977 "},
4978 vec!["first_completion", "second_completion"],
4979 )
4980 .await;
4981 cx.condition(|editor, _| editor.context_menu_visible())
4982 .await;
4983 let apply_additional_edits = cx.update_editor(|editor, cx| {
4984 editor.move_down(&MoveDown, cx);
4985 editor
4986 .confirm_completion(&ConfirmCompletion::default(), cx)
4987 .unwrap()
4988 });
4989 cx.assert_editor_state(indoc! {"
4990 one.second_completionˇ
4991 two
4992 three
4993 "});
4994
4995 handle_resolve_completion_request(
4996 &mut cx,
4997 Some(vec![
4998 (
4999 //This overlaps with the primary completion edit which is
5000 //misbehavior from the LSP spec, test that we filter it out
5001 indoc! {"
5002 one.second_ˇcompletion
5003 two
5004 threeˇ
5005 "},
5006 "overlapping additional edit",
5007 ),
5008 (
5009 indoc! {"
5010 one.second_completion
5011 two
5012 threeˇ
5013 "},
5014 "\nadditional edit",
5015 ),
5016 ]),
5017 )
5018 .await;
5019 apply_additional_edits.await.unwrap();
5020 cx.assert_editor_state(indoc! {"
5021 one.second_completionˇ
5022 two
5023 three
5024 additional edit
5025 "});
5026
5027 cx.set_state(indoc! {"
5028 one.second_completion
5029 twoˇ
5030 threeˇ
5031 additional edit
5032 "});
5033 cx.simulate_keystroke(" ");
5034 assert!(cx.editor(|e, _| e.context_menu.is_none()));
5035 cx.simulate_keystroke("s");
5036 assert!(cx.editor(|e, _| e.context_menu.is_none()));
5037
5038 cx.assert_editor_state(indoc! {"
5039 one.second_completion
5040 two sˇ
5041 three sˇ
5042 additional edit
5043 "});
5044 handle_completion_request(
5045 &mut cx,
5046 indoc! {"
5047 one.second_completion
5048 two s
5049 three <s|>
5050 additional edit
5051 "},
5052 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
5053 )
5054 .await;
5055 cx.condition(|editor, _| editor.context_menu_visible())
5056 .await;
5057
5058 cx.simulate_keystroke("i");
5059
5060 handle_completion_request(
5061 &mut cx,
5062 indoc! {"
5063 one.second_completion
5064 two si
5065 three <si|>
5066 additional edit
5067 "},
5068 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
5069 )
5070 .await;
5071 cx.condition(|editor, _| editor.context_menu_visible())
5072 .await;
5073
5074 let apply_additional_edits = cx.update_editor(|editor, cx| {
5075 editor
5076 .confirm_completion(&ConfirmCompletion::default(), cx)
5077 .unwrap()
5078 });
5079 cx.assert_editor_state(indoc! {"
5080 one.second_completion
5081 two sixth_completionˇ
5082 three sixth_completionˇ
5083 additional edit
5084 "});
5085
5086 handle_resolve_completion_request(&mut cx, None).await;
5087 apply_additional_edits.await.unwrap();
5088
5089 cx.update(|cx| {
5090 cx.update_global::<SettingsStore, _, _>(|settings, cx| {
5091 settings.update_user_settings::<EditorSettings>(cx, |settings| {
5092 settings.show_completions_on_input = Some(false);
5093 });
5094 })
5095 });
5096 cx.set_state("editorˇ");
5097 cx.simulate_keystroke(".");
5098 assert!(cx.editor(|e, _| e.context_menu.is_none()));
5099 cx.simulate_keystroke("c");
5100 cx.simulate_keystroke("l");
5101 cx.simulate_keystroke("o");
5102 cx.assert_editor_state("editor.cloˇ");
5103 assert!(cx.editor(|e, _| e.context_menu.is_none()));
5104 cx.update_editor(|editor, cx| {
5105 editor.show_completions(&ShowCompletions, cx);
5106 });
5107 handle_completion_request(&mut cx, "editor.<clo|>", vec!["close", "clobber"]).await;
5108 cx.condition(|editor, _| editor.context_menu_visible())
5109 .await;
5110 let apply_additional_edits = cx.update_editor(|editor, cx| {
5111 editor
5112 .confirm_completion(&ConfirmCompletion::default(), cx)
5113 .unwrap()
5114 });
5115 cx.assert_editor_state("editor.closeˇ");
5116 handle_resolve_completion_request(&mut cx, None).await;
5117 apply_additional_edits.await.unwrap();
5118}
5119
5120#[gpui::test]
5121async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
5122 init_test(cx, |_| {});
5123 let mut cx = EditorTestContext::new(cx).await;
5124 let language = Arc::new(Language::new(
5125 LanguageConfig {
5126 line_comment: Some("// ".into()),
5127 ..Default::default()
5128 },
5129 Some(tree_sitter_rust::language()),
5130 ));
5131 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
5132
5133 // If multiple selections intersect a line, the line is only toggled once.
5134 cx.set_state(indoc! {"
5135 fn a() {
5136 «//b();
5137 ˇ»// «c();
5138 //ˇ» d();
5139 }
5140 "});
5141
5142 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5143
5144 cx.assert_editor_state(indoc! {"
5145 fn a() {
5146 «b();
5147 c();
5148 ˇ» d();
5149 }
5150 "});
5151
5152 // The comment prefix is inserted at the same column for every line in a
5153 // selection.
5154 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5155
5156 cx.assert_editor_state(indoc! {"
5157 fn a() {
5158 // «b();
5159 // c();
5160 ˇ»// d();
5161 }
5162 "});
5163
5164 // If a selection ends at the beginning of a line, that line is not toggled.
5165 cx.set_selections_state(indoc! {"
5166 fn a() {
5167 // b();
5168 «// c();
5169 ˇ» // d();
5170 }
5171 "});
5172
5173 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5174
5175 cx.assert_editor_state(indoc! {"
5176 fn a() {
5177 // b();
5178 «c();
5179 ˇ» // d();
5180 }
5181 "});
5182
5183 // If a selection span a single line and is empty, the line is toggled.
5184 cx.set_state(indoc! {"
5185 fn a() {
5186 a();
5187 b();
5188 ˇ
5189 }
5190 "});
5191
5192 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5193
5194 cx.assert_editor_state(indoc! {"
5195 fn a() {
5196 a();
5197 b();
5198 //•ˇ
5199 }
5200 "});
5201
5202 // If a selection span multiple lines, empty lines are not toggled.
5203 cx.set_state(indoc! {"
5204 fn a() {
5205 «a();
5206
5207 c();ˇ»
5208 }
5209 "});
5210
5211 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5212
5213 cx.assert_editor_state(indoc! {"
5214 fn a() {
5215 // «a();
5216
5217 // c();ˇ»
5218 }
5219 "});
5220}
5221
5222#[gpui::test]
5223async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
5224 init_test(cx, |_| {});
5225
5226 let language = Arc::new(Language::new(
5227 LanguageConfig {
5228 line_comment: Some("// ".into()),
5229 ..Default::default()
5230 },
5231 Some(tree_sitter_rust::language()),
5232 ));
5233
5234 let registry = Arc::new(LanguageRegistry::test());
5235 registry.add(language.clone());
5236
5237 let mut cx = EditorTestContext::new(cx).await;
5238 cx.update_buffer(|buffer, cx| {
5239 buffer.set_language_registry(registry);
5240 buffer.set_language(Some(language), cx);
5241 });
5242
5243 let toggle_comments = &ToggleComments {
5244 advance_downwards: true,
5245 };
5246
5247 // Single cursor on one line -> advance
5248 // Cursor moves horizontally 3 characters as well on non-blank line
5249 cx.set_state(indoc!(
5250 "fn a() {
5251 ˇdog();
5252 cat();
5253 }"
5254 ));
5255 cx.update_editor(|editor, cx| {
5256 editor.toggle_comments(toggle_comments, cx);
5257 });
5258 cx.assert_editor_state(indoc!(
5259 "fn a() {
5260 // dog();
5261 catˇ();
5262 }"
5263 ));
5264
5265 // Single selection on one line -> don't advance
5266 cx.set_state(indoc!(
5267 "fn a() {
5268 «dog()ˇ»;
5269 cat();
5270 }"
5271 ));
5272 cx.update_editor(|editor, cx| {
5273 editor.toggle_comments(toggle_comments, cx);
5274 });
5275 cx.assert_editor_state(indoc!(
5276 "fn a() {
5277 // «dog()ˇ»;
5278 cat();
5279 }"
5280 ));
5281
5282 // Multiple cursors on one line -> advance
5283 cx.set_state(indoc!(
5284 "fn a() {
5285 ˇdˇog();
5286 cat();
5287 }"
5288 ));
5289 cx.update_editor(|editor, cx| {
5290 editor.toggle_comments(toggle_comments, cx);
5291 });
5292 cx.assert_editor_state(indoc!(
5293 "fn a() {
5294 // dog();
5295 catˇ(ˇ);
5296 }"
5297 ));
5298
5299 // Multiple cursors on one line, with selection -> don't advance
5300 cx.set_state(indoc!(
5301 "fn a() {
5302 ˇdˇog«()ˇ»;
5303 cat();
5304 }"
5305 ));
5306 cx.update_editor(|editor, cx| {
5307 editor.toggle_comments(toggle_comments, cx);
5308 });
5309 cx.assert_editor_state(indoc!(
5310 "fn a() {
5311 // ˇdˇog«()ˇ»;
5312 cat();
5313 }"
5314 ));
5315
5316 // Single cursor on one line -> advance
5317 // Cursor moves to column 0 on blank line
5318 cx.set_state(indoc!(
5319 "fn a() {
5320 ˇdog();
5321
5322 cat();
5323 }"
5324 ));
5325 cx.update_editor(|editor, cx| {
5326 editor.toggle_comments(toggle_comments, cx);
5327 });
5328 cx.assert_editor_state(indoc!(
5329 "fn a() {
5330 // dog();
5331 ˇ
5332 cat();
5333 }"
5334 ));
5335
5336 // Single cursor on one line -> advance
5337 // Cursor starts and ends at column 0
5338 cx.set_state(indoc!(
5339 "fn a() {
5340 ˇ dog();
5341 cat();
5342 }"
5343 ));
5344 cx.update_editor(|editor, cx| {
5345 editor.toggle_comments(toggle_comments, cx);
5346 });
5347 cx.assert_editor_state(indoc!(
5348 "fn a() {
5349 // dog();
5350 ˇ cat();
5351 }"
5352 ));
5353}
5354
5355#[gpui::test]
5356async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
5357 init_test(cx, |_| {});
5358
5359 let mut cx = EditorTestContext::new(cx).await;
5360
5361 let html_language = Arc::new(
5362 Language::new(
5363 LanguageConfig {
5364 name: "HTML".into(),
5365 block_comment: Some(("<!-- ".into(), " -->".into())),
5366 ..Default::default()
5367 },
5368 Some(tree_sitter_html::language()),
5369 )
5370 .with_injection_query(
5371 r#"
5372 (script_element
5373 (raw_text) @content
5374 (#set! "language" "javascript"))
5375 "#,
5376 )
5377 .unwrap(),
5378 );
5379
5380 let javascript_language = Arc::new(Language::new(
5381 LanguageConfig {
5382 name: "JavaScript".into(),
5383 line_comment: Some("// ".into()),
5384 ..Default::default()
5385 },
5386 Some(tree_sitter_typescript::language_tsx()),
5387 ));
5388
5389 let registry = Arc::new(LanguageRegistry::test());
5390 registry.add(html_language.clone());
5391 registry.add(javascript_language.clone());
5392
5393 cx.update_buffer(|buffer, cx| {
5394 buffer.set_language_registry(registry);
5395 buffer.set_language(Some(html_language), cx);
5396 });
5397
5398 // Toggle comments for empty selections
5399 cx.set_state(
5400 &r#"
5401 <p>A</p>ˇ
5402 <p>B</p>ˇ
5403 <p>C</p>ˇ
5404 "#
5405 .unindent(),
5406 );
5407 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5408 cx.assert_editor_state(
5409 &r#"
5410 <!-- <p>A</p>ˇ -->
5411 <!-- <p>B</p>ˇ -->
5412 <!-- <p>C</p>ˇ -->
5413 "#
5414 .unindent(),
5415 );
5416 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5417 cx.assert_editor_state(
5418 &r#"
5419 <p>A</p>ˇ
5420 <p>B</p>ˇ
5421 <p>C</p>ˇ
5422 "#
5423 .unindent(),
5424 );
5425
5426 // Toggle comments for mixture of empty and non-empty selections, where
5427 // multiple selections occupy a given line.
5428 cx.set_state(
5429 &r#"
5430 <p>A«</p>
5431 <p>ˇ»B</p>ˇ
5432 <p>C«</p>
5433 <p>ˇ»D</p>ˇ
5434 "#
5435 .unindent(),
5436 );
5437
5438 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5439 cx.assert_editor_state(
5440 &r#"
5441 <!-- <p>A«</p>
5442 <p>ˇ»B</p>ˇ -->
5443 <!-- <p>C«</p>
5444 <p>ˇ»D</p>ˇ -->
5445 "#
5446 .unindent(),
5447 );
5448 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5449 cx.assert_editor_state(
5450 &r#"
5451 <p>A«</p>
5452 <p>ˇ»B</p>ˇ
5453 <p>C«</p>
5454 <p>ˇ»D</p>ˇ
5455 "#
5456 .unindent(),
5457 );
5458
5459 // Toggle comments when different languages are active for different
5460 // selections.
5461 cx.set_state(
5462 &r#"
5463 ˇ<script>
5464 ˇvar x = new Y();
5465 ˇ</script>
5466 "#
5467 .unindent(),
5468 );
5469 cx.foreground().run_until_parked();
5470 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5471 cx.assert_editor_state(
5472 &r#"
5473 <!-- ˇ<script> -->
5474 // ˇvar x = new Y();
5475 <!-- ˇ</script> -->
5476 "#
5477 .unindent(),
5478 );
5479}
5480
5481#[gpui::test]
5482fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
5483 init_test(cx, |_| {});
5484
5485 let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
5486 let multibuffer = cx.add_model(|cx| {
5487 let mut multibuffer = MultiBuffer::new(0);
5488 multibuffer.push_excerpts(
5489 buffer.clone(),
5490 [
5491 ExcerptRange {
5492 context: Point::new(0, 0)..Point::new(0, 4),
5493 primary: None,
5494 },
5495 ExcerptRange {
5496 context: Point::new(1, 0)..Point::new(1, 4),
5497 primary: None,
5498 },
5499 ],
5500 cx,
5501 );
5502 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
5503 multibuffer
5504 });
5505
5506 let (_, view) = cx.add_window(|cx| build_editor(multibuffer, cx));
5507 view.update(cx, |view, cx| {
5508 assert_eq!(view.text(cx), "aaaa\nbbbb");
5509 view.change_selections(None, cx, |s| {
5510 s.select_ranges([
5511 Point::new(0, 0)..Point::new(0, 0),
5512 Point::new(1, 0)..Point::new(1, 0),
5513 ])
5514 });
5515
5516 view.handle_input("X", cx);
5517 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
5518 assert_eq!(
5519 view.selections.ranges(cx),
5520 [
5521 Point::new(0, 1)..Point::new(0, 1),
5522 Point::new(1, 1)..Point::new(1, 1),
5523 ]
5524 );
5525
5526 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
5527 view.change_selections(None, cx, |s| {
5528 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
5529 });
5530 view.backspace(&Default::default(), cx);
5531 assert_eq!(view.text(cx), "Xa\nbbb");
5532 assert_eq!(
5533 view.selections.ranges(cx),
5534 [Point::new(1, 0)..Point::new(1, 0)]
5535 );
5536
5537 view.change_selections(None, cx, |s| {
5538 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
5539 });
5540 view.backspace(&Default::default(), cx);
5541 assert_eq!(view.text(cx), "X\nbb");
5542 assert_eq!(
5543 view.selections.ranges(cx),
5544 [Point::new(0, 1)..Point::new(0, 1)]
5545 );
5546 });
5547}
5548
5549#[gpui::test]
5550fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
5551 init_test(cx, |_| {});
5552
5553 let markers = vec![('[', ']').into(), ('(', ')').into()];
5554 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
5555 indoc! {"
5556 [aaaa
5557 (bbbb]
5558 cccc)",
5559 },
5560 markers.clone(),
5561 );
5562 let excerpt_ranges = markers.into_iter().map(|marker| {
5563 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
5564 ExcerptRange {
5565 context,
5566 primary: None,
5567 }
5568 });
5569 let buffer = cx.add_model(|cx| Buffer::new(0, initial_text, cx));
5570 let multibuffer = cx.add_model(|cx| {
5571 let mut multibuffer = MultiBuffer::new(0);
5572 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
5573 multibuffer
5574 });
5575
5576 let (_, view) = cx.add_window(|cx| build_editor(multibuffer, cx));
5577 view.update(cx, |view, cx| {
5578 let (expected_text, selection_ranges) = marked_text_ranges(
5579 indoc! {"
5580 aaaa
5581 bˇbbb
5582 bˇbbˇb
5583 cccc"
5584 },
5585 true,
5586 );
5587 assert_eq!(view.text(cx), expected_text);
5588 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
5589
5590 view.handle_input("X", cx);
5591
5592 let (expected_text, expected_selections) = marked_text_ranges(
5593 indoc! {"
5594 aaaa
5595 bXˇbbXb
5596 bXˇbbXˇb
5597 cccc"
5598 },
5599 false,
5600 );
5601 assert_eq!(view.text(cx), expected_text);
5602 assert_eq!(view.selections.ranges(cx), expected_selections);
5603
5604 view.newline(&Newline, cx);
5605 let (expected_text, expected_selections) = marked_text_ranges(
5606 indoc! {"
5607 aaaa
5608 bX
5609 ˇbbX
5610 b
5611 bX
5612 ˇbbX
5613 ˇb
5614 cccc"
5615 },
5616 false,
5617 );
5618 assert_eq!(view.text(cx), expected_text);
5619 assert_eq!(view.selections.ranges(cx), expected_selections);
5620 });
5621}
5622
5623#[gpui::test]
5624fn test_refresh_selections(cx: &mut TestAppContext) {
5625 init_test(cx, |_| {});
5626
5627 let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
5628 let mut excerpt1_id = None;
5629 let multibuffer = cx.add_model(|cx| {
5630 let mut multibuffer = MultiBuffer::new(0);
5631 excerpt1_id = multibuffer
5632 .push_excerpts(
5633 buffer.clone(),
5634 [
5635 ExcerptRange {
5636 context: Point::new(0, 0)..Point::new(1, 4),
5637 primary: None,
5638 },
5639 ExcerptRange {
5640 context: Point::new(1, 0)..Point::new(2, 4),
5641 primary: None,
5642 },
5643 ],
5644 cx,
5645 )
5646 .into_iter()
5647 .next();
5648 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
5649 multibuffer
5650 });
5651
5652 let (_, editor) = cx.add_window(|cx| {
5653 let mut editor = build_editor(multibuffer.clone(), cx);
5654 let snapshot = editor.snapshot(cx);
5655 editor.change_selections(None, cx, |s| {
5656 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
5657 });
5658 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
5659 assert_eq!(
5660 editor.selections.ranges(cx),
5661 [
5662 Point::new(1, 3)..Point::new(1, 3),
5663 Point::new(2, 1)..Point::new(2, 1),
5664 ]
5665 );
5666 editor
5667 });
5668
5669 // Refreshing selections is a no-op when excerpts haven't changed.
5670 editor.update(cx, |editor, cx| {
5671 editor.change_selections(None, cx, |s| s.refresh());
5672 assert_eq!(
5673 editor.selections.ranges(cx),
5674 [
5675 Point::new(1, 3)..Point::new(1, 3),
5676 Point::new(2, 1)..Point::new(2, 1),
5677 ]
5678 );
5679 });
5680
5681 multibuffer.update(cx, |multibuffer, cx| {
5682 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
5683 });
5684 editor.update(cx, |editor, cx| {
5685 // Removing an excerpt causes the first selection to become degenerate.
5686 assert_eq!(
5687 editor.selections.ranges(cx),
5688 [
5689 Point::new(0, 0)..Point::new(0, 0),
5690 Point::new(0, 1)..Point::new(0, 1)
5691 ]
5692 );
5693
5694 // Refreshing selections will relocate the first selection to the original buffer
5695 // location.
5696 editor.change_selections(None, cx, |s| s.refresh());
5697 assert_eq!(
5698 editor.selections.ranges(cx),
5699 [
5700 Point::new(0, 1)..Point::new(0, 1),
5701 Point::new(0, 3)..Point::new(0, 3)
5702 ]
5703 );
5704 assert!(editor.selections.pending_anchor().is_some());
5705 });
5706}
5707
5708#[gpui::test]
5709fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
5710 init_test(cx, |_| {});
5711
5712 let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
5713 let mut excerpt1_id = None;
5714 let multibuffer = cx.add_model(|cx| {
5715 let mut multibuffer = MultiBuffer::new(0);
5716 excerpt1_id = multibuffer
5717 .push_excerpts(
5718 buffer.clone(),
5719 [
5720 ExcerptRange {
5721 context: Point::new(0, 0)..Point::new(1, 4),
5722 primary: None,
5723 },
5724 ExcerptRange {
5725 context: Point::new(1, 0)..Point::new(2, 4),
5726 primary: None,
5727 },
5728 ],
5729 cx,
5730 )
5731 .into_iter()
5732 .next();
5733 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
5734 multibuffer
5735 });
5736
5737 let (_, editor) = cx.add_window(|cx| {
5738 let mut editor = build_editor(multibuffer.clone(), cx);
5739 let snapshot = editor.snapshot(cx);
5740 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
5741 assert_eq!(
5742 editor.selections.ranges(cx),
5743 [Point::new(1, 3)..Point::new(1, 3)]
5744 );
5745 editor
5746 });
5747
5748 multibuffer.update(cx, |multibuffer, cx| {
5749 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
5750 });
5751 editor.update(cx, |editor, cx| {
5752 assert_eq!(
5753 editor.selections.ranges(cx),
5754 [Point::new(0, 0)..Point::new(0, 0)]
5755 );
5756
5757 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
5758 editor.change_selections(None, cx, |s| s.refresh());
5759 assert_eq!(
5760 editor.selections.ranges(cx),
5761 [Point::new(0, 3)..Point::new(0, 3)]
5762 );
5763 assert!(editor.selections.pending_anchor().is_some());
5764 });
5765}
5766
5767#[gpui::test]
5768async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
5769 init_test(cx, |_| {});
5770
5771 let language = Arc::new(
5772 Language::new(
5773 LanguageConfig {
5774 brackets: BracketPairConfig {
5775 pairs: vec![
5776 BracketPair {
5777 start: "{".to_string(),
5778 end: "}".to_string(),
5779 close: true,
5780 newline: true,
5781 },
5782 BracketPair {
5783 start: "/* ".to_string(),
5784 end: " */".to_string(),
5785 close: true,
5786 newline: true,
5787 },
5788 ],
5789 ..Default::default()
5790 },
5791 ..Default::default()
5792 },
5793 Some(tree_sitter_rust::language()),
5794 )
5795 .with_indents_query("")
5796 .unwrap(),
5797 );
5798
5799 let text = concat!(
5800 "{ }\n", //
5801 " x\n", //
5802 " /* */\n", //
5803 "x\n", //
5804 "{{} }\n", //
5805 );
5806
5807 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
5808 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
5809 let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
5810 view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5811 .await;
5812
5813 view.update(cx, |view, cx| {
5814 view.change_selections(None, cx, |s| {
5815 s.select_display_ranges([
5816 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3),
5817 DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
5818 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
5819 ])
5820 });
5821 view.newline(&Newline, cx);
5822
5823 assert_eq!(
5824 view.buffer().read(cx).read(cx).text(),
5825 concat!(
5826 "{ \n", // Suppress rustfmt
5827 "\n", //
5828 "}\n", //
5829 " x\n", //
5830 " /* \n", //
5831 " \n", //
5832 " */\n", //
5833 "x\n", //
5834 "{{} \n", //
5835 "}\n", //
5836 )
5837 );
5838 });
5839}
5840
5841#[gpui::test]
5842fn test_highlighted_ranges(cx: &mut TestAppContext) {
5843 init_test(cx, |_| {});
5844
5845 let (_, editor) = cx.add_window(|cx| {
5846 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
5847 build_editor(buffer.clone(), cx)
5848 });
5849
5850 editor.update(cx, |editor, cx| {
5851 struct Type1;
5852 struct Type2;
5853
5854 let buffer = editor.buffer.read(cx).snapshot(cx);
5855
5856 let anchor_range =
5857 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
5858
5859 editor.highlight_background::<Type1>(
5860 vec![
5861 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
5862 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
5863 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
5864 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
5865 ],
5866 |_| Color::red(),
5867 cx,
5868 );
5869 editor.highlight_background::<Type2>(
5870 vec![
5871 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
5872 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
5873 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
5874 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
5875 ],
5876 |_| Color::green(),
5877 cx,
5878 );
5879
5880 let snapshot = editor.snapshot(cx);
5881 let mut highlighted_ranges = editor.background_highlights_in_range(
5882 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
5883 &snapshot,
5884 theme::current(cx).as_ref(),
5885 );
5886 // Enforce a consistent ordering based on color without relying on the ordering of the
5887 // highlight's `TypeId` which is non-deterministic.
5888 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
5889 assert_eq!(
5890 highlighted_ranges,
5891 &[
5892 (
5893 DisplayPoint::new(3, 2)..DisplayPoint::new(3, 5),
5894 Color::green(),
5895 ),
5896 (
5897 DisplayPoint::new(5, 3)..DisplayPoint::new(5, 6),
5898 Color::green(),
5899 ),
5900 (
5901 DisplayPoint::new(4, 2)..DisplayPoint::new(4, 4),
5902 Color::red(),
5903 ),
5904 (
5905 DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
5906 Color::red(),
5907 ),
5908 ]
5909 );
5910 assert_eq!(
5911 editor.background_highlights_in_range(
5912 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
5913 &snapshot,
5914 theme::current(cx).as_ref(),
5915 ),
5916 &[(
5917 DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
5918 Color::red(),
5919 )]
5920 );
5921 });
5922}
5923
5924#[gpui::test]
5925async fn test_following(cx: &mut gpui::TestAppContext) {
5926 init_test(cx, |_| {});
5927
5928 let fs = FakeFs::new(cx.background());
5929 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
5930
5931 let buffer = project.update(cx, |project, cx| {
5932 let buffer = project
5933 .create_buffer(&sample_text(16, 8, 'a'), None, cx)
5934 .unwrap();
5935 cx.add_model(|cx| MultiBuffer::singleton(buffer, cx))
5936 });
5937 let (_, leader) = cx.add_window(|cx| build_editor(buffer.clone(), cx));
5938 let (_, follower) = cx.update(|cx| {
5939 cx.add_window(
5940 WindowOptions {
5941 bounds: WindowBounds::Fixed(RectF::from_points(vec2f(0., 0.), vec2f(10., 80.))),
5942 ..Default::default()
5943 },
5944 |cx| build_editor(buffer.clone(), cx),
5945 )
5946 });
5947
5948 let is_still_following = Rc::new(RefCell::new(true));
5949 let follower_edit_event_count = Rc::new(RefCell::new(0));
5950 let pending_update = Rc::new(RefCell::new(None));
5951 follower.update(cx, {
5952 let update = pending_update.clone();
5953 let is_still_following = is_still_following.clone();
5954 let follower_edit_event_count = follower_edit_event_count.clone();
5955 |_, cx| {
5956 cx.subscribe(&leader, move |_, leader, event, cx| {
5957 leader
5958 .read(cx)
5959 .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx);
5960 })
5961 .detach();
5962
5963 cx.subscribe(&follower, move |_, _, event, cx| {
5964 if Editor::should_unfollow_on_event(event, cx) {
5965 *is_still_following.borrow_mut() = false;
5966 }
5967 if let Event::BufferEdited = event {
5968 *follower_edit_event_count.borrow_mut() += 1;
5969 }
5970 })
5971 .detach();
5972 }
5973 });
5974
5975 // Update the selections only
5976 leader.update(cx, |leader, cx| {
5977 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
5978 });
5979 follower
5980 .update(cx, |follower, cx| {
5981 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
5982 })
5983 .await
5984 .unwrap();
5985 follower.read_with(cx, |follower, cx| {
5986 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
5987 });
5988 assert_eq!(*is_still_following.borrow(), true);
5989 assert_eq!(*follower_edit_event_count.borrow(), 0);
5990
5991 // Update the scroll position only
5992 leader.update(cx, |leader, cx| {
5993 leader.set_scroll_position(vec2f(1.5, 3.5), cx);
5994 });
5995 follower
5996 .update(cx, |follower, cx| {
5997 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
5998 })
5999 .await
6000 .unwrap();
6001 assert_eq!(
6002 follower.update(cx, |follower, cx| follower.scroll_position(cx)),
6003 vec2f(1.5, 3.5)
6004 );
6005 assert_eq!(*is_still_following.borrow(), true);
6006 assert_eq!(*follower_edit_event_count.borrow(), 0);
6007
6008 // Update the selections and scroll position. The follower's scroll position is updated
6009 // via autoscroll, not via the leader's exact scroll position.
6010 leader.update(cx, |leader, cx| {
6011 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
6012 leader.request_autoscroll(Autoscroll::newest(), cx);
6013 leader.set_scroll_position(vec2f(1.5, 3.5), cx);
6014 });
6015 follower
6016 .update(cx, |follower, cx| {
6017 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6018 })
6019 .await
6020 .unwrap();
6021 follower.update(cx, |follower, cx| {
6022 assert_eq!(follower.scroll_position(cx), vec2f(1.5, 0.0));
6023 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
6024 });
6025 assert_eq!(*is_still_following.borrow(), true);
6026
6027 // Creating a pending selection that precedes another selection
6028 leader.update(cx, |leader, cx| {
6029 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
6030 leader.begin_selection(DisplayPoint::new(0, 0), true, 1, cx);
6031 });
6032 follower
6033 .update(cx, |follower, cx| {
6034 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6035 })
6036 .await
6037 .unwrap();
6038 follower.read_with(cx, |follower, cx| {
6039 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
6040 });
6041 assert_eq!(*is_still_following.borrow(), true);
6042
6043 // Extend the pending selection so that it surrounds another selection
6044 leader.update(cx, |leader, cx| {
6045 leader.extend_selection(DisplayPoint::new(0, 2), 1, cx);
6046 });
6047 follower
6048 .update(cx, |follower, cx| {
6049 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6050 })
6051 .await
6052 .unwrap();
6053 follower.read_with(cx, |follower, cx| {
6054 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
6055 });
6056
6057 // Scrolling locally breaks the follow
6058 follower.update(cx, |follower, cx| {
6059 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
6060 follower.set_scroll_anchor(
6061 ScrollAnchor {
6062 anchor: top_anchor,
6063 offset: vec2f(0.0, 0.5),
6064 },
6065 cx,
6066 );
6067 });
6068 assert_eq!(*is_still_following.borrow(), false);
6069}
6070
6071#[gpui::test]
6072async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
6073 init_test(cx, |_| {});
6074
6075 let fs = FakeFs::new(cx.background());
6076 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6077 let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
6078 let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
6079
6080 let leader = pane.update(cx, |_, cx| {
6081 let multibuffer = cx.add_model(|_| MultiBuffer::new(0));
6082 cx.add_view(|cx| build_editor(multibuffer.clone(), cx))
6083 });
6084
6085 // Start following the editor when it has no excerpts.
6086 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
6087 let follower_1 = cx
6088 .update(|cx| {
6089 Editor::from_state_proto(
6090 pane.clone(),
6091 project.clone(),
6092 ViewId {
6093 creator: Default::default(),
6094 id: 0,
6095 },
6096 &mut state_message,
6097 cx,
6098 )
6099 })
6100 .unwrap()
6101 .await
6102 .unwrap();
6103
6104 let update_message = Rc::new(RefCell::new(None));
6105 follower_1.update(cx, {
6106 let update = update_message.clone();
6107 |_, cx| {
6108 cx.subscribe(&leader, move |_, leader, event, cx| {
6109 leader
6110 .read(cx)
6111 .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx);
6112 })
6113 .detach();
6114 }
6115 });
6116
6117 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
6118 (
6119 project
6120 .create_buffer("abc\ndef\nghi\njkl\n", None, cx)
6121 .unwrap(),
6122 project
6123 .create_buffer("mno\npqr\nstu\nvwx\n", None, cx)
6124 .unwrap(),
6125 )
6126 });
6127
6128 // Insert some excerpts.
6129 leader.update(cx, |leader, cx| {
6130 leader.buffer.update(cx, |multibuffer, cx| {
6131 let excerpt_ids = multibuffer.push_excerpts(
6132 buffer_1.clone(),
6133 [
6134 ExcerptRange {
6135 context: 1..6,
6136 primary: None,
6137 },
6138 ExcerptRange {
6139 context: 12..15,
6140 primary: None,
6141 },
6142 ExcerptRange {
6143 context: 0..3,
6144 primary: None,
6145 },
6146 ],
6147 cx,
6148 );
6149 multibuffer.insert_excerpts_after(
6150 excerpt_ids[0],
6151 buffer_2.clone(),
6152 [
6153 ExcerptRange {
6154 context: 8..12,
6155 primary: None,
6156 },
6157 ExcerptRange {
6158 context: 0..6,
6159 primary: None,
6160 },
6161 ],
6162 cx,
6163 );
6164 });
6165 });
6166
6167 // Apply the update of adding the excerpts.
6168 follower_1
6169 .update(cx, |follower, cx| {
6170 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
6171 })
6172 .await
6173 .unwrap();
6174 assert_eq!(
6175 follower_1.read_with(cx, |editor, cx| editor.text(cx)),
6176 leader.read_with(cx, |editor, cx| editor.text(cx))
6177 );
6178 update_message.borrow_mut().take();
6179
6180 // Start following separately after it already has excerpts.
6181 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
6182 let follower_2 = cx
6183 .update(|cx| {
6184 Editor::from_state_proto(
6185 pane.clone(),
6186 project.clone(),
6187 ViewId {
6188 creator: Default::default(),
6189 id: 0,
6190 },
6191 &mut state_message,
6192 cx,
6193 )
6194 })
6195 .unwrap()
6196 .await
6197 .unwrap();
6198 assert_eq!(
6199 follower_2.read_with(cx, |editor, cx| editor.text(cx)),
6200 leader.read_with(cx, |editor, cx| editor.text(cx))
6201 );
6202
6203 // Remove some excerpts.
6204 leader.update(cx, |leader, cx| {
6205 leader.buffer.update(cx, |multibuffer, cx| {
6206 let excerpt_ids = multibuffer.excerpt_ids();
6207 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
6208 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
6209 });
6210 });
6211
6212 // Apply the update of removing the excerpts.
6213 follower_1
6214 .update(cx, |follower, cx| {
6215 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
6216 })
6217 .await
6218 .unwrap();
6219 follower_2
6220 .update(cx, |follower, cx| {
6221 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
6222 })
6223 .await
6224 .unwrap();
6225 update_message.borrow_mut().take();
6226 assert_eq!(
6227 follower_1.read_with(cx, |editor, cx| editor.text(cx)),
6228 leader.read_with(cx, |editor, cx| editor.text(cx))
6229 );
6230}
6231
6232#[test]
6233fn test_combine_syntax_and_fuzzy_match_highlights() {
6234 let string = "abcdefghijklmnop";
6235 let syntax_ranges = [
6236 (
6237 0..3,
6238 HighlightStyle {
6239 color: Some(Color::red()),
6240 ..Default::default()
6241 },
6242 ),
6243 (
6244 4..8,
6245 HighlightStyle {
6246 color: Some(Color::green()),
6247 ..Default::default()
6248 },
6249 ),
6250 ];
6251 let match_indices = [4, 6, 7, 8];
6252 assert_eq!(
6253 combine_syntax_and_fuzzy_match_highlights(
6254 string,
6255 Default::default(),
6256 syntax_ranges.into_iter(),
6257 &match_indices,
6258 ),
6259 &[
6260 (
6261 0..3,
6262 HighlightStyle {
6263 color: Some(Color::red()),
6264 ..Default::default()
6265 },
6266 ),
6267 (
6268 4..5,
6269 HighlightStyle {
6270 color: Some(Color::green()),
6271 weight: Some(fonts::Weight::BOLD),
6272 ..Default::default()
6273 },
6274 ),
6275 (
6276 5..6,
6277 HighlightStyle {
6278 color: Some(Color::green()),
6279 ..Default::default()
6280 },
6281 ),
6282 (
6283 6..8,
6284 HighlightStyle {
6285 color: Some(Color::green()),
6286 weight: Some(fonts::Weight::BOLD),
6287 ..Default::default()
6288 },
6289 ),
6290 (
6291 8..9,
6292 HighlightStyle {
6293 weight: Some(fonts::Weight::BOLD),
6294 ..Default::default()
6295 },
6296 ),
6297 ]
6298 );
6299}
6300
6301#[gpui::test]
6302async fn go_to_hunk(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
6303 init_test(cx, |_| {});
6304
6305 let mut cx = EditorTestContext::new(cx).await;
6306
6307 let diff_base = r#"
6308 use some::mod;
6309
6310 const A: u32 = 42;
6311
6312 fn main() {
6313 println!("hello");
6314
6315 println!("world");
6316 }
6317 "#
6318 .unindent();
6319
6320 // Edits are modified, removed, modified, added
6321 cx.set_state(
6322 &r#"
6323 use some::modified;
6324
6325 ˇ
6326 fn main() {
6327 println!("hello there");
6328
6329 println!("around the");
6330 println!("world");
6331 }
6332 "#
6333 .unindent(),
6334 );
6335
6336 cx.set_diff_base(Some(&diff_base));
6337 deterministic.run_until_parked();
6338
6339 cx.update_editor(|editor, cx| {
6340 //Wrap around the bottom of the buffer
6341 for _ in 0..3 {
6342 editor.go_to_hunk(&GoToHunk, cx);
6343 }
6344 });
6345
6346 cx.assert_editor_state(
6347 &r#"
6348 ˇuse some::modified;
6349
6350
6351 fn main() {
6352 println!("hello there");
6353
6354 println!("around the");
6355 println!("world");
6356 }
6357 "#
6358 .unindent(),
6359 );
6360
6361 cx.update_editor(|editor, cx| {
6362 //Wrap around the top of the buffer
6363 for _ in 0..2 {
6364 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
6365 }
6366 });
6367
6368 cx.assert_editor_state(
6369 &r#"
6370 use some::modified;
6371
6372
6373 fn main() {
6374 ˇ println!("hello there");
6375
6376 println!("around the");
6377 println!("world");
6378 }
6379 "#
6380 .unindent(),
6381 );
6382
6383 cx.update_editor(|editor, cx| {
6384 editor.fold(&Fold, cx);
6385
6386 //Make sure that the fold only gets one hunk
6387 for _ in 0..4 {
6388 editor.go_to_hunk(&GoToHunk, cx);
6389 }
6390 });
6391
6392 cx.assert_editor_state(
6393 &r#"
6394 ˇuse some::modified;
6395
6396
6397 fn main() {
6398 println!("hello there");
6399
6400 println!("around the");
6401 println!("world");
6402 }
6403 "#
6404 .unindent(),
6405 );
6406}
6407
6408#[test]
6409fn test_split_words() {
6410 fn split<'a>(text: &'a str) -> Vec<&'a str> {
6411 split_words(text).collect()
6412 }
6413
6414 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
6415 assert_eq!(split("hello_world"), &["hello_", "world"]);
6416 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
6417 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
6418 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
6419 assert_eq!(split("helloworld"), &["helloworld"]);
6420}
6421
6422#[gpui::test]
6423async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
6424 init_test(cx, |_| {});
6425
6426 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
6427 let mut assert = |before, after| {
6428 let _state_context = cx.set_state(before);
6429 cx.update_editor(|editor, cx| {
6430 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
6431 });
6432 cx.assert_editor_state(after);
6433 };
6434
6435 // Outside bracket jumps to outside of matching bracket
6436 assert("console.logˇ(var);", "console.log(var)ˇ;");
6437 assert("console.log(var)ˇ;", "console.logˇ(var);");
6438
6439 // Inside bracket jumps to inside of matching bracket
6440 assert("console.log(ˇvar);", "console.log(varˇ);");
6441 assert("console.log(varˇ);", "console.log(ˇvar);");
6442
6443 // When outside a bracket and inside, favor jumping to the inside bracket
6444 assert(
6445 "console.log('foo', [1, 2, 3]ˇ);",
6446 "console.log(ˇ'foo', [1, 2, 3]);",
6447 );
6448 assert(
6449 "console.log(ˇ'foo', [1, 2, 3]);",
6450 "console.log('foo', [1, 2, 3]ˇ);",
6451 );
6452
6453 // Bias forward if two options are equally likely
6454 assert(
6455 "let result = curried_fun()ˇ();",
6456 "let result = curried_fun()()ˇ;",
6457 );
6458
6459 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
6460 assert(
6461 indoc! {"
6462 function test() {
6463 console.log('test')ˇ
6464 }"},
6465 indoc! {"
6466 function test() {
6467 console.logˇ('test')
6468 }"},
6469 );
6470}
6471
6472#[gpui::test(iterations = 10)]
6473async fn test_copilot(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
6474 init_test(cx, |_| {});
6475
6476 let (copilot, copilot_lsp) = Copilot::fake(cx);
6477 cx.update(|cx| cx.set_global(copilot));
6478 let mut cx = EditorLspTestContext::new_rust(
6479 lsp::ServerCapabilities {
6480 completion_provider: Some(lsp::CompletionOptions {
6481 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
6482 ..Default::default()
6483 }),
6484 ..Default::default()
6485 },
6486 cx,
6487 )
6488 .await;
6489
6490 // When inserting, ensure autocompletion is favored over Copilot suggestions.
6491 cx.set_state(indoc! {"
6492 oneˇ
6493 two
6494 three
6495 "});
6496 cx.simulate_keystroke(".");
6497 let _ = handle_completion_request(
6498 &mut cx,
6499 indoc! {"
6500 one.|<>
6501 two
6502 three
6503 "},
6504 vec!["completion_a", "completion_b"],
6505 );
6506 handle_copilot_completion_request(
6507 &copilot_lsp,
6508 vec![copilot::request::Completion {
6509 text: "one.copilot1".into(),
6510 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
6511 ..Default::default()
6512 }],
6513 vec![],
6514 );
6515 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6516 cx.update_editor(|editor, cx| {
6517 assert!(editor.context_menu_visible());
6518 assert!(!editor.has_active_copilot_suggestion(cx));
6519
6520 // Confirming a completion inserts it and hides the context menu, without showing
6521 // the copilot suggestion afterwards.
6522 editor
6523 .confirm_completion(&Default::default(), cx)
6524 .unwrap()
6525 .detach();
6526 assert!(!editor.context_menu_visible());
6527 assert!(!editor.has_active_copilot_suggestion(cx));
6528 assert_eq!(editor.text(cx), "one.completion_a\ntwo\nthree\n");
6529 assert_eq!(editor.display_text(cx), "one.completion_a\ntwo\nthree\n");
6530 });
6531
6532 // Ensure Copilot suggestions are shown right away if no autocompletion is available.
6533 cx.set_state(indoc! {"
6534 oneˇ
6535 two
6536 three
6537 "});
6538 cx.simulate_keystroke(".");
6539 let _ = handle_completion_request(
6540 &mut cx,
6541 indoc! {"
6542 one.|<>
6543 two
6544 three
6545 "},
6546 vec![],
6547 );
6548 handle_copilot_completion_request(
6549 &copilot_lsp,
6550 vec![copilot::request::Completion {
6551 text: "one.copilot1".into(),
6552 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
6553 ..Default::default()
6554 }],
6555 vec![],
6556 );
6557 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6558 cx.update_editor(|editor, cx| {
6559 assert!(!editor.context_menu_visible());
6560 assert!(editor.has_active_copilot_suggestion(cx));
6561 assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
6562 assert_eq!(editor.text(cx), "one.\ntwo\nthree\n");
6563 });
6564
6565 // Reset editor, and ensure autocompletion is still favored over Copilot suggestions.
6566 cx.set_state(indoc! {"
6567 oneˇ
6568 two
6569 three
6570 "});
6571 cx.simulate_keystroke(".");
6572 let _ = handle_completion_request(
6573 &mut cx,
6574 indoc! {"
6575 one.|<>
6576 two
6577 three
6578 "},
6579 vec!["completion_a", "completion_b"],
6580 );
6581 handle_copilot_completion_request(
6582 &copilot_lsp,
6583 vec![copilot::request::Completion {
6584 text: "one.copilot1".into(),
6585 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
6586 ..Default::default()
6587 }],
6588 vec![],
6589 );
6590 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6591 cx.update_editor(|editor, cx| {
6592 assert!(editor.context_menu_visible());
6593 assert!(!editor.has_active_copilot_suggestion(cx));
6594
6595 // When hiding the context menu, the Copilot suggestion becomes visible.
6596 editor.hide_context_menu(cx);
6597 assert!(!editor.context_menu_visible());
6598 assert!(editor.has_active_copilot_suggestion(cx));
6599 assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
6600 assert_eq!(editor.text(cx), "one.\ntwo\nthree\n");
6601 });
6602
6603 // Ensure existing completion is interpolated when inserting again.
6604 cx.simulate_keystroke("c");
6605 deterministic.run_until_parked();
6606 cx.update_editor(|editor, cx| {
6607 assert!(!editor.context_menu_visible());
6608 assert!(editor.has_active_copilot_suggestion(cx));
6609 assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
6610 assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
6611 });
6612
6613 // After debouncing, new Copilot completions should be requested.
6614 handle_copilot_completion_request(
6615 &copilot_lsp,
6616 vec![copilot::request::Completion {
6617 text: "one.copilot2".into(),
6618 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 5)),
6619 ..Default::default()
6620 }],
6621 vec![],
6622 );
6623 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6624 cx.update_editor(|editor, cx| {
6625 assert!(!editor.context_menu_visible());
6626 assert!(editor.has_active_copilot_suggestion(cx));
6627 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
6628 assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
6629
6630 // Canceling should remove the active Copilot suggestion.
6631 editor.cancel(&Default::default(), cx);
6632 assert!(!editor.has_active_copilot_suggestion(cx));
6633 assert_eq!(editor.display_text(cx), "one.c\ntwo\nthree\n");
6634 assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
6635
6636 // After canceling, tabbing shouldn't insert the previously shown suggestion.
6637 editor.tab(&Default::default(), cx);
6638 assert!(!editor.has_active_copilot_suggestion(cx));
6639 assert_eq!(editor.display_text(cx), "one.c \ntwo\nthree\n");
6640 assert_eq!(editor.text(cx), "one.c \ntwo\nthree\n");
6641
6642 // When undoing the previously active suggestion is shown again.
6643 editor.undo(&Default::default(), cx);
6644 assert!(editor.has_active_copilot_suggestion(cx));
6645 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
6646 assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
6647 });
6648
6649 // If an edit occurs outside of this editor, the suggestion is still correctly interpolated.
6650 cx.update_buffer(|buffer, cx| buffer.edit([(5..5, "o")], None, cx));
6651 cx.update_editor(|editor, cx| {
6652 assert!(editor.has_active_copilot_suggestion(cx));
6653 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
6654 assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
6655
6656 // Tabbing when there is an active suggestion inserts it.
6657 editor.tab(&Default::default(), cx);
6658 assert!(!editor.has_active_copilot_suggestion(cx));
6659 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
6660 assert_eq!(editor.text(cx), "one.copilot2\ntwo\nthree\n");
6661
6662 // When undoing the previously active suggestion is shown again.
6663 editor.undo(&Default::default(), cx);
6664 assert!(editor.has_active_copilot_suggestion(cx));
6665 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
6666 assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
6667
6668 // Hide suggestion.
6669 editor.cancel(&Default::default(), cx);
6670 assert!(!editor.has_active_copilot_suggestion(cx));
6671 assert_eq!(editor.display_text(cx), "one.co\ntwo\nthree\n");
6672 assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
6673 });
6674
6675 // If an edit occurs outside of this editor but no suggestion is being shown,
6676 // we won't make it visible.
6677 cx.update_buffer(|buffer, cx| buffer.edit([(6..6, "p")], None, cx));
6678 cx.update_editor(|editor, cx| {
6679 assert!(!editor.has_active_copilot_suggestion(cx));
6680 assert_eq!(editor.display_text(cx), "one.cop\ntwo\nthree\n");
6681 assert_eq!(editor.text(cx), "one.cop\ntwo\nthree\n");
6682 });
6683
6684 // Reset the editor to verify how suggestions behave when tabbing on leading indentation.
6685 cx.update_editor(|editor, cx| {
6686 editor.set_text("fn foo() {\n \n}", cx);
6687 editor.change_selections(None, cx, |s| {
6688 s.select_ranges([Point::new(1, 2)..Point::new(1, 2)])
6689 });
6690 });
6691 handle_copilot_completion_request(
6692 &copilot_lsp,
6693 vec![copilot::request::Completion {
6694 text: " let x = 4;".into(),
6695 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 2)),
6696 ..Default::default()
6697 }],
6698 vec![],
6699 );
6700
6701 cx.update_editor(|editor, cx| editor.next_copilot_suggestion(&Default::default(), cx));
6702 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6703 cx.update_editor(|editor, cx| {
6704 assert!(editor.has_active_copilot_suggestion(cx));
6705 assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
6706 assert_eq!(editor.text(cx), "fn foo() {\n \n}");
6707
6708 // Tabbing inside of leading whitespace inserts indentation without accepting the suggestion.
6709 editor.tab(&Default::default(), cx);
6710 assert!(editor.has_active_copilot_suggestion(cx));
6711 assert_eq!(editor.text(cx), "fn foo() {\n \n}");
6712 assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
6713
6714 // Tabbing again accepts the suggestion.
6715 editor.tab(&Default::default(), cx);
6716 assert!(!editor.has_active_copilot_suggestion(cx));
6717 assert_eq!(editor.text(cx), "fn foo() {\n let x = 4;\n}");
6718 assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
6719 });
6720}
6721
6722#[gpui::test]
6723async fn test_copilot_completion_invalidation(
6724 deterministic: Arc<Deterministic>,
6725 cx: &mut gpui::TestAppContext,
6726) {
6727 init_test(cx, |_| {});
6728
6729 let (copilot, copilot_lsp) = Copilot::fake(cx);
6730 cx.update(|cx| cx.set_global(copilot));
6731 let mut cx = EditorLspTestContext::new_rust(
6732 lsp::ServerCapabilities {
6733 completion_provider: Some(lsp::CompletionOptions {
6734 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
6735 ..Default::default()
6736 }),
6737 ..Default::default()
6738 },
6739 cx,
6740 )
6741 .await;
6742
6743 cx.set_state(indoc! {"
6744 one
6745 twˇ
6746 three
6747 "});
6748
6749 handle_copilot_completion_request(
6750 &copilot_lsp,
6751 vec![copilot::request::Completion {
6752 text: "two.foo()".into(),
6753 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 2)),
6754 ..Default::default()
6755 }],
6756 vec![],
6757 );
6758 cx.update_editor(|editor, cx| editor.next_copilot_suggestion(&Default::default(), cx));
6759 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6760 cx.update_editor(|editor, cx| {
6761 assert!(editor.has_active_copilot_suggestion(cx));
6762 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
6763 assert_eq!(editor.text(cx), "one\ntw\nthree\n");
6764
6765 editor.backspace(&Default::default(), cx);
6766 assert!(editor.has_active_copilot_suggestion(cx));
6767 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
6768 assert_eq!(editor.text(cx), "one\nt\nthree\n");
6769
6770 editor.backspace(&Default::default(), cx);
6771 assert!(editor.has_active_copilot_suggestion(cx));
6772 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
6773 assert_eq!(editor.text(cx), "one\n\nthree\n");
6774
6775 // Deleting across the original suggestion range invalidates it.
6776 editor.backspace(&Default::default(), cx);
6777 assert!(!editor.has_active_copilot_suggestion(cx));
6778 assert_eq!(editor.display_text(cx), "one\nthree\n");
6779 assert_eq!(editor.text(cx), "one\nthree\n");
6780
6781 // Undoing the deletion restores the suggestion.
6782 editor.undo(&Default::default(), cx);
6783 assert!(editor.has_active_copilot_suggestion(cx));
6784 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
6785 assert_eq!(editor.text(cx), "one\n\nthree\n");
6786 });
6787}
6788
6789#[gpui::test]
6790async fn test_copilot_multibuffer(
6791 deterministic: Arc<Deterministic>,
6792 cx: &mut gpui::TestAppContext,
6793) {
6794 init_test(cx, |_| {});
6795
6796 let (copilot, copilot_lsp) = Copilot::fake(cx);
6797 cx.update(|cx| cx.set_global(copilot));
6798
6799 let buffer_1 = cx.add_model(|cx| Buffer::new(0, "a = 1\nb = 2\n", cx));
6800 let buffer_2 = cx.add_model(|cx| Buffer::new(0, "c = 3\nd = 4\n", cx));
6801 let multibuffer = cx.add_model(|cx| {
6802 let mut multibuffer = MultiBuffer::new(0);
6803 multibuffer.push_excerpts(
6804 buffer_1.clone(),
6805 [ExcerptRange {
6806 context: Point::new(0, 0)..Point::new(2, 0),
6807 primary: None,
6808 }],
6809 cx,
6810 );
6811 multibuffer.push_excerpts(
6812 buffer_2.clone(),
6813 [ExcerptRange {
6814 context: Point::new(0, 0)..Point::new(2, 0),
6815 primary: None,
6816 }],
6817 cx,
6818 );
6819 multibuffer
6820 });
6821 let (_, editor) = cx.add_window(|cx| build_editor(multibuffer, cx));
6822
6823 handle_copilot_completion_request(
6824 &copilot_lsp,
6825 vec![copilot::request::Completion {
6826 text: "b = 2 + a".into(),
6827 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 5)),
6828 ..Default::default()
6829 }],
6830 vec![],
6831 );
6832 editor.update(cx, |editor, cx| {
6833 // Ensure copilot suggestions are shown for the first excerpt.
6834 editor.change_selections(None, cx, |s| {
6835 s.select_ranges([Point::new(1, 5)..Point::new(1, 5)])
6836 });
6837 editor.next_copilot_suggestion(&Default::default(), cx);
6838 });
6839 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6840 editor.update(cx, |editor, cx| {
6841 assert!(editor.has_active_copilot_suggestion(cx));
6842 assert_eq!(
6843 editor.display_text(cx),
6844 "\n\na = 1\nb = 2 + a\n\n\n\nc = 3\nd = 4\n"
6845 );
6846 assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4\n");
6847 });
6848
6849 handle_copilot_completion_request(
6850 &copilot_lsp,
6851 vec![copilot::request::Completion {
6852 text: "d = 4 + c".into(),
6853 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 6)),
6854 ..Default::default()
6855 }],
6856 vec![],
6857 );
6858 editor.update(cx, |editor, cx| {
6859 // Move to another excerpt, ensuring the suggestion gets cleared.
6860 editor.change_selections(None, cx, |s| {
6861 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
6862 });
6863 assert!(!editor.has_active_copilot_suggestion(cx));
6864 assert_eq!(
6865 editor.display_text(cx),
6866 "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4\n"
6867 );
6868 assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4\n");
6869
6870 // Type a character, ensuring we don't even try to interpolate the previous suggestion.
6871 editor.handle_input(" ", cx);
6872 assert!(!editor.has_active_copilot_suggestion(cx));
6873 assert_eq!(
6874 editor.display_text(cx),
6875 "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4 \n"
6876 );
6877 assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4 \n");
6878 });
6879
6880 // Ensure the new suggestion is displayed when the debounce timeout expires.
6881 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6882 editor.update(cx, |editor, cx| {
6883 assert!(editor.has_active_copilot_suggestion(cx));
6884 assert_eq!(
6885 editor.display_text(cx),
6886 "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4 + c\n"
6887 );
6888 assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4 \n");
6889 });
6890}
6891
6892#[gpui::test]
6893async fn test_copilot_disabled_globs(
6894 deterministic: Arc<Deterministic>,
6895 cx: &mut gpui::TestAppContext,
6896) {
6897 init_test(cx, |settings| {
6898 settings
6899 .copilot
6900 .get_or_insert(Default::default())
6901 .disabled_globs = Some(vec![".env*".to_string()]);
6902 });
6903
6904 let (copilot, copilot_lsp) = Copilot::fake(cx);
6905 cx.update(|cx| cx.set_global(copilot));
6906
6907 let fs = FakeFs::new(cx.background());
6908 fs.insert_tree(
6909 "/test",
6910 json!({
6911 ".env": "SECRET=something\n",
6912 "README.md": "hello\n"
6913 }),
6914 )
6915 .await;
6916 let project = Project::test(fs, ["/test".as_ref()], cx).await;
6917
6918 let private_buffer = project
6919 .update(cx, |project, cx| {
6920 project.open_local_buffer("/test/.env", cx)
6921 })
6922 .await
6923 .unwrap();
6924 let public_buffer = project
6925 .update(cx, |project, cx| {
6926 project.open_local_buffer("/test/README.md", cx)
6927 })
6928 .await
6929 .unwrap();
6930
6931 let multibuffer = cx.add_model(|cx| {
6932 let mut multibuffer = MultiBuffer::new(0);
6933 multibuffer.push_excerpts(
6934 private_buffer.clone(),
6935 [ExcerptRange {
6936 context: Point::new(0, 0)..Point::new(1, 0),
6937 primary: None,
6938 }],
6939 cx,
6940 );
6941 multibuffer.push_excerpts(
6942 public_buffer.clone(),
6943 [ExcerptRange {
6944 context: Point::new(0, 0)..Point::new(1, 0),
6945 primary: None,
6946 }],
6947 cx,
6948 );
6949 multibuffer
6950 });
6951 let (_, editor) = cx.add_window(|cx| build_editor(multibuffer, cx));
6952
6953 let mut copilot_requests = copilot_lsp
6954 .handle_request::<copilot::request::GetCompletions, _, _>(move |_params, _cx| async move {
6955 Ok(copilot::request::GetCompletionsResult {
6956 completions: vec![copilot::request::Completion {
6957 text: "next line".into(),
6958 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
6959 ..Default::default()
6960 }],
6961 })
6962 });
6963
6964 editor.update(cx, |editor, cx| {
6965 editor.change_selections(None, cx, |selections| {
6966 selections.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
6967 });
6968 editor.next_copilot_suggestion(&Default::default(), cx);
6969 });
6970
6971 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6972 assert!(copilot_requests.try_next().is_err());
6973
6974 editor.update(cx, |editor, cx| {
6975 editor.change_selections(None, cx, |s| {
6976 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
6977 });
6978 editor.next_copilot_suggestion(&Default::default(), cx);
6979 });
6980
6981 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6982 assert!(copilot_requests.try_next().is_ok());
6983}
6984
6985#[gpui::test]
6986async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
6987 init_test(cx, |_| {});
6988
6989 let mut language = Language::new(
6990 LanguageConfig {
6991 name: "Rust".into(),
6992 path_suffixes: vec!["rs".to_string()],
6993 brackets: BracketPairConfig {
6994 pairs: vec![BracketPair {
6995 start: "{".to_string(),
6996 end: "}".to_string(),
6997 close: true,
6998 newline: true,
6999 }],
7000 disabled_scopes_by_bracket_ix: Vec::new(),
7001 },
7002 ..Default::default()
7003 },
7004 Some(tree_sitter_rust::language()),
7005 );
7006 let mut fake_servers = language
7007 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
7008 capabilities: lsp::ServerCapabilities {
7009 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
7010 first_trigger_character: "{".to_string(),
7011 more_trigger_character: None,
7012 }),
7013 ..Default::default()
7014 },
7015 ..Default::default()
7016 }))
7017 .await;
7018
7019 let fs = FakeFs::new(cx.background());
7020 fs.insert_tree(
7021 "/a",
7022 json!({
7023 "main.rs": "fn main() { let a = 5; }",
7024 "other.rs": "// Test file",
7025 }),
7026 )
7027 .await;
7028 let project = Project::test(fs, ["/a".as_ref()], cx).await;
7029 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
7030 let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
7031 let worktree_id = workspace.update(cx, |workspace, cx| {
7032 workspace.project().read_with(cx, |project, cx| {
7033 project.worktrees(cx).next().unwrap().read(cx).id()
7034 })
7035 });
7036
7037 let buffer = project
7038 .update(cx, |project, cx| {
7039 project.open_local_buffer("/a/main.rs", cx)
7040 })
7041 .await
7042 .unwrap();
7043 cx.foreground().run_until_parked();
7044 cx.foreground().start_waiting();
7045 let fake_server = fake_servers.next().await.unwrap();
7046 let editor_handle = workspace
7047 .update(cx, |workspace, cx| {
7048 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
7049 })
7050 .await
7051 .unwrap()
7052 .downcast::<Editor>()
7053 .unwrap();
7054
7055 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
7056 assert_eq!(
7057 params.text_document_position.text_document.uri,
7058 lsp::Url::from_file_path("/a/main.rs").unwrap(),
7059 );
7060 assert_eq!(
7061 params.text_document_position.position,
7062 lsp::Position::new(0, 21),
7063 );
7064
7065 Ok(Some(vec![lsp::TextEdit {
7066 new_text: "]".to_string(),
7067 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
7068 }]))
7069 });
7070
7071 editor_handle.update(cx, |editor, cx| {
7072 cx.focus(&editor_handle);
7073 editor.change_selections(None, cx, |s| {
7074 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
7075 });
7076 editor.handle_input("{", cx);
7077 });
7078
7079 cx.foreground().run_until_parked();
7080
7081 buffer.read_with(cx, |buffer, _| {
7082 assert_eq!(
7083 buffer.text(),
7084 "fn main() { let a = {5}; }",
7085 "No extra braces from on type formatting should appear in the buffer"
7086 )
7087 });
7088}
7089
7090#[gpui::test]
7091async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
7092 init_test(cx, |_| {});
7093
7094 let language_name: Arc<str> = "Rust".into();
7095 let mut language = Language::new(
7096 LanguageConfig {
7097 name: Arc::clone(&language_name),
7098 path_suffixes: vec!["rs".to_string()],
7099 ..Default::default()
7100 },
7101 Some(tree_sitter_rust::language()),
7102 );
7103
7104 let server_restarts = Arc::new(AtomicUsize::new(0));
7105 let closure_restarts = Arc::clone(&server_restarts);
7106 let language_server_name = "test language server";
7107 let mut fake_servers = language
7108 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
7109 name: language_server_name,
7110 initialization_options: Some(json!({
7111 "testOptionValue": true
7112 })),
7113 initializer: Some(Box::new(move |fake_server| {
7114 let task_restarts = Arc::clone(&closure_restarts);
7115 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
7116 task_restarts.fetch_add(1, atomic::Ordering::Release);
7117 futures::future::ready(Ok(()))
7118 });
7119 })),
7120 ..Default::default()
7121 }))
7122 .await;
7123
7124 let fs = FakeFs::new(cx.background());
7125 fs.insert_tree(
7126 "/a",
7127 json!({
7128 "main.rs": "fn main() { let a = 5; }",
7129 "other.rs": "// Test file",
7130 }),
7131 )
7132 .await;
7133 let project = Project::test(fs, ["/a".as_ref()], cx).await;
7134 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
7135 let (_, _workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
7136 let _buffer = project
7137 .update(cx, |project, cx| {
7138 project.open_local_buffer("/a/main.rs", cx)
7139 })
7140 .await
7141 .unwrap();
7142 let _fake_server = fake_servers.next().await.unwrap();
7143 update_test_language_settings(cx, |language_settings| {
7144 language_settings.languages.insert(
7145 Arc::clone(&language_name),
7146 LanguageSettingsContent {
7147 tab_size: NonZeroU32::new(8),
7148 ..Default::default()
7149 },
7150 );
7151 });
7152 cx.foreground().run_until_parked();
7153 assert_eq!(
7154 server_restarts.load(atomic::Ordering::Acquire),
7155 0,
7156 "Should not restart LSP server on an unrelated change"
7157 );
7158
7159 update_test_project_settings(cx, |project_settings| {
7160 project_settings.lsp.insert(
7161 "Some other server name".into(),
7162 LspSettings {
7163 initialization_options: Some(json!({
7164 "some other init value": false
7165 })),
7166 },
7167 );
7168 });
7169 cx.foreground().run_until_parked();
7170 assert_eq!(
7171 server_restarts.load(atomic::Ordering::Acquire),
7172 0,
7173 "Should not restart LSP server on an unrelated LSP settings change"
7174 );
7175
7176 update_test_project_settings(cx, |project_settings| {
7177 project_settings.lsp.insert(
7178 language_server_name.into(),
7179 LspSettings {
7180 initialization_options: Some(json!({
7181 "anotherInitValue": false
7182 })),
7183 },
7184 );
7185 });
7186 cx.foreground().run_until_parked();
7187 assert_eq!(
7188 server_restarts.load(atomic::Ordering::Acquire),
7189 1,
7190 "Should restart LSP server on a related LSP settings change"
7191 );
7192
7193 update_test_project_settings(cx, |project_settings| {
7194 project_settings.lsp.insert(
7195 language_server_name.into(),
7196 LspSettings {
7197 initialization_options: Some(json!({
7198 "anotherInitValue": false
7199 })),
7200 },
7201 );
7202 });
7203 cx.foreground().run_until_parked();
7204 assert_eq!(
7205 server_restarts.load(atomic::Ordering::Acquire),
7206 1,
7207 "Should not restart LSP server on a related LSP settings change that is the same"
7208 );
7209
7210 update_test_project_settings(cx, |project_settings| {
7211 project_settings.lsp.insert(
7212 language_server_name.into(),
7213 LspSettings {
7214 initialization_options: None,
7215 },
7216 );
7217 });
7218 cx.foreground().run_until_parked();
7219 assert_eq!(
7220 server_restarts.load(atomic::Ordering::Acquire),
7221 2,
7222 "Should restart LSP server on another related LSP settings change"
7223 );
7224}
7225
7226#[gpui::test]
7227async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
7228 init_test(cx, |_| {});
7229
7230 let mut cx = EditorLspTestContext::new_rust(
7231 lsp::ServerCapabilities {
7232 completion_provider: Some(lsp::CompletionOptions {
7233 trigger_characters: Some(vec![".".to_string()]),
7234 ..Default::default()
7235 }),
7236 ..Default::default()
7237 },
7238 cx,
7239 )
7240 .await;
7241
7242 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
7243 cx.simulate_keystroke(".");
7244 let completion_item = lsp::CompletionItem {
7245 label: "some".into(),
7246 kind: Some(lsp::CompletionItemKind::SNIPPET),
7247 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
7248 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
7249 kind: lsp::MarkupKind::Markdown,
7250 value: "```rust\nSome(2)\n```".to_string(),
7251 })),
7252 deprecated: Some(false),
7253 sort_text: Some("fffffff2".to_string()),
7254 filter_text: Some("some".to_string()),
7255 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
7256 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
7257 range: lsp::Range {
7258 start: lsp::Position {
7259 line: 0,
7260 character: 22,
7261 },
7262 end: lsp::Position {
7263 line: 0,
7264 character: 22,
7265 },
7266 },
7267 new_text: "Some(2)".to_string(),
7268 })),
7269 additional_text_edits: Some(vec![lsp::TextEdit {
7270 range: lsp::Range {
7271 start: lsp::Position {
7272 line: 0,
7273 character: 20,
7274 },
7275 end: lsp::Position {
7276 line: 0,
7277 character: 22,
7278 },
7279 },
7280 new_text: "".to_string(),
7281 }]),
7282 ..Default::default()
7283 };
7284
7285 let closure_completion_item = completion_item.clone();
7286 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
7287 let task_completion_item = closure_completion_item.clone();
7288 async move {
7289 Ok(Some(lsp::CompletionResponse::Array(vec![
7290 task_completion_item,
7291 ])))
7292 }
7293 });
7294
7295 request.next().await;
7296
7297 cx.condition(|editor, _| editor.context_menu_visible())
7298 .await;
7299 let apply_additional_edits = cx.update_editor(|editor, cx| {
7300 editor
7301 .confirm_completion(&ConfirmCompletion::default(), cx)
7302 .unwrap()
7303 });
7304 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
7305
7306 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
7307 let task_completion_item = completion_item.clone();
7308 async move { Ok(task_completion_item) }
7309 })
7310 .next()
7311 .await
7312 .unwrap();
7313 apply_additional_edits.await.unwrap();
7314 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
7315}
7316
7317fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
7318 let point = DisplayPoint::new(row as u32, column as u32);
7319 point..point
7320}
7321
7322fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
7323 let (text, ranges) = marked_text_ranges(marked_text, true);
7324 assert_eq!(view.text(cx), text);
7325 assert_eq!(
7326 view.selections.ranges(cx),
7327 ranges,
7328 "Assert selections are {}",
7329 marked_text
7330 );
7331}
7332
7333/// Handle completion request passing a marked string specifying where the completion
7334/// should be triggered from using '|' character, what range should be replaced, and what completions
7335/// should be returned using '<' and '>' to delimit the range
7336fn handle_completion_request<'a>(
7337 cx: &mut EditorLspTestContext<'a>,
7338 marked_string: &str,
7339 completions: Vec<&'static str>,
7340) -> impl Future<Output = ()> {
7341 let complete_from_marker: TextRangeMarker = '|'.into();
7342 let replace_range_marker: TextRangeMarker = ('<', '>').into();
7343 let (_, mut marked_ranges) = marked_text_ranges_by(
7344 marked_string,
7345 vec![complete_from_marker.clone(), replace_range_marker.clone()],
7346 );
7347
7348 let complete_from_position =
7349 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
7350 let replace_range =
7351 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
7352
7353 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
7354 let completions = completions.clone();
7355 async move {
7356 assert_eq!(params.text_document_position.text_document.uri, url.clone());
7357 assert_eq!(
7358 params.text_document_position.position,
7359 complete_from_position
7360 );
7361 Ok(Some(lsp::CompletionResponse::Array(
7362 completions
7363 .iter()
7364 .map(|completion_text| lsp::CompletionItem {
7365 label: completion_text.to_string(),
7366 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
7367 range: replace_range,
7368 new_text: completion_text.to_string(),
7369 })),
7370 ..Default::default()
7371 })
7372 .collect(),
7373 )))
7374 }
7375 });
7376
7377 async move {
7378 request.next().await;
7379 }
7380}
7381
7382fn handle_resolve_completion_request<'a>(
7383 cx: &mut EditorLspTestContext<'a>,
7384 edits: Option<Vec<(&'static str, &'static str)>>,
7385) -> impl Future<Output = ()> {
7386 let edits = edits.map(|edits| {
7387 edits
7388 .iter()
7389 .map(|(marked_string, new_text)| {
7390 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
7391 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
7392 lsp::TextEdit::new(replace_range, new_text.to_string())
7393 })
7394 .collect::<Vec<_>>()
7395 });
7396
7397 let mut request =
7398 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
7399 let edits = edits.clone();
7400 async move {
7401 Ok(lsp::CompletionItem {
7402 additional_text_edits: edits,
7403 ..Default::default()
7404 })
7405 }
7406 });
7407
7408 async move {
7409 request.next().await;
7410 }
7411}
7412
7413fn handle_copilot_completion_request(
7414 lsp: &lsp::FakeLanguageServer,
7415 completions: Vec<copilot::request::Completion>,
7416 completions_cycling: Vec<copilot::request::Completion>,
7417) {
7418 lsp.handle_request::<copilot::request::GetCompletions, _, _>(move |_params, _cx| {
7419 let completions = completions.clone();
7420 async move {
7421 Ok(copilot::request::GetCompletionsResult {
7422 completions: completions.clone(),
7423 })
7424 }
7425 });
7426 lsp.handle_request::<copilot::request::GetCompletionsCycling, _, _>(move |_params, _cx| {
7427 let completions_cycling = completions_cycling.clone();
7428 async move {
7429 Ok(copilot::request::GetCompletionsResult {
7430 completions: completions_cycling.clone(),
7431 })
7432 }
7433 });
7434}
7435
7436pub(crate) fn update_test_language_settings(
7437 cx: &mut TestAppContext,
7438 f: impl Fn(&mut AllLanguageSettingsContent),
7439) {
7440 cx.update(|cx| {
7441 cx.update_global::<SettingsStore, _, _>(|store, cx| {
7442 store.update_user_settings::<AllLanguageSettings>(cx, f);
7443 });
7444 });
7445}
7446
7447pub(crate) fn update_test_project_settings(
7448 cx: &mut TestAppContext,
7449 f: impl Fn(&mut ProjectSettings),
7450) {
7451 cx.update(|cx| {
7452 cx.update_global::<SettingsStore, _, _>(|store, cx| {
7453 store.update_user_settings::<ProjectSettings>(cx, f);
7454 });
7455 });
7456}
7457
7458pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
7459 cx.foreground().forbid_parking();
7460
7461 cx.update(|cx| {
7462 cx.set_global(SettingsStore::test(cx));
7463 theme::init((), cx);
7464 client::init_settings(cx);
7465 language::init(cx);
7466 Project::init_settings(cx);
7467 workspace::init_settings(cx);
7468 crate::init(cx);
7469 });
7470
7471 update_test_language_settings(cx, f);
7472}