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