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