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