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