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