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| {
4197 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
4198 });
4199 fake_server
4200 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
4201 assert_eq!(
4202 params.text_document.uri,
4203 lsp::Url::from_file_path("/file.rs").unwrap()
4204 );
4205 assert_eq!(params.options.tab_size, 4);
4206 Ok(Some(vec![lsp::TextEdit::new(
4207 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
4208 ", ".to_string(),
4209 )]))
4210 })
4211 .next()
4212 .await;
4213 cx.foreground().start_waiting();
4214 format.await.unwrap();
4215 assert_eq!(
4216 editor.read_with(cx, |editor, cx| editor.text(cx)),
4217 "one, two\nthree\n"
4218 );
4219
4220 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
4221 // Ensure we don't lock if formatting hangs.
4222 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
4223 assert_eq!(
4224 params.text_document.uri,
4225 lsp::Url::from_file_path("/file.rs").unwrap()
4226 );
4227 futures::future::pending::<()>().await;
4228 unreachable!()
4229 });
4230 let format = editor.update(cx, |editor, cx| {
4231 editor.perform_format(project, FormatTrigger::Manual, cx)
4232 });
4233 cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
4234 cx.foreground().start_waiting();
4235 format.await.unwrap();
4236 assert_eq!(
4237 editor.read_with(cx, |editor, cx| editor.text(cx)),
4238 "one\ntwo\nthree\n"
4239 );
4240}
4241
4242#[gpui::test]
4243async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
4244 cx.foreground().forbid_parking();
4245
4246 let mut cx = EditorLspTestContext::new_rust(
4247 lsp::ServerCapabilities {
4248 document_formatting_provider: Some(lsp::OneOf::Left(true)),
4249 ..Default::default()
4250 },
4251 cx,
4252 )
4253 .await;
4254
4255 cx.set_state(indoc! {"
4256 one.twoˇ
4257 "});
4258
4259 // The format request takes a long time. When it completes, it inserts
4260 // a newline and an indent before the `.`
4261 cx.lsp
4262 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
4263 let executor = cx.background();
4264 async move {
4265 executor.timer(Duration::from_millis(100)).await;
4266 Ok(Some(vec![lsp::TextEdit {
4267 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
4268 new_text: "\n ".into(),
4269 }]))
4270 }
4271 });
4272
4273 // Submit a format request.
4274 let format_1 = cx
4275 .update_editor(|editor, cx| editor.format(&Format, cx))
4276 .unwrap();
4277 cx.foreground().run_until_parked();
4278
4279 // Submit a second format request.
4280 let format_2 = cx
4281 .update_editor(|editor, cx| editor.format(&Format, cx))
4282 .unwrap();
4283 cx.foreground().run_until_parked();
4284
4285 // Wait for both format requests to complete
4286 cx.foreground().advance_clock(Duration::from_millis(200));
4287 cx.foreground().start_waiting();
4288 format_1.await.unwrap();
4289 cx.foreground().start_waiting();
4290 format_2.await.unwrap();
4291
4292 // The formatting edits only happens once.
4293 cx.assert_editor_state(indoc! {"
4294 one
4295 .twoˇ
4296 "});
4297}
4298
4299#[gpui::test]
4300async fn test_completion(cx: &mut gpui::TestAppContext) {
4301 let mut cx = EditorLspTestContext::new_rust(
4302 lsp::ServerCapabilities {
4303 completion_provider: Some(lsp::CompletionOptions {
4304 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
4305 ..Default::default()
4306 }),
4307 ..Default::default()
4308 },
4309 cx,
4310 )
4311 .await;
4312
4313 cx.set_state(indoc! {"
4314 oneˇ
4315 two
4316 three
4317 "});
4318 cx.simulate_keystroke(".");
4319 handle_completion_request(
4320 &mut cx,
4321 indoc! {"
4322 one.|<>
4323 two
4324 three
4325 "},
4326 vec!["first_completion", "second_completion"],
4327 )
4328 .await;
4329 cx.condition(|editor, _| editor.context_menu_visible())
4330 .await;
4331 let apply_additional_edits = cx.update_editor(|editor, cx| {
4332 editor.move_down(&MoveDown, cx);
4333 editor
4334 .confirm_completion(&ConfirmCompletion::default(), cx)
4335 .unwrap()
4336 });
4337 cx.assert_editor_state(indoc! {"
4338 one.second_completionˇ
4339 two
4340 three
4341 "});
4342
4343 handle_resolve_completion_request(
4344 &mut cx,
4345 Some(vec![
4346 (
4347 //This overlaps with the primary completion edit which is
4348 //misbehavior from the LSP spec, test that we filter it out
4349 indoc! {"
4350 one.second_ˇcompletion
4351 two
4352 threeˇ
4353 "},
4354 "overlapping aditional edit",
4355 ),
4356 (
4357 indoc! {"
4358 one.second_completion
4359 two
4360 threeˇ
4361 "},
4362 "\nadditional edit",
4363 ),
4364 ]),
4365 )
4366 .await;
4367 apply_additional_edits.await.unwrap();
4368 cx.assert_editor_state(indoc! {"
4369 one.second_completionˇ
4370 two
4371 three
4372 additional edit
4373 "});
4374
4375 cx.set_state(indoc! {"
4376 one.second_completion
4377 twoˇ
4378 threeˇ
4379 additional edit
4380 "});
4381 cx.simulate_keystroke(" ");
4382 assert!(cx.editor(|e, _| e.context_menu.is_none()));
4383 cx.simulate_keystroke("s");
4384 assert!(cx.editor(|e, _| e.context_menu.is_none()));
4385
4386 cx.assert_editor_state(indoc! {"
4387 one.second_completion
4388 two sˇ
4389 three sˇ
4390 additional edit
4391 "});
4392 handle_completion_request(
4393 &mut cx,
4394 indoc! {"
4395 one.second_completion
4396 two s
4397 three <s|>
4398 additional edit
4399 "},
4400 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
4401 )
4402 .await;
4403 cx.condition(|editor, _| editor.context_menu_visible())
4404 .await;
4405
4406 cx.simulate_keystroke("i");
4407
4408 handle_completion_request(
4409 &mut cx,
4410 indoc! {"
4411 one.second_completion
4412 two si
4413 three <si|>
4414 additional edit
4415 "},
4416 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
4417 )
4418 .await;
4419 cx.condition(|editor, _| editor.context_menu_visible())
4420 .await;
4421
4422 let apply_additional_edits = cx.update_editor(|editor, cx| {
4423 editor
4424 .confirm_completion(&ConfirmCompletion::default(), cx)
4425 .unwrap()
4426 });
4427 cx.assert_editor_state(indoc! {"
4428 one.second_completion
4429 two sixth_completionˇ
4430 three sixth_completionˇ
4431 additional edit
4432 "});
4433
4434 handle_resolve_completion_request(&mut cx, None).await;
4435 apply_additional_edits.await.unwrap();
4436
4437 cx.update(|cx| {
4438 cx.update_global::<Settings, _, _>(|settings, _| {
4439 settings.show_completions_on_input = false;
4440 })
4441 });
4442 cx.set_state("editorˇ");
4443 cx.simulate_keystroke(".");
4444 assert!(cx.editor(|e, _| e.context_menu.is_none()));
4445 cx.simulate_keystroke("c");
4446 cx.simulate_keystroke("l");
4447 cx.simulate_keystroke("o");
4448 cx.assert_editor_state("editor.cloˇ");
4449 assert!(cx.editor(|e, _| e.context_menu.is_none()));
4450 cx.update_editor(|editor, cx| {
4451 editor.show_completions(&ShowCompletions, cx);
4452 });
4453 handle_completion_request(&mut cx, "editor.<clo|>", vec!["close", "clobber"]).await;
4454 cx.condition(|editor, _| editor.context_menu_visible())
4455 .await;
4456 let apply_additional_edits = cx.update_editor(|editor, cx| {
4457 editor
4458 .confirm_completion(&ConfirmCompletion::default(), cx)
4459 .unwrap()
4460 });
4461 cx.assert_editor_state("editor.closeˇ");
4462 handle_resolve_completion_request(&mut cx, None).await;
4463 apply_additional_edits.await.unwrap();
4464
4465 // Handle completion request passing a marked string specifying where the completion
4466 // should be triggered from using '|' character, what range should be replaced, and what completions
4467 // should be returned using '<' and '>' to delimit the range
4468 async fn handle_completion_request<'a>(
4469 cx: &mut EditorLspTestContext<'a>,
4470 marked_string: &str,
4471 completions: Vec<&'static str>,
4472 ) {
4473 let complete_from_marker: TextRangeMarker = '|'.into();
4474 let replace_range_marker: TextRangeMarker = ('<', '>').into();
4475 let (_, mut marked_ranges) = marked_text_ranges_by(
4476 marked_string,
4477 vec![complete_from_marker.clone(), replace_range_marker.clone()],
4478 );
4479
4480 let complete_from_position =
4481 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
4482 let replace_range =
4483 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
4484
4485 cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
4486 let completions = completions.clone();
4487 async move {
4488 assert_eq!(params.text_document_position.text_document.uri, url.clone());
4489 assert_eq!(
4490 params.text_document_position.position,
4491 complete_from_position
4492 );
4493 Ok(Some(lsp::CompletionResponse::Array(
4494 completions
4495 .iter()
4496 .map(|completion_text| lsp::CompletionItem {
4497 label: completion_text.to_string(),
4498 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
4499 range: replace_range,
4500 new_text: completion_text.to_string(),
4501 })),
4502 ..Default::default()
4503 })
4504 .collect(),
4505 )))
4506 }
4507 })
4508 .next()
4509 .await;
4510 }
4511
4512 async fn handle_resolve_completion_request<'a>(
4513 cx: &mut EditorLspTestContext<'a>,
4514 edits: Option<Vec<(&'static str, &'static str)>>,
4515 ) {
4516 let edits = edits.map(|edits| {
4517 edits
4518 .iter()
4519 .map(|(marked_string, new_text)| {
4520 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
4521 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
4522 lsp::TextEdit::new(replace_range, new_text.to_string())
4523 })
4524 .collect::<Vec<_>>()
4525 });
4526
4527 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
4528 let edits = edits.clone();
4529 async move {
4530 Ok(lsp::CompletionItem {
4531 additional_text_edits: edits,
4532 ..Default::default()
4533 })
4534 }
4535 })
4536 .next()
4537 .await;
4538 }
4539}
4540
4541#[gpui::test]
4542async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
4543 cx.update(|cx| cx.set_global(Settings::test(cx)));
4544 let language = Arc::new(Language::new(
4545 LanguageConfig {
4546 line_comment: Some("// ".into()),
4547 ..Default::default()
4548 },
4549 Some(tree_sitter_rust::language()),
4550 ));
4551
4552 let text = "
4553 fn a() {
4554 //b();
4555 // c();
4556 // d();
4557 }
4558 "
4559 .unindent();
4560
4561 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
4562 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
4563 let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
4564
4565 view.update(cx, |editor, cx| {
4566 // If multiple selections intersect a line, the line is only
4567 // toggled once.
4568 editor.change_selections(None, cx, |s| {
4569 s.select_display_ranges([
4570 DisplayPoint::new(1, 3)..DisplayPoint::new(2, 3),
4571 DisplayPoint::new(3, 5)..DisplayPoint::new(3, 6),
4572 ])
4573 });
4574 editor.toggle_comments(&ToggleComments::default(), cx);
4575 assert_eq!(
4576 editor.text(cx),
4577 "
4578 fn a() {
4579 b();
4580 c();
4581 d();
4582 }
4583 "
4584 .unindent()
4585 );
4586
4587 // The comment prefix is inserted at the same column for every line
4588 // in a selection.
4589 editor.change_selections(None, cx, |s| {
4590 s.select_display_ranges([DisplayPoint::new(1, 3)..DisplayPoint::new(3, 6)])
4591 });
4592 editor.toggle_comments(&ToggleComments::default(), cx);
4593 assert_eq!(
4594 editor.text(cx),
4595 "
4596 fn a() {
4597 // b();
4598 // c();
4599 // d();
4600 }
4601 "
4602 .unindent()
4603 );
4604
4605 // If a selection ends at the beginning of a line, that line is not toggled.
4606 editor.change_selections(None, cx, |s| {
4607 s.select_display_ranges([DisplayPoint::new(2, 0)..DisplayPoint::new(3, 0)])
4608 });
4609 editor.toggle_comments(&ToggleComments::default(), cx);
4610 assert_eq!(
4611 editor.text(cx),
4612 "
4613 fn a() {
4614 // b();
4615 c();
4616 // d();
4617 }
4618 "
4619 .unindent()
4620 );
4621 });
4622}
4623
4624#[gpui::test]
4625async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
4626 let mut cx = EditorTestContext::new(cx);
4627 cx.update(|cx| cx.set_global(Settings::test(cx)));
4628
4629 let language = Arc::new(Language::new(
4630 LanguageConfig {
4631 line_comment: Some("// ".into()),
4632 ..Default::default()
4633 },
4634 Some(tree_sitter_rust::language()),
4635 ));
4636
4637 let registry = Arc::new(LanguageRegistry::test());
4638 registry.add(language.clone());
4639
4640 cx.update_buffer(|buffer, cx| {
4641 buffer.set_language_registry(registry);
4642 buffer.set_language(Some(language), cx);
4643 });
4644
4645 let toggle_comments = &ToggleComments {
4646 advance_downwards: true,
4647 };
4648
4649 // Single cursor on one line -> advance
4650 // Cursor moves horizontally 3 characters as well on non-blank line
4651 cx.set_state(indoc!(
4652 "fn a() {
4653 ˇdog();
4654 cat();
4655 }"
4656 ));
4657 cx.update_editor(|editor, cx| {
4658 editor.toggle_comments(toggle_comments, cx);
4659 });
4660 cx.assert_editor_state(indoc!(
4661 "fn a() {
4662 // dog();
4663 catˇ();
4664 }"
4665 ));
4666
4667 // Single selection on one line -> don't advance
4668 cx.set_state(indoc!(
4669 "fn a() {
4670 «dog()ˇ»;
4671 cat();
4672 }"
4673 ));
4674 cx.update_editor(|editor, cx| {
4675 editor.toggle_comments(toggle_comments, cx);
4676 });
4677 cx.assert_editor_state(indoc!(
4678 "fn a() {
4679 // «dog()ˇ»;
4680 cat();
4681 }"
4682 ));
4683
4684 // Multiple cursors on one line -> advance
4685 cx.set_state(indoc!(
4686 "fn a() {
4687 ˇdˇog();
4688 cat();
4689 }"
4690 ));
4691 cx.update_editor(|editor, cx| {
4692 editor.toggle_comments(toggle_comments, cx);
4693 });
4694 cx.assert_editor_state(indoc!(
4695 "fn a() {
4696 // dog();
4697 catˇ(ˇ);
4698 }"
4699 ));
4700
4701 // Multiple cursors on one line, with selection -> don't advance
4702 cx.set_state(indoc!(
4703 "fn a() {
4704 ˇdˇog«()ˇ»;
4705 cat();
4706 }"
4707 ));
4708 cx.update_editor(|editor, cx| {
4709 editor.toggle_comments(toggle_comments, cx);
4710 });
4711 cx.assert_editor_state(indoc!(
4712 "fn a() {
4713 // ˇdˇog«()ˇ»;
4714 cat();
4715 }"
4716 ));
4717
4718 // Single cursor on one line -> advance
4719 // Cursor moves to column 0 on blank line
4720 cx.set_state(indoc!(
4721 "fn a() {
4722 ˇdog();
4723
4724 cat();
4725 }"
4726 ));
4727 cx.update_editor(|editor, cx| {
4728 editor.toggle_comments(toggle_comments, cx);
4729 });
4730 cx.assert_editor_state(indoc!(
4731 "fn a() {
4732 // dog();
4733 ˇ
4734 cat();
4735 }"
4736 ));
4737
4738 // Single cursor on one line -> advance
4739 // Cursor starts and ends at column 0
4740 cx.set_state(indoc!(
4741 "fn a() {
4742 ˇ dog();
4743 cat();
4744 }"
4745 ));
4746 cx.update_editor(|editor, cx| {
4747 editor.toggle_comments(toggle_comments, cx);
4748 });
4749 cx.assert_editor_state(indoc!(
4750 "fn a() {
4751 // dog();
4752 ˇ cat();
4753 }"
4754 ));
4755}
4756
4757#[gpui::test]
4758async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
4759 let mut cx = EditorTestContext::new(cx);
4760
4761 let html_language = Arc::new(
4762 Language::new(
4763 LanguageConfig {
4764 name: "HTML".into(),
4765 block_comment: Some(("<!-- ".into(), " -->".into())),
4766 ..Default::default()
4767 },
4768 Some(tree_sitter_html::language()),
4769 )
4770 .with_injection_query(
4771 r#"
4772 (script_element
4773 (raw_text) @content
4774 (#set! "language" "javascript"))
4775 "#,
4776 )
4777 .unwrap(),
4778 );
4779
4780 let javascript_language = Arc::new(Language::new(
4781 LanguageConfig {
4782 name: "JavaScript".into(),
4783 line_comment: Some("// ".into()),
4784 ..Default::default()
4785 },
4786 Some(tree_sitter_javascript::language()),
4787 ));
4788
4789 let registry = Arc::new(LanguageRegistry::test());
4790 registry.add(html_language.clone());
4791 registry.add(javascript_language.clone());
4792
4793 cx.update_buffer(|buffer, cx| {
4794 buffer.set_language_registry(registry);
4795 buffer.set_language(Some(html_language), cx);
4796 });
4797
4798 // Toggle comments for empty selections
4799 cx.set_state(
4800 &r#"
4801 <p>A</p>ˇ
4802 <p>B</p>ˇ
4803 <p>C</p>ˇ
4804 "#
4805 .unindent(),
4806 );
4807 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
4808 cx.assert_editor_state(
4809 &r#"
4810 <!-- <p>A</p>ˇ -->
4811 <!-- <p>B</p>ˇ -->
4812 <!-- <p>C</p>ˇ -->
4813 "#
4814 .unindent(),
4815 );
4816 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
4817 cx.assert_editor_state(
4818 &r#"
4819 <p>A</p>ˇ
4820 <p>B</p>ˇ
4821 <p>C</p>ˇ
4822 "#
4823 .unindent(),
4824 );
4825
4826 // Toggle comments for mixture of empty and non-empty selections, where
4827 // multiple selections occupy a given line.
4828 cx.set_state(
4829 &r#"
4830 <p>A«</p>
4831 <p>ˇ»B</p>ˇ
4832 <p>C«</p>
4833 <p>ˇ»D</p>ˇ
4834 "#
4835 .unindent(),
4836 );
4837
4838 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
4839 cx.assert_editor_state(
4840 &r#"
4841 <!-- <p>A«</p>
4842 <p>ˇ»B</p>ˇ -->
4843 <!-- <p>C«</p>
4844 <p>ˇ»D</p>ˇ -->
4845 "#
4846 .unindent(),
4847 );
4848 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
4849 cx.assert_editor_state(
4850 &r#"
4851 <p>A«</p>
4852 <p>ˇ»B</p>ˇ
4853 <p>C«</p>
4854 <p>ˇ»D</p>ˇ
4855 "#
4856 .unindent(),
4857 );
4858
4859 // Toggle comments when different languages are active for different
4860 // selections.
4861 cx.set_state(
4862 &r#"
4863 ˇ<script>
4864 ˇvar x = new Y();
4865 ˇ</script>
4866 "#
4867 .unindent(),
4868 );
4869 cx.foreground().run_until_parked();
4870 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
4871 cx.assert_editor_state(
4872 &r#"
4873 <!-- ˇ<script> -->
4874 // ˇvar x = new Y();
4875 <!-- ˇ</script> -->
4876 "#
4877 .unindent(),
4878 );
4879}
4880
4881#[gpui::test]
4882fn test_editing_disjoint_excerpts(cx: &mut gpui::MutableAppContext) {
4883 cx.set_global(Settings::test(cx));
4884 let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
4885 let multibuffer = cx.add_model(|cx| {
4886 let mut multibuffer = MultiBuffer::new(0);
4887 multibuffer.push_excerpts(
4888 buffer.clone(),
4889 [
4890 ExcerptRange {
4891 context: Point::new(0, 0)..Point::new(0, 4),
4892 primary: None,
4893 },
4894 ExcerptRange {
4895 context: Point::new(1, 0)..Point::new(1, 4),
4896 primary: None,
4897 },
4898 ],
4899 cx,
4900 );
4901 multibuffer
4902 });
4903
4904 assert_eq!(multibuffer.read(cx).read(cx).text(), "aaaa\nbbbb");
4905
4906 let (_, view) = cx.add_window(Default::default(), |cx| build_editor(multibuffer, cx));
4907 view.update(cx, |view, cx| {
4908 assert_eq!(view.text(cx), "aaaa\nbbbb");
4909 view.change_selections(None, cx, |s| {
4910 s.select_ranges([
4911 Point::new(0, 0)..Point::new(0, 0),
4912 Point::new(1, 0)..Point::new(1, 0),
4913 ])
4914 });
4915
4916 view.handle_input("X", cx);
4917 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
4918 assert_eq!(
4919 view.selections.ranges(cx),
4920 [
4921 Point::new(0, 1)..Point::new(0, 1),
4922 Point::new(1, 1)..Point::new(1, 1),
4923 ]
4924 )
4925 });
4926}
4927
4928#[gpui::test]
4929fn test_editing_overlapping_excerpts(cx: &mut gpui::MutableAppContext) {
4930 cx.set_global(Settings::test(cx));
4931 let markers = vec![('[', ']').into(), ('(', ')').into()];
4932 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
4933 indoc! {"
4934 [aaaa
4935 (bbbb]
4936 cccc)",
4937 },
4938 markers.clone(),
4939 );
4940 let excerpt_ranges = markers.into_iter().map(|marker| {
4941 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
4942 ExcerptRange {
4943 context,
4944 primary: None,
4945 }
4946 });
4947 let buffer = cx.add_model(|cx| Buffer::new(0, initial_text, cx));
4948 let multibuffer = cx.add_model(|cx| {
4949 let mut multibuffer = MultiBuffer::new(0);
4950 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
4951 multibuffer
4952 });
4953
4954 let (_, view) = cx.add_window(Default::default(), |cx| build_editor(multibuffer, cx));
4955 view.update(cx, |view, cx| {
4956 let (expected_text, selection_ranges) = marked_text_ranges(
4957 indoc! {"
4958 aaaa
4959 bˇbbb
4960 bˇbbˇb
4961 cccc"
4962 },
4963 true,
4964 );
4965 assert_eq!(view.text(cx), expected_text);
4966 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
4967
4968 view.handle_input("X", cx);
4969
4970 let (expected_text, expected_selections) = marked_text_ranges(
4971 indoc! {"
4972 aaaa
4973 bXˇbbXb
4974 bXˇbbXˇb
4975 cccc"
4976 },
4977 false,
4978 );
4979 assert_eq!(view.text(cx), expected_text);
4980 assert_eq!(view.selections.ranges(cx), expected_selections);
4981
4982 view.newline(&Newline, cx);
4983 let (expected_text, expected_selections) = marked_text_ranges(
4984 indoc! {"
4985 aaaa
4986 bX
4987 ˇbbX
4988 b
4989 bX
4990 ˇbbX
4991 ˇb
4992 cccc"
4993 },
4994 false,
4995 );
4996 assert_eq!(view.text(cx), expected_text);
4997 assert_eq!(view.selections.ranges(cx), expected_selections);
4998 });
4999}
5000
5001#[gpui::test]
5002fn test_refresh_selections(cx: &mut gpui::MutableAppContext) {
5003 cx.set_global(Settings::test(cx));
5004 let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
5005 let mut excerpt1_id = None;
5006 let multibuffer = cx.add_model(|cx| {
5007 let mut multibuffer = MultiBuffer::new(0);
5008 excerpt1_id = multibuffer
5009 .push_excerpts(
5010 buffer.clone(),
5011 [
5012 ExcerptRange {
5013 context: Point::new(0, 0)..Point::new(1, 4),
5014 primary: None,
5015 },
5016 ExcerptRange {
5017 context: Point::new(1, 0)..Point::new(2, 4),
5018 primary: None,
5019 },
5020 ],
5021 cx,
5022 )
5023 .into_iter()
5024 .next();
5025 multibuffer
5026 });
5027 assert_eq!(
5028 multibuffer.read(cx).read(cx).text(),
5029 "aaaa\nbbbb\nbbbb\ncccc"
5030 );
5031 let (_, editor) = cx.add_window(Default::default(), |cx| {
5032 let mut editor = build_editor(multibuffer.clone(), cx);
5033 let snapshot = editor.snapshot(cx);
5034 editor.change_selections(None, cx, |s| {
5035 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
5036 });
5037 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
5038 assert_eq!(
5039 editor.selections.ranges(cx),
5040 [
5041 Point::new(1, 3)..Point::new(1, 3),
5042 Point::new(2, 1)..Point::new(2, 1),
5043 ]
5044 );
5045 editor
5046 });
5047
5048 // Refreshing selections is a no-op when excerpts haven't changed.
5049 editor.update(cx, |editor, cx| {
5050 editor.change_selections(None, cx, |s| s.refresh());
5051 assert_eq!(
5052 editor.selections.ranges(cx),
5053 [
5054 Point::new(1, 3)..Point::new(1, 3),
5055 Point::new(2, 1)..Point::new(2, 1),
5056 ]
5057 );
5058 });
5059
5060 multibuffer.update(cx, |multibuffer, cx| {
5061 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
5062 });
5063 editor.update(cx, |editor, cx| {
5064 // Removing an excerpt causes the first selection to become degenerate.
5065 assert_eq!(
5066 editor.selections.ranges(cx),
5067 [
5068 Point::new(0, 0)..Point::new(0, 0),
5069 Point::new(0, 1)..Point::new(0, 1)
5070 ]
5071 );
5072
5073 // Refreshing selections will relocate the first selection to the original buffer
5074 // location.
5075 editor.change_selections(None, cx, |s| s.refresh());
5076 assert_eq!(
5077 editor.selections.ranges(cx),
5078 [
5079 Point::new(0, 1)..Point::new(0, 1),
5080 Point::new(0, 3)..Point::new(0, 3)
5081 ]
5082 );
5083 assert!(editor.selections.pending_anchor().is_some());
5084 });
5085}
5086
5087#[gpui::test]
5088fn test_refresh_selections_while_selecting_with_mouse(cx: &mut gpui::MutableAppContext) {
5089 cx.set_global(Settings::test(cx));
5090 let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
5091 let mut excerpt1_id = None;
5092 let multibuffer = cx.add_model(|cx| {
5093 let mut multibuffer = MultiBuffer::new(0);
5094 excerpt1_id = multibuffer
5095 .push_excerpts(
5096 buffer.clone(),
5097 [
5098 ExcerptRange {
5099 context: Point::new(0, 0)..Point::new(1, 4),
5100 primary: None,
5101 },
5102 ExcerptRange {
5103 context: Point::new(1, 0)..Point::new(2, 4),
5104 primary: None,
5105 },
5106 ],
5107 cx,
5108 )
5109 .into_iter()
5110 .next();
5111 multibuffer
5112 });
5113 assert_eq!(
5114 multibuffer.read(cx).read(cx).text(),
5115 "aaaa\nbbbb\nbbbb\ncccc"
5116 );
5117 let (_, editor) = cx.add_window(Default::default(), |cx| {
5118 let mut editor = build_editor(multibuffer.clone(), cx);
5119 let snapshot = editor.snapshot(cx);
5120 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
5121 assert_eq!(
5122 editor.selections.ranges(cx),
5123 [Point::new(1, 3)..Point::new(1, 3)]
5124 );
5125 editor
5126 });
5127
5128 multibuffer.update(cx, |multibuffer, cx| {
5129 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
5130 });
5131 editor.update(cx, |editor, cx| {
5132 assert_eq!(
5133 editor.selections.ranges(cx),
5134 [Point::new(0, 0)..Point::new(0, 0)]
5135 );
5136
5137 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
5138 editor.change_selections(None, cx, |s| s.refresh());
5139 assert_eq!(
5140 editor.selections.ranges(cx),
5141 [Point::new(0, 3)..Point::new(0, 3)]
5142 );
5143 assert!(editor.selections.pending_anchor().is_some());
5144 });
5145}
5146
5147#[gpui::test]
5148async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
5149 cx.update(|cx| cx.set_global(Settings::test(cx)));
5150 let language = Arc::new(
5151 Language::new(
5152 LanguageConfig {
5153 brackets: BracketPairConfig {
5154 pairs: vec![
5155 BracketPair {
5156 start: "{".to_string(),
5157 end: "}".to_string(),
5158 close: true,
5159 newline: true,
5160 },
5161 BracketPair {
5162 start: "/* ".to_string(),
5163 end: " */".to_string(),
5164 close: true,
5165 newline: true,
5166 },
5167 ],
5168 ..Default::default()
5169 },
5170 ..Default::default()
5171 },
5172 Some(tree_sitter_rust::language()),
5173 )
5174 .with_indents_query("")
5175 .unwrap(),
5176 );
5177
5178 let text = concat!(
5179 "{ }\n", //
5180 " x\n", //
5181 " /* */\n", //
5182 "x\n", //
5183 "{{} }\n", //
5184 );
5185
5186 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
5187 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
5188 let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
5189 view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5190 .await;
5191
5192 view.update(cx, |view, cx| {
5193 view.change_selections(None, cx, |s| {
5194 s.select_display_ranges([
5195 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3),
5196 DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
5197 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
5198 ])
5199 });
5200 view.newline(&Newline, cx);
5201
5202 assert_eq!(
5203 view.buffer().read(cx).read(cx).text(),
5204 concat!(
5205 "{ \n", // Suppress rustfmt
5206 "\n", //
5207 "}\n", //
5208 " x\n", //
5209 " /* \n", //
5210 " \n", //
5211 " */\n", //
5212 "x\n", //
5213 "{{} \n", //
5214 "}\n", //
5215 )
5216 );
5217 });
5218}
5219
5220#[gpui::test]
5221fn test_highlighted_ranges(cx: &mut gpui::MutableAppContext) {
5222 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
5223
5224 cx.set_global(Settings::test(cx));
5225 let (_, editor) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
5226
5227 editor.update(cx, |editor, cx| {
5228 struct Type1;
5229 struct Type2;
5230
5231 let buffer = buffer.read(cx).snapshot(cx);
5232
5233 let anchor_range =
5234 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
5235
5236 editor.highlight_background::<Type1>(
5237 vec![
5238 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
5239 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
5240 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
5241 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
5242 ],
5243 |_| Color::red(),
5244 cx,
5245 );
5246 editor.highlight_background::<Type2>(
5247 vec![
5248 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
5249 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
5250 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
5251 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
5252 ],
5253 |_| Color::green(),
5254 cx,
5255 );
5256
5257 let snapshot = editor.snapshot(cx);
5258 let mut highlighted_ranges = editor.background_highlights_in_range(
5259 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
5260 &snapshot,
5261 cx.global::<Settings>().theme.as_ref(),
5262 );
5263 // Enforce a consistent ordering based on color without relying on the ordering of the
5264 // highlight's `TypeId` which is non-deterministic.
5265 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
5266 assert_eq!(
5267 highlighted_ranges,
5268 &[
5269 (
5270 DisplayPoint::new(3, 2)..DisplayPoint::new(3, 5),
5271 Color::green(),
5272 ),
5273 (
5274 DisplayPoint::new(5, 3)..DisplayPoint::new(5, 6),
5275 Color::green(),
5276 ),
5277 (
5278 DisplayPoint::new(4, 2)..DisplayPoint::new(4, 4),
5279 Color::red(),
5280 ),
5281 (
5282 DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
5283 Color::red(),
5284 ),
5285 ]
5286 );
5287 assert_eq!(
5288 editor.background_highlights_in_range(
5289 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
5290 &snapshot,
5291 cx.global::<Settings>().theme.as_ref(),
5292 ),
5293 &[(
5294 DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
5295 Color::red(),
5296 )]
5297 );
5298 });
5299}
5300
5301#[gpui::test]
5302async fn test_following(cx: &mut gpui::TestAppContext) {
5303 Settings::test_async(cx);
5304 let fs = FakeFs::new(cx.background());
5305 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
5306
5307 let buffer = project.update(cx, |project, cx| {
5308 let buffer = project
5309 .create_buffer(&sample_text(16, 8, 'a'), None, cx)
5310 .unwrap();
5311 cx.add_model(|cx| MultiBuffer::singleton(buffer, cx))
5312 });
5313 let (_, leader) = cx.add_window(|cx| build_editor(buffer.clone(), cx));
5314 let (_, follower) = cx.update(|cx| {
5315 cx.add_window(
5316 WindowOptions {
5317 bounds: WindowBounds::Fixed(RectF::from_points(vec2f(0., 0.), vec2f(10., 80.))),
5318 ..Default::default()
5319 },
5320 |cx| build_editor(buffer.clone(), cx),
5321 )
5322 });
5323
5324 let is_still_following = Rc::new(RefCell::new(true));
5325 let pending_update = Rc::new(RefCell::new(None));
5326 follower.update(cx, {
5327 let update = pending_update.clone();
5328 let is_still_following = is_still_following.clone();
5329 |_, cx| {
5330 cx.subscribe(&leader, move |_, leader, event, cx| {
5331 leader
5332 .read(cx)
5333 .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx);
5334 })
5335 .detach();
5336
5337 cx.subscribe(&follower, move |_, _, event, cx| {
5338 if Editor::should_unfollow_on_event(event, cx) {
5339 *is_still_following.borrow_mut() = false;
5340 }
5341 })
5342 .detach();
5343 }
5344 });
5345
5346 // Update the selections only
5347 leader.update(cx, |leader, cx| {
5348 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
5349 });
5350 follower
5351 .update(cx, |follower, cx| {
5352 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
5353 })
5354 .await
5355 .unwrap();
5356 follower.read_with(cx, |follower, cx| {
5357 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
5358 });
5359 assert_eq!(*is_still_following.borrow(), true);
5360
5361 // Update the scroll position only
5362 leader.update(cx, |leader, cx| {
5363 leader.set_scroll_position(vec2f(1.5, 3.5), cx);
5364 });
5365 follower
5366 .update(cx, |follower, cx| {
5367 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
5368 })
5369 .await
5370 .unwrap();
5371 assert_eq!(
5372 follower.update(cx, |follower, cx| follower.scroll_position(cx)),
5373 vec2f(1.5, 3.5)
5374 );
5375 assert_eq!(*is_still_following.borrow(), true);
5376
5377 // Update the selections and scroll position. The follower's scroll position is updated
5378 // via autoscroll, not via the leader's exact scroll position.
5379 leader.update(cx, |leader, cx| {
5380 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
5381 leader.request_autoscroll(Autoscroll::newest(), cx);
5382 leader.set_scroll_position(vec2f(1.5, 3.5), cx);
5383 });
5384 follower
5385 .update(cx, |follower, cx| {
5386 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
5387 })
5388 .await
5389 .unwrap();
5390 follower.update(cx, |follower, cx| {
5391 assert_eq!(follower.scroll_position(cx), vec2f(1.5, 0.0));
5392 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
5393 });
5394 assert_eq!(*is_still_following.borrow(), true);
5395
5396 // Creating a pending selection that precedes another selection
5397 leader.update(cx, |leader, cx| {
5398 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
5399 leader.begin_selection(DisplayPoint::new(0, 0), true, 1, cx);
5400 });
5401 follower
5402 .update(cx, |follower, cx| {
5403 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
5404 })
5405 .await
5406 .unwrap();
5407 follower.read_with(cx, |follower, cx| {
5408 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
5409 });
5410 assert_eq!(*is_still_following.borrow(), true);
5411
5412 // Extend the pending selection so that it surrounds another selection
5413 leader.update(cx, |leader, cx| {
5414 leader.extend_selection(DisplayPoint::new(0, 2), 1, cx);
5415 });
5416 follower
5417 .update(cx, |follower, cx| {
5418 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
5419 })
5420 .await
5421 .unwrap();
5422 follower.read_with(cx, |follower, cx| {
5423 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
5424 });
5425
5426 // Scrolling locally breaks the follow
5427 follower.update(cx, |follower, cx| {
5428 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
5429 follower.set_scroll_anchor(
5430 ScrollAnchor {
5431 top_anchor,
5432 offset: vec2f(0.0, 0.5),
5433 },
5434 cx,
5435 );
5436 });
5437 assert_eq!(*is_still_following.borrow(), false);
5438}
5439
5440#[gpui::test]
5441async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
5442 Settings::test_async(cx);
5443 let fs = FakeFs::new(cx.background());
5444 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
5445 let (_, pane) = cx.add_window(|cx| Pane::new(None, cx));
5446
5447 let leader = pane.update(cx, |_, cx| {
5448 let multibuffer = cx.add_model(|_| MultiBuffer::new(0));
5449 cx.add_view(|cx| build_editor(multibuffer.clone(), cx))
5450 });
5451
5452 // Start following the editor when it has no excerpts.
5453 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
5454 let follower_1 = cx
5455 .update(|cx| {
5456 Editor::from_state_proto(
5457 pane.clone(),
5458 project.clone(),
5459 ViewId {
5460 creator: Default::default(),
5461 id: 0,
5462 },
5463 &mut state_message,
5464 cx,
5465 )
5466 })
5467 .unwrap()
5468 .await
5469 .unwrap();
5470
5471 let update_message = Rc::new(RefCell::new(None));
5472 follower_1.update(cx, {
5473 let update = update_message.clone();
5474 |_, cx| {
5475 cx.subscribe(&leader, move |_, leader, event, cx| {
5476 leader
5477 .read(cx)
5478 .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx);
5479 })
5480 .detach();
5481 }
5482 });
5483
5484 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
5485 (
5486 project
5487 .create_buffer("abc\ndef\nghi\njkl\n", None, cx)
5488 .unwrap(),
5489 project
5490 .create_buffer("mno\npqr\nstu\nvwx\n", None, cx)
5491 .unwrap(),
5492 )
5493 });
5494
5495 // Insert some excerpts.
5496 leader.update(cx, |leader, cx| {
5497 leader.buffer.update(cx, |multibuffer, cx| {
5498 let excerpt_ids = multibuffer.push_excerpts(
5499 buffer_1.clone(),
5500 [
5501 ExcerptRange {
5502 context: 1..6,
5503 primary: None,
5504 },
5505 ExcerptRange {
5506 context: 12..15,
5507 primary: None,
5508 },
5509 ExcerptRange {
5510 context: 0..3,
5511 primary: None,
5512 },
5513 ],
5514 cx,
5515 );
5516 multibuffer.insert_excerpts_after(
5517 excerpt_ids[0],
5518 buffer_2.clone(),
5519 [
5520 ExcerptRange {
5521 context: 8..12,
5522 primary: None,
5523 },
5524 ExcerptRange {
5525 context: 0..6,
5526 primary: None,
5527 },
5528 ],
5529 cx,
5530 );
5531 });
5532 });
5533
5534 // Apply the update of adding the excerpts.
5535 follower_1
5536 .update(cx, |follower, cx| {
5537 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
5538 })
5539 .await
5540 .unwrap();
5541 assert_eq!(
5542 follower_1.read_with(cx, Editor::text),
5543 leader.read_with(cx, Editor::text)
5544 );
5545 update_message.borrow_mut().take();
5546
5547 // Start following separately after it already has excerpts.
5548 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
5549 let follower_2 = cx
5550 .update(|cx| {
5551 Editor::from_state_proto(
5552 pane.clone(),
5553 project.clone(),
5554 ViewId {
5555 creator: Default::default(),
5556 id: 0,
5557 },
5558 &mut state_message,
5559 cx,
5560 )
5561 })
5562 .unwrap()
5563 .await
5564 .unwrap();
5565 assert_eq!(
5566 follower_2.read_with(cx, Editor::text),
5567 leader.read_with(cx, Editor::text)
5568 );
5569
5570 // Remove some excerpts.
5571 leader.update(cx, |leader, cx| {
5572 leader.buffer.update(cx, |multibuffer, cx| {
5573 let excerpt_ids = multibuffer.excerpt_ids();
5574 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
5575 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
5576 });
5577 });
5578
5579 // Apply the update of removing the excerpts.
5580 follower_1
5581 .update(cx, |follower, cx| {
5582 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
5583 })
5584 .await
5585 .unwrap();
5586 follower_2
5587 .update(cx, |follower, cx| {
5588 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
5589 })
5590 .await
5591 .unwrap();
5592 update_message.borrow_mut().take();
5593 assert_eq!(
5594 follower_1.read_with(cx, Editor::text),
5595 leader.read_with(cx, Editor::text)
5596 );
5597}
5598
5599#[test]
5600fn test_combine_syntax_and_fuzzy_match_highlights() {
5601 let string = "abcdefghijklmnop";
5602 let syntax_ranges = [
5603 (
5604 0..3,
5605 HighlightStyle {
5606 color: Some(Color::red()),
5607 ..Default::default()
5608 },
5609 ),
5610 (
5611 4..8,
5612 HighlightStyle {
5613 color: Some(Color::green()),
5614 ..Default::default()
5615 },
5616 ),
5617 ];
5618 let match_indices = [4, 6, 7, 8];
5619 assert_eq!(
5620 combine_syntax_and_fuzzy_match_highlights(
5621 string,
5622 Default::default(),
5623 syntax_ranges.into_iter(),
5624 &match_indices,
5625 ),
5626 &[
5627 (
5628 0..3,
5629 HighlightStyle {
5630 color: Some(Color::red()),
5631 ..Default::default()
5632 },
5633 ),
5634 (
5635 4..5,
5636 HighlightStyle {
5637 color: Some(Color::green()),
5638 weight: Some(fonts::Weight::BOLD),
5639 ..Default::default()
5640 },
5641 ),
5642 (
5643 5..6,
5644 HighlightStyle {
5645 color: Some(Color::green()),
5646 ..Default::default()
5647 },
5648 ),
5649 (
5650 6..8,
5651 HighlightStyle {
5652 color: Some(Color::green()),
5653 weight: Some(fonts::Weight::BOLD),
5654 ..Default::default()
5655 },
5656 ),
5657 (
5658 8..9,
5659 HighlightStyle {
5660 weight: Some(fonts::Weight::BOLD),
5661 ..Default::default()
5662 },
5663 ),
5664 ]
5665 );
5666}
5667
5668#[gpui::test]
5669async fn go_to_hunk(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
5670 let mut cx = EditorTestContext::new(cx);
5671
5672 let diff_base = r#"
5673 use some::mod;
5674
5675 const A: u32 = 42;
5676
5677 fn main() {
5678 println!("hello");
5679
5680 println!("world");
5681 }
5682 "#
5683 .unindent();
5684
5685 // Edits are modified, removed, modified, added
5686 cx.set_state(
5687 &r#"
5688 use some::modified;
5689
5690 ˇ
5691 fn main() {
5692 println!("hello there");
5693
5694 println!("around the");
5695 println!("world");
5696 }
5697 "#
5698 .unindent(),
5699 );
5700
5701 cx.set_diff_base(Some(&diff_base));
5702 deterministic.run_until_parked();
5703
5704 cx.update_editor(|editor, cx| {
5705 //Wrap around the bottom of the buffer
5706 for _ in 0..3 {
5707 editor.go_to_hunk(&GoToHunk, cx);
5708 }
5709 });
5710
5711 cx.assert_editor_state(
5712 &r#"
5713 ˇuse some::modified;
5714
5715
5716 fn main() {
5717 println!("hello there");
5718
5719 println!("around the");
5720 println!("world");
5721 }
5722 "#
5723 .unindent(),
5724 );
5725
5726 cx.update_editor(|editor, cx| {
5727 //Wrap around the top of the buffer
5728 for _ in 0..2 {
5729 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
5730 }
5731 });
5732
5733 cx.assert_editor_state(
5734 &r#"
5735 use some::modified;
5736
5737
5738 fn main() {
5739 ˇ println!("hello there");
5740
5741 println!("around the");
5742 println!("world");
5743 }
5744 "#
5745 .unindent(),
5746 );
5747
5748 cx.update_editor(|editor, cx| {
5749 editor.fold(&Fold, cx);
5750
5751 //Make sure that the fold only gets one hunk
5752 for _ in 0..4 {
5753 editor.go_to_hunk(&GoToHunk, cx);
5754 }
5755 });
5756
5757 cx.assert_editor_state(
5758 &r#"
5759 ˇuse some::modified;
5760
5761
5762 fn main() {
5763 println!("hello there");
5764
5765 println!("around the");
5766 println!("world");
5767 }
5768 "#
5769 .unindent(),
5770 );
5771}
5772
5773#[test]
5774fn test_split_words() {
5775 fn split<'a>(text: &'a str) -> Vec<&'a str> {
5776 split_words(text).collect()
5777 }
5778
5779 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
5780 assert_eq!(split("hello_world"), &["hello_", "world"]);
5781 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
5782 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
5783 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
5784 assert_eq!(split("helloworld"), &["helloworld"]);
5785}
5786
5787#[gpui::test]
5788async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
5789 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
5790 let mut assert = |before, after| {
5791 let _state_context = cx.set_state(before);
5792 cx.update_editor(|editor, cx| {
5793 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
5794 });
5795 cx.assert_editor_state(after);
5796 };
5797
5798 // Outside bracket jumps to outside of matching bracket
5799 assert("console.logˇ(var);", "console.log(var)ˇ;");
5800 assert("console.log(var)ˇ;", "console.logˇ(var);");
5801
5802 // Inside bracket jumps to inside of matching bracket
5803 assert("console.log(ˇvar);", "console.log(varˇ);");
5804 assert("console.log(varˇ);", "console.log(ˇvar);");
5805
5806 // When outside a bracket and inside, favor jumping to the inside bracket
5807 assert(
5808 "console.log('foo', [1, 2, 3]ˇ);",
5809 "console.log(ˇ'foo', [1, 2, 3]);",
5810 );
5811 assert(
5812 "console.log(ˇ'foo', [1, 2, 3]);",
5813 "console.log('foo', [1, 2, 3]ˇ);",
5814 );
5815
5816 // Bias forward if two options are equally likely
5817 assert(
5818 "let result = curried_fun()ˇ();",
5819 "let result = curried_fun()()ˇ;",
5820 );
5821
5822 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
5823 assert(
5824 indoc! {"
5825 function test() {
5826 console.log('test')ˇ
5827 }"},
5828 indoc! {"
5829 function test() {
5830 console.logˇ('test')
5831 }"},
5832 );
5833}
5834
5835fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
5836 let point = DisplayPoint::new(row as u32, column as u32);
5837 point..point
5838}
5839
5840fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
5841 let (text, ranges) = marked_text_ranges(marked_text, true);
5842 assert_eq!(view.text(cx), text);
5843 assert_eq!(
5844 view.selections.ranges(cx),
5845 ranges,
5846 "Assert selections are {}",
5847 marked_text
5848 );
5849}