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