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