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