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