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