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