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