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