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