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_in_leading_whitespace_auto_indents_lines(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_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
1628 let mut cx = EditorTestContext::new(cx);
1629 let language = Arc::new(
1630 Language::new(
1631 LanguageConfig::default(),
1632 Some(tree_sitter_rust::language()),
1633 )
1634 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
1635 .unwrap(),
1636 );
1637 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
1638
1639 cx.update(|cx| {
1640 cx.update_global::<Settings, _, _>(|settings, _| {
1641 settings.editor_overrides.tab_size = Some(4.try_into().unwrap());
1642 });
1643 });
1644
1645 cx.set_state(indoc! {"
1646 fn a() {
1647 if b {
1648 \t ˇc
1649 }
1650 }
1651 "});
1652
1653 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1654 cx.assert_editor_state(indoc! {"
1655 fn a() {
1656 if b {
1657 ˇc
1658 }
1659 }
1660 "});
1661}
1662
1663#[gpui::test]
1664async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
1665 let mut cx = EditorTestContext::new(cx);
1666
1667 cx.set_state(indoc! {"
1668 «oneˇ» «twoˇ»
1669 three
1670 four
1671 "});
1672 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1673 cx.assert_editor_state(indoc! {"
1674 «oneˇ» «twoˇ»
1675 three
1676 four
1677 "});
1678
1679 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
1680 cx.assert_editor_state(indoc! {"
1681 «oneˇ» «twoˇ»
1682 three
1683 four
1684 "});
1685
1686 // select across line ending
1687 cx.set_state(indoc! {"
1688 one two
1689 t«hree
1690 ˇ» four
1691 "});
1692 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1693 cx.assert_editor_state(indoc! {"
1694 one two
1695 t«hree
1696 ˇ» four
1697 "});
1698
1699 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
1700 cx.assert_editor_state(indoc! {"
1701 one two
1702 t«hree
1703 ˇ» four
1704 "});
1705
1706 // Ensure that indenting/outdenting works when the cursor is at column 0.
1707 cx.set_state(indoc! {"
1708 one two
1709 ˇthree
1710 four
1711 "});
1712 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1713 cx.assert_editor_state(indoc! {"
1714 one two
1715 ˇthree
1716 four
1717 "});
1718
1719 cx.set_state(indoc! {"
1720 one two
1721 ˇ three
1722 four
1723 "});
1724 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
1725 cx.assert_editor_state(indoc! {"
1726 one two
1727 ˇthree
1728 four
1729 "});
1730}
1731
1732#[gpui::test]
1733async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
1734 let mut cx = EditorTestContext::new(cx);
1735 cx.update(|cx| {
1736 cx.update_global::<Settings, _, _>(|settings, _| {
1737 settings.editor_overrides.hard_tabs = Some(true);
1738 });
1739 });
1740
1741 // select two ranges on one line
1742 cx.set_state(indoc! {"
1743 «oneˇ» «twoˇ»
1744 three
1745 four
1746 "});
1747 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1748 cx.assert_editor_state(indoc! {"
1749 \t«oneˇ» «twoˇ»
1750 three
1751 four
1752 "});
1753 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1754 cx.assert_editor_state(indoc! {"
1755 \t\t«oneˇ» «twoˇ»
1756 three
1757 four
1758 "});
1759 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
1760 cx.assert_editor_state(indoc! {"
1761 \t«oneˇ» «twoˇ»
1762 three
1763 four
1764 "});
1765 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
1766 cx.assert_editor_state(indoc! {"
1767 «oneˇ» «twoˇ»
1768 three
1769 four
1770 "});
1771
1772 // select across a line ending
1773 cx.set_state(indoc! {"
1774 one two
1775 t«hree
1776 ˇ»four
1777 "});
1778 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1779 cx.assert_editor_state(indoc! {"
1780 one two
1781 \tt«hree
1782 ˇ»four
1783 "});
1784 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1785 cx.assert_editor_state(indoc! {"
1786 one two
1787 \t\tt«hree
1788 ˇ»four
1789 "});
1790 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
1791 cx.assert_editor_state(indoc! {"
1792 one two
1793 \tt«hree
1794 ˇ»four
1795 "});
1796 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
1797 cx.assert_editor_state(indoc! {"
1798 one two
1799 t«hree
1800 ˇ»four
1801 "});
1802
1803 // Ensure that indenting/outdenting works when the cursor is at column 0.
1804 cx.set_state(indoc! {"
1805 one two
1806 ˇthree
1807 four
1808 "});
1809 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
1810 cx.assert_editor_state(indoc! {"
1811 one two
1812 ˇthree
1813 four
1814 "});
1815 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1816 cx.assert_editor_state(indoc! {"
1817 one two
1818 \tˇthree
1819 four
1820 "});
1821 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
1822 cx.assert_editor_state(indoc! {"
1823 one two
1824 ˇthree
1825 four
1826 "});
1827}
1828
1829#[gpui::test]
1830fn test_indent_outdent_with_excerpts(cx: &mut gpui::MutableAppContext) {
1831 cx.set_global(
1832 Settings::test(cx)
1833 .with_language_defaults(
1834 "TOML",
1835 EditorSettings {
1836 tab_size: Some(2.try_into().unwrap()),
1837 ..Default::default()
1838 },
1839 )
1840 .with_language_defaults(
1841 "Rust",
1842 EditorSettings {
1843 tab_size: Some(4.try_into().unwrap()),
1844 ..Default::default()
1845 },
1846 ),
1847 );
1848 let toml_language = Arc::new(Language::new(
1849 LanguageConfig {
1850 name: "TOML".into(),
1851 ..Default::default()
1852 },
1853 None,
1854 ));
1855 let rust_language = Arc::new(Language::new(
1856 LanguageConfig {
1857 name: "Rust".into(),
1858 ..Default::default()
1859 },
1860 None,
1861 ));
1862
1863 let toml_buffer =
1864 cx.add_model(|cx| Buffer::new(0, "a = 1\nb = 2\n", cx).with_language(toml_language, cx));
1865 let rust_buffer = cx.add_model(|cx| {
1866 Buffer::new(0, "const c: usize = 3;\n", cx).with_language(rust_language, cx)
1867 });
1868 let multibuffer = cx.add_model(|cx| {
1869 let mut multibuffer = MultiBuffer::new(0);
1870 multibuffer.push_excerpts(
1871 toml_buffer.clone(),
1872 [ExcerptRange {
1873 context: Point::new(0, 0)..Point::new(2, 0),
1874 primary: None,
1875 }],
1876 cx,
1877 );
1878 multibuffer.push_excerpts(
1879 rust_buffer.clone(),
1880 [ExcerptRange {
1881 context: Point::new(0, 0)..Point::new(1, 0),
1882 primary: None,
1883 }],
1884 cx,
1885 );
1886 multibuffer
1887 });
1888
1889 cx.add_window(Default::default(), |cx| {
1890 let mut editor = build_editor(multibuffer, cx);
1891
1892 assert_eq!(
1893 editor.text(cx),
1894 indoc! {"
1895 a = 1
1896 b = 2
1897
1898 const c: usize = 3;
1899 "}
1900 );
1901
1902 select_ranges(
1903 &mut editor,
1904 indoc! {"
1905 «aˇ» = 1
1906 b = 2
1907
1908 «const c:ˇ» usize = 3;
1909 "},
1910 cx,
1911 );
1912
1913 editor.tab(&Tab, cx);
1914 assert_text_with_selections(
1915 &mut editor,
1916 indoc! {"
1917 «aˇ» = 1
1918 b = 2
1919
1920 «const c:ˇ» usize = 3;
1921 "},
1922 cx,
1923 );
1924 editor.tab_prev(&TabPrev, cx);
1925 assert_text_with_selections(
1926 &mut editor,
1927 indoc! {"
1928 «aˇ» = 1
1929 b = 2
1930
1931 «const c:ˇ» usize = 3;
1932 "},
1933 cx,
1934 );
1935
1936 editor
1937 });
1938}
1939
1940#[gpui::test]
1941async fn test_backspace(cx: &mut gpui::TestAppContext) {
1942 let mut cx = EditorTestContext::new(cx);
1943
1944 // Basic backspace
1945 cx.set_state(indoc! {"
1946 onˇe two three
1947 fou«rˇ» five six
1948 seven «ˇeight nine
1949 »ten
1950 "});
1951 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
1952 cx.assert_editor_state(indoc! {"
1953 oˇe two three
1954 fouˇ five six
1955 seven ˇten
1956 "});
1957
1958 // Test backspace inside and around indents
1959 cx.set_state(indoc! {"
1960 zero
1961 ˇone
1962 ˇtwo
1963 ˇ ˇ ˇ three
1964 ˇ ˇ four
1965 "});
1966 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
1967 cx.assert_editor_state(indoc! {"
1968 zero
1969 ˇone
1970 ˇtwo
1971 ˇ threeˇ four
1972 "});
1973
1974 // Test backspace with line_mode set to true
1975 cx.update_editor(|e, _| e.selections.line_mode = true);
1976 cx.set_state(indoc! {"
1977 The ˇquick ˇbrown
1978 fox jumps over
1979 the lazy dog
1980 ˇThe qu«ick bˇ»rown"});
1981 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
1982 cx.assert_editor_state(indoc! {"
1983 ˇfox jumps over
1984 the lazy dogˇ"});
1985}
1986
1987#[gpui::test]
1988async fn test_delete(cx: &mut gpui::TestAppContext) {
1989 let mut cx = EditorTestContext::new(cx);
1990
1991 cx.set_state(indoc! {"
1992 onˇe two three
1993 fou«rˇ» five six
1994 seven «ˇeight nine
1995 »ten
1996 "});
1997 cx.update_editor(|e, cx| e.delete(&Delete, cx));
1998 cx.assert_editor_state(indoc! {"
1999 onˇ two three
2000 fouˇ five six
2001 seven ˇten
2002 "});
2003
2004 // Test backspace with line_mode set to true
2005 cx.update_editor(|e, _| e.selections.line_mode = true);
2006 cx.set_state(indoc! {"
2007 The ˇquick ˇbrown
2008 fox «ˇjum»ps over
2009 the lazy dog
2010 ˇThe qu«ick bˇ»rown"});
2011 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2012 cx.assert_editor_state("ˇthe lazy dogˇ");
2013}
2014
2015#[gpui::test]
2016fn test_delete_line(cx: &mut gpui::MutableAppContext) {
2017 cx.set_global(Settings::test(cx));
2018 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2019 let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
2020 view.update(cx, |view, cx| {
2021 view.change_selections(None, cx, |s| {
2022 s.select_display_ranges([
2023 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
2024 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
2025 DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
2026 ])
2027 });
2028 view.delete_line(&DeleteLine, cx);
2029 assert_eq!(view.display_text(cx), "ghi");
2030 assert_eq!(
2031 view.selections.display_ranges(cx),
2032 vec![
2033 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
2034 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)
2035 ]
2036 );
2037 });
2038
2039 cx.set_global(Settings::test(cx));
2040 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2041 let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
2042 view.update(cx, |view, cx| {
2043 view.change_selections(None, cx, |s| {
2044 s.select_display_ranges([DisplayPoint::new(2, 0)..DisplayPoint::new(0, 1)])
2045 });
2046 view.delete_line(&DeleteLine, cx);
2047 assert_eq!(view.display_text(cx), "ghi\n");
2048 assert_eq!(
2049 view.selections.display_ranges(cx),
2050 vec![DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)]
2051 );
2052 });
2053}
2054
2055#[gpui::test]
2056fn test_duplicate_line(cx: &mut gpui::MutableAppContext) {
2057 cx.set_global(Settings::test(cx));
2058 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2059 let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
2060 view.update(cx, |view, cx| {
2061 view.change_selections(None, cx, |s| {
2062 s.select_display_ranges([
2063 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
2064 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
2065 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
2066 DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
2067 ])
2068 });
2069 view.duplicate_line(&DuplicateLine, cx);
2070 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
2071 assert_eq!(
2072 view.selections.display_ranges(cx),
2073 vec![
2074 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
2075 DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
2076 DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
2077 DisplayPoint::new(6, 0)..DisplayPoint::new(6, 0),
2078 ]
2079 );
2080 });
2081
2082 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2083 let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
2084 view.update(cx, |view, cx| {
2085 view.change_selections(None, cx, |s| {
2086 s.select_display_ranges([
2087 DisplayPoint::new(0, 1)..DisplayPoint::new(1, 1),
2088 DisplayPoint::new(1, 2)..DisplayPoint::new(2, 1),
2089 ])
2090 });
2091 view.duplicate_line(&DuplicateLine, cx);
2092 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
2093 assert_eq!(
2094 view.selections.display_ranges(cx),
2095 vec![
2096 DisplayPoint::new(3, 1)..DisplayPoint::new(4, 1),
2097 DisplayPoint::new(4, 2)..DisplayPoint::new(5, 1),
2098 ]
2099 );
2100 });
2101}
2102
2103#[gpui::test]
2104fn test_move_line_up_down(cx: &mut gpui::MutableAppContext) {
2105 cx.set_global(Settings::test(cx));
2106 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
2107 let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
2108 view.update(cx, |view, cx| {
2109 view.fold_ranges(
2110 vec![
2111 Point::new(0, 2)..Point::new(1, 2),
2112 Point::new(2, 3)..Point::new(4, 1),
2113 Point::new(7, 0)..Point::new(8, 4),
2114 ],
2115 cx,
2116 );
2117 view.change_selections(None, cx, |s| {
2118 s.select_display_ranges([
2119 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
2120 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
2121 DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
2122 DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2),
2123 ])
2124 });
2125 assert_eq!(
2126 view.display_text(cx),
2127 "aa…bbb\nccc…eeee\nfffff\nggggg\n…i\njjjjj"
2128 );
2129
2130 view.move_line_up(&MoveLineUp, cx);
2131 assert_eq!(
2132 view.display_text(cx),
2133 "aa…bbb\nccc…eeee\nggggg\n…i\njjjjj\nfffff"
2134 );
2135 assert_eq!(
2136 view.selections.display_ranges(cx),
2137 vec![
2138 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
2139 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
2140 DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3),
2141 DisplayPoint::new(4, 0)..DisplayPoint::new(4, 2)
2142 ]
2143 );
2144 });
2145
2146 view.update(cx, |view, cx| {
2147 view.move_line_down(&MoveLineDown, cx);
2148 assert_eq!(
2149 view.display_text(cx),
2150 "ccc…eeee\naa…bbb\nfffff\nggggg\n…i\njjjjj"
2151 );
2152 assert_eq!(
2153 view.selections.display_ranges(cx),
2154 vec![
2155 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
2156 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
2157 DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
2158 DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2)
2159 ]
2160 );
2161 });
2162
2163 view.update(cx, |view, cx| {
2164 view.move_line_down(&MoveLineDown, cx);
2165 assert_eq!(
2166 view.display_text(cx),
2167 "ccc…eeee\nfffff\naa…bbb\nggggg\n…i\njjjjj"
2168 );
2169 assert_eq!(
2170 view.selections.display_ranges(cx),
2171 vec![
2172 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
2173 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
2174 DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
2175 DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2)
2176 ]
2177 );
2178 });
2179
2180 view.update(cx, |view, cx| {
2181 view.move_line_up(&MoveLineUp, cx);
2182 assert_eq!(
2183 view.display_text(cx),
2184 "ccc…eeee\naa…bbb\nggggg\n…i\njjjjj\nfffff"
2185 );
2186 assert_eq!(
2187 view.selections.display_ranges(cx),
2188 vec![
2189 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
2190 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
2191 DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3),
2192 DisplayPoint::new(4, 0)..DisplayPoint::new(4, 2)
2193 ]
2194 );
2195 });
2196}
2197
2198#[gpui::test]
2199fn test_move_line_up_down_with_blocks(cx: &mut gpui::MutableAppContext) {
2200 cx.set_global(Settings::test(cx));
2201 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
2202 let snapshot = buffer.read(cx).snapshot(cx);
2203 let (_, editor) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
2204 editor.update(cx, |editor, cx| {
2205 editor.insert_blocks(
2206 [BlockProperties {
2207 style: BlockStyle::Fixed,
2208 position: snapshot.anchor_after(Point::new(2, 0)),
2209 disposition: BlockDisposition::Below,
2210 height: 1,
2211 render: Arc::new(|_| Empty::new().boxed()),
2212 }],
2213 cx,
2214 );
2215 editor.change_selections(None, cx, |s| {
2216 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
2217 });
2218 editor.move_line_down(&MoveLineDown, cx);
2219 });
2220}
2221
2222#[gpui::test]
2223fn test_transpose(cx: &mut gpui::MutableAppContext) {
2224 cx.set_global(Settings::test(cx));
2225
2226 _ = cx
2227 .add_window(Default::default(), |cx| {
2228 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
2229
2230 editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
2231 editor.transpose(&Default::default(), cx);
2232 assert_eq!(editor.text(cx), "bac");
2233 assert_eq!(editor.selections.ranges(cx), [2..2]);
2234
2235 editor.transpose(&Default::default(), cx);
2236 assert_eq!(editor.text(cx), "bca");
2237 assert_eq!(editor.selections.ranges(cx), [3..3]);
2238
2239 editor.transpose(&Default::default(), cx);
2240 assert_eq!(editor.text(cx), "bac");
2241 assert_eq!(editor.selections.ranges(cx), [3..3]);
2242
2243 editor
2244 })
2245 .1;
2246
2247 _ = cx
2248 .add_window(Default::default(), |cx| {
2249 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
2250
2251 editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
2252 editor.transpose(&Default::default(), cx);
2253 assert_eq!(editor.text(cx), "acb\nde");
2254 assert_eq!(editor.selections.ranges(cx), [3..3]);
2255
2256 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
2257 editor.transpose(&Default::default(), cx);
2258 assert_eq!(editor.text(cx), "acbd\ne");
2259 assert_eq!(editor.selections.ranges(cx), [5..5]);
2260
2261 editor.transpose(&Default::default(), cx);
2262 assert_eq!(editor.text(cx), "acbde\n");
2263 assert_eq!(editor.selections.ranges(cx), [6..6]);
2264
2265 editor.transpose(&Default::default(), cx);
2266 assert_eq!(editor.text(cx), "acbd\ne");
2267 assert_eq!(editor.selections.ranges(cx), [6..6]);
2268
2269 editor
2270 })
2271 .1;
2272
2273 _ = cx
2274 .add_window(Default::default(), |cx| {
2275 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
2276
2277 editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
2278 editor.transpose(&Default::default(), cx);
2279 assert_eq!(editor.text(cx), "bacd\ne");
2280 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
2281
2282 editor.transpose(&Default::default(), cx);
2283 assert_eq!(editor.text(cx), "bcade\n");
2284 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
2285
2286 editor.transpose(&Default::default(), cx);
2287 assert_eq!(editor.text(cx), "bcda\ne");
2288 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
2289
2290 editor.transpose(&Default::default(), cx);
2291 assert_eq!(editor.text(cx), "bcade\n");
2292 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
2293
2294 editor.transpose(&Default::default(), cx);
2295 assert_eq!(editor.text(cx), "bcaed\n");
2296 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
2297
2298 editor
2299 })
2300 .1;
2301
2302 _ = cx
2303 .add_window(Default::default(), |cx| {
2304 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
2305
2306 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
2307 editor.transpose(&Default::default(), cx);
2308 assert_eq!(editor.text(cx), "🏀🍐✋");
2309 assert_eq!(editor.selections.ranges(cx), [8..8]);
2310
2311 editor.transpose(&Default::default(), cx);
2312 assert_eq!(editor.text(cx), "🏀✋🍐");
2313 assert_eq!(editor.selections.ranges(cx), [11..11]);
2314
2315 editor.transpose(&Default::default(), cx);
2316 assert_eq!(editor.text(cx), "🏀🍐✋");
2317 assert_eq!(editor.selections.ranges(cx), [11..11]);
2318
2319 editor
2320 })
2321 .1;
2322}
2323
2324#[gpui::test]
2325async fn test_clipboard(cx: &mut gpui::TestAppContext) {
2326 let mut cx = EditorTestContext::new(cx);
2327
2328 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
2329 cx.update_editor(|e, cx| e.cut(&Cut, cx));
2330 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
2331
2332 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
2333 cx.set_state("two ˇfour ˇsix ˇ");
2334 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2335 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
2336
2337 // Paste again but with only two cursors. Since the number of cursors doesn't
2338 // match the number of slices in the clipboard, the entire clipboard text
2339 // is pasted at each cursor.
2340 cx.set_state("ˇtwo one✅ four three six five ˇ");
2341 cx.update_editor(|e, cx| {
2342 e.handle_input("( ", cx);
2343 e.paste(&Paste, cx);
2344 e.handle_input(") ", cx);
2345 });
2346 cx.assert_editor_state(indoc! {"
2347 ( one✅
2348 three
2349 five ) ˇtwo one✅ four three six five ( one✅
2350 three
2351 five ) ˇ"});
2352
2353 // Cut with three selections, one of which is full-line.
2354 cx.set_state(indoc! {"
2355 1«2ˇ»3
2356 4ˇ567
2357 «8ˇ»9"});
2358 cx.update_editor(|e, cx| e.cut(&Cut, cx));
2359 cx.assert_editor_state(indoc! {"
2360 1ˇ3
2361 ˇ9"});
2362
2363 // Paste with three selections, noticing how the copied selection that was full-line
2364 // gets inserted before the second cursor.
2365 cx.set_state(indoc! {"
2366 1ˇ3
2367 9ˇ
2368 «oˇ»ne"});
2369 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2370 cx.assert_editor_state(indoc! {"
2371 12ˇ3
2372 4567
2373 9ˇ
2374 8ˇne"});
2375
2376 // Copy with a single cursor only, which writes the whole line into the clipboard.
2377 cx.set_state(indoc! {"
2378 The quick brown
2379 fox juˇmps over
2380 the lazy dog"});
2381 cx.update_editor(|e, cx| e.copy(&Copy, cx));
2382 cx.cx.assert_clipboard_content(Some("fox jumps over\n"));
2383
2384 // Paste with three selections, noticing how the copied full-line selection is inserted
2385 // before the empty selections but replaces the selection that is non-empty.
2386 cx.set_state(indoc! {"
2387 Tˇhe quick brown
2388 «foˇ»x jumps over
2389 tˇhe lazy dog"});
2390 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2391 cx.assert_editor_state(indoc! {"
2392 fox jumps over
2393 Tˇhe quick brown
2394 fox jumps over
2395 ˇx jumps over
2396 fox jumps over
2397 tˇhe lazy dog"});
2398}
2399
2400#[gpui::test]
2401async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
2402 let mut cx = EditorTestContext::new(cx);
2403 let language = Arc::new(Language::new(
2404 LanguageConfig::default(),
2405 Some(tree_sitter_rust::language()),
2406 ));
2407 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2408
2409 // Cut an indented block, without the leading whitespace.
2410 cx.set_state(indoc! {"
2411 const a: B = (
2412 c(),
2413 «d(
2414 e,
2415 f
2416 )ˇ»
2417 );
2418 "});
2419 cx.update_editor(|e, cx| e.cut(&Cut, cx));
2420 cx.assert_editor_state(indoc! {"
2421 const a: B = (
2422 c(),
2423 ˇ
2424 );
2425 "});
2426
2427 // Paste it at the same position.
2428 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2429 cx.assert_editor_state(indoc! {"
2430 const a: B = (
2431 c(),
2432 d(
2433 e,
2434 f
2435 )ˇ
2436 );
2437 "});
2438
2439 // Paste it at a line with a lower indent level.
2440 cx.set_state(indoc! {"
2441 ˇ
2442 const a: B = (
2443 c(),
2444 );
2445 "});
2446 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2447 cx.assert_editor_state(indoc! {"
2448 d(
2449 e,
2450 f
2451 )ˇ
2452 const a: B = (
2453 c(),
2454 );
2455 "});
2456
2457 // Cut an indented block, with the leading whitespace.
2458 cx.set_state(indoc! {"
2459 const a: B = (
2460 c(),
2461 « d(
2462 e,
2463 f
2464 )
2465 ˇ»);
2466 "});
2467 cx.update_editor(|e, cx| e.cut(&Cut, cx));
2468 cx.assert_editor_state(indoc! {"
2469 const a: B = (
2470 c(),
2471 ˇ);
2472 "});
2473
2474 // Paste it at the same position.
2475 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2476 cx.assert_editor_state(indoc! {"
2477 const a: B = (
2478 c(),
2479 d(
2480 e,
2481 f
2482 )
2483 ˇ);
2484 "});
2485
2486 // Paste it at a line with a higher indent level.
2487 cx.set_state(indoc! {"
2488 const a: B = (
2489 c(),
2490 d(
2491 e,
2492 fˇ
2493 )
2494 );
2495 "});
2496 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2497 cx.assert_editor_state(indoc! {"
2498 const a: B = (
2499 c(),
2500 d(
2501 e,
2502 f d(
2503 e,
2504 f
2505 )
2506 ˇ
2507 )
2508 );
2509 "});
2510}
2511
2512#[gpui::test]
2513fn test_select_all(cx: &mut gpui::MutableAppContext) {
2514 cx.set_global(Settings::test(cx));
2515 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
2516 let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
2517 view.update(cx, |view, cx| {
2518 view.select_all(&SelectAll, cx);
2519 assert_eq!(
2520 view.selections.display_ranges(cx),
2521 &[DisplayPoint::new(0, 0)..DisplayPoint::new(2, 3)]
2522 );
2523 });
2524}
2525
2526#[gpui::test]
2527fn test_select_line(cx: &mut gpui::MutableAppContext) {
2528 cx.set_global(Settings::test(cx));
2529 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
2530 let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
2531 view.update(cx, |view, cx| {
2532 view.change_selections(None, cx, |s| {
2533 s.select_display_ranges([
2534 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
2535 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
2536 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
2537 DisplayPoint::new(4, 2)..DisplayPoint::new(4, 2),
2538 ])
2539 });
2540 view.select_line(&SelectLine, cx);
2541 assert_eq!(
2542 view.selections.display_ranges(cx),
2543 vec![
2544 DisplayPoint::new(0, 0)..DisplayPoint::new(2, 0),
2545 DisplayPoint::new(4, 0)..DisplayPoint::new(5, 0),
2546 ]
2547 );
2548 });
2549
2550 view.update(cx, |view, cx| {
2551 view.select_line(&SelectLine, cx);
2552 assert_eq!(
2553 view.selections.display_ranges(cx),
2554 vec![
2555 DisplayPoint::new(0, 0)..DisplayPoint::new(3, 0),
2556 DisplayPoint::new(4, 0)..DisplayPoint::new(5, 5),
2557 ]
2558 );
2559 });
2560
2561 view.update(cx, |view, cx| {
2562 view.select_line(&SelectLine, cx);
2563 assert_eq!(
2564 view.selections.display_ranges(cx),
2565 vec![DisplayPoint::new(0, 0)..DisplayPoint::new(5, 5)]
2566 );
2567 });
2568}
2569
2570#[gpui::test]
2571fn test_split_selection_into_lines(cx: &mut gpui::MutableAppContext) {
2572 cx.set_global(Settings::test(cx));
2573 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
2574 let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
2575 view.update(cx, |view, cx| {
2576 view.fold_ranges(
2577 vec![
2578 Point::new(0, 2)..Point::new(1, 2),
2579 Point::new(2, 3)..Point::new(4, 1),
2580 Point::new(7, 0)..Point::new(8, 4),
2581 ],
2582 cx,
2583 );
2584 view.change_selections(None, cx, |s| {
2585 s.select_display_ranges([
2586 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
2587 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
2588 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
2589 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
2590 ])
2591 });
2592 assert_eq!(view.display_text(cx), "aa…bbb\nccc…eeee\nfffff\nggggg\n…i");
2593 });
2594
2595 view.update(cx, |view, cx| {
2596 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
2597 assert_eq!(
2598 view.display_text(cx),
2599 "aaaaa\nbbbbb\nccc…eeee\nfffff\nggggg\n…i"
2600 );
2601 assert_eq!(
2602 view.selections.display_ranges(cx),
2603 [
2604 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
2605 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
2606 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 0),
2607 DisplayPoint::new(5, 4)..DisplayPoint::new(5, 4)
2608 ]
2609 );
2610 });
2611
2612 view.update(cx, |view, cx| {
2613 view.change_selections(None, cx, |s| {
2614 s.select_display_ranges([DisplayPoint::new(5, 0)..DisplayPoint::new(0, 1)])
2615 });
2616 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
2617 assert_eq!(
2618 view.display_text(cx),
2619 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
2620 );
2621 assert_eq!(
2622 view.selections.display_ranges(cx),
2623 [
2624 DisplayPoint::new(0, 5)..DisplayPoint::new(0, 5),
2625 DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
2626 DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
2627 DisplayPoint::new(3, 5)..DisplayPoint::new(3, 5),
2628 DisplayPoint::new(4, 5)..DisplayPoint::new(4, 5),
2629 DisplayPoint::new(5, 5)..DisplayPoint::new(5, 5),
2630 DisplayPoint::new(6, 5)..DisplayPoint::new(6, 5),
2631 DisplayPoint::new(7, 0)..DisplayPoint::new(7, 0)
2632 ]
2633 );
2634 });
2635}
2636
2637#[gpui::test]
2638fn test_add_selection_above_below(cx: &mut gpui::MutableAppContext) {
2639 cx.set_global(Settings::test(cx));
2640 let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
2641 let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
2642
2643 view.update(cx, |view, cx| {
2644 view.change_selections(None, cx, |s| {
2645 s.select_display_ranges([DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)])
2646 });
2647 });
2648 view.update(cx, |view, cx| {
2649 view.add_selection_above(&AddSelectionAbove, cx);
2650 assert_eq!(
2651 view.selections.display_ranges(cx),
2652 vec![
2653 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
2654 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)
2655 ]
2656 );
2657 });
2658
2659 view.update(cx, |view, cx| {
2660 view.add_selection_above(&AddSelectionAbove, cx);
2661 assert_eq!(
2662 view.selections.display_ranges(cx),
2663 vec![
2664 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
2665 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)
2666 ]
2667 );
2668 });
2669
2670 view.update(cx, |view, cx| {
2671 view.add_selection_below(&AddSelectionBelow, cx);
2672 assert_eq!(
2673 view.selections.display_ranges(cx),
2674 vec![DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)]
2675 );
2676
2677 view.undo_selection(&UndoSelection, cx);
2678 assert_eq!(
2679 view.selections.display_ranges(cx),
2680 vec![
2681 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
2682 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)
2683 ]
2684 );
2685
2686 view.redo_selection(&RedoSelection, cx);
2687 assert_eq!(
2688 view.selections.display_ranges(cx),
2689 vec![DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)]
2690 );
2691 });
2692
2693 view.update(cx, |view, cx| {
2694 view.add_selection_below(&AddSelectionBelow, cx);
2695 assert_eq!(
2696 view.selections.display_ranges(cx),
2697 vec![
2698 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3),
2699 DisplayPoint::new(4, 3)..DisplayPoint::new(4, 3)
2700 ]
2701 );
2702 });
2703
2704 view.update(cx, |view, cx| {
2705 view.add_selection_below(&AddSelectionBelow, cx);
2706 assert_eq!(
2707 view.selections.display_ranges(cx),
2708 vec![
2709 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3),
2710 DisplayPoint::new(4, 3)..DisplayPoint::new(4, 3)
2711 ]
2712 );
2713 });
2714
2715 view.update(cx, |view, cx| {
2716 view.change_selections(None, cx, |s| {
2717 s.select_display_ranges([DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)])
2718 });
2719 });
2720 view.update(cx, |view, cx| {
2721 view.add_selection_below(&AddSelectionBelow, cx);
2722 assert_eq!(
2723 view.selections.display_ranges(cx),
2724 vec![
2725 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3),
2726 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 3)
2727 ]
2728 );
2729 });
2730
2731 view.update(cx, |view, cx| {
2732 view.add_selection_below(&AddSelectionBelow, cx);
2733 assert_eq!(
2734 view.selections.display_ranges(cx),
2735 vec![
2736 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3),
2737 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 3)
2738 ]
2739 );
2740 });
2741
2742 view.update(cx, |view, cx| {
2743 view.add_selection_above(&AddSelectionAbove, cx);
2744 assert_eq!(
2745 view.selections.display_ranges(cx),
2746 vec![DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)]
2747 );
2748 });
2749
2750 view.update(cx, |view, cx| {
2751 view.add_selection_above(&AddSelectionAbove, cx);
2752 assert_eq!(
2753 view.selections.display_ranges(cx),
2754 vec![DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)]
2755 );
2756 });
2757
2758 view.update(cx, |view, cx| {
2759 view.change_selections(None, cx, |s| {
2760 s.select_display_ranges([DisplayPoint::new(0, 1)..DisplayPoint::new(1, 4)])
2761 });
2762 view.add_selection_below(&AddSelectionBelow, cx);
2763 assert_eq!(
2764 view.selections.display_ranges(cx),
2765 vec![
2766 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
2767 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4),
2768 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2),
2769 ]
2770 );
2771 });
2772
2773 view.update(cx, |view, cx| {
2774 view.add_selection_below(&AddSelectionBelow, cx);
2775 assert_eq!(
2776 view.selections.display_ranges(cx),
2777 vec![
2778 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
2779 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4),
2780 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2),
2781 DisplayPoint::new(4, 1)..DisplayPoint::new(4, 4),
2782 ]
2783 );
2784 });
2785
2786 view.update(cx, |view, cx| {
2787 view.add_selection_above(&AddSelectionAbove, cx);
2788 assert_eq!(
2789 view.selections.display_ranges(cx),
2790 vec![
2791 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
2792 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4),
2793 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2),
2794 ]
2795 );
2796 });
2797
2798 view.update(cx, |view, cx| {
2799 view.change_selections(None, cx, |s| {
2800 s.select_display_ranges([DisplayPoint::new(4, 3)..DisplayPoint::new(1, 1)])
2801 });
2802 });
2803 view.update(cx, |view, cx| {
2804 view.add_selection_above(&AddSelectionAbove, cx);
2805 assert_eq!(
2806 view.selections.display_ranges(cx),
2807 vec![
2808 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 1),
2809 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 1),
2810 DisplayPoint::new(3, 2)..DisplayPoint::new(3, 1),
2811 DisplayPoint::new(4, 3)..DisplayPoint::new(4, 1),
2812 ]
2813 );
2814 });
2815
2816 view.update(cx, |view, cx| {
2817 view.add_selection_below(&AddSelectionBelow, cx);
2818 assert_eq!(
2819 view.selections.display_ranges(cx),
2820 vec![
2821 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 1),
2822 DisplayPoint::new(3, 2)..DisplayPoint::new(3, 1),
2823 DisplayPoint::new(4, 3)..DisplayPoint::new(4, 1),
2824 ]
2825 );
2826 });
2827}
2828
2829#[gpui::test]
2830async fn test_select_next(cx: &mut gpui::TestAppContext) {
2831 let mut cx = EditorTestContext::new(cx);
2832 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
2833
2834 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx));
2835 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
2836
2837 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx));
2838 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
2839
2840 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
2841 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
2842
2843 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
2844 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
2845
2846 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx));
2847 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
2848
2849 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx));
2850 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
2851}
2852
2853#[gpui::test]
2854async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
2855 cx.update(|cx| cx.set_global(Settings::test(cx)));
2856 let language = Arc::new(Language::new(
2857 LanguageConfig::default(),
2858 Some(tree_sitter_rust::language()),
2859 ));
2860
2861 let text = r#"
2862 use mod1::mod2::{mod3, mod4};
2863
2864 fn fn_1(param1: bool, param2: &str) {
2865 let var1 = "text";
2866 }
2867 "#
2868 .unindent();
2869
2870 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
2871 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
2872 let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
2873 view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
2874 .await;
2875
2876 view.update(cx, |view, cx| {
2877 view.change_selections(None, cx, |s| {
2878 s.select_display_ranges([
2879 DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
2880 DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
2881 DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
2882 ]);
2883 });
2884 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
2885 });
2886 assert_eq!(
2887 view.update(cx, |view, cx| { view.selections.display_ranges(cx) }),
2888 &[
2889 DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27),
2890 DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
2891 DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21),
2892 ]
2893 );
2894
2895 view.update(cx, |view, cx| {
2896 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
2897 });
2898 assert_eq!(
2899 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
2900 &[
2901 DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
2902 DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0),
2903 ]
2904 );
2905
2906 view.update(cx, |view, cx| {
2907 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
2908 });
2909 assert_eq!(
2910 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
2911 &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)]
2912 );
2913
2914 // Trying to expand the selected syntax node one more time has no effect.
2915 view.update(cx, |view, cx| {
2916 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
2917 });
2918 assert_eq!(
2919 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
2920 &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)]
2921 );
2922
2923 view.update(cx, |view, cx| {
2924 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
2925 });
2926 assert_eq!(
2927 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
2928 &[
2929 DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
2930 DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0),
2931 ]
2932 );
2933
2934 view.update(cx, |view, cx| {
2935 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
2936 });
2937 assert_eq!(
2938 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
2939 &[
2940 DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27),
2941 DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
2942 DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21),
2943 ]
2944 );
2945
2946 view.update(cx, |view, cx| {
2947 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
2948 });
2949 assert_eq!(
2950 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
2951 &[
2952 DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
2953 DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
2954 DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
2955 ]
2956 );
2957
2958 // Trying to shrink the selected syntax node one more time has no effect.
2959 view.update(cx, |view, cx| {
2960 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
2961 });
2962 assert_eq!(
2963 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
2964 &[
2965 DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
2966 DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
2967 DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
2968 ]
2969 );
2970
2971 // Ensure that we keep expanding the selection if the larger selection starts or ends within
2972 // a fold.
2973 view.update(cx, |view, cx| {
2974 view.fold_ranges(
2975 vec![
2976 Point::new(0, 21)..Point::new(0, 24),
2977 Point::new(3, 20)..Point::new(3, 22),
2978 ],
2979 cx,
2980 );
2981 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
2982 });
2983 assert_eq!(
2984 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
2985 &[
2986 DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
2987 DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
2988 DisplayPoint::new(3, 4)..DisplayPoint::new(3, 23),
2989 ]
2990 );
2991}
2992
2993#[gpui::test]
2994async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
2995 cx.update(|cx| cx.set_global(Settings::test(cx)));
2996 let language = Arc::new(
2997 Language::new(
2998 LanguageConfig {
2999 brackets: vec![
3000 BracketPair {
3001 start: "{".to_string(),
3002 end: "}".to_string(),
3003 close: false,
3004 newline: true,
3005 },
3006 BracketPair {
3007 start: "(".to_string(),
3008 end: ")".to_string(),
3009 close: false,
3010 newline: true,
3011 },
3012 ],
3013 ..Default::default()
3014 },
3015 Some(tree_sitter_rust::language()),
3016 )
3017 .with_indents_query(
3018 r#"
3019 (_ "(" ")" @end) @indent
3020 (_ "{" "}" @end) @indent
3021 "#,
3022 )
3023 .unwrap(),
3024 );
3025
3026 let text = "fn a() {}";
3027
3028 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
3029 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
3030 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
3031 editor
3032 .condition(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
3033 .await;
3034
3035 editor.update(cx, |editor, cx| {
3036 editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
3037 editor.newline(&Newline, cx);
3038 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
3039 assert_eq!(
3040 editor.selections.ranges(cx),
3041 &[
3042 Point::new(1, 4)..Point::new(1, 4),
3043 Point::new(3, 4)..Point::new(3, 4),
3044 Point::new(5, 0)..Point::new(5, 0)
3045 ]
3046 );
3047 });
3048}
3049
3050#[gpui::test]
3051async fn test_autoclose_pairs(cx: &mut gpui::TestAppContext) {
3052 let mut cx = EditorTestContext::new(cx);
3053
3054 let language = Arc::new(Language::new(
3055 LanguageConfig {
3056 brackets: vec![
3057 BracketPair {
3058 start: "{".to_string(),
3059 end: "}".to_string(),
3060 close: true,
3061 newline: true,
3062 },
3063 BracketPair {
3064 start: "(".to_string(),
3065 end: ")".to_string(),
3066 close: true,
3067 newline: true,
3068 },
3069 BracketPair {
3070 start: "/*".to_string(),
3071 end: " */".to_string(),
3072 close: true,
3073 newline: true,
3074 },
3075 BracketPair {
3076 start: "[".to_string(),
3077 end: "]".to_string(),
3078 close: false,
3079 newline: true,
3080 },
3081 BracketPair {
3082 start: "\"".to_string(),
3083 end: "\"".to_string(),
3084 close: true,
3085 newline: false,
3086 },
3087 ],
3088 autoclose_before: "})]".to_string(),
3089 ..Default::default()
3090 },
3091 Some(tree_sitter_rust::language()),
3092 ));
3093
3094 let registry = Arc::new(LanguageRegistry::test());
3095 registry.add(language.clone());
3096 cx.update_buffer(|buffer, cx| {
3097 buffer.set_language_registry(registry);
3098 buffer.set_language(Some(language), cx);
3099 });
3100
3101 cx.set_state(
3102 &r#"
3103 🏀ˇ
3104 εˇ
3105 ❤️ˇ
3106 "#
3107 .unindent(),
3108 );
3109
3110 // autoclose multiple nested brackets at multiple cursors
3111 cx.update_editor(|view, cx| {
3112 view.handle_input("{", cx);
3113 view.handle_input("{", cx);
3114 view.handle_input("{", cx);
3115 });
3116 cx.assert_editor_state(
3117 &"
3118 🏀{{{ˇ}}}
3119 ε{{{ˇ}}}
3120 ❤️{{{ˇ}}}
3121 "
3122 .unindent(),
3123 );
3124
3125 // insert a different closing bracket
3126 cx.update_editor(|view, cx| {
3127 view.handle_input(")", cx);
3128 });
3129 cx.assert_editor_state(
3130 &"
3131 🏀{{{)ˇ}}}
3132 ε{{{)ˇ}}}
3133 ❤️{{{)ˇ}}}
3134 "
3135 .unindent(),
3136 );
3137
3138 // skip over the auto-closed brackets when typing a closing bracket
3139 cx.update_editor(|view, cx| {
3140 view.move_right(&MoveRight, cx);
3141 view.handle_input("}", cx);
3142 view.handle_input("}", cx);
3143 view.handle_input("}", cx);
3144 });
3145 cx.assert_editor_state(
3146 &"
3147 🏀{{{)}}}}ˇ
3148 ε{{{)}}}}ˇ
3149 ❤️{{{)}}}}ˇ
3150 "
3151 .unindent(),
3152 );
3153
3154 // autoclose multi-character pairs
3155 cx.set_state(
3156 &"
3157 ˇ
3158 ˇ
3159 "
3160 .unindent(),
3161 );
3162 cx.update_editor(|view, cx| {
3163 view.handle_input("/", cx);
3164 view.handle_input("*", cx);
3165 });
3166 cx.assert_editor_state(
3167 &"
3168 /*ˇ */
3169 /*ˇ */
3170 "
3171 .unindent(),
3172 );
3173
3174 // one cursor autocloses a multi-character pair, one cursor
3175 // does not autoclose.
3176 cx.set_state(
3177 &"
3178 /ˇ
3179 ˇ
3180 "
3181 .unindent(),
3182 );
3183 cx.update_editor(|view, cx| view.handle_input("*", cx));
3184 cx.assert_editor_state(
3185 &"
3186 /*ˇ */
3187 *ˇ
3188 "
3189 .unindent(),
3190 );
3191
3192 // Don't autoclose if the next character isn't whitespace and isn't
3193 // listed in the language's "autoclose_before" section.
3194 cx.set_state("ˇa b");
3195 cx.update_editor(|view, cx| view.handle_input("{", cx));
3196 cx.assert_editor_state("{ˇa b");
3197
3198 // Don't autoclose if `close` is false for the bracket pair
3199 cx.set_state("ˇ");
3200 cx.update_editor(|view, cx| view.handle_input("[", cx));
3201 cx.assert_editor_state("[ˇ");
3202
3203 // Surround with brackets if text is selected
3204 cx.set_state("«aˇ» b");
3205 cx.update_editor(|view, cx| view.handle_input("{", cx));
3206 cx.assert_editor_state("{«aˇ»} b");
3207
3208 // Autclose pair where the start and end characters are the same
3209 cx.set_state("aˇ");
3210 cx.update_editor(|view, cx| view.handle_input("\"", cx));
3211 cx.assert_editor_state("a\"ˇ\"");
3212 cx.update_editor(|view, cx| view.handle_input("\"", cx));
3213 cx.assert_editor_state("a\"\"ˇ");
3214}
3215
3216#[gpui::test]
3217async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
3218 let mut cx = EditorTestContext::new(cx);
3219
3220 let html_language = Arc::new(
3221 Language::new(
3222 LanguageConfig {
3223 name: "HTML".into(),
3224 brackets: vec![
3225 BracketPair {
3226 start: "<".into(),
3227 end: ">".into(),
3228 close: true,
3229 ..Default::default()
3230 },
3231 BracketPair {
3232 start: "{".into(),
3233 end: "}".into(),
3234 close: true,
3235 ..Default::default()
3236 },
3237 BracketPair {
3238 start: "(".into(),
3239 end: ")".into(),
3240 close: true,
3241 ..Default::default()
3242 },
3243 ],
3244 autoclose_before: "})]>".into(),
3245 ..Default::default()
3246 },
3247 Some(tree_sitter_html::language()),
3248 )
3249 .with_injection_query(
3250 r#"
3251 (script_element
3252 (raw_text) @content
3253 (#set! "language" "javascript"))
3254 "#,
3255 )
3256 .unwrap(),
3257 );
3258
3259 let javascript_language = Arc::new(Language::new(
3260 LanguageConfig {
3261 name: "JavaScript".into(),
3262 brackets: vec![
3263 BracketPair {
3264 start: "/*".into(),
3265 end: " */".into(),
3266 close: true,
3267 ..Default::default()
3268 },
3269 BracketPair {
3270 start: "{".into(),
3271 end: "}".into(),
3272 close: true,
3273 ..Default::default()
3274 },
3275 BracketPair {
3276 start: "(".into(),
3277 end: ")".into(),
3278 close: true,
3279 ..Default::default()
3280 },
3281 ],
3282 autoclose_before: "})]>".into(),
3283 ..Default::default()
3284 },
3285 Some(tree_sitter_javascript::language()),
3286 ));
3287
3288 let registry = Arc::new(LanguageRegistry::test());
3289 registry.add(html_language.clone());
3290 registry.add(javascript_language.clone());
3291
3292 cx.update_buffer(|buffer, cx| {
3293 buffer.set_language_registry(registry);
3294 buffer.set_language(Some(html_language), cx);
3295 });
3296
3297 cx.set_state(
3298 &r#"
3299 <body>ˇ
3300 <script>
3301 var x = 1;ˇ
3302 </script>
3303 </body>ˇ
3304 "#
3305 .unindent(),
3306 );
3307
3308 // Precondition: different languages are active at different locations.
3309 cx.update_editor(|editor, cx| {
3310 let snapshot = editor.snapshot(cx);
3311 let cursors = editor.selections.ranges::<usize>(cx);
3312 let languages = cursors
3313 .iter()
3314 .map(|c| snapshot.language_at(c.start).unwrap().name())
3315 .collect::<Vec<_>>();
3316 assert_eq!(
3317 languages,
3318 &["HTML".into(), "JavaScript".into(), "HTML".into()]
3319 );
3320 });
3321
3322 // Angle brackets autoclose in HTML, but not JavaScript.
3323 cx.update_editor(|editor, cx| {
3324 editor.handle_input("<", cx);
3325 editor.handle_input("a", cx);
3326 });
3327 cx.assert_editor_state(
3328 &r#"
3329 <body><aˇ>
3330 <script>
3331 var x = 1;<aˇ
3332 </script>
3333 </body><aˇ>
3334 "#
3335 .unindent(),
3336 );
3337
3338 // Curly braces and parens autoclose in both HTML and JavaScript.
3339 cx.update_editor(|editor, cx| {
3340 editor.handle_input(" b=", cx);
3341 editor.handle_input("{", cx);
3342 editor.handle_input("c", cx);
3343 editor.handle_input("(", cx);
3344 });
3345 cx.assert_editor_state(
3346 &r#"
3347 <body><a b={c(ˇ)}>
3348 <script>
3349 var x = 1;<a b={c(ˇ)}
3350 </script>
3351 </body><a b={c(ˇ)}>
3352 "#
3353 .unindent(),
3354 );
3355
3356 // Brackets that were already autoclosed are skipped.
3357 cx.update_editor(|editor, cx| {
3358 editor.handle_input(")", cx);
3359 editor.handle_input("d", cx);
3360 editor.handle_input("}", cx);
3361 });
3362 cx.assert_editor_state(
3363 &r#"
3364 <body><a b={c()d}ˇ>
3365 <script>
3366 var x = 1;<a b={c()d}ˇ
3367 </script>
3368 </body><a b={c()d}ˇ>
3369 "#
3370 .unindent(),
3371 );
3372 cx.update_editor(|editor, cx| {
3373 editor.handle_input(">", cx);
3374 });
3375 cx.assert_editor_state(
3376 &r#"
3377 <body><a b={c()d}>ˇ
3378 <script>
3379 var x = 1;<a b={c()d}>ˇ
3380 </script>
3381 </body><a b={c()d}>ˇ
3382 "#
3383 .unindent(),
3384 );
3385
3386 // Reset
3387 cx.set_state(
3388 &r#"
3389 <body>ˇ
3390 <script>
3391 var x = 1;ˇ
3392 </script>
3393 </body>ˇ
3394 "#
3395 .unindent(),
3396 );
3397
3398 cx.update_editor(|editor, cx| {
3399 editor.handle_input("<", cx);
3400 });
3401 cx.assert_editor_state(
3402 &r#"
3403 <body><ˇ>
3404 <script>
3405 var x = 1;<ˇ
3406 </script>
3407 </body><ˇ>
3408 "#
3409 .unindent(),
3410 );
3411
3412 // When backspacing, the closing angle brackets are removed.
3413 cx.update_editor(|editor, cx| {
3414 editor.backspace(&Backspace, cx);
3415 });
3416 cx.assert_editor_state(
3417 &r#"
3418 <body>ˇ
3419 <script>
3420 var x = 1;ˇ
3421 </script>
3422 </body>ˇ
3423 "#
3424 .unindent(),
3425 );
3426
3427 // Block comments autoclose in JavaScript, but not HTML.
3428 cx.update_editor(|editor, cx| {
3429 editor.handle_input("/", cx);
3430 editor.handle_input("*", cx);
3431 });
3432 cx.assert_editor_state(
3433 &r#"
3434 <body>/*ˇ
3435 <script>
3436 var x = 1;/*ˇ */
3437 </script>
3438 </body>/*ˇ
3439 "#
3440 .unindent(),
3441 );
3442}
3443
3444#[gpui::test]
3445async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
3446 cx.update(|cx| cx.set_global(Settings::test(cx)));
3447 let language = Arc::new(Language::new(
3448 LanguageConfig {
3449 brackets: vec![BracketPair {
3450 start: "{".to_string(),
3451 end: "}".to_string(),
3452 close: true,
3453 newline: true,
3454 }],
3455 ..Default::default()
3456 },
3457 Some(tree_sitter_rust::language()),
3458 ));
3459
3460 let text = r#"
3461 a
3462 b
3463 c
3464 "#
3465 .unindent();
3466
3467 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
3468 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
3469 let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
3470 view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
3471 .await;
3472
3473 view.update(cx, |view, cx| {
3474 view.change_selections(None, cx, |s| {
3475 s.select_display_ranges([
3476 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
3477 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
3478 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1),
3479 ])
3480 });
3481
3482 view.handle_input("{", cx);
3483 view.handle_input("{", cx);
3484 view.handle_input("{", cx);
3485 assert_eq!(
3486 view.text(cx),
3487 "
3488 {{{a}}}
3489 {{{b}}}
3490 {{{c}}}
3491 "
3492 .unindent()
3493 );
3494 assert_eq!(
3495 view.selections.display_ranges(cx),
3496 [
3497 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 4),
3498 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 4),
3499 DisplayPoint::new(2, 3)..DisplayPoint::new(2, 4)
3500 ]
3501 );
3502
3503 view.undo(&Undo, cx);
3504 assert_eq!(
3505 view.text(cx),
3506 "
3507 a
3508 b
3509 c
3510 "
3511 .unindent()
3512 );
3513 assert_eq!(
3514 view.selections.display_ranges(cx),
3515 [
3516 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
3517 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
3518 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1)
3519 ]
3520 );
3521 });
3522}
3523
3524#[gpui::test]
3525async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
3526 cx.update(|cx| cx.set_global(Settings::test(cx)));
3527 let language = Arc::new(Language::new(
3528 LanguageConfig {
3529 brackets: vec![BracketPair {
3530 start: "{".to_string(),
3531 end: "}".to_string(),
3532 close: true,
3533 newline: true,
3534 }],
3535 autoclose_before: "}".to_string(),
3536 ..Default::default()
3537 },
3538 Some(tree_sitter_rust::language()),
3539 ));
3540
3541 let text = r#"
3542 a
3543 b
3544 c
3545 "#
3546 .unindent();
3547
3548 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
3549 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
3550 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
3551 editor
3552 .condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
3553 .await;
3554
3555 editor.update(cx, |editor, cx| {
3556 editor.change_selections(None, cx, |s| {
3557 s.select_ranges([
3558 Point::new(0, 1)..Point::new(0, 1),
3559 Point::new(1, 1)..Point::new(1, 1),
3560 Point::new(2, 1)..Point::new(2, 1),
3561 ])
3562 });
3563
3564 editor.handle_input("{", cx);
3565 editor.handle_input("{", cx);
3566 editor.handle_input("_", cx);
3567 assert_eq!(
3568 editor.text(cx),
3569 "
3570 a{{_}}
3571 b{{_}}
3572 c{{_}}
3573 "
3574 .unindent()
3575 );
3576 assert_eq!(
3577 editor.selections.ranges::<Point>(cx),
3578 [
3579 Point::new(0, 4)..Point::new(0, 4),
3580 Point::new(1, 4)..Point::new(1, 4),
3581 Point::new(2, 4)..Point::new(2, 4)
3582 ]
3583 );
3584
3585 editor.backspace(&Default::default(), cx);
3586 editor.backspace(&Default::default(), cx);
3587 assert_eq!(
3588 editor.text(cx),
3589 "
3590 a{}
3591 b{}
3592 c{}
3593 "
3594 .unindent()
3595 );
3596 assert_eq!(
3597 editor.selections.ranges::<Point>(cx),
3598 [
3599 Point::new(0, 2)..Point::new(0, 2),
3600 Point::new(1, 2)..Point::new(1, 2),
3601 Point::new(2, 2)..Point::new(2, 2)
3602 ]
3603 );
3604
3605 editor.delete_to_previous_word_start(&Default::default(), cx);
3606 assert_eq!(
3607 editor.text(cx),
3608 "
3609 a
3610 b
3611 c
3612 "
3613 .unindent()
3614 );
3615 assert_eq!(
3616 editor.selections.ranges::<Point>(cx),
3617 [
3618 Point::new(0, 1)..Point::new(0, 1),
3619 Point::new(1, 1)..Point::new(1, 1),
3620 Point::new(2, 1)..Point::new(2, 1)
3621 ]
3622 );
3623 });
3624}
3625
3626#[gpui::test]
3627async fn test_snippets(cx: &mut gpui::TestAppContext) {
3628 cx.update(|cx| cx.set_global(Settings::test(cx)));
3629
3630 let (text, insertion_ranges) = marked_text_ranges(
3631 indoc! {"
3632 a.ˇ b
3633 a.ˇ b
3634 a.ˇ b
3635 "},
3636 false,
3637 );
3638
3639 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
3640 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
3641
3642 editor.update(cx, |editor, cx| {
3643 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
3644
3645 editor
3646 .insert_snippet(&insertion_ranges, snippet, cx)
3647 .unwrap();
3648
3649 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
3650 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
3651 assert_eq!(editor.text(cx), expected_text);
3652 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
3653 }
3654
3655 assert(
3656 editor,
3657 cx,
3658 indoc! {"
3659 a.f(«one», two, «three») b
3660 a.f(«one», two, «three») b
3661 a.f(«one», two, «three») b
3662 "},
3663 );
3664
3665 // Can't move earlier than the first tab stop
3666 assert!(!editor.move_to_prev_snippet_tabstop(cx));
3667 assert(
3668 editor,
3669 cx,
3670 indoc! {"
3671 a.f(«one», two, «three») b
3672 a.f(«one», two, «three») b
3673 a.f(«one», two, «three») b
3674 "},
3675 );
3676
3677 assert!(editor.move_to_next_snippet_tabstop(cx));
3678 assert(
3679 editor,
3680 cx,
3681 indoc! {"
3682 a.f(one, «two», three) b
3683 a.f(one, «two», three) b
3684 a.f(one, «two», three) b
3685 "},
3686 );
3687
3688 editor.move_to_prev_snippet_tabstop(cx);
3689 assert(
3690 editor,
3691 cx,
3692 indoc! {"
3693 a.f(«one», two, «three») b
3694 a.f(«one», two, «three») b
3695 a.f(«one», two, «three») b
3696 "},
3697 );
3698
3699 assert!(editor.move_to_next_snippet_tabstop(cx));
3700 assert(
3701 editor,
3702 cx,
3703 indoc! {"
3704 a.f(one, «two», three) b
3705 a.f(one, «two», three) b
3706 a.f(one, «two», three) b
3707 "},
3708 );
3709 assert!(editor.move_to_next_snippet_tabstop(cx));
3710 assert(
3711 editor,
3712 cx,
3713 indoc! {"
3714 a.f(one, two, three)ˇ b
3715 a.f(one, two, three)ˇ b
3716 a.f(one, two, three)ˇ b
3717 "},
3718 );
3719
3720 // As soon as the last tab stop is reached, snippet state is gone
3721 editor.move_to_prev_snippet_tabstop(cx);
3722 assert(
3723 editor,
3724 cx,
3725 indoc! {"
3726 a.f(one, two, three)ˇ b
3727 a.f(one, two, three)ˇ b
3728 a.f(one, two, three)ˇ b
3729 "},
3730 );
3731 });
3732}
3733
3734#[gpui::test]
3735async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
3736 cx.foreground().forbid_parking();
3737
3738 let mut language = Language::new(
3739 LanguageConfig {
3740 name: "Rust".into(),
3741 path_suffixes: vec!["rs".to_string()],
3742 ..Default::default()
3743 },
3744 Some(tree_sitter_rust::language()),
3745 );
3746 let mut fake_servers = language
3747 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
3748 capabilities: lsp::ServerCapabilities {
3749 document_formatting_provider: Some(lsp::OneOf::Left(true)),
3750 ..Default::default()
3751 },
3752 ..Default::default()
3753 }))
3754 .await;
3755
3756 let fs = FakeFs::new(cx.background());
3757 fs.insert_file("/file.rs", Default::default()).await;
3758
3759 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
3760 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
3761 let buffer = project
3762 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
3763 .await
3764 .unwrap();
3765
3766 cx.foreground().start_waiting();
3767 let fake_server = fake_servers.next().await.unwrap();
3768
3769 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
3770 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
3771 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
3772 assert!(cx.read(|cx| editor.is_dirty(cx)));
3773
3774 let save = cx.update(|cx| editor.save(project.clone(), cx));
3775 fake_server
3776 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
3777 assert_eq!(
3778 params.text_document.uri,
3779 lsp::Url::from_file_path("/file.rs").unwrap()
3780 );
3781 assert_eq!(params.options.tab_size, 4);
3782 Ok(Some(vec![lsp::TextEdit::new(
3783 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
3784 ", ".to_string(),
3785 )]))
3786 })
3787 .next()
3788 .await;
3789 cx.foreground().start_waiting();
3790 save.await.unwrap();
3791 assert_eq!(
3792 editor.read_with(cx, |editor, cx| editor.text(cx)),
3793 "one, two\nthree\n"
3794 );
3795 assert!(!cx.read(|cx| editor.is_dirty(cx)));
3796
3797 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
3798 assert!(cx.read(|cx| editor.is_dirty(cx)));
3799
3800 // Ensure we can still save even if formatting hangs.
3801 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
3802 assert_eq!(
3803 params.text_document.uri,
3804 lsp::Url::from_file_path("/file.rs").unwrap()
3805 );
3806 futures::future::pending::<()>().await;
3807 unreachable!()
3808 });
3809 let save = cx.update(|cx| editor.save(project.clone(), cx));
3810 cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
3811 cx.foreground().start_waiting();
3812 save.await.unwrap();
3813 assert_eq!(
3814 editor.read_with(cx, |editor, cx| editor.text(cx)),
3815 "one\ntwo\nthree\n"
3816 );
3817 assert!(!cx.read(|cx| editor.is_dirty(cx)));
3818
3819 // Set rust language override and assert overriden tabsize is sent to language server
3820 cx.update(|cx| {
3821 cx.update_global::<Settings, _, _>(|settings, _| {
3822 settings.language_overrides.insert(
3823 "Rust".into(),
3824 EditorSettings {
3825 tab_size: Some(8.try_into().unwrap()),
3826 ..Default::default()
3827 },
3828 );
3829 })
3830 });
3831
3832 let save = cx.update(|cx| editor.save(project.clone(), cx));
3833 fake_server
3834 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
3835 assert_eq!(
3836 params.text_document.uri,
3837 lsp::Url::from_file_path("/file.rs").unwrap()
3838 );
3839 assert_eq!(params.options.tab_size, 8);
3840 Ok(Some(vec![]))
3841 })
3842 .next()
3843 .await;
3844 cx.foreground().start_waiting();
3845 save.await.unwrap();
3846}
3847
3848#[gpui::test]
3849async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
3850 cx.foreground().forbid_parking();
3851
3852 let mut language = Language::new(
3853 LanguageConfig {
3854 name: "Rust".into(),
3855 path_suffixes: vec!["rs".to_string()],
3856 ..Default::default()
3857 },
3858 Some(tree_sitter_rust::language()),
3859 );
3860 let mut fake_servers = language
3861 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
3862 capabilities: lsp::ServerCapabilities {
3863 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
3864 ..Default::default()
3865 },
3866 ..Default::default()
3867 }))
3868 .await;
3869
3870 let fs = FakeFs::new(cx.background());
3871 fs.insert_file("/file.rs", Default::default()).await;
3872
3873 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
3874 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
3875 let buffer = project
3876 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
3877 .await
3878 .unwrap();
3879
3880 cx.foreground().start_waiting();
3881 let fake_server = fake_servers.next().await.unwrap();
3882
3883 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
3884 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
3885 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
3886 assert!(cx.read(|cx| editor.is_dirty(cx)));
3887
3888 let save = cx.update(|cx| editor.save(project.clone(), cx));
3889 fake_server
3890 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
3891 assert_eq!(
3892 params.text_document.uri,
3893 lsp::Url::from_file_path("/file.rs").unwrap()
3894 );
3895 assert_eq!(params.options.tab_size, 4);
3896 Ok(Some(vec![lsp::TextEdit::new(
3897 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
3898 ", ".to_string(),
3899 )]))
3900 })
3901 .next()
3902 .await;
3903 cx.foreground().start_waiting();
3904 save.await.unwrap();
3905 assert_eq!(
3906 editor.read_with(cx, |editor, cx| editor.text(cx)),
3907 "one, two\nthree\n"
3908 );
3909 assert!(!cx.read(|cx| editor.is_dirty(cx)));
3910
3911 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
3912 assert!(cx.read(|cx| editor.is_dirty(cx)));
3913
3914 // Ensure we can still save even if formatting hangs.
3915 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
3916 move |params, _| async move {
3917 assert_eq!(
3918 params.text_document.uri,
3919 lsp::Url::from_file_path("/file.rs").unwrap()
3920 );
3921 futures::future::pending::<()>().await;
3922 unreachable!()
3923 },
3924 );
3925 let save = cx.update(|cx| editor.save(project.clone(), cx));
3926 cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
3927 cx.foreground().start_waiting();
3928 save.await.unwrap();
3929 assert_eq!(
3930 editor.read_with(cx, |editor, cx| editor.text(cx)),
3931 "one\ntwo\nthree\n"
3932 );
3933 assert!(!cx.read(|cx| editor.is_dirty(cx)));
3934
3935 // Set rust language override and assert overriden tabsize is sent to language server
3936 cx.update(|cx| {
3937 cx.update_global::<Settings, _, _>(|settings, _| {
3938 settings.language_overrides.insert(
3939 "Rust".into(),
3940 EditorSettings {
3941 tab_size: Some(8.try_into().unwrap()),
3942 ..Default::default()
3943 },
3944 );
3945 })
3946 });
3947
3948 let save = cx.update(|cx| editor.save(project.clone(), cx));
3949 fake_server
3950 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
3951 assert_eq!(
3952 params.text_document.uri,
3953 lsp::Url::from_file_path("/file.rs").unwrap()
3954 );
3955 assert_eq!(params.options.tab_size, 8);
3956 Ok(Some(vec![]))
3957 })
3958 .next()
3959 .await;
3960 cx.foreground().start_waiting();
3961 save.await.unwrap();
3962}
3963
3964#[gpui::test]
3965async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
3966 cx.foreground().forbid_parking();
3967
3968 let mut language = Language::new(
3969 LanguageConfig {
3970 name: "Rust".into(),
3971 path_suffixes: vec!["rs".to_string()],
3972 ..Default::default()
3973 },
3974 Some(tree_sitter_rust::language()),
3975 );
3976 let mut fake_servers = language
3977 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
3978 capabilities: lsp::ServerCapabilities {
3979 document_formatting_provider: Some(lsp::OneOf::Left(true)),
3980 ..Default::default()
3981 },
3982 ..Default::default()
3983 }))
3984 .await;
3985
3986 let fs = FakeFs::new(cx.background());
3987 fs.insert_file("/file.rs", Default::default()).await;
3988
3989 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
3990 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
3991 let buffer = project
3992 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
3993 .await
3994 .unwrap();
3995
3996 cx.foreground().start_waiting();
3997 let fake_server = fake_servers.next().await.unwrap();
3998
3999 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
4000 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
4001 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
4002
4003 let format = editor.update(cx, |editor, cx| editor.perform_format(project.clone(), cx));
4004 fake_server
4005 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
4006 assert_eq!(
4007 params.text_document.uri,
4008 lsp::Url::from_file_path("/file.rs").unwrap()
4009 );
4010 assert_eq!(params.options.tab_size, 4);
4011 Ok(Some(vec![lsp::TextEdit::new(
4012 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
4013 ", ".to_string(),
4014 )]))
4015 })
4016 .next()
4017 .await;
4018 cx.foreground().start_waiting();
4019 format.await.unwrap();
4020 assert_eq!(
4021 editor.read_with(cx, |editor, cx| editor.text(cx)),
4022 "one, two\nthree\n"
4023 );
4024
4025 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
4026 // Ensure we don't lock if formatting hangs.
4027 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
4028 assert_eq!(
4029 params.text_document.uri,
4030 lsp::Url::from_file_path("/file.rs").unwrap()
4031 );
4032 futures::future::pending::<()>().await;
4033 unreachable!()
4034 });
4035 let format = editor.update(cx, |editor, cx| editor.perform_format(project, cx));
4036 cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
4037 cx.foreground().start_waiting();
4038 format.await.unwrap();
4039 assert_eq!(
4040 editor.read_with(cx, |editor, cx| editor.text(cx)),
4041 "one\ntwo\nthree\n"
4042 );
4043}
4044
4045#[gpui::test]
4046async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
4047 cx.foreground().forbid_parking();
4048
4049 let mut cx = EditorLspTestContext::new_rust(
4050 lsp::ServerCapabilities {
4051 document_formatting_provider: Some(lsp::OneOf::Left(true)),
4052 ..Default::default()
4053 },
4054 cx,
4055 )
4056 .await;
4057
4058 cx.set_state(indoc! {"
4059 one.twoˇ
4060 "});
4061
4062 // The format request takes a long time. When it completes, it inserts
4063 // a newline and an indent before the `.`
4064 cx.lsp
4065 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
4066 let executor = cx.background();
4067 async move {
4068 executor.timer(Duration::from_millis(100)).await;
4069 Ok(Some(vec![lsp::TextEdit {
4070 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
4071 new_text: "\n ".into(),
4072 }]))
4073 }
4074 });
4075
4076 // Submit a format request.
4077 let format_1 = cx
4078 .update_editor(|editor, cx| editor.format(&Format, cx))
4079 .unwrap();
4080 cx.foreground().run_until_parked();
4081
4082 // Submit a second format request.
4083 let format_2 = cx
4084 .update_editor(|editor, cx| editor.format(&Format, cx))
4085 .unwrap();
4086 cx.foreground().run_until_parked();
4087
4088 // Wait for both format requests to complete
4089 cx.foreground().advance_clock(Duration::from_millis(200));
4090 cx.foreground().start_waiting();
4091 format_1.await.unwrap();
4092 cx.foreground().start_waiting();
4093 format_2.await.unwrap();
4094
4095 // The formatting edits only happens once.
4096 cx.assert_editor_state(indoc! {"
4097 one
4098 .twoˇ
4099 "});
4100}
4101
4102#[gpui::test]
4103async fn test_completion(cx: &mut gpui::TestAppContext) {
4104 let mut cx = EditorLspTestContext::new_rust(
4105 lsp::ServerCapabilities {
4106 completion_provider: Some(lsp::CompletionOptions {
4107 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
4108 ..Default::default()
4109 }),
4110 ..Default::default()
4111 },
4112 cx,
4113 )
4114 .await;
4115
4116 cx.set_state(indoc! {"
4117 oneˇ
4118 two
4119 three
4120 "});
4121 cx.simulate_keystroke(".");
4122 handle_completion_request(
4123 &mut cx,
4124 indoc! {"
4125 one.|<>
4126 two
4127 three
4128 "},
4129 vec!["first_completion", "second_completion"],
4130 )
4131 .await;
4132 cx.condition(|editor, _| editor.context_menu_visible())
4133 .await;
4134 let apply_additional_edits = cx.update_editor(|editor, cx| {
4135 editor.move_down(&MoveDown, cx);
4136 editor
4137 .confirm_completion(&ConfirmCompletion::default(), cx)
4138 .unwrap()
4139 });
4140 cx.assert_editor_state(indoc! {"
4141 one.second_completionˇ
4142 two
4143 three
4144 "});
4145
4146 handle_resolve_completion_request(
4147 &mut cx,
4148 Some((
4149 indoc! {"
4150 one.second_completion
4151 two
4152 threeˇ
4153 "},
4154 "\nadditional edit",
4155 )),
4156 )
4157 .await;
4158 apply_additional_edits.await.unwrap();
4159 cx.assert_editor_state(indoc! {"
4160 one.second_completionˇ
4161 two
4162 three
4163 additional edit
4164 "});
4165
4166 cx.set_state(indoc! {"
4167 one.second_completion
4168 twoˇ
4169 threeˇ
4170 additional edit
4171 "});
4172 cx.simulate_keystroke(" ");
4173 assert!(cx.editor(|e, _| e.context_menu.is_none()));
4174 cx.simulate_keystroke("s");
4175 assert!(cx.editor(|e, _| e.context_menu.is_none()));
4176
4177 cx.assert_editor_state(indoc! {"
4178 one.second_completion
4179 two sˇ
4180 three sˇ
4181 additional edit
4182 "});
4183 handle_completion_request(
4184 &mut cx,
4185 indoc! {"
4186 one.second_completion
4187 two s
4188 three <s|>
4189 additional edit
4190 "},
4191 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
4192 )
4193 .await;
4194 cx.condition(|editor, _| editor.context_menu_visible())
4195 .await;
4196
4197 cx.simulate_keystroke("i");
4198
4199 handle_completion_request(
4200 &mut cx,
4201 indoc! {"
4202 one.second_completion
4203 two si
4204 three <si|>
4205 additional edit
4206 "},
4207 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
4208 )
4209 .await;
4210 cx.condition(|editor, _| editor.context_menu_visible())
4211 .await;
4212
4213 let apply_additional_edits = cx.update_editor(|editor, cx| {
4214 editor
4215 .confirm_completion(&ConfirmCompletion::default(), cx)
4216 .unwrap()
4217 });
4218 cx.assert_editor_state(indoc! {"
4219 one.second_completion
4220 two sixth_completionˇ
4221 three sixth_completionˇ
4222 additional edit
4223 "});
4224
4225 handle_resolve_completion_request(&mut cx, None).await;
4226 apply_additional_edits.await.unwrap();
4227
4228 cx.update(|cx| {
4229 cx.update_global::<Settings, _, _>(|settings, _| {
4230 settings.show_completions_on_input = false;
4231 })
4232 });
4233 cx.set_state("editorˇ");
4234 cx.simulate_keystroke(".");
4235 assert!(cx.editor(|e, _| e.context_menu.is_none()));
4236 cx.simulate_keystroke("c");
4237 cx.simulate_keystroke("l");
4238 cx.simulate_keystroke("o");
4239 cx.assert_editor_state("editor.cloˇ");
4240 assert!(cx.editor(|e, _| e.context_menu.is_none()));
4241 cx.update_editor(|editor, cx| {
4242 editor.show_completions(&ShowCompletions, cx);
4243 });
4244 handle_completion_request(&mut cx, "editor.<clo|>", vec!["close", "clobber"]).await;
4245 cx.condition(|editor, _| editor.context_menu_visible())
4246 .await;
4247 let apply_additional_edits = cx.update_editor(|editor, cx| {
4248 editor
4249 .confirm_completion(&ConfirmCompletion::default(), cx)
4250 .unwrap()
4251 });
4252 cx.assert_editor_state("editor.closeˇ");
4253 handle_resolve_completion_request(&mut cx, None).await;
4254 apply_additional_edits.await.unwrap();
4255
4256 // Handle completion request passing a marked string specifying where the completion
4257 // should be triggered from using '|' character, what range should be replaced, and what completions
4258 // should be returned using '<' and '>' to delimit the range
4259 async fn handle_completion_request<'a>(
4260 cx: &mut EditorLspTestContext<'a>,
4261 marked_string: &str,
4262 completions: Vec<&'static str>,
4263 ) {
4264 let complete_from_marker: TextRangeMarker = '|'.into();
4265 let replace_range_marker: TextRangeMarker = ('<', '>').into();
4266 let (_, mut marked_ranges) = marked_text_ranges_by(
4267 marked_string,
4268 vec![complete_from_marker.clone(), replace_range_marker.clone()],
4269 );
4270
4271 let complete_from_position =
4272 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
4273 let replace_range =
4274 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
4275
4276 cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
4277 let completions = completions.clone();
4278 async move {
4279 assert_eq!(params.text_document_position.text_document.uri, url.clone());
4280 assert_eq!(
4281 params.text_document_position.position,
4282 complete_from_position
4283 );
4284 Ok(Some(lsp::CompletionResponse::Array(
4285 completions
4286 .iter()
4287 .map(|completion_text| lsp::CompletionItem {
4288 label: completion_text.to_string(),
4289 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
4290 range: replace_range,
4291 new_text: completion_text.to_string(),
4292 })),
4293 ..Default::default()
4294 })
4295 .collect(),
4296 )))
4297 }
4298 })
4299 .next()
4300 .await;
4301 }
4302
4303 async fn handle_resolve_completion_request<'a>(
4304 cx: &mut EditorLspTestContext<'a>,
4305 edit: Option<(&'static str, &'static str)>,
4306 ) {
4307 let edit = edit.map(|(marked_string, new_text)| {
4308 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
4309 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
4310 vec![lsp::TextEdit::new(replace_range, new_text.to_string())]
4311 });
4312
4313 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
4314 let edit = edit.clone();
4315 async move {
4316 Ok(lsp::CompletionItem {
4317 additional_text_edits: edit,
4318 ..Default::default()
4319 })
4320 }
4321 })
4322 .next()
4323 .await;
4324 }
4325}
4326
4327#[gpui::test]
4328async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
4329 cx.update(|cx| cx.set_global(Settings::test(cx)));
4330 let language = Arc::new(Language::new(
4331 LanguageConfig {
4332 line_comment: Some("// ".into()),
4333 ..Default::default()
4334 },
4335 Some(tree_sitter_rust::language()),
4336 ));
4337
4338 let text = "
4339 fn a() {
4340 //b();
4341 // c();
4342 // d();
4343 }
4344 "
4345 .unindent();
4346
4347 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
4348 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
4349 let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
4350
4351 view.update(cx, |editor, cx| {
4352 // If multiple selections intersect a line, the line is only
4353 // toggled once.
4354 editor.change_selections(None, cx, |s| {
4355 s.select_display_ranges([
4356 DisplayPoint::new(1, 3)..DisplayPoint::new(2, 3),
4357 DisplayPoint::new(3, 5)..DisplayPoint::new(3, 6),
4358 ])
4359 });
4360 editor.toggle_comments(&ToggleComments, cx);
4361 assert_eq!(
4362 editor.text(cx),
4363 "
4364 fn a() {
4365 b();
4366 c();
4367 d();
4368 }
4369 "
4370 .unindent()
4371 );
4372
4373 // The comment prefix is inserted at the same column for every line
4374 // in a selection.
4375 editor.change_selections(None, cx, |s| {
4376 s.select_display_ranges([DisplayPoint::new(1, 3)..DisplayPoint::new(3, 6)])
4377 });
4378 editor.toggle_comments(&ToggleComments, cx);
4379 assert_eq!(
4380 editor.text(cx),
4381 "
4382 fn a() {
4383 // b();
4384 // c();
4385 // d();
4386 }
4387 "
4388 .unindent()
4389 );
4390
4391 // If a selection ends at the beginning of a line, that line is not toggled.
4392 editor.change_selections(None, cx, |s| {
4393 s.select_display_ranges([DisplayPoint::new(2, 0)..DisplayPoint::new(3, 0)])
4394 });
4395 editor.toggle_comments(&ToggleComments, cx);
4396 assert_eq!(
4397 editor.text(cx),
4398 "
4399 fn a() {
4400 // b();
4401 c();
4402 // d();
4403 }
4404 "
4405 .unindent()
4406 );
4407 });
4408}
4409
4410#[gpui::test]
4411async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
4412 let mut cx = EditorTestContext::new(cx);
4413
4414 let html_language = Arc::new(
4415 Language::new(
4416 LanguageConfig {
4417 name: "HTML".into(),
4418 block_comment: Some(("<!-- ".into(), " -->".into())),
4419 ..Default::default()
4420 },
4421 Some(tree_sitter_html::language()),
4422 )
4423 .with_injection_query(
4424 r#"
4425 (script_element
4426 (raw_text) @content
4427 (#set! "language" "javascript"))
4428 "#,
4429 )
4430 .unwrap(),
4431 );
4432
4433 let javascript_language = Arc::new(Language::new(
4434 LanguageConfig {
4435 name: "JavaScript".into(),
4436 line_comment: Some("// ".into()),
4437 ..Default::default()
4438 },
4439 Some(tree_sitter_javascript::language()),
4440 ));
4441
4442 let registry = Arc::new(LanguageRegistry::test());
4443 registry.add(html_language.clone());
4444 registry.add(javascript_language.clone());
4445
4446 cx.update_buffer(|buffer, cx| {
4447 buffer.set_language_registry(registry);
4448 buffer.set_language(Some(html_language), cx);
4449 });
4450
4451 // Toggle comments for empty selections
4452 cx.set_state(
4453 &r#"
4454 <p>A</p>ˇ
4455 <p>B</p>ˇ
4456 <p>C</p>ˇ
4457 "#
4458 .unindent(),
4459 );
4460 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments, cx));
4461 cx.assert_editor_state(
4462 &r#"
4463 <!-- <p>A</p>ˇ -->
4464 <!-- <p>B</p>ˇ -->
4465 <!-- <p>C</p>ˇ -->
4466 "#
4467 .unindent(),
4468 );
4469 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments, cx));
4470 cx.assert_editor_state(
4471 &r#"
4472 <p>A</p>ˇ
4473 <p>B</p>ˇ
4474 <p>C</p>ˇ
4475 "#
4476 .unindent(),
4477 );
4478
4479 // Toggle comments for mixture of empty and non-empty selections, where
4480 // multiple selections occupy a given line.
4481 cx.set_state(
4482 &r#"
4483 <p>A«</p>
4484 <p>ˇ»B</p>ˇ
4485 <p>C«</p>
4486 <p>ˇ»D</p>ˇ
4487 "#
4488 .unindent(),
4489 );
4490
4491 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments, cx));
4492 cx.assert_editor_state(
4493 &r#"
4494 <!-- <p>A«</p>
4495 <p>ˇ»B</p>ˇ -->
4496 <!-- <p>C«</p>
4497 <p>ˇ»D</p>ˇ -->
4498 "#
4499 .unindent(),
4500 );
4501 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments, cx));
4502 cx.assert_editor_state(
4503 &r#"
4504 <p>A«</p>
4505 <p>ˇ»B</p>ˇ
4506 <p>C«</p>
4507 <p>ˇ»D</p>ˇ
4508 "#
4509 .unindent(),
4510 );
4511
4512 // Toggle comments when different languages are active for different
4513 // selections.
4514 cx.set_state(
4515 &r#"
4516 ˇ<script>
4517 ˇvar x = new Y();
4518 ˇ</script>
4519 "#
4520 .unindent(),
4521 );
4522 cx.foreground().run_until_parked();
4523 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments, cx));
4524 cx.assert_editor_state(
4525 &r#"
4526 <!-- ˇ<script> -->
4527 // ˇvar x = new Y();
4528 <!-- ˇ</script> -->
4529 "#
4530 .unindent(),
4531 );
4532}
4533
4534#[gpui::test]
4535fn test_editing_disjoint_excerpts(cx: &mut gpui::MutableAppContext) {
4536 cx.set_global(Settings::test(cx));
4537 let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
4538 let multibuffer = cx.add_model(|cx| {
4539 let mut multibuffer = MultiBuffer::new(0);
4540 multibuffer.push_excerpts(
4541 buffer.clone(),
4542 [
4543 ExcerptRange {
4544 context: Point::new(0, 0)..Point::new(0, 4),
4545 primary: None,
4546 },
4547 ExcerptRange {
4548 context: Point::new(1, 0)..Point::new(1, 4),
4549 primary: None,
4550 },
4551 ],
4552 cx,
4553 );
4554 multibuffer
4555 });
4556
4557 assert_eq!(multibuffer.read(cx).read(cx).text(), "aaaa\nbbbb");
4558
4559 let (_, view) = cx.add_window(Default::default(), |cx| build_editor(multibuffer, cx));
4560 view.update(cx, |view, cx| {
4561 assert_eq!(view.text(cx), "aaaa\nbbbb");
4562 view.change_selections(None, cx, |s| {
4563 s.select_ranges([
4564 Point::new(0, 0)..Point::new(0, 0),
4565 Point::new(1, 0)..Point::new(1, 0),
4566 ])
4567 });
4568
4569 view.handle_input("X", cx);
4570 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
4571 assert_eq!(
4572 view.selections.ranges(cx),
4573 [
4574 Point::new(0, 1)..Point::new(0, 1),
4575 Point::new(1, 1)..Point::new(1, 1),
4576 ]
4577 )
4578 });
4579}
4580
4581#[gpui::test]
4582fn test_editing_overlapping_excerpts(cx: &mut gpui::MutableAppContext) {
4583 cx.set_global(Settings::test(cx));
4584 let markers = vec![('[', ']').into(), ('(', ')').into()];
4585 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
4586 indoc! {"
4587 [aaaa
4588 (bbbb]
4589 cccc)",
4590 },
4591 markers.clone(),
4592 );
4593 let excerpt_ranges = markers.into_iter().map(|marker| {
4594 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
4595 ExcerptRange {
4596 context,
4597 primary: None,
4598 }
4599 });
4600 let buffer = cx.add_model(|cx| Buffer::new(0, initial_text, cx));
4601 let multibuffer = cx.add_model(|cx| {
4602 let mut multibuffer = MultiBuffer::new(0);
4603 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
4604 multibuffer
4605 });
4606
4607 let (_, view) = cx.add_window(Default::default(), |cx| build_editor(multibuffer, cx));
4608 view.update(cx, |view, cx| {
4609 let (expected_text, selection_ranges) = marked_text_ranges(
4610 indoc! {"
4611 aaaa
4612 bˇbbb
4613 bˇbbˇb
4614 cccc"
4615 },
4616 true,
4617 );
4618 assert_eq!(view.text(cx), expected_text);
4619 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
4620
4621 view.handle_input("X", cx);
4622
4623 let (expected_text, expected_selections) = marked_text_ranges(
4624 indoc! {"
4625 aaaa
4626 bXˇbbXb
4627 bXˇbbXˇb
4628 cccc"
4629 },
4630 false,
4631 );
4632 assert_eq!(view.text(cx), expected_text);
4633 assert_eq!(view.selections.ranges(cx), expected_selections);
4634
4635 view.newline(&Newline, cx);
4636 let (expected_text, expected_selections) = marked_text_ranges(
4637 indoc! {"
4638 aaaa
4639 bX
4640 ˇbbX
4641 b
4642 bX
4643 ˇbbX
4644 ˇb
4645 cccc"
4646 },
4647 false,
4648 );
4649 assert_eq!(view.text(cx), expected_text);
4650 assert_eq!(view.selections.ranges(cx), expected_selections);
4651 });
4652}
4653
4654#[gpui::test]
4655fn test_refresh_selections(cx: &mut gpui::MutableAppContext) {
4656 cx.set_global(Settings::test(cx));
4657 let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
4658 let mut excerpt1_id = None;
4659 let multibuffer = cx.add_model(|cx| {
4660 let mut multibuffer = MultiBuffer::new(0);
4661 excerpt1_id = multibuffer
4662 .push_excerpts(
4663 buffer.clone(),
4664 [
4665 ExcerptRange {
4666 context: Point::new(0, 0)..Point::new(1, 4),
4667 primary: None,
4668 },
4669 ExcerptRange {
4670 context: Point::new(1, 0)..Point::new(2, 4),
4671 primary: None,
4672 },
4673 ],
4674 cx,
4675 )
4676 .into_iter()
4677 .next();
4678 multibuffer
4679 });
4680 assert_eq!(
4681 multibuffer.read(cx).read(cx).text(),
4682 "aaaa\nbbbb\nbbbb\ncccc"
4683 );
4684 let (_, editor) = cx.add_window(Default::default(), |cx| {
4685 let mut editor = build_editor(multibuffer.clone(), cx);
4686 let snapshot = editor.snapshot(cx);
4687 editor.change_selections(None, cx, |s| {
4688 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
4689 });
4690 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
4691 assert_eq!(
4692 editor.selections.ranges(cx),
4693 [
4694 Point::new(1, 3)..Point::new(1, 3),
4695 Point::new(2, 1)..Point::new(2, 1),
4696 ]
4697 );
4698 editor
4699 });
4700
4701 // Refreshing selections is a no-op when excerpts haven't changed.
4702 editor.update(cx, |editor, cx| {
4703 editor.change_selections(None, cx, |s| {
4704 s.refresh();
4705 });
4706 assert_eq!(
4707 editor.selections.ranges(cx),
4708 [
4709 Point::new(1, 3)..Point::new(1, 3),
4710 Point::new(2, 1)..Point::new(2, 1),
4711 ]
4712 );
4713 });
4714
4715 multibuffer.update(cx, |multibuffer, cx| {
4716 multibuffer.remove_excerpts([&excerpt1_id.unwrap()], cx);
4717 });
4718 editor.update(cx, |editor, cx| {
4719 // Removing an excerpt causes the first selection to become degenerate.
4720 assert_eq!(
4721 editor.selections.ranges(cx),
4722 [
4723 Point::new(0, 0)..Point::new(0, 0),
4724 Point::new(0, 1)..Point::new(0, 1)
4725 ]
4726 );
4727
4728 // Refreshing selections will relocate the first selection to the original buffer
4729 // location.
4730 editor.change_selections(None, cx, |s| {
4731 s.refresh();
4732 });
4733 assert_eq!(
4734 editor.selections.ranges(cx),
4735 [
4736 Point::new(0, 1)..Point::new(0, 1),
4737 Point::new(0, 3)..Point::new(0, 3)
4738 ]
4739 );
4740 assert!(editor.selections.pending_anchor().is_some());
4741 });
4742}
4743
4744#[gpui::test]
4745fn test_refresh_selections_while_selecting_with_mouse(cx: &mut gpui::MutableAppContext) {
4746 cx.set_global(Settings::test(cx));
4747 let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
4748 let mut excerpt1_id = None;
4749 let multibuffer = cx.add_model(|cx| {
4750 let mut multibuffer = MultiBuffer::new(0);
4751 excerpt1_id = multibuffer
4752 .push_excerpts(
4753 buffer.clone(),
4754 [
4755 ExcerptRange {
4756 context: Point::new(0, 0)..Point::new(1, 4),
4757 primary: None,
4758 },
4759 ExcerptRange {
4760 context: Point::new(1, 0)..Point::new(2, 4),
4761 primary: None,
4762 },
4763 ],
4764 cx,
4765 )
4766 .into_iter()
4767 .next();
4768 multibuffer
4769 });
4770 assert_eq!(
4771 multibuffer.read(cx).read(cx).text(),
4772 "aaaa\nbbbb\nbbbb\ncccc"
4773 );
4774 let (_, editor) = cx.add_window(Default::default(), |cx| {
4775 let mut editor = build_editor(multibuffer.clone(), cx);
4776 let snapshot = editor.snapshot(cx);
4777 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
4778 assert_eq!(
4779 editor.selections.ranges(cx),
4780 [Point::new(1, 3)..Point::new(1, 3)]
4781 );
4782 editor
4783 });
4784
4785 multibuffer.update(cx, |multibuffer, cx| {
4786 multibuffer.remove_excerpts([&excerpt1_id.unwrap()], cx);
4787 });
4788 editor.update(cx, |editor, cx| {
4789 assert_eq!(
4790 editor.selections.ranges(cx),
4791 [Point::new(0, 0)..Point::new(0, 0)]
4792 );
4793
4794 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
4795 editor.change_selections(None, cx, |s| {
4796 s.refresh();
4797 });
4798 assert_eq!(
4799 editor.selections.ranges(cx),
4800 [Point::new(0, 3)..Point::new(0, 3)]
4801 );
4802 assert!(editor.selections.pending_anchor().is_some());
4803 });
4804}
4805
4806#[gpui::test]
4807async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
4808 cx.update(|cx| cx.set_global(Settings::test(cx)));
4809 let language = Arc::new(
4810 Language::new(
4811 LanguageConfig {
4812 brackets: vec![
4813 BracketPair {
4814 start: "{".to_string(),
4815 end: "}".to_string(),
4816 close: true,
4817 newline: true,
4818 },
4819 BracketPair {
4820 start: "/* ".to_string(),
4821 end: " */".to_string(),
4822 close: true,
4823 newline: true,
4824 },
4825 ],
4826 ..Default::default()
4827 },
4828 Some(tree_sitter_rust::language()),
4829 )
4830 .with_indents_query("")
4831 .unwrap(),
4832 );
4833
4834 let text = concat!(
4835 "{ }\n", //
4836 " x\n", //
4837 " /* */\n", //
4838 "x\n", //
4839 "{{} }\n", //
4840 );
4841
4842 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
4843 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
4844 let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
4845 view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
4846 .await;
4847
4848 view.update(cx, |view, cx| {
4849 view.change_selections(None, cx, |s| {
4850 s.select_display_ranges([
4851 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3),
4852 DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
4853 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
4854 ])
4855 });
4856 view.newline(&Newline, cx);
4857
4858 assert_eq!(
4859 view.buffer().read(cx).read(cx).text(),
4860 concat!(
4861 "{ \n", // Suppress rustfmt
4862 "\n", //
4863 "}\n", //
4864 " x\n", //
4865 " /* \n", //
4866 " \n", //
4867 " */\n", //
4868 "x\n", //
4869 "{{} \n", //
4870 "}\n", //
4871 )
4872 );
4873 });
4874}
4875
4876#[gpui::test]
4877fn test_highlighted_ranges(cx: &mut gpui::MutableAppContext) {
4878 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
4879
4880 cx.set_global(Settings::test(cx));
4881 let (_, editor) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
4882
4883 editor.update(cx, |editor, cx| {
4884 struct Type1;
4885 struct Type2;
4886
4887 let buffer = buffer.read(cx).snapshot(cx);
4888
4889 let anchor_range =
4890 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
4891
4892 editor.highlight_background::<Type1>(
4893 vec![
4894 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
4895 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
4896 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
4897 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
4898 ],
4899 |_| Color::red(),
4900 cx,
4901 );
4902 editor.highlight_background::<Type2>(
4903 vec![
4904 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
4905 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
4906 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
4907 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
4908 ],
4909 |_| Color::green(),
4910 cx,
4911 );
4912
4913 let snapshot = editor.snapshot(cx);
4914 let mut highlighted_ranges = editor.background_highlights_in_range(
4915 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
4916 &snapshot,
4917 cx.global::<Settings>().theme.as_ref(),
4918 );
4919 // Enforce a consistent ordering based on color without relying on the ordering of the
4920 // highlight's `TypeId` which is non-deterministic.
4921 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
4922 assert_eq!(
4923 highlighted_ranges,
4924 &[
4925 (
4926 DisplayPoint::new(3, 2)..DisplayPoint::new(3, 5),
4927 Color::green(),
4928 ),
4929 (
4930 DisplayPoint::new(5, 3)..DisplayPoint::new(5, 6),
4931 Color::green(),
4932 ),
4933 (
4934 DisplayPoint::new(4, 2)..DisplayPoint::new(4, 4),
4935 Color::red(),
4936 ),
4937 (
4938 DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
4939 Color::red(),
4940 ),
4941 ]
4942 );
4943 assert_eq!(
4944 editor.background_highlights_in_range(
4945 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
4946 &snapshot,
4947 cx.global::<Settings>().theme.as_ref(),
4948 ),
4949 &[(
4950 DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
4951 Color::red(),
4952 )]
4953 );
4954 });
4955}
4956
4957#[gpui::test]
4958fn test_following(cx: &mut gpui::MutableAppContext) {
4959 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
4960
4961 cx.set_global(Settings::test(cx));
4962
4963 let (_, leader) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
4964 let (_, follower) = cx.add_window(
4965 WindowOptions {
4966 bounds: WindowBounds::Fixed(RectF::from_points(vec2f(0., 0.), vec2f(10., 80.))),
4967 ..Default::default()
4968 },
4969 |cx| build_editor(buffer.clone(), cx),
4970 );
4971
4972 let pending_update = Rc::new(RefCell::new(None));
4973 follower.update(cx, {
4974 let update = pending_update.clone();
4975 |_, cx| {
4976 cx.subscribe(&leader, move |_, leader, event, cx| {
4977 leader
4978 .read(cx)
4979 .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx);
4980 })
4981 .detach();
4982 }
4983 });
4984
4985 // Update the selections only
4986 leader.update(cx, |leader, cx| {
4987 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
4988 });
4989 follower.update(cx, |follower, cx| {
4990 follower
4991 .apply_update_proto(pending_update.borrow_mut().take().unwrap(), cx)
4992 .unwrap();
4993 });
4994 assert_eq!(follower.read(cx).selections.ranges(cx), vec![1..1]);
4995
4996 // Update the scroll position only
4997 leader.update(cx, |leader, cx| {
4998 leader.set_scroll_position(vec2f(1.5, 3.5), cx);
4999 });
5000 follower.update(cx, |follower, cx| {
5001 follower
5002 .apply_update_proto(pending_update.borrow_mut().take().unwrap(), cx)
5003 .unwrap();
5004 });
5005 assert_eq!(
5006 follower.update(cx, |follower, cx| follower.scroll_position(cx)),
5007 vec2f(1.5, 3.5)
5008 );
5009
5010 // Update the selections and scroll position
5011 leader.update(cx, |leader, cx| {
5012 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
5013 leader.request_autoscroll(Autoscroll::Newest, cx);
5014 leader.set_scroll_position(vec2f(1.5, 3.5), cx);
5015 });
5016 follower.update(cx, |follower, cx| {
5017 let initial_scroll_position = follower.scroll_position(cx);
5018 follower
5019 .apply_update_proto(pending_update.borrow_mut().take().unwrap(), cx)
5020 .unwrap();
5021 assert_eq!(follower.scroll_position(cx), initial_scroll_position);
5022 assert!(follower.autoscroll_request.is_some());
5023 });
5024 assert_eq!(follower.read(cx).selections.ranges(cx), vec![0..0]);
5025
5026 // Creating a pending selection that precedes another selection
5027 leader.update(cx, |leader, cx| {
5028 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
5029 leader.begin_selection(DisplayPoint::new(0, 0), true, 1, cx);
5030 });
5031 follower.update(cx, |follower, cx| {
5032 follower
5033 .apply_update_proto(pending_update.borrow_mut().take().unwrap(), cx)
5034 .unwrap();
5035 });
5036 assert_eq!(follower.read(cx).selections.ranges(cx), vec![0..0, 1..1]);
5037
5038 // Extend the pending selection so that it surrounds another selection
5039 leader.update(cx, |leader, cx| {
5040 leader.extend_selection(DisplayPoint::new(0, 2), 1, cx);
5041 });
5042 follower.update(cx, |follower, cx| {
5043 follower
5044 .apply_update_proto(pending_update.borrow_mut().take().unwrap(), cx)
5045 .unwrap();
5046 });
5047 assert_eq!(follower.read(cx).selections.ranges(cx), vec![0..2]);
5048}
5049
5050#[test]
5051fn test_combine_syntax_and_fuzzy_match_highlights() {
5052 let string = "abcdefghijklmnop";
5053 let syntax_ranges = [
5054 (
5055 0..3,
5056 HighlightStyle {
5057 color: Some(Color::red()),
5058 ..Default::default()
5059 },
5060 ),
5061 (
5062 4..8,
5063 HighlightStyle {
5064 color: Some(Color::green()),
5065 ..Default::default()
5066 },
5067 ),
5068 ];
5069 let match_indices = [4, 6, 7, 8];
5070 assert_eq!(
5071 combine_syntax_and_fuzzy_match_highlights(
5072 string,
5073 Default::default(),
5074 syntax_ranges.into_iter(),
5075 &match_indices,
5076 ),
5077 &[
5078 (
5079 0..3,
5080 HighlightStyle {
5081 color: Some(Color::red()),
5082 ..Default::default()
5083 },
5084 ),
5085 (
5086 4..5,
5087 HighlightStyle {
5088 color: Some(Color::green()),
5089 weight: Some(fonts::Weight::BOLD),
5090 ..Default::default()
5091 },
5092 ),
5093 (
5094 5..6,
5095 HighlightStyle {
5096 color: Some(Color::green()),
5097 ..Default::default()
5098 },
5099 ),
5100 (
5101 6..8,
5102 HighlightStyle {
5103 color: Some(Color::green()),
5104 weight: Some(fonts::Weight::BOLD),
5105 ..Default::default()
5106 },
5107 ),
5108 (
5109 8..9,
5110 HighlightStyle {
5111 weight: Some(fonts::Weight::BOLD),
5112 ..Default::default()
5113 },
5114 ),
5115 ]
5116 );
5117}
5118
5119fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
5120 let point = DisplayPoint::new(row as u32, column as u32);
5121 point..point
5122}
5123
5124fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
5125 let (text, ranges) = marked_text_ranges(marked_text, true);
5126 assert_eq!(view.text(cx), text);
5127 assert_eq!(
5128 view.selections.ranges(cx),
5129 ranges,
5130 "Assert selections are {}",
5131 marked_text
5132 );
5133}